diff options
Diffstat (limited to 'src/de/danoeh')
92 files changed, 5946 insertions, 5228 deletions
diff --git a/src/de/danoeh/antennapod/AppConfig.java b/src/de/danoeh/antennapod/AppConfig.java index 98a231330..1f55e73a6 100644 --- a/src/de/danoeh/antennapod/AppConfig.java +++ b/src/de/danoeh/antennapod/AppConfig.java @@ -2,5 +2,5 @@ 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.8.3"; + public final static String USER_AGENT = "AntennaPod/0.9.9.0"; } diff --git a/src/de/danoeh/antennapod/activity/AddFeedActivity.java b/src/de/danoeh/antennapod/activity/AddFeedActivity.java deleted file mode 100644 index a77689ddb..000000000 --- a/src/de/danoeh/antennapod/activity/AddFeedActivity.java +++ /dev/null @@ -1,144 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.app.ProgressDialog; -import android.content.Intent; -import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; -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.EditText; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.gpoddernet.GpodnetMainActivity; -import de.danoeh.antennapod.preferences.UserPreferences; -import de.danoeh.antennapod.util.StorageUtils; -import org.apache.commons.lang3.StringUtils; - -/** - * Activity for adding a Feed - */ -public class AddFeedActivity extends ActionBarActivity { - private static final String TAG = "AddFeedActivity"; - - private EditText etxtFeedurl; - private Button butBrowseMiroGuide; - private Button butBrowserGpoddernet; - private Button butOpmlImport; - private Button butConfirm; - - private ProgressDialog progDialog; - - @Override - protected void onCreate(Bundle savedInstanceState) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Was started with Intent " + getIntent().getAction() - + " and Data " + getIntent().getDataString()); - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - StorageUtils.checkStorageAvailability(this); - setContentView(R.layout.addfeed); - - progDialog = new ProgressDialog(this); - - etxtFeedurl = (EditText) findViewById(R.id.etxtFeedurl); - if (StringUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) { - etxtFeedurl.setText(getIntent().getDataString()); - } - - butBrowseMiroGuide = (Button) findViewById(R.id.butBrowseMiroguide); - butBrowserGpoddernet = (Button) findViewById(R.id.butBrowseGpoddernet); - butOpmlImport = (Button) findViewById(R.id.butOpmlImport); - butConfirm = (Button) findViewById(R.id.butConfirm); - - butBrowseMiroGuide.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - startActivity(new Intent(AddFeedActivity.this, - MiroGuideMainActivity.class)); - } - }); - butBrowserGpoddernet.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - startActivity(new Intent(AddFeedActivity.this, - GpodnetMainActivity.class)); - } - }); - - butOpmlImport.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - startActivity(new Intent(AddFeedActivity.this, - OpmlImportFromPathActivity.class)); - } - }); - - butConfirm.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(AddFeedActivity.this, DefaultOnlineFeedViewActivity.class); - intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, etxtFeedurl.getText().toString()); - intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, getSupportActionBar().getTitle()); - startActivity(intent); - } - }); - - } - - @Override - protected void onResume() { - super.onResume(); - StorageUtils.checkStorageAvailability(this); - Intent intent = getIntent(); - if (intent.getAction() != null - && intent.getAction().equals(Intent.ACTION_SEND)) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Resuming with ACTION_SEND intent"); - String text = intent.getStringExtra(Intent.EXTRA_TEXT); - if (text != null) { - etxtFeedurl.setText(text); - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "No text was sent"); - } - } - - } - - @Override - protected void onStop() { - super.onStop(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Stopping Activity"); - } - - @Override - protected void onPause() { - super.onPause(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - default: - return false; - } - } - -} diff --git a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java index de989fa46..2ffaae967 100644 --- a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -4,11 +4,15 @@ 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.support.v4.app.ActionBarDrawerToggle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v4.app.ListFragment; +import android.support.v4.widget.DrawerLayout; import android.util.Log; +import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; @@ -18,18 +22,20 @@ import android.widget.ImageView.ScaleType; import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.ChapterListAdapter; +import de.danoeh.antennapod.adapter.NavListAdapter; import de.danoeh.antennapod.asynctask.ImageLoader; import de.danoeh.antennapod.dialog.VariableSpeedDialog; -import de.danoeh.antennapod.feed.Chapter; -import de.danoeh.antennapod.feed.MediaType; -import de.danoeh.antennapod.feed.SimpleChapter; +import de.danoeh.antennapod.feed.*; import de.danoeh.antennapod.fragment.CoverFragment; import de.danoeh.antennapod.fragment.ItemDescriptionFragment; import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.service.playback.PlaybackService; +import de.danoeh.antennapod.storage.DBReader; import de.danoeh.antennapod.util.playback.ExternalMedia; import de.danoeh.antennapod.util.playback.Playable; +import java.util.List; + /** * Activity for playing audio files. */ @@ -44,6 +50,11 @@ public class AudioplayerActivity extends MediaplayerActivity { private static final String PREF_KEY_SELECTED_FRAGMENT_POSITION = "selectedFragmentPosition"; private static final String PREF_PLAYABLE_ID = "playableId"; + private DrawerLayout drawerLayout; + private NavListAdapter navAdapter; + private ListView navList; + private ActionBarDrawerToggle drawerToggle; + private Fragment[] detachedFragments; private CoverFragment coverFragment; @@ -58,7 +69,6 @@ public class AudioplayerActivity extends MediaplayerActivity { private int savedPosition = -1; private TextView txtvTitle; - private TextView txtvFeed; private Button butPlaybackSpeed; private ImageButton butNavLeft; private ImageButton butNavRight; @@ -108,6 +118,8 @@ public class AudioplayerActivity extends MediaplayerActivity { super.onStop(); if (BuildConfig.DEBUG) Log.d(TAG, "onStop"); + cancelLoadTask(); + EventDistributor.getInstance().unregister(contentUpdate); } @@ -142,6 +154,7 @@ public class AudioplayerActivity extends MediaplayerActivity { @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + drawerToggle.onConfigurationChanged(newConfig); } @Override @@ -149,6 +162,7 @@ public class AudioplayerActivity extends MediaplayerActivity { // super.onSaveInstanceState(outState); would cause crash if (BuildConfig.DEBUG) Log.d(TAG, "onSaveInstanceState"); + } @Override @@ -193,7 +207,8 @@ public class AudioplayerActivity extends MediaplayerActivity { if (BuildConfig.DEBUG) Log.d(TAG, "Couldn't restore from preferences: savedPosition was -1 or saved identifier and playable identifier didn't match.\nsavedPosition: " - + savedPosition + ", id: " + playableId); + + savedPosition + ", id: " + playableId + ); } return false; @@ -223,6 +238,8 @@ public class AudioplayerActivity extends MediaplayerActivity { switchToFragment(savedPosition); } + EventDistributor.getInstance().register(contentUpdate); + loadData(); } @Override @@ -233,7 +250,8 @@ public class AudioplayerActivity extends MediaplayerActivity { @Override protected void onAwaitingVideoSurface() { - if (BuildConfig.DEBUG) Log.d(TAG, "onAwaitingVideoSurface was called in audio player -> switching to video player"); + if (BuildConfig.DEBUG) + Log.d(TAG, "onAwaitingVideoSurface was called in audio player -> switching to video player"); startActivity(new Intent(this, VideoplayerActivity.class)); } @@ -297,7 +315,8 @@ public class AudioplayerActivity extends MediaplayerActivity { }; chapterFragment.setListAdapter(new ChapterListAdapter( AudioplayerActivity.this, 0, media - .getChapters(), media)); + .getChapters(), media + )); } currentlyShownFragment = chapterFragment; break; @@ -323,8 +342,8 @@ public class AudioplayerActivity extends MediaplayerActivity { private void updateNavButtonDrawable() { - final int[] buttonTexts = new int[] {R.string.show_shownotes_label, - R.string.show_chapters_label, R.string.show_cover_label}; + final int[] buttonTexts = new int[]{R.string.show_shownotes_label, + R.string.show_chapters_label, R.string.show_cover_label}; final TypedArray drawables = obtainStyledAttributes(new int[]{ R.attr.navigation_shownotes, R.attr.navigation_chapters}); @@ -382,12 +401,53 @@ public class AudioplayerActivity extends MediaplayerActivity { protected void setupGUI() { super.setupGUI(); resetFragmentView(); + drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + navList = (ListView) findViewById(R.id.nav_list); txtvTitle = (TextView) findViewById(R.id.txtvTitle); - txtvFeed = (TextView) findViewById(R.id.txtvFeed); butNavLeft = (ImageButton) findViewById(R.id.butNavLeft); butNavRight = (ImageButton) findViewById(R.id.butNavRight); butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed); + TypedArray typedArray = obtainStyledAttributes(new int[]{R.attr.nav_drawer_toggle}); + drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, typedArray.getResourceId(0, 0), R.string.drawer_open, R.string.drawer_close) { + String currentTitle = getSupportActionBar().getTitle().toString(); + + @Override + public void onDrawerOpened(View drawerView) { + super.onDrawerOpened(drawerView); + currentTitle = getSupportActionBar().getTitle().toString(); + getSupportActionBar().setTitle(R.string.app_name); + supportInvalidateOptionsMenu(); + } + + @Override + public void onDrawerClosed(View drawerView) { + super.onDrawerClosed(drawerView); + getSupportActionBar().setTitle(currentTitle); + supportInvalidateOptionsMenu(); + } + }; + typedArray.recycle(); + drawerToggle.setDrawerIndicatorEnabled(false); + + 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) { + int relPos = (viewType == NavListAdapter.VIEW_TYPE_NAV) ? position : position - NavListAdapter.SUBSCRIPTION_OFFSET; + Intent intent = new Intent(AudioplayerActivity.this, MainActivity.class); + intent.putExtra(MainActivity.EXTRA_NAV_TYPE, viewType); + intent.putExtra(MainActivity.EXTRA_NAV_INDEX, relPos); + startActivity(intent); + } + drawerLayout.closeDrawer(navList); + } + }); + drawerToggle.syncState(); + butNavLeft.setOnClickListener(new OnClickListener() { @Override @@ -466,11 +526,7 @@ public class AudioplayerActivity extends MediaplayerActivity { } private void updateButPlaybackSpeed() { - if (controller == null - || (controller.getCurrentPlaybackSpeedMultiplier() == -1)) { - butPlaybackSpeed.setVisibility(View.GONE); - } else { - butPlaybackSpeed.setVisibility(View.VISIBLE); + if (controller != null && controller.canSetPlaybackSpeed()) { butPlaybackSpeed.setText(UserPreferences.getPlaybackSpeed()); } } @@ -491,11 +547,10 @@ public class AudioplayerActivity extends MediaplayerActivity { return false; } txtvTitle.setText(media.getEpisodeTitle()); - txtvFeed.setText(media.getFeedTitle()); if (media.getChapters() != null) { butNavRight.setVisibility(View.VISIBLE); } else { - butNavRight.setVisibility(View.GONE); + butNavRight.setVisibility(View.INVISIBLE); } @@ -508,6 +563,14 @@ public class AudioplayerActivity extends MediaplayerActivity { ((AudioplayerContentFragment) currentlyShownFragment) .onDataSetChanged(media); } + + if (controller == null + || !controller.canSetPlaybackSpeed()) { + butPlaybackSpeed.setVisibility(View.GONE); + } else { + butPlaybackSpeed.setVisibility(View.VISIBLE); + } + updateButPlaybackSpeed(); return true; } @@ -551,4 +614,78 @@ public class AudioplayerActivity extends MediaplayerActivity { return R.layout.audioplayer_activity; } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (drawerToggle.onOptionsItemSelected(item)) { + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + private List<Feed> feeds; + private AsyncTask<Void, Void, List<Feed>> loadTask; + + private void loadData() { + loadTask = new AsyncTask<Void, Void, List<Feed>>() { + @Override + protected List<Feed> doInBackground(Void... params) { + return DBReader.getFeedList(AudioplayerActivity.this); + } + + @Override + protected void onPostExecute(List<Feed> result) { + super.onPostExecute(result); + feeds = result; + if (navAdapter != null) { + navAdapter.notifyDataSetChanged(); + } + } + }; + loadTask.execute(); + } + + private void cancelLoadTask() { + if (loadTask != null) { + loadTask.cancel(true); + } + } + + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + + @Override + public void update(EventDistributor eventDistributor, Integer arg) { + if ((EventDistributor.FEED_LIST_UPDATE & arg) != 0) { + if (BuildConfig.DEBUG) + Log.d(TAG, "Received contentUpdate Intent."); + loadData(); + } + } + }; + + private final NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() { + @Override + public int getCount() { + if (feeds != null) { + return feeds.size(); + } else { + return 0; + } + } + + @Override + public Feed getItem(int position) { + if (feeds != null && position < feeds.size()) { + return feeds.get(position); + } else { + return null; + } + } + + @Override + public int getSelectedItemIndex() { + return -1; + } + }; } diff --git a/src/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java b/src/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java index 5709fc958..597189885 100644 --- a/src/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java +++ b/src/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java @@ -1,15 +1,14 @@ 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.view.LayoutInflater; import android.view.MenuItem; import android.view.View; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; +import android.widget.*; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.FeedItemlistDescriptionAdapter; import de.danoeh.antennapod.asynctask.ImageDiskCache; @@ -20,8 +19,10 @@ import de.danoeh.antennapod.storage.DBReader; import de.danoeh.antennapod.storage.DownloadRequestException; import de.danoeh.antennapod.storage.DownloadRequester; +import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Map; /** * Created by daniel on 24.08.13. @@ -31,6 +32,7 @@ public class DefaultOnlineFeedViewActivity extends OnlineFeedViewActivity { 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; @@ -44,7 +46,12 @@ public class DefaultOnlineFeedViewActivity extends OnlineFeedViewActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: - finish(); + Intent destIntent = new Intent(this, MainActivity.class); + if (NavUtils.shouldUpRecreateTask(this, destIntent)) { + startActivity(destIntent); + } else { + NavUtils.navigateUpFromSameTask(this); + } return true; } return super.onOptionsItemSelected(item); @@ -57,11 +64,12 @@ public class DefaultOnlineFeedViewActivity extends OnlineFeedViewActivity { } @Override - protected void showFeedInformation(final Feed feed) { - super.showFeedInformation(feed); + 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) @@ -75,6 +83,8 @@ public class DefaultOnlineFeedViewActivity extends OnlineFeedViewActivity { 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) { @@ -89,8 +99,10 @@ public class DefaultOnlineFeedViewActivity extends OnlineFeedViewActivity { @Override public void onClick(View v) { try { - Feed f = new Feed(feed.getDownload_url(), new Date(), feed.getTitle()); + Feed f = new Feed(selectedDownloadUrl, new Date(), feed.getTitle()); f.setPreferences(feed.getPreferences()); + DefaultOnlineFeedViewActivity.this.feed = f; + DownloadRequester.getInstance().downloadFeed( DefaultOnlineFeedViewActivity.this, f); @@ -102,6 +114,40 @@ public class DefaultOnlineFeedViewActivity extends OnlineFeedViewActivity { 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); } diff --git a/src/de/danoeh/antennapod/activity/DownloadActivity.java b/src/de/danoeh/antennapod/activity/DownloadActivity.java deleted file mode 100644 index 996929cdb..000000000 --- a/src/de/danoeh/antennapod/activity/DownloadActivity.java +++ /dev/null @@ -1,190 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.Intent; -import android.content.res.TypedArray; -import android.os.Bundle; -import android.os.Handler; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.app.ActionBarActivity; -import android.support.v7.view.ActionMode; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemLongClickListener; -import android.widget.ListView; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.adapter.DownloadlistAdapter; -import de.danoeh.antennapod.asynctask.DownloadObserver; -import de.danoeh.antennapod.preferences.UserPreferences; -import de.danoeh.antennapod.service.download.DownloadRequest; -import de.danoeh.antennapod.service.download.Downloader; -import de.danoeh.antennapod.storage.DownloadRequester; - -import java.util.List; - -/** - * Shows all running downloads in a list. The list objects are DownloadStatus - * objects created by a DownloadObserver. - */ -public class DownloadActivity extends ActionBarActivity implements - ActionMode.Callback { - - private static final String TAG = "DownloadActivity"; - private static final int MENU_SHOW_LOG = 0; - private static final int MENU_CANCEL_ALL_DOWNLOADS = 1; - private DownloadlistAdapter dla; - private DownloadRequester requester; - - private ActionMode mActionMode; - private DownloadRequest selectedDownload; - - private ListView listview; - - private DownloadObserver downloadObserver; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - setContentView(R.layout.listview_activity); - - listview = (ListView) findViewById(R.id.listview); - - if (BuildConfig.DEBUG) - Log.d(TAG, "Creating Activity"); - requester = DownloadRequester.getInstance(); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - downloadObserver = new DownloadObserver(this, new Handler(), observerCallback); - } - - @Override - protected void onPause() { - super.onPause(); - downloadObserver.onPause(); - } - - @Override - protected void onResume() { - super.onResume(); - downloadObserver.onResume(); - if (dla != null) { - dla.notifyDataSetChanged(); - } - } - - @Override - protected void onStop() { - super.onStop(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Stopping Activity"); - } - - - @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - listview.setOnItemLongClickListener(new OnItemLongClickListener() { - - @Override - public boolean onItemLongClick(AdapterView<?> arg0, View view, - int position, long id) { - DownloadRequest selection = dla.getItem(position) - .getDownloadRequest(); - if (selection != null && mActionMode != null) { - mActionMode.finish(); - } - dla.setSelectedItemIndex(position); - selectedDownload = selection; - mActionMode = startSupportActionMode(DownloadActivity.this); - return true; - } - - }); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, MENU_SHOW_LOG, Menu.NONE, - R.string.show_download_log), - MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); - MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, MENU_CANCEL_ALL_DOWNLOADS, Menu.NONE, - R.string.cancel_all_downloads_label), - MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - break; - case MENU_SHOW_LOG: - startActivity(new Intent(this, DownloadLogActivity.class)); - break; - case MENU_CANCEL_ALL_DOWNLOADS: - requester.cancelAllDownloads(this); - break; - } - return true; - } - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - if (selectedDownload != null) { - TypedArray drawables = obtainStyledAttributes(new int[]{R.attr.navigation_cancel}); - menu.add(Menu.NONE, R.id.cancel_download_item, Menu.NONE, - R.string.cancel_download_label).setIcon( - drawables.getDrawable(0)); - } - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - boolean handled = false; - switch (item.getItemId()) { - case R.id.cancel_download_item: - requester.cancelDownload(this, selectedDownload.getSource()); - handled = true; - break; - } - mActionMode.finish(); - return handled; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - mActionMode = null; - selectedDownload = null; - dla.setSelectedItemIndex(DownloadlistAdapter.SELECTION_NONE); - } - - - private DownloadObserver.Callback observerCallback = new DownloadObserver.Callback() { - @Override - public void onContentChanged() { - if (dla != null) { - dla.notifyDataSetChanged(); - } - } - - @Override - public void onDownloadDataAvailable(List<Downloader> downloaderList) { - dla = new DownloadlistAdapter(DownloadActivity.this, 0, - downloaderList); - listview.setAdapter(dla); - dla.notifyDataSetChanged(); - } - }; - -} diff --git a/src/de/danoeh/antennapod/activity/DownloadLogActivity.java b/src/de/danoeh/antennapod/activity/DownloadLogActivity.java deleted file mode 100644 index 4629b8670..000000000 --- a/src/de/danoeh/antennapod/activity/DownloadLogActivity.java +++ /dev/null @@ -1,123 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.NavUtils; -import android.support.v7.app.ActionBarActivity; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.ListView; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.adapter.DownloadLogAdapter; -import de.danoeh.antennapod.feed.EventDistributor; -import de.danoeh.antennapod.preferences.UserPreferences; -import de.danoeh.antennapod.service.download.DownloadStatus; -import de.danoeh.antennapod.storage.DBReader; - -import java.util.List; - -/** - * Displays completed and failed downloads in a list. - */ -public class DownloadLogActivity extends ActionBarActivity { - private static final String TAG = "DownloadLogActivity"; - - private List<DownloadStatus> downloadLog; - private DownloadLogAdapter dla; - - private ListView listview; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - setContentView(R.layout.listview_activity); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - - listview = (ListView) findViewById(R.id.listview); - - dla = new DownloadLogAdapter(this, itemAccess); - listview.setAdapter(dla); - loadData(); - } - - @Override - protected void onPause() { - super.onPause(); - EventDistributor.getInstance().unregister(contentUpdate); - } - - @Override - protected void onResume() { - super.onResume(); - EventDistributor.getInstance().register(contentUpdate); - dla.notifyDataSetChanged(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - break; - default: - return false; - } - return true; - } - - private void loadData() { - AsyncTask<Void, Void, List<DownloadStatus>> loadTask = new AsyncTask<Void, Void, List<DownloadStatus>>() { - @Override - protected List<DownloadStatus> doInBackground(Void... voids) { - return DBReader.getDownloadLog(DownloadLogActivity.this); - } - - @Override - protected void onPostExecute(List<DownloadStatus> downloadStatuses) { - super.onPostExecute(downloadStatuses); - if (downloadStatuses != null) { - downloadLog = downloadStatuses; - if (dla != null) { - dla.notifyDataSetChanged(); - } - } else { - Log.e(TAG, "Could not load download log"); - } - } - }; - loadTask.execute(); - } - - private DownloadLogAdapter.ItemAccess itemAccess = new DownloadLogAdapter.ItemAccess() { - - @Override - public int getCount() { - return (downloadLog != null) ? downloadLog.size() : 0; - } - - @Override - public DownloadStatus getItem(int position) { - return (downloadLog != null) ? downloadLog.get(position) : null; - } - }; - - private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((arg & EventDistributor.DOWNLOADLOG_UPDATE) != 0) { - loadData(); - } - } - }; - -} diff --git a/src/de/danoeh/antennapod/activity/FeedInfoActivity.java b/src/de/danoeh/antennapod/activity/FeedInfoActivity.java index db0755ccd..f00ce13e8 100644 --- a/src/de/danoeh/antennapod/activity/FeedInfoActivity.java +++ b/src/de/danoeh/antennapod/activity/FeedInfoActivity.java @@ -3,21 +3,22 @@ package de.danoeh.antennapod.activity; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; +import android.text.Editable; +import android.text.TextWatcher; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.ImageView; -import android.widget.TextView; +import android.widget.*; import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.asynctask.ImageLoader; import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedPreferences; import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.storage.DBReader; +import de.danoeh.antennapod.storage.DBWriter; import de.danoeh.antennapod.storage.DownloadRequestException; import de.danoeh.antennapod.util.LangUtils; import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler; @@ -37,6 +38,8 @@ public class FeedInfoActivity extends ActionBarActivity { private TextView txtvDescription; private TextView txtvLanguage; private TextView txtvAuthor; + private EditText etxtUsername; + private EditText etxtPassword; private CheckBox cbxAutoDownload; @Override @@ -53,6 +56,8 @@ public class FeedInfoActivity extends ActionBarActivity { txtvLanguage = (TextView) findViewById(R.id.txtvLanguage); txtvAuthor = (TextView) findViewById(R.id.txtvAuthor); cbxAutoDownload = (CheckBox) findViewById(R.id.cbxAutoDownload); + etxtUsername = (EditText) findViewById(R.id.etxtUsername); + etxtPassword = (EditText) findViewById(R.id.etxtPassword); AsyncTask<Long, Void, Feed> loadTask = new AsyncTask<Long, Void, Feed>() { @@ -98,6 +103,12 @@ public class FeedInfoActivity extends ActionBarActivity { } }); + etxtUsername.setText(feed.getPreferences().getUsername()); + etxtPassword.setText(feed.getPreferences().getPassword()); + + etxtUsername.addTextChangedListener(authTextWatcher); + etxtPassword.addTextChangedListener(authTextWatcher); + supportInvalidateOptionsMenu(); } else { @@ -108,6 +119,39 @@ public class FeedInfoActivity extends ActionBarActivity { loadTask.execute(feedId); } + + private boolean authInfoChanged = false; + + private TextWatcher authTextWatcher = 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) { + authInfoChanged = true; + } + }; + + @Override + protected void onPause() { + super.onPause(); + if (feed != null && authInfoChanged) { + Log.d(TAG, "Auth info changed, saving credentials"); + FeedPreferences prefs = feed.getPreferences(); + prefs.setUsername(etxtUsername.getText().toString()); + prefs.setPassword(etxtPassword.getText().toString()); + DBWriter.setFeedPreferences(this, prefs); + authInfoChanged = false; + } + } + @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); diff --git a/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java b/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java deleted file mode 100644 index d0305eada..000000000 --- a/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java +++ /dev/null @@ -1,232 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.annotation.SuppressLint; -import android.app.SearchManager; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.TypedArray; -import android.media.AudioManager; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.app.NavUtils; -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.Menu; -import android.view.MenuItem; -import android.view.Window; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.asynctask.FeedRemover; -import de.danoeh.antennapod.dialog.ConfirmationDialog; -import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; -import de.danoeh.antennapod.feed.Feed; -import de.danoeh.antennapod.fragment.ExternalPlayerFragment; -import de.danoeh.antennapod.fragment.FeedlistFragment; -import de.danoeh.antennapod.fragment.ItemlistFragment; -import de.danoeh.antennapod.preferences.UserPreferences; -import de.danoeh.antennapod.storage.DBReader; -import de.danoeh.antennapod.storage.DownloadRequestException; -import de.danoeh.antennapod.util.StorageUtils; -import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler; - -/** - * Displays a List of FeedItems - */ -public class FeedItemlistActivity extends ActionBarActivity { - private static final String TAG = "FeedItemlistActivity"; - - /** - * The feed which the activity displays - */ - private Feed feed; - private ItemlistFragment filf; - private ExternalPlayerFragment externalPlayerFragment; - - private AsyncTask<?, ?, ?> currentLoadTask; - - @Override - public void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - StorageUtils.checkStorageAvailability(this); - requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.feeditemlist_activity); - setVolumeControlStream(AudioManager.STREAM_MUSIC); - - long feedId = getIntent().getLongExtra( - FeedlistFragment.EXTRA_SELECTED_FEED, -1); - if (feedId == -1) { - Log.e(TAG, "Received invalid feed selection."); - } else { - loadData(feedId); - } - - } - - private synchronized void loadData(long id) { - if (currentLoadTask != null) { - currentLoadTask.cancel(true); - } - AsyncTask<Long, Void, Feed> loadTask = new AsyncTask<Long, Void, Feed>() { - - @Override - protected Feed doInBackground(Long... longs) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Loading feed data in background"); - return DBReader.getFeed(FeedItemlistActivity.this, longs[0]); - } - - @Override - protected void onCancelled(Feed feed) { - super.onCancelled(feed); - if (BuildConfig.DEBUG) Log.d(TAG, "load task was cancelled"); - } - - @Override - protected void onPostExecute(Feed result) { - super.onPostExecute(result); - if (result != null) { - if (BuildConfig.DEBUG) Log.d(TAG, "Finished loading feed data"); - feed = result; - setTitle(feed.getTitle()); - - FragmentManager fragmentManager = getSupportFragmentManager(); - FragmentTransaction fT = fragmentManager.beginTransaction(); - - filf = ItemlistFragment.newInstance(feed.getId()); - fT.replace(R.id.feeditemlistFragment, filf); - - externalPlayerFragment = new ExternalPlayerFragment(); - fT.replace(R.id.playerFragment, externalPlayerFragment); - fT.commit(); - supportInvalidateOptionsMenu(); - } else { - Log.e(TAG, "Error: Feed was null"); - } - } - }; - currentLoadTask = loadTask; - loadTask.execute(id); - } - - @Override - protected void onResume() { - super.onResume(); - StorageUtils.checkStorageAvailability(this); - } - - @Override - protected void onStop() { - super.onStop(); - if (currentLoadTask != null) { - currentLoadTask.cancel(true); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - if (feed != null) { - TypedArray drawables = obtainStyledAttributes(new int[]{R.attr.action_search}); - MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label) - .setIcon(drawables.getDrawable(0)), - MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); - MenuItemCompat.setActionView(menu.findItem(R.id.search_item), new SearchView(this)); - - SearchManager searchManager = - (SearchManager) getSystemService(Context.SEARCH_SERVICE); - SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item)); - - - searchView.setIconifiedByDefault(true); - - searchView.setSearchableInfo( - searchManager.getSearchableInfo(getComponentName())); - FeedMenuHandler - .onCreateOptionsMenu(getMenuInflater(), menu); - } - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - return FeedMenuHandler.onPrepareOptionsMenu(menu, feed); - } - - @SuppressLint("NewApi") - @Override - public boolean onOptionsItemSelected(MenuItem item) { - try { - if (FeedMenuHandler.onOptionsItemClicked(this, item, feed)) { - filf.getListAdapter().notifyDataSetChanged(); - } else { - switch (item.getItemId()) { - case R.id.remove_item: - final FeedRemover remover = new FeedRemover( - FeedItemlistActivity.this, feed) { - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - finish(); - } - }; - ConfirmationDialog conDialog = new ConfirmationDialog(this, - R.string.remove_feed_label, - R.string.feed_delete_confirmation_msg) { - - @Override - public void onConfirmButtonPressed( - DialogInterface dialog) { - dialog.dismiss(); - remover.executeAsync(); - } - }; - conDialog.createNewDialog().show(); - break; - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - break; - } - } - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog(this, - e.getMessage()); - } - return true; - } - - @Override - public boolean onSearchRequested() { - if (feed != null) { - Bundle bundle = new Bundle(); - bundle.putLong(SearchActivity.EXTRA_FEED_ID, feed.getId()); - startSearch(null, false, bundle, false); - return true; - } else { - return false; - } - } - - @Override - public void startActivity(Intent intent) { - if (intent.getAction() != null && - intent.getAction().equals(Intent.ACTION_SEARCH)) { - addSearchInformation(intent); - } - super.startActivity(intent); - } - - private void addSearchInformation(Intent startIntent) { - startIntent.putExtra(SearchActivity.EXTRA_FEED_ID, feed.getId()); - } - -} diff --git a/src/de/danoeh/antennapod/activity/ItemviewActivity.java b/src/de/danoeh/antennapod/activity/ItemviewActivity.java deleted file mode 100644 index 699ba84ea..000000000 --- a/src/de/danoeh/antennapod/activity/ItemviewActivity.java +++ /dev/null @@ -1,205 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.media.AudioManager; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.ActionBarActivity; -import android.text.format.DateUtils; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.Window; -import android.widget.TextView; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; -import de.danoeh.antennapod.feed.EventDistributor; -import de.danoeh.antennapod.feed.FeedItem; -import de.danoeh.antennapod.fragment.ItemDescriptionFragment; -import de.danoeh.antennapod.fragment.ItemlistFragment; -import de.danoeh.antennapod.preferences.UserPreferences; -import de.danoeh.antennapod.storage.DBReader; -import de.danoeh.antennapod.storage.DownloadRequestException; -import de.danoeh.antennapod.util.QueueAccess; -import de.danoeh.antennapod.util.StorageUtils; -import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler; - -import java.text.DateFormat; - -/** - * Displays a single FeedItem and provides various actions - */ -public class ItemviewActivity extends ActionBarActivity { - private static final String TAG = "ItemviewActivity"; - - private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | EventDistributor.DOWNLOAD_QUEUED; - - private FeedItem item; - private boolean guiInitialized; - private AsyncTask<?, ?, ?> currentLoadTask; - - @Override - public void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - StorageUtils.checkStorageAvailability(this); - requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - getSupportActionBar().setDisplayShowTitleEnabled(false); - EventDistributor.getInstance().register(contentUpdate); - setVolumeControlStream(AudioManager.STREAM_MUSIC); - - guiInitialized = false; - - long itemId = getIntent().getLongExtra( - ItemlistFragment.EXTRA_SELECTED_FEEDITEM, -1); - if (itemId == -1) { - Log.e(TAG, "Received invalid selection of either feeditem or feed."); - } else { - loadData(itemId); - } - } - - @Override - protected void onResume() { - super.onResume(); - StorageUtils.checkStorageAvailability(this); - } - - @Override - public void onStop() { - super.onStop(); - EventDistributor.getInstance().unregister(contentUpdate); - if (currentLoadTask != null) { - currentLoadTask.cancel(true); - } - if (BuildConfig.DEBUG) - Log.d(TAG, "Stopping Activity"); - } - - private synchronized void loadData(long itemId) { - if (currentLoadTask != null) { - currentLoadTask.cancel(true); - } - AsyncTask<Long, Void, FeedItem> loadTask = new AsyncTask<Long, Void, FeedItem>() { - - @Override - protected FeedItem doInBackground(Long... longs) { - return DBReader.getFeedItem(ItemviewActivity.this, longs[0]); - } - - @Override - protected void onCancelled(FeedItem feedItem) { - super.onCancelled(feedItem); - if (BuildConfig.DEBUG) Log.d(TAG, "loadTask was cancelled"); - } - - @Override - protected void onPostExecute(FeedItem feedItem) { - super.onPostExecute(feedItem); - if (feedItem != null && feedItem.getFeed() != null) { - item = feedItem; - populateUI(); - supportInvalidateOptionsMenu(); - } else { - if (feedItem == null) { - Log.e(TAG, "Error: FeedItem was null"); - } else if (feedItem.getFeed() == null) { - Log.e(TAG, "Error: Feed was null"); - } - } - } - }; - loadTask.execute(itemId); - currentLoadTask = loadTask; - } - - private synchronized void populateUI() { - if (!guiInitialized) { - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.feeditemview); - FragmentManager fragmentManager = getSupportFragmentManager(); - FragmentTransaction fragmentTransaction = fragmentManager - .beginTransaction(); - ItemDescriptionFragment fragment = ItemDescriptionFragment - .newInstance(item, false); - fragmentTransaction.replace(R.id.description_fragment, fragment); - fragmentTransaction.commit(); - } - TextView txtvTitle = (TextView) findViewById(R.id.txtvItemname); - TextView txtvPublished = (TextView) findViewById(R.id.txtvPublished); - setTitle(item.getFeed().getTitle()); - - txtvPublished.setText(DateUtils.formatSameDayTime(item.getPubDate() - .getTime(), System.currentTimeMillis(), DateFormat.MEDIUM, - DateFormat.SHORT)); - txtvTitle.setText(item.getTitle()); - - guiInitialized = true; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - if (item != null) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.feeditem, menu); - } - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - if (item == null) { - return false; - } - try { - if (!FeedItemMenuHandler.onMenuItemClicked(this, - menuItem.getItemId(), item)) { - switch (menuItem.getItemId()) { - case android.R.id.home: - finish(); - break; - } - } - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog(this, - e.getMessage()); - } - supportInvalidateOptionsMenu(); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(final Menu menu) { - super.onPrepareOptionsMenu(menu); - FeedItemMenuHandler.onPrepareMenu( - new FeedItemMenuHandler.MenuInterface() { - - @Override - public void setItemVisibility(int id, boolean visible) { - menu.findItem(id).setVisible(visible); - } - }, item, true, QueueAccess.NotInQueueAccess()); - return true; - } - - private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((EVENTS & arg) != 0) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received contentUpdate Intent."); - if (item != null) { - loadData(item.getId()); - } - } - } - }; - - -} diff --git a/src/de/danoeh/antennapod/activity/MainActivity.java b/src/de/danoeh/antennapod/activity/MainActivity.java index 29e36abc8..92afea77c 100644 --- a/src/de/danoeh/antennapod/activity/MainActivity.java +++ b/src/de/danoeh/antennapod/activity/MainActivity.java @@ -1,284 +1,414 @@ package de.danoeh.antennapod.activity; -import android.app.SearchManager; -import android.app.SearchableInfo; -import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.content.res.TypedArray; import android.media.AudioManager; +import android.os.AsyncTask; import android.os.Bundle; +import android.os.Handler; +import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; -import android.support.v4.view.MenuItemCompat; -import android.support.v4.view.ViewPager; +import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; -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.Window; +import android.view.*; +import android.widget.AdapterView; +import android.widget.ListView; import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.adapter.NavListAdapter; import de.danoeh.antennapod.feed.EventDistributor; -import de.danoeh.antennapod.fragment.EpisodesFragment; -import de.danoeh.antennapod.fragment.ExternalPlayerFragment; -import de.danoeh.antennapod.fragment.FeedlistFragment; +import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.fragment.*; import de.danoeh.antennapod.preferences.UserPreferences; -import de.danoeh.antennapod.service.download.DownloadService; -import de.danoeh.antennapod.service.playback.PlaybackService; import de.danoeh.antennapod.storage.DBReader; -import de.danoeh.antennapod.storage.DBTasks; -import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.StorageUtils; -import java.util.ArrayList; +import java.util.List; -/** The activity that is shown when the user launches the app. */ +/** + * The activity that is shown when the user launches the app. + */ public class MainActivity extends ActionBarActivity { - private static final String TAG = "MainActivity"; + private static final String TAG = "MainActivity"; + private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED + | EventDistributor.DOWNLOAD_QUEUED + | EventDistributor.FEED_LIST_UPDATE + | EventDistributor.UNREAD_ITEMS_UPDATE; - private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED - | EventDistributor.DOWNLOAD_QUEUED; + private static final String PREF_NAME = "MainActivityPrefs"; + private static final String PREF_IS_FIRST_LAUNCH = "prefMainActivityIsFirstLaunch"; - private ViewPager viewpager; - private TabsAdapter pagerAdapter; - private ExternalPlayerFragment externalPlayerFragment; + public static final String EXTRA_NAV_INDEX = "nav_index"; + public static final String EXTRA_NAV_TYPE = "nav_type"; + public static final String EXTRA_FRAGMENT_ARGS = "fragment_args"; - private static boolean appLaunched = false; + public static final int POS_NEW = 0, + POS_QUEUE = 1, + POS_DOWNLOADS = 2, + POS_HISTORY = 3, + POS_ADD = 4; - @Override - public void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - StorageUtils.checkStorageAvailability(this); - requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - setContentView(R.layout.main); + private ExternalPlayerFragment externalPlayerFragment; + private DrawerLayout drawerLayout; + + private ListView navList; + private NavListAdapter navAdapter; + + private ActionBarDrawerToggle drawerToogle; + + private CharSequence drawerTitle; + private CharSequence currentTitle; + + + @Override + public void onCreate(Bundle savedInstanceState) { + setTheme(UserPreferences.getTheme()); + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + StorageUtils.checkStorageAvailability(this); + setContentView(R.layout.main); setVolumeControlStream(AudioManager.STREAM_MUSIC); - getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); - - viewpager = (ViewPager) findViewById(R.id.viewpager); - pagerAdapter = new TabsAdapter(this, viewpager); - - viewpager.setAdapter(pagerAdapter); - - ActionBar.Tab feedsTab = getSupportActionBar().newTab(); - feedsTab.setText(R.string.podcasts_label); - ActionBar.Tab episodesTab = getSupportActionBar().newTab(); - episodesTab.setText(R.string.episodes_label); - - pagerAdapter.addTab(feedsTab, FeedlistFragment.class, null); - pagerAdapter.addTab(episodesTab, EpisodesFragment.class, null); - - FragmentTransaction transaction = getSupportFragmentManager() - .beginTransaction(); - externalPlayerFragment = new ExternalPlayerFragment(); - transaction.replace(R.id.playerFragment, externalPlayerFragment); - transaction.commit(); - - // executed on application start - if (!appLaunched && getIntent().getAction() != null - && getIntent().getAction().equals(Intent.ACTION_MAIN)) { - appLaunched = true; - if (DBReader.getNumberOfUnreadItems(this) > 0) { - // select 'episodes' tab - getSupportActionBar().setSelectedNavigationItem(1); - } - } - if (savedInstanceState != null) { - getSupportActionBar().setSelectedNavigationItem( - savedInstanceState.getInt("tab", 0)); - } - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt("tab", getSupportActionBar() - .getSelectedNavigationIndex()); - } - - @Override - protected void onPause() { - super.onPause(); - EventDistributor.getInstance().unregister(contentUpdate); - } - - @Override - protected void onResume() { - super.onResume(); - StorageUtils.checkStorageAvailability(this); - updateProgressBarVisibility(); - EventDistributor.getInstance().register(contentUpdate); - - } - - private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((EVENTS & arg) != 0) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received contentUpdate Intent."); - updateProgressBarVisibility(); - } - } - }; - - private void updateProgressBarVisibility() { - if (DownloadService.isRunning - && DownloadRequester.getInstance().isDownloadingFeeds()) { - setSupportProgressBarIndeterminateVisibility(true); - } else { - setSupportProgressBarIndeterminateVisibility(false); - } - supportInvalidateOptionsMenu(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.add_feed: - startActivity(new Intent(this, AddFeedActivity.class)); - return true; - case R.id.all_feed_refresh: - DBTasks.refreshAllFeeds(this, null); - return true; - case R.id.show_downloads: - startActivity(new Intent(this, DownloadActivity.class)); - return true; - case R.id.show_preferences: - startActivity(new Intent(this, PreferenceActivity.class)); - return true; - case R.id.show_player: - startActivity(PlaybackService.getPlayerActivityIntent(this)); - return true; - case R.id.show_playback_history: - startActivity(new Intent(this, PlaybackHistoryActivity.class)); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { + drawerTitle = currentTitle = getTitle(); + + drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + navList = (ListView) findViewById(R.id.nav_list); + + TypedArray typedArray = obtainStyledAttributes(new int[]{R.attr.nav_drawer_toggle}); + drawerToogle = new ActionBarDrawerToggle(this, drawerLayout, typedArray.getResourceId(0, 0), R.string.drawer_open, R.string.drawer_close) { + @Override + public void onDrawerOpened(View drawerView) { + super.onDrawerOpened(drawerView); + currentTitle = getSupportActionBar().getTitle(); + getSupportActionBar().setTitle(drawerTitle); + supportInvalidateOptionsMenu(); + } + + @Override + public void onDrawerClosed(View drawerView) { + super.onDrawerClosed(drawerView); + getSupportActionBar().setTitle(currentTitle); + supportInvalidateOptionsMenu(); + + } + }; + typedArray.recycle(); + + drawerLayout.setDrawerListener(drawerToogle); + FragmentManager fm = getSupportFragmentManager(); + + FragmentTransaction transaction = fm.beginTransaction(); + + Fragment mainFragment = fm.findFragmentByTag("main"); + if (mainFragment != null) { + transaction.replace(R.id.main_view, mainFragment); + } else { + loadFragment(NavListAdapter.VIEW_TYPE_NAV, POS_NEW, null); + } + + externalPlayerFragment = new ExternalPlayerFragment(); + transaction.replace(R.id.playerFragment, externalPlayerFragment); + transaction.commit(); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeButtonEnabled(true); + + navAdapter = new NavListAdapter(itemAccess, this); + navList.setAdapter(navAdapter); + navList.setOnItemClickListener(navListClickListener); + + checkFirstLaunch(); + } + + 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(navList); + } + }, 1500); + + SharedPreferences.Editor edit = prefs.edit(); + edit.putBoolean(PREF_IS_FIRST_LAUNCH, false); + edit.commit(); + } + } + + public ActionBar getMainActivtyActionBar() { + return getSupportActionBar(); + } + + public List<Feed> getFeeds() { + return feeds; + } + + private void loadFragment(int viewType, int relPos, Bundle args) { + FragmentManager fragmentManager = getSupportFragmentManager(); + // clear back stack + for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) { + fragmentManager.popBackStack(); + } + + FragmentTransaction fT = fragmentManager.beginTransaction(); + Fragment fragment = null; + if (viewType == NavListAdapter.VIEW_TYPE_NAV) { + switch (relPos) { + case POS_NEW: + fragment = new NewEpisodesFragment(); + break; + case POS_QUEUE: + fragment = new QueueFragment(); + break; + case POS_DOWNLOADS: + fragment = new DownloadsFragment(); + break; + case POS_HISTORY: + fragment = new PlaybackHistoryFragment(); + break; + case POS_ADD: + fragment = new AddFeedFragment(); + break; + + } + currentTitle = getString(NavListAdapter.NAV_TITLES[relPos]); + selectedNavListIndex = relPos; + + } else if (viewType == NavListAdapter.VIEW_TYPE_SUBSCRIPTION) { + Feed feed = itemAccess.getItem(relPos); + currentTitle = ""; + fragment = ItemlistFragment.newInstance(feed.getId()); + selectedNavListIndex = NavListAdapter.SUBSCRIPTION_OFFSET + relPos; + + } + if (fragment != null) { + if (args != null) { + fragment.setArguments(args); + } + fT.replace(R.id.main_view, fragment, "main"); + fragmentManager.popBackStack(); + } + fT.commit(); + getSupportActionBar().setTitle(currentTitle); + if (navAdapter != null) { + navAdapter.notifyDataSetChanged(); + } + } + + public void loadNavFragment(int position, Bundle args) { + loadFragment(NavListAdapter.VIEW_TYPE_NAV, position, args); + } + + public void loadFeedFragment(long feedID) { + if (feeds != null) { + for (int i = 0; i < feeds.size(); i++) { + if (feeds.get(i).getId() == feedID) { + loadFragment(NavListAdapter.VIEW_TYPE_SUBSCRIPTION, i, null); + break; + } + } + } + } + + public void loadChildFragment(Fragment fragment) { + if (fragment == null) throw new IllegalArgumentException("fragment = null"); + FragmentManager fm = getSupportFragmentManager(); + fm.beginTransaction() + .replace(R.id.main_view, fragment, "main") + .addToBackStack(null) + .commit(); + } + + private AdapterView.OnItemClickListener navListClickListener = 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 && position != selectedNavListIndex) { + int relPos = (viewType == NavListAdapter.VIEW_TYPE_NAV) ? position : position - NavListAdapter.SUBSCRIPTION_OFFSET; + loadFragment(viewType, relPos, null); + selectedNavListIndex = position; + navAdapter.notifyDataSetChanged(); + } + drawerLayout.closeDrawer(navList); + } + }; + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + drawerToogle.syncState(); + if (savedInstanceState != null) { + currentTitle = savedInstanceState.getString("title"); + if (!drawerLayout.isDrawerOpen(navList)) { + getSupportActionBar().setTitle(currentTitle); + } + selectedNavListIndex = savedInstanceState.getInt("selectedNavIndex"); + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + drawerToogle.onConfigurationChanged(newConfig); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString("title", getSupportActionBar().getTitle().toString()); + outState.putInt("selectedNavIndex", selectedNavListIndex); + + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + StorageUtils.checkStorageAvailability(this); + EventDistributor.getInstance().register(contentUpdate); + + Intent intent = getIntent(); + if (feeds != null && intent.hasExtra(EXTRA_NAV_INDEX) && intent.hasExtra(EXTRA_NAV_TYPE)) { + handleNavIntent(); + } + + loadData(); + } + + @Override + protected void onStop() { + super.onStop(); + cancelLoadTask(); + EventDistributor.getInstance().unregister(contentUpdate); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (drawerToogle.onOptionsItemSelected(item)) { + return true; + } + switch (item.getItemId()) { + case R.id.show_preferences: + startActivity(new Intent(this, PreferenceActivity.class)); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); - MenuItem refreshAll = menu.findItem(R.id.all_feed_refresh); - if (DownloadService.isRunning - && DownloadRequester.getInstance().isDownloadingFeeds()) { - refreshAll.setVisible(false); - } else { - refreshAll.setVisible(true); - } - return true; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { + + return true; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.main, menu); - - SearchManager searchManager = - (SearchManager) getSystemService(Context.SEARCH_SERVICE); - MenuItem searchItem = menu.findItem(R.id.search_item); - SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem); - if (searchView == null) { - MenuItemCompat.setActionView(searchItem, new SearchView(this)); - searchView = (SearchView) MenuItemCompat.getActionView(searchItem); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.main, menu); + return true; + } + + private List<Feed> feeds; + private AsyncTask<Void, Void, List<Feed>> loadTask; + private int selectedNavListIndex = 0; + + private NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() { + @Override + public int getCount() { + if (feeds != null) { + return feeds.size(); + } else { + return 0; + } + } + + @Override + public Feed getItem(int position) { + if (feeds != null && position < feeds.size()) { + return feeds.get(position); + } else { + return null; + } } - searchView.setIconifiedByDefault(true); - SearchableInfo info = searchManager.getSearchableInfo(getComponentName()); - searchView.setSearchableInfo( - searchManager.getSearchableInfo(getComponentName())); + @Override + public int getSelectedItemIndex() { + return selectedNavListIndex; + } - return true; - } - - public static class TabsAdapter extends FragmentPagerAdapter implements - ActionBar.TabListener, ViewPager.OnPageChangeListener { - private final Context mContext; - private final ActionBar mActionBar; - private final ViewPager mViewPager; - private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); - - static final class TabInfo { - private final Class<?> clss; - private final Bundle args; - - TabInfo(Class<?> _class, Bundle _args) { - clss = _class; - args = _args; - } - } - - public TabsAdapter(MainActivity activity, ViewPager pager) { - super(activity.getSupportFragmentManager()); - mContext = activity; - mActionBar = activity.getSupportActionBar(); - mViewPager = pager; - mViewPager.setAdapter(this); - mViewPager.setOnPageChangeListener(this); - } - - public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) { - TabInfo info = new TabInfo(clss, args); - tab.setTag(info); - tab.setTabListener(this); - mTabs.add(info); - mActionBar.addTab(tab); - notifyDataSetChanged(); - } - - @Override - public int getCount() { - return mTabs.size(); - } - - @Override - public Fragment getItem(int position) { - TabInfo info = mTabs.get(position); - return Fragment.instantiate(mContext, info.clss.getName(), - info.args); - } - - @Override - public void onPageScrolled(int position, float positionOffset, - int positionOffsetPixels) { - } - - @Override - public void onPageSelected(int position) { - mActionBar.setSelectedNavigationItem(position); - } - - @Override - public void onPageScrollStateChanged(int state) { - } - - @Override - public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { - Object tag = tab.getTag(); - for (int i = 0; i < mTabs.size(); i++) { - if (mTabs.get(i) == tag) { - mViewPager.setCurrentItem(i); - } - } - } - - @Override - public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { - - } - - @Override - public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { - } - } + }; + + private void loadData() { + cancelLoadTask(); + loadTask = new AsyncTask<Void, Void, List<Feed>>() { + @Override + protected List<Feed> doInBackground(Void... params) { + return DBReader.getFeedList(MainActivity.this); + } + + @Override + protected void onPostExecute(List<Feed> result) { + super.onPostExecute(result); + boolean handleIntent = (feeds == null); + + feeds = result; + navAdapter.notifyDataSetChanged(); + + if (handleIntent) { + handleNavIntent(); + } + } + }; + loadTask.execute(); + } + private void cancelLoadTask() { + if (loadTask != null) { + loadTask.cancel(true); + } + } + + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + + @Override + public void update(EventDistributor eventDistributor, Integer arg) { + if ((EVENTS & arg) != 0) { + if (BuildConfig.DEBUG) + Log.d(TAG, "Received contentUpdate Intent."); + loadData(); + } + } + }; + + private void handleNavIntent() { + Intent intent = getIntent(); + if (intent.hasExtra(EXTRA_NAV_INDEX) && intent.hasExtra(EXTRA_NAV_TYPE)) { + int index = intent.getIntExtra(EXTRA_NAV_INDEX, 0); + int type = intent.getIntExtra(EXTRA_NAV_TYPE, NavListAdapter.VIEW_TYPE_NAV); + Bundle args = intent.getBundleExtra(EXTRA_FRAGMENT_ARGS); + loadFragment(type, index, args); + } + setIntent(new Intent(MainActivity.this, MainActivity.class)); // to avoid handling the intent twice when the configuration changes + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + } } diff --git a/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java deleted file mode 100644 index 6d732b9ff..000000000 --- a/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java +++ /dev/null @@ -1,110 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentStatePagerAdapter; -import android.support.v4.app.NavUtils; -import android.support.v4.view.ViewPager; -import android.support.v7.app.ActionBarActivity; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.fragment.MiroGuideChannellistFragment; -import de.danoeh.antennapod.preferences.UserPreferences; - -/** - * Shows channels of a category sorted by different criteria in lists. The - * activity uses MiroGuideChannelListFragments for these lists. If the user - * selects a channel, the MiroGuideChannelViewActivity is started. - */ -public class MiroGuideCategoryActivity extends ActionBarActivity { - private static final String TAG = "MiroGuideCategoryActivity"; - - public static final String EXTRA_CATEGORY = "category"; - - private ViewPager viewpager; - private CategoryPagerAdapter pagerAdapter; - - private String category; - - @Override - protected void onCreate(Bundle arg0) { - setTheme(UserPreferences.getTheme()); - super.onCreate(arg0); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.miroguide_category); - - viewpager = (ViewPager) findViewById(R.id.viewpager); - - category = getIntent().getStringExtra(EXTRA_CATEGORY); - if (category != null) { - getSupportActionBar().setTitle(category); - pagerAdapter = new CategoryPagerAdapter(getSupportFragmentManager()); - viewpager.setAdapter(pagerAdapter); - } else { - Log.e(TAG, "Activity was started with invalid arguments"); - } - - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - default: - return false; - } - } - - public class CategoryPagerAdapter extends FragmentStatePagerAdapter { - - public CategoryPagerAdapter(FragmentManager fm) { - super(fm); - } - - private static final int NUM_ITEMS = 2; - private static final int POS_RATING = 0; - private static final int POS_POPULAR = 1; - - @Override - public Fragment getItem(int position) { - switch (position) { - case POS_RATING: - return MiroGuideChannellistFragment.newInstance("category", - category, "rating"); - case POS_POPULAR: - return MiroGuideChannellistFragment.newInstance("category", - category, "popular"); - default: - return null; - } - } - - @Override - public CharSequence getPageTitle(int position) { - switch (position) { - case POS_RATING: - return getString(R.string.best_rating_label); - case POS_POPULAR: - return getString(R.string.popular_label); - default: - return null; - } - } - - @Override - public int getCount() { - return NUM_ITEMS; - } - } -} diff --git a/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java deleted file mode 100644 index 96c8385ce..000000000 --- a/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java +++ /dev/null @@ -1,198 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.annotation.SuppressLint; -import android.content.Intent; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.NavUtils; -import android.support.v7.app.ActionBarActivity; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.*; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.adapter.MiroGuideItemlistAdapter; -import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; -import de.danoeh.antennapod.feed.Feed; -import de.danoeh.antennapod.miroguide.conn.MiroGuideException; -import de.danoeh.antennapod.miroguide.conn.MiroGuideService; -import de.danoeh.antennapod.miroguide.model.MiroGuideChannel; -import de.danoeh.antennapod.preferences.UserPreferences; -import de.danoeh.antennapod.storage.DBReader; -import de.danoeh.antennapod.storage.DownloadRequestException; -import de.danoeh.antennapod.storage.DownloadRequester; - -import java.util.Date; -import java.util.List; - -/** - * Displays information about one channel and lets the user add this channel to - * his library. - */ -public class MiroGuideChannelViewActivity extends ActionBarActivity { - private static final String TAG = "MiroGuideChannelViewActivity"; - - public static final String EXTRA_CHANNEL_ID = "id"; - public static final String EXTRA_CHANNEL_URL = "url"; - - private RelativeLayout layoutContent; - private ProgressBar progLoading; - private TextView txtvTitle; - private TextView txtVDescription; - private ListView listEntries; - - private long channelId; - private String channelUrl; - private MiroGuideChannel channel; - private volatile List<Feed> feeds; - - @Override - protected void onPause() { - super.onPause(); - channelLoader.cancel(true); - } - - @SuppressLint("NewApi") - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.miroguide_channelview); - - layoutContent = (RelativeLayout) findViewById(R.id.layout_content); - progLoading = (ProgressBar) findViewById(R.id.progLoading); - txtvTitle = (TextView) findViewById(R.id.txtvTitle); - txtVDescription = (TextView) findViewById(R.id.txtvDescription); - listEntries = (ListView) findViewById(R.id.itemlist); - - channelId = getIntent().getLongExtra(EXTRA_CHANNEL_ID, -1); - channelUrl = getIntent().getStringExtra(EXTRA_CHANNEL_URL); - - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - channelLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - channelLoader.execute(); - } - - } - - /** - * Is used to load channel information asynchronously. - */ - private AsyncTask<Void, Void, Void> channelLoader = new AsyncTask<Void, Void, Void>() { - private static final String TAG = "ChannelLoader"; - private Exception exception; - - @Override - protected Void doInBackground(Void... params) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Starting background task"); - feeds = DBReader.getFeedList(MiroGuideChannelViewActivity.this); - MiroGuideService service = new MiroGuideService(); - try { - channel = service.getChannel(channelId); - } catch (MiroGuideException e) { - e.printStackTrace(); - exception = e; - } - return null; - } - - @SuppressLint("NewApi") - @Override - protected void onPostExecute(Void result) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Loading finished"); - if (exception == null) { - txtvTitle.setText(channel.getName()); - txtVDescription.setText(channel.getDescription()); - - MiroGuideItemlistAdapter listAdapter = new MiroGuideItemlistAdapter( - MiroGuideChannelViewActivity.this, 0, - channel.getItems()); - listEntries.setAdapter(listAdapter); - progLoading.setVisibility(View.GONE); - layoutContent.setVisibility(View.VISIBLE); - supportInvalidateOptionsMenu(); - } else { - finish(); - } - } - - }; - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.channelview, menu); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - boolean channelLoaded = channel != null; - boolean beingDownloaded = channelLoaded - && DownloadRequester.getInstance().isDownloadingFile( - channel.getDownloadUrl()); - boolean notAdded = channelLoaded - && !((feedExists( - channel.getDownloadUrl()) || beingDownloaded)); - menu.findItem(R.id.add_feed).setVisible(notAdded); - menu.findItem(R.id.visit_website_item).setVisible( - channelLoaded && channel.getWebsiteUrl() != null); - return true; - } - - @SuppressLint("NewApi") - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - case R.id.visit_website_item: - Uri uri = Uri.parse(channel.getWebsiteUrl()); - startActivity(new Intent(Intent.ACTION_VIEW, uri)); - return true; - case R.id.add_feed: - try { - DownloadRequester.getInstance().downloadFeed( - this, - new Feed(channel.getDownloadUrl(), new Date(), channel - .getName())); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog(this, - e.getMessage()); - } - Toast toast = Toast.makeText(this, R.string.miro_feed_added, - Toast.LENGTH_LONG); - toast.show(); - supportInvalidateOptionsMenu(); - return true; - default: - return false; - } - } - - private boolean feedExists(String downloadUrl) { - if (feeds == null) { - return false; - } - - for (Feed feed : feeds) { - if (feed.getDownload_url().equals(downloadUrl)) { - return true; - } - } - return false; - } - -} diff --git a/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java deleted file mode 100644 index 9f74e77b6..000000000 --- a/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java +++ /dev/null @@ -1,168 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.annotation.SuppressLint; -import android.app.SearchManager; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.NavUtils; -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.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.ListView; -import android.widget.TextView; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.miroguide.conn.MiroGuideException; -import de.danoeh.antennapod.miroguide.conn.MiroGuideService; -import de.danoeh.antennapod.preferences.UserPreferences; - -/** - * Shows a list of available categories and offers a search button. If the user - * selects a category, the MiroGuideCategoryActivity is started. - */ -public class MiroGuideMainActivity extends ActionBarActivity implements AdapterView.OnItemClickListener { - private static final String TAG = "MiroGuideMainActivity"; - - private static String[] categories; - private ArrayAdapter<String> listAdapter; - - private TextView txtvStatus; - private ListView listView; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.miroguide_categorylist); - - txtvStatus = (TextView) findViewById(android.R.id.empty); - listView = (ListView) findViewById(android.R.id.list); - listView.setOnItemClickListener(this); - listView.setEmptyView(txtvStatus); - } - - @Override - protected void onPause() { - super.onPause(); - } - - @Override - protected void onResume() { - super.onResume(); - if (categories != null) { - createAdapter(); - } else { - loadCategories(); - } - } - - private void createAdapter() { - if (categories != null) { - listAdapter = new ArrayAdapter<String>(this, - android.R.layout.simple_list_item_1, categories); - txtvStatus.setText(R.string.no_items_label); - listView.setAdapter(listAdapter); - } - } - - /** - * Launches an AsyncTask to load the available categories in the background. - */ - @SuppressLint("NewApi") - private void loadCategories() { - AsyncTask<Void, Void, Void> listLoader = new AsyncTask<Void, Void, Void>() { - - private String[] c; - private MiroGuideException exception; - - @Override - protected void onPostExecute(Void result) { - if (exception == null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Successfully loaded categories"); - categories = c; - createAdapter(); - } else { - Log.e(TAG, "Error happened while trying to load categories"); - txtvStatus.setText(exception.getMessage()); - } - } - - @Override - protected void onPreExecute() { - txtvStatus.setText(R.string.loading_categories_label); - } - - @Override - protected Void doInBackground(Void... params) { - MiroGuideService service = new MiroGuideService(); - try { - c = service.getCategories(); - } catch (MiroGuideException e) { - e.printStackTrace(); - exception = e; - } finally { - service.close(); - } - return null; - } - - }; - - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - listLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - listLoader.execute(); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label) - .setIcon( - obtainStyledAttributes( - new int[]{R.attr.action_search}) - .getDrawable(0)), - MenuItem.SHOW_AS_ACTION_IF_ROOM); - MenuItemCompat.setActionView(menu.findItem(R.id.search_item), new SearchView(this)); - - SearchManager searchManager = - (SearchManager) getSystemService(Context.SEARCH_SERVICE); - SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item)); - searchView.setIconifiedByDefault(true); - searchView.setSearchableInfo( - searchManager.getSearchableInfo(getComponentName())); - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - default: - return false; - } - } - - @Override - public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) { - String selection = listAdapter.getItem(position); - Intent launchIntent = new Intent(this, MiroGuideCategoryActivity.class); - launchIntent.putExtra(MiroGuideCategoryActivity.EXTRA_CATEGORY, - selection); - startActivity(launchIntent); - } -} diff --git a/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java deleted file mode 100644 index e20253dc6..000000000 --- a/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java +++ /dev/null @@ -1,90 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.app.SearchManager; -import android.content.Intent; -import android.os.Bundle; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.app.ActionBarActivity; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.fragment.MiroGuideChannellistFragment; -import de.danoeh.antennapod.preferences.UserPreferences; - -/** - * Displays results when a search for miroguide channels has been performed. It - * uses a MiroGuideChannelListFragment to display the results. - */ -public class MiroGuideSearchActivity extends ActionBarActivity { - private static final String TAG = "MiroGuideSearchActivity"; - - private MiroGuideChannellistFragment listFragment; - - @Override - protected void onCreate(Bundle arg0) { - setTheme(UserPreferences.getTheme()); - super.onCreate(arg0); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.miroguidesearch); - } - - @Override - protected void onResume() { - super.onResume(); - Intent intent = getIntent(); - if (Intent.ACTION_SEARCH.equals(intent.getAction())) { - String query = intent.getStringExtra(SearchManager.QUERY); - getSupportActionBar() - .setSubtitle( - getString(R.string.search_term_label) + "\"" - + query + "\""); - handleSearchRequest(query); - } - } - - private void handleSearchRequest(String query) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Performing search"); - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - listFragment = MiroGuideChannellistFragment.newInstance("name", query, - "name"); - ft.replace(R.id.channellistFragment, listFragment); - ft.commit(); - } - - @Override - protected void onNewIntent(Intent intent) { - setIntent(intent); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label) - .setIcon( - obtainStyledAttributes( - new int[]{R.attr.action_search}) - .getDrawable(0)), - MenuItem.SHOW_AS_ACTION_IF_ROOM); - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - case R.id.search_item: - onSearchRequested(); - return true; - default: - return false; - } - } - -} diff --git a/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java index 6bd5e4eb9..6dd4b4edc 100644 --- a/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -5,6 +5,7 @@ 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.support.v7.app.ActionBarActivity; import android.util.Log; @@ -22,17 +23,20 @@ import de.danoeh.antennapod.service.download.DownloadStatus; import de.danoeh.antennapod.service.download.Downloader; import de.danoeh.antennapod.service.download.HttpDownloader; import de.danoeh.antennapod.syndication.handler.FeedHandler; +import de.danoeh.antennapod.syndication.handler.FeedHandlerResult; import de.danoeh.antennapod.syndication.handler.UnsupportedFeedtypeException; import de.danoeh.antennapod.util.DownloadError; import de.danoeh.antennapod.util.FileNameGenerator; import de.danoeh.antennapod.util.StorageUtils; import de.danoeh.antennapod.util.URLChecker; +import org.apache.commons.lang3.StringUtils; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import java.io.File; import java.io.IOException; import java.util.Date; +import java.util.Map; /** * Downloads a feed from a feed URL and parses it. Subclasses can display the @@ -46,12 +50,15 @@ public abstract 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"; public static final int RESULT_ERROR = 2; private Feed feed; + private Map<String, String> alternateFeedUrls; private Downloader downloader; @Override @@ -64,11 +71,21 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { } StorageUtils.checkStorageAvailability(this); - final String feedUrl = getIntent().getStringExtra(ARG_FEEDURL); - if (feedUrl == null) { + + 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)) + ? 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!"); } + if (BuildConfig.DEBUG) Log.d(TAG, "Activity was started with url " + feedUrl); setLoadingLayout(); @@ -146,7 +163,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { .toString(); feed.setFile_url(fileUrl); final DownloadRequest request = new DownloadRequest(feed.getFile_url(), - feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED, username, password); + feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED, username, password, true); downloader = new HttpDownloader( request); new Thread() { @@ -181,7 +198,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { } private void parseFeed() { - if (feed == null || feed.getFile_url() == null) { + if (feed == null || feed.getFile_url() == null && feed.isDownloaded()) { throw new IllegalStateException( "feed must be non-null and downloaded when parseFeed is called"); } @@ -197,7 +214,9 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { boolean successful = false; FeedHandler handler = new FeedHandler(); try { - handler.parseFeed(feed); + FeedHandlerResult result = handler.parseFeed(feed); + feed = result.feed; + alternateFeedUrls = result.alternateFeedUrls; successful = true; } catch (SAXException e) { e.printStackTrace(); @@ -221,7 +240,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { runOnUiThread(new Runnable() { @Override public void run() { - showFeedInformation(feed); + showFeedInformation(feed, alternateFeedUrls); } }); } else { @@ -244,7 +263,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { /** * Can be used to load data asynchronously. - * */ + */ protected void loadData() { } @@ -252,7 +271,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { /** * Called when feed parsed successfully */ - protected void showFeedInformation(Feed feed) { + protected void showFeedInformation(Feed feed, Map<String, String> alternateFeedUrls) { } @@ -271,7 +290,8 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } - }); + } + ); builder.setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { diff --git a/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java b/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java deleted file mode 100644 index 6e2cf597f..000000000 --- a/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java +++ /dev/null @@ -1,307 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.Context; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.v7.app.ActionBarActivity; -import android.util.Log; -import android.view.*; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; -import com.mobeta.android.dslv.DragSortListView; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.asynctask.ImageLoader; -import de.danoeh.antennapod.feed.EventDistributor; -import de.danoeh.antennapod.feed.FeedItem; -import de.danoeh.antennapod.preferences.UserPreferences; -import de.danoeh.antennapod.storage.DBReader; -import de.danoeh.antennapod.storage.DBTasks; -import de.danoeh.antennapod.storage.DBWriter; -import de.danoeh.antennapod.util.UndoBarController; - -import java.util.List; - -public class OrganizeQueueActivity extends ActionBarActivity implements - UndoBarController.UndoListener { - private static final String TAG = "OrganizeQueueActivity"; - - private static final int MENU_ID_ACCEPT = 2; - - private List<FeedItem> queue; - - private OrganizeAdapter adapter; - private UndoBarController undoBarController; - - private DragSortListView listView; - private TextView emptyView; - private ProgressBar progLoading; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - setContentView(R.layout.organize_queue); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - listView = (DragSortListView) findViewById(android.R.id.list); - emptyView = (TextView) findViewById(android.R.id.empty); - progLoading = (ProgressBar) findViewById(R.id.progLoading); - - listView.setDropListener(dropListener); - listView.setRemoveListener(removeListener); - listView.setEmptyView(findViewById(android.R.id.empty)); - - loadData(); - undoBarController = new UndoBarController(findViewById(R.id.undobar), - this); - } - - private void loadData() { - AsyncTask<Void, Void, List<FeedItem>> loadTask = new AsyncTask<Void, Void, List<FeedItem>>() { - - @Override - protected List<FeedItem> doInBackground(Void... voids) { - return DBReader.getQueue(OrganizeQueueActivity.this); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - // do not show loading animation if queue is already loaded - if (queue == null) { - progLoading.setVisibility(View.VISIBLE); - listView.setVisibility(View.GONE); - emptyView.setVisibility(View.GONE); - } - } - - @Override - protected void onPostExecute(List<FeedItem> feedItems) { - super.onPostExecute(feedItems); - if (feedItems != null) { - queue = feedItems; - if (adapter == null) { - adapter = new OrganizeAdapter(OrganizeQueueActivity.this); - listView.setAdapter(adapter); - } - adapter.notifyDataSetChanged(); - } else { - Log.e(TAG, "Queue was null"); - } - progLoading.setVisibility(View.GONE); - listView.setVisibility(View.VISIBLE); - } - }; - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - loadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - loadTask.execute(); - } - } - - @Override - protected void onPause() { - super.onPause(); - EventDistributor.getInstance().unregister(contentUpdate); - } - - @Override - protected void onStop() { - super.onStop(); - DBTasks.autodownloadUndownloadedItems(getApplicationContext()); - } - - @Override - protected void onResume() { - super.onResume(); - EventDistributor.getInstance().register(contentUpdate); - } - - private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if (((EventDistributor.QUEUE_UPDATE | EventDistributor.FEED_LIST_UPDATE) & arg) != 0) { - loadData(); - } - } - }; - - private DragSortListView.DropListener dropListener = new DragSortListView.DropListener() { - - @Override - public void drop(int from, int to) { - final FeedItem item = queue.remove(from); - queue.add(to, item); - adapter.notifyDataSetChanged(); - DBWriter.moveQueueItem(OrganizeQueueActivity.this, from, to, true); - } - }; - - private DragSortListView.RemoveListener removeListener = new DragSortListView.RemoveListener() { - - @Override - public void remove(int which) { - FeedItem item = (FeedItem) listView.getAdapter().getItem(which); - DBWriter.removeQueueItem(OrganizeQueueActivity.this, item.getId(), true); - undoBarController.showUndoBar(false, - getString(R.string.removed_from_queue), new UndoToken(item, - which)); - } - }; - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - default: - return false; - } - } - - @Override - public void onUndo(Parcelable token) { - // Perform the undo - UndoToken undoToken = (UndoToken) token; - if (token != null) { - long itemId = undoToken.getFeedItemId(); - int position = undoToken.getPosition(); - DBWriter.addQueueItemAt(OrganizeQueueActivity.this, itemId, position, false); - } - } - - private static class OrganizeAdapter extends BaseAdapter { - - private OrganizeQueueActivity organizeQueueActivity; - - public OrganizeAdapter(OrganizeQueueActivity organizeQueueActivity) { - super(); - this.organizeQueueActivity = organizeQueueActivity; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - Holder holder; - final FeedItem item = getItem(position); - - if (convertView == null) { - holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) organizeQueueActivity - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate( - R.layout.organize_queue_listitem, null); - holder.title = (TextView) convertView - .findViewById(R.id.txtvTitle); - holder.feedTitle = (TextView) convertView - .findViewById(R.id.txtvFeedname); - - holder.feedImage = (ImageView) convertView - .findViewById(R.id.imgvFeedimage); - convertView.setTag(holder); - } else { - holder = (Holder) convertView.getTag(); - } - - holder.title.setText(item.getTitle()); - holder.feedTitle.setText(item.getFeed().getTitle()); - - holder.feedImage.setTag(item.getImageLoaderCacheKey()); - ImageLoader.getInstance().loadThumbnailBitmap( - item, - holder.feedImage, - (int) convertView.getResources().getDimension( - R.dimen.thumbnail_length)); - - return convertView; - } - - static class Holder { - TextView title; - TextView feedTitle; - ImageView feedImage; - } - - @Override - public int getCount() { - if (organizeQueueActivity.queue != null) { - return organizeQueueActivity.queue.size(); - } else { - return 0; - } - } - - @Override - public FeedItem getItem(int position) { - if (organizeQueueActivity.queue != null) { - return organizeQueueActivity.queue.get(position); - } else { - return null; - } - } - - @Override - public long getItemId(int position) { - return position; - } - - } - - private static class UndoToken implements Parcelable { - private long itemId; - private long feedId; - private int position; - - public UndoToken(FeedItem item, int position) { - this.itemId = item.getId(); - this.feedId = item.getFeed().getId(); - this.position = position; - } - - private UndoToken(Parcel in) { - itemId = in.readLong(); - feedId = in.readLong(); - position = in.readInt(); - } - - public static final Parcelable.Creator<UndoToken> CREATOR = new Parcelable.Creator<UndoToken>() { - public UndoToken createFromParcel(Parcel in) { - return new UndoToken(in); - } - - public UndoToken[] newArray(int size) { - return new UndoToken[size]; - } - }; - - public int describeContents() { - return 0; - } - - public void writeToParcel(Parcel out, int flags) { - out.writeLong(itemId); - out.writeLong(feedId); - out.writeInt(position); - } - - public long getFeedItemId() { - return itemId; - } - - public int getPosition() { - return position; - } - } - -} diff --git a/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java b/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java deleted file mode 100644 index ba3d12105..000000000 --- a/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java +++ /dev/null @@ -1,56 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.os.Bundle; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.app.ActionBarActivity; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; -import de.danoeh.antennapod.preferences.UserPreferences; -import de.danoeh.antennapod.storage.DBWriter; - -public class PlaybackHistoryActivity extends ActionBarActivity { - private static final String TAG = "PlaybackHistoryActivity"; - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.clear_history_item, Menu.NONE, - R.string.clear_history_label), - MenuItem.SHOW_AS_ACTION_IF_ROOM); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - case R.id.clear_history_item: - DBWriter.clearPlaybackHistory(this); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - protected void onCreate(Bundle arg0) { - setTheme(UserPreferences.getTheme()); - super.onCreate(arg0); - - if (BuildConfig.DEBUG) - Log.d(TAG, "Activity created"); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.playbackhistory_activity); - - FragmentTransaction fT = getSupportFragmentManager().beginTransaction(); - fT.replace(R.id.playbackhistory_fragment, new PlaybackHistoryFragment()); - fT.commit(); - } - -} diff --git a/src/de/danoeh/antennapod/activity/SearchActivity.java b/src/de/danoeh/antennapod/activity/SearchActivity.java deleted file mode 100644 index f330aeb7d..000000000 --- a/src/de/danoeh/antennapod/activity/SearchActivity.java +++ /dev/null @@ -1,198 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.SearchManager; -import android.content.Intent; -import android.os.Bundle; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.app.ActionBarActivity; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; -import android.widget.TextView; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.adapter.SearchlistAdapter; -import de.danoeh.antennapod.feed.Feed; -import de.danoeh.antennapod.feed.FeedItem; -import de.danoeh.antennapod.feed.SearchResult; -import de.danoeh.antennapod.fragment.FeedlistFragment; -import de.danoeh.antennapod.fragment.ItemlistFragment; -import de.danoeh.antennapod.preferences.UserPreferences; -import de.danoeh.antennapod.storage.FeedSearcher; - -import java.util.ArrayList; -import java.util.List; - -/** - * Displays the results when the user searches for FeedItems or Feeds. - */ -public class SearchActivity extends ActionBarActivity implements AdapterView.OnItemClickListener { - private static final String TAG = "SearchActivity"; - - public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.searchactivity.extra.feedId"; - - private SearchlistAdapter searchAdapter; - - /** - * ID of the feed that is being searched or null if the search is global. - */ - private long feedID; - - private ListView listView; - private TextView txtvStatus; - - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.searchlist); - listView = (ListView) findViewById(android.R.id.list); - txtvStatus = (TextView) findViewById(android.R.id.empty); - - listView.setOnItemClickListener(this); - searchAdapter = new SearchlistAdapter(this, 0, new ArrayList<SearchResult>()); - listView.setAdapter(searchAdapter); - listView.setEmptyView(txtvStatus); - } - - @Override - protected void onNewIntent(Intent intent) { - setIntent(intent); - } - - @Override - protected void onResume() { - super.onResume(); - Intent intent = getIntent(); - if (Intent.ACTION_SEARCH.equals(intent.getAction())) { - if (intent.hasExtra(SearchActivity.EXTRA_FEED_ID)) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Found bundle extra"); - feedID = intent.getLongExtra(SearchActivity.EXTRA_FEED_ID, 0); - } - if (BuildConfig.DEBUG) - Log.d(TAG, "Starting search"); - String query = intent.getStringExtra(SearchManager.QUERY); - getSupportActionBar() - .setSubtitle( - getString(R.string.search_term_label) + "\"" - + query + "\""); - handleSearchRequest(query); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label) - .setIcon( - obtainStyledAttributes( - new int[]{R.attr.action_search}) - .getDrawable(0)), - (MenuItem.SHOW_AS_ACTION_IF_ROOM)); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - Intent intent = new Intent(this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - case R.id.search_item: - onSearchRequested(); - return true; - default: - return false; - } - } - - @Override - public boolean onSearchRequested() { - Bundle extra = null; - if (feedID != 0) { - extra = new Bundle(); - extra.putLong(EXTRA_FEED_ID, feedID); - } - startSearch(null, false, extra, false); - return true; - } - - @SuppressLint({"NewApi", "NewApi"}) - private void handleSearchRequest(final String query) { - if (searchAdapter != null) { - searchAdapter.clear(); - searchAdapter.notifyDataSetChanged(); - } - txtvStatus.setText(R.string.search_status_searching); - - Thread thread = new Thread() { - - @Override - public void run() { - Log.d(TAG, "Starting background work"); - final Activity activity = SearchActivity.this; - final List<SearchResult> result = FeedSearcher - .performSearch(activity, query, feedID); - activity.runOnUiThread(new Runnable() { - - @Override - public void run() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Background work finished"); - if (BuildConfig.DEBUG) - Log.d(TAG, "Found " + result.size() - + " results"); - - searchAdapter.clear(); - for (SearchResult s : result) { - searchAdapter.add(s); - } - searchAdapter.notifyDataSetChanged(); - txtvStatus - .setText(R.string.search_status_no_results); - if (!searchAdapter.isEmpty()) { - txtvStatus.setVisibility(View.GONE); - } else { - txtvStatus.setVisibility(View.VISIBLE); - } - } - }); - - } - }; - thread.start(); - - } - - @Override - public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) { - SearchResult selection = searchAdapter.getItem(position); - if (selection.getComponent().getClass() == Feed.class) { - Feed feed = (Feed) selection.getComponent(); - Intent launchIntent = new Intent(this, FeedItemlistActivity.class); - launchIntent.putExtra(FeedlistFragment.EXTRA_SELECTED_FEED, - feed.getId()); - startActivity(launchIntent); - - } else if (selection.getComponent().getClass() == FeedItem.class) { - FeedItem item = (FeedItem) selection.getComponent(); - Intent launchIntent = new Intent(this, ItemviewActivity.class); - launchIntent.putExtra(FeedlistFragment.EXTRA_SELECTED_FEED, item - .getFeed().getId()); - launchIntent.putExtra(ItemlistFragment.EXTRA_SELECTED_FEEDITEM, - item.getId()); - startActivity(launchIntent); - } - } -} diff --git a/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetActivity.java b/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetActivity.java deleted file mode 100644 index 7ee0a9ac9..000000000 --- a/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetActivity.java +++ /dev/null @@ -1,45 +0,0 @@ -package de.danoeh.antennapod.activity.gpoddernet; - -import android.app.SearchManager; -import android.content.Context; -import android.os.Bundle; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.app.ActionBarActivity; -import android.support.v7.widget.SearchView; -import android.view.Menu; -import android.view.MenuItem; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.preferences.UserPreferences; - -/** - * Created by daniel on 23.08.13. - */ -public class GpodnetActivity extends ActionBarActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label) - .setIcon( - obtainStyledAttributes( - new int[]{R.attr.action_search}) - .getDrawable(0)), - MenuItem.SHOW_AS_ACTION_IF_ROOM); - MenuItemCompat.setActionView(menu.findItem(R.id.search_item), new SearchView(this)); - - SearchManager searchManager = - (SearchManager) getSystemService(Context.SEARCH_SERVICE); - SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item)); - searchView.setIconifiedByDefault(true); - searchView.setSearchableInfo( - searchManager.getSearchableInfo(getComponentName())); - - return true; - } -} diff --git a/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetMainActivity.java b/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetMainActivity.java deleted file mode 100644 index e77a6a7a3..000000000 --- a/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetMainActivity.java +++ /dev/null @@ -1,88 +0,0 @@ -package de.danoeh.antennapod.activity.gpoddernet; - -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentStatePagerAdapter; -import android.support.v4.app.NavUtils; -import android.support.v4.view.ViewPager; -import android.view.MenuItem; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.fragment.gpodnet.PodcastTopListFragment; -import de.danoeh.antennapod.fragment.gpodnet.SuggestionListFragment; -import de.danoeh.antennapod.fragment.gpodnet.TagListFragment; - -/** - * Created by daniel on 22.08.13. - */ -public class GpodnetMainActivity extends GpodnetActivity { - private static final String TAG = "GPodnetMainActivity"; - - private static final int POS_TAGS = 0; - private static final int POS_TOPLIST = 1; - private static final int POS_SUGGESTIONS = 2; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.gpodnet_main); - ViewPager viewpager = (ViewPager) findViewById(R.id.viewpager); - viewpager.setAdapter(new PagerAdapter(getSupportFragmentManager())); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - } - return super.onOptionsItemSelected(item); - } - - private class PagerAdapter extends FragmentStatePagerAdapter { - - private static final int NUM_PAGES_LOGGED_OUT = 2; - private static final int NUM_PAGES_LOGGED_IN = 3; - private final int NUM_PAGES; - - public PagerAdapter(FragmentManager fm) { - super(fm); - NUM_PAGES = NUM_PAGES_LOGGED_OUT; - } - - @Override - public Fragment getItem(int i) { - switch (i) { - case POS_TAGS: - return new TagListFragment(); - case POS_TOPLIST: - return new PodcastTopListFragment(); - case POS_SUGGESTIONS: - return new SuggestionListFragment(); - default: - return null; - } - } - - @Override - public CharSequence getPageTitle(int position) { - switch (position) { - case POS_TAGS: - return getString(R.string.gpodnet_taglist_header); - case POS_TOPLIST: - return getString(R.string.gpodnet_toplist_header); - case POS_SUGGESTIONS: - return getString(R.string.gpodnet_suggestions_header); - default: - return super.getPageTitle(position); - } - } - - @Override - public int getCount() { - return NUM_PAGES; - } - } -} diff --git a/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetSearchActivity.java b/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetSearchActivity.java deleted file mode 100644 index 199b45dc9..000000000 --- a/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetSearchActivity.java +++ /dev/null @@ -1,63 +0,0 @@ -package de.danoeh.antennapod.activity.gpoddernet; - -import android.app.SearchManager; -import android.content.Intent; -import android.os.Bundle; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.app.NavUtils; -import android.view.MenuItem; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.fragment.gpodnet.SearchListFragment; -import org.apache.commons.lang3.StringUtils; - -/** - * Created by daniel on 23.08.13. - */ -public class GpodnetSearchActivity extends GpodnetActivity { - - private SearchListFragment searchFragment; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.gpodnet_search); - } - - @Override - protected void onResume() { - super.onResume(); - Intent intent = getIntent(); - if (StringUtils.equals(intent.getAction(), Intent.ACTION_SEARCH)) { - handleSearchRequest(intent.getStringExtra(SearchManager.QUERY)); - } - } - - @Override - protected void onNewIntent(Intent intent) { - setIntent(intent); - } - - private void handleSearchRequest(String query) { - getSupportActionBar().setSubtitle(getString(R.string.search_term_label) + query); - if (searchFragment == null) { - FragmentTransaction transaction = getSupportFragmentManager() - .beginTransaction(); - searchFragment = SearchListFragment.newInstance(query); - transaction.replace(R.id.searchListFragment, searchFragment); - transaction.commit(); - } else { - searchFragment.changeQuery(query); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - } - return super.onOptionsItemSelected(item); - } -} diff --git a/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetTagActivity.java b/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetTagActivity.java deleted file mode 100644 index 14897c60c..000000000 --- a/src/de/danoeh/antennapod/activity/gpoddernet/GpodnetTagActivity.java +++ /dev/null @@ -1,63 +0,0 @@ -package de.danoeh.antennapod.activity.gpoddernet; - -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.app.NavUtils; -import android.view.MenuItem; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.fragment.gpodnet.PodcastListFragment; -import de.danoeh.antennapod.gpoddernet.GpodnetService; -import de.danoeh.antennapod.gpoddernet.GpodnetServiceException; -import de.danoeh.antennapod.gpoddernet.model.GpodnetPodcast; -import de.danoeh.antennapod.gpoddernet.model.GpodnetTag; - -import java.util.List; - -/** - * Created by daniel on 23.08.13. - */ -public class GpodnetTagActivity extends GpodnetActivity{ - - private static final int PODCAST_COUNT = 50; - public static final String ARG_TAGNAME = "tagname"; - - private GpodnetTag tag; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.gpodnet_tag_activity); - - if (!getIntent().hasExtra(ARG_TAGNAME)) { - throw new IllegalArgumentException("No tagname argument"); - } - tag = new GpodnetTag(getIntent().getStringExtra(ARG_TAGNAME)); - getSupportActionBar().setTitle(tag.getName()); - - FragmentTransaction transaction = getSupportFragmentManager() - .beginTransaction(); - Fragment taglistFragment = new TaglistFragment(); - transaction.replace(R.id.taglistFragment, taglistFragment); - transaction.commit(); - } - - private class TaglistFragment extends PodcastListFragment { - - @Override - protected List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException { - return service.getPodcastsForTag(tag, PODCAST_COUNT); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - } - return super.onOptionsItemSelected(item); - } -} diff --git a/src/de/danoeh/antennapod/adapter/ActionButtonUtils.java b/src/de/danoeh/antennapod/adapter/ActionButtonUtils.java new file mode 100644 index 000000000..17c61a86c --- /dev/null +++ b/src/de/danoeh/antennapod/adapter/ActionButtonUtils.java @@ -0,0 +1,67 @@ +package de.danoeh.antennapod.adapter; + +import android.content.Context; +import android.content.res.TypedArray; +import android.view.View; +import android.widget.ImageButton; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.storage.DownloadRequester; + +/** + * Utility methods for the action button that is displayed on the right hand side + * of a listitem. + */ +public class ActionButtonUtils { + + private final int[] labels; + private final TypedArray drawables; + private final Context context; + + public ActionButtonUtils(Context context) { + if (context == null) throw new IllegalArgumentException("context = null"); + this.context = context; + drawables = context.obtainStyledAttributes(new int[]{ + R.attr.av_play, R.attr.navigation_cancel, R.attr.av_download, R.attr.navigation_chapters}); + labels = new int[]{R.string.play_label, R.string.cancel_download_label, R.string.download_label}; + } + + /** + * 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) { + if (butSecondary == null || item == null) throw new IllegalArgumentException("butSecondary or item was null"); + final FeedMedia media = item.getMedia(); + if (media != null) { + final boolean isDownloadingMedia = DownloadRequester.getInstance().isDownloadingFile(media); + if (!media.isDownloaded()) { + if (isDownloadingMedia) { + // item is being downloaded + butSecondary.setVisibility(View.VISIBLE); + butSecondary.setImageDrawable(drawables + .getDrawable(1)); + butSecondary.setContentDescription(context.getString(labels[1])); + } else { + // item is not downloaded and not being downloaded + butSecondary.setVisibility(View.VISIBLE); + butSecondary.setImageDrawable(drawables.getDrawable(2)); + butSecondary.setContentDescription(context.getString(labels[2])); + } + } else { + // item is not being downloaded + butSecondary.setVisibility(View.VISIBLE); + if (media.isPlaying()) { + butSecondary.setImageDrawable(drawables.getDrawable(3)); + } else { + butSecondary + .setImageDrawable(drawables.getDrawable(0)); + } + butSecondary.setContentDescription(context.getString(labels[0])); + } + } else { + butSecondary.setVisibility(View.INVISIBLE); + } + } +} diff --git a/src/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java b/src/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java new file mode 100644 index 000000000..3acd587af --- /dev/null +++ b/src/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java @@ -0,0 +1,49 @@ +package de.danoeh.antennapod.adapter; + +import android.content.Context; +import android.widget.Toast; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.storage.DBTasks; +import de.danoeh.antennapod.storage.DownloadRequestException; +import de.danoeh.antennapod.storage.DownloadRequester; + +/** + * Default implementation of an ActionButtonCallback + */ +public class DefaultActionButtonCallback implements ActionButtonCallback { + private static final String TAG = "DefaultActionButtonCallback"; + + private final Context context; + + public DefaultActionButtonCallback(Context context) { + if (context == null) throw new IllegalArgumentException("context = null"); + this.context = context; + } + + @Override + public void onActionButtonPressed(final FeedItem item) { + final FeedMedia media = item.getMedia(); + if (media == null) { + return; + } + + boolean isDownloading = DownloadRequester.getInstance().isDownloadingFile(media); + if (!isDownloading && !media.isDownloaded()) { + 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()); + } + } else if (isDownloading) { + DownloadRequester.getInstance().cancelDownload(context, media); + Toast.makeText(context, R.string.download_cancelled_msg, Toast.LENGTH_SHORT).show(); + } else { // media is downloaded + DBTasks.playMedia(context, media, true, true, false); + } + } +} diff --git a/src/de/danoeh/antennapod/adapter/DefaultFeedItemlistAdapter.java b/src/de/danoeh/antennapod/adapter/DefaultFeedItemlistAdapter.java deleted file mode 100644 index e384ecffc..000000000 --- a/src/de/danoeh/antennapod/adapter/DefaultFeedItemlistAdapter.java +++ /dev/null @@ -1,129 +0,0 @@ -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.ViewGroup; -import android.widget.Adapter; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.feed.FeedItem; -import de.danoeh.antennapod.feed.MediaType; -import de.danoeh.antennapod.util.Converter; - -public class DefaultFeedItemlistAdapter extends BaseAdapter { - - ItemAccess itemAccess; - private Context context; - - public DefaultFeedItemlistAdapter(Context context, ItemAccess itemAccess) { - super(); - this.context = context; - if (itemAccess == null) { - throw new IllegalArgumentException("itemAccess must not be null"); - } - this.itemAccess = itemAccess; - } - - @Override - public int getCount() { - return itemAccess.getCount(); - - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public FeedItem getItem(int position) { - return itemAccess.getItem(position); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - Holder holder; - final FeedItem item = getItem(position); - - if (convertView == null) { - holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(R.layout.default_feeditemlist_item, null); - holder.title = (TextView) convertView - .findViewById(R.id.txtvItemname); - holder.lenSize = (TextView) convertView - .findViewById(R.id.txtvLenSize); - - holder.published = (TextView) convertView - .findViewById(R.id.txtvPublished); - holder.type = (ImageView) convertView.findViewById(R.id.imgvType); - convertView.setTag(holder); - - } else { - holder = (Holder) convertView.getTag(); - } - if (!(getItemViewType(position) == Adapter.IGNORE_ITEM_VIEW_TYPE)) { - convertView.setVisibility(View.VISIBLE); - holder.title.setText(item.getTitle()); - holder.published.setText(convertView.getResources().getString( - R.string.published_prefix) - + DateUtils.getRelativeTimeSpanString( - item.getPubDate().getTime(), - System.currentTimeMillis(), 0, 0)); - if (item.getMedia() == null) { - holder.type.setVisibility(View.GONE); - holder.lenSize.setVisibility(View.GONE); - } else { - holder.lenSize.setVisibility(View.VISIBLE); - holder.lenSize.setText(convertView.getResources().getString( - R.string.size_prefix) - + Converter.byteToString(item.getMedia().getSize())); - - TypedArray typeDrawables = context - .obtainStyledAttributes(new int[] { R.attr.type_audio, - R.attr.type_video }); - MediaType mediaType = item.getMedia().getMediaType(); - if (mediaType == MediaType.AUDIO) { - holder.type.setImageDrawable(typeDrawables.getDrawable(0)); - holder.type.setContentDescription(context.getString(R.string.media_type_audio_label)); - holder.type.setVisibility(View.VISIBLE); - } else if (mediaType == MediaType.VIDEO) { - holder.type.setImageDrawable(typeDrawables.getDrawable(1)); - holder.type.setContentDescription(context.getString(R.string.media_type_video_label)); - holder.type.setVisibility(View.VISIBLE); - } else { - holder.type.setImageBitmap(null); - holder.type.setVisibility(View.GONE); - } - } - - } else { - convertView.setVisibility(View.GONE); - } - return convertView; - } - - protected static class Holder { - TextView title; - TextView published; - TextView lenSize; - ImageView type; - - } - - public static interface ItemAccess { - int getCount(); - - FeedItem getItem(int position); - } - - protected Context getContext() { - return context; - } -} diff --git a/src/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java b/src/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java new file mode 100644 index 000000000..0bf770df2 --- /dev/null +++ b/src/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java @@ -0,0 +1,119 @@ +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.TextView; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.asynctask.ImageLoader; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.util.Converter; + +/** + * Shows a list of downloaded episodes + */ +public class DownloadedEpisodesListAdapter extends BaseAdapter { + + private final Context context; + private final ItemAccess itemAccess; + + public DownloadedEpisodesListAdapter(Context context, ItemAccess itemAccess) { + super(); + this.context = context; + this.itemAccess = itemAccess; + } + + @Override + public int getCount() { + return itemAccess.getCount(); + } + + @Override + public FeedItem 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.downloaded_episodeslist_item, + null); + holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); + holder.pubDate = (TextView) convertView + .findViewById(R.id.txtvPublished); + holder.butSecondary = (ImageButton) convertView + .findViewById(R.id.butSecondaryAction); + holder.imageView = (ImageView) convertView.findViewById(R.id.imgvImage); + holder.txtvSize = (TextView) convertView.findViewById(R.id.txtvSize); + convertView.setTag(holder); + } else { + holder = (Holder) convertView.getTag(); + } + + holder.title.setText(item.getTitle()); + holder.pubDate.setText(DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_SHOW_DATE)); + holder.txtvSize.setText(Converter.byteToString(item.getMedia().getSize())); + FeedItem.State state = item.getState(); + + if (state == FeedItem.State.PLAYING) { + holder.butSecondary.setEnabled(false); + } else { + holder.butSecondary.setEnabled(true); + } + + holder.butSecondary.setFocusable(false); + holder.butSecondary.setTag(item); + holder.butSecondary.setOnClickListener(secondaryActionListener); + + + ImageLoader.getInstance().loadThumbnailBitmap( + item, + holder.imageView, + (int) convertView.getResources().getDimension( + R.dimen.thumbnail_length) + ); + return convertView; + } + + private View.OnClickListener secondaryActionListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + FeedItem item = (FeedItem) v.getTag(); + itemAccess.onFeedItemSecondaryAction(item); + } + }; + + + static class Holder { + TextView title; + TextView pubDate; + ImageView imageView; + TextView txtvSize; + ImageButton butSecondary; + } + + public interface ItemAccess { + int getCount(); + + FeedItem getItem(int position); + + void onFeedItemSecondaryAction(FeedItem item); + } +} diff --git a/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java b/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java index 2739d2f27..fa2e5a0a7 100644 --- a/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java +++ b/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java @@ -4,7 +4,8 @@ import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; +import android.widget.ImageButton; import android.widget.ProgressBar; import android.widget.TextView; import de.danoeh.antennapod.R; @@ -14,86 +15,128 @@ import de.danoeh.antennapod.service.download.Downloader; import de.danoeh.antennapod.util.Converter; import de.danoeh.antennapod.util.ThemeUtils; -import java.util.List; - -public class DownloadlistAdapter extends ArrayAdapter<Downloader> { - private int selectedItemIndex; - - public static final int SELECTION_NONE = -1; - - public DownloadlistAdapter(Context context, int textViewResourceId, - List<Downloader> objects) { - super(context, textViewResourceId, objects); - this.selectedItemIndex = SELECTION_NONE; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - Holder holder; - DownloadRequest request = getItem(position).getDownloadRequest(); - // Inflate layout - if (convertView == null) { - holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(R.layout.downloadlist_item, null); - holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); - holder.message = (TextView) convertView - .findViewById(R.id.txtvMessage); - holder.downloaded = (TextView) convertView - .findViewById(R.id.txtvDownloaded); - holder.percent = (TextView) convertView - .findViewById(R.id.txtvPercent); - holder.progbar = (ProgressBar) convertView - .findViewById(R.id.progProgress); - - convertView.setTag(holder); - } else { - holder = (Holder) convertView.getTag(); - } - - if (position == selectedItemIndex) { - convertView.setBackgroundColor(convertView.getResources().getColor( - ThemeUtils.getSelectionBackgroundColor())); - } else { - convertView.setBackgroundResource(0); - } - - holder.title.setText(request.getTitle()); - if (request.getStatusMsg() != 0) { - holder.message.setText(request.getStatusMsg()); - } - String strDownloaded = Converter.byteToString(request.getSoFar()); - if (request.getSize() != DownloadStatus.SIZE_UNKNOWN) { - strDownloaded += " / " + Converter.byteToString(request.getSize()); - holder.percent.setText(request.getProgressPercent() + "%"); - holder.progbar.setProgress(request.getProgressPercent()); - holder.percent.setVisibility(View.VISIBLE); - } else { - holder.progbar.setProgress(0); - holder.percent.setVisibility(View.INVISIBLE); - } - - holder.downloaded.setText(strDownloaded); - - return convertView; - } - - static class Holder { - TextView title; - TextView message; - TextView downloaded; - TextView percent; - ProgressBar progbar; - } - - public int getSelectedItemIndex() { - return selectedItemIndex; - } - - public void setSelectedItemIndex(int selectedItemIndex) { - this.selectedItemIndex = selectedItemIndex; - notifyDataSetChanged(); - } +public class DownloadlistAdapter extends BaseAdapter { + + public static final int SELECTION_NONE = -1; + + private int selectedItemIndex; + private ItemAccess itemAccess; + private Context context; + + public DownloadlistAdapter(Context context, + ItemAccess itemAccess) { + super(); + this.selectedItemIndex = SELECTION_NONE; + this.context = context; + this.itemAccess = itemAccess; + } + + @Override + public int getCount() { + return itemAccess.getCount(); + } + + @Override + public Downloader 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; + Downloader downloader = getItem(position); + DownloadRequest request = downloader.getDownloadRequest(); + // Inflate layout + if (convertView == null) { + holder = new Holder(); + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + convertView = inflater.inflate(R.layout.downloadlist_item, null); + holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); + holder.message = (TextView) convertView + .findViewById(R.id.txtvMessage); + holder.downloaded = (TextView) convertView + .findViewById(R.id.txtvDownloaded); + holder.percent = (TextView) convertView + .findViewById(R.id.txtvPercent); + holder.progbar = (ProgressBar) convertView + .findViewById(R.id.progProgress); + holder.butSecondary = (ImageButton) convertView + .findViewById(R.id.butSecondaryAction); + + convertView.setTag(holder); + } else { + holder = (Holder) convertView.getTag(); + } + + if (position == selectedItemIndex) { + convertView.setBackgroundColor(convertView.getResources().getColor( + ThemeUtils.getSelectionBackgroundColor())); + } else { + convertView.setBackgroundResource(0); + } + + holder.title.setText(request.getTitle()); + if (request.getStatusMsg() != 0) { + holder.message.setText(request.getStatusMsg()); + } + String strDownloaded = Converter.byteToString(request.getSoFar()); + if (request.getSize() != DownloadStatus.SIZE_UNKNOWN) { + strDownloaded += " / " + Converter.byteToString(request.getSize()); + holder.percent.setText(request.getProgressPercent() + "%"); + holder.progbar.setProgress(request.getProgressPercent()); + holder.percent.setVisibility(View.VISIBLE); + } else { + holder.progbar.setProgress(0); + holder.percent.setVisibility(View.INVISIBLE); + } + + holder.downloaded.setText(strDownloaded); + + holder.butSecondary.setFocusable(false); + holder.butSecondary.setTag(downloader); + holder.butSecondary.setOnClickListener(butSecondaryListener); + + return convertView; + } + + private View.OnClickListener butSecondaryListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + Downloader downloader = (Downloader) v.getTag(); + itemAccess.onSecondaryActionClick(downloader); + } + }; + + static class Holder { + TextView title; + TextView message; + TextView downloaded; + TextView percent; + ProgressBar progbar; + ImageButton butSecondary; + } + + public int getSelectedItemIndex() { + return selectedItemIndex; + } + + public void setSelectedItemIndex(int selectedItemIndex) { + this.selectedItemIndex = selectedItemIndex; + notifyDataSetChanged(); + } + + public interface ItemAccess { + public int getCount(); + + public Downloader getItem(int position); + + public void onSecondaryActionClick(Downloader downloader); + } } diff --git a/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java b/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java index aa724f991..9a7b607aa 100644 --- a/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java +++ b/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java @@ -227,10 +227,10 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter { .getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.feeditemlist_header, null); TextView headerTitle = (TextView) convertView - .findViewById(R.id.txtvHeaderTitle); + .findViewById(0); ImageButton actionButton = (ImageButton) convertView .findViewById(R.id.butAction); - TextView numItems = (TextView) convertView.findViewById(R.id.txtvNumItems); + TextView numItems = (TextView) convertView.findViewById(0); String headerString = null; int childrenCount = 0; diff --git a/src/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java b/src/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java new file mode 100644 index 000000000..5475f122f --- /dev/null +++ b/src/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java @@ -0,0 +1,243 @@ +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; +import android.view.ViewGroup; +import android.widget.*; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.feed.MediaType; +import de.danoeh.antennapod.storage.DownloadRequester; +import de.danoeh.antennapod.util.Converter; +import de.danoeh.antennapod.util.ThemeUtils; + +/** + * List adapter for items of feeds that the user has already subscribed to. + */ +public class FeedItemlistAdapter extends BaseAdapter { + + private ActionButtonCallback callback; + private final ItemAccess itemAccess; + private final Context context; + private boolean showFeedtitle; + private int selectedItemIndex; + private final ActionButtonUtils actionButtonUtils; + + public static final int SELECTION_NONE = -1; + + public FeedItemlistAdapter(Context context, + ItemAccess itemAccess, + ActionButtonCallback callback, boolean showFeedtitle) { + super(); + this.callback = callback; + this.context = context; + this.itemAccess = itemAccess; + this.showFeedtitle = showFeedtitle; + this.selectedItemIndex = SELECTION_NONE; + this.actionButtonUtils = new ActionButtonUtils(context); + } + + @Override + public int getCount() { + return itemAccess.getCount(); + + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public FeedItem getItem(int position) { + return itemAccess.getItem(position); + } + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + Holder holder; + final FeedItem item = getItem(position); + + if (convertView == null) { + holder = new Holder(); + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + convertView = inflater.inflate(R.layout.feeditemlist_item, null); + holder.title = (TextView) convertView + .findViewById(R.id.txtvItemname); + holder.lenSize = (TextView) convertView + .findViewById(R.id.txtvLenSize); + holder.butAction = (ImageButton) convertView + .findViewById(R.id.butSecondaryAction); + holder.published = (TextView) convertView + .findViewById(R.id.txtvPublished); + holder.inPlaylist = (ImageView) convertView + .findViewById(R.id.imgvInPlaylist); + holder.type = (ImageView) convertView.findViewById(R.id.imgvType); + holder.statusUnread = (View) convertView + .findViewById(R.id.statusUnread); + holder.episodeProgress = (ProgressBar) convertView + .findViewById(R.id.pbar_episode_progress); + + convertView.setTag(holder); + } else { + holder = (Holder) convertView.getTag(); + } + if (!(getItemViewType(position) == Adapter.IGNORE_ITEM_VIEW_TYPE)) { + convertView.setVisibility(View.VISIBLE); + if (position == selectedItemIndex) { + convertView.setBackgroundColor(convertView.getResources() + .getColor(ThemeUtils.getSelectionBackgroundColor())); + } else { + convertView.setBackgroundResource(0); + } + + StringBuilder buffer = new StringBuilder(item.getTitle()); + if (showFeedtitle) { + buffer.append("("); + buffer.append(item.getFeed().getTitle()); + buffer.append(")"); + } + holder.title.setText(buffer.toString()); + + FeedItem.State state = item.getState(); + switch (state) { + case PLAYING: + holder.statusUnread.setVisibility(View.GONE); + holder.episodeProgress.setVisibility(View.VISIBLE); + break; + case IN_PROGRESS: + holder.statusUnread.setVisibility(View.GONE); + holder.episodeProgress.setVisibility(View.VISIBLE); + break; + case NEW: + holder.statusUnread.setVisibility(View.VISIBLE); + break; + default: + holder.statusUnread.setVisibility(View.GONE); + break; + } + + holder.published.setText(DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_SHOW_DATE)); + + + FeedMedia media = item.getMedia(); + if (media == null) { + holder.episodeProgress.setVisibility(View.GONE); + holder.inPlaylist.setVisibility(View.INVISIBLE); + holder.type.setVisibility(View.INVISIBLE); + holder.lenSize.setVisibility(View.INVISIBLE); + } else { + + if (state == FeedItem.State.PLAYING + || state == FeedItem.State.IN_PROGRESS) { + if (media.getDuration() > 0) { + holder.episodeProgress + .setProgress((int) (((double) media + .getPosition()) / media.getDuration() * 100)); + holder.lenSize.setText(Converter + .getDurationStringLong(media.getDuration() + - media.getPosition())); + } + } else if (!media.isDownloaded()) { + holder.lenSize.setText(context.getString( + R.string.size_prefix) + + Converter.byteToString(media.getSize())); + } else { + holder.lenSize.setText(context.getString( + R.string.length_prefix) + + Converter.getDurationStringLong(media + .getDuration())); + } + + holder.lenSize.setVisibility(View.VISIBLE); + if (((ItemAccess) itemAccess).isInQueue(item)) { + holder.inPlaylist.setVisibility(View.VISIBLE); + } else { + holder.inPlaylist.setVisibility(View.INVISIBLE); + } + + if (DownloadRequester.getInstance().isDownloadingFile( + item.getMedia())) { + holder.episodeProgress.setVisibility(View.VISIBLE); + holder.episodeProgress.setProgress(((ItemAccess) itemAccess).getItemDownloadProgressPercent(item)); + } else if (!(state == FeedItem.State.IN_PROGRESS + || state == FeedItem.State.PLAYING)) { + holder.episodeProgress.setVisibility(View.GONE); + } + + TypedArray typeDrawables = context.obtainStyledAttributes( + new int[]{R.attr.type_audio, R.attr.type_video}); + final int[] labels = new int[]{R.string.media_type_audio_label, R.string.media_type_video_label}; + + MediaType mediaType = item.getMedia().getMediaType(); + if (mediaType == MediaType.AUDIO) { + holder.type.setImageDrawable(typeDrawables.getDrawable(0)); + holder.type.setContentDescription(context.getString(labels[0])); + holder.type.setVisibility(View.VISIBLE); + } else if (mediaType == MediaType.VIDEO) { + holder.type.setImageDrawable(typeDrawables.getDrawable(1)); + holder.type.setContentDescription(context.getString(labels[1])); + holder.type.setVisibility(View.VISIBLE); + } else { + holder.type.setImageBitmap(null); + holder.type.setVisibility(View.GONE); + } + } + + actionButtonUtils.configureActionButton(holder.butAction, item); + holder.butAction.setFocusable(false); + holder.butAction.setTag(item); + holder.butAction.setOnClickListener(butActionListener); + + } else { + convertView.setVisibility(View.GONE); + } + return convertView; + + } + + private final OnClickListener butActionListener = new OnClickListener() { + @Override + public void onClick(View v) { + FeedItem item = (FeedItem) v.getTag(); + callback.onActionButtonPressed(item); + } + }; + + static class Holder { + TextView title; + TextView published; + TextView lenSize; + ImageView type; + ImageView inPlaylist; + ImageButton butAction; + View statusUnread; + ProgressBar episodeProgress; + } + + public int getSelectedItemIndex() { + return selectedItemIndex; + } + + public void setSelectedItemIndex(int selectedItemIndex) { + this.selectedItemIndex = selectedItemIndex; + notifyDataSetChanged(); + } + + public static interface ItemAccess { + public boolean isInQueue(FeedItem item); + + int getItemDownloadProgressPercent(FeedItem item); + + int getCount(); + + FeedItem getItem(int position); + } + +} diff --git a/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java b/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java index 37600838e..30c1ff880 100644 --- a/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java +++ b/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java @@ -118,7 +118,7 @@ public class FeedlistAdapter extends BaseAdapter { holder.inProgressEpisodesLabel.setVisibility(View.INVISIBLE); } } - final String imageUrl = (feed.getImage() != null) ? feed.getImage() + final String imageUrl = (feed.getImage() != null && feed.getImage().isDownloaded()) ? feed.getImage() .getFile_url() : null; imageLoader.loadThumbnailBitmap( feed.getImage(), diff --git a/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java b/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java deleted file mode 100644 index 4681284f5..000000000 --- a/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java +++ /dev/null @@ -1,230 +0,0 @@ -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; -import android.view.ViewGroup; -import android.widget.*; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.feed.FeedItem; -import de.danoeh.antennapod.feed.FeedMedia; -import de.danoeh.antennapod.feed.MediaType; -import de.danoeh.antennapod.storage.DownloadRequester; -import de.danoeh.antennapod.util.Converter; -import de.danoeh.antennapod.util.ThemeUtils; - -/** List adapter for items of feeds that the user has already subscribed to. */ -public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter { - - private ActionButtonCallback callback; - private boolean showFeedtitle; - private int selectedItemIndex; - - public static final int SELECTION_NONE = -1; - - public InternalFeedItemlistAdapter(Context context, - ItemAccess itemAccess, - ActionButtonCallback callback, boolean showFeedtitle) { - super(context, itemAccess); - this.callback = callback; - this.showFeedtitle = showFeedtitle; - this.selectedItemIndex = SELECTION_NONE; - } - - @Override - public View getView(final int position, View convertView, ViewGroup parent) { - Holder holder; - final FeedItem item = getItem(position); - - if (convertView == null) { - holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(R.layout.feeditemlist_item, null); - holder.title = (TextView) convertView - .findViewById(R.id.txtvItemname); - holder.lenSize = (TextView) convertView - .findViewById(R.id.txtvLenSize); - holder.butAction = (ImageButton) convertView - .findViewById(R.id.butAction); - holder.published = (TextView) convertView - .findViewById(R.id.txtvPublished); - holder.inPlaylist = (ImageView) convertView - .findViewById(R.id.imgvInPlaylist); - holder.downloaded = (ImageView) convertView - .findViewById(R.id.imgvDownloaded); - holder.type = (ImageView) convertView.findViewById(R.id.imgvType); - holder.downloading = (ImageView) convertView - .findViewById(R.id.imgvDownloading); - if (showFeedtitle) { - holder.feedtitle = (TextView) convertView - .findViewById(R.id.txtvFeedname); - } - holder.statusPlaying = (View) convertView - .findViewById(R.id.statusPlaying); - holder.statusUnread = (View) convertView - .findViewById(R.id.statusUnread); - holder.episodeProgress = (ProgressBar) convertView - .findViewById(R.id.pbar_episode_progress); - - convertView.setTag(holder); - } else { - holder = (Holder) convertView.getTag(); - } - if (!(getItemViewType(position) == Adapter.IGNORE_ITEM_VIEW_TYPE)) { - convertView.setVisibility(View.VISIBLE); - if (position == selectedItemIndex) { - convertView.setBackgroundColor(convertView.getResources() - .getColor(ThemeUtils.getSelectionBackgroundColor())); - } else { - convertView.setBackgroundResource(0); - } - - holder.title.setText(item.getTitle()); - if (showFeedtitle) { - holder.feedtitle.setVisibility(View.VISIBLE); - holder.feedtitle.setText(item.getFeed().getTitle()); - } - - FeedItem.State state = item.getState(); - switch (state) { - case PLAYING: - holder.statusPlaying.setVisibility(View.VISIBLE); - holder.statusUnread.setVisibility(View.GONE); - holder.episodeProgress.setVisibility(View.VISIBLE); - break; - case IN_PROGRESS: - holder.statusPlaying.setVisibility(View.GONE); - holder.statusUnread.setVisibility(View.GONE); - holder.episodeProgress.setVisibility(View.VISIBLE); - break; - case NEW: - holder.statusPlaying.setVisibility(View.GONE); - holder.statusUnread.setVisibility(View.VISIBLE); - holder.episodeProgress.setVisibility(View.GONE); - break; - default: - holder.statusPlaying.setVisibility(View.GONE); - holder.statusUnread.setVisibility(View.GONE); - holder.episodeProgress.setVisibility(View.GONE); - break; - } - - holder.published.setText(convertView.getResources().getString( - R.string.published_prefix) - + DateUtils.getRelativeTimeSpanString( - item.getPubDate().getTime(), - System.currentTimeMillis(), 0, 0)); - - FeedMedia media = item.getMedia(); - if (media == null) { - holder.downloaded.setVisibility(View.GONE); - holder.downloading.setVisibility(View.GONE); - holder.inPlaylist.setVisibility(View.GONE); - holder.type.setVisibility(View.GONE); - holder.lenSize.setVisibility(View.GONE); - } else { - - if (state == FeedItem.State.PLAYING - || state == FeedItem.State.IN_PROGRESS) { - if (media.getDuration() > 0) { - holder.episodeProgress - .setProgress((int) (((double) media - .getPosition()) / media.getDuration() * 100)); - holder.lenSize.setText(Converter - .getDurationStringLong(media.getDuration() - - media.getPosition())); - } - } else if (!media.isDownloaded()) { - holder.lenSize.setText(getContext().getString( - R.string.size_prefix) - + Converter.byteToString(media.getSize())); - } else { - holder.lenSize.setText(getContext().getString( - R.string.length_prefix) - + Converter.getDurationStringLong(media - .getDuration())); - } - - holder.lenSize.setVisibility(View.VISIBLE); - if (((ItemAccess) itemAccess).isInQueue(item)) { - holder.inPlaylist.setVisibility(View.VISIBLE); - } else { - holder.inPlaylist.setVisibility(View.GONE); - } - if (item.getMedia().isDownloaded()) { - holder.downloaded.setVisibility(View.VISIBLE); - } else { - holder.downloaded.setVisibility(View.GONE); - } - - if (DownloadRequester.getInstance().isDownloadingFile( - item.getMedia())) { - holder.downloading.setVisibility(View.VISIBLE); - } else { - holder.downloading.setVisibility(View.GONE); - } - - TypedArray typeDrawables = getContext().obtainStyledAttributes( - new int[] { R.attr.type_audio, R.attr.type_video }); - final int[] labels = new int[] {R.string.media_type_audio_label, R.string.media_type_video_label}; - - MediaType mediaType = item.getMedia().getMediaType(); - if (mediaType == MediaType.AUDIO) { - holder.type.setImageDrawable(typeDrawables.getDrawable(0)); - holder.type.setContentDescription(getContext().getString(labels[0])); - holder.type.setVisibility(View.VISIBLE); - } else if (mediaType == MediaType.VIDEO) { - holder.type.setImageDrawable(typeDrawables.getDrawable(1)); - holder.type.setContentDescription(getContext().getString(labels[1])); - holder.type.setVisibility(View.VISIBLE); - } else { - holder.type.setImageBitmap(null); - holder.type.setVisibility(View.GONE); - } - } - - holder.butAction.setFocusable(false); - holder.butAction.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - callback.onActionButtonPressed(item); - } - }); - - } else { - convertView.setVisibility(View.GONE); - } - return convertView; - - } - - static class Holder extends DefaultFeedItemlistAdapter.Holder { - TextView feedtitle; - ImageView inPlaylist; - ImageView downloaded; - ImageView downloading; - ImageButton butAction; - View statusUnread; - View statusPlaying; - ProgressBar episodeProgress; - } - - public int getSelectedItemIndex() { - return selectedItemIndex; - } - - public void setSelectedItemIndex(int selectedItemIndex) { - this.selectedItemIndex = selectedItemIndex; - notifyDataSetChanged(); - } - - public static interface ItemAccess extends DefaultFeedItemlistAdapter.ItemAccess { - public boolean isInQueue(FeedItem item); - } - -} diff --git a/src/de/danoeh/antennapod/adapter/MiroGuideChannelListAdapter.java b/src/de/danoeh/antennapod/adapter/MiroGuideChannelListAdapter.java deleted file mode 100644 index 4361b3af8..000000000 --- a/src/de/danoeh/antennapod/adapter/MiroGuideChannelListAdapter.java +++ /dev/null @@ -1,50 +0,0 @@ -package de.danoeh.antennapod.adapter; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.TextView; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.miroguide.model.MiroGuideChannel; - -import java.util.List; - -public class MiroGuideChannelListAdapter extends ArrayAdapter<MiroGuideChannel> { - - public MiroGuideChannelListAdapter(Context context, int textViewResourceId, - List<MiroGuideChannel> objects) { - super(context, textViewResourceId, objects); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - Holder holder; - MiroGuideChannel channel = getItem(position); - - // Inflate Layout - if (convertView == null) { - holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - convertView = inflater.inflate(R.layout.miroguide_channellist_item, null); - holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); - - convertView.setTag(holder); - } else { - holder = (Holder) convertView.getTag(); - } - - holder.title.setText(channel.getName()); - return convertView; - } - - static class Holder { - TextView title; - } - - - -} diff --git a/src/de/danoeh/antennapod/adapter/MiroGuideItemlistAdapter.java b/src/de/danoeh/antennapod/adapter/MiroGuideItemlistAdapter.java deleted file mode 100644 index 18a4b42cc..000000000 --- a/src/de/danoeh/antennapod/adapter/MiroGuideItemlistAdapter.java +++ /dev/null @@ -1,58 +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.ArrayAdapter; -import android.widget.TextView; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.miroguide.model.MiroGuideItem; - -import java.util.List; - -public class MiroGuideItemlistAdapter extends ArrayAdapter<MiroGuideItem> { - - public MiroGuideItemlistAdapter(Context context, int textViewResourceId, - List<MiroGuideItem> objects) { - super(context, textViewResourceId, objects); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - Holder holder; - MiroGuideItem item = getItem(position); - - // Inflate Layout - if (convertView == null) { - holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - convertView = inflater.inflate(R.layout.miroguide_itemlist_item, - null); - holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); - holder.date = (TextView) convertView.findViewById(R.id.txtvDate); - convertView.setTag(holder); - } else { - holder = (Holder) convertView.getTag(); - } - - holder.title.setText(item.getName()); - if (item.getDate() != null) { - holder.date.setText(DateUtils.getRelativeTimeSpanString( - item.getDate().getTime(), System.currentTimeMillis(), 0, 0)); - holder.date.setVisibility(View.VISIBLE); - } else { - holder.date.setVisibility(View.GONE); - } - return convertView; - } - - static class Holder { - TextView title; - TextView date; - } - -} diff --git a/src/de/danoeh/antennapod/adapter/NavListAdapter.java b/src/de/danoeh/antennapod/adapter/NavListAdapter.java new file mode 100644 index 000000000..928ec5dde --- /dev/null +++ b/src/de/danoeh/antennapod/adapter/NavListAdapter.java @@ -0,0 +1,198 @@ +package de.danoeh.antennapod.adapter; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.asynctask.ImageLoader; +import de.danoeh.antennapod.feed.Feed; + +/** + * BaseAdapter for the navigation drawer + */ +public class NavListAdapter extends BaseAdapter { + public static final int VIEW_TYPE_COUNT = 3; + public static final int VIEW_TYPE_NAV = 0; + public static final int VIEW_TYPE_SECTION_DIVIDER = 1; + public static final int VIEW_TYPE_SUBSCRIPTION = 2; + + public static final int[] NAV_TITLES = {R.string.all_episodes_label, R.string.queue_label, R.string.downloads_label, R.string.playback_history_label, R.string.add_feed_label}; + + private final Drawable[] drawables; + + public static final int SUBSCRIPTION_OFFSET = 1 + NAV_TITLES.length; + + private ItemAccess itemAccess; + private Context context; + + public NavListAdapter(ItemAccess itemAccess, Context context) { + this.itemAccess = itemAccess; + this.context = context; + + TypedArray ta = context.obtainStyledAttributes(new int[] {R.attr.ic_new, R.attr.stat_playlist, + R.attr.av_download, R.attr.device_access_time, R.attr.content_new}); + drawables = new Drawable[] {ta.getDrawable(0), ta.getDrawable(1), ta.getDrawable(2), + ta.getDrawable(3), ta.getDrawable(4)}; + ta.recycle(); + } + + @Override + public int getCount() { + return NAV_TITLES.length + 1 + itemAccess.getCount(); + } + + @Override + public Object getItem(int position) { + int viewType = getItemViewType(position); + if (viewType == VIEW_TYPE_NAV) { + return context.getString(NAV_TITLES[position]); + } else if (viewType == VIEW_TYPE_SECTION_DIVIDER) { + return context.getString(R.string.podcasts_label); + } else { + return itemAccess.getItem(position); + } + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public int getItemViewType(int position) { + if (0 <= position && position < NAV_TITLES.length) { + return VIEW_TYPE_NAV; + } else if (position < NAV_TITLES.length + 1) { + return VIEW_TYPE_SECTION_DIVIDER; + } else { + return VIEW_TYPE_SUBSCRIPTION; + } + } + + @Override + public int getViewTypeCount() { + return VIEW_TYPE_COUNT; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + int viewType = getItemViewType(position); + View v = null; + if (viewType == VIEW_TYPE_NAV) { + v = getNavView((String) getItem(position), position, convertView, parent); + } else if (viewType == VIEW_TYPE_SECTION_DIVIDER) { + v = getSectionDividerView((String) getItem(position), position, convertView, parent); + } else { + v = getFeedView(position - SUBSCRIPTION_OFFSET, convertView, parent); + } + if (v != null) { + TextView txtvTitle = (TextView) v.findViewById(R.id.txtvTitle); + if (position == itemAccess.getSelectedItemIndex()) { + txtvTitle.setTypeface(null, Typeface.BOLD); + } else { + txtvTitle.setTypeface(null, Typeface.NORMAL); + } + } + return v; + } + + private View getNavView(String title, int position, View convertView, ViewGroup parent) { + NavHolder holder; + if (convertView == null) { + holder = new NavHolder(); + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + convertView = inflater.inflate(R.layout.nav_listitem, null); + + holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); + holder.image = (ImageView) convertView.findViewById(R.id.imgvCover); + convertView.setTag(holder); + } else { + holder = (NavHolder) convertView.getTag(); + } + + holder.title.setText(title); + holder.image.setImageDrawable(drawables[position]); + + return convertView; + } + + private View getSectionDividerView(String title, int position, View convertView, ViewGroup parent) { + SectionHolder holder; + if (convertView == null) { + holder = new SectionHolder(); + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + convertView = inflater.inflate(R.layout.nav_section_item, null); + + holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); + convertView.setTag(holder); + } else { + holder = (SectionHolder) convertView.getTag(); + } + + holder.title.setText(title); + + convertView.setEnabled(false); + convertView.setOnClickListener(null); + + return convertView; + } + + private View getFeedView(int feedPos, View convertView, ViewGroup parent) { + FeedHolder holder; + Feed feed = itemAccess.getItem(feedPos); + + if (convertView == null) { + holder = new FeedHolder(); + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + convertView = inflater.inflate(R.layout.nav_feedlistitem, null); + + holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); + holder.image = (ImageView) convertView.findViewById(R.id.imgvCover); + convertView.setTag(holder); + } else { + holder = (FeedHolder) convertView.getTag(); + } + + holder.title.setText(feed.getTitle()); + ImageLoader.getInstance().loadThumbnailBitmap(feed.getImage(), holder.image, (int) context.getResources().getDimension(R.dimen.thumbnail_length_navlist)); + + return convertView; + } + + static class NavHolder { + TextView title; + ImageView image; + } + + static class SectionHolder { + TextView title; + } + + static class FeedHolder { + TextView title; + ImageView image; + } + + + public interface ItemAccess { + public int getCount(); + + public Feed getItem(int position); + + public int getSelectedItemIndex(); + } + +} diff --git a/src/de/danoeh/antennapod/adapter/NewEpisodesListAdapter.java b/src/de/danoeh/antennapod/adapter/NewEpisodesListAdapter.java new file mode 100644 index 000000000..4a959dfd2 --- /dev/null +++ b/src/de/danoeh/antennapod/adapter/NewEpisodesListAdapter.java @@ -0,0 +1,160 @@ +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.*; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.asynctask.ImageLoader; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.storage.DownloadRequester; +import de.danoeh.antennapod.util.Converter; + +/** + * List adapter for the list of new episodes + */ +public class NewEpisodesListAdapter extends BaseAdapter { + + private final Context context; + private final ItemAccess itemAccess; + private final ActionButtonCallback actionButtonCallback; + private final ActionButtonUtils actionButtonUtils; + + public NewEpisodesListAdapter(Context context, ItemAccess itemAccess, ActionButtonCallback actionButtonCallback) { + super(); + this.context = context; + this.itemAccess = itemAccess; + this.actionButtonUtils = new ActionButtonUtils(context); + this.actionButtonCallback = actionButtonCallback; + } + + @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, + null); + holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); + holder.pubDate = (TextView) convertView + .findViewById(R.id.txtvPublished); + 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_SHOW_DATE)); + + 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 { + holder.txtvDuration.setText(""); + } + + if (isDownloadingMedia) { + holder.downloadProgress.setVisibility(View.VISIBLE); + holder.txtvDuration.setVisibility(View.GONE); + } else { + holder.txtvDuration.setVisibility(View.VISIBLE); + holder.downloadProgress.setVisibility(View.GONE); + } + + if (!media.isDownloaded()) { + if (isDownloadingMedia) { + // item is being downloaded + holder.downloadProgress.setProgress(itemAccess.getItemDownloadProgressPercent(item)); + } + } + } + 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); + + + ImageLoader.getInstance().loadThumbnailBitmap( + item, + holder.imageView, + (int) convertView.getResources().getDimension( + R.dimen.thumbnail_length) + ); + 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; + 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); + } +} diff --git a/src/de/danoeh/antennapod/adapter/QueueListAdapter.java b/src/de/danoeh/antennapod/adapter/QueueListAdapter.java new file mode 100644 index 000000000..fb6848a1e --- /dev/null +++ b/src/de/danoeh/antennapod/adapter/QueueListAdapter.java @@ -0,0 +1,130 @@ +package de.danoeh.antennapod.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.*; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.asynctask.ImageLoader; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.storage.DownloadRequester; + +/** + * 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; + + public QueueListAdapter(Context context, ItemAccess itemAccess, ActionButtonCallback actionButtonCallback) { + super(); + this.context = context; + this.itemAccess = itemAccess; + this.actionButtonUtils = new ActionButtonUtils(context); + this.actionButtonCallback = actionButtonCallback; + + } + + @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, + null); + holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); + holder.butSecondary = (ImageButton) convertView + .findViewById(R.id.butSecondaryAction); + holder.downloadProgress = (ProgressBar) convertView + .findViewById(R.id.pbar_download_progress); + holder.imageView = (ImageView) convertView.findViewById(R.id.imgvImage); + convertView.setTag(holder); + } else { + holder = (Holder) convertView.getTag(); + } + + holder.title.setText(item.getTitle()); + + FeedMedia media = item.getMedia(); + if (media != null) { + final boolean isDownloadingMedia = DownloadRequester.getInstance().isDownloadingFile(media); + + + if (isDownloadingMedia) { + holder.downloadProgress.setVisibility(View.VISIBLE); + } else { + holder.downloadProgress.setVisibility(View.GONE); + } + if (!media.isDownloaded()) { + if (isDownloadingMedia) { + // item is being downloaded + holder.downloadProgress.setProgress(itemAccess.getItemDownloadProgressPercent(item)); + } + } + } + + actionButtonUtils.configureActionButton(holder.butSecondary, item); + holder.butSecondary.setFocusable(false); + holder.butSecondary.setTag(item); + holder.butSecondary.setOnClickListener(secondaryActionListener); + + + ImageLoader.getInstance().loadThumbnailBitmap( + item, + holder.imageView, + (int) convertView.getResources().getDimension( + R.dimen.thumbnail_length) + ); + 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; + ImageView imageView; + ProgressBar downloadProgress; + ImageButton butSecondary; + } + + public interface ItemAccess { + int getCount(); + + FeedItem getItem(int position); + + int getItemDownloadProgressPercent(FeedItem item); + } +} diff --git a/src/de/danoeh/antennapod/adapter/SearchlistAdapter.java b/src/de/danoeh/antennapod/adapter/SearchlistAdapter.java index 926a5a5ad..5c6af3943 100644 --- a/src/de/danoeh/antennapod/adapter/SearchlistAdapter.java +++ b/src/de/danoeh/antennapod/adapter/SearchlistAdapter.java @@ -5,6 +5,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import de.danoeh.antennapod.R; @@ -17,14 +18,32 @@ import de.danoeh.antennapod.feed.SearchResult; import java.util.List; /** List adapter for search activity. */ -public class SearchlistAdapter extends ArrayAdapter<SearchResult> { +public class SearchlistAdapter extends BaseAdapter { - public SearchlistAdapter(Context context, int textViewResourceId, - List<SearchResult> objects) { - super(context, textViewResourceId, objects); - } + private final Context context; + private final ItemAccess itemAccess; + + public SearchlistAdapter(Context context, ItemAccess itemAccess) { + this.context = context; + this.itemAccess = itemAccess; + } + + @Override + public int getCount() { + return itemAccess.getCount(); + } - @Override + @Override + public SearchResult getItem(int position) { + return itemAccess.getItem(position); + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override public View getView(int position, View convertView, ViewGroup parent) { final Holder holder; SearchResult result = getItem(position); @@ -33,7 +52,7 @@ public class SearchlistAdapter extends ArrayAdapter<SearchResult> { // Inflate Layout if (convertView == null) { holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) getContext() + LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.searchlist_item, null); @@ -78,4 +97,9 @@ public class SearchlistAdapter extends ArrayAdapter<SearchResult> { TextView subtitle; } + public static interface ItemAccess { + int getCount(); + SearchResult getItem(int position); + } + } diff --git a/src/de/danoeh/antennapod/asynctask/DownloadObserver.java b/src/de/danoeh/antennapod/asynctask/DownloadObserver.java index 40388cde5..1c5003ab3 100644 --- a/src/de/danoeh/antennapod/asynctask/DownloadObserver.java +++ b/src/de/danoeh/antennapod/asynctask/DownloadObserver.java @@ -23,9 +23,9 @@ public class DownloadObserver { /** * Time period between update notifications. */ - public static final int WAITING_INTERVAL_MS = 1000; + public static final int WAITING_INTERVAL_MS = 3000; - private final Activity activity; + private volatile Activity activity; private final Handler handler; private final Callback callback; @@ -57,19 +57,31 @@ public class DownloadObserver { public void onResume() { if (BuildConfig.DEBUG) Log.d(TAG, "DownloadObserver resumed"); activity.registerReceiver(contentChangedReceiver, new IntentFilter(DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED)); - activity.bindService(new Intent(activity, DownloadService.class), mConnection, 0); + connectToDownloadService(); } public void onPause() { if (BuildConfig.DEBUG) Log.d(TAG, "DownloadObserver paused"); - activity.unregisterReceiver(contentChangedReceiver); - activity.unbindService(mConnection); + try { + activity.unregisterReceiver(contentChangedReceiver); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + try { + activity.unbindService(mConnection); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } stopRefresher(); } private BroadcastReceiver contentChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + // reconnect to DownloadService if connection has been closed + if (downloadService == null) { + connectToDownloadService(); + } callback.onContentChanged(); startRefresher(); } @@ -81,6 +93,10 @@ public class DownloadObserver { void onDownloadDataAvailable(List<Downloader> downloaderList); } + private void connectToDownloadService() { + activity.bindService(new Intent(activity, DownloadService.class), mConnection, 0); + } + private ServiceConnection mConnection = new ServiceConnection() { public void onServiceDisconnected(ComponentName className) { downloadService = null; @@ -138,13 +154,21 @@ public class DownloadObserver { @Override public void run() { callback.onContentChanged(); - List<Downloader> downloaderList = downloadService.getDownloads(); - if (downloaderList == null || downloaderList.isEmpty()) { - Thread.currentThread().interrupt(); + if (downloadService != null) { + List<Downloader> downloaderList = downloadService.getDownloads(); + if (downloaderList == null || downloaderList.isEmpty()) { + Thread.currentThread().interrupt(); + } } } }); } } + public void setActivity(Activity activity) { + if (activity == null) throw new IllegalArgumentException("activity = null"); + this.activity = activity; + } + } + diff --git a/src/de/danoeh/antennapod/backup/OpmlBackupAgent.java b/src/de/danoeh/antennapod/backup/OpmlBackupAgent.java new file mode 100644 index 000000000..56d1ca092 --- /dev/null +++ b/src/de/danoeh/antennapod/backup/OpmlBackupAgent.java @@ -0,0 +1,212 @@ +package de.danoeh.antennapod.backup; + +import android.app.backup.BackupAgentHelper; +import android.app.backup.BackupDataInputStream; +import android.app.backup.BackupDataOutput; +import android.app.backup.BackupHelper; +import android.content.Context; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import de.danoeh.antennapod.BuildConfig; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.math.BigInteger; +import java.security.DigestInputStream; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; + +import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.opml.OpmlElement; +import de.danoeh.antennapod.opml.OpmlReader; +import de.danoeh.antennapod.opml.OpmlWriter; +import de.danoeh.antennapod.storage.DBReader; +import de.danoeh.antennapod.storage.DownloadRequestException; +import de.danoeh.antennapod.storage.DownloadRequester; +import de.danoeh.antennapod.util.LangUtils; + +public class OpmlBackupAgent extends BackupAgentHelper { + private static final String OPML_BACKUP_KEY = "opml"; + + @Override + public void onCreate() { + addHelper(OPML_BACKUP_KEY, new OpmlBackupHelper(this)); + } + + private static final void LOGD(String tag, String msg) { + if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.DEBUG)) { + Log.d(tag, msg); + } + } + + private static final void LOGD(String tag, String msg, Throwable tr) { + if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.DEBUG)) { + Log.d(tag, msg, tr); + } + } + + /** Class for backing up and restoring the OPML file. */ + private static class OpmlBackupHelper implements BackupHelper { + private static final String TAG = "OpmlBackupHelper"; + + private static final String OPML_ENTITY_KEY = "antennapod-feeds.opml"; + + private final Context mContext; + + /** Checksum of restored OPML file */ + private byte[] mChecksum; + + public OpmlBackupHelper(Context context) { + mContext = context; + } + + @Override + public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) { + Log.d(TAG, "Performing backup"); + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + MessageDigest digester = null; + Writer writer; + + try { + digester = MessageDigest.getInstance("MD5"); + writer = new OutputStreamWriter(new DigestOutputStream(byteStream, digester), + LangUtils.UTF_8); + } catch (NoSuchAlgorithmException e) { + writer = new OutputStreamWriter(byteStream, LangUtils.UTF_8); + } + + try { + // Write OPML + new OpmlWriter().writeDocument(DBReader.getFeedList(mContext), writer); + + // Compare checksum of new and old file to see if we need to perform a backup at all + if (digester != null) { + byte[] newChecksum = digester.digest(); + LOGD(TAG, "New checksum: " + new BigInteger(1, newChecksum).toString(16)); + + // Get the old checksum + if (oldState != null) { + FileInputStream inState = new FileInputStream(oldState.getFileDescriptor()); + int len = inState.read(); + + if (len != -1) { + byte[] oldChecksum = new byte[len]; + inState.read(oldChecksum); + LOGD(TAG, "Old checksum: " + new BigInteger(1, oldChecksum).toString(16)); + + if (Arrays.equals(oldChecksum, newChecksum)) { + LOGD(TAG, "Checksums are the same; won't backup"); + return; + } + } + } + + writeNewStateDescription(newState, newChecksum); + } + + LOGD(TAG, "Backing up OPML"); + byte[] bytes = byteStream.toByteArray(); + data.writeEntityHeader(OPML_ENTITY_KEY, bytes.length); + data.writeEntityData(bytes, bytes.length); + } catch (IOException e) { + Log.e(TAG, "Error during backup", e); + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException e) { + } + } + } + } + + @Override + public void restoreEntity(BackupDataInputStream data) { + LOGD(TAG, "Backup restore"); + + if (!OPML_ENTITY_KEY.equals(data.getKey())) { + LOGD(TAG, "Unknown entity key: " + data.getKey()); + return; + } + + MessageDigest digester = null; + Reader reader; + + try { + digester = MessageDigest.getInstance("MD5"); + reader = new InputStreamReader(new DigestInputStream(data, digester), + LangUtils.UTF_8); + } catch (NoSuchAlgorithmException e) { + reader = new InputStreamReader(data, LangUtils.UTF_8); + } + + try { + ArrayList<OpmlElement> opmlElements = new OpmlReader().readDocument(reader); + mChecksum = digester == null ? null : digester.digest(); + DownloadRequester downloader = DownloadRequester.getInstance(); + Date lastUpdated = new Date(); + + for (OpmlElement opmlElem : opmlElements) { + Feed feed = new Feed(opmlElem.getXmlUrl(), lastUpdated, opmlElem.getText()); + + try { + downloader.downloadFeed(mContext, feed); + } catch (DownloadRequestException e) { + LOGD(TAG, "Error while restoring/downloading feed", e); + } + } + } catch (XmlPullParserException e) { + Log.e(TAG, "Error while parsing the OPML file", e); + } catch (IOException e) { + Log.e(TAG, "Failed to restore OPML backup", e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + } + } + } + } + + @Override + public void writeNewStateDescription(ParcelFileDescriptor newState) { + writeNewStateDescription(newState, mChecksum); + } + + /** + * Writes the new state description, which is the checksum of the OPML file. + * + * @param newState + * @param checksum + */ + private void writeNewStateDescription(ParcelFileDescriptor newState, byte[] checksum) { + if (checksum == null) { + return; + } + + try { + FileOutputStream outState = new FileOutputStream(newState.getFileDescriptor()); + outState.write(checksum.length); + outState.write(checksum); + outState.flush(); + outState.close(); + } catch (IOException e) { + Log.e(TAG, "Failed to write new state description", e); + } + } + } +} diff --git a/src/de/danoeh/antennapod/dialog/FeedItemDialog.java b/src/de/danoeh/antennapod/dialog/FeedItemDialog.java new file mode 100644 index 000000000..8db4c9d19 --- /dev/null +++ b/src/de/danoeh/antennapod/dialog/FeedItemDialog.java @@ -0,0 +1,398 @@ +package de.danoeh.antennapod.dialog; + +import android.app.Dialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.widget.PopupMenu; +import android.util.Log; +import android.util.TypedValue; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.ImageButton; +import android.widget.TextView; +import android.widget.Toast; +import de.danoeh.antennapod.BuildConfig; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.adapter.DefaultActionButtonCallback; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.preferences.UserPreferences; +import de.danoeh.antennapod.storage.DBTasks; +import de.danoeh.antennapod.storage.DBWriter; +import de.danoeh.antennapod.storage.DownloadRequestException; +import de.danoeh.antennapod.storage.DownloadRequester; +import de.danoeh.antennapod.util.QueueAccess; +import de.danoeh.antennapod.util.ShownotesProvider; +import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler; +import org.apache.commons.lang3.StringEscapeUtils; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; + +/** + * Shows information about a specific FeedItem and provides actions like playing, downloading, etc. + */ +public class FeedItemDialog extends Dialog { + private static final String TAG = "FeedItemDialog"; + + private FeedItem item; + private QueueAccess queue; + + private View header; + private TextView txtvTitle; + private WebView webvDescription; + private ImageButton butAction1; + private ImageButton butAction2; + private ImageButton butMore; + private PopupMenu popupMenu; + + public static FeedItemDialog newInstance(Context context, FeedItemDialogSavedInstance savedInstance) { + if (savedInstance == null) throw new IllegalArgumentException("savedInstance = null"); + FeedItemDialog dialog = newInstance(context, savedInstance.item, savedInstance.queueAccess); + if (savedInstance.isShowing) { + dialog.show(); + } + return dialog; + } + + public static FeedItemDialog newInstance(Context context, FeedItem item, QueueAccess queue) { + if (useDarkThemeWorkAround()) { + return new FeedItemDialog(context, R.style.Theme_AntennaPod_Dark, item, queue); + } else { + return new FeedItemDialog(context, item, queue); + } + } + + public FeedItemDialog(Context context, int theme, FeedItem item, QueueAccess queue) { + super(context, theme); + if (item == null) throw new IllegalArgumentException("item = null"); + if (queue == null) throw new IllegalArgumentException("queue = null"); + this.item = item; + this.queue = queue; + } + + private FeedItemDialog(Context context, FeedItem item, QueueAccess queue) { + this(context, 0, item, queue); + } + + /** + * Returns true if the dialog should use a dark theme. This has to be done on Gingerbread devices + * because dialogs are only available in a dark theme. + */ + private static boolean useDarkThemeWorkAround() { + return Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1 + && UserPreferences.getTheme() != R.style.Theme_AntennaPod_Dark; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.feeditem_dialog); + + txtvTitle = (TextView) findViewById(R.id.txtvTitle); + header = findViewById(R.id.header); + webvDescription = (WebView) findViewById(R.id.webview); + butAction1 = (ImageButton) findViewById(R.id.butAction1); + butAction2 = (ImageButton) findViewById(R.id.butAction2); + butMore = (ImageButton) findViewById(R.id.butMoreActions); + popupMenu = new PopupMenu(getContext(), butMore); + + webvDescription.setWebViewClient(new WebViewClient()); + txtvTitle.setText(item.getTitle()); + + 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(getContext().getResources().getColor( + R.color.black)); + } + webvDescription.getSettings().setUseWideViewPort(false); + webvDescription.getSettings().setLayoutAlgorithm( + WebSettings.LayoutAlgorithm.NARROW_COLUMNS); + webvDescription.getSettings().setLoadWithOverviewMode(true); + webvDescription.setWebViewClient(new WebViewClient() { + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + try { + getContext().startActivity(intent); + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + return false; + } + return true; + } + }); + + loadDescriptionWebview(item); + + butAction1.setOnClickListener(new View.OnClickListener() { + DefaultActionButtonCallback actionButtonCallback = new DefaultActionButtonCallback(getContext()); + + @Override + + public void onClick(View v) { + FeedMedia media = item.getMedia(); + if (media == null) { + return; + } + actionButtonCallback.onActionButtonPressed(item); + + } + } + ); + + butAction2.setOnClickListener(new View.OnClickListener() + + { + @Override + public void onClick(View v) { + FeedMedia media = item.getMedia(); + if (media == null) { + return; + } + + if (!media.isDownloaded()) { + DBTasks.playMedia(getContext(), media, true, true, true); + dismiss(); + } else { + DBWriter.deleteFeedMediaOfItem(getContext(), media.getId()); + } + } + } + ); + + butMore.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + popupMenu.getMenu().clear(); + popupMenu.inflate(R.menu.feeditem_dialog); + FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, queue); + popupMenu.show(); + } + } + ); + + popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + + try { + return FeedItemMenuHandler.onMenuItemClicked(getContext(), menuItem.getItemId(), item); + } catch (DownloadRequestException e) { + e.printStackTrace(); + Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_LONG).show(); + return true; + } + } + } + ); + + updateMenuAppearance(); + } + + + private final FeedItemMenuHandler.MenuInterface popupMenuInterface = new FeedItemMenuHandler.MenuInterface() { + @Override + public void setItemVisibility(int id, boolean visible) { + MenuItem item = popupMenu.getMenu().findItem(id); + if (item != null) { + item.setVisible(visible); + } + } + }; + + public void updateMenuAppearance() { + if (item == null || queue == null) { + Log.w(TAG, "UpdateMenuAppearance called while item or queue was null"); + return; + } + FeedMedia media = item.getMedia(); + if (media == null) { + header.setVisibility(View.GONE); + } else { + header.setVisibility(View.VISIBLE); + boolean isDownloading = DownloadRequester.getInstance().isDownloadingFile(media); + TypedArray drawables = getContext().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.setImageDrawable(drawables.getDrawable(2)); + butAction2.setContentDescription(getContext().getString(R.string.stream_label)); + } else { + butAction2.setImageDrawable(drawables.getDrawable(3)); + butAction2.setContentDescription(getContext().getString(R.string.remove_episode_lable)); + } + + if (isDownloading) { + butAction1.setImageDrawable(drawables.getDrawable(4)); + butAction1.setContentDescription(getContext().getString(R.string.cancel_download_label)); + } else if (media.isDownloaded()) { + butAction1.setImageDrawable(drawables.getDrawable(0)); + butAction1.setContentDescription(getContext().getString(R.string.play_label)); + } else { + butAction1.setImageDrawable(drawables.getDrawable(1)); + butAction1.setContentDescription(getContext().getString(R.string.download_label)); + } + + drawables.recycle(); + } + } + + + private void loadDescriptionWebview(final ShownotesProvider shownotesProvider) { + AsyncTask<Void, Void, Void> loadTask = new AsyncTask<Void, Void, Void>() { + String data; + + + private String applyWebviewStyle(String textColor, String data) { + final String WEBVIEW_STYLE = "<html><head><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: 11pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }</style></head><body>%s</body></html>"; + final int pageMargin = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 8, getContext().getResources() + .getDisplayMetrics() + ); + return String.format(WEBVIEW_STYLE, textColor, "100%", pageMargin, + pageMargin, pageMargin, pageMargin, data); + } + + + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + // /webvDescription.loadData(url, "text/html", "utf-8"); + if (FeedItemDialog.this.isShowing() && webvDescription != null) { + webvDescription.loadDataWithBaseURL(null, data, "text/html", + "utf-8", "about:blank"); + if (BuildConfig.DEBUG) + Log.d(TAG, "Webview loaded"); + } + } + + + @Override + protected Void doInBackground(Void... params) { + if (BuildConfig.DEBUG) + Log.d(TAG, "Loading Webview"); + try { + Callable<String> shownotesLoadTask = shownotesProvider.loadShownotes(); + final String shownotes = shownotesLoadTask.call(); + + data = StringEscapeUtils.unescapeHtml4(shownotes); + TypedArray res = getContext() + .getTheme() + .obtainStyledAttributes( + new int[]{android.R.attr.textColorPrimary}); + int colorResource; + if (useDarkThemeWorkAround()) { + colorResource = getContext().getResources().getColor(R.color.black); + } else { + colorResource = res.getColor(0, 0); + } + String colorString = String.format("#%06X", + 0xFFFFFF & colorResource); + Log.i(TAG, "text color: " + colorString); + res.recycle(); + data = applyWebviewStyle(colorString, data); + + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + }; + loadTask.execute(); + } + + /** + * Convenience method that calls setQueue() and setItemFromCollection() with + * the given arguments. + * + * @return true if one of the calls to setItemFromCollection returned true, + * false otherwise. + */ + public boolean updateContent(QueueAccess queue, List<FeedItem>... collections) { + setQueue(queue); + + boolean setItemFromCollectionResult = false; + if (collections != null) { + for (List<FeedItem> list : collections) { + setItemFromCollectionResult |= setItemFromCollection(list); + } + } + if (isShowing()) { + updateMenuAppearance(); + } + + return setItemFromCollectionResult; + } + + + public void setItem(FeedItem item) { + if (item == null) throw new IllegalArgumentException("item = null"); + this.item = item; + } + + /** + * Finds the FeedItem of this dialog in a collection and updates its state from that + * collection. + * + * @return true if the FeedItem was found, false otherwise. + */ + public boolean setItemFromCollection(Collection<FeedItem> items) { + for (FeedItem item : items) { + if (item.getId() == this.item.getId()) { + setItem(item); + return true; + } + } + return false; + } + + public void setQueue(QueueAccess queue) { + if (queue == null) throw new IllegalArgumentException("queue = null"); + this.queue = queue; + } + + public FeedItem getItem() { + return item; + } + + public QueueAccess getQueue() { + return queue; + } + + public FeedItemDialogSavedInstance save() { + return new FeedItemDialogSavedInstance(item, queue, isShowing()); + } + + /** + * Used to save the FeedItemDialog's state across configuration changes + * */ + public static class FeedItemDialogSavedInstance { + final FeedItem item; + final QueueAccess queueAccess; + final boolean isShowing; + + private FeedItemDialogSavedInstance(FeedItem item, QueueAccess queueAccess, boolean isShowing) { + this.item = item; + this.queueAccess = queueAccess; + this.isShowing = isShowing; + } + } +} diff --git a/src/de/danoeh/antennapod/dialog/TimeDialog.java b/src/de/danoeh/antennapod/dialog/TimeDialog.java index 353a50adb..cb3ebf0ab 100644 --- a/src/de/danoeh/antennapod/dialog/TimeDialog.java +++ b/src/de/danoeh/antennapod/dialog/TimeDialog.java @@ -26,7 +26,6 @@ public abstract class TimeDialog extends Dialog { private Button butConfirm; private Button butCancel; - private String[] spinnerContent = { "s", "min", "h" }; private TimeUnit[] units = { TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS }; @@ -39,6 +38,10 @@ public abstract class TimeDialog extends Dialog { 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); diff --git a/src/de/danoeh/antennapod/feed/Feed.java b/src/de/danoeh/antennapod/feed/Feed.java index 9e423ff8b..f9da65e03 100644 --- a/src/de/danoeh/antennapod/feed/Feed.java +++ b/src/de/danoeh/antennapod/feed/Feed.java @@ -223,6 +223,8 @@ public class Feed extends FeedFile implements FlattrThing { public String getIdentifyingValue() { if (feedIdentifier != null && !feedIdentifier.isEmpty()) { return feedIdentifier; + } else if (download_url != null && !download_url.isEmpty()) { + return download_url; } else if (title != null && !title.isEmpty()) { return title; } else { diff --git a/src/de/danoeh/antennapod/feed/FeedFile.java b/src/de/danoeh/antennapod/feed/FeedFile.java index 28a9b1e10..a05533ebc 100644 --- a/src/de/danoeh/antennapod/feed/FeedFile.java +++ b/src/de/danoeh/antennapod/feed/FeedFile.java @@ -2,84 +2,104 @@ package de.danoeh.antennapod.feed; import java.io.File; -/** Represents a component of a Feed that has to be downloaded */ +/** + * Represents a component of a Feed that has to be downloaded + */ public abstract class FeedFile extends FeedComponent { - protected String file_url; - protected String download_url; - protected boolean downloaded; + protected String file_url; + protected String download_url; + protected boolean downloaded; - public FeedFile(String file_url, String download_url, boolean downloaded) { - super(); - this.file_url = file_url; - this.download_url = download_url; - this.downloaded = downloaded; - } + /** + * Creates a new FeedFile object. + * + * @param file_url The location of the FeedFile. If this is null, the downloaded-attribute + * will automatically be set to false. + * @param download_url The location where the FeedFile can be downloaded. + * @param downloaded true if the FeedFile has been downloaded, false otherwise. This parameter + * will automatically be interpreted as false if the file_url is null. + */ + public FeedFile(String file_url, String download_url, boolean downloaded) { + super(); + this.file_url = file_url; + this.download_url = download_url; + this.downloaded = (file_url != null) && downloaded; + } - public FeedFile() { + public FeedFile() { this(null, null, false); } - public abstract int getTypeAsInt(); + public abstract int getTypeAsInt(); - /** - * Update this FeedFile's attributes with the attributes from another - * FeedFile. This method should only update attributes which where read from - * the feed. - */ - public void updateFromOther(FeedFile other) { - super.updateFromOther(other); - this.download_url = other.download_url; - } + /** + * Update this FeedFile's attributes with the attributes from another + * FeedFile. This method should only update attributes which where read from + * the feed. + */ + public void updateFromOther(FeedFile other) { + super.updateFromOther(other); + this.download_url = other.download_url; + } - /** - * Compare's this FeedFile's attribute values with another FeedFile's - * attribute values. This method will only compare attributes which were - * read from the feed. - * - * @return true if attribute values are different, false otherwise - */ - public boolean compareWithOther(FeedFile other) { - if (super.compareWithOther(other)) { - return true; - } - if (!download_url.equals(other.download_url)) { - return true; - } - return false; - } - - /** Returns true if the file exists at file_url. */ - public boolean fileExists() { - if (file_url == null) { - return false; - } else { - File f = new File(file_url); - return f.exists(); - } - } + /** + * Compare's this FeedFile's attribute values with another FeedFile's + * attribute values. This method will only compare attributes which were + * read from the feed. + * + * @return true if attribute values are different, false otherwise + */ + public boolean compareWithOther(FeedFile other) { + if (super.compareWithOther(other)) { + return true; + } + if (!download_url.equals(other.download_url)) { + return true; + } + return false; + } - public String getFile_url() { - return file_url; - } + /** + * Returns true if the file exists at file_url. + */ + public boolean fileExists() { + if (file_url == null) { + return false; + } else { + File f = new File(file_url); + return f.exists(); + } + } - public void setFile_url(String file_url) { - this.file_url = file_url; - } + public String getFile_url() { + return file_url; + } - public String getDownload_url() { - return download_url; - } + /** + * Changes the file_url of this FeedFile. Setting this value to + * null will also set the downloaded-attribute to false. + */ + public void setFile_url(String file_url) { + this.file_url = file_url; + if (file_url == null) { + downloaded = false; + } + } - public void setDownload_url(String download_url) { - this.download_url = download_url; - } + public String getDownload_url() { + return download_url; + } - public boolean isDownloaded() { - return downloaded; - } + public void setDownload_url(String download_url) { + this.download_url = download_url; + } - public void setDownloaded(boolean downloaded) { - this.downloaded = downloaded; - } + public boolean isDownloaded() { + return downloaded; + } + + public void setDownloaded(boolean downloaded) { + this.downloaded = downloaded; + } } diff --git a/src/de/danoeh/antennapod/feed/FeedItem.java b/src/de/danoeh/antennapod/feed/FeedItem.java index 921a03bff..956131ab2 100644 --- a/src/de/danoeh/antennapod/feed/FeedItem.java +++ b/src/de/danoeh/antennapod/feed/FeedItem.java @@ -280,7 +280,7 @@ public class FeedItem extends FeedComponent implements @Override public InputStream openImageInputStream() { InputStream out = null; - if (hasItemImage()) { + if (hasItemImageDownloaded()) { out = image.openImageInputStream(); } else if (hasMedia()) { out = media.openImageInputStream(); @@ -293,7 +293,7 @@ public class FeedItem extends FeedComponent implements @Override public InputStream reopenImageInputStream(InputStream input) { InputStream out = null; - if (hasItemImage()) { + if (hasItemImageDownloaded()) { out = image.reopenImageInputStream(input); } else if (hasMedia()) { out = media.reopenImageInputStream(input); @@ -306,7 +306,7 @@ public class FeedItem extends FeedComponent implements @Override public String getImageLoaderCacheKey() { String out = null; - if (hasItemImage()) { + if (hasItemImageDownloaded()) { out = image.getImageLoaderCacheKey(); } else if (hasMedia()) { out = media.getImageLoaderCacheKey(); @@ -346,6 +346,13 @@ public class FeedItem extends FeedComponent implements return image != null; } + /** + * Returns true if this FeedItem has its own image and the image has been downloaded. + */ + public boolean hasItemImageDownloaded() { + return image != null && image.isDownloaded(); + } + @Override public String getHumanReadableIdentifier() { return title; diff --git a/src/de/danoeh/antennapod/feed/FeedMedia.java b/src/de/danoeh/antennapod/feed/FeedMedia.java index f38e92398..1f8e7f8f8 100644 --- a/src/de/danoeh/antennapod/feed/FeedMedia.java +++ b/src/de/danoeh/antennapod/feed/FeedMedia.java @@ -204,7 +204,7 @@ public class FeedMedia extends FeedFile implements Playable { public FeedImage getImage() { if (item != null) { - return (item.hasItemImage()) ? item.getImage() : item.getFeed().getImage(); + return (item.hasItemImageDownloaded()) ? item.getImage() : item.getFeed().getImage(); } return null; } @@ -384,7 +384,7 @@ public class FeedMedia extends FeedFile implements Playable { @Override public InputStream openImageInputStream() { InputStream out; - if (item.hasItemImage()) { + if (item.hasItemImageDownloaded()) { out = item.openImageInputStream(); } else { out = new Playable.DefaultPlayableImageLoader(this) @@ -401,7 +401,7 @@ public class FeedMedia extends FeedFile implements Playable { @Override public String getImageLoaderCacheKey() { String out; - if (item.hasItemImage()) { + if (item.hasItemImageDownloaded()) { out = item.getImageLoaderCacheKey(); } else { out = new Playable.DefaultPlayableImageLoader(this) @@ -418,7 +418,11 @@ public class FeedMedia extends FeedFile implements Playable { @Override public InputStream reopenImageInputStream(InputStream input) { if (input instanceof FileInputStream) { - return item.getImage().reopenImageInputStream(input); + if (item.hasItemImageDownloaded()) { + return item.getImage().reopenImageInputStream(input); + } else { + return item.getFeed().getImage().reopenImageInputStream(input); + } } else { return new Playable.DefaultPlayableImageLoader(this) .reopenImageInputStream(input); diff --git a/src/de/danoeh/antennapod/fragment/AddFeedFragment.java b/src/de/danoeh/antennapod/fragment/AddFeedFragment.java new file mode 100644 index 000000000..f5ae5a777 --- /dev/null +++ b/src/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -0,0 +1,76 @@ +package de.danoeh.antennapod.fragment; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +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; +import de.danoeh.antennapod.fragment.gpodnet.GpodnetMainFragment; + +/** + * Provides actions for adding new podcast subscriptions + */ +public class AddFeedFragment extends Fragment { + private static final String TAG = "AddFeedFragment"; + + /** + * Preset value for url text field. + */ + public static final String ARG_FEED_URL = "feedurl"; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + View root = inflater.inflate(R.layout.addfeed, container, false); + + final EditText etxtFeedurl = (EditText) root.findViewById(R.id.etxtFeedurl); + + Bundle args = getArguments(); + if (args != null && args.getString(ARG_FEED_URL) != null) { + etxtFeedurl.setText(args.getString(ARG_FEED_URL)); + } + + Button butBrowserGpoddernet = (Button) root.findViewById(R.id.butBrowseGpoddernet); + Button butOpmlImport = (Button) root.findViewById(R.id.butOpmlImport); + Button butConfirm = (Button) root.findViewById(R.id.butConfirm); + + final MainActivity activity = (MainActivity) getActivity(); + activity.getMainActivtyActionBar().setTitle(R.string.add_feed_label); + + butBrowserGpoddernet.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + activity.loadChildFragment(new GpodnetMainFragment()); + } + }); + + butOpmlImport.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + startActivity(new Intent(getActivity(), + OpmlImportFromPathActivity.class)); + } + }); + + butConfirm.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(getActivity(), DefaultOnlineFeedViewActivity.class); + intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, etxtFeedurl.getText().toString()); + intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, getString(R.string.add_feed_label)); + startActivity(intent); + } + }); + + return root; + } +} diff --git a/src/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java b/src/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java new file mode 100644 index 000000000..ed304ad37 --- /dev/null +++ b/src/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -0,0 +1,195 @@ +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.view.View; +import android.widget.ListView; +import de.danoeh.antennapod.adapter.DownloadedEpisodesListAdapter; +import de.danoeh.antennapod.dialog.FeedItemDialog; +import de.danoeh.antennapod.feed.EventDistributor; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.storage.DBReader; +import de.danoeh.antennapod.storage.DBWriter; +import de.danoeh.antennapod.util.QueueAccess; + +import java.util.List; + +/** + * Displays all running downloads and provides a button to delete them + */ +public class CompletedDownloadsFragment extends ListFragment { + private static final int EVENTS = + EventDistributor.DOWNLOAD_HANDLED | + EventDistributor.DOWNLOADLOG_UPDATE | + EventDistributor.QUEUE_UPDATE | + EventDistributor.UNREAD_ITEMS_UPDATE; + + private List<FeedItem> items; + private QueueAccess queue; + private DownloadedEpisodesListAdapter listAdapter; + + private boolean viewCreated = false; + private boolean itemsLoaded = false; + + private FeedItemDialog feedItemDialog; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + startItemLoader(); + } + + @Override + public void onStart() { + super.onStart(); + EventDistributor.getInstance().register(contentUpdate); + } + + @Override + public void onStop() { + super.onStop(); + EventDistributor.getInstance().unregister(contentUpdate); + stopItemLoader(); + } + + @Override + public void onDetach() { + super.onDetach(); + stopItemLoader(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + listAdapter = null; + viewCreated = false; + feedItemDialog = null; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + if (viewCreated && itemsLoaded) { + onFragmentLoaded(); + } + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + viewCreated = true; + if (itemsLoaded && getActivity() != null) { + onFragmentLoaded(); + } + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + super.onListItemClick(l, v, position, id); + FeedItem item = listAdapter.getItem(position - l.getHeaderViewsCount()); + if (item != null) { + feedItemDialog = FeedItemDialog.newInstance(getActivity(), item, queue); + feedItemDialog.show(); + } + + } + + private void onFragmentLoaded() { + if (listAdapter == null) { + listAdapter = new DownloadedEpisodesListAdapter(getActivity(), itemAccess); + setListAdapter(listAdapter); + } + listAdapter.notifyDataSetChanged(); + if (feedItemDialog != null) { + boolean res = feedItemDialog.updateContent(queue, items); + if (!res && feedItemDialog.isShowing()) { + feedItemDialog.dismiss(); + } + } + } + + private DownloadedEpisodesListAdapter.ItemAccess itemAccess = new DownloadedEpisodesListAdapter.ItemAccess() { + @Override + public int getCount() { + return (items != null) ? items.size() : 0; + } + + @Override + public FeedItem getItem(int position) { + return (items != null) ? items.get(position) : null; + } + + @Override + public void onFeedItemSecondaryAction(FeedItem item) { + DBWriter.deleteFeedMediaOfItem(getActivity(), item.getMedia().getId()); + } + }; + + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + @Override + public void update(EventDistributor eventDistributor, Integer arg) { + if ((arg & EventDistributor.DOWNLOAD_QUEUED) != 0) { + if (feedItemDialog != null && feedItemDialog.isShowing()) { + feedItemDialog.updateMenuAppearance(); + } + } else if ((arg & EVENTS) != 0) { + startItemLoader(); + } + } + }; + + 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); + } + } + + private class ItemLoader extends AsyncTask<Void, Void, Object[]> { + + @Override + protected void onPreExecute() { + super.onPreExecute(); + if (!itemsLoaded && viewCreated) { + setListShown(false); + } + } + + @Override + protected void onPostExecute(Object[] results) { + super.onPostExecute(results); + setListShown(true); + if (results != null) { + items = (List<FeedItem>) results[0]; + queue = (QueueAccess) results[1]; + itemsLoaded = true; + if (viewCreated && getActivity() != null) { + onFragmentLoaded(); + } + } + } + + @Override + protected Object[] doInBackground(Void... params) { + Context context = getActivity(); + if (context != null) { + return new Object[]{DBReader.getDownloadedItems(context), + QueueAccess.IDListAccess(DBReader.getQueueIDList(context))}; + } + return null; + } + } +} diff --git a/src/de/danoeh/antennapod/fragment/DownloadLogFragment.java b/src/de/danoeh/antennapod/fragment/DownloadLogFragment.java new file mode 100644 index 000000000..d81ba4b86 --- /dev/null +++ b/src/de/danoeh/antennapod/fragment/DownloadLogFragment.java @@ -0,0 +1,121 @@ +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.view.View; +import de.danoeh.antennapod.adapter.DownloadLogAdapter; +import de.danoeh.antennapod.feed.EventDistributor; +import de.danoeh.antennapod.service.download.DownloadStatus; +import de.danoeh.antennapod.storage.DBReader; + +import java.util.List; + +/** + * Shows the download log + */ +public class DownloadLogFragment extends ListFragment { + + private List<DownloadStatus> downloadLog; + private DownloadLogAdapter adapter; + + private boolean viewsCreated = false; + private boolean itemsLoaded = false; + + @Override + public void onStart() { + super.onStart(); + EventDistributor.getInstance().register(contentUpdate); + startItemLoader(); + } + + @Override + public void onStop() { + super.onStop(); + EventDistributor.getInstance().unregister(contentUpdate); + stopItemLoader(); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + viewsCreated = true; + if (itemsLoaded) { + onFragmentLoaded(); + } + } + + private void onFragmentLoaded() { + if (adapter == null) { + adapter = new DownloadLogAdapter(getActivity(), itemAccess); + setListAdapter(adapter); + } + setListShown(true); + adapter.notifyDataSetChanged(); + + } + + private DownloadLogAdapter.ItemAccess itemAccess = new DownloadLogAdapter.ItemAccess() { + + @Override + public int getCount() { + return (downloadLog != null) ? downloadLog.size() : 0; + } + + @Override + public DownloadStatus getItem(int position) { + return (downloadLog != null) ? downloadLog.get(position) : null; + } + }; + + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + + @Override + public void update(EventDistributor eventDistributor, Integer arg) { + if ((arg & EventDistributor.DOWNLOADLOG_UPDATE) != 0) { + startItemLoader(); + } + } + }; + + 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); + } + } + + 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; + } + } +} diff --git a/src/de/danoeh/antennapod/fragment/DownloadsFragment.java b/src/de/danoeh/antennapod/fragment/DownloadsFragment.java new file mode 100644 index 000000000..5a71cb36b --- /dev/null +++ b/src/de/danoeh/antennapod/fragment/DownloadsFragment.java @@ -0,0 +1,145 @@ +package de.danoeh.antennapod.fragment; + +import android.app.Activity; +import android.content.res.Resources; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.ViewPager; +import android.support.v7.app.ActionBar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; + +/** + * Shows the CompletedDownloadsFragment and the RunningDownloadsFragment + */ +public class DownloadsFragment extends Fragment { + + public static final String ARG_SELECTED_TAB = "selected_tab"; + + public static final int POS_RUNNING = 0; + public static final int POS_COMPLETED = 1; + public static final int POS_LOG = 2; + + private ViewPager pager; + private MainActivity activity; + + @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); + DownloadsPagerAdapter pagerAdapter = new DownloadsPagerAdapter(getChildFragmentManager(), getResources()); + pager.setAdapter(pagerAdapter); + final ActionBar actionBar = activity.getMainActivtyActionBar(); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + ActionBar.TabListener tabListener = new ActionBar.TabListener() { + @Override + public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + pager.setCurrentItem(tab.getPosition()); + } + + @Override + public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + + } + + @Override + public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + + } + }; + actionBar.removeAllTabs(); + actionBar.addTab(actionBar.newTab() + .setText(R.string.downloads_running_label) + .setTabListener(tabListener)); + actionBar.addTab(actionBar.newTab() + .setText(R.string.downloads_completed_label) + .setTabListener(tabListener)); + actionBar.addTab(actionBar.newTab() + .setText(R.string.downloads_log_label) + .setTabListener(tabListener)); + + pager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + actionBar.setSelectedNavigationItem(position); + } + }); + return root; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + if (getArguments() != null) { + int tab = getArguments().getInt(ARG_SELECTED_TAB); + pager.setCurrentItem(tab, false); + } + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + this.activity = (MainActivity) activity; + } + + @Override + public void onDetach() { + super.onDetach(); + activity.getMainActivtyActionBar().removeAllTabs(); + activity.getMainActivtyActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); + } + + public class DownloadsPagerAdapter extends FragmentPagerAdapter { + + + + + Resources resources; + + public DownloadsPagerAdapter(FragmentManager fm, Resources resources) { + super(fm); + this.resources = resources; + } + + @Override + public Fragment getItem(int position) { + switch (position) { + case POS_RUNNING: + return new RunningDownloadsFragment(); + case POS_COMPLETED: + return new CompletedDownloadsFragment(); + case POS_LOG: + return new DownloadLogFragment(); + default: + return null; + } + } + + @Override + public int getCount() { + return 3; + } + + @Override + public CharSequence getPageTitle(int position) { + switch (position) { + case POS_RUNNING: + return resources.getString(R.string.downloads_running_label); + case POS_COMPLETED: + return resources.getString(R.string.downloads_completed_label); + case POS_LOG: + return resources.getString(R.string.downloads_log_label); + default: + return super.getPageTitle(position); + } + } + } +} diff --git a/src/de/danoeh/antennapod/fragment/EpisodesFragment.java b/src/de/danoeh/antennapod/fragment/EpisodesFragment.java deleted file mode 100644 index 3c79a8c10..000000000 --- a/src/de/danoeh/antennapod/fragment/EpisodesFragment.java +++ /dev/null @@ -1,327 +0,0 @@ -package de.danoeh.antennapod.fragment; - -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.util.Log; -import android.view.*; -import android.view.ContextMenu.ContextMenuInfo; -import android.widget.ExpandableListView; -import android.widget.ExpandableListView.OnChildClickListener; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.ItemviewActivity; -import de.danoeh.antennapod.activity.OrganizeQueueActivity; -import de.danoeh.antennapod.adapter.ActionButtonCallback; -import de.danoeh.antennapod.adapter.ExternalEpisodesListAdapter; -import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; -import de.danoeh.antennapod.feed.EventDistributor; -import de.danoeh.antennapod.feed.FeedItem; -import de.danoeh.antennapod.storage.DBReader; -import de.danoeh.antennapod.storage.DBTasks; -import de.danoeh.antennapod.storage.DBWriter; -import de.danoeh.antennapod.storage.DownloadRequestException; -import de.danoeh.antennapod.util.QueueAccess; -import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler; - -import java.util.List; - -public class EpisodesFragment extends Fragment { - private static final String TAG = "EpisodesFragment"; - - private static final int EVENTS = EventDistributor.QUEUE_UPDATE - | EventDistributor.UNREAD_ITEMS_UPDATE - | EventDistributor.FEED_LIST_UPDATE - | EventDistributor.DOWNLOAD_HANDLED - | EventDistributor.DOWNLOAD_QUEUED; - - private ExpandableListView listView; - private ExternalEpisodesListAdapter adapter; - - private List<FeedItem> queue; - private List<FeedItem> unreadItems; - - protected FeedItem selectedItem = null; - protected long selectedGroupId = -1; - protected boolean contextMenuClosed = true; - - @Override - public void onDestroy() { - super.onDestroy(); - EventDistributor.getInstance().unregister(contentUpdate); - } - - @Override - public void onResume() { - super.onResume(); - - EventDistributor.getInstance().register(contentUpdate); - if (adapter != null) { - adapter.notifyDataSetChanged(); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.episodes_fragment, null); - listView = (ExpandableListView) v.findViewById(android.R.id.list); - return v; - } - - protected ActionButtonCallback adapterCallback = new ActionButtonCallback() { - - @Override - public void onActionButtonPressed(FeedItem item) { - resetContextMenuSelection(); - selectedItem = item; - listView.showContextMenu(); - } - }; - - protected ExternalEpisodesListAdapter.OnGroupActionClicked groupActionCallback = new ExternalEpisodesListAdapter.OnGroupActionClicked() { - - @Override - public void onClick(long groupId) { - resetContextMenuSelection(); - selectedGroupId = groupId; - listView.showContextMenu(); - } - }; - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - adapter = new ExternalEpisodesListAdapter(getActivity(), - adapterCallback, groupActionCallback, itemAccess); - listView.setAdapter(adapter); - listView.expandGroup(ExternalEpisodesListAdapter.GROUP_POS_QUEUE); - listView.expandGroup(ExternalEpisodesListAdapter.GROUP_POS_UNREAD); - listView.setOnChildClickListener(new OnChildClickListener() { - - @Override - public boolean onChildClick(ExpandableListView parent, View v, - int groupPosition, int childPosition, long id) { - FeedItem selection = adapter.getChild(groupPosition, - childPosition); - if (selection != null) { - Intent showItem = new Intent(getActivity(), - ItemviewActivity.class); - showItem.putExtra(FeedlistFragment.EXTRA_SELECTED_FEED, - selection.getFeed().getId()); - showItem.putExtra(ItemlistFragment.EXTRA_SELECTED_FEEDITEM, - selection.getId()); - - startActivity(showItem); - return true; - } - return true; - } - }); - loadData(); - registerForContextMenu(listView); - - } - - ExternalEpisodesListAdapter.ItemAccess itemAccess = new ExternalEpisodesListAdapter.ItemAccess() { - - @Override - public int getQueueSize() { - return (queue != null) ? queue.size() : 0; - } - - @Override - public int getUnreadItemsSize() { - return (unreadItems != null) ? unreadItems.size() : 0; - } - - @Override - public FeedItem getQueueItemAt(int position) { - return (queue != null) ? queue.get(position) : null; - } - - @Override - public FeedItem getUnreadItemAt(int position) { - return (unreadItems != null) ? unreadItems.get(position) : null; - } - }; - - private void loadData() { - AsyncTask<Void, Void, Void> loadTask = new AsyncTask<Void, Void, Void>() { - private volatile List<FeedItem> queueRef; - private volatile List<FeedItem> unreadItemsRef; - - @Override - protected Void doInBackground(Void... voids) { - if (BuildConfig.DEBUG) Log.d(TAG, "Starting to load list data"); - Context context = EpisodesFragment.this.getActivity(); - if (context != null) { - queueRef = DBReader.getQueue(context); - unreadItemsRef = DBReader.getUnreadItemsList(context); - } - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); - if (queueRef != null && unreadItemsRef != null) { - if (BuildConfig.DEBUG) Log.d(TAG, "Done loading list data"); - queue = queueRef; - unreadItems = unreadItemsRef; - if (adapter != null) { - adapter.notifyDataSetChanged(); - } - } else { - if (queueRef == null) { - Log.e(TAG, "Could not load queue"); - } - if (unreadItemsRef == null) { - Log.e(TAG, "Could not load unread items"); - } - } - } - }; - loadTask.execute(); - } - - private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((EVENTS & arg) != 0) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received contentUpdate Intent."); - loadData(); - } - } - }; - - @Override - public void onCreateContextMenu(final ContextMenu menu, View v, - ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - if (!contextMenuClosed) { // true if context menu was cancelled before - resetContextMenuSelection(); - } - contextMenuClosed = false; - listView.setOnItemLongClickListener(null); - if (selectedItem != null) { - new MenuInflater(getActivity()).inflate(R.menu.feeditem, menu); - - menu.setHeaderTitle(selectedItem.getTitle()); - FeedItemMenuHandler.onPrepareMenu( - new FeedItemMenuHandler.MenuInterface() { - - @Override - public void setItemVisibility(int id, boolean visible) { - menu.findItem(id).setVisible(visible); - } - }, selectedItem, false, QueueAccess.ItemListAccess(queue)); - - // check to see if the item is in the queue, if so add queue menu items - int itemIndex = queue.indexOf(selectedItem); - if (itemIndex != -1) { - addQueueOnlyMenus(menu, itemIndex); - } - - } else if (selectedGroupId == ExternalEpisodesListAdapter.GROUP_POS_QUEUE) { - menu.add(Menu.NONE, R.id.organize_queue_item, Menu.NONE, - R.string.organize_queue_label); - menu.add(Menu.NONE, R.id.clear_queue_item, Menu.NONE, getActivity() - .getString(R.string.clear_queue_label)); - menu.add(Menu.NONE, R.id.download_all_item, Menu.NONE, - getActivity().getString(R.string.download_all)); - } else if (selectedGroupId == ExternalEpisodesListAdapter.GROUP_POS_UNREAD) { - menu.add(Menu.NONE, R.id.mark_all_read_item, Menu.NONE, - getActivity().getString(R.string.mark_all_read_label)); - menu.add(Menu.NONE, R.id.enqueue_all_item, Menu.NONE, getActivity() - .getString(R.string.enqueue_all_new)); - } - } - - /** - * Adds submenus to the ContextMenu if the item selected is in the queue. - * @param menu the ContextMenu to add the submenus to - * @param itemIndex the index of the selected item within the queue. - */ - private void addQueueOnlyMenus(ContextMenu menu, int itemIndex) { - if (itemIndex != 0) { - // don't add move to top if this item is already on the top - menu.add(Menu.NONE, R.id.move_to_top_item, Menu.NONE, getActivity() - .getString(R.string.move_to_top_label)); - } - if (itemIndex != queue.size() - 1) { - // don't add move to bottom if this item is already on the bottom - menu.add(Menu.NONE, R.id.move_to_bottom_item, Menu.NONE, getActivity() - .getString(R.string.move_to_bottom_label)); - } - } - - @Override - public boolean onContextItemSelected(android.view.MenuItem item) { - boolean handled = false; - if (selectedItem != null) { - try { - handled = FeedItemMenuHandler.onMenuItemClicked( - getActivity(), item.getItemId(), selectedItem); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog( - getActivity(), e.getMessage()); - } - if (!handled) { - // if it wasn't handled by the FeedItemMenuHandler it might be one of ours - switch (item.getItemId()) { - case R.id.move_to_top_item: - DBWriter.moveQueueItemToTop(getActivity(), selectedItem.getId(), true); - handled = true; - break; - case R.id.move_to_bottom_item: - DBWriter.moveQueueItemToBottom(getActivity(), selectedItem.getId(), true); - handled = true; - break; - } - } - } else if (selectedGroupId == ExternalEpisodesListAdapter.GROUP_POS_QUEUE) { - handled = true; - switch (item.getItemId()) { - case R.id.organize_queue_item: - startActivity(new Intent(getActivity(), - OrganizeQueueActivity.class)); - break; - case R.id.clear_queue_item: - DBWriter.clearQueue(getActivity()); - break; - case R.id.download_all_item: - DBTasks.downloadAllItemsInQueue(getActivity()); - break; - default: - handled = false; - } - } else if (selectedGroupId == ExternalEpisodesListAdapter.GROUP_POS_UNREAD) { - handled = true; - switch (item.getItemId()) { - case R.id.mark_all_read_item: - DBWriter.markAllItemsRead(getActivity()); - break; - case R.id.enqueue_all_item: - DBTasks.enqueueAllNewItems(getActivity()); - break; - default: - handled = false; - } - } - - resetContextMenuSelection(); - return handled; - } - - private void resetContextMenuSelection() { - selectedItem = null; - selectedGroupId = -1; - contextMenuClosed = true; - } -} diff --git a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java index 47cd3f244..db47cd8a4 100644 --- a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -29,8 +29,6 @@ public class ExternalPlayerFragment extends Fragment { private ImageView imgvCover; private ViewGroup layoutInfo; private TextView txtvTitle; - private TextView txtvPosition; - private TextView txtvStatus; private ImageButton butPlay; private PlaybackController controller; @@ -48,9 +46,7 @@ public class ExternalPlayerFragment extends Fragment { imgvCover = (ImageView) root.findViewById(R.id.imgvCover); layoutInfo = (ViewGroup) root.findViewById(R.id.layoutInfo); txtvTitle = (TextView) root.findViewById(R.id.txtvTitle); - txtvPosition = (TextView) root.findViewById(R.id.txtvPosition); butPlay = (ImageButton) root.findViewById(R.id.butPlay); - txtvStatus = (TextView) root.findViewById(R.id.txtvStatus); layoutInfo.setOnClickListener(new OnClickListener() { @@ -84,12 +80,6 @@ public class ExternalPlayerFragment extends Fragment { @Override public void onPositionObserverUpdate() { - int duration = controller.getDuration(); - int position = controller.getPosition(); - if (duration != PlaybackController.INVALID_TIME - && position != PlaybackController.INVALID_TIME) { - txtvPosition.setText(getPositionString(position, duration)); - } } @Override @@ -127,12 +117,10 @@ public class ExternalPlayerFragment extends Fragment { @Override public void postStatusMsg(int msg) { - txtvStatus.setText(msg); } @Override public void clearStatusMsg() { - txtvStatus.setText(""); } @Override @@ -223,8 +211,6 @@ public class ExternalPlayerFragment extends Fragment { (int) getActivity().getResources().getDimension( R.dimen.external_player_height)); - txtvPosition.setText(getPositionString(media.getPosition(), - media.getDuration())); fragmentLayout.setVisibility(View.VISIBLE); if (controller.isPlayingVideo()) { butPlay.setVisibility(View.GONE); diff --git a/src/de/danoeh/antennapod/fragment/FeedlistFragment.java b/src/de/danoeh/antennapod/fragment/FeedlistFragment.java deleted file mode 100644 index 0d2f0d079..000000000 --- a/src/de/danoeh/antennapod/fragment/FeedlistFragment.java +++ /dev/null @@ -1,292 +0,0 @@ -package de.danoeh.antennapod.fragment; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v7.app.ActionBarActivity; -import android.support.v7.view.ActionMode; -import android.util.Log; -import android.view.*; -import android.widget.*; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.FeedItemlistActivity; -import de.danoeh.antennapod.adapter.FeedlistAdapter; -import de.danoeh.antennapod.asynctask.FeedRemover; -import de.danoeh.antennapod.dialog.ConfirmationDialog; -import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; -import de.danoeh.antennapod.feed.EventDistributor; -import de.danoeh.antennapod.feed.Feed; -import de.danoeh.antennapod.storage.DBReader; -import de.danoeh.antennapod.storage.DownloadRequestException; -import de.danoeh.antennapod.storage.FeedItemStatistics; -import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler; - -import java.util.List; - -public class FeedlistFragment extends Fragment implements - ActionMode.Callback, AdapterView.OnItemClickListener, - AdapterView.OnItemLongClickListener { - private static final String TAG = "FeedlistFragment"; - - private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED - | EventDistributor.DOWNLOAD_QUEUED - | EventDistributor.FEED_LIST_UPDATE - | EventDistributor.UNREAD_ITEMS_UPDATE; - - public static final String EXTRA_SELECTED_FEED = "extra.de.danoeh.antennapod.activity.selected_feed"; - - private FeedlistAdapter fla; - private List<Feed> feeds; - private List<FeedItemStatistics> feedItemStatistics; - - private Feed selectedFeed; - private ActionMode mActionMode; - - private GridView gridView; - private ListView listView; - private TextView emptyView; - - private FeedlistAdapter.ItemAccess itemAccess = new FeedlistAdapter.ItemAccess() { - - @Override - public Feed getItem(int position) { - if (feeds != null) { - return feeds.get(position); - } else { - return null; - } - } - - @Override - public FeedItemStatistics getFeedItemStatistics(int position) { - if (feedItemStatistics != null && position < feedItemStatistics.size()) { - return feedItemStatistics.get(position); - } else { - return null; - } - } - - @Override - public int getCount() { - if (feeds != null) { - return feeds.size(); - } else { - return 0; - } - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (BuildConfig.DEBUG) - Log.d(TAG, "Creating"); - fla = new FeedlistAdapter(getActivity(), itemAccess); - loadFeeds(); - } - - private void loadFeeds() { - AsyncTask<Void, Void, List[]> loadTask = new AsyncTask<Void, Void, List[]>() { - @Override - protected List[] doInBackground(Void... params) { - Context context = getActivity(); - if (context != null) { - return new List[]{DBReader.getFeedList(context), - DBReader.getFeedStatisticsList(context)}; - } else { - return null; - } - } - - - @Override - protected void onPostExecute(List[] result) { - super.onPostExecute(result); - if (result != null) { - feeds = result[0]; - feedItemStatistics = result[1]; - setEmptyViewIfListIsEmpty(); - if (fla != null) { - fla.notifyDataSetChanged(); - } - } else { - Log.e(TAG, "Failed to load feeds"); - } - } - }; - loadTask.execute(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View result = inflater.inflate(R.layout.feedlist, container, false); - listView = (ListView) result.findViewById(android.R.id.list); - gridView = (GridView) result.findViewById(R.id.grid); - emptyView = (TextView) result.findViewById(android.R.id.empty); - - return result; - - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - if (listView != null) { - listView.setOnItemClickListener(this); - listView.setOnItemLongClickListener(this); - listView.setAdapter(fla); - listView.setEmptyView(emptyView); - if (BuildConfig.DEBUG) - Log.d(TAG, "Using ListView"); - } else { - gridView.setOnItemClickListener(this); - gridView.setOnItemLongClickListener(this); - gridView.setAdapter(fla); - gridView.setEmptyView(emptyView); - if (BuildConfig.DEBUG) - Log.d(TAG, "Using GridView"); - } - setEmptyViewIfListIsEmpty(); - } - - @Override - public void onResume() { - super.onResume(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Resuming"); - EventDistributor.getInstance().register(contentUpdate); - } - - @Override - public void onDestroy() { - super.onDestroy(); - EventDistributor.getInstance().unregister(contentUpdate); - } - - @Override - public void onPause() { - super.onPause(); - if (mActionMode != null) { - mActionMode.finish(); - } - } - - private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((EVENTS & arg) != 0) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received contentUpdate Intent."); - loadFeeds(); - } - } - }; - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - FeedMenuHandler.onCreateOptionsMenu(mode.getMenuInflater(), menu); - mode.setTitle(selectedFeed.getTitle()); - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return FeedMenuHandler.onPrepareOptionsMenu(menu, selectedFeed); - } - - @SuppressLint("NewApi") - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - try { - if (FeedMenuHandler.onOptionsItemClicked(getActivity(), - item, selectedFeed)) { - loadFeeds(); - } else { - switch (item.getItemId()) { - case R.id.remove_item: - final FeedRemover remover = new FeedRemover( - getActivity(), selectedFeed) { - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - loadFeeds(); - } - }; - ConfirmationDialog conDialog = new ConfirmationDialog( - getActivity(), R.string.remove_feed_label, - R.string.feed_delete_confirmation_msg) { - - @Override - public void onConfirmButtonPressed( - DialogInterface dialog) { - dialog.dismiss(); - remover.executeAsync(); - } - }; - conDialog.createNewDialog().show(); - break; - } - } - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog( - getActivity(), e.getMessage()); - } - mode.finish(); - return true; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - mActionMode = null; - selectedFeed = null; - fla.setSelectedItemIndex(FeedlistAdapter.SELECTION_NONE); - } - - @Override - public void onItemClick(AdapterView<?> arg0, View arg1, int position, - long id) { - Feed selection = fla.getItem(position); - Intent showFeed = new Intent(getActivity(), FeedItemlistActivity.class); - showFeed.putExtra(EXTRA_SELECTED_FEED, selection.getId()); - - getActivity().startActivity(showFeed); - } - - @Override - public boolean onItemLongClick(AdapterView<?> parent, View view, - int position, long id) { - Feed selection = fla.getItem(position); - if (selection != null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Selected Feed with title " + selection.getTitle()); - if (mActionMode != null) { - mActionMode.finish(); - } - fla.setSelectedItemIndex(position); - selectedFeed = selection; - mActionMode = ((ActionBarActivity) getActivity()).startSupportActionMode(FeedlistFragment.this); - - } - return true; - } - - private AbsListView getMainView() { - return (listView != null) ? listView : gridView; - } - - private void setEmptyViewIfListIsEmpty() { - if (getMainView() != null && emptyView != null && feeds != null) { - if (feeds.isEmpty()) { - emptyView.setText(R.string.no_feeds_label); - } - } - } -} diff --git a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java index 48c544457..bf6974982 100644 --- a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -236,7 +236,7 @@ public class ItemDescriptionFragment extends Fragment { * value is inserted directly into the CSS String. */ private String applyWebviewStyle(String textColor, String data) { - final String WEBVIEW_STYLE = "<html><head><style type=\"text/css\"> * { color: %s; font-family: Helvetica; line-height: 1.5em; font-size: 11pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }</style></head><body>%s</body></html>"; + final String WEBVIEW_STYLE = "<html><head><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: 11pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }</style></head><body>%s</body></html>"; final int pageMargin = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 8, getResources() .getDisplayMetrics()); diff --git a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java index e5845dd76..5d0b1bec8 100644 --- a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java +++ b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java @@ -2,326 +2,437 @@ package de.danoeh.antennapod.fragment; import android.annotation.SuppressLint; 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.os.Handler; import android.support.v4.app.ListFragment; import android.support.v7.app.ActionBarActivity; +import android.support.v7.widget.SearchView; import android.util.Log; import android.view.*; -import android.view.ContextMenu.ContextMenuInfo; +import android.widget.ImageButton; +import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.ItemviewActivity; -import de.danoeh.antennapod.adapter.ActionButtonCallback; -import de.danoeh.antennapod.adapter.InternalFeedItemlistAdapter; +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.asynctask.DownloadObserver; +import de.danoeh.antennapod.asynctask.FeedRemover; +import de.danoeh.antennapod.asynctask.ImageLoader; +import de.danoeh.antennapod.dialog.ConfirmationDialog; import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; +import de.danoeh.antennapod.dialog.FeedItemDialog; import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.service.download.DownloadService; +import de.danoeh.antennapod.service.download.Downloader; import de.danoeh.antennapod.storage.DBReader; import de.danoeh.antennapod.storage.DownloadRequestException; import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.QueueAccess; -import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler; +import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler; +import de.danoeh.antennapod.util.menuhandler.MenuItemUtils; import java.util.List; -/** Displays a list of FeedItems. */ +/** + * Displays a list of FeedItems. + */ @SuppressLint("ValidFragment") public class ItemlistFragment extends ListFragment { - private static final String TAG = "ItemlistFragment"; - - private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED - | EventDistributor.DOWNLOAD_QUEUED - | EventDistributor.QUEUE_UPDATE - | EventDistributor.UNREAD_ITEMS_UPDATE; - - public static final String EXTRA_SELECTED_FEEDITEM = "extra.de.danoeh.antennapod.activity.selected_feeditem"; - public static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id"; - protected InternalFeedItemlistAdapter fila; - - private Feed feed; - protected List<Long> queue; - - protected FeedItem selectedItem = null; - protected boolean contextMenuClosed = true; - - /** Argument for FeeditemlistAdapter */ - protected boolean showFeedtitle; - - private AsyncTask<Long, Void, Feed> currentLoadTask; - - public ItemlistFragment(boolean showFeedtitle) { - super(); - this.showFeedtitle = showFeedtitle; - } - - public ItemlistFragment() { - } - - /** - * Creates new ItemlistFragment which shows the Feeditems of a specific - * feed. Sets 'showFeedtitle' to false - * - * @param feedId - * The id of the feed to show - * @return the newly created instance of an ItemlistFragment - */ - public static ItemlistFragment newInstance(long feedId) { - ItemlistFragment i = new ItemlistFragment(); - i.showFeedtitle = false; - Bundle b = new Bundle(); - b.putLong(ARGUMENT_FEED_ID, feedId); - i.setArguments(b); - return i; - } - - private InternalFeedItemlistAdapter.ItemAccess itemAccessRef; - protected InternalFeedItemlistAdapter.ItemAccess itemAccess() { - if (itemAccessRef == null) { - itemAccessRef = new InternalFeedItemlistAdapter.ItemAccess() { + private static final String TAG = "ItemlistFragment"; - @Override - public FeedItem getItem(int position) { - return (feed != null) ? feed.getItemAtIndex(true, position) : null; - } + private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED + | EventDistributor.DOWNLOAD_QUEUED + | EventDistributor.QUEUE_UPDATE + | EventDistributor.UNREAD_ITEMS_UPDATE; - @Override - public int getCount() { - return (feed != null) ? feed.getNumOfItems(true) : 0; - } + public static final String EXTRA_SELECTED_FEEDITEM = "extra.de.danoeh.antennapod.activity.selected_feeditem"; + public static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id"; - @Override - public boolean isInQueue(FeedItem item) { - return (queue != null) && queue.contains(item.getId()); - } - }; - } - return itemAccessRef; + protected FeedItemlistAdapter adapter; + + private long feedID; + private Feed feed; + protected QueueAccess queue; + + private boolean itemsLoaded = false; + private boolean viewsCreated = false; + + private DownloadObserver downloadObserver; + private List<Downloader> downloaderList; + + private FeedItemDialog feedItemDialog; + private FeedItemDialog.FeedItemDialogSavedInstance feedItemDialogSavedInstance; + + + /** + * Creates new ItemlistFragment which shows the Feeditems of a specific + * feed. Sets 'showFeedtitle' to false + * + * @param feedId The id of the feed to show + * @return the newly created instance of an ItemlistFragment + */ + public static ItemlistFragment newInstance(long feedId) { + ItemlistFragment i = new ItemlistFragment(); + Bundle b = new Bundle(); + b.putLong(ARGUMENT_FEED_ID, feedId); + i.setArguments(b); + return i; } - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - return inflater.inflate(R.layout.feeditemlist, container, false); - } + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + setHasOptionsMenu(true); + + Bundle args = getArguments(); + if (args == null) throw new IllegalArgumentException("args invalid"); + feedID = args.getLong(ARGUMENT_FEED_ID); + } @Override public void onStart() { super.onStart(); EventDistributor.getInstance().register(contentUpdate); - loadData(); + if (downloadObserver != null) { + downloadObserver.setActivity(getActivity()); + downloadObserver.onResume(); + } + if (viewsCreated && itemsLoaded) { + onFragmentLoaded(); + } } @Override public void onStop() { super.onStop(); EventDistributor.getInstance().unregister(contentUpdate); - if (currentLoadTask != null) { - currentLoadTask.cancel(true); - } + stopItemLoader(); } - protected synchronized void loadData() { - final long feedId; - if (feed == null) { - feedId = getArguments().getLong(ARGUMENT_FEED_ID); - } else { - feedId = feed.getId(); + @Override + public void onResume() { + super.onResume(); + updateProgressBarVisibility(); + startItemLoader(); + } + + @Override + public void onDetach() { + super.onDetach(); + stopItemLoader(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + resetViewState(); + } + + private void resetViewState() { + adapter = null; + viewsCreated = false; + if (downloadObserver != null) { + downloadObserver.onPause(); } - if (currentLoadTask != null) { - currentLoadTask.cancel(true); + if (feedItemDialog != null) { + feedItemDialogSavedInstance = feedItemDialog.save(); } - AsyncTask<Long, Void, Feed> loadTask = new AsyncTask<Long, Void, Feed>(){ - private volatile List<Long> queueRef; + feedItemDialog = null; + } + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + FeedMenuHandler.onCreateOptionsMenu(inflater, menu); + + final SearchView sv = new SearchView(getActivity()); + MenuItemUtils.addSearchItem(menu, sv); + sv.setQueryHint(getString(R.string.search_hint)); + sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override - protected Feed doInBackground(Long... longs) { - Context context = ItemlistFragment.this.getActivity(); - if (context != null) { - Feed result = DBReader.getFeed(context, longs[0]); - if (result != null) { - result.setItems(DBReader.getFeedItemList(context, result)); - queueRef = DBReader.getQueueIDList(context); - return result; - } + public boolean onQueryTextSubmit(String s) { + sv.clearFocus(); + if (itemsLoaded) { + ((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(s, feed.getId())); } - return null; + return true; } @Override - protected void onPostExecute(Feed result) { - super.onPostExecute(result); - if (result != null && result.getItems() != null) { - feed = result; - if (queueRef != null) { - queue = queueRef; - } else { - Log.e(TAG, "Could not load queue"); - } - setEmptyViewIfListIsEmpty(); - if (fila != null) { - fila.notifyDataSetChanged(); + public boolean onQueryTextChange(String s) { + return false; + } + }); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + FeedMenuHandler.onPrepareOptionsMenu(menu, feed); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (!super.onOptionsItemSelected(item)) { + try { + if (!FeedMenuHandler.onOptionsItemClicked(getActivity(), item, feed)) { + switch (item.getItemId()) { + case R.id.remove_item: + final FeedRemover remover = new FeedRemover( + getActivity(), feed) { + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + ((MainActivity) getActivity()).loadNavFragment(MainActivity.POS_NEW, null); + } + }; + ConfirmationDialog conDialog = new ConfirmationDialog(getActivity(), + R.string.remove_feed_label, + R.string.feed_delete_confirmation_msg) { + + @Override + public void onConfirmButtonPressed( + DialogInterface dialog) { + dialog.dismiss(); + remover.executeAsync(); + } + }; + conDialog.createNewDialog().show(); + return true; + default: + return false; + } } else { - if (result == null) { - Log.e(TAG, "Could not load feed with id " + feedId); - } else if (result.getItems() == null) { - Log.e(TAG, "Could not load feed items"); - } + return true; } + } catch (DownloadRequestException e) { + e.printStackTrace(); + DownloadRequestErrorDialogCreator.newRequestErrorDialog(getActivity(), e.getMessage()); + return true; } - }; - currentLoadTask = loadTask; - loadTask.execute(feedId); + } else { + return true; + } + } - private void setEmptyViewIfListIsEmpty() { - if (getListView() != null && feed != null && feed.getItems() != null) { - if (feed.getItems().isEmpty()) { - ((TextView) getActivity().findViewById(android.R.id.empty)).setText(R.string.no_items_label); - } + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + ((ActionBarActivity) getActivity()).getSupportActionBar().setTitle(""); + + viewsCreated = true; + if (itemsLoaded) { + onFragmentLoaded(); } } - protected InternalFeedItemlistAdapter createListAdapter() { - return new InternalFeedItemlistAdapter(getActivity(), itemAccess(), - adapterCallback, showFeedtitle); - } - - @Override - public void onResume() { - super.onResume(); - getActivity().runOnUiThread(new Runnable() { - - @Override - public void run() { - fila.notifyDataSetChanged(); - } - }); - updateProgressBarVisibility(); - } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - FeedItem selection = fila.getItem(position - l.getHeaderViewsCount()); - Intent showItem = new Intent(getActivity(), ItemviewActivity.class); - showItem.putExtra(FeedlistFragment.EXTRA_SELECTED_FEED, selection - .getFeed().getId()); - showItem.putExtra(EXTRA_SELECTED_FEEDITEM, selection.getId()); - - startActivity(showItem); - } - - private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((EVENTS & arg) != 0) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received contentUpdate Intent."); - if ((EventDistributor.DOWNLOAD_QUEUED & arg) != 0) { - updateProgressBarVisibility(); - } else { - if (feed != null) { - loadData(); - } - updateProgressBarVisibility(); - } - } - } - }; - - private void updateProgressBarVisibility() { - if (feed != null) { - if (DownloadService.isRunning - && DownloadRequester.getInstance().isDownloadingFile(feed)) { + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + FeedItem selection = adapter.getItem(position - l.getHeaderViewsCount()); + feedItemDialog = FeedItemDialog.newInstance(getActivity(), selection, queue); + feedItemDialog.show(); + } + + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + + @Override + public void update(EventDistributor eventDistributor, Integer arg) { + if ((EVENTS & arg) != 0) { + if (BuildConfig.DEBUG) + Log.d(TAG, "Received contentUpdate Intent."); + if ((EventDistributor.DOWNLOAD_QUEUED & arg) != 0) { + updateProgressBarVisibility(); + } else { + startItemLoader(); + updateProgressBarVisibility(); + } + } + } + }; + + private void updateProgressBarVisibility() { + if (feed != null) { + if (DownloadService.isRunning + && DownloadRequester.getInstance().isDownloadingFile(feed)) { ((ActionBarActivity) getActivity()) - .setSupportProgressBarIndeterminateVisibility(true); - } else { + .setSupportProgressBarIndeterminateVisibility(true); + } else { ((ActionBarActivity) getActivity()) - .setSupportProgressBarIndeterminateVisibility(false); - } + .setSupportProgressBarIndeterminateVisibility(false); + } getActivity().supportInvalidateOptionsMenu(); - } - } - - protected ActionButtonCallback adapterCallback = new ActionButtonCallback() { - - @Override - public void onActionButtonPressed(FeedItem item) { - selectedItem = item; - contextMenuClosed = true; - getListView().showContextMenu(); - } - }; - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - fila = createListAdapter(); - setListAdapter(fila); - this.getListView().setItemsCanFocus(true); - getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); - registerForContextMenu(getListView()); - getListView().setOnItemLongClickListener(null); - } - - @Override - public void onCreateContextMenu(final ContextMenu menu, View v, - ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - if (!contextMenuClosed) { // true if context menu was cancelled before - selectedItem = null; - } - contextMenuClosed = false; - getListView().setOnItemLongClickListener(null); - if (selectedItem != null) { - new MenuInflater(ItemlistFragment.this.getActivity()).inflate( - R.menu.feeditem, menu); - - menu.setHeaderTitle(selectedItem.getTitle()); - FeedItemMenuHandler.onPrepareMenu( - new FeedItemMenuHandler.MenuInterface() { - - @Override - public void setItemVisibility(int id, boolean visible) { - menu.findItem(id).setVisible(visible); - } - }, selectedItem, false, QueueAccess.IDListAccess(queue)); - - } - } - - @Override - public boolean onContextItemSelected(android.view.MenuItem item) { - boolean handled = false; - - if (selectedItem != null) { - - try { - handled = FeedItemMenuHandler.onMenuItemClicked( - getActivity(), item.getItemId(), selectedItem); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog( - getActivity(), e.getMessage()); - } - if (handled) { - fila.notifyDataSetChanged(); - } - - } - selectedItem = null; - contextMenuClosed = true; - return handled; - } - - public InternalFeedItemlistAdapter getListAdapter() { - return fila; - } + } + } + + private void onFragmentLoaded() { + if (adapter == null) { + getListView().setAdapter(null); + setupHeaderView(); + adapter = new FeedItemlistAdapter(getActivity(), itemAccess, new DefaultActionButtonCallback(getActivity()), false); + setListAdapter(adapter); + downloadObserver = new DownloadObserver(getActivity(), new Handler(), downloadObserverCallback); + downloadObserver.onResume(); + } + setListShown(true); + adapter.notifyDataSetChanged(); + + if (feedItemDialog != null) { + feedItemDialog.updateContent(queue, feed.getItems()); + } else if (feedItemDialogSavedInstance != null) { + feedItemDialog = FeedItemDialog.newInstance(getActivity(), feedItemDialogSavedInstance); + } + getActivity().supportInvalidateOptionsMenu(); + } + + private DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() { + @Override + public void onContentChanged() { + if (adapter != null) { + adapter.notifyDataSetChanged(); + } + if (feedItemDialog != null && feedItemDialog.isShowing()) { + feedItemDialog.updateMenuAppearance(); + } + } + + @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"); + return; + } + LayoutInflater inflater = (LayoutInflater) + getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View header = inflater.inflate(R.layout.feeditemlist_header, null); + getListView().addHeaderView(header); + + TextView txtvTitle = (TextView) header.findViewById(R.id.txtvTitle); + TextView txtvAuthor = (TextView) header.findViewById(R.id.txtvAuthor); + ImageView imgvCover = (ImageView) header.findViewById(R.id.imgvCover); + ImageButton butShowInfo = (ImageButton) header.findViewById(R.id.butShowInfo); + ImageButton butVisitWebsite = (ImageButton) header.findViewById(R.id.butVisitWebsite); + + txtvTitle.setText(feed.getTitle()); + txtvAuthor.setText(feed.getAuthor()); + ImageLoader.getInstance().loadThumbnailBitmap(feed.getImage(), imgvCover, + (int) getResources().getDimension(R.dimen.thumbnail_length_onlinefeedview)); + if (feed.getLink() == null) { + butVisitWebsite.setVisibility(View.INVISIBLE); + } else { + butVisitWebsite.setVisibility(View.VISIBLE); + butVisitWebsite.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Uri uri = Uri.parse(feed.getLink()); + startActivity(new Intent(Intent.ACTION_VIEW, uri)); + } + }); + } + 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); + } + } + }); + } + private FeedItemlistAdapter.ItemAccess itemAccess = new FeedItemlistAdapter.ItemAccess() { + + @Override + public FeedItem getItem(int position) { + return (feed != null) ? feed.getItemAtIndex(true, position) : null; + } + + @Override + public int getCount() { + return (feed != null) ? feed.getNumOfItems(true) : 0; + } + + @Override + public boolean isInQueue(FeedItem item) { + return (queue != null) && queue.contains(item.getId()); + } + + @Override + public int getItemDownloadProgressPercent(FeedItem item) { + if (downloaderList != null) { + for (Downloader downloader : downloaderList) { + if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA + && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) { + return downloader.getDownloadRequest().getProgressPercent(); + } + } + } + return 0; + } + }; + + private ItemLoader itemLoader; + + private void startItemLoader() { + if (itemLoader != null) { + itemLoader.cancel(true); + } + itemLoader = new ItemLoader(); + itemLoader.execute(feedID); + } + + private void stopItemLoader() { + if (itemLoader != null) { + itemLoader.cancel(true); + } + } + + private class ItemLoader extends AsyncTask<Long, Void, Object[]> { + @Override + protected Object[] doInBackground(Long... params) { + long feedID = params[0]; + Context context = getActivity(); + if (context != null) { + return new Object[]{DBReader.getFeed(context, feedID), + QueueAccess.IDListAccess(DBReader.getQueueIDList(context))}; + } else { + return null; + } + } + + @Override + protected void onPostExecute(Object[] res) { + super.onPostExecute(res); + if (res != null) { + feed = (Feed) res[0]; + queue = (QueueAccess) res[1]; + itemsLoaded = true; + if (viewsCreated) { + onFragmentLoaded(); + } + } + } + } } diff --git a/src/de/danoeh/antennapod/fragment/MiroGuideChannellistFragment.java b/src/de/danoeh/antennapod/fragment/MiroGuideChannellistFragment.java deleted file mode 100644 index 53910b673..000000000 --- a/src/de/danoeh/antennapod/fragment/MiroGuideChannellistFragment.java +++ /dev/null @@ -1,266 +0,0 @@ -package de.danoeh.antennapod.fragment; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.ListFragment; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.AbsListView; -import android.widget.AbsListView.OnScrollListener; -import android.widget.ListView; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.MiroGuideChannelViewActivity; -import de.danoeh.antennapod.adapter.MiroGuideChannelListAdapter; -import de.danoeh.antennapod.miroguide.conn.MiroGuideException; -import de.danoeh.antennapod.miroguide.conn.MiroGuideService; -import de.danoeh.antennapod.miroguide.model.MiroGuideChannel; - -import java.util.ArrayList; -import java.util.List; - -/** - * Displays a list of MiroGuideChannel objects that were results of a certain - * MiroGuideService query. If the user reaches the bottom of the list, more - * entries will be loaded until all entries have been loaded or the maximum - * number of channels has been reached. - * */ -public class MiroGuideChannellistFragment extends ListFragment { - private static final String TAG = "MiroGuideChannellistFragment"; - - private static final String ARG_FILTER = "filter"; - private static final String ARG_FILTER_VALUE = "filter_value"; - private static final String ARG_SORT = "sort"; - - private static final int MAX_CHANNELS = 200; - private static final int CHANNELS_PER_QUERY = MiroGuideService.DEFAULT_CHANNEL_LIMIT; - - private ArrayList<MiroGuideChannel> channels; - private MiroGuideChannelListAdapter listAdapter; - private int offset; - - private boolean isLoadingChannels; - /** - * True if there are no more entries to load or if the maximum number of - * channels in the channellist has been reached - */ - private boolean stopLoading; - - private View footer; - - private String filter; - private String filterValue; - private String sort; - - private AsyncTask<Void, Void, List<MiroGuideChannel>> channelLoader; - - /** - * Creates a new instance of Channellist fragment. - * - * @throws IllegalArgumentException - * if filter, filterValue or sort is null - * */ - public static MiroGuideChannellistFragment newInstance(String filter, - String filterValue, String sort) { - if (filter == null) { - throw new IllegalArgumentException("filter cannot be null"); - } - if (filterValue == null) { - throw new IllegalArgumentException("filter value cannot be null"); - } - if (sort == null) { - throw new IllegalArgumentException("sort cannot be null"); - } - MiroGuideChannellistFragment cf = new MiroGuideChannellistFragment(); - Bundle args = new Bundle(); - args.putString(ARG_FILTER, filter); - args.putString(ARG_FILTER_VALUE, filterValue); - args.putString(ARG_SORT, sort); - cf.setArguments(args); - return cf; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - offset = 0; - channels = new ArrayList<MiroGuideChannel>(); - - Bundle args = getArguments(); - filter = args.getString(ARG_FILTER); - filterValue = args.getString(ARG_FILTER_VALUE); - sort = args.getString(ARG_SORT); - - LayoutInflater inflater = (LayoutInflater) getActivity() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - footer = inflater.inflate(R.layout.loading_footer, null); - listAdapter = new MiroGuideChannelListAdapter(getActivity(), 0, - channels); - } - - @Override - public void onResume() { - super.onResume(); - if (channels.isEmpty()) { - setListShown(false); - loadChannels(); - } - } - - @Override - public void onPause() { - super.onPause(); - if (channelLoader != null) { - channelLoader.cancel(true); - } - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - getListView().addFooterView(footer); // footer has to be added before - // the adapter has been set - getListView().setAdapter(listAdapter); - getListView().removeFooterView(footer); - - getListView().setOnScrollListener(new OnScrollListener() { - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, - int visibleItemCount, int totalItemCount) { - int lastVisibleItem = firstVisibleItem + visibleItemCount; - if (lastVisibleItem == totalItemCount) { - if (BuildConfig.DEBUG) - loadChannels(); - } - } - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - } - }); - } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - super.onListItemClick(l, v, position, id); - if (listAdapter != null) { - MiroGuideChannel selection = listAdapter.getItem(position); - Intent launchIntent = new Intent(getActivity(), - MiroGuideChannelViewActivity.class); - launchIntent.putExtra( - MiroGuideChannelViewActivity.EXTRA_CHANNEL_ID, - selection.getId()); - launchIntent.putExtra( - MiroGuideChannelViewActivity.EXTRA_CHANNEL_URL, - selection.getDownloadUrl()); - startActivity(launchIntent); - } - } - - @SuppressLint("NewApi") - private void loadChannels() { - if (!isLoadingChannels) { - if (!stopLoading) { - isLoadingChannels = true; - channelLoader = new AsyncTask<Void, Void, List<MiroGuideChannel>>() { - private MiroGuideException exception; - - @Override - protected void onCancelled() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Channel loader was cancelled"); - } - - @Override - protected void onPostExecute(List<MiroGuideChannel> result) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Channel loading finished"); - if (exception == null) { - getListView().removeFooterView(footer); - for (MiroGuideChannel channel : result) { - channels.add(channel); - } - listAdapter.notifyDataSetChanged(); - offset += CHANNELS_PER_QUERY; - // check if fragment should not send any more - // queries - if (result.size() < CHANNELS_PER_QUERY) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Query result was less than requested number of channels. Stopping to send any more queries"); - stopLoading = true; - } - if (offset >= MAX_CHANNELS) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Maximum number of feeds has been reached. Stopping to send any more queries"); - stopLoading = true; - } - - setListShown(true); - } else { - AlertDialog.Builder dialog = new AlertDialog.Builder( - getActivity()); - dialog.setTitle(R.string.error_label); - dialog.setMessage(exception.getMessage()); - dialog.setNeutralButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - - @Override - public void onClick( - DialogInterface dialog, - int which) { - dialog.dismiss(); - } - }); - dialog.create().show(); - } - isLoadingChannels = false; - } - - @Override - protected void onPreExecute() { - getListView().addFooterView(footer); - } - - @Override - protected List<MiroGuideChannel> doInBackground( - Void... params) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Background channel loader started"); - MiroGuideService service = new MiroGuideService(); - try { - return service.getChannelList(filter, filterValue, - sort, CHANNELS_PER_QUERY, offset); - } catch (MiroGuideException e) { - exception = e; - e.printStackTrace(); - } finally { - // service.close(); - } - return null; - } - }; - - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - channelLoader - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - channelLoader.execute(); - } - } - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Channels are already being loaded"); - } - } -} diff --git a/src/de/danoeh/antennapod/fragment/NewEpisodesFragment.java b/src/de/danoeh/antennapod/fragment/NewEpisodesFragment.java new file mode 100644 index 000000000..0c42bdd65 --- /dev/null +++ b/src/de/danoeh/antennapod/fragment/NewEpisodesFragment.java @@ -0,0 +1,442 @@ +package de.danoeh.antennapod.fragment; + +import android.app.Activity; +import android.content.Context; +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.widget.SwipeRefreshLayout; +import android.support.v7.app.ActionBarActivity; +import android.support.v7.widget.SearchView; +import android.view.*; +import android.widget.AdapterView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; +import com.mobeta.android.dslv.DragSortListView; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.adapter.DefaultActionButtonCallback; +import de.danoeh.antennapod.adapter.NewEpisodesListAdapter; +import de.danoeh.antennapod.asynctask.DownloadObserver; +import de.danoeh.antennapod.dialog.FeedItemDialog; +import de.danoeh.antennapod.feed.EventDistributor; +import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.preferences.UserPreferences; +import de.danoeh.antennapod.service.download.DownloadService; +import de.danoeh.antennapod.service.download.Downloader; +import de.danoeh.antennapod.storage.DBReader; +import de.danoeh.antennapod.storage.DBTasks; +import de.danoeh.antennapod.storage.DBWriter; +import de.danoeh.antennapod.storage.DownloadRequester; +import de.danoeh.antennapod.util.QueueAccess; +import de.danoeh.antennapod.util.menuhandler.MenuItemUtils; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Shows unread or recently published episodes + */ +public class NewEpisodesFragment extends Fragment { + private static final String TAG = "NewEpisodesFragment"; + private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | + EventDistributor.DOWNLOAD_QUEUED | + EventDistributor.QUEUE_UPDATE | + EventDistributor.UNREAD_ITEMS_UPDATE; + + private static final int RECENT_EPISODES_LIMIT = 150; + private static final String PREF_NAME = "PrefNewEpisodesFragment"; + private static final String PREF_EPISODE_FILTER_BOOL = "newEpisodeFilterEnabled"; + + + private DragSortListView listView; + private SwipeRefreshLayout swipeRefreshLayout; + private NewEpisodesListAdapter listAdapter; + private TextView txtvEmpty; + private ProgressBar progLoading; + + private List<FeedItem> unreadItems; + private List<FeedItem> recentItems; + private QueueAccess queueAccess; + private List<Downloader> downloaderList; + + private boolean itemsLoaded = false; + private boolean viewsCreated = false; + private boolean showOnlyNewEpisodes = false; + + private AtomicReference<MainActivity> activity = new AtomicReference<MainActivity>(); + + private DownloadObserver downloadObserver = null; + + private FeedItemDialog feedItemDialog; + private FeedItemDialog.FeedItemDialogSavedInstance feedItemDialogSavedInstance; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + setHasOptionsMenu(true); + + updateShowOnlyEpisodes(); + } + + @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 onStop() { + super.onStop(); + EventDistributor.getInstance().unregister(contentUpdate); + stopItemLoader(); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + this.activity.set((MainActivity) getActivity()); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + resetViewState(); + } + + private void resetViewState() { + listAdapter = null; + activity.set(null); + viewsCreated = false; + if (downloadObserver != null) { + downloadObserver.onPause(); + } + if (feedItemDialog != null) { + feedItemDialogSavedInstance = feedItemDialog.save(); + } + feedItemDialog = null; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.new_episodes, menu); + + final SearchView sv = new SearchView(getActivity()); + MenuItemUtils.addSearchItem(menu, sv); + sv.setQueryHint(getString(R.string.search_hint)); + sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String s) { + sv.clearFocus(); + ((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(s)); + return true; + } + + @Override + public boolean onQueryTextChange(String s) { + return false; + } + }); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + menu.findItem(R.id.mark_all_read_item).setVisible(unreadItems != null && !unreadItems.isEmpty()); + menu.findItem(R.id.episode_filter_item).setChecked(showOnlyNewEpisodes); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (!super.onOptionsItemSelected(item)) { + switch (item.getItemId()) { + case R.id.refresh_item: + List<Feed> feeds = ((MainActivity) getActivity()).getFeeds(); + if (feeds != null) { + DBTasks.refreshAllFeeds(getActivity(), feeds); + } + return true; + case R.id.mark_all_read_item: + DBWriter.markAllItemsRead(getActivity()); + Toast.makeText(getActivity(), R.string.mark_all_read_msg, Toast.LENGTH_SHORT).show(); + return true; + case R.id.episode_filter_item: + boolean newVal = !item.isChecked(); + setShowOnlyNewEpisodes(newVal); + item.setChecked(newVal); + return true; + default: + return false; + } + } else { + return true; + } + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + ((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.all_episodes_label); + + View root = inflater.inflate(R.layout.new_episodes_fragment, container, false); + + swipeRefreshLayout = (SwipeRefreshLayout) root.findViewById(R.id.swipeRefreshLayout); + 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) { + feedItemDialog = FeedItemDialog.newInstance(activity.get(), item, queueAccess); + feedItemDialog.show(); + } + + } + }); + + final int secondColor = (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) ? R.color.swipe_refresh_secondary_color_dark : R.color.swipe_refresh_secondary_color_light; + swipeRefreshLayout.setColorScheme(R.color.bright_blue, secondColor, R.color.bright_blue, secondColor); + swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + List<Feed> feeds = ((MainActivity) getActivity()).getFeeds(); + if (feeds != null) { + DBTasks.refreshAllFeeds(getActivity(), feeds); + } + } + }); + + if (!itemsLoaded) { + progLoading.setVisibility(View.VISIBLE); + txtvEmpty.setVisibility(View.GONE); + } + + viewsCreated = true; + + if (itemsLoaded && activity.get() != null) { + onFragmentLoaded(); + } + + return root; + } + + private void onFragmentLoaded() { + if (listAdapter == null) { + listAdapter = new NewEpisodesListAdapter(activity.get(), itemAccess, new DefaultActionButtonCallback(activity.get())); + listView.setAdapter(listAdapter); + listView.setEmptyView(txtvEmpty); + downloadObserver = new DownloadObserver(activity.get(), new Handler(), downloadObserverCallback); + downloadObserver.onResume(); + } + if (feedItemDialog != null) { + feedItemDialog.updateContent(queueAccess, unreadItems, recentItems); + } else if (feedItemDialogSavedInstance != null) { + feedItemDialog = FeedItemDialog.newInstance(activity.get(), feedItemDialogSavedInstance); + } + listAdapter.notifyDataSetChanged(); + getActivity().supportInvalidateOptionsMenu(); + updateProgressBarVisibility(); + updateShowOnlyEpisodesListViewState(); + } + + private DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() { + @Override + public void onContentChanged() { + if (listAdapter != null) { + listAdapter.notifyDataSetChanged(); + } + if (feedItemDialog != null && feedItemDialog.isShowing()) { + feedItemDialog.updateMenuAppearance(); + } + } + + @Override + public void onDownloadDataAvailable(List<Downloader> downloaderList) { + NewEpisodesFragment.this.downloaderList = downloaderList; + if (listAdapter != null) { + listAdapter.notifyDataSetChanged(); + } + } + }; + + private NewEpisodesListAdapter.ItemAccess itemAccess = new NewEpisodesListAdapter.ItemAccess() { + + @Override + public int getCount() { + if (itemsLoaded) { + return (showOnlyNewEpisodes) ? unreadItems.size() : recentItems.size(); + } + return 0; + } + + @Override + public FeedItem getItem(int position) { + if (itemsLoaded) { + return (showOnlyNewEpisodes) ? unreadItems.get(position) : recentItems.get(position); + } + return null; + } + + @Override + public int getItemDownloadProgressPercent(FeedItem item) { + if (downloaderList != null) { + for (Downloader downloader : downloaderList) { + if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA + && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) { + return downloader.getDownloadRequest().getProgressPercent(); + } + } + } + return 0; + } + + @Override + public boolean isInQueue(FeedItem item) { + if (itemsLoaded) { + return queueAccess.contains(item.getId()); + } else { + return false; + } + } + + + }; + + private void updateProgressBarVisibility() { + if (!viewsCreated) { + return; + } + + if (DownloadService.isRunning + && DownloadRequester.getInstance().isDownloadingFeeds()) { + swipeRefreshLayout.setRefreshing(true); + + } else { + swipeRefreshLayout.setRefreshing(false); + + // if case other fragments have set this to true, this fragment should remove the progress indicator + ((ActionBarActivity) getActivity()) + .setSupportProgressBarIndeterminateVisibility(false); + } + } + + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + @Override + public void update(EventDistributor eventDistributor, Integer arg) { + if ((arg & EVENTS) != 0) { + startItemLoader(); + updateProgressBarVisibility(); + } + } + }; + + private void updateShowOnlyEpisodes() { + SharedPreferences prefs = getActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + showOnlyNewEpisodes = prefs.getBoolean(PREF_EPISODE_FILTER_BOOL, false); + } + + private void setShowOnlyNewEpisodes(boolean newVal) { + showOnlyNewEpisodes = newVal; + SharedPreferences prefs = getActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(PREF_EPISODE_FILTER_BOOL, showOnlyNewEpisodes); + editor.commit(); + if (itemsLoaded && viewsCreated) { + listAdapter.notifyDataSetChanged(); + activity.get().supportInvalidateOptionsMenu(); + updateShowOnlyEpisodesListViewState(); + } + } + + private void updateShowOnlyEpisodesListViewState() { + if (showOnlyNewEpisodes) { + listView.setEmptyView(null); + txtvEmpty.setVisibility(View.GONE); + } else { + listView.setEmptyView(txtvEmpty); + } + } + + 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); + } + } + + 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); + } + } + + @Override + protected Object[] doInBackground(Void... params) { + Context context = activity.get(); + if (context != null) { + return new Object[]{DBReader.getUnreadItemsList(context), + DBReader.getRecentlyPublishedEpisodes(context, RECENT_EPISODES_LIMIT), + QueueAccess.IDListAccess(DBReader.getQueueIDList(context))}; + } else { + return null; + } + } + + @Override + protected void onPostExecute(Object[] lists) { + super.onPostExecute(lists); + listView.setVisibility(View.VISIBLE); + progLoading.setVisibility(View.GONE); + + if (lists != null) { + unreadItems = (List<FeedItem>) lists[0]; + recentItems = (List<FeedItem>) lists[1]; + queueAccess = (QueueAccess) lists[2]; + itemsLoaded = true; + if (viewsCreated && activity.get() != null) { + onFragmentLoaded(); + } + } + } + } +} diff --git a/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java index d6524853f..bab5143d5 100644 --- a/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -1,112 +1,282 @@ 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.util.Log; -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.adapter.InternalFeedItemlistAdapter; +import android.os.Handler; +import android.support.v4.app.ListFragment; +import android.support.v4.view.MenuItemCompat; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.ListView; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.adapter.DefaultActionButtonCallback; +import de.danoeh.antennapod.adapter.FeedItemlistAdapter; +import de.danoeh.antennapod.asynctask.DownloadObserver; +import de.danoeh.antennapod.dialog.FeedItemDialog; import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.service.download.Downloader; import de.danoeh.antennapod.storage.DBReader; +import de.danoeh.antennapod.storage.DBWriter; +import de.danoeh.antennapod.util.QueueAccess; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; -public class PlaybackHistoryFragment extends ItemlistFragment { - private static final String TAG = "PlaybackHistoryFragment"; +public class PlaybackHistoryFragment extends ListFragment { + private static final String TAG = "PlaybackHistoryFragment"; private List<FeedItem> playbackHistory; + private QueueAccess queue; + private FeedItemlistAdapter adapter; - public PlaybackHistoryFragment() { - super(true); - } + private boolean itemsLoaded = false; + private boolean viewsCreated = false; + + private AtomicReference<Activity> activity = new AtomicReference<Activity>(); + + private DownloadObserver downloadObserver; + private List<Downloader> downloaderList; + + private FeedItemDialog feedItemDialog; + private FeedItemDialog.FeedItemDialogSavedInstance feedItemDialogSavedInstance; - InternalFeedItemlistAdapter.ItemAccess itemAccessRef; @Override - protected InternalFeedItemlistAdapter.ItemAccess itemAccess() { - if (itemAccessRef == null) { - itemAccessRef = new InternalFeedItemlistAdapter.ItemAccess() { + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + setHasOptionsMenu(true); + } - @Override - public FeedItem getItem(int position) { - return (playbackHistory != null) ? playbackHistory.get(position) : null; - } + @Override + public void onResume() { + super.onResume(); + startItemLoader(); + } - @Override - public int getCount() { - return (playbackHistory != null) ? playbackHistory.size() : 0; - } + @Override + public void onStart() { + super.onStart(); + EventDistributor.getInstance().register(contentUpdate); + } - @Override - public boolean isInQueue(FeedItem item) { - return (queue != null) ? queue.contains(item.getId()) : false; - } - }; + @Override + public void onStop() { + super.onStop(); + EventDistributor.getInstance().unregister(contentUpdate); + stopItemLoader(); + } + + @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(); } - return itemAccessRef; } @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - EventDistributor.getInstance().register(historyUpdate); - } - - @Override - public void onDestroy() { - super.onDestroy(); - EventDistributor.getInstance().unregister(historyUpdate); - } - - private EventDistributor.EventListener historyUpdate = new EventDistributor.EventListener() { - - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((EventDistributor.PLAYBACK_HISTORY_UPDATE & arg) != 0) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received content update"); - loadData(); - } - - } - }; + public void onDestroyView() { + super.onDestroyView(); + adapter = null; + viewsCreated = false; + if (downloadObserver != null) { + downloadObserver.onPause(); + } + if (feedItemDialog != null) { + feedItemDialogSavedInstance = feedItemDialog.save(); + } + feedItemDialog = null; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + viewsCreated = true; + if (itemsLoaded) { + onFragmentLoaded(); + } + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + super.onListItemClick(l, v, position, id); + FeedItem item = adapter.getItem(position - l.getHeaderViewsCount()); + if (item != null) { + feedItemDialog = FeedItemDialog.newInstance(activity.get(), item, queue); + feedItemDialog.show(); + } + } @Override - protected void loadData() { - AsyncTask<Void, Void, Void> loadTask = new AsyncTask<Void, Void, Void>() { - private volatile List<FeedItem> phRef; - private volatile List<Long> queueRef; - - @Override - protected Void doInBackground(Void... voids) { - Context context = PlaybackHistoryFragment.this.getActivity(); - if (context != null) { - queueRef = DBReader.getQueueIDList(context); - phRef = DBReader.getPlaybackHistory(context); + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + MenuItem clearHistory = menu.add(Menu.NONE, R.id.clear_history_item, Menu.CATEGORY_CONTAINER, R.string.clear_history_label); + MenuItemCompat.setShowAsAction(clearHistory, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); + TypedArray drawables = getActivity().obtainStyledAttributes(new int[] {R.attr.content_discard}); + clearHistory.setIcon(drawables.getDrawable(0)); + drawables.recycle(); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + menu.findItem(R.id.clear_history_item).setVisible(playbackHistory != null && !playbackHistory.isEmpty()); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (!super.onOptionsItemSelected(item)) { + switch(item.getItemId()) { + case R.id.clear_history_item: + DBWriter.clearPlaybackHistory(getActivity()); + return true; + default: + return false; + } + } else { + return true; + } + } + + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + + @Override + public void update(EventDistributor eventDistributor, Integer arg) { + if ((arg & EventDistributor.PLAYBACK_HISTORY_UPDATE) != 0) { + startItemLoader(); + getActivity().supportInvalidateOptionsMenu(); + } + } + }; + + private void onFragmentLoaded() { + if (adapter == null) { + adapter = new FeedItemlistAdapter(getActivity(), itemAccess, new DefaultActionButtonCallback(activity.get()), true); + setListAdapter(adapter); + downloadObserver = new DownloadObserver(activity.get(), new Handler(), downloadObserverCallback); + downloadObserver.onResume(); + } + setListShown(true); + adapter.notifyDataSetChanged(); + if (feedItemDialog != null && feedItemDialog.isShowing()) { + feedItemDialog.updateContent(queue, playbackHistory); + } else if (feedItemDialogSavedInstance != null) { + feedItemDialog = FeedItemDialog.newInstance(activity.get(), feedItemDialogSavedInstance); + } + getActivity().supportInvalidateOptionsMenu(); + } + + private DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() { + @Override + public void onContentChanged() { + if (adapter != null) { + adapter.notifyDataSetChanged(); + } + if (feedItemDialog != null && feedItemDialog.isShowing()) { + feedItemDialog.updateMenuAppearance(); + } + } + + @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) { + return (queue != null) ? queue.contains(item.getId()) : false; + } + + @Override + public int getItemDownloadProgressPercent(FeedItem item) { + if (downloaderList != null) { + for (Downloader downloader : downloaderList) { + if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA + && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) { + return downloader.getDownloadRequest().getProgressPercent(); + } } + } + return 0; + } + + @Override + public int getCount() { + return (playbackHistory != null) ? playbackHistory.size() : 0; + } + + @Override + public FeedItem getItem(int position) { + return (playbackHistory != null) ? playbackHistory.get(position) : null; + } + }; + + 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); + } + } + + private class ItemLoader extends AsyncTask<Void, Void, Object[]> { + + @Override + protected Object[] doInBackground(Void... params) { + Context context = activity.get(); + if (context != null) { + List<FeedItem> ph = DBReader.getPlaybackHistory(context); + DBReader.loadFeedDataOfFeedItemlist(context, ph); + return new Object[]{ph, + QueueAccess.IDListAccess(DBReader.getQueueIDList(context))}; + } else { return null; } + } - @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); - if (queueRef != null && phRef != null) { - queue = queueRef; - playbackHistory = phRef; - Log.i(TAG, "Number of items in playback history: " + playbackHistory.size()); - if (fila != null) { - fila.notifyDataSetChanged(); - } - } else { - if (queueRef == null) { - Log.e(TAG, "Could not load queue"); - } - if (phRef == null) { - Log.e(TAG, "Could not load playback history"); - } + @Override + protected void onPostExecute(Object[] res) { + super.onPostExecute(res); + if (res != null) { + playbackHistory = (List<FeedItem>) res[0]; + queue = (QueueAccess) res[1]; + itemsLoaded = true; + if (viewsCreated) { + onFragmentLoaded(); } } - }; - loadTask.execute(); + } } } diff --git a/src/de/danoeh/antennapod/fragment/QueueFragment.java b/src/de/danoeh/antennapod/fragment/QueueFragment.java new file mode 100644 index 000000000..e3f0bb19d --- /dev/null +++ b/src/de/danoeh/antennapod/fragment/QueueFragment.java @@ -0,0 +1,394 @@ +package de.danoeh.antennapod.fragment; + +import android.app.Activity; +import android.content.Context; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcelable; +import android.support.v4.app.Fragment; +import android.support.v7.widget.SearchView; +import android.util.Log; +import android.view.*; +import android.widget.AdapterView; +import android.widget.ProgressBar; +import android.widget.TextView; +import com.mobeta.android.dslv.DragSortListView; +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.asynctask.DownloadObserver; +import de.danoeh.antennapod.dialog.FeedItemDialog; +import de.danoeh.antennapod.feed.EventDistributor; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.service.download.Downloader; +import de.danoeh.antennapod.storage.DBReader; +import de.danoeh.antennapod.storage.DBWriter; +import de.danoeh.antennapod.util.QueueAccess; +import de.danoeh.antennapod.util.UndoBarController; +import de.danoeh.antennapod.util.gui.FeedItemUndoToken; +import de.danoeh.antennapod.util.menuhandler.MenuItemUtils; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Shows all items in the queue + */ +public class QueueFragment extends Fragment { + private static final String TAG = "QueueFragment"; + private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | + EventDistributor.DOWNLOAD_QUEUED | + EventDistributor.QUEUE_UPDATE; + + private DragSortListView listView; + private QueueListAdapter listAdapter; + private TextView txtvEmpty; + private ProgressBar progLoading; + private UndoBarController undoBarController; + + private List<FeedItem> queue; + private List<Downloader> downloaderList; + + private boolean itemsLoaded = false; + private boolean viewsCreated = false; + + private AtomicReference<Activity> activity = new AtomicReference<Activity>(); + + private DownloadObserver downloadObserver = null; + + private FeedItemDialog feedItemDialog; + private FeedItemDialog.FeedItemDialogSavedInstance feedItemDialogSavedInstance; + + /** + * Download observer updates won't result in an upate of the list adapter if this is true. + */ + private boolean blockDownloadObserverUpdate = false; + + + @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 onStop() { + super.onStop(); + EventDistributor.getInstance().unregister(contentUpdate); + stopItemLoader(); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + this.activity.set((MainActivity) activity); + } + + private void resetViewState() { + unregisterForContextMenu(listView); + listAdapter = null; + undoBarController = null; + activity.set(null); + viewsCreated = false; + blockDownloadObserverUpdate = false; + if (downloadObserver != null) { + downloadObserver.onPause(); + } + if (feedItemDialog != null) { + feedItemDialogSavedInstance = feedItemDialog.save(); + } + feedItemDialog = null; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + resetViewState(); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + final SearchView sv = new SearchView(getActivity()); + MenuItemUtils.addSearchItem(menu, sv); + sv.setQueryHint(getString(R.string.search_hint)); + sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String s) { + sv.clearFocus(); + ((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(s)); + return true; + } + + @Override + public boolean onQueryTextChange(String s) { + return false; + } + }); + } + + @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()); + } + + menu.findItem(R.id.move_to_top_item).setEnabled(!queue.isEmpty() && queue.get(0) != item); + menu.findItem(R.id.move_to_bottom_item).setEnabled(!queue.isEmpty() && queue.get(queue.size() - 1) != item); + } + + @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); + } + + switch (item.getItemId()) { + case R.id.move_to_top_item: + DBWriter.moveQueueItemToTop(getActivity(), selectedItem.getId(), true); + return true; + case R.id.move_to_bottom_item: + DBWriter.moveQueueItemToBottom(getActivity(), selectedItem.getId(), true); + return true; + default: + return super.onContextItemSelected(item); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + ((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); + + 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) { + feedItemDialog = FeedItemDialog.newInstance(activity.get(), item, QueueAccess.ItemListAccess(queue)); + feedItemDialog.show(); + } + } + }); + + undoBarController = new UndoBarController(root.findViewById(R.id.undobar), new UndoBarController.UndoListener() { + @Override + public void onUndo(Parcelable token) { + // Perform the undo + FeedItemUndoToken undoToken = (FeedItemUndoToken) token; + if (token != null) { + long itemId = undoToken.getFeedItemId(); + int position = undoToken.getPosition(); + DBWriter.addQueueItemAt(getActivity(), itemId, position, false); + } + } + }); + + listView.setDragSortListener(new DragSortListView.DragSortListener() { + @Override + public void drag(int from, int to) { + Log.d(TAG, "drag"); + blockDownloadObserverUpdate = 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 remove(int which) { + stopItemLoader(); + FeedItem item = (FeedItem) listView.getAdapter().getItem(which); + DBWriter.removeQueueItem(getActivity(), item.getId(), true); + undoBarController.showUndoBar(false, + getString(R.string.removed_from_queue), new FeedItemUndoToken(item, + which) + ); + } + }); + + registerForContextMenu(listView); + + if (!itemsLoaded) { + progLoading.setVisibility(View.VISIBLE); + txtvEmpty.setVisibility(View.GONE); + } + + viewsCreated = true; + + if (itemsLoaded && activity.get() != null) { + onFragmentLoaded(); + } + + 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(); + } + listAdapter.notifyDataSetChanged(); + if (feedItemDialog != null) { + feedItemDialog.updateContent(QueueAccess.ItemListAccess(queue), queue); + } else if (feedItemDialogSavedInstance != null) { + feedItemDialog = FeedItemDialog.newInstance(activity.get(), feedItemDialogSavedInstance); + } + } + + private DownloadObserver.Callback downloadObserverCallback = new DownloadObserver.Callback() { + @Override + public void onContentChanged() { + if (listAdapter != null && !blockDownloadObserverUpdate) { + listAdapter.notifyDataSetChanged(); + } + if (feedItemDialog != null && feedItemDialog.isShowing()) { + feedItemDialog.updateMenuAppearance(); + } + } + + @Override + public void onDownloadDataAvailable(List<Downloader> downloaderList) { + QueueFragment.this.downloaderList = downloaderList; + if (listAdapter != null && !blockDownloadObserverUpdate) { + listAdapter.notifyDataSetChanged(); + } + } + }; + + private QueueListAdapter.ItemAccess itemAccess = new QueueListAdapter.ItemAccess() { + @Override + public int getCount() { + return (itemsLoaded) ? queue.size() : 0; + } + + @Override + public FeedItem getItem(int position) { + return (itemsLoaded) ? queue.get(position) : null; + } + + @Override + public int getItemDownloadProgressPercent(FeedItem item) { + if (downloaderList != null) { + for (Downloader downloader : downloaderList) { + if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA + && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) { + return downloader.getDownloadRequest().getProgressPercent(); + } + } + } + return 0; + } + }; + + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + @Override + public void update(EventDistributor eventDistributor, Integer arg) { + if ((arg & EVENTS) != 0) { + startItemLoader(); + } + } + }; + + 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); + } + } + + 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/src/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java b/src/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java new file mode 100644 index 000000000..89c30e34b --- /dev/null +++ b/src/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java @@ -0,0 +1,69 @@ +package de.danoeh.antennapod.fragment; + +import android.os.Bundle; +import android.os.Handler; +import android.support.v4.app.ListFragment; +import android.view.View; +import de.danoeh.antennapod.adapter.DownloadlistAdapter; +import de.danoeh.antennapod.asynctask.DownloadObserver; +import de.danoeh.antennapod.service.download.Downloader; +import de.danoeh.antennapod.storage.DownloadRequester; + +import java.util.List; + +/** + * 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; + + + @Override + public void onDetach() { + super.onDetach(); + if (downloadObserver != null) { + downloadObserver.onPause(); + } + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + final DownloadlistAdapter downloadlistAdapter = new DownloadlistAdapter(getActivity(), itemAccess); + setListAdapter(downloadlistAdapter); + + downloadObserver = new DownloadObserver(getActivity(), new Handler(), new DownloadObserver.Callback() { + @Override + public void onContentChanged() { + downloadlistAdapter.notifyDataSetChanged(); + } + + @Override + public void onDownloadDataAvailable(List<Downloader> downloaderList) { + RunningDownloadsFragment.this.downloaderList = downloaderList; + downloadlistAdapter.notifyDataSetChanged(); + } + }); + downloadObserver.onResume(); + } + + private DownloadlistAdapter.ItemAccess itemAccess = new DownloadlistAdapter.ItemAccess() { + @Override + public int getCount() { + return (downloaderList != null) ? downloaderList.size() : 0; + } + + @Override + public Downloader getItem(int position) { + return (downloaderList != null) ? downloaderList.get(position) : null; + } + + @Override + public void onSecondaryActionClick(Downloader downloader) { + DownloadRequester.getInstance().cancelDownload(getActivity(), downloader.getDownloadRequest().getSource()); + } + }; +} diff --git a/src/de/danoeh/antennapod/fragment/SearchFragment.java b/src/de/danoeh/antennapod/fragment/SearchFragment.java new file mode 100644 index 000000000..f89e44717 --- /dev/null +++ b/src/de/danoeh/antennapod/fragment/SearchFragment.java @@ -0,0 +1,254 @@ +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.widget.SearchView; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.ListView; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.adapter.SearchlistAdapter; +import de.danoeh.antennapod.dialog.FeedItemDialog; +import de.danoeh.antennapod.feed.*; +import de.danoeh.antennapod.storage.DBReader; +import de.danoeh.antennapod.storage.FeedSearcher; +import de.danoeh.antennapod.util.QueueAccess; + +import java.util.List; + +/** + * Performs a search operation on all feeds or one specific feed and displays the search result. + */ +public class SearchFragment extends ListFragment { + private static final String TAG = "SearchFragment"; + + private static final String ARG_QUERY = "query"; + private static final String ARG_FEED = "feed"; + + private SearchlistAdapter searchAdapter; + private List<SearchResult> searchResults; + + private boolean viewCreated = false; + private boolean itemsLoaded = false; + + private QueueAccess queue; + + private FeedItemDialog feedItemDialog; + private FeedItemDialog.FeedItemDialogSavedInstance feedItemDialogSavedInstance; + + /** + * Create a new SearchFragment that searches all feeds. + */ + public static SearchFragment newInstance(String query) { + if (query == null) query = ""; + SearchFragment fragment = new SearchFragment(); + Bundle args = new Bundle(); + args.putString(ARG_QUERY, query); + args.putLong(ARG_FEED, 0); + fragment.setArguments(args); + return fragment; + } + + /** + * Create a new SearchFragment that searches one specific feed. + */ + public static SearchFragment newInstance(String query, long feed) { + SearchFragment fragment = newInstance(query); + fragment.getArguments().putLong(ARG_FEED, feed); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + setHasOptionsMenu(true); + startSearchTask(); + } + + @Override + public void onStart() { + super.onStart(); + EventDistributor.getInstance().register(contentUpdate); + } + + @Override + public void onStop() { + super.onStop(); + stopSearchTask(); + EventDistributor.getInstance().unregister(contentUpdate); + } + + @Override + public void onDetach() { + super.onDetach(); + stopSearchTask(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + searchAdapter = null; + viewCreated = false; + if (feedItemDialog != null) { + feedItemDialogSavedInstance = feedItemDialog.save(); + } + feedItemDialog = null; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + ((ActionBarActivity) getActivity()).getSupportActionBar().setTitle(R.string.search_label); + viewCreated = true; + if (itemsLoaded) { + onFragmentLoaded(); + } + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + super.onListItemClick(l, v, position, id); + SearchResult result = (SearchResult) l.getAdapter().getItem(position); + FeedComponent comp = result.getComponent(); + if (comp.getClass() == Feed.class) { + ((MainActivity)getActivity()).loadFeedFragment(comp.getId()); + } else { + if (comp.getClass() == FeedItem.class) { + feedItemDialog = FeedItemDialog.newInstance(getActivity(), (FeedItem) comp, queue); + feedItemDialog.show(); + } + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + MenuItem item = menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label); + MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); + final SearchView sv = new SearchView(getActivity()); + sv.setQueryHint(getString(R.string.search_hint)); + sv.setQuery(getArguments().getString(ARG_QUERY), false); + sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String s) { + getArguments().putString(ARG_QUERY, s); + itemsLoaded = false; + startSearchTask(); + return true; + } + + @Override + public boolean onQueryTextChange(String s) { + return false; + } + }); + MenuItemCompat.setActionView(item, sv); + } + + private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + @Override + public void update(EventDistributor eventDistributor, Integer arg) { + if ((arg & (EventDistributor.DOWNLOAD_QUEUED)) != 0) { + feedItemDialog.updateMenuAppearance(); + } + if ((arg & (EventDistributor.UNREAD_ITEMS_UPDATE + | EventDistributor.DOWNLOAD_HANDLED + | EventDistributor.QUEUE_UPDATE)) != 0) { + startSearchTask(); + } + } + }; + + private void onFragmentLoaded() { + if (searchAdapter == null) { + searchAdapter = new SearchlistAdapter(getActivity(), itemAccess); + setListAdapter(searchAdapter); + } + searchAdapter.notifyDataSetChanged(); + setListShown(true); + if (feedItemDialog != null && feedItemDialog.isShowing()) { + feedItemDialog.setQueue(queue); + for (SearchResult result : searchResults) { + FeedComponent comp = result.getComponent(); + if (comp.getClass() == FeedItem.class && ((FeedItem) comp).getId() == feedItemDialog.getItem().getId()) { + feedItemDialog.setItem((FeedItem) comp); + } + } + feedItemDialog.updateMenuAppearance(); + } else if (feedItemDialogSavedInstance != null) { + feedItemDialog = FeedItemDialog.newInstance(getActivity(), feedItemDialogSavedInstance); + } + } + + private final SearchlistAdapter.ItemAccess itemAccess = new SearchlistAdapter.ItemAccess() { + @Override + public int getCount() { + return (searchResults != null) ? searchResults.size() : 0; + } + + @Override + public SearchResult getItem(int position) { + return (searchResults != null) ? searchResults.get(position) : null; + } + }; + + private SearchTask searchTask; + + private void startSearchTask() { + if (searchTask != null) { + searchTask.cancel(true); + } + searchTask = new SearchTask(); + searchTask.execute(getArguments()); + } + + private void stopSearchTask() { + if (searchTask != null) { + searchTask.cancel(true); + } + } + + private class SearchTask extends AsyncTask<Bundle, Void, Object[]> { + @Override + protected Object[] doInBackground(Bundle... params) { + String query = params[0].getString(ARG_QUERY); + long feed = params[0].getLong(ARG_FEED); + Context context = getActivity(); + if (context != null) { + return new Object[]{FeedSearcher.performSearch(context, query, feed), + QueueAccess.IDListAccess(DBReader.getQueueIDList(context))}; + } else { + return null; + } + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + if (viewCreated && !itemsLoaded) { + setListShown(false); + } + } + + @Override + protected void onPostExecute(Object[] results) { + super.onPostExecute(results); + if (results != null) { + itemsLoaded = true; + searchResults = (List<SearchResult>) results[0]; + queue = (QueueAccess) results[1]; + if (viewCreated) { + onFragmentLoaded(); + } + } + } + } +} diff --git a/src/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java b/src/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java new file mode 100644 index 000000000..ec8f69368 --- /dev/null +++ b/src/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java @@ -0,0 +1,131 @@ +package de.danoeh.antennapod.fragment.gpodnet; + +import android.app.Activity; +import android.content.res.Resources; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.ViewPager; +import android.support.v7.app.ActionBar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; + +/** + * Main navigation hub for gpodder.net podcast directory + */ +public class GpodnetMainFragment extends Fragment { + + private ViewPager pager; + private MainActivity activity; + + @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); + GpodnetPagerAdapter pagerAdapter = new GpodnetPagerAdapter(getChildFragmentManager(), getResources()); + pager.setAdapter(pagerAdapter); + final ActionBar actionBar = activity.getMainActivtyActionBar(); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + ActionBar.TabListener tabListener = new ActionBar.TabListener() { + @Override + public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + pager.setCurrentItem(tab.getPosition()); + } + + @Override + public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + + } + + @Override + public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + + } + }; + actionBar.removeAllTabs(); + actionBar.addTab(actionBar.newTab() + .setText(R.string.gpodnet_taglist_header) + .setTabListener(tabListener)); + actionBar.addTab(actionBar.newTab() + .setText(R.string.gpodnet_toplist_header) + .setTabListener(tabListener)); + actionBar.setTitle(R.string.gpodnet_main_label); + + pager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + actionBar.setSelectedNavigationItem(position); + } + }); + return root; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + activity.getMainActivtyActionBar().removeAllTabs(); + activity.getMainActivtyActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + this.activity = (MainActivity) activity; + } + + public class GpodnetPagerAdapter extends FragmentPagerAdapter { + + + private static final int NUM_PAGES = 2; + private static final int POS_TAGS = 0; + private static final int POS_TOPLIST = 1; + private static final int POS_SUGGESTIONS = 2; + + Resources resources; + + public GpodnetPagerAdapter(FragmentManager fm, Resources resources) { + super(fm); + this.resources = resources; + } + + @Override + public Fragment getItem(int i) { + switch (i) { + case POS_TAGS: + return new TagListFragment(); + case POS_TOPLIST: + return new PodcastTopListFragment(); + case POS_SUGGESTIONS: + return new SuggestionListFragment(); + default: + return null; + } + } + + @Override + public CharSequence getPageTitle(int position) { + switch (position) { + case POS_TAGS: + return getString(R.string.gpodnet_taglist_header); + case POS_TOPLIST: + return getString(R.string.gpodnet_toplist_header); + case POS_SUGGESTIONS: + return getString(R.string.gpodnet_suggestions_header); + default: + return super.getPageTitle(position); + } + } + + @Override + public int getCount() { + return NUM_PAGES; + } + } +} diff --git a/src/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java b/src/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java index 4164429b2..837df0594 100644 --- a/src/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java +++ b/src/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java @@ -5,19 +5,22 @@ import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.Fragment; +import android.support.v7.widget.*; import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; +import android.view.*; import android.widget.*; +import android.widget.SearchView; import de.danoeh.antennapod.BuildConfig; 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; +import de.danoeh.antennapod.fragment.SearchFragment; import de.danoeh.antennapod.gpoddernet.GpodnetService; import de.danoeh.antennapod.gpoddernet.GpodnetServiceException; import de.danoeh.antennapod.gpoddernet.model.GpodnetPodcast; +import de.danoeh.antennapod.util.menuhandler.MenuItemUtils; import java.util.List; @@ -33,8 +36,34 @@ public abstract class PodcastListFragment extends Fragment { private Button butRetry; @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + final android.support.v7.widget.SearchView sv = new android.support.v7.widget.SearchView(getActivity()); + MenuItemUtils.addSearchItem(menu, sv); + sv.setQueryHint(getString(R.string.gpodnet_search_hint)); + sv.setOnQueryTextListener(new android.support.v7.widget.SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String s) { + sv.clearFocus(); + ((MainActivity) getActivity()).loadChildFragment(SearchListFragment.newInstance(s)); + return true; + } + + @Override + public boolean onQueryTextChange(String s) { + return false; + } + }); + } + + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - setRetainInstance(true); View root = inflater.inflate(R.layout.gpodnet_podcast_list, container, false); gridView = (GridView) root.findViewById(R.id.gridView); diff --git a/src/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java b/src/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java index 322d13097..79d0c5d6f 100644 --- a/src/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java +++ b/src/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java @@ -1,14 +1,21 @@ package de.danoeh.antennapod.fragment.gpodnet; import android.os.Bundle; +import android.support.v7.widget.SearchView; +import android.view.Menu; +import android.view.MenuInflater; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.fragment.SearchFragment; import de.danoeh.antennapod.gpoddernet.GpodnetService; import de.danoeh.antennapod.gpoddernet.GpodnetServiceException; import de.danoeh.antennapod.gpoddernet.model.GpodnetPodcast; +import de.danoeh.antennapod.util.menuhandler.MenuItemUtils; import java.util.List; /** - * Created by daniel on 23.08.13. + * Performs a search on the gpodder.net directory and displays the results. */ public class SearchListFragment extends PodcastListFragment { private static final String ARG_QUERY = "query"; @@ -26,6 +33,7 @@ public class SearchListFragment extends PodcastListFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (getArguments() != null && getArguments().containsKey(ARG_QUERY)) { this.query = getArguments().getString(ARG_QUERY); } else { @@ -34,6 +42,27 @@ public class SearchListFragment extends PodcastListFragment { } @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + final SearchView sv = new SearchView(getActivity()); + MenuItemUtils.addSearchItem(menu, sv); + sv.setQueryHint(getString(R.string.gpodnet_search_hint)); + sv.setQuery(query, false); + sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String s) { + sv.clearFocus(); + changeQuery(s); + return true; + } + + @Override + public boolean onQueryTextChange(String s) { + return false; + } + }); + } + + @Override protected List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException { return service.searchPodcasts(query, 0); } diff --git a/src/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java b/src/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java new file mode 100644 index 000000000..f016290bf --- /dev/null +++ b/src/de/danoeh/antennapod/fragment/gpodnet/TagFragment.java @@ -0,0 +1,47 @@ +package de.danoeh.antennapod.fragment.gpodnet; + +import android.os.Bundle; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.gpoddernet.GpodnetService; +import de.danoeh.antennapod.gpoddernet.GpodnetServiceException; +import de.danoeh.antennapod.gpoddernet.model.GpodnetPodcast; +import de.danoeh.antennapod.gpoddernet.model.GpodnetTag; + +import java.util.List; + +/** + * Shows all podcasts from gpodder.net that belong to a specific tag. + * Use the newInstance method of this class to create a new TagFragment. + */ +public class TagFragment extends PodcastListFragment { + + private static final String TAG = "TagFragment"; + private static final int PODCAST_COUNT = 50; + + private GpodnetTag tag; + + public static TagFragment newInstance(String tagName) { + if (tagName == null) throw new IllegalArgumentException("tagName = null"); + TagFragment fragment = new TagFragment(); + Bundle args = new Bundle(); + args.putString("tag", tagName); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + if (args == null || args.getString("tag") == null) throw new IllegalArgumentException("args invalid"); + + tag = new GpodnetTag(args.getString("tag")); + ((MainActivity) getActivity()).getMainActivtyActionBar().setTitle(tag.getName()); + } + + @Override + protected List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException { + return service.getPodcastsForTag(tag, PODCAST_COUNT); + } +} diff --git a/src/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java b/src/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java index fcb9d01c5..880726e50 100644 --- a/src/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java +++ b/src/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java @@ -1,18 +1,23 @@ package de.danoeh.antennapod.fragment.gpodnet; import android.content.Context; -import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.ListFragment; +import android.support.v7.widget.SearchView; +import android.view.Menu; +import android.view.MenuInflater; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.TextView; -import de.danoeh.antennapod.activity.gpoddernet.GpodnetTagActivity; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.fragment.SearchFragment; import de.danoeh.antennapod.gpoddernet.GpodnetService; import de.danoeh.antennapod.gpoddernet.GpodnetServiceException; import de.danoeh.antennapod.gpoddernet.model.GpodnetTag; +import de.danoeh.antennapod.util.menuhandler.MenuItemUtils; import java.util.ArrayList; import java.util.List; @@ -22,17 +27,42 @@ public class TagListFragment extends ListFragment { private static final int COUNT = 50; @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + final SearchView sv = new SearchView(getActivity()); + MenuItemUtils.addSearchItem(menu, sv); + sv.setQueryHint(getString(R.string.gpodnet_search_hint)); + sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String s) { + sv.clearFocus(); + ((MainActivity) getActivity()).loadChildFragment(SearchListFragment.newInstance(s)); + return true; + } + + @Override + public boolean onQueryTextChange(String s) { + return false; + } + }); + } + + @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - setRetainInstance(true); getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String selectedTag = (String) getListAdapter().getItem(position); - Intent intent = new Intent(getActivity(), GpodnetTagActivity.class); - intent.putExtra(GpodnetTagActivity.ARG_TAGNAME, selectedTag); - startActivity(intent); + MainActivity activity = (MainActivity) getActivity(); + activity.loadChildFragment(TagFragment.newInstance(selectedTag)); } }); diff --git a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java deleted file mode 100644 index b4c06c340..000000000 --- a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java +++ /dev/null @@ -1,129 +0,0 @@ -package de.danoeh.antennapod.miroguide.conn; - -import android.net.Uri; -import de.danoeh.antennapod.util.LangUtils; -import org.apache.http.HttpEntity; -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.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; - -/** Executes HTTP requests and returns the results. */ -public class MiroGuideConnector { - private HttpClient httpClient; - - private static final String HOST_URL = "http://www.miroguide.com/api/"; - private static final String PATH_GET_CHANNELS = "get_channels"; - private static final String PATH_LIST_CATEGORIES = "list_categories"; - private static final String PATH_GET_CHANNEL = "get_channel"; - - public MiroGuideConnector() { - httpClient = new DefaultHttpClient(); - } - - public void shutdown() { - httpClient.getConnectionManager().shutdown(); - } - - private Uri.Builder getBaseURIBuilder(String path) { - Uri.Builder builder = Uri.parse(HOST_URL).buildUpon(); - builder.appendPath(path).appendQueryParameter("datatype", "json"); - return builder; - } - - public JSONArray getArrayResponse(Uri uri) throws MiroGuideException { - try { - JSONArray result = new JSONArray(executeRequest(uri)); - return result; - } catch (JSONException e) { - e.printStackTrace(); - throw new MiroGuideException(); - } - } - - public JSONObject getSingleObjectResponse(Uri uri) throws MiroGuideException { - try { - JSONObject result = new JSONObject(executeRequest(uri)); - return result; - } catch (JSONException e) { - e.printStackTrace(); - throw new MiroGuideException(); - } - } - - /** - * Executes a HTTP GET request with the given URI and returns the content of - * the return value. - * - * @throws MiroGuideException - */ - private String executeRequest(Uri uri) throws MiroGuideException { - HttpGet httpGet = new HttpGet(uri.toString()); - String result = null; - try { - HttpResponse response = httpClient.execute(httpGet); - if (response.getStatusLine().getStatusCode() == 200) { - HttpEntity entity = response.getEntity(); - if (entity != null) { - BufferedReader reader = new BufferedReader( - new InputStreamReader(entity.getContent(), - LangUtils.UTF_8)); - try { - result = reader.readLine(); - } finally { - reader.close(); - } - } - } else { - throw new MiroGuideException(response.getStatusLine() - .getReasonPhrase()); - } - } catch (IOException e) { - e.printStackTrace(); - throw new MiroGuideException(e.getMessage()); - } - return result; - - } - - public Uri createGetChannelsUri(String filter, String filterValue, - String sort, String limit, String offset) throws MiroGuideException { - Uri.Builder resultBuilder = getBaseURIBuilder(PATH_GET_CHANNELS); - resultBuilder.appendQueryParameter("filter", filter) - .appendQueryParameter("filter_value", filterValue); - - if (sort != null) { - resultBuilder.appendQueryParameter("sort", sort); - } - if (limit != null) { - resultBuilder.appendQueryParameter("limit", limit); - } - if (offset != null) { - resultBuilder.appendQueryParameter("offset", offset); - } - Uri result = resultBuilder.build(); - return result; - } - - public Uri createListCategoriesURI() throws MiroGuideException { - Uri.Builder resultBuilder = getBaseURIBuilder(PATH_LIST_CATEGORIES); - Uri result = resultBuilder.build(); - - return result; - } - - public Uri createGetChannelUri(String id) throws MiroGuideException { - Uri.Builder resultBuilder = getBaseURIBuilder(PATH_GET_CHANNEL) - .appendQueryParameter("id", id); - Uri result = resultBuilder.build(); - return result; - } - -} diff --git a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideException.java b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideException.java deleted file mode 100644 index 6097761d8..000000000 --- a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideException.java +++ /dev/null @@ -1,23 +0,0 @@ -package de.danoeh.antennapod.miroguide.conn; - -public class MiroGuideException extends Exception { - private static final long serialVersionUID = -8834656185748713194L; - - public MiroGuideException() { - super(); - } - - public MiroGuideException(String arg0, Throwable arg1) { - super(arg0, arg1); - } - - public MiroGuideException(String arg0) { - super(arg0); - } - - public MiroGuideException(Throwable arg0) { - super(arg0); - } - - -} diff --git a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideService.java b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideService.java deleted file mode 100644 index bdb4ec5dd..000000000 --- a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideService.java +++ /dev/null @@ -1,153 +0,0 @@ -package de.danoeh.antennapod.miroguide.conn; - -import de.danoeh.antennapod.miroguide.model.MiroGuideChannel; -import de.danoeh.antennapod.miroguide.model.MiroGuideItem; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; - - -/** Provides methods to communicate with the Miroguide API on an abstract level. */ -public class MiroGuideService { - private static final String TAG = "MiroGuideService"; - - public static final int DEFAULT_CHANNEL_LIMIT = 20; - - public static final String FILTER_CATEGORY = "category"; - public static final String FILTER_NAME = "name"; - public static final String SORT_NAME = "name"; - public static final String SORT_POPULAR = "popular"; - public static final String SORT_RATING = "rating"; - - public static final String JSON_DATE_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ss"; - - private MiroGuideConnector connector; - - private static ThreadLocal<SimpleDateFormat> jSONDateFormat = new ThreadLocal<SimpleDateFormat>() { - @Override - protected SimpleDateFormat initialValue() { - return new SimpleDateFormat(JSON_DATE_FORMAT_STRING, Locale.US); - } - - }; - - public MiroGuideService() { - connector = new MiroGuideConnector(); - } - - public void close() { - connector.shutdown(); - } - - public String[] getCategories() throws MiroGuideException { - JSONArray resultArray = connector.getArrayResponse(connector - .createListCategoriesURI()); - String[] result = new String[resultArray.length()]; - for (int i = 0; i < resultArray.length(); i++) { - try { - result[i] = resultArray.getJSONObject(i).getString("name"); - } catch (JSONException e) { - e.printStackTrace(); - throw new MiroGuideException(); - } - } - return result; - } - - /** Get a list of MiroGuideChannel objects without their items. */ - public List<MiroGuideChannel> getChannelList(String filter, String filterValue, - String sort, int limit, int offset) throws MiroGuideException { - JSONArray resultArray = connector.getArrayResponse(connector - .createGetChannelsUri(filter, filterValue, sort, - Integer.toString(limit), Integer.toString(offset))); - int resultLen = resultArray.length(); - List<MiroGuideChannel> channels = new ArrayList<MiroGuideChannel>(resultLen); - for (int i = 0; i < resultLen; i++) { - JSONObject content = null; - try { - content = resultArray.getJSONObject(i); - MiroGuideChannel channel = extractMiroChannel(content, false); - channels.add(channel); - } catch (JSONException e) { - e.printStackTrace(); - throw new MiroGuideException(); - } - } - - return channels; - } - - /** - * Get a single channel with its items. - * - * @throws MiroGuideException - */ - public MiroGuideChannel getChannel(long id) throws MiroGuideException { - JSONObject resultObject = connector.getSingleObjectResponse(connector - .createGetChannelUri(Long.toString(id))); - MiroGuideChannel result = null; - try { - result = extractMiroChannel(resultObject, true); - } catch (JSONException e) { - e.printStackTrace(); - throw new MiroGuideException(); - } - return result; - } - - /** - * Get a MiroGuideChannel object from it's JSON source. The itemlist of the - * channel can be included or excluded - * - * @throws JSONException - */ - private MiroGuideChannel extractMiroChannel(JSONObject content, boolean withItems) - throws JSONException { - long id = content.getLong("id"); - String name = content.getString("name"); - String description = content.getString("description"); - String thumbnailUrl = content.optString("thumbnail_url"); - String downloadUrl = content.getString("url"); - String websiteUrl = content.getString("website_url"); - if (!withItems) { - return new MiroGuideChannel(id, name, thumbnailUrl, downloadUrl, - websiteUrl, description); - } else { - JSONArray itemData = content.getJSONArray("item"); - int numItems = itemData.length(); - ArrayList<MiroGuideItem> items = new ArrayList<MiroGuideItem>(numItems); - for (int i = 0; i < numItems; i++) { - items.add(extractMiroItem(itemData.getJSONObject(i))); - } - - return new MiroGuideChannel(id, name, thumbnailUrl, downloadUrl, - websiteUrl, description, items); - } - } - - /** Get a MiroGuideItem from its JSON source. */ - private MiroGuideItem extractMiroItem(JSONObject content) throws JSONException { - Date date = parseMiroItemDate(content.getString("date")); - String description = content.getString("description"); - String name = content.getString("name"); - String url = content.getString("url"); - return new MiroGuideItem(name, description, date, url); - } - - private Date parseMiroItemDate(String s) { - try { - return jSONDateFormat.get().parse(s); - } catch (ParseException e) { - e.printStackTrace(); - } - return null; - } - -} diff --git a/src/de/danoeh/antennapod/miroguide/model/MiroGuideChannel.java b/src/de/danoeh/antennapod/miroguide/model/MiroGuideChannel.java deleted file mode 100644 index f5d62d2e8..000000000 --- a/src/de/danoeh/antennapod/miroguide/model/MiroGuideChannel.java +++ /dev/null @@ -1,75 +0,0 @@ -package de.danoeh.antennapod.miroguide.model; - -import java.util.ArrayList; - -public class MiroGuideChannel { - private long id; - private String name; - private String thumbnailUrl; - private String downloadUrl; - private String websiteUrl; - private String description; - private ArrayList<MiroGuideItem> items; - - public MiroGuideChannel(long id, String name, String thumbnailUrl, - String downloadUrl, String websiteUrl, String description) { - super(); - this.id = id; - this.name = name; - this.thumbnailUrl = thumbnailUrl; - this.downloadUrl = downloadUrl; - this.websiteUrl = websiteUrl; - this.description = description; - } - - public MiroGuideChannel(long id, String name, String thumbnailUrl, - String downloadUrl, String websiteUrl, String description, - ArrayList<MiroGuideItem> items) { - super(); - this.id = id; - this.name = name; - this.thumbnailUrl = thumbnailUrl; - this.downloadUrl = downloadUrl; - this.websiteUrl = websiteUrl; - this.description = description; - this.items = items; - } - - @Override - public String toString() { - return id + " " + name; - } - - public long getId() { - return id; - } - - public String getName() { - return name; - } - - public String getThumbnailUrl() { - return thumbnailUrl; - } - - public String getDownloadUrl() { - return downloadUrl; - } - - public String getWebsiteUrl() { - return websiteUrl; - } - - public String getDescription() { - return description; - } - - public ArrayList<MiroGuideItem> getItems() { - return items; - } - - public void setThumbnailUrl(String thumbnailUrl) { - this.thumbnailUrl = thumbnailUrl; - } - -} diff --git a/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java b/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java deleted file mode 100644 index cb5b15c56..000000000 --- a/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java +++ /dev/null @@ -1,40 +0,0 @@ -package de.danoeh.antennapod.miroguide.model; - -import java.util.Date; - -public class MiroGuideItem { - private String name; - private String description; - private Date date; - private String url; - - public MiroGuideItem(String name, String description, Date date, String url) { - super(); - this.name = name; - this.description = description; - this.date = (Date) date.clone(); - this.url = url; - } - - @Override - public String toString() { - return name + " " + date.toString(); - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public Date getDate() { - return (Date) date.clone(); - } - - public String getUrl() { - return url; - } - -} diff --git a/src/de/danoeh/antennapod/preferences/UserPreferences.java b/src/de/danoeh/antennapod/preferences/UserPreferences.java index 3662b646e..31250bcd9 100644 --- a/src/de/danoeh/antennapod/preferences/UserPreferences.java +++ b/src/de/danoeh/antennapod/preferences/UserPreferences.java @@ -74,6 +74,7 @@ public class UserPreferences implements private String playbackSpeed; private String[] playbackSpeedArray; private boolean pauseForFocusLoss; + private boolean isFreshInstall; private UserPreferences(Context context) { this.context = context; @@ -282,6 +283,11 @@ public class UserPreferences implements return instance.pauseForFocusLoss; } + public static boolean isFreshInstall() { + instanceAvailable(); + return instance.isFreshInstall; + } + @Override public void onSharedPreferenceChanged(SharedPreferences sp, String key) { if (BuildConfig.DEBUG) diff --git a/src/de/danoeh/antennapod/service/GpodnetSyncService.java b/src/de/danoeh/antennapod/service/GpodnetSyncService.java index 4cb0e4cc8..1d652323a 100644 --- a/src/de/danoeh/antennapod/service/GpodnetSyncService.java +++ b/src/de/danoeh/antennapod/service/GpodnetSyncService.java @@ -8,6 +8,7 @@ import android.content.Intent; import android.os.IBinder; import android.support.v4.app.NotificationCompat; import android.util.Log; +import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.feed.Feed; @@ -93,7 +94,6 @@ public class GpodnetSyncService extends Service { GpodnetUploadChangesResponse uploadChangesResponse = service.uploadChanges(GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID(), localSubscriptions, new LinkedList<String>()); if (BuildConfig.DEBUG) Log.d(TAG, "Uploading changes response: " + uploadChangesResponse); - DBWriter.updateFeedDownloadURLs(GpodnetSyncService.this, uploadChangesResponse.updatedUrls).get(); GpodnetPreferences.removeAddedFeeds(localSubscriptions); GpodnetPreferences.removeRemovedFeeds(GpodnetPreferences.getRemovedFeedsCopy()); GpodnetPreferences.setLastSyncTimestamp(uploadChangesResponse.timestamp); @@ -114,7 +114,6 @@ public class GpodnetSyncService extends Service { GpodnetPreferences.removeAddedFeeds(added); GpodnetPreferences.removeRemovedFeeds(removed); - DBWriter.updateFeedDownloadURLs(GpodnetSyncService.this, uploadChangesResponse.updatedUrls).get(); GpodnetPreferences.setLastSyncTimestamp(uploadChangesResponse.timestamp); } clearErrorNotifications(); @@ -123,10 +122,6 @@ public class GpodnetSyncService extends Service { updateErrorNotification(e); } catch (DownloadRequestException e) { e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); } } stopSelf(); diff --git a/src/de/danoeh/antennapod/service/download/AntennapodHttpClient.java b/src/de/danoeh/antennapod/service/download/AntennapodHttpClient.java index 7b3f014e8..be331ce9b 100644 --- a/src/de/danoeh/antennapod/service/download/AntennapodHttpClient.java +++ b/src/de/danoeh/antennapod/service/download/AntennapodHttpClient.java @@ -33,7 +33,7 @@ public class AntennapodHttpClient { public static final int CONNECTION_TIMEOUT = 30000; public static final int SOCKET_TIMEOUT = 30000; - public static final int MAX_CONNECTIONS = 6; + public static final int MAX_CONNECTIONS = 8; private static volatile HttpClient httpClient = null; diff --git a/src/de/danoeh/antennapod/service/download/DownloadRequest.java b/src/de/danoeh/antennapod/service/download/DownloadRequest.java index be22bbebb..7ff9bc01c 100644 --- a/src/de/danoeh/antennapod/service/download/DownloadRequest.java +++ b/src/de/danoeh/antennapod/service/download/DownloadRequest.java @@ -10,6 +10,7 @@ public class DownloadRequest implements Parcelable { private final String title; private String username; private String password; + private boolean deleteOnFailure; private final long feedfileId; private final int feedfileType; @@ -19,7 +20,7 @@ public class DownloadRequest implements Parcelable { protected int statusMsg; public DownloadRequest(String destination, String source, String title, - long feedfileId, int feedfileType, String username, String password) { + long feedfileId, int feedfileType, String username, String password, boolean deleteOnFailure) { if (destination == null) { throw new IllegalArgumentException("Destination must not be null"); } @@ -37,11 +38,12 @@ public class DownloadRequest implements Parcelable { this.feedfileType = feedfileType; this.username = username; this.password = password; + this.deleteOnFailure = deleteOnFailure; } public DownloadRequest(String destination, String source, String title, long feedfileId, int feedfileType) { - this(destination, source, title, feedfileId, feedfileType, null, null); + this(destination, source, title, feedfileId, feedfileType, null, null, true); } private DownloadRequest(Parcel in) { @@ -50,6 +52,7 @@ public class DownloadRequest implements Parcelable { title = in.readString(); feedfileId = in.readLong(); feedfileType = in.readInt(); + deleteOnFailure = (in.readByte() > 0); if (in.dataAvail() > 0) { username = in.readString(); } else { @@ -74,6 +77,7 @@ public class DownloadRequest implements Parcelable { dest.writeString(title); dest.writeLong(feedfileId); dest.writeInt(feedfileType); + dest.writeByte((deleteOnFailure) ? (byte) 1 : 0); if (username != null) { dest.writeString(username); } @@ -93,59 +97,43 @@ public class DownloadRequest implements Parcelable { }; @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result - + ((destination == null) ? 0 : destination.hashCode()); - result = prime * result + (int) (feedfileId ^ (feedfileId >>> 32)); - result = prime * result + feedfileType; - result = prime * result + progressPercent; - result = prime * result + (int) (size ^ (size >>> 32)); - result = prime * result + (int) (soFar ^ (soFar >>> 32)); - result = prime * result + ((source == null) ? 0 : source.hashCode()); - result = prime * result + statusMsg; - result = prime * result + ((title == null) ? 0 : title.hashCode()); - return result; + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DownloadRequest that = (DownloadRequest) o; + + if (deleteOnFailure != that.deleteOnFailure) return false; + if (feedfileId != that.feedfileId) return false; + if (feedfileType != that.feedfileType) return false; + if (progressPercent != that.progressPercent) return false; + if (size != that.size) return false; + if (soFar != that.soFar) return false; + if (statusMsg != that.statusMsg) return false; + if (destination != null ? !destination.equals(that.destination) : that.destination != null) return false; + if (password != null ? !password.equals(that.password) : that.password != null) return false; + if (source != null ? !source.equals(that.source) : that.source != null) return false; + if (title != null ? !title.equals(that.title) : that.title != null) return false; + if (username != null ? !username.equals(that.username) : that.username != null) return false; + + return true; } @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - DownloadRequest other = (DownloadRequest) obj; - if (destination == null) { - if (other.destination != null) - return false; - } else if (!destination.equals(other.destination)) - return false; - if (feedfileId != other.feedfileId) - return false; - if (feedfileType != other.feedfileType) - return false; - if (progressPercent != other.progressPercent) - return false; - if (size != other.size) - return false; - if (soFar != other.soFar) - return false; - if (source == null) { - if (other.source != null) - return false; - } else if (!source.equals(other.source)) - return false; - if (statusMsg != other.statusMsg) - return false; - if (title == null) { - if (other.title != null) - return false; - } else if (!title.equals(other.title)) - return false; - return true; + public int hashCode() { + int result = destination != null ? destination.hashCode() : 0; + result = 31 * result + (source != null ? source.hashCode() : 0); + result = 31 * result + (title != null ? title.hashCode() : 0); + result = 31 * result + (username != null ? username.hashCode() : 0); + result = 31 * result + (password != null ? password.hashCode() : 0); + result = 31 * result + (deleteOnFailure ? 1 : 0); + result = 31 * result + (int) (feedfileId ^ (feedfileId >>> 32)); + result = 31 * result + feedfileType; + result = 31 * result + progressPercent; + result = 31 * result + (int) (soFar ^ (soFar >>> 32)); + result = 31 * result + (int) (size ^ (size >>> 32)); + result = 31 * result + statusMsg; + return result; } public String getDestination() { @@ -215,4 +203,8 @@ public class DownloadRequest implements Parcelable { public void setPassword(String password) { this.password = password; } + + public boolean isDeleteOnFailure() { + return deleteOnFailure; + } } diff --git a/src/de/danoeh/antennapod/service/download/DownloadService.java b/src/de/danoeh/antennapod/service/download/DownloadService.java index 72e0852bb..ed2f5d532 100644 --- a/src/de/danoeh/antennapod/service/download/DownloadService.java +++ b/src/de/danoeh/antennapod/service/download/DownloadService.java @@ -13,6 +13,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.MediaMetadataRetriever; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.support.v4.app.NotificationCompat; @@ -20,26 +21,26 @@ import android.util.Log; import android.webkit.URLUtil; import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.DownloadActivity; import de.danoeh.antennapod.activity.DownloadAuthenticationActivity; -import de.danoeh.antennapod.activity.DownloadLogActivity; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.adapter.NavListAdapter; import de.danoeh.antennapod.feed.*; +import de.danoeh.antennapod.fragment.DownloadsFragment; import de.danoeh.antennapod.storage.*; import de.danoeh.antennapod.syndication.handler.FeedHandler; import de.danoeh.antennapod.syndication.handler.UnsupportedFeedtypeException; import de.danoeh.antennapod.util.ChapterUtils; import de.danoeh.antennapod.util.DownloadError; import de.danoeh.antennapod.util.InvalidFeedException; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpStatus; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; @@ -87,10 +88,12 @@ public class DownloadService extends Service { private ExecutorService syncExecutor; private CompletionService<Downloader> downloadExecutor; + private FeedSyncThread feedSyncThread; + /** * Number of threads of downloadExecutor. */ - private static final int NUM_PARALLEL_DOWNLOADS = 4; + private static final int NUM_PARALLEL_DOWNLOADS = 6; private DownloadRequester requester; @@ -160,9 +163,16 @@ public class DownloadService extends Service { if (!status.isCancelled()) { if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) { postAuthenticationNotification(downloader.getDownloadRequest()); + } else if (status.getReason() == DownloadError.ERROR_HTTP_DATA_ERROR + && Integer.valueOf(status.getReasonDetailed()) == HttpStatus.SC_REQUESTED_RANGE_NOT_SATISFIABLE) { + + Log.d(TAG, "Requested invalid range, restarting download from the beginning"); + FileUtils.deleteQuietly(new File(downloader.getDownloadRequest().getDestination())); + DownloadRequester.getInstance().download(DownloadService.this, downloader.getDownloadRequest()); } else { Log.e(TAG, "Download failed"); saveDownloadStatus(status); + handleFailedDownload(status, downloader.getDownloadRequest()); } } sendDownloadHandledIntent(); @@ -245,6 +255,9 @@ public class DownloadService extends Service { } ); downloadCompletionThread.start(); + feedSyncThread = new FeedSyncThread(); + feedSyncThread.start(); + setupNotificationBuilders(); requester = DownloadRequester.getInstance(); } @@ -268,17 +281,27 @@ public class DownloadService extends Service { downloadCompletionThread.interrupt(); syncExecutor.shutdown(); schedExecutor.shutdown(); + feedSyncThread.shutdown(); cancelNotificationUpdater(); unregisterReceiver(cancelDownloadReceiver); + + DBTasks.autodownloadUndownloadedItems(getApplicationContext()); } @SuppressLint("NewApi") private void setupNotificationBuilders() { - PendingIntent pIntent = PendingIntent.getActivity(this, 0, new Intent( - this, DownloadActivity.class), + Intent intent = new Intent(this, MainActivity.class); + intent.putExtra(MainActivity.EXTRA_NAV_TYPE, NavListAdapter.VIEW_TYPE_NAV); + intent.putExtra(MainActivity.EXTRA_NAV_INDEX, MainActivity.POS_DOWNLOADS); + Bundle args = new Bundle(); + args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_RUNNING); + intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args); + + PendingIntent pIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT ); + Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.stat_notify_sync); @@ -305,8 +328,14 @@ public class DownloadService extends Service { @SuppressLint("NewApi") private Notification updateNotifications() { String contentTitle = getString(R.string.download_notification_title); - String downloadsLeft = requester.getNumberOfDownloads() - + getString(R.string.downloads_left); + int numDownloads = requester.getNumberOfDownloads(); + String downloadsLeft; + if (numDownloads > 0) { + downloadsLeft = requester.getNumberOfDownloads() + + getString(R.string.downloads_left); + } else { + downloadsLeft = getString(R.string.downloads_processing); + } if (android.os.Build.VERSION.SDK_INT >= 16) { if (notificationBuilder != null) { @@ -346,7 +375,7 @@ public class DownloadService extends Service { if (notificationCompatBuilder != null) { notificationCompatBuilder.setContentTitle(contentTitle); notificationCompatBuilder.setContentText(downloadsLeft); - return notificationCompatBuilder.getNotification(); + return notificationCompatBuilder.build(); } } return null; @@ -490,6 +519,13 @@ public class DownloadService extends Service { if (createReport) { if (BuildConfig.DEBUG) Log.d(TAG, "Creating report"); + Intent intent = new Intent(this, MainActivity.class); + intent.putExtra(MainActivity.EXTRA_NAV_TYPE, NavListAdapter.VIEW_TYPE_NAV); + intent.putExtra(MainActivity.EXTRA_NAV_INDEX, MainActivity.POS_DOWNLOADS); + Bundle args = new Bundle(); + args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_LOG); + intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args); + // create notification object Notification notification = new NotificationCompat.Builder(this) .setTicker( @@ -507,10 +543,9 @@ public class DownloadService extends Service { R.drawable.stat_notify_sync) ) .setContentIntent( - PendingIntent.getActivity(this, 0, new Intent(this, - DownloadLogActivity.class), 0) + PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) ) - .setAutoCancel(true).getNotification(); + .setAutoCancel(true).build(); NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); nm.notify(REPORT_ID, notification); } else { @@ -586,7 +621,7 @@ public class DownloadService extends Service { private void handleCompletedFeedDownload(DownloadRequest request) { if (BuildConfig.DEBUG) Log.d(TAG, "Handling completed Feed Download"); - syncExecutor.execute(new FeedSyncThread(request)); + feedSyncThread.submitCompletedDownload(request); } @@ -608,102 +643,217 @@ public class DownloadService extends Service { syncExecutor.execute(new MediaHandlerThread(status, request)); } + private void handleFailedDownload(DownloadStatus status, DownloadRequest request) { + if (BuildConfig.DEBUG) Log.d(TAG, "Handling failed download"); + syncExecutor.execute(new FailedDownloadHandler(status, request)); + } + /** * Takes a single Feed, parses the corresponding file and refreshes * information in the manager */ - class FeedSyncThread implements Runnable { + class FeedSyncThread extends Thread { private static final String TAG = "FeedSyncThread"; - private DownloadRequest request; + private BlockingQueue<DownloadRequest> completedRequests = new LinkedBlockingDeque<DownloadRequest>(); + private CompletionService<Feed> parserService = new ExecutorCompletionService<Feed>(Executors.newSingleThreadExecutor()); + private ExecutorService dbService = Executors.newSingleThreadExecutor(); + private Future<?> dbUpdateFuture; + private volatile boolean isActive = true; + private volatile boolean isCollectingRequests = false; - private DownloadError reason; - private boolean successful; + private final long WAIT_TIMEOUT = 3000; - public FeedSyncThread(DownloadRequest request) { - if (request == null) { - throw new IllegalArgumentException("Request must not be null"); + + /** + * Waits for completed requests. Once the first request has been taken, the method will wait WAIT_TIMEOUT ms longer to + * collect more completed requests. + * + * @return Collected feeds or null if the method has been interrupted during the first waiting period. + */ + private List<Feed> collectCompletedRequests() { + List<Feed> results = new LinkedList<Feed>(); + DownloadRequester requester = DownloadRequester.getInstance(); + int tasks = 0; + + try { + DownloadRequest request = completedRequests.take(); + parserService.submit(new FeedParserTask(request)); + tasks++; + } catch (InterruptedException e) { + return null; } - this.request = request; + tasks += pollCompletedDownloads(); + + isCollectingRequests = true; + + if (requester.isDownloadingFeeds()) { + // wait for completion of more downloads + long startTime = System.currentTimeMillis(); + long currentTime = startTime; + while (requester.isDownloadingFeeds() && (currentTime - startTime) < WAIT_TIMEOUT) { + try { + if (BuildConfig.DEBUG) + Log.d(TAG, "Waiting for " + (startTime + WAIT_TIMEOUT - currentTime) + " ms"); + sleep(startTime + WAIT_TIMEOUT - currentTime); + } catch (InterruptedException e) { + if (BuildConfig.DEBUG) Log.d(TAG, "interrupted while waiting for more downloads"); + tasks += pollCompletedDownloads(); + } finally { + currentTime = System.currentTimeMillis(); + } + } + + tasks += pollCompletedDownloads(); + + } + + isCollectingRequests = false; + + for (int i = 0; i < tasks; i++) { + try { + Feed f = parserService.take().get(); + if (f != null) { + results.add(f); + } + } catch (InterruptedException e) { + e.printStackTrace(); + + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + + return results; } + private int pollCompletedDownloads() { + int tasks = 0; + for (int i = 0; i < completedRequests.size(); i++) { + parserService.submit(new FeedParserTask(completedRequests.poll())); + tasks++; + } + return tasks; + } + + @Override public void run() { + while (isActive) { + final List<Feed> feeds = collectCompletedRequests(); + + if (feeds == null) { + continue; + } + + if (BuildConfig.DEBUG) Log.d(TAG, "Bundling " + feeds.size() + " feeds"); + + for (Feed feed : feeds) { + removeDuplicateImages(feed); // duplicate images have to removed because the DownloadRequester does not accept two downloads with the same download URL yet. + } + + // Save information of feed in DB + if (dbUpdateFuture != null) { + try { + dbUpdateFuture.get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + + dbUpdateFuture = dbService.submit(new Runnable() { + @Override + public void run() { + Feed[] savedFeeds = DBTasks.updateFeed(DownloadService.this, feeds.toArray(new Feed[feeds.size()])); + + for (Feed savedFeed : savedFeeds) { + // Download Feed Image if provided and not downloaded + if (savedFeed.getImage() != null + && savedFeed.getImage().isDownloaded() == false) { + if (BuildConfig.DEBUG) + Log.d(TAG, "Feed has image; Downloading...."); + savedFeed.getImage().setOwner(savedFeed); + final Feed savedFeedRef = savedFeed; + try { + requester.downloadImage(DownloadService.this, + savedFeedRef.getImage()); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DBWriter.addDownloadStatus( + DownloadService.this, + new DownloadStatus( + savedFeedRef.getImage(), + savedFeedRef + .getImage() + .getHumanReadableIdentifier(), + DownloadError.ERROR_REQUEST_ERROR, + false, e.getMessage() + ) + ); + } + } + numberOfDownloads.decrementAndGet(); + } + + sendDownloadHandledIntent(); + + queryDownloadsAsync(); + } + }); + + } + + if (dbUpdateFuture != null) { + try { + dbUpdateFuture.get(); + } catch (InterruptedException e) { + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + + if (BuildConfig.DEBUG) Log.d(TAG, "Shutting down"); + + } + + private class FeedParserTask implements Callable<Feed> { + + private DownloadRequest request; + + private FeedParserTask(DownloadRequest request) { + this.request = request; + } + + @Override + public Feed call() throws Exception { + return parseFeed(request); + } + } + + private Feed parseFeed(DownloadRequest request) { Feed savedFeed = null; Feed feed = new Feed(request.getSource(), new Date()); feed.setFile_url(request.getDestination()); + feed.setId(request.getFeedfileId()); feed.setDownloaded(true); feed.setPreferences(new FeedPreferences(0, true, request.getUsername(), request.getPassword())); - reason = null; + DownloadError reason = null; String reasonDetailed = null; - successful = true; + boolean successful = true; FeedHandler feedHandler = new FeedHandler(); try { - feed = feedHandler.parseFeed(feed); + feed = feedHandler.parseFeed(feed).feed; if (BuildConfig.DEBUG) Log.d(TAG, feed.getTitle() + " parsed"); if (checkFeedData(feed) == false) { throw new InvalidFeedException(); } - removeDuplicateImages(feed); // duplicate images have to removed because the DownloadRequester does not accept two downloads with the same download URL yet. - - // Save information of feed in DB - savedFeed = DBTasks.updateFeed(DownloadService.this, feed); - // Download Feed Image if provided and not downloaded - if (savedFeed.getImage() != null - && savedFeed.getImage().isDownloaded() == false) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Feed has image; Downloading...."); - savedFeed.getImage().setOwner(savedFeed); - final Feed savedFeedRef = savedFeed; - try { - requester.downloadImage(DownloadService.this, - savedFeedRef.getImage()); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DBWriter.addDownloadStatus( - DownloadService.this, - new DownloadStatus( - savedFeedRef.getImage(), - savedFeedRef - .getImage() - .getHumanReadableIdentifier(), - DownloadError.ERROR_REQUEST_ERROR, - false, e.getMessage() - ) - ); - } - } - // download FeedItem images if provided and not downloaded - for (FeedItem item : savedFeed.getItems()) { - if (item.hasItemImage() && (!item.getImage().isDownloaded())) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Item has image; Downloading...."); - try { - requester.downloadImage(DownloadService.this, - item.getImage()); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DBWriter.addDownloadStatus( - DownloadService.this, - new DownloadStatus( - item.getImage(), - item - .getImage() - .getHumanReadableIdentifier(), - DownloadError.ERROR_REQUEST_ERROR, - false, e.getMessage() - ) - ); - } - } - } - - } catch (SAXException e) { successful = false; e.printStackTrace(); @@ -736,14 +886,18 @@ public class DownloadService extends Service { savedFeed = feed; } - saveDownloadStatus(new DownloadStatus(savedFeed, - savedFeed.getHumanReadableIdentifier(), reason, successful, - reasonDetailed)); - sendDownloadHandledIntent(); - numberOfDownloads.decrementAndGet(); - queryDownloadsAsync(); + + if (successful) { + return savedFeed; + } else { + saveDownloadStatus(new DownloadStatus(savedFeed, + savedFeed.getHumanReadableIdentifier(), reason, successful, + reasonDetailed)); + return null; + } } + /** * Checks if the feed was parsed correctly. */ @@ -756,8 +910,6 @@ public class DownloadService extends Service { Log.e(TAG, "Feed has invalid items"); return false; } - if (BuildConfig.DEBUG) - Log.d(TAG, "Feed appears to be valid."); return true; } @@ -815,6 +967,60 @@ public class DownloadService extends Service { } } + public void shutdown() { + isActive = false; + if (isCollectingRequests) { + interrupt(); + } + } + + public void submitCompletedDownload(DownloadRequest request) { + completedRequests.offer(request); + if (isCollectingRequests) { + interrupt(); + } + } + + } + + /** + * Handles failed downloads. + * <p/> + * If the file has been partially downloaded, this handler will set the file_url of the FeedFile to the location + * of the downloaded file. + * <p/> + * Currently, this handler only handles FeedMedia objects, because Feeds and FeedImages are deleted if the download fails. + */ + class FailedDownloadHandler implements Runnable { + + private DownloadRequest request; + private DownloadStatus status; + + FailedDownloadHandler(DownloadStatus status, DownloadRequest request) { + this.request = request; + this.status = status; + } + + @Override + public void run() { + if (request.isDeleteOnFailure()) { + if (BuildConfig.DEBUG) Log.d(TAG, "Ignoring failed download, deleteOnFailure=true"); + } else { + File dest = new File(request.getDestination()); + if (dest.exists() && request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { + Log.d(TAG, "File has been partially downloaded. Writing file url"); + FeedMedia media = DBReader.getFeedMedia(DownloadService.this, request.getFeedfileId()); + media.setFile_url(request.getDestination()); + try { + DBWriter.setFeedMedia(DownloadService.this, media).get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + } + } } /** diff --git a/src/de/danoeh/antennapod/service/download/HttpDownloader.java b/src/de/danoeh/antennapod/service/download/HttpDownloader.java index 84bafb027..7ae96dc07 100644 --- a/src/de/danoeh/antennapod/service/download/HttpDownloader.java +++ b/src/de/danoeh/antennapod/service/download/HttpDownloader.java @@ -14,10 +14,12 @@ import org.apache.commons.lang3.StringUtils; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.message.BasicHeader; import java.io.*; import java.net.HttpURLConnection; @@ -36,7 +38,9 @@ public class HttpDownloader extends Downloader { @Override protected void download() { File destination = new File(request.getDestination()); - if (destination.exists()) { + final boolean fileExists = destination.exists(); + + if (request.isDeleteOnFailure() && fileExists) { Log.w(TAG, "File already exists"); if (request.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE) { onFail(DownloadError.ERROR_FILE_EXISTS, null); @@ -48,10 +52,12 @@ public class HttpDownloader extends Downloader { } HttpClient httpClient = AntennapodHttpClient.getHttpClient(); - BufferedOutputStream out = null; + RandomAccessFile out = null; InputStream connection = null; try { HttpGet httpGet = new HttpGet(URIUtil.getURIFromRequestUrl(request.getSource())); + + // add authentication information String userInfo = httpGet.getURI().getUserInfo(); if (userInfo != null) { String[] parts = userInfo.split(":"); @@ -64,6 +70,15 @@ public class HttpDownloader extends Downloader { httpGet.addHeader(BasicScheme.authenticate(new UsernamePasswordCredentials(request.getUsername(), request.getPassword()), "UTF-8", false)); } + + // add range header if necessary + if (fileExists) { + request.setSoFar(destination.length()); + httpGet.addHeader(new BasicHeader("Range", + "bytes=" + request.getSoFar() + "-")); + if (BuildConfig.DEBUG) Log.d(TAG, "Adding range header: " + request.getSoFar()); + } + HttpResponse response = httpClient.execute(httpGet); HttpEntity httpEntity = response.getEntity(); int responseCode = response.getStatusLine().getStatusCode(); @@ -75,7 +90,7 @@ public class HttpDownloader extends Downloader { if (BuildConfig.DEBUG) Log.d(TAG, "Response code is " + responseCode); - if (responseCode != HttpURLConnection.HTTP_OK || httpEntity == null) { + if (responseCode / 100 != 2 || httpEntity == null) { final DownloadError error; final String details; if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { @@ -96,14 +111,31 @@ public class HttpDownloader extends Downloader { connection = new BufferedInputStream(AndroidHttpClient .getUngzippedContent(httpEntity)); - out = new BufferedOutputStream(new FileOutputStream( - destination)); + + Header[] contentRangeHeaders = (fileExists) ? response.getHeaders("Content-Range") : null; + + if (fileExists && responseCode == HttpStatus.SC_PARTIAL_CONTENT + && contentRangeHeaders != null && contentRangeHeaders.length > 0) { + String start = contentRangeHeaders[0].getValue().substring("bytes ".length(), + contentRangeHeaders[0].getValue().indexOf("-")); + request.setSoFar(Long.valueOf(start)); + Log.d(TAG, "Starting download at position " + request.getSoFar()); + + out = new RandomAccessFile(destination, "rw"); + out.seek(request.getSoFar()); + } else { + destination.delete(); + destination.createNewFile(); + out = new RandomAccessFile(destination, "rw"); + } + + byte[] buffer = new byte[BUFFER_SIZE]; int count = 0; request.setStatusMsg(R.string.download_running); if (BuildConfig.DEBUG) Log.d(TAG, "Getting size of download"); - request.setSize(httpEntity.getContentLength()); + request.setSize(httpEntity.getContentLength() + request.getSoFar()); if (BuildConfig.DEBUG) Log.d(TAG, "Size is " + request.getSize()); if (request.getSize() < 0) { @@ -133,7 +165,6 @@ public class HttpDownloader extends Downloader { if (cancelled) { onCancelled(); } else { - out.flush(); // check if size specified in the response header is the same as the size of the // written file. This check cannot be made if compression was used if (!isGzip && request.getSize() != DownloadStatus.SIZE_UNKNOWN && @@ -182,7 +213,9 @@ public class HttpDownloader extends Downloader { Log.d(TAG, "Download failed"); } result.setFailed(reason, reasonDetailed); - cleanup(); + if (request.isDeleteOnFailure()) { + cleanup(); + } } private void onCancelled() { diff --git a/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java b/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java index 82759a902..24ff9b3fa 100644 --- a/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java +++ b/src/de/danoeh/antennapod/service/playback/PlaybackServiceMediaPlayer.java @@ -509,19 +509,13 @@ public class PlaybackServiceMediaPlayer { } /** - * Returns true if the playback speed can be adjusted. This method can also return false if the PSMP object's - * internal MediaPlayer cannot be accessed at the moment. + * Returns true if the playback speed can be adjusted. */ public boolean canSetSpeed() { - if (!playerLock.tryLock()) { - return false; - } boolean retVal = false; if (mediaPlayer != null && media != null && media.getMediaType() == MediaType.AUDIO) { retVal = (mediaPlayer).canSetSpeed(); } - - playerLock.unlock(); return retVal; } diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index 8d4785bd4..4aeca7cd6 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -504,6 +504,32 @@ public final class DBReader { return itemIds; } + + /** + * Loads a list of FeedItems sorted by pubDate in descending order. + * + * @param context A context that is used for opening a database connection. + * @param limit The maximum number of episodes that should be loaded. + * */ + public static List<FeedItem> getRecentlyPublishedEpisodes(Context context, int limit) { + if (BuildConfig.DEBUG) + Log.d(TAG, "Extracting recently published items list"); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + Cursor itemlistCursor = adapter.getRecentlyPublishedItemsCursor(limit); + List<FeedItem> items = extractItemlistFromCursor(adapter, + itemlistCursor); + itemlistCursor.close(); + + loadFeedDataOfFeedItemlist(context, items); + + adapter.close(); + + return items; + } + /** * Loads the playback history from the database. A FeedItem is in the playback history if playback of the correpsonding episode * has been completed at least once. @@ -619,12 +645,18 @@ public final class DBReader { * database and the items-attribute will be set correctly. */ public static Feed getFeed(final Context context, final long feedId) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Feed result = getFeed(context, feedId, adapter); + adapter.close(); + return result; + } + + static Feed getFeed(final Context context, final long feedId, PodDBAdapter adapter) { if (BuildConfig.DEBUG) Log.d(TAG, "Loading feed with id " + feedId); Feed feed = null; - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); Cursor feedCursor = adapter.getFeedCursor(feedId); if (feedCursor.moveToFirst()) { feed = extractFeedFromCursorRow(adapter, feedCursor); @@ -633,7 +665,6 @@ public final class DBReader { Log.e(TAG, "getFeed could not find feed with id " + feedId); } feedCursor.close(); - adapter.close(); return feed; } diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java index 92efeea62..c6a34aeea 100644 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -228,7 +228,9 @@ public final class DBTasks { new DownloadStatus(feed, feed .getHumanReadableIdentifier(), DownloadError.ERROR_REQUEST_ERROR, false, e - .getMessage())); + .getMessage() + ) + ); } } @@ -249,7 +251,7 @@ public final class DBTasks { f = new Feed(feed.getDownload_url(), new Date(), feed.getTitle(), feed.getPreferences().getUsername(), feed.getPreferences().getPassword()); } - + f.setId(feed.getId()); DownloadRequester.getInstance().downloadFeed(context, f); } @@ -347,7 +349,9 @@ public final class DBTasks { .getMedia() .getHumanReadableIdentifier(), DownloadError.ERROR_REQUEST_ERROR, - false, e.getMessage())); + false, e.getMessage() + ) + ); } } else { requester.downloadMedia(context, item.getMedia()); @@ -449,7 +453,8 @@ public final class DBTasks { try { downloadFeedItems(false, context, itemsToDownload.toArray(new FeedItem[itemsToDownload - .size()])); + .size()]) + ); } catch (DownloadRequestException e) { e.printStackTrace(); } @@ -595,12 +600,17 @@ public final class DBTasks { return QueueAccess.IDListAccess(queue).contains(feedItemId); } - private static Feed searchFeedByIdentifyingValue(Context context, - String identifier) { - List<Feed> feeds = DBReader.getFeedList(context); - for (Feed feed : feeds) { - if (feed.getIdentifyingValue().equals(identifier)) { - return feed; + private static Feed searchFeedByIdentifyingValueOrID(Context context, PodDBAdapter adapter, + Feed feed) { + if (feed.getId() != 0) { + return DBReader.getFeed(context, feed.getId(), adapter); + } else { + List<Feed> feeds = DBReader.getFeedList(context); + for (Feed f : feeds) { + if (f.getIdentifyingValue().equals(feed.getIdentifyingValue())) { + f.setItems(DBReader.getFeedItemList(context, f)); + return f; + } } } return null; @@ -620,79 +630,97 @@ public final class DBTasks { } /** - * Adds a new Feed to the database or updates the old version if it already exists. If another Feed with the same + * Adds new Feeds to the database or updates the old versions if they already exists. If another Feed with the same * identifying value already exists, this method will add new FeedItems from the new Feed to the existing Feed. * These FeedItems will be marked as unread. + * <p/> + * This method can update multiple feeds at once. Submitting a feed twice in the same method call can result in undefined behavior. + * <p/> * This method should NOT be executed on the GUI thread. * - * @param context Used for accessing the DB. - * @param newFeed The new Feed object. - * @return The updated Feed from the database if it already existed, or the new Feed from the parameters otherwise. + * @param context Used for accessing the DB. + * @param newFeeds The new Feed objects. + * @return The updated Feeds from the database if it already existed, or the new Feed from the parameters otherwise. */ - public static synchronized Feed updateFeed(final Context context, - final Feed newFeed) { - // Look up feed in the feedslist - final Feed savedFeed = searchFeedByIdentifyingValue(context, - newFeed.getIdentifyingValue()); - if (savedFeed == null) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Found no existing Feed with title " - + newFeed.getTitle() + ". Adding as new one."); - // Add a new Feed - try { - DBWriter.addNewFeed(context, newFeed).get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - return newFeed; - } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Feed with title " + newFeed.getTitle() - + " already exists. Syncing new with existing one."); + public static synchronized Feed[] updateFeed(final Context context, + final Feed... newFeeds) { + List<Feed> newFeedsList = new ArrayList<Feed>(); + List<Feed> updatedFeedsList = new ArrayList<Feed>(); + Feed[] resultFeeds = new Feed[newFeeds.length]; + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); - Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator()); - savedFeed.setItems(DBReader.getFeedItemList(context, savedFeed)); - if (savedFeed.compareWithOther(newFeed)) { + for (int feedIdx = 0; feedIdx < newFeeds.length; feedIdx++) { + + final Feed newFeed = newFeeds[feedIdx]; + + // Look up feed in the feedslist + final Feed savedFeed = searchFeedByIdentifyingValueOrID(context, adapter, + newFeed); + if (savedFeed == null) { if (BuildConfig.DEBUG) Log.d(TAG, - "Feed has updated attribute values. Updating old feed's attributes"); - savedFeed.updateFromOther(newFeed); - } - if (savedFeed.getPreferences().compareWithOther(newFeed.getPreferences())) { + "Found no existing Feed with title " + + newFeed.getTitle() + ". Adding as new one." + ); + // Add a new Feed + newFeedsList.add(newFeed); + resultFeeds[feedIdx] = newFeed; + } else { if (BuildConfig.DEBUG) - Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences"); - savedFeed.getPreferences().updateFromOther(newFeed.getPreferences()); - } - // Look for new or updated Items - for (int idx = 0; idx < newFeed.getItems().size(); idx++) { - final FeedItem item = newFeed.getItems().get(idx); - FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed, - item.getIdentifyingValue()); - if (oldItem == null) { - // item is new - final int i = idx; - item.setFeed(savedFeed); - savedFeed.getItems().add(i, item); - item.setRead(false); - } else { - oldItem.updateFromOther(item); + Log.d(TAG, "Feed with title " + newFeed.getTitle() + + " already exists. Syncing new with existing one."); + + Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator()); + if (savedFeed.compareWithOther(newFeed)) { + if (BuildConfig.DEBUG) + Log.d(TAG, + "Feed has updated attribute values. Updating old feed's attributes"); + savedFeed.updateFromOther(newFeed); } + if (savedFeed.getPreferences().compareWithOther(newFeed.getPreferences())) { + if (BuildConfig.DEBUG) + Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences"); + savedFeed.getPreferences().updateFromOther(newFeed.getPreferences()); + } + // Look for new or updated Items + for (int idx = 0; idx < newFeed.getItems().size(); idx++) { + final FeedItem item = newFeed.getItems().get(idx); + FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed, + item.getIdentifyingValue()); + if (oldItem == null) { + // item is new + final int i = idx; + item.setFeed(savedFeed); + savedFeed.getItems().add(i, item); + item.setRead(false); + } else { + oldItem.updateFromOther(item); + } + } + // update attributes + savedFeed.setLastUpdate(newFeed.getLastUpdate()); + savedFeed.setType(newFeed.getType()); + + updatedFeedsList.add(savedFeed); + resultFeeds[feedIdx] = savedFeed; } - // update attributes - savedFeed.setLastUpdate(newFeed.getLastUpdate()); - savedFeed.setType(newFeed.getType()); - try { - DBWriter.setCompleteFeed(context, savedFeed).get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - return savedFeed; } + + adapter.close(); + + try { + DBWriter.addNewFeed(context, newFeedsList.toArray(new Feed[newFeedsList.size()])).get(); + DBWriter.setCompleteFeed(context, updatedFeedsList.toArray(new Feed[updatedFeedsList.size()])).get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + + EventDistributor.getInstance().sendFeedUpdateBroadcast(); + + return resultFeeds; } /** diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index c1ce9da36..ffdfc65fd 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.storage; +import android.app.backup.BackupManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -197,6 +198,9 @@ public class DBWriter { GpodnetPreferences.addRemovedFeed(feed.getDownload_url()); EventDistributor.getInstance().sendFeedUpdateBroadcast(); + + BackupManager backupManager = new BackupManager(context); + backupManager.dataChanged(); } } }); @@ -683,33 +687,36 @@ public class DBWriter { } - static Future<?> addNewFeed(final Context context, final Feed feed) { + static Future<?> addNewFeed(final Context context, final Feed... feeds) { return dbExec.submit(new Runnable() { @Override public void run() { final PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); - adapter.setCompleteFeed(feed); + adapter.setCompleteFeed(feeds); adapter.close(); - GpodnetPreferences.addAddedFeed(feed.getDownload_url()); - EventDistributor.getInstance().sendFeedUpdateBroadcast(); + for (Feed feed : feeds) { + GpodnetPreferences.addAddedFeed(feed.getDownload_url()); + } + + BackupManager backupManager = new BackupManager(context); + backupManager.dataChanged(); } }); } - static Future<?> setCompleteFeed(final Context context, final Feed feed) { + static Future<?> setCompleteFeed(final Context context, final Feed... feeds) { return dbExec.submit(new Runnable() { @Override public void run() { PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); - adapter.setCompleteFeed(feed); + adapter.setCompleteFeed(feeds); adapter.close(); - EventDistributor.getInstance().sendFeedUpdateBroadcast(); } }); @@ -830,6 +837,7 @@ public class DBWriter { adapter.open(); adapter.setFeedPreferences(preferences); adapter.close(); + EventDistributor.getInstance().sendFeedUpdateBroadcast(); } }); } diff --git a/src/de/danoeh/antennapod/storage/DownloadRequester.java b/src/de/danoeh/antennapod/storage/DownloadRequester.java index 0a1747253..7bf21352a 100644 --- a/src/de/danoeh/antennapod/storage/DownloadRequester.java +++ b/src/de/danoeh/antennapod/storage/DownloadRequester.java @@ -12,6 +12,7 @@ import de.danoeh.antennapod.service.download.DownloadService; import de.danoeh.antennapod.util.FileNameGenerator; import de.danoeh.antennapod.util.URLChecker; import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import java.io.File; @@ -72,9 +73,9 @@ public class DownloadRequester { } private void download(Context context, FeedFile item, File dest, - boolean overwriteIfExists, String username, String password) { + boolean overwriteIfExists, String username, String password, boolean deleteOnFailure) { if (!isDownloadingFile(item)) { - if (!isFilenameAvailable(dest.toString()) || dest.exists()) { + if (!isFilenameAvailable(dest.toString()) || (deleteOnFailure && dest.exists())) { if (BuildConfig.DEBUG) Log.d(TAG, "Filename already used."); if (isFilenameAvailable(dest.toString()) && overwriteIfExists) { @@ -113,8 +114,8 @@ public class DownloadRequester { item.setDownload_url(URLChecker.prepareURL(item.getDownload_url())); DownloadRequest request = new DownloadRequest(dest.toString(), - item.getDownload_url(), item.getHumanReadableIdentifier(), - item.getId(), item.getTypeAsInt(), username, password); + URLChecker.prepareURL(item.getDownload_url()), item.getHumanReadableIdentifier(), + item.getId(), item.getTypeAsInt(), username, password, deleteOnFailure); download(context, request); } else { @@ -149,7 +150,7 @@ public class DownloadRequester { String password = (feed.getPreferences() != null) ? feed.getPreferences().getPassword() : null; download(context, feed, new File(getFeedfilePath(context), - getFeedfileName(feed)), true, username, password); + getFeedfileName(feed)), true, username, password, true); } } @@ -157,16 +158,33 @@ public class DownloadRequester { throws DownloadRequestException { if (feedFileValid(image)) { download(context, image, new File(getImagefilePath(context), - getImagefileName(image)), false, null, null); + getImagefileName(image)), false, null, null, false); } } public void downloadMedia(Context context, FeedMedia feedmedia) throws DownloadRequestException { if (feedFileValid(feedmedia)) { + Feed feed = feedmedia.getItem().getFeed(); + String username; + String password; + if (feed != null && feed.getPreferences() != null) { + username = feed.getPreferences().getUsername(); + password = feed.getPreferences().getPassword(); + } else { + username = null; + password = null; + } + + File dest; + if (feedmedia.getFile_url() != null) { + dest = new File(feedmedia.getFile_url()); + } else { + dest = new File(getMediafilePath(context, feedmedia), + getMediafilename(feedmedia)); + } download(context, feedmedia, - new File(getMediafilePath(context, feedmedia), - getMediafilename(feedmedia)), false, null, null + dest, false, username, password, false ); } } diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 825b5ac30..285709537 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.storage; +import android.app.backup.BackupManager; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -386,6 +387,7 @@ public class PodDBAdapter { Log.d(this.toString(), "Updating existing Feed in db"); db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feed.getId())}); + } return feed.getId(); } @@ -495,16 +497,18 @@ public class PodDBAdapter { * Insert all FeedItems of a feed and the feed object itself in a single * transaction */ - public void setCompleteFeed(Feed feed) { + public void setCompleteFeed(Feed... feeds) { db.beginTransaction(); - setFeed(feed); - if (feed.getItems() != null) { - for (FeedItem item : feed.getItems()) { - setFeedItem(item, false); + for (Feed feed : feeds) { + setFeed(feed); + if (feed.getItems() != null) { + for (FeedItem item : feed.getItems()) { + setFeedItem(item, false); + } + } + if (feed.getPreferences() != null) { + setFeedPreferences(feed.getPreferences()); } - } - if (feed.getPreferences() != null) { - setFeedPreferences(feed.getPreferences()); } db.setTransactionSuccessful(); db.endTransaction(); @@ -844,6 +848,7 @@ public class PodDBAdapter { removeFeedItem(item); } } + db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?", new String[]{String.valueOf(feed.getId())}); db.setTransactionSuccessful(); @@ -996,6 +1001,11 @@ public class PodDBAdapter { } + public final Cursor getRecentlyPublishedItemsCursor(int limit) { + Cursor c = db.query(TABLE_NAME_FEED_ITEMS, FEEDITEM_SEL_FI_SMALL, null, null, null, null, KEY_PUBDATE + " DESC LIMIT " + limit); + return c; + } + public Cursor getDownloadedItemsCursor() { final String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON " diff --git a/src/de/danoeh/antennapod/syndication/handler/FeedHandler.java b/src/de/danoeh/antennapod/syndication/handler/FeedHandler.java index 5576603b6..aafa1c209 100644 --- a/src/de/danoeh/antennapod/syndication/handler/FeedHandler.java +++ b/src/de/danoeh/antennapod/syndication/handler/FeedHandler.java @@ -14,7 +14,7 @@ import java.io.Reader; public class FeedHandler { - public Feed parseFeed(Feed feed) throws SAXException, IOException, + public FeedHandlerResult parseFeed(Feed feed) throws SAXException, IOException, ParserConfigurationException, UnsupportedFeedtypeException { TypeGetter tg = new TypeGetter(); TypeGetter.Type type = tg.getType(feed); @@ -29,6 +29,6 @@ public class FeedHandler { saxParser.parse(inputSource, handler); inputStreamReader.close(); - return handler.state.feed; + return new FeedHandlerResult(handler.state.feed, handler.state.alternateUrls); } } diff --git a/src/de/danoeh/antennapod/syndication/handler/FeedHandlerResult.java b/src/de/danoeh/antennapod/syndication/handler/FeedHandlerResult.java new file mode 100644 index 000000000..41aa29b52 --- /dev/null +++ b/src/de/danoeh/antennapod/syndication/handler/FeedHandlerResult.java @@ -0,0 +1,19 @@ +package de.danoeh.antennapod.syndication.handler; + +import de.danoeh.antennapod.feed.Feed; + +import java.util.Map; + +/** + * Container for results returned by the Feed parser + */ +public class FeedHandlerResult { + + public Feed feed; + public Map<String, String> alternateFeedUrls; + + public FeedHandlerResult(Feed feed, Map<String, String> alternateFeedUrls) { + this.feed = feed; + this.alternateFeedUrls = alternateFeedUrls; + } +} diff --git a/src/de/danoeh/antennapod/syndication/handler/HandlerState.java b/src/de/danoeh/antennapod/syndication/handler/HandlerState.java index a9a8bb934..17f84724f 100644 --- a/src/de/danoeh/antennapod/syndication/handler/HandlerState.java +++ b/src/de/danoeh/antennapod/syndication/handler/HandlerState.java @@ -5,9 +5,7 @@ import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.syndication.namespace.Namespace; import de.danoeh.antennapod.syndication.namespace.SyndElement; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Stack; +import java.util.*; /** * Contains all relevant information to describe the current state of a @@ -15,70 +13,86 @@ import java.util.Stack; */ public class HandlerState { - /** Feed that the Handler is currently processing. */ - protected Feed feed; - protected ArrayList<FeedItem> items; - protected FeedItem currentItem; - protected Stack<SyndElement> tagstack; - /** Namespaces that have been defined so far. */ - protected HashMap<String, Namespace> namespaces; - protected Stack<Namespace> defaultNamespaces; - /** Buffer for saving characters. */ - protected StringBuffer contentBuf; - - public HandlerState(Feed feed) { - this.feed = feed; - items = new ArrayList<FeedItem>(); - tagstack = new Stack<SyndElement>(); - namespaces = new HashMap<String, Namespace>(); - defaultNamespaces = new Stack<Namespace>(); - } - - public Feed getFeed() { - return feed; - } - - public ArrayList<FeedItem> getItems() { - return items; - } - - public FeedItem getCurrentItem() { - return currentItem; - } - - public Stack<SyndElement> getTagstack() { - return tagstack; - } - - public void setFeed(Feed feed) { - this.feed = feed; - } - - public void setCurrentItem(FeedItem currentItem) { - this.currentItem = currentItem; - } - - /** - * Returns the SyndElement that comes after the top element of the tagstack. - */ - public SyndElement getSecondTag() { - SyndElement top = tagstack.pop(); - SyndElement second = tagstack.peek(); - tagstack.push(top); - return second; - } - - public SyndElement getThirdTag() { - SyndElement top = tagstack.pop(); - SyndElement second = tagstack.pop(); - SyndElement third = tagstack.peek(); - tagstack.push(second); - tagstack.push(top); - return third; - } - - public StringBuffer getContentBuf() { - return contentBuf; - } + /** + * Feed that the Handler is currently processing. + */ + protected Feed feed; + /** + * Contains links to related feeds, e.g. feeds with enclosures in other formats. The key of the map is the + * URL of the feed, the value is the title + */ + protected Map<String, String> alternateUrls; + protected ArrayList<FeedItem> items; + protected FeedItem currentItem; + protected Stack<SyndElement> tagstack; + /** + * Namespaces that have been defined so far. + */ + protected HashMap<String, Namespace> namespaces; + protected Stack<Namespace> defaultNamespaces; + /** + * Buffer for saving characters. + */ + protected StringBuffer contentBuf; + + public HandlerState(Feed feed) { + this.feed = feed; + alternateUrls = new LinkedHashMap<String, String>(); + items = new ArrayList<FeedItem>(); + tagstack = new Stack<SyndElement>(); + namespaces = new HashMap<String, Namespace>(); + defaultNamespaces = new Stack<Namespace>(); + } + + public Feed getFeed() { + return feed; + } + + public ArrayList<FeedItem> getItems() { + return items; + } + + public FeedItem getCurrentItem() { + return currentItem; + } + + public Stack<SyndElement> getTagstack() { + return tagstack; + } + + public void setFeed(Feed feed) { + this.feed = feed; + } + + public void setCurrentItem(FeedItem currentItem) { + this.currentItem = currentItem; + } + + /** + * Returns the SyndElement that comes after the top element of the tagstack. + */ + public SyndElement getSecondTag() { + SyndElement top = tagstack.pop(); + SyndElement second = tagstack.peek(); + tagstack.push(top); + return second; + } + + public SyndElement getThirdTag() { + SyndElement top = tagstack.pop(); + SyndElement second = tagstack.pop(); + SyndElement third = tagstack.peek(); + tagstack.push(second); + tagstack.push(top); + return third; + } + + public StringBuffer getContentBuf() { + return contentBuf; + } + + public void addAlternateFeedUrl(String title, String url) { + alternateUrls.put(url, title); + } } diff --git a/src/de/danoeh/antennapod/syndication/handler/TypeGetter.java b/src/de/danoeh/antennapod/syndication/handler/TypeGetter.java index 0ac1b7ae2..5ed9ff2b0 100644 --- a/src/de/danoeh/antennapod/syndication/handler/TypeGetter.java +++ b/src/de/danoeh/antennapod/syndication/handler/TypeGetter.java @@ -17,7 +17,7 @@ import java.io.Reader; public class TypeGetter { private static final String TAG = "TypeGetter"; - enum Type { + public enum Type { RSS20, RSS091, ATOM, INVALID } diff --git a/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java b/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java index 383b29fc8..2c8e232ff 100644 --- a/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java +++ b/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java @@ -92,7 +92,8 @@ public class NSAtom extends Namespace { .getValidMimeTypeFromUrl(href)) != null) { state.getCurrentItem().setMedia( new FeedMedia(state.getCurrentItem(), href, - size, type)); + size, type) + ); } } else if (rel.equals(LINK_REL_PAYMENT)) { state.getCurrentItem().setPaymentLink(href); @@ -101,13 +102,20 @@ public class NSAtom extends Namespace { if (rel == null || rel.equals(LINK_REL_ALTERNATE)) { String type = attributes.getValue(LINK_TYPE); /* - * Use as link if a) no type-attribute is given and + * Use as link if a) no type-attribute is given and * feed-object has no link yet b) type of link is * LINK_TYPE_HTML or LINK_TYPE_XHTML */ if ((type == null && state.getFeed().getLink() == null) || (type != null && (type.equals(LINK_TYPE_HTML) || type.equals(LINK_TYPE_XHTML)))) { state.getFeed().setLink(href); + } else if (type != null && (type.equals(LINK_TYPE_ATOM) || type.equals(LINK_TYPE_RSS))) { + // treat as podlove alternate feed + String title = attributes.getValue(LINK_TITLE); + if (title == null) { + title = href; + } + state.addAlternateFeedUrl(title, href); } } else if (rel.equals(LINK_REL_PAYMENT)) { state.getFeed().setPaymentLink(href); diff --git a/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java b/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java index 2c1cff914..3138f087a 100644 --- a/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java +++ b/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java @@ -79,7 +79,7 @@ public class SyndDateUtils { second = date.substring(date.indexOf("-")); } } - + date = first + second; } if (isLocal) { @@ -131,4 +131,23 @@ public class SyndDateUtils { result += (Float.valueOf(parts[idx])) * 1000L; return result; } + + public static String formatRFC822Date(Date date) { + SimpleDateFormat format = RFC822Formatter.get(); + return format.format(date); + } + + public static String formatRFC3339Local(Date date) { + SimpleDateFormat format = RFC3339Formatter.get(); + format.applyPattern(RFC3339LOCAL); + String result = format.format(date); + format.applyPattern(RFC3339UTC); + return result; + } + + public static String formatRFC3339UTC(Date date) { + SimpleDateFormat format = RFC3339Formatter.get(); + format.applyPattern(RFC3339UTC); + return format.format(date); + } } diff --git a/src/de/danoeh/antennapod/util/URLChecker.java b/src/de/danoeh/antennapod/util/URLChecker.java index a3c675899..eb522ffa8 100644 --- a/src/de/danoeh/antennapod/util/URLChecker.java +++ b/src/de/danoeh/antennapod/util/URLChecker.java @@ -3,25 +3,36 @@ package de.danoeh.antennapod.util; import android.util.Log; import de.danoeh.antennapod.BuildConfig; -/** Provides methods for checking and editing a URL.*/ +/** + * Provides methods for checking and editing a URL. + */ public final class URLChecker { - /**Class shall not be instantiated.*/ + /** + * Class shall not be instantiated. + */ private URLChecker() { } - /**Logging tag.*/ + /** + * Logging tag. + */ private static final String TAG = "URLChecker"; - /** Checks if URL is valid and modifies it if necessary. - * @param url The url which is going to be prepared - * @return The prepared url - * */ + /** + * Checks if URL is valid and modifies it if necessary. + * + * @param url The url which is going to be prepared + * @return The prepared url + */ public static String prepareURL(String url) { StringBuilder builder = new StringBuilder(); if (url.startsWith("feed://")) { if (BuildConfig.DEBUG) Log.d(TAG, "Replacing feed:// with http://"); - url = url.replace("feed://", "http://"); + url = url.replaceFirst("feed://", "http://"); + } else if (url.startsWith("pcast://")) { + if (BuildConfig.DEBUG) Log.d(TAG, "Replacing pcast:// with http://"); + url = url.replaceFirst("pcast://", "http://"); } else if (!(url.startsWith("http://") || url.startsWith("https://"))) { if (BuildConfig.DEBUG) Log.d(TAG, "Adding http:// at the beginning of the URL"); builder.append("http://"); diff --git a/src/de/danoeh/antennapod/util/gui/FeedItemUndoToken.java b/src/de/danoeh/antennapod/util/gui/FeedItemUndoToken.java new file mode 100644 index 000000000..b920559db --- /dev/null +++ b/src/de/danoeh/antennapod/util/gui/FeedItemUndoToken.java @@ -0,0 +1,55 @@ +package de.danoeh.antennapod.util.gui; + +import android.os.Parcel; +import android.os.Parcelable; +import de.danoeh.antennapod.feed.FeedItem; + +/** + * Used by an UndoBarController for saving a removed FeedItem + */ +public class FeedItemUndoToken implements Parcelable { + private long itemId; + private long feedId; + private int position; + + public FeedItemUndoToken(FeedItem item, int position) { + this.itemId = item.getId(); + this.feedId = item.getFeed().getId(); + this.position = position; + } + + private FeedItemUndoToken(Parcel in) { + itemId = in.readLong(); + feedId = in.readLong(); + position = in.readInt(); + } + + public static final Parcelable.Creator<FeedItemUndoToken> CREATOR = new Parcelable.Creator<FeedItemUndoToken>() { + public FeedItemUndoToken createFromParcel(Parcel in) { + return new FeedItemUndoToken(in); + } + + public FeedItemUndoToken[] newArray(int size) { + return new FeedItemUndoToken[size]; + } + }; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeLong(itemId); + out.writeLong(feedId); + out.writeInt(position); + } + + public long getFeedItemId() { + return itemId; + } + + public int getPosition() { + return position; + } +} + diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java index ae8b3ac1e..a3adec66d 100644 --- a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java +++ b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java @@ -60,12 +60,6 @@ public class FeedMenuHandler { public static boolean onOptionsItemClicked(Context context, MenuItem item, Feed selectedFeed) throws DownloadRequestException { switch (item.getItemId()) { - case R.id.show_info_item: - Intent startIntent = new Intent(context, FeedInfoActivity.class); - startIntent.putExtra(FeedInfoActivity.EXTRA_FEED_ID, - selectedFeed.getId()); - context.startActivity(startIntent); - break; case R.id.refresh_item: DBTasks.refreshFeed(context, selectedFeed); break; diff --git a/src/de/danoeh/antennapod/util/menuhandler/MenuItemUtils.java b/src/de/danoeh/antennapod/util/menuhandler/MenuItemUtils.java new file mode 100644 index 000000000..e75fa394a --- /dev/null +++ b/src/de/danoeh/antennapod/util/menuhandler/MenuItemUtils.java @@ -0,0 +1,20 @@ +package de.danoeh.antennapod.util.menuhandler; + +import android.support.v4.view.MenuItemCompat; +import android.support.v7.widget.SearchView; +import android.view.Menu; +import android.view.MenuItem; +import de.danoeh.antennapod.R; + +/** + * Utilities for menu items + */ +public class MenuItemUtils { + + public static MenuItem addSearchItem(Menu menu, SearchView searchView) { + MenuItem item = menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label); + MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); + MenuItemCompat.setActionView(item, searchView); + return item; + } +} |