diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 424bbcd..9ac1578 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -63,6 +63,7 @@ + > { + public static final String METHOD_NAME = "Favourites.GetFavourites"; + private static final String LIST_NODE = "favourites"; + + /** + * Default ctor, gets all the properties by default. + */ + public GetFavourites() { + addParameterToRequest("properties", new String[]{ + FavouriteType.DetailsFavourite.WINDOW, FavouriteType.DetailsFavourite.WINDOW_PARAMETER, + FavouriteType.DetailsFavourite.THUMBNAIL, FavouriteType.DetailsFavourite.PATH + }); + } + + @Override + public String getMethodName() { + return METHOD_NAME; + } + + @Override + public ApiList resultFromJson(ObjectNode jsonObject) throws ApiException { + ListType.LimitsReturned limits = new ListType.LimitsReturned(jsonObject); + + JsonNode resultNode = jsonObject.get(RESULT_NODE); + ArrayNode items = resultNode.has(LIST_NODE) ? + (ArrayNode) resultNode.get(LIST_NODE) : null; + if (items == null) { + return new ApiList<>(Collections.emptyList(), limits); + } + ArrayList result = new ArrayList<>(items.size()); + for (JsonNode item : items) { + result.add(new FavouriteType.DetailsFavourite(item)); + } + return new ApiList<>(result, limits); + } + } +} diff --git a/app/src/main/java/org/xbmc/kore/jsonrpc/method/Player.java b/app/src/main/java/org/xbmc/kore/jsonrpc/method/Player.java index f98aafc..62a8459 100644 --- a/app/src/main/java/org/xbmc/kore/jsonrpc/method/Player.java +++ b/app/src/main/java/org/xbmc/kore/jsonrpc/method/Player.java @@ -18,6 +18,7 @@ package org.xbmc.kore.jsonrpc.method; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; + import org.xbmc.kore.jsonrpc.ApiException; import org.xbmc.kore.jsonrpc.ApiMethod; import org.xbmc.kore.jsonrpc.type.ListType; diff --git a/app/src/main/java/org/xbmc/kore/jsonrpc/type/FavouriteType.java b/app/src/main/java/org/xbmc/kore/jsonrpc/type/FavouriteType.java new file mode 100644 index 0000000..1714b1d --- /dev/null +++ b/app/src/main/java/org/xbmc/kore/jsonrpc/type/FavouriteType.java @@ -0,0 +1,64 @@ +/* + * Copyright 2017 XBMC Foundation. 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.jsonrpc.type; + +import com.fasterxml.jackson.databind.JsonNode; + +import org.xbmc.kore.utils.JsonUtils; + +/** + * Types from Favourite.* + */ +public class FavouriteType { + + /** + * Favourite.Type + */ + public interface FavouriteTypeEnum { + String MEDIA = "media"; + String WINDOW = "window"; + String SCRIPT = "script"; + String UNKNOWN = "unknown"; + } + + /** + * Favourite.Details.Favourite + */ + public static class DetailsFavourite { + public static final String PATH = "path"; + public static final String THUMBNAIL = "thumbnail"; + public static final String TITLE = "title"; + public static final String TYPE = "type"; + public static final String WINDOW = "window"; + public static final String WINDOW_PARAMETER = "windowparameter"; + + public final String thumbnail; + public final String path; + public final String title; + public final String type; + public final String window; + public final String windowParameter; + + public DetailsFavourite(JsonNode node) { + thumbnail = JsonUtils.stringFromJsonNode(node, THUMBNAIL); + path = JsonUtils.stringFromJsonNode(node, PATH); + title = JsonUtils.stringFromJsonNode(node, TITLE); + type = JsonUtils.stringFromJsonNode(node, TYPE, FavouriteTypeEnum.MEDIA); + window = JsonUtils.stringFromJsonNode(node, WINDOW); + windowParameter = JsonUtils.stringFromJsonNode(node, WINDOW_PARAMETER); + } + } +} diff --git a/app/src/main/java/org/xbmc/kore/ui/generic/NavigationDrawerFragment.java b/app/src/main/java/org/xbmc/kore/ui/generic/NavigationDrawerFragment.java index aeadfc3..d6a1028 100644 --- a/app/src/main/java/org/xbmc/kore/ui/generic/NavigationDrawerFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/generic/NavigationDrawerFragment.java @@ -47,6 +47,7 @@ import org.xbmc.kore.host.HostInfo; import org.xbmc.kore.host.HostManager; import org.xbmc.kore.ui.sections.addon.AddonsActivity; import org.xbmc.kore.ui.sections.audio.MusicActivity; +import org.xbmc.kore.ui.sections.favourites.FavouritesActivity; import org.xbmc.kore.ui.sections.file.FileActivity; import org.xbmc.kore.ui.sections.hosts.HostManagerActivity; import org.xbmc.kore.ui.sections.remote.RemoteActivity; @@ -83,7 +84,9 @@ public class NavigationDrawerFragment extends Fragment { ACTIVITY_PVR = 5, ACTIVITY_FILES = 6, ACTIVITY_ADDONS = 7, - ACTIVITY_SETTINGS = 8; + ACTIVITY_SETTINGS = 8, + ACTIVITY_FAVOURITES = 9; + // The current selected item id (based on the activity) private static int selectedItemId = -1; @@ -146,6 +149,7 @@ public class NavigationDrawerFragment extends Fragment { R.attr.iconFiles, R.attr.iconAddons, R.attr.iconSettings, + R.attr.iconFavourites }); HostInfo hostInfo = HostManager.getInstance(getActivity()).getHostInfo(); @@ -179,6 +183,10 @@ public class NavigationDrawerFragment extends Fragment { items.add(new DrawerItem(DrawerItem.TYPE_NORMAL_ITEM, ACTIVITY_PVR, getString(R.string.pvr), styledAttributes.getResourceId(styledAttributes.getIndex(ACTIVITY_PVR), 0))); + if (shownItems.contains(String.valueOf(ACTIVITY_FAVOURITES))) + items.add(new DrawerItem(DrawerItem.TYPE_NORMAL_ITEM, ACTIVITY_FAVOURITES, + getString(R.string.favourites), + styledAttributes.getResourceId(styledAttributes.getIndex(ACTIVITY_FAVOURITES), 0))); if (shownItems.contains(String.valueOf(ACTIVITY_FILES))) items.add(new DrawerItem(DrawerItem.TYPE_NORMAL_ITEM, ACTIVITY_FILES, getString(R.string.files), @@ -344,6 +352,8 @@ public class NavigationDrawerFragment extends Fragment { return ACTIVITY_MUSIC; else if (activity instanceof PVRActivity) return ACTIVITY_PVR; + else if (activity instanceof FavouritesActivity) + return ACTIVITY_FAVOURITES; else if (activity instanceof FileActivity) return ACTIVITY_FILES; else if (activity instanceof AddonsActivity) @@ -364,6 +374,7 @@ public class NavigationDrawerFragment extends Fragment { activityItemIdMap.put(ACTIVITY_REMOTE, RemoteActivity.class); activityItemIdMap.put(ACTIVITY_MOVIES, MoviesActivity.class); activityItemIdMap.put(ACTIVITY_MUSIC, MusicActivity.class); + activityItemIdMap.put(ACTIVITY_FAVOURITES, FavouritesActivity.class); activityItemIdMap.put(ACTIVITY_FILES, FileActivity.class); activityItemIdMap.put(ACTIVITY_TVSHOWS, TVShowsActivity.class); activityItemIdMap.put(ACTIVITY_PVR, PVRActivity.class); diff --git a/app/src/main/java/org/xbmc/kore/ui/sections/favourites/FavouritesActivity.java b/app/src/main/java/org/xbmc/kore/ui/sections/favourites/FavouritesActivity.java new file mode 100644 index 0000000..717f728 --- /dev/null +++ b/app/src/main/java/org/xbmc/kore/ui/sections/favourites/FavouritesActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017 XBMC Foundation. 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.sections.favourites; + +import android.support.v4.app.Fragment; + +import org.xbmc.kore.R; +import org.xbmc.kore.ui.BaseMediaActivity; + +public class FavouritesActivity extends BaseMediaActivity { + + @Override + protected String getActionBarTitle() { + return getString(R.string.favourites); + } + + @Override + protected Fragment createFragment() { + return new FavouritesListFragment(); + } +} diff --git a/app/src/main/java/org/xbmc/kore/ui/sections/favourites/FavouritesListFragment.java b/app/src/main/java/org/xbmc/kore/ui/sections/favourites/FavouritesListFragment.java new file mode 100644 index 0000000..4d2362c --- /dev/null +++ b/app/src/main/java/org/xbmc/kore/ui/sections/favourites/FavouritesListFragment.java @@ -0,0 +1,228 @@ +/* + * Copyright 2017 XBMC Foundation. 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.sections.favourites; + +import android.content.Context; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.widget.SwipeRefreshLayout; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import org.xbmc.kore.R; +import org.xbmc.kore.host.HostManager; +import org.xbmc.kore.jsonrpc.ApiCallback; +import org.xbmc.kore.jsonrpc.ApiList; +import org.xbmc.kore.jsonrpc.method.Favourites; +import org.xbmc.kore.jsonrpc.method.GUI; +import org.xbmc.kore.jsonrpc.type.FavouriteType; +import org.xbmc.kore.jsonrpc.type.PlaylistType; +import org.xbmc.kore.ui.AbstractListFragment; +import org.xbmc.kore.utils.LogUtils; +import org.xbmc.kore.utils.MediaPlayerUtils; +import org.xbmc.kore.utils.UIUtils; + +import java.util.List; + +import butterknife.ButterKnife; + +public class FavouritesListFragment extends AbstractListFragment implements SwipeRefreshLayout.OnRefreshListener { + private static final String TAG = "FavouritesListFragment"; + + private Handler callbackHandler = new Handler(); + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + getFavourites(); + } + + @Override + protected AdapterView.OnItemClickListener createOnItemClickListener() { + final ApiCallback genericApiCallback = new ApiCallback() { + @Override + public void onSuccess(String result) { + // Do Nothing + } + + @Override + public void onError(int errorCode, String description) { + Toast.makeText(getActivity(), description, Toast.LENGTH_SHORT).show(); + } + }; + return new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + final FavouritesAdapter favouritesAdapter = (FavouritesAdapter) getAdapter(); + final HostManager hostManager = HostManager.getInstance(getActivity()); + + final FavouriteType.DetailsFavourite detailsFavourite = + favouritesAdapter.getItem(position); + if (detailsFavourite == null) { + return; + } + if (detailsFavourite.type.equals(FavouriteType.FavouriteTypeEnum.WINDOW) + && !TextUtils.isEmpty(detailsFavourite.window)) { + GUI.ActivateWindow activateWindow = new GUI.ActivateWindow(detailsFavourite.window, + detailsFavourite.windowParameter); + hostManager.getConnection().execute(activateWindow, genericApiCallback, callbackHandler); + } else if (detailsFavourite.type.equals(FavouriteType.FavouriteTypeEnum.MEDIA) + && !TextUtils.isEmpty(detailsFavourite.path)) { + final PlaylistType.Item playlistItem = new PlaylistType.Item(); + playlistItem.file = detailsFavourite.path; + MediaPlayerUtils.play(FavouritesListFragment.this, playlistItem); + } else { + Toast.makeText(getActivity(), R.string.unable_to_play_favourite_item, + Toast.LENGTH_SHORT).show(); + } + } + }; + } + + @Override + protected BaseAdapter createAdapter() { + return new FavouritesAdapter(getActivity(), HostManager.getInstance(getActivity())); + } + + @Override + public void onRefresh() { + getFavourites(); + } + + private void getFavourites() { + final HostManager hostManager = HostManager.getInstance(getActivity()); + final Favourites.GetFavourites action = new Favourites.GetFavourites(); + + hostManager.getConnection().execute(action, new ApiCallback>() { + @Override + public void onSuccess(ApiList result) { + if (!isAdded()) return; + LogUtils.LOGD(TAG, "Got Favourites"); + + // To prevent the empty text from appearing on the first load, set it now + getEmptyView().setText(getString(R.string.no_channels_found_refresh)); + setupFavouritesList(result.items); + hideRefreshAnimation(); + } + + @Override + public void onError(int errorCode, String description) { + if (!isAdded()) return; + LogUtils.LOGD(TAG, "Error getting favourites: " + description); + + getEmptyView().setText(getString(R.string.error_favourites, description)); + Toast.makeText(getActivity(), getString(R.string.error_favourites, description), + Toast.LENGTH_SHORT).show(); + hideRefreshAnimation(); + } + }, callbackHandler); + } + + /** + * Called to set the GridView with the favourites that are coming from the host. + * + * @param favourites the favourites list that is supplied to the GridView. + */ + private void setupFavouritesList(List favourites) { + final FavouritesAdapter favouritesAdapter = (FavouritesAdapter) getAdapter(); + favouritesAdapter.clear(); + favouritesAdapter.addAll(favourites); + favouritesAdapter.notifyDataSetChanged(); + } + + private static class FavouritesAdapter extends ArrayAdapter { + + private final HostManager hostManager; + private final int artWidth, artHeight; + + FavouritesAdapter(@NonNull Context context, HostManager hostManager) { + super(context, R.layout.grid_item_channel); + this.hostManager = hostManager; + Resources resources = context.getResources(); + artWidth = (int) (resources.getDimension(R.dimen.channellist_art_width) / + UIUtils.IMAGE_RESIZE_FACTOR); + artHeight = (int) (resources.getDimension(R.dimen.channellist_art_heigth) / + UIUtils.IMAGE_RESIZE_FACTOR); + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(getContext()).inflate(R.layout.grid_item_channel, + parent, false); + final FavouriteItemViewHolder vh = new FavouriteItemViewHolder(convertView); + convertView.setTag(vh); + } + + final FavouriteItemViewHolder vh = (FavouriteItemViewHolder) convertView.getTag(); + final FavouriteType.DetailsFavourite favouriteDetail = getItem(position); + + // We don't need the context menu here. + vh.contextMenu.setVisibility(View.GONE); + + vh.titleView.setText(favouriteDetail.title); + + @StringRes final int typeRes; + switch (favouriteDetail.type) { + case FavouriteType.FavouriteTypeEnum.MEDIA: + typeRes = R.string.media; + break; + case FavouriteType.FavouriteTypeEnum.SCRIPT: + typeRes = R.string.script; + break; + case FavouriteType.FavouriteTypeEnum.WINDOW: + typeRes = R.string.window; + break; + default: + typeRes = R.string.unknown; + } + vh.detailView.setText(typeRes); + + UIUtils.loadImageWithCharacterAvatar(getContext(), hostManager, + favouriteDetail.thumbnail, favouriteDetail.title, + vh.artView, artWidth, artHeight); + + return convertView; + } + } + + private static class FavouriteItemViewHolder { + final ImageView artView; + final ImageView contextMenu; + final TextView titleView; + final TextView detailView; + + FavouriteItemViewHolder(View v) { + artView = ButterKnife.findById(v, R.id.art); + contextMenu = ButterKnife.findById(v, R.id.list_context_menu); + titleView = ButterKnife.findById(v, R.id.title); + detailView = ButterKnife.findById(v, R.id.details); + } + } +} diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index ec36c3f..9e03bad 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -62,6 +62,7 @@ @string/tv_shows @string/music @string/pvr + @string/favourites @string/files @string/addons @@ -110,6 +111,7 @@ 3 4 5 + 9 6 7 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 697e704..2cc4d97 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -36,6 +36,10 @@ Files Video Videos + Media + Window + Script + Unknown File Browser PVR Favourites @@ -248,6 +252,7 @@ No videos found\n\nSwipe down to refresh No channel groups found.\n\nSwipe down to refresh No channels found.\n\nSwipe down to refresh + No favourites found.\n\nSwipe down to refresh No recordings found.\n\nSwipe down to refresh No broadcasts found.\n\nSwipe down to refresh Pull to refresh @@ -373,6 +378,7 @@ An error occurred while getting pvr info: %1$s An error occurred while getting channels info, probably because your media center doesn\'t have a tuner or it isn\'t configured.\n\nIf that\'s the case and you\'d like to remove this entry from the side menu, you can do it in the Settings. An error occurred starting channel playback: %1$s + An error occurred while getting the favourites with description: %1$s An error occurred starting a recording: %1$s An error occurred while recording: %1$s Switching to channel %1$s @@ -388,6 +394,7 @@ Record Guide Unable to move item + Unable to play favourite item. Unsupported Type Cannot move currently playing/paused item No connection type selected in settings Single column