Improved keeping user informed on sync progress

Implemented binding to LibrarySyncService to check if there are items currently
syncing or queued to sync. This makes it possible to inform user of background
sync processes.

Created two abstract classes to reduce code duplication and check if
LibrarySyncService has any items queued which are currently being displayed.

Fixed issue with SwipeRefreshLayout from appcompat library, which does not
always show the refresh animation when refresh is set to true.
This commit is contained in:
Martijn Brekhof 2015-08-22 20:03:09 +02:00
parent dab9bc055e
commit 499e54f2cf
13 changed files with 806 additions and 1149 deletions

View File

@ -16,16 +16,23 @@
package org.xbmc.kore.service;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Service;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Process;
import android.support.v4.app.Fragment;
import android.util.Log;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
@ -44,6 +51,7 @@ import org.xbmc.kore.utils.Utils;
import java.lang.System;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import de.greenrobot.event.EventBus;
@ -89,6 +97,10 @@ public class LibrarySyncService extends Service {
private Handler callbackHandler;
private HandlerThread handlerThread;
private ArrayList<SyncOrchestrator> syncOrchestrators;
private final IBinder serviceBinder = new LocalBinder();
@Override
public void onCreate() {
// Create a Handler Thread to process callback calls after the Xbmc method call
@ -98,6 +110,8 @@ public class LibrarySyncService extends Service {
// Get the HandlerThread's Looper and use it for our Handler
callbackHandler = new Handler(handlerThread.getLooper());
// Check which libraries to update and call the corresponding methods on Xbmc
syncOrchestrators = new ArrayList<>();
}
@Override
@ -107,12 +121,12 @@ public class LibrarySyncService extends Service {
// to not interfere with the normal application usage of it (namely calls to disconnect
// and usage of the socket).
HostInfo hostInfo = HostManager.getInstance(this).getHostInfo();
HostConnection hostConnection = new HostConnection(hostInfo);
hostConnection.setProtocol(HostConnection.PROTOCOL_HTTP);
SyncOrchestrator syncOrchestrator = new SyncOrchestrator(this, startId, hostConnection,
SyncOrchestrator syncOrchestrator = new SyncOrchestrator(this, startId, hostInfo,
callbackHandler, getContentResolver());
syncOrchestrators.add(syncOrchestrator);
// Get the request parameters that we should pass when calling back the caller
Bundle syncExtras = intent.getBundleExtra(SYNC_EXTRAS);
@ -167,8 +181,7 @@ public class LibrarySyncService extends Service {
@Override
public IBinder onBind(Intent intent) {
// We don't provide binding, so return null
return null;
return serviceBinder;
}
@SuppressLint("NewApi")
@ -182,40 +195,70 @@ public class LibrarySyncService extends Service {
}
}
public class LocalBinder extends Binder {
public LibrarySyncService getService() {
return LibrarySyncService.this;
}
}
/**
*
* @param hostInfo host information for which to get items currently syncing
* @return currently syncing syncitems for given hostInfo
*/
public ArrayList<SyncItem> getItemsSyncing(HostInfo hostInfo) {
ArrayList<SyncItem> syncItems = new ArrayList<>();
for( SyncOrchestrator orchestrator : syncOrchestrators) {
if( orchestrator.getHostInfo().getId() == hostInfo.getId() ) {
syncItems.addAll(orchestrator.getSyncItems());
return syncItems;
}
}
return null;
}
/**
* Orchestrator for a list os SyncItems
* Keeps a list of SyncItems to sync, and calls each one in order
* When finishes cleans up and stops the service by calling stopSelf
*/
private static class SyncOrchestrator {
private class SyncOrchestrator {
private ArrayDeque<SyncItem> syncItems;
private Service syncService;
private final int serviceStartId;
private final HostConnection hostConnection;
private HostConnection hostConnection;
private final HostInfo hostInfo;
private final Handler callbackHandler;
private final ContentResolver contentResolver;
private SyncItem currentSyncItem;
private Iterator<SyncItem> syncItemIterator;
/**
* Constructor
* @param syncService Service on which to call {@link #stopSelf()} when finished
* @param startId Service startid to use when calling {@link #stopSelf()}
* @param hostConnection Host connection to use
* @param hostInfo Host from which to sync
* @param callbackHandler Handler on which to post callbacks
* @param contentResolver Content resolver
*/
public SyncOrchestrator(Service syncService, final int startId,
final HostConnection hostConnection,
final HostInfo hostInfo,
final Handler callbackHandler,
final ContentResolver contentResolver) {
this.syncService = syncService;
this.syncItems = new ArrayDeque<SyncItem>();
this.serviceStartId = startId;
this.hostConnection = hostConnection;
this.hostInfo = hostInfo;
this.callbackHandler = callbackHandler;
this.contentResolver = contentResolver;
}
public HostInfo getHostInfo() {
return hostInfo;
}
/**
* Add this item to the sync list
* @param syncItem Sync item
@ -224,6 +267,10 @@ public class LibrarySyncService extends Service {
syncItems.add(syncItem);
}
public ArrayDeque<SyncItem> getSyncItems() {
return syncItems;
}
private long startTime = -1;
private long partialStartTime;
@ -232,16 +279,19 @@ public class LibrarySyncService extends Service {
*/
public void startSync() {
startTime = System.currentTimeMillis();
hostConnection = new HostConnection(hostInfo);
hostConnection.setProtocol(HostConnection.PROTOCOL_HTTP);
syncItemIterator = syncItems.iterator();
nextSync();
}
/**
* Processes the next item on the sync list, or cleans up if it is finished.
*/
public void nextSync() {
if (syncItems.size() > 0) {
private void nextSync() {
if (syncItemIterator.hasNext()) {
partialStartTime = System.currentTimeMillis();
currentSyncItem = syncItems.poll();
currentSyncItem = syncItemIterator.next();
currentSyncItem.sync(this, hostConnection, callbackHandler, contentResolver);
} else {
LogUtils.LOGD(TAG, "Sync finished for all items. Total time: " +
@ -249,6 +299,8 @@ public class LibrarySyncService extends Service {
// No more syncs, cleanup.
// No need to disconnect, as this is HTTP
//hostConnection.disconnect();
syncOrchestrators.remove(this);
syncService.stopSelf(serviceStartId);
}
}
@ -256,7 +308,7 @@ public class LibrarySyncService extends Service {
/**
* One of the syync items finish syncing
*/
public void syncItemFinished() {
private void syncItemFinished() {
LogUtils.LOGD(TAG, "Sync finished for item: " + currentSyncItem.getDescription() +
". Total time: " + (System.currentTimeMillis() - partialStartTime));
@ -264,6 +316,9 @@ public class LibrarySyncService extends Service {
.post(new MediaSyncEvent(currentSyncItem.getSyncType(),
currentSyncItem.getSyncExtras(),
MediaSyncEvent.STATUS_SUCCESS));
syncItems.remove(currentSyncItem);
nextSync();
}
@ -272,7 +327,7 @@ public class LibrarySyncService extends Service {
* @param errorCode Error code
* @param description Description
*/
public void syncItemFailed(int errorCode, String description) {
private void syncItemFailed(int errorCode, String description) {
LogUtils.LOGD(TAG, "A Sync item has got an error. Sync item: " +
currentSyncItem.getDescription() +
". Error description: " + description);
@ -291,7 +346,7 @@ public class LibrarySyncService extends Service {
/**
* Represent an item that can be synced
*/
private interface SyncItem {
public interface SyncItem {
/**
* Syncs an item from the XBMC host to the local database
* @param orchestrator Orchestrator to call when finished

View File

@ -15,8 +15,14 @@
*/
package org.xbmc.kore.service;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.jsonrpc.type.VideoType;
import org.xbmc.kore.jsonrpc.type.AudioType;
import org.xbmc.kore.jsonrpc.type.LibraryType;
@ -31,6 +37,10 @@ import java.util.List;
*/
public class SyncUtils {
public interface OnServiceListener {
void onServiceConnected(LibrarySyncService librarySyncService);
}
public static final String LIST_DELIMETER = ", ";
/**
@ -336,7 +346,7 @@ public class SyncUtils {
* @return {@link android.content.ContentValues} with the music video values
*/
public static ContentValues contentValuesFromMusicVideo(int hostId,
VideoType.DetailsMusicVideo musicVideo) {
VideoType.DetailsMusicVideo musicVideo) {
ContentValues musicVideoValues = new ContentValues();
musicVideoValues.put(MediaContract.MusicVideosColumns.HOST_ID, hostId);
musicVideoValues.put(MediaContract.MusicVideosColumns.MUSICVIDEOID, musicVideo.musicvideoid);
@ -401,4 +411,69 @@ public class SyncUtils {
return musicVideoValues;
}
/**
* Binds to {@link LibrarySyncService} and calls {@link OnServiceListener#onServiceConnected(LibrarySyncService)} when connected
* @param context {@link Context}
* @param listener {@link OnServiceListener}
* @return {@link ServiceConnection} to be able to disconnect the connection
*/
public static ServiceConnection connectToLibrarySyncService(Context context, final OnServiceListener listener) {
Intent intent = new Intent(context, LibrarySyncService.class);
final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
LibrarySyncService.LocalBinder binder = (LibrarySyncService.LocalBinder) service;
LibrarySyncService librarySyncService = binder.getService();
listener.onServiceConnected(librarySyncService);
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
}
};
context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
return serviceConnection;
}
/**
* Disconnects from a running LibrarySyncService
* @param context {@link Context}
* @param serviceConnection {@link ServiceConnection} that was used to connect/bind to the service
*/
public static void disconnectFromLibrarySyncService(Context context, ServiceConnection serviceConnection) {
context.unbindService(serviceConnection);
};
/**
* Checks if a running LibrarySyncService is syncing any given SyncType from a specific host
* @param service running LibrarySyncService. Use {@link #connectToLibrarySyncService(Context, OnServiceListener)} to connect to a running LibrarySyncService
* @param hostInfo host to check for sync items currently running or queued
* @param syncTypes sync types to check
* @return true if any of the given syncTypes is running or queued, false otherwise
*/
public static boolean isLibrarySyncing(LibrarySyncService service, HostInfo hostInfo, String... syncTypes) {
if (service == null || hostInfo == null || syncTypes == null)
return false;
ArrayList<LibrarySyncService.SyncItem> itemsSyncing = service.getItemsSyncing(hostInfo);
if( itemsSyncing == null )
return false;
for (LibrarySyncService.SyncItem syncItem : itemsSyncing) {
for( String syncType : syncTypes ) {
if (syncItem.getSyncType().equals(syncType)) {
return true;
}
}
}
return false;
}
}

View File

@ -0,0 +1,231 @@
/*
* Copyright 2015 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.ui;
import android.content.ServiceConnection;
import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.SearchView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.GridView;
import android.widget.TextView;
import android.widget.Toast;
import org.xbmc.kore.R;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
import org.xbmc.kore.service.SyncUtils;
import butterknife.ButterKnife;
import butterknife.InjectView;
import de.greenrobot.event.EventBus;
public abstract class AbstractListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor>,
SyncUtils.OnServiceListener,
SearchView.OnQueryTextListener,
SwipeRefreshLayout.OnRefreshListener {
private ServiceConnection serviceConnection;
private CursorAdapter adapter;
private HostInfo hostInfo;
private EventBus bus;
// Loader IDs
private static final int LOADER = 0;
// The search filter to use in the loader
private String searchFilter = null;
private boolean isSyncing;
@InjectView(R.id.swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout;
@InjectView(R.id.list) GridView gridView;
@InjectView(android.R.id.empty) TextView emptyView;
abstract protected AdapterView.OnItemClickListener createOnItemClickListener();
abstract protected CursorAdapter createAdapter();
abstract protected void onSwipeRefresh();
abstract protected CursorLoader createCursorLoader();
/**
* Called each time a MediaSyncEvent is received.
* @param event
*/
abstract protected void onSyncProcessEnded(MediaSyncEvent event);
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_generic_media_list, container, false);
ButterKnife.inject(this, root);
bus = EventBus.getDefault();
HostManager hostManager = HostManager.getInstance(getActivity());
hostInfo = hostManager.getHostInfo();
swipeRefreshLayout.setOnRefreshListener(this);
return root;
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
gridView.setEmptyView(emptyView);
gridView.setOnItemClickListener(createOnItemClickListener());
// Configure the adapter and start the loader
adapter = createAdapter();
gridView.setAdapter(adapter);
getLoaderManager().initLoader(LOADER, null, this);
setHasOptionsMenu(true);
}
@Override
public void onStart() {
super.onStart();
serviceConnection = SyncUtils.connectToLibrarySyncService(getActivity(), this);
}
@Override
public void onResume() {
bus.register(this);
super.onResume();
}
@Override
public void onPause() {
bus.unregister(this);
super.onPause();
}
@Override
public void onStop() {
super.onStop();
SyncUtils.disconnectFromLibrarySyncService(getActivity(), serviceConnection);
}
/**
* Swipe refresh layout callback
*/
/** {@inheritDoc} */
@Override
public void onRefresh() {
if (hostInfo != null) {
showRefreshAnimation();
onSwipeRefresh();
} else {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), R.string.no_xbmc_configured, Toast.LENGTH_SHORT)
.show();
}
}
/**
* Event bus post. Called when the syncing process ended
*
* @param event Refreshes data
*/
public void onEventMainThread(MediaSyncEvent event) {
onSyncProcessEnded(event);
}
/**
* Search view callbacks
*/
/** {@inheritDoc} */
@Override
public boolean onQueryTextChange(String newText) {
searchFilter = newText;
getLoaderManager().restartLoader(LOADER, null, this);
return true;
}
/** {@inheritDoc} */
@Override
public boolean onQueryTextSubmit(String newText) {
// All is handled in onQueryTextChange
return true;
}
/**
* Loader callbacks
*/
/** {@inheritDoc} */
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return createCursorLoader();
}
/** {@inheritDoc} */
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
adapter.swapCursor(cursor);
// To prevent the empty text from appearing on the first load, set it now
emptyView.setText(getString(R.string.swipe_down_to_refresh));
}
/** {@inheritDoc} */
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
adapter.swapCursor(null);
}
/**
* @return text entered in searchview
*/
public String getSearchFilter() {
return searchFilter;
}
/**
* Use this to reload the items in the list
*/
public void refreshList() {
getLoaderManager().restartLoader(LOADER, null, this);
}
public void showRefreshAnimation() {
/**
* Fixes issue with refresh animation not showing when using appcompat library (from version 20?)
* See https://code.google.com/p/android/issues/detail?id=77712
*/
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
swipeRefreshLayout.setRefreshing(true);
}
});
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2015 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.ui;
import android.content.Intent;
import android.widget.Toast;
import org.xbmc.kore.R;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.ApiException;
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
import org.xbmc.kore.service.LibrarySyncService;
import org.xbmc.kore.service.SyncUtils;
import org.xbmc.kore.utils.UIUtils;
public abstract class AbstractMusicListFragment extends AbstractListFragment {
@Override
protected void onSwipeRefresh() {
// Start the syncing process
Intent syncIntent = new Intent(this.getActivity(), LibrarySyncService.class);
syncIntent.putExtra(LibrarySyncService.SYNC_ALL_MUSIC, true);
getActivity().startService(syncIntent);
}
@Override
protected void onSyncProcessEnded(MediaSyncEvent event) {
if (event.syncType.equals(LibrarySyncService.SYNC_ALL_MUSIC)) {
swipeRefreshLayout.setRefreshing(false);
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
refreshList();
Toast.makeText(getActivity(), R.string.sync_successful, Toast.LENGTH_SHORT)
.show();
} else {
String msg = (event.errorCode == ApiException.API_ERROR) ?
String.format(getString(R.string.error_while_syncing), event.errorMessage) :
getString(R.string.unable_to_connect_to_xbmc);
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onServiceConnected(LibrarySyncService librarySyncService) {
if(SyncUtils.isLibrarySyncing(librarySyncService, HostManager.getInstance(getActivity()).getHostInfo(),
LibrarySyncService.SYNC_ALL_MUSIC, LibrarySyncService.SYNC_ALL_MUSIC_VIDEOS)) {
showRefreshAnimation();
}
}
}

View File

@ -62,39 +62,22 @@ import de.greenrobot.event.EventBus;
/**
* Fragment that presents the albums list
*/
public class AlbumListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor>,
SwipeRefreshLayout.OnRefreshListener,
SearchView.OnQueryTextListener {
public class AlbumListFragment extends AbstractMusicListFragment {
private static final String TAG = LogUtils.makeLogTag(AlbumListFragment.class);
public interface OnAlbumSelectedListener {
public void onAlbumSelected(int albumId, String albumTitle);
}
// Loader IDs
private static final int LOADER_ALBUMS = 0;
private static final String GENREID = "genreid",
ARTISTID = "artistid";
// The search filter to use in the loader
private String searchFilter = null;
private int genreId = -1;
private int artistId = -1;
// Movies adapter
private CursorAdapter adapter;
// Activity listener
private OnAlbumSelectedListener listenerActivity;
private HostInfo hostInfo;
private EventBus bus;
@InjectView(R.id.list) GridView albumsGridView;
@InjectView(R.id.swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout;
@InjectView(android.R.id.empty) TextView emptyView;
/**
* Create a new instance of this, initialized to show albums of genres
@ -121,8 +104,47 @@ public class AlbumListFragment extends Fragment
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
protected AdapterView.OnItemClickListener createOnItemClickListener() {
return new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Get the movie id from the tag
ViewHolder tag = (ViewHolder) view.getTag();
// Notify the activity
listenerActivity.onAlbumSelected(tag.albumId, tag.albumTitle);
}
};
}
@Override
protected CursorAdapter createAdapter() {
return new AlbumsAdapter(getActivity());
}
@Override
protected CursorLoader createCursorLoader() {
Uri uri;
HostInfo hostInfo = HostManager.getInstance(getActivity()).getHostInfo();
int hostId = hostInfo != null ? hostInfo.getId() : -1;
if (artistId != -1) {
uri = MediaContract.AlbumArtists.buildAlbumsForArtistListUri(hostId, artistId);
} else if (genreId != -1) {
uri = MediaContract.AlbumGenres.buildAlbumsForGenreListUri(hostId, genreId);
} else {
uri = MediaContract.Albums.buildAlbumsListUri(hostId);
}
String selection = null;
String selectionArgs[] = null;
String searchFilter = getSearchFilter();
if (!TextUtils.isEmpty(searchFilter)) {
selection = MediaContract.Albums.TITLE + " LIKE ?";
selectionArgs = new String[] {"%" + searchFilter + "%"};
}
return new CursorLoader(getActivity(), uri,
AlbumListQuery.PROJECTION, selection, selectionArgs, AlbumListQuery.SORT);
}
@Override
@ -133,43 +155,7 @@ public class AlbumListFragment extends Fragment
artistId = getArguments().getInt(ARTISTID, -1);
}
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_generic_media_list, container, false);
ButterKnife.inject(this, root);
bus = EventBus.getDefault();
hostInfo = HostManager.getInstance(getActivity()).getHostInfo();
swipeRefreshLayout.setOnRefreshListener(this);
// Pad main content view to overlap with bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), albumsGridView, false, false, true);
// albumsGridView.setClipToPadding(false);
return root;
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
albumsGridView.setEmptyView(emptyView);
albumsGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Get the movie id from the tag
ViewHolder tag = (ViewHolder) view.getTag();
// Notify the activity
listenerActivity.onAlbumSelected(tag.albumId, tag.albumTitle);
}
});
// Configure the adapter and start the loader
adapter = new AlbumsAdapter(getActivity());
albumsGridView.setAdapter(adapter);
getLoaderManager().initLoader(LOADER_ALBUMS, null, this);
setHasOptionsMenu(true);
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
@ -188,18 +174,6 @@ public class AlbumListFragment extends Fragment
listenerActivity = null;
}
@Override
public void onResume() {
bus.register(this);
super.onResume();
}
@Override
public void onPause() {
bus.unregister(this);
super.onPause();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.media_search, menu);
@ -210,112 +184,6 @@ public class AlbumListFragment extends Fragment
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
/**
* Search view callbacks
*/
/** {@inheritDoc} */
@Override
public boolean onQueryTextChange(String newText) {
searchFilter = newText;
getLoaderManager().restartLoader(LOADER_ALBUMS, null, this);
return true;
}
/** {@inheritDoc} */
@Override
public boolean onQueryTextSubmit(String newText) {
// All is handled in onQueryTextChange
return true;
}
/**
* Swipe refresh layout callback
*/
/** {@inheritDoc} */
@Override
public void onRefresh () {
if (hostInfo != null) {
// Make sure we're showing the refresh
swipeRefreshLayout.setRefreshing(true);
// Start the syncing process
Intent syncIntent = new Intent(this.getActivity(), LibrarySyncService.class);
syncIntent.putExtra(LibrarySyncService.SYNC_ALL_MUSIC, true);
getActivity().startService(syncIntent);
} else {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), R.string.no_xbmc_configured, Toast.LENGTH_SHORT)
.show();
}
}
/**
* Event bus post. Called when the syncing process ended
*
* @param event Refreshes data
*/
public void onEventMainThread(MediaSyncEvent event) {
if (event.syncType.equals(LibrarySyncService.SYNC_ALL_MUSIC)) {
swipeRefreshLayout.setRefreshing(false);
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
getLoaderManager().restartLoader(LOADER_ALBUMS, null, this);
Toast.makeText(getActivity(), R.string.sync_successful, Toast.LENGTH_SHORT)
.show();
} else {
String msg = (event.errorCode == ApiException.API_ERROR) ?
String.format(getString(R.string.error_while_syncing), event.errorMessage) :
getString(R.string.unable_to_connect_to_xbmc);
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
}
}
/**
* Loader callbacks
*/
/** {@inheritDoc} */
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
Uri uri;
int hostId = hostInfo != null ? hostInfo.getId() : -1;
if (artistId != -1) {
uri = MediaContract.AlbumArtists.buildAlbumsForArtistListUri(hostId, artistId);
} else if (genreId != -1) {
uri = MediaContract.AlbumGenres.buildAlbumsForGenreListUri(hostId, genreId);
} else {
uri = MediaContract.Albums.buildAlbumsListUri(hostId);
}
String selection = null;
String selectionArgs[] = null;
if (!TextUtils.isEmpty(searchFilter)) {
selection = MediaContract.Albums.TITLE + " LIKE ?";
selectionArgs = new String[] {"%" + searchFilter + "%"};
}
return new CursorLoader(getActivity(), uri,
AlbumListQuery.PROJECTION, selection, selectionArgs, AlbumListQuery.SORT);
}
/** {@inheritDoc} */
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
adapter.swapCursor(cursor);
// To prevent the empty text from appearing on the first load, set it now
emptyView.setText(getString(R.string.no_albums_found_refresh));
}
/** {@inheritDoc} */
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
adapter.swapCursor(null);
}
/**
* Album list query parameters.
*/

View File

@ -17,18 +17,12 @@ package org.xbmc.kore.ui;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.SearchView;
import android.text.TextUtils;
import android.view.LayoutInflater;
@ -39,104 +33,63 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import org.xbmc.kore.R;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.ApiException;
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
import org.xbmc.kore.provider.MediaContract;
import org.xbmc.kore.provider.MediaDatabase;
import org.xbmc.kore.service.LibrarySyncService;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
import butterknife.ButterKnife;
import butterknife.InjectView;
import de.greenrobot.event.EventBus;
/**
* Fragment that presents the artists list
*/
public class ArtistListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor>,
SwipeRefreshLayout.OnRefreshListener,
SearchView.OnQueryTextListener {
public class ArtistListFragment extends AbstractMusicListFragment {
private static final String TAG = LogUtils.makeLogTag(ArtistListFragment.class);
public interface OnArtistSelectedListener {
public void onArtistSelected(int artistId, String artistName);
}
// Loader IDs
private static final int LOADER_ARTISTS = 0;
// The search filter to use in the loader
private String searchFilter = null;
// Movies adapter
private CursorAdapter adapter;
// Activity listener
private OnArtistSelectedListener listenerActivity;
private HostManager hostManager;
private HostInfo hostInfo;
private EventBus bus;
@InjectView(R.id.list) GridView artistsGridView;
@InjectView(R.id.swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout;
@InjectView(android.R.id.empty) TextView emptyView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_generic_media_list, container, false);
ButterKnife.inject(this, root);
bus = EventBus.getDefault();
hostManager = HostManager.getInstance(getActivity());
hostInfo = hostManager.getHostInfo();
swipeRefreshLayout.setOnRefreshListener(this);
//UIUtils.setSwipeRefreshLayoutColorScheme(swipeRefreshLayout);
// Pad main content view to overlap with bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), artistsGridView, false, false, true);
// artistsGridView.setClipToPadding(false);
return root;
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
artistsGridView.setEmptyView(emptyView);
artistsGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
protected AdapterView.OnItemClickListener createOnItemClickListener() {
return new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Get the movie id from the tag
// Get the artist id from the tag
ViewHolder tag = (ViewHolder) view.getTag();
// Notify the activity
listenerActivity.onArtistSelected(tag.artistId, tag.artistName);
}
});
};
}
// Configure the adapter and start the loader
adapter = new ArtistsAdapter(getActivity());
artistsGridView.setAdapter(adapter);
getLoaderManager().initLoader(LOADER_ARTISTS, null, this);
@Override
protected CursorAdapter createAdapter() {
return new ArtistsAdapter(getActivity());
}
setHasOptionsMenu(true);
@Override
protected CursorLoader createCursorLoader() {
HostInfo hostInfo = HostManager.getInstance(getActivity()).getHostInfo();
Uri uri = MediaContract.Artists.buildArtistsListUri(hostInfo != null ? hostInfo.getId() : -1);
String selection = null;
String selectionArgs[] = null;
String searchFilter = getSearchFilter();
if (!TextUtils.isEmpty(searchFilter)) {
selection = MediaContract.ArtistsColumns.ARTIST + " LIKE ?";
selectionArgs = new String[] {"%" + searchFilter + "%"};
}
return new CursorLoader(getActivity(), uri,
ArtistListQuery.PROJECTION, selection, selectionArgs, ArtistListQuery.SORT);
}
@Override
@ -155,18 +108,6 @@ public class ArtistListFragment extends Fragment
listenerActivity = null;
}
@Override
public void onResume() {
bus.register(this);
super.onResume();
}
@Override
public void onPause() {
bus.unregister(this);
super.onPause();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.media_search, menu);
@ -177,113 +118,6 @@ public class ArtistListFragment extends Fragment
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// switch (item.getItemId()) {
// default:
// break;
// }
//
return super.onOptionsItemSelected(item);
}
/**
* Search view callbacks
*/
/** {@inheritDoc} */
@Override
public boolean onQueryTextChange(String newText) {
searchFilter = newText;
getLoaderManager().restartLoader(LOADER_ARTISTS, null, this);
return true;
}
/** {@inheritDoc} */
@Override
public boolean onQueryTextSubmit(String newText) {
// All is handled in onQueryTextChange
return true;
}
/**
* Swipe refresh layout callback
*/
/** {@inheritDoc} */
@Override
public void onRefresh() {
if (hostInfo != null) {
// Make sure we're showing the refresh
swipeRefreshLayout.setRefreshing(true);
// Start the syncing process
Intent syncIntent = new Intent(this.getActivity(), LibrarySyncService.class);
syncIntent.putExtra(LibrarySyncService.SYNC_ALL_MUSIC, true);
getActivity().startService(syncIntent);
// Toast.makeText(getActivity(),
// String.format(getString(R.string.sync_movies_for_host), hostInfo.getName()),
// Toast.LENGTH_SHORT)
// .show();
} else {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), R.string.no_xbmc_configured, Toast.LENGTH_SHORT)
.show();
}
}
/**
* Event bus post. Called when the syncing process ended
*
* @param event Refreshes data
*/
public void onEventMainThread(MediaSyncEvent event) {
if (event.syncType.equals(LibrarySyncService.SYNC_ALL_MUSIC)) {
swipeRefreshLayout.setRefreshing(false);
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
getLoaderManager().restartLoader(LOADER_ARTISTS, null, this);
Toast.makeText(getActivity(), R.string.sync_successful, Toast.LENGTH_SHORT)
.show();
} else {
String msg = (event.errorCode == ApiException.API_ERROR) ?
String.format(getString(R.string.error_while_syncing), event.errorMessage) :
getString(R.string.unable_to_connect_to_xbmc);
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
}
}
/**
* Loader callbacks
*/
/** {@inheritDoc} */
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
Uri uri = MediaContract.Artists.buildArtistsListUri(hostInfo != null ? hostInfo.getId() : -1);
String selection = null;
String selectionArgs[] = null;
if (!TextUtils.isEmpty(searchFilter)) {
selection = MediaContract.ArtistsColumns.ARTIST + " LIKE ?";
selectionArgs = new String[] {"%" + searchFilter + "%"};
}
return new CursorLoader(getActivity(), uri,
ArtistListQuery.PROJECTION, selection, selectionArgs, ArtistListQuery.SORT);
}
/** {@inheritDoc} */
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
adapter.swapCursor(cursor);
// To prevent the empty text from appearing on the first load, set it now
emptyView.setText(getString(R.string.no_artists_found_refresh));
}
/** {@inheritDoc} */
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
adapter.swapCursor(null);
}
/**
* Artist list query parameters.
*/
@ -317,16 +151,16 @@ public class ArtistListFragment extends Fragment
// Get the art dimensions
Resources resources = context.getResources();
artWidth = (int)(resources.getDimension(R.dimen.artistlist_art_width) /
UIUtils.IMAGE_RESIZE_FACTOR);
UIUtils.IMAGE_RESIZE_FACTOR);
artHeight = (int)(resources.getDimension(R.dimen.artistlist_art_heigth) /
UIUtils.IMAGE_RESIZE_FACTOR);
UIUtils.IMAGE_RESIZE_FACTOR);
}
/** {@inheritDoc} */
@Override
public View newView(Context context, final Cursor cursor, ViewGroup parent) {
final View view = LayoutInflater.from(context)
.inflate(R.layout.grid_item_artist, parent, false);
.inflate(R.layout.grid_item_artist, parent, false);
// Setup View holder pattern
ViewHolder viewHolder = new ViewHolder();

View File

@ -61,65 +61,19 @@ import de.greenrobot.event.EventBus;
/**
* Fragment that presents the album genres list
*/
public class AudioGenresListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor>,
SwipeRefreshLayout.OnRefreshListener,
SearchView.OnQueryTextListener {
public class AudioGenresListFragment extends AbstractMusicListFragment {
private static final String TAG = LogUtils.makeLogTag(AudioGenresListFragment.class);
public interface OnAudioGenreSelectedListener {
public void onAudioGenreSelected(int genreId, String genreTitle);
}
// Loader IDs
private static final int LOADER_AUDIO_GENRES = 0;
// The search filter to use in the loader
private String searchFilter = null;
// Movies adapter
private CursorAdapter adapter;
// Activity listener
private OnAudioGenreSelectedListener listenerActivity;
private HostInfo hostInfo;
private EventBus bus;
@InjectView(R.id.list) GridView audioGenresGridView;
@InjectView(R.id.swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout;
@InjectView(android.R.id.empty) TextView emptyView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_generic_media_list, container, false);
ButterKnife.inject(this, root);
bus = EventBus.getDefault();
hostInfo = HostManager.getInstance(getActivity()).getHostInfo();
swipeRefreshLayout.setOnRefreshListener(this);
//UIUtils.setSwipeRefreshLayoutColorScheme(swipeRefreshLayout);
// Pad main content view to overlap with bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), audioGenresGridView, false, false, true);
// audioGenresGridView.setClipToPadding(false);
return root;
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
audioGenresGridView.setEmptyView(emptyView);
audioGenresGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
protected AdapterView.OnItemClickListener createOnItemClickListener() {
return new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Get the movie id from the tag
@ -127,14 +81,29 @@ public class AudioGenresListFragment extends Fragment
// Notify the activity
listenerActivity.onAudioGenreSelected(tag.genreId, tag.genreTitle);
}
});
};
}
// Configure the adapter and start the loader
adapter = new AudioGenresAdapter(getActivity());
audioGenresGridView.setAdapter(adapter);
getLoaderManager().initLoader(LOADER_AUDIO_GENRES, null, this);
@Override
protected CursorAdapter createAdapter() {
return new AudioGenresAdapter(getActivity());
}
setHasOptionsMenu(true);
@Override
protected CursorLoader createCursorLoader() {
HostInfo hostInfo = HostManager.getInstance(getActivity()).getHostInfo();
Uri uri = MediaContract.AudioGenres.buildAudioGenresListUri(hostInfo != null ? hostInfo.getId() : -1);
String selection = null;
String selectionArgs[] = null;
String searchFilter = getSearchFilter();
if (!TextUtils.isEmpty(searchFilter)) {
selection = MediaContract.AudioGenres.TITLE + " LIKE ?";
selectionArgs = new String[] {"%" + searchFilter + "%"};
}
return new CursorLoader(getActivity(), uri,
AudioGenreListQuery.PROJECTION, selection, selectionArgs, AudioGenreListQuery.SORT);
}
@Override
@ -153,18 +122,6 @@ public class AudioGenresListFragment extends Fragment
listenerActivity = null;
}
@Override
public void onResume() {
bus.register(this);
super.onResume();
}
@Override
public void onPause() {
bus.unregister(this);
super.onPause();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.media_search, menu);
@ -175,102 +132,6 @@ public class AudioGenresListFragment extends Fragment
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
/**
* Search view callbacks
*/
/** {@inheritDoc} */
@Override
public boolean onQueryTextChange(String newText) {
searchFilter = newText;
getLoaderManager().restartLoader(LOADER_AUDIO_GENRES, null, this);
return true;
}
/** {@inheritDoc} */
@Override
public boolean onQueryTextSubmit(String newText) {
// All is handled in onQueryTextChange
return true;
}
/**
* Swipe refresh layout callback
*/
/** {@inheritDoc} */
@Override
public void onRefresh() {
if (hostInfo != null) {
// Make sure we're showing the refresh
swipeRefreshLayout.setRefreshing(true);
// Start the syncing process
Intent syncIntent = new Intent(this.getActivity(), LibrarySyncService.class);
syncIntent.putExtra(LibrarySyncService.SYNC_ALL_MUSIC, true);
getActivity().startService(syncIntent);
} else {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), R.string.no_xbmc_configured, Toast.LENGTH_SHORT)
.show();
}
}
/**
* Event bus post. Called when the syncing process ended
*
* @param event Refreshes data
*/
public void onEventMainThread(MediaSyncEvent event) {
if (event.syncType.equals(LibrarySyncService.SYNC_ALL_MUSIC)) {
swipeRefreshLayout.setRefreshing(false);
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
getLoaderManager().restartLoader(LOADER_AUDIO_GENRES, null, this);
Toast.makeText(getActivity(), R.string.sync_successful, Toast.LENGTH_SHORT)
.show();
} else {
String msg = (event.errorCode == ApiException.API_ERROR) ?
String.format(getString(R.string.error_while_syncing), event.errorMessage) :
getString(R.string.unable_to_connect_to_xbmc);
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
}
}
/**
* Loader callbacks
*/
/** {@inheritDoc} */
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
Uri uri = MediaContract.AudioGenres.buildAudioGenresListUri(hostInfo != null ? hostInfo.getId() : -1);
String selection = null;
String selectionArgs[] = null;
if (!TextUtils.isEmpty(searchFilter)) {
selection = MediaContract.AudioGenres.TITLE + " LIKE ?";
selectionArgs = new String[] {"%" + searchFilter + "%"};
}
return new CursorLoader(getActivity(), uri,
AudioGenreListQuery.PROJECTION, selection, selectionArgs, AudioGenreListQuery.SORT);
}
/** {@inheritDoc} */
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
adapter.swapCursor(cursor);
// To prevent the empty text from appearing on the first load, set it now
emptyView.setText(getString(R.string.no_genres_found_refresh));
}
/** {@inheritDoc} */
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
adapter.swapCursor(null);
}
/**
* Audio genres list query parameters.

View File

@ -22,15 +22,10 @@ import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.SearchView;
import android.text.TextUtils;
import android.view.LayoutInflater;
@ -41,7 +36,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@ -55,77 +49,26 @@ import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
import org.xbmc.kore.provider.MediaContract;
import org.xbmc.kore.provider.MediaDatabase;
import org.xbmc.kore.service.LibrarySyncService;
import org.xbmc.kore.service.SyncUtils;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
import butterknife.ButterKnife;
import butterknife.InjectView;
import de.greenrobot.event.EventBus;
/**
* Fragment that presents the movie list
*/
public class MovieListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor>,
SwipeRefreshLayout.OnRefreshListener,
SearchView.OnQueryTextListener {
public class MovieListFragment extends AbstractListFragment {
private static final String TAG = LogUtils.makeLogTag(MovieListFragment.class);
public interface OnMovieSelectedListener {
public void onMovieSelected(int movieId, String movieTitle);
}
// Loader IDs
private static final int LOADER_MOVIES = 0;
// The search filter to use in the loader
private String searchFilter = null;
// Movies adapter
private CursorAdapter adapter;
// Activity listener
private OnMovieSelectedListener listenerActivity;
private HostManager hostManager;
private HostInfo hostInfo;
private EventBus bus;
@InjectView(R.id.list) GridView moviesGridView;
@InjectView(R.id.swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout;
@InjectView(android.R.id.empty) TextView emptyView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_generic_media_list, container, false);
ButterKnife.inject(this, root);
bus = EventBus.getDefault();
hostManager = HostManager.getInstance(getActivity());
hostInfo = hostManager.getHostInfo();
swipeRefreshLayout.setOnRefreshListener(this);
//UIUtils.setSwipeRefreshLayoutColorScheme(swipeRefreshLayout);
// Pad main content view to overlap with bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), moviesGridView, false, false, true);
// moviesGridView.setClipToPadding(false);
return root;
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
moviesGridView.setEmptyView(emptyView);
moviesGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
protected AdapterView.OnItemClickListener createOnItemClickListener() {
return new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Get the movie id from the tag
@ -133,14 +76,90 @@ public class MovieListFragment extends Fragment
// Notify the activity
listenerActivity.onMovieSelected(tag.movieId, tag.movieTitle);
}
});
};
}
// Configure the adapter and start the loader
adapter = new MoviesAdapter(getActivity());
moviesGridView.setAdapter(adapter);
getLoaderManager().initLoader(LOADER_MOVIES, null, this);
@Override
protected CursorAdapter createAdapter() {
return new MoviesAdapter(getActivity());
}
setHasOptionsMenu(true);
@Override
protected void onSwipeRefresh() {
Intent syncIntent = new Intent(this.getActivity(), LibrarySyncService.class);
syncIntent.putExtra(LibrarySyncService.SYNC_ALL_MOVIES, true);
getActivity().startService(syncIntent);
}
@Override
protected CursorLoader createCursorLoader() {
HostInfo hostInfo = HostManager.getInstance(getActivity()).getHostInfo();
Uri uri = MediaContract.Movies.buildMoviesListUri(hostInfo != null? hostInfo.getId() : -1);
StringBuilder selection = new StringBuilder();
String selectionArgs[] = null;
String searchFilter = getSearchFilter();
if (!TextUtils.isEmpty(searchFilter)) {
selection.append(MediaContract.MoviesColumns.TITLE + " LIKE ?");
selectionArgs = new String[] {"%" + searchFilter + "%"};
}
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
if (preferences.getBoolean(Settings.KEY_PREF_MOVIES_FILTER_HIDE_WATCHED, Settings.DEFAULT_PREF_MOVIES_FILTER_HIDE_WATCHED)) {
if (selection.length() != 0)
selection.append(" AND ");
selection.append(MediaContract.MoviesColumns.PLAYCOUNT)
.append("=0");
}
String sortOrderStr;
int sortOrder = preferences.getInt(Settings.KEY_PREF_MOVIES_SORT_ORDER, Settings.DEFAULT_PREF_MOVIES_SORT_ORDER);
if (sortOrder == Settings.SORT_BY_DATE_ADDED) {
sortOrderStr = MovieListQuery.SORT_BY_DATE_ADDED;
} else {
// Sort by name
if (preferences.getBoolean(Settings.KEY_PREF_MOVIES_IGNORE_PREFIXES, Settings.DEFAULT_PREF_MOVIES_IGNORE_PREFIXES)) {
sortOrderStr = MovieListQuery.SORT_BY_NAME_IGNORE_ARTICLES;
} else {
sortOrderStr = MovieListQuery.SORT_BY_NAME;
}
}
return new CursorLoader(getActivity(), uri,
MovieListQuery.PROJECTION, selection.toString(), selectionArgs, sortOrderStr);
}
@Override
protected void onSyncProcessEnded(MediaSyncEvent event) {
boolean silentSync = false;
if (event.syncExtras != null) {
silentSync = event.syncExtras.getBoolean(LibrarySyncService.SILENT_SYNC, false);
}
if (event.syncType.equals(LibrarySyncService.SYNC_SINGLE_MOVIE) ||
event.syncType.equals(LibrarySyncService.SYNC_ALL_MOVIES)) {
swipeRefreshLayout.setRefreshing(false);
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
refreshList();
if (!silentSync) {
Toast.makeText(getActivity(), R.string.sync_successful, Toast.LENGTH_SHORT)
.show();
}
} else if (!silentSync) {
String msg = (event.errorCode == ApiException.API_ERROR) ?
String.format(getString(R.string.error_while_syncing), event.errorMessage) :
getString(R.string.unable_to_connect_to_xbmc);
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onServiceConnected(LibrarySyncService librarySyncService) {
if(SyncUtils.isLibrarySyncing(librarySyncService, HostManager.getInstance(getActivity()).getHostInfo(),
LibrarySyncService.SYNC_ALL_MOVIES, LibrarySyncService.SYNC_SINGLE_MOVIE)) {
showRefreshAnimation();
}
}
@Override
@ -159,18 +178,6 @@ public class MovieListFragment extends Fragment
listenerActivity = null;
}
@Override
public void onResume() {
bus.register(this);
super.onResume();
}
@Override
public void onPause() {
bus.unregister(this);
super.onPause();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.movie_list, menu);
@ -213,28 +220,28 @@ public class MovieListFragment extends Fragment
preferences.edit()
.putBoolean(Settings.KEY_PREF_MOVIES_FILTER_HIDE_WATCHED, item.isChecked())
.apply();
getLoaderManager().restartLoader(LOADER_MOVIES, null, this);
refreshList();
break;
case R.id.action_ignore_prefixes:
item.setChecked(!item.isChecked());
preferences.edit()
.putBoolean(Settings.KEY_PREF_MOVIES_IGNORE_PREFIXES, item.isChecked())
.apply();
getLoaderManager().restartLoader(LOADER_MOVIES, null, this);
refreshList();
break;
case R.id.action_sort_by_name:
item.setChecked(true);
preferences.edit()
.putInt(Settings.KEY_PREF_MOVIES_SORT_ORDER, Settings.SORT_BY_NAME)
.apply();
getLoaderManager().restartLoader(LOADER_MOVIES, null, this);
refreshList();
break;
case R.id.action_sort_by_date_added:
item.setChecked(true);
preferences.edit()
.putInt(Settings.KEY_PREF_MOVIES_SORT_ORDER, Settings.SORT_BY_DATE_ADDED)
.apply();
getLoaderManager().restartLoader(LOADER_MOVIES, null, this);
refreshList();
break;
default:
break;
@ -243,132 +250,6 @@ public class MovieListFragment extends Fragment
return super.onOptionsItemSelected(item);
}
/**
* Search view callbacks
*/
/** {@inheritDoc} */
@Override
public boolean onQueryTextChange(String newText) {
searchFilter = newText;
getLoaderManager().restartLoader(LOADER_MOVIES, null, this);
return true;
}
/** {@inheritDoc} */
@Override
public boolean onQueryTextSubmit(String newText) {
// All is handled in onQueryTextChange
return true;
}
/**
* Swipe refresh layout callback
*/
/** {@inheritDoc} */
@Override
public void onRefresh () {
if (hostInfo != null) {
// Make sure we're showing the refresh
swipeRefreshLayout.setRefreshing(true);
// Start the syncing process
Intent syncIntent = new Intent(this.getActivity(), LibrarySyncService.class);
syncIntent.putExtra(LibrarySyncService.SYNC_ALL_MOVIES, true);
getActivity().startService(syncIntent);
// Toast.makeText(getActivity(),
// String.format(getString(R.string.sync_movies_for_host), hostInfo.getName()),
// Toast.LENGTH_SHORT)
// .show();
} else {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), R.string.no_xbmc_configured, Toast.LENGTH_SHORT)
.show();
}
}
/**
* Event bus post. Called when the syncing process ended
*
* @param event Refreshes data
*/
public void onEventMainThread(MediaSyncEvent event) {
boolean silentSync = false;
if (event.syncExtras != null) {
silentSync = event.syncExtras.getBoolean(LibrarySyncService.SILENT_SYNC, false);
}
if (event.syncType.equals(LibrarySyncService.SYNC_SINGLE_MOVIE) ||
event.syncType.equals(LibrarySyncService.SYNC_ALL_MOVIES)) {
swipeRefreshLayout.setRefreshing(false);
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
getLoaderManager().restartLoader(LOADER_MOVIES, null, this);
if (!silentSync) {
Toast.makeText(getActivity(), R.string.sync_successful, Toast.LENGTH_SHORT)
.show();
}
} else if (!silentSync) {
String msg = (event.errorCode == ApiException.API_ERROR) ?
String.format(getString(R.string.error_while_syncing), event.errorMessage) :
getString(R.string.unable_to_connect_to_xbmc);
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
}
}
/**
* Loader callbacks
*/
/** {@inheritDoc} */
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
Uri uri = MediaContract.Movies.buildMoviesListUri(hostInfo != null? hostInfo.getId() : -1);
StringBuilder selection = new StringBuilder();
String selectionArgs[] = null;
if (!TextUtils.isEmpty(searchFilter)) {
selection.append(MediaContract.MoviesColumns.TITLE + " LIKE ?");
selectionArgs = new String[] {"%" + searchFilter + "%"};
}
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
if (preferences.getBoolean(Settings.KEY_PREF_MOVIES_FILTER_HIDE_WATCHED, Settings.DEFAULT_PREF_MOVIES_FILTER_HIDE_WATCHED)) {
if (selection.length() != 0)
selection.append(" AND ");
selection.append(MediaContract.MoviesColumns.PLAYCOUNT)
.append("=0");
}
String sortOrderStr;
int sortOrder = preferences.getInt(Settings.KEY_PREF_MOVIES_SORT_ORDER, Settings.DEFAULT_PREF_MOVIES_SORT_ORDER);
if (sortOrder == Settings.SORT_BY_DATE_ADDED) {
sortOrderStr = MovieListQuery.SORT_BY_DATE_ADDED;
} else {
// Sort by name
if (preferences.getBoolean(Settings.KEY_PREF_MOVIES_IGNORE_PREFIXES, Settings.DEFAULT_PREF_MOVIES_IGNORE_PREFIXES)) {
sortOrderStr = MovieListQuery.SORT_BY_NAME_IGNORE_ARTICLES;
} else {
sortOrderStr = MovieListQuery.SORT_BY_NAME;
}
}
return new CursorLoader(getActivity(), uri,
MovieListQuery.PROJECTION, selection.toString(), selectionArgs, sortOrderStr);
}
/** {@inheritDoc} */
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
adapter.swapCursor(cursor);
// To prevent the empty text from appearing on the first load, set it now
emptyView.setText(getString(R.string.no_movies_found_refresh));
}
/** {@inheritDoc} */
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
adapter.swapCursor(null);
}
/**
* Movie list query parameters.
*/

View File

@ -15,20 +15,44 @@
*/
package org.xbmc.kore.ui;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;
import com.astuetz.PagerSlidingTabStrip;
import org.xbmc.kore.R;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.ApiException;
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
import org.xbmc.kore.service.LibrarySyncService;
import org.xbmc.kore.service.SyncUtils;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.TabsAdapter;
import java.util.ArrayList;
import butterknife.ButterKnife;
import butterknife.InjectView;
import de.greenrobot.event.EventBus;
/**
* Container for the various music lists
@ -36,20 +60,17 @@ import butterknife.InjectView;
public class MusicListFragment extends Fragment {
private static final String TAG = LogUtils.makeLogTag(MusicListFragment.class);
private TabsAdapter tabsAdapter;
@InjectView(R.id.pager_tab_strip) PagerSlidingTabStrip pagerTabStrip;
@InjectView(R.id.pager) ViewPager viewPager;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_music_list, container, false);
ButterKnife.inject(this, root);
TabsAdapter tabsAdapter = new TabsAdapter(getActivity(), getChildFragmentManager())
tabsAdapter = new TabsAdapter(getActivity(), getChildFragmentManager())
.addTab(ArtistListFragment.class, getArguments(), R.string.artists, 1)
.addTab(AlbumListFragment.class, getArguments(), R.string.albums, 2)
.addTab(AudioGenresListFragment.class, getArguments(), R.string.genres, 3)
@ -66,20 +87,4 @@ public class MusicListFragment extends Fragment {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(false);
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onPause() {
super.onPause();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// outState.putInt(TVSHOWID, tvshowId);
}
}

View File

@ -21,12 +21,9 @@ import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.SearchView;
@ -39,7 +36,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@ -52,74 +48,26 @@ import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
import org.xbmc.kore.provider.MediaContract;
import org.xbmc.kore.provider.MediaDatabase;
import org.xbmc.kore.service.LibrarySyncService;
import org.xbmc.kore.service.SyncUtils;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
import butterknife.ButterKnife;
import butterknife.InjectView;
import de.greenrobot.event.EventBus;
/**
* Fragment that presents the artists list
*/
public class MusicVideoListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor>,
SwipeRefreshLayout.OnRefreshListener,
SearchView.OnQueryTextListener {
public class MusicVideoListFragment extends AbstractListFragment {
private static final String TAG = LogUtils.makeLogTag(MusicVideoListFragment.class);
public interface OnMusicVideoSelectedListener {
public void onMusicVideoSelected(int musicVideoId, String musicVideoTitle);
}
// Loader IDs
private static final int LOADER_MUSIC_VIDEOS = 0;
// The search filter to use in the loader
private String searchFilter = null;
// Movies adapter
private CursorAdapter adapter;
// Activity listener
private OnMusicVideoSelectedListener listenerActivity;
private HostInfo hostInfo;
private EventBus bus;
@InjectView(R.id.list) GridView musicVideosGridView;
@InjectView(R.id.swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout;
@InjectView(android.R.id.empty) TextView emptyView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_generic_media_list, container, false);
ButterKnife.inject(this, root);
bus = EventBus.getDefault();
hostInfo = HostManager.getInstance(getActivity()).getHostInfo();
swipeRefreshLayout.setOnRefreshListener(this);
// UIUtils.setSwipeRefreshLayoutColorScheme(swipeRefreshLayout);
// Pad main content view to overlap with bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), musicVideosGridView, false, false, true);
// musicVideosGridView.setClipToPadding(false);
return root;
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
musicVideosGridView.setEmptyView(emptyView);
musicVideosGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
protected AdapterView.OnItemClickListener createOnItemClickListener() {
return new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Get the movie id from the tag
@ -127,14 +75,61 @@ public class MusicVideoListFragment extends Fragment
// Notify the activity
listenerActivity.onMusicVideoSelected(tag.musicVideoId, tag.musicVideoTitle);
}
});
};
}
// Configure the adapter and start the loader
adapter = new MusicVideosAdapter(getActivity());
musicVideosGridView.setAdapter(adapter);
getLoaderManager().initLoader(LOADER_MUSIC_VIDEOS, null, this);
@Override
protected CursorAdapter createAdapter() {
return new MusicVideosAdapter(getActivity());
}
setHasOptionsMenu(true);
@Override
protected void onSwipeRefresh() {
Intent syncIntent = new Intent(this.getActivity(), LibrarySyncService.class);
syncIntent.putExtra(LibrarySyncService.SYNC_ALL_MUSIC_VIDEOS, true);
getActivity().startService(syncIntent);
}
@Override
protected CursorLoader createCursorLoader() {
HostInfo hostInfo = HostManager.getInstance(getActivity()).getHostInfo();
Uri uri = MediaContract.MusicVideos.buildMusicVideosListUri(hostInfo != null ? hostInfo.getId() : -1);
String selection = null;
String selectionArgs[] = null;
String searchFilter = getSearchFilter();
if (!TextUtils.isEmpty(searchFilter)) {
selection = MediaContract.MusicVideosColumns.TITLE + " LIKE ?";
selectionArgs = new String[] {"%" + searchFilter + "%"};
}
return new CursorLoader(getActivity(), uri,
MusicVideosListQuery.PROJECTION, selection, selectionArgs, MusicVideosListQuery.SORT);
}
@Override
protected void onSyncProcessEnded(MediaSyncEvent event) {
if (event.syncType.equals(LibrarySyncService.SYNC_ALL_MUSIC_VIDEOS)) {
swipeRefreshLayout.setRefreshing(false);
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
refreshList();
Toast.makeText(getActivity(), R.string.sync_successful, Toast.LENGTH_SHORT)
.show();
} else {
String msg = (event.errorCode == ApiException.API_ERROR) ?
String.format(getString(R.string.error_while_syncing), event.errorMessage) :
getString(R.string.unable_to_connect_to_xbmc);
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onServiceConnected(LibrarySyncService librarySyncService) {
if(SyncUtils.isLibrarySyncing(librarySyncService, HostManager.getInstance(getActivity()).getHostInfo(),
LibrarySyncService.SYNC_ALL_MUSIC_VIDEOS)) {
showRefreshAnimation();
}
}
@Override
@ -153,18 +148,6 @@ public class MusicVideoListFragment extends Fragment
listenerActivity = null;
}
@Override
public void onResume() {
bus.register(this);
super.onResume();
}
@Override
public void onPause() {
bus.unregister(this);
super.onPause();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.media_search, menu);
@ -175,103 +158,6 @@ public class MusicVideoListFragment extends Fragment
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
/**
* Search view callbacks
*/
/** {@inheritDoc} */
@Override
public boolean onQueryTextChange(String newText) {
searchFilter = newText;
getLoaderManager().restartLoader(LOADER_MUSIC_VIDEOS, null, this);
return true;
}
/** {@inheritDoc} */
@Override
public boolean onQueryTextSubmit(String newText) {
// All is handled in onQueryTextChange
return true;
}
/**
* Swipe refresh layout callback
*/
/** {@inheritDoc} */
@Override
public void onRefresh() {
if (hostInfo != null) {
// Make sure we're showing the refresh
swipeRefreshLayout.setRefreshing(true);
// Start the syncing process
Intent syncIntent = new Intent(this.getActivity(), LibrarySyncService.class);
syncIntent.putExtra(LibrarySyncService.SYNC_ALL_MUSIC_VIDEOS, true);
getActivity().startService(syncIntent);
} else {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), R.string.no_xbmc_configured, Toast.LENGTH_SHORT)
.show();
}
}
/**
* Event bus post. Called when the syncing process ended
*
* @param event Refreshes data
*/
public void onEventMainThread(MediaSyncEvent event) {
if (event.syncType.equals(LibrarySyncService.SYNC_ALL_MUSIC_VIDEOS)) {
swipeRefreshLayout.setRefreshing(false);
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
getLoaderManager().restartLoader(LOADER_MUSIC_VIDEOS, null, this);
Toast.makeText(getActivity(), R.string.sync_successful, Toast.LENGTH_SHORT)
.show();
} else {
String msg = (event.errorCode == ApiException.API_ERROR) ?
String.format(getString(R.string.error_while_syncing), event.errorMessage) :
getString(R.string.unable_to_connect_to_xbmc);
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
}
}
/**
* Loader callbacks
*/
/** {@inheritDoc} */
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
Uri uri = MediaContract.MusicVideos.buildMusicVideosListUri(hostInfo != null ? hostInfo.getId() : -1);
String selection = null;
String selectionArgs[] = null;
if (!TextUtils.isEmpty(searchFilter)) {
selection = MediaContract.MusicVideosColumns.TITLE + " LIKE ?";
selectionArgs = new String[] {"%" + searchFilter + "%"};
}
return new CursorLoader(getActivity(), uri,
MusicVideosListQuery.PROJECTION, selection, selectionArgs, MusicVideosListQuery.SORT);
}
/** {@inheritDoc} */
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
adapter.swapCursor(cursor);
// To prevent the empty text from appearing on the first load, set it now
emptyView.setText(getString(R.string.no_music_videos_found_refresh));
}
/** {@inheritDoc} */
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
adapter.swapCursor(null);
}
/**
* Videos list query parameters.
*/

View File

@ -55,6 +55,7 @@ import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
import org.xbmc.kore.provider.MediaContract;
import org.xbmc.kore.provider.MediaDatabase;
import org.xbmc.kore.service.LibrarySyncService;
import org.xbmc.kore.service.SyncUtils;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
@ -65,66 +66,19 @@ import de.greenrobot.event.EventBus;
/**
* Fragment that presents the tv show list
*/
public class TVShowListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor>,
SwipeRefreshLayout.OnRefreshListener,
SearchView.OnQueryTextListener {
public class TVShowListFragment extends AbstractListFragment {
private static final String TAG = LogUtils.makeLogTag(TVShowListFragment.class);
public interface OnTVShowSelectedListener {
public void onTVShowSelected(int tvshowId, String tvshowTitle);
}
// Loader IDs
private static final int LOADER_TVSHOWS = 0;
// The search filter to use in the loader
private String searchFilter = null;
// Movies adapter
private CursorAdapter adapter;
// Activity listener
private OnTVShowSelectedListener listenerActivity;
private HostManager hostManager;
private HostInfo hostInfo;
private EventBus bus;
@InjectView(R.id.list) GridView tvshowsGridView;
@InjectView(R.id.swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout;
@InjectView(android.R.id.empty) TextView emptyView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_generic_media_list, container, false);
ButterKnife.inject(this, root);
bus = EventBus.getDefault();
hostManager = HostManager.getInstance(getActivity());
hostInfo = hostManager.getHostInfo();
swipeRefreshLayout.setOnRefreshListener(this);
//UIUtils.setSwipeRefreshLayoutColorScheme(swipeRefreshLayout);
// Pad main content view to overlap with bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), moviesGridView, false, false, true);
// moviesGridView.setClipToPadding(false);
return root;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
tvshowsGridView.setEmptyView(emptyView);
tvshowsGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
protected AdapterView.OnItemClickListener createOnItemClickListener() {
return new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Get the movie id from the tag
@ -132,14 +86,94 @@ public class TVShowListFragment extends Fragment
// Notify the activity
listenerActivity.onTVShowSelected(tag.tvshowId, tag.tvshowTitle);
}
});
};
}
// Configure the adapter and start the loader
adapter = new TVShowsAdapter(getActivity());
tvshowsGridView.setAdapter(adapter);
getLoaderManager().initLoader(LOADER_TVSHOWS, null, this);
@Override
protected CursorAdapter createAdapter() {
return new TVShowsAdapter(getActivity());
}
setHasOptionsMenu(true);
@Override
protected void onSwipeRefresh() {
Intent syncIntent = new Intent(this.getActivity(), LibrarySyncService.class);
syncIntent.putExtra(LibrarySyncService.SYNC_ALL_TVSHOWS, true);
getActivity().startService(syncIntent);
}
@Override
protected CursorLoader createCursorLoader() {
HostInfo hostInfo = HostManager.getInstance(getActivity()).getHostInfo();
Uri uri = MediaContract.TVShows.buildTVShowsListUri(hostInfo != null ? hostInfo.getId() : -1);
StringBuilder selection = new StringBuilder();
String selectionArgs[] = null;
String searchFilter = getSearchFilter();
if (!TextUtils.isEmpty(searchFilter)) {
selection.append(MediaContract.TVShowsColumns.TITLE + " LIKE ?");
selectionArgs = new String[] {"%" + searchFilter + "%"};
}
// Filters
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
if (preferences.getBoolean(Settings.KEY_PREF_TVSHOWS_FILTER_HIDE_WATCHED, Settings.DEFAULT_PREF_TVSHOWS_FILTER_HIDE_WATCHED)) {
if (selection.length() != 0)
selection.append(" AND ");
selection.append(MediaContract.TVShowsColumns.WATCHEDEPISODES)
.append("!=")
.append(MediaContract.TVShowsColumns.EPISODE);
}
String sortOrderStr;
int sortOrder = preferences.getInt(Settings.KEY_PREF_TVSHOWS_SORT_ORDER, Settings.DEFAULT_PREF_TVSHOWS_SORT_ORDER);
if (sortOrder == Settings.SORT_BY_DATE_ADDED) {
sortOrderStr = TVShowListQuery.SORT_BY_DATE_ADDED;
} else {
// Sort by name
if (preferences.getBoolean(Settings.KEY_PREF_TVSHOWS_IGNORE_PREFIXES, Settings.DEFAULT_PREF_TVSHOWS_IGNORE_PREFIXES)) {
sortOrderStr = TVShowListQuery.SORT_BY_NAME_IGNORE_ARTICLES;
} else {
sortOrderStr = TVShowListQuery.SORT_BY_NAME;
}
}
return new CursorLoader(getActivity(), uri,
TVShowListQuery.PROJECTION, selection.toString(),
selectionArgs, sortOrderStr);
}
@Override
protected void onSyncProcessEnded(MediaSyncEvent event) {
boolean silentSync = false;
if (event.syncExtras != null) {
silentSync = event.syncExtras.getBoolean(LibrarySyncService.SILENT_SYNC, false);
}
if (event.syncType.equals(LibrarySyncService.SYNC_SINGLE_TVSHOW) ||
event.syncType.equals(LibrarySyncService.SYNC_ALL_TVSHOWS)) {
swipeRefreshLayout.setRefreshing(false);
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
refreshList();
if (!silentSync) {
Toast.makeText(getActivity(), R.string.sync_successful, Toast.LENGTH_SHORT)
.show();
}
} else if (!silentSync) {
String msg = (event.errorCode == ApiException.API_ERROR) ?
String.format(getString(R.string.error_while_syncing), event.errorMessage) :
getString(R.string.unable_to_connect_to_xbmc);
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onServiceConnected(LibrarySyncService librarySyncService) {
if(SyncUtils.isLibrarySyncing(librarySyncService, HostManager.getInstance(getActivity()).getHostInfo(),
LibrarySyncService.SYNC_ALL_TVSHOWS, LibrarySyncService.SYNC_SINGLE_TVSHOW)) {
showRefreshAnimation();
}
}
@Override
@ -158,18 +192,6 @@ public class TVShowListFragment extends Fragment
listenerActivity = null;
}
@Override
public void onResume() {
bus.register(this);
super.onResume();
}
@Override
public void onPause() {
bus.unregister(this);
super.onPause();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.tvshow_list, menu);
@ -210,28 +232,28 @@ public class TVShowListFragment extends Fragment
preferences.edit()
.putBoolean(Settings.KEY_PREF_TVSHOWS_FILTER_HIDE_WATCHED, item.isChecked())
.apply();
getLoaderManager().restartLoader(LOADER_TVSHOWS, null, this);
refreshList();
break;
case R.id.action_ignore_prefixes:
item.setChecked(!item.isChecked());
preferences.edit()
.putBoolean(Settings.KEY_PREF_TVSHOWS_IGNORE_PREFIXES, item.isChecked())
.apply();
getLoaderManager().restartLoader(LOADER_TVSHOWS, null, this);
refreshList();
break;
case R.id.action_sort_by_name:
item.setChecked(true);
preferences.edit()
.putInt(Settings.KEY_PREF_TVSHOWS_SORT_ORDER, Settings.SORT_BY_NAME)
.apply();
getLoaderManager().restartLoader(LOADER_TVSHOWS, null, this);
refreshList();
break;
case R.id.action_sort_by_date_added:
item.setChecked(true);
preferences.edit()
.putInt(Settings.KEY_PREF_TVSHOWS_SORT_ORDER, Settings.SORT_BY_DATE_ADDED)
.apply();
getLoaderManager().restartLoader(LOADER_TVSHOWS, null, this);
refreshList();
break;
default:
break;
@ -240,132 +262,6 @@ public class TVShowListFragment extends Fragment
return super.onOptionsItemSelected(item);
}
/**
* Search view callbacks
*/
/** {@inheritDoc} */
@Override
public boolean onQueryTextChange(String newText) {
searchFilter = newText;
getLoaderManager().restartLoader(LOADER_TVSHOWS, null, this);
return true;
}
/** {@inheritDoc} */
@Override
public boolean onQueryTextSubmit(String newText) {
// All is handled in onQueryTextChange
return true;
}
/**
* Swipe refresh layout callback
*/
/** {@inheritDoc} */
@Override
public void onRefresh() {
if (hostInfo != null) {
LogUtils.LOGD(TAG, "Starting onRefresh");
// Make sure we're showing the refresh
swipeRefreshLayout.setRefreshing(true);
// Start the syncing process
Intent syncIntent = new Intent(this.getActivity(), LibrarySyncService.class);
syncIntent.putExtra(LibrarySyncService.SYNC_ALL_TVSHOWS, true);
getActivity().startService(syncIntent);
} else {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), R.string.no_xbmc_configured, Toast.LENGTH_SHORT)
.show();
}
}
/**
* Event bus post. Called when the syncing process ended
*
* @param event Refreshes data
*/
public void onEventMainThread(MediaSyncEvent event) {
boolean silentSync = false;
if (event.syncExtras != null) {
silentSync = event.syncExtras.getBoolean(LibrarySyncService.SILENT_SYNC, false);
}
if (event.syncType.equals(LibrarySyncService.SYNC_SINGLE_TVSHOW) ||
event.syncType.equals(LibrarySyncService.SYNC_ALL_TVSHOWS)) {
swipeRefreshLayout.setRefreshing(false);
if (event.status == MediaSyncEvent.STATUS_SUCCESS) {
getLoaderManager().restartLoader(LOADER_TVSHOWS, null, this);
if (!silentSync) {
Toast.makeText(getActivity(), R.string.sync_successful, Toast.LENGTH_SHORT)
.show();
}
} else if (!silentSync) {
String msg = (event.errorCode == ApiException.API_ERROR) ?
String.format(getString(R.string.error_while_syncing), event.errorMessage) :
getString(R.string.unable_to_connect_to_xbmc);
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
}
}
/**
* Loader callbacks
*/
/** {@inheritDoc} */
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
Uri uri = MediaContract.TVShows.buildTVShowsListUri(hostInfo != null ? hostInfo.getId() : -1);
StringBuilder selection = new StringBuilder();
String selectionArgs[] = null;
if (!TextUtils.isEmpty(searchFilter)) {
selection.append(MediaContract.TVShowsColumns.TITLE + " LIKE ?");
selectionArgs = new String[] {"%" + searchFilter + "%"};
}
// Filters
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
if (preferences.getBoolean(Settings.KEY_PREF_TVSHOWS_FILTER_HIDE_WATCHED, Settings.DEFAULT_PREF_TVSHOWS_FILTER_HIDE_WATCHED)) {
if (selection.length() != 0)
selection.append(" AND ");
selection.append(MediaContract.TVShowsColumns.WATCHEDEPISODES)
.append("!=")
.append(MediaContract.TVShowsColumns.EPISODE);
}
String sortOrderStr;
int sortOrder = preferences.getInt(Settings.KEY_PREF_TVSHOWS_SORT_ORDER, Settings.DEFAULT_PREF_TVSHOWS_SORT_ORDER);
if (sortOrder == Settings.SORT_BY_DATE_ADDED) {
sortOrderStr = TVShowListQuery.SORT_BY_DATE_ADDED;
} else {
// Sort by name
if (preferences.getBoolean(Settings.KEY_PREF_TVSHOWS_IGNORE_PREFIXES, Settings.DEFAULT_PREF_TVSHOWS_IGNORE_PREFIXES)) {
sortOrderStr = TVShowListQuery.SORT_BY_NAME_IGNORE_ARTICLES;
} else {
sortOrderStr = TVShowListQuery.SORT_BY_NAME;
}
}
return new CursorLoader(getActivity(), uri,
TVShowListQuery.PROJECTION, selection.toString(),
selectionArgs, sortOrderStr);
}
/** {@inheritDoc} */
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
adapter.swapCursor(cursor);
// To prevent the empty text from appearing on the first load, set it now
emptyView.setText(getString(R.string.no_tvshows_found_refresh));
}
/** {@inheritDoc} */
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
adapter.swapCursor(null);
}
/**
* TVShow list query parameters.
*/

View File

@ -24,6 +24,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;

View File

@ -229,6 +229,7 @@
<string name="no_movies_found_refresh">No movies found\n\nSwipe down to refresh</string>
<string name="no_tvshows_found_refresh">No TV Shows found\n\nSwipe down to refresh</string>
<string name="no_episodes_found">No episodes found</string>
<string name="swipe_down_to_refresh">Swipe down to refresh</string>
<string name="no_artists_found_refresh">No artists found\n\nSwipe down to refresh</string>
<string name="no_albums_found_refresh">No albums found\n\nSwipe down to refresh</string>
<string name="no_genres_found_refresh">No genres found\n\nSwipe down to refresh</string>