diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0fe16d9..68375ca 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -53,6 +53,7 @@ + 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 b3e804f..0352fec 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 @@ -433,13 +433,17 @@ public class Player { public static final class Open extends ApiMethod { public final static String METHOD_NAME = "Player.Open"; + public final static String TYPE_PLAYLIST = "playlist", + TYPE_CHANNEL = "channel"; + /** * Start playback of either the playlist with the given ID, a slideshow with the pictures * from the given directory or a single file or an item from the database. - * @param playlistId - * @param position + * @param itemType This should always be TYPE_PLAYLIST + * @param playlistId Id + * @param position Position to start */ - public Open(int playlistId, int position) { + public Open(String itemType, int playlistId, int position) { super(); final ObjectNode item = objectMapper.createObjectNode(); item.put("playlistid", playlistId); @@ -458,14 +462,22 @@ public class Player { } /** - * Select the active player - * @param playlistId playlist ID to select + * Starts playing a playlist or channel + * @param itemType TYPE_PLAYLIST or TYPE_CHANNEL + * @param itemId Corresponding ID to open */ - public Open(int playlistId) { + public Open(String itemType, int itemId) { super(); final ObjectNode item = objectMapper.createObjectNode(); - item.put("playlistid", playlistId); - addParameterToRequest("item", item); + switch (itemType) { + case TYPE_PLAYLIST: + item.put("playlistid", itemId); + addParameterToRequest("item", item); + break; + case TYPE_CHANNEL: + item.put("channelid", itemId); + addParameterToRequest("item", item); + } } @Override diff --git a/app/src/main/java/org/xbmc/kore/ui/MediaFileListFragment.java b/app/src/main/java/org/xbmc/kore/ui/MediaFileListFragment.java index f94ccf9..414a18f 100644 --- a/app/src/main/java/org/xbmc/kore/ui/MediaFileListFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/MediaFileListFragment.java @@ -402,7 +402,7 @@ public class MediaFileListFragment extends Fragment { public void onSuccess(ArrayList result ) { // find out if any player is running. If it is not, start one if (result.isEmpty()) { - Player.Open action = new Player.Open(playlistId); + Player.Open action = new Player.Open(Player.Open.TYPE_PLAYLIST, playlistId); action.execute(connection, new ApiCallback() { @Override public void onSuccess(String result) { } diff --git a/app/src/main/java/org/xbmc/kore/ui/NavigationDrawerFragment.java b/app/src/main/java/org/xbmc/kore/ui/NavigationDrawerFragment.java index d765779..f5518af 100644 --- a/app/src/main/java/org/xbmc/kore/ui/NavigationDrawerFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/NavigationDrawerFragment.java @@ -29,7 +29,6 @@ import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.util.SparseArray; -import android.view.Gravity; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -65,9 +64,10 @@ public class NavigationDrawerFragment extends Fragment { ACTIVITY_MOVIES = 2, ACTIVITY_TVSHOWS = 3, ACTIVITY_MUSIC = 4, - ACTIVITY_FILES = 5, - ACTIVITY_ADDONS = 6, - ACTIVITY_SETTINGS = 7; + ACTIVITY_PVR = 5, + ACTIVITY_FILES = 6, + ACTIVITY_ADDONS = 7, + ACTIVITY_SETTINGS = 8; // The current selected item id (based on the activity) private static int selectedItemId = -1; @@ -126,6 +126,7 @@ public class NavigationDrawerFragment extends Fragment { R.attr.iconMovies, R.attr.iconTvShows, R.attr.iconMusic, + R.attr.iconPVR, R.attr.iconFiles, R.attr.iconAddons, R.attr.iconSettings, @@ -149,6 +150,9 @@ public class NavigationDrawerFragment extends Fragment { new DrawerItem(DrawerItem.TYPE_NORMAL_ITEM, ACTIVITY_MUSIC, getString(R.string.music), styledAttributes.getResourceId(ACTIVITY_MUSIC, 0)), + new DrawerItem(DrawerItem.TYPE_NORMAL_ITEM, ACTIVITY_PVR, + getString(R.string.tv_radio), + styledAttributes.getResourceId(ACTIVITY_PVR, 0)), new DrawerItem(DrawerItem.TYPE_NORMAL_ITEM, ACTIVITY_FILES, getString(R.string.files), styledAttributes.getResourceId(ACTIVITY_FILES, 0)), @@ -274,6 +278,8 @@ public class NavigationDrawerFragment extends Fragment { return ACTIVITY_TVSHOWS; else if (activity instanceof MusicActivity) return ACTIVITY_MUSIC; + else if (activity instanceof PVRActivity) + return ACTIVITY_PVR; else if (activity instanceof FileActivity) return ACTIVITY_FILES; else if (activity instanceof AddonsActivity) @@ -289,13 +295,14 @@ public class NavigationDrawerFragment extends Fragment { */ private static final SparseArray activityItemIdMap; static { - activityItemIdMap = new SparseArray(10); + activityItemIdMap = new SparseArray<>(10); activityItemIdMap.put(ACTIVITY_HOSTS, HostManagerActivity.class); activityItemIdMap.put(ACTIVITY_REMOTE, RemoteActivity.class); activityItemIdMap.put(ACTIVITY_MOVIES, MoviesActivity.class); activityItemIdMap.put(ACTIVITY_MUSIC, MusicActivity.class); activityItemIdMap.put(ACTIVITY_FILES, FileActivity.class); activityItemIdMap.put(ACTIVITY_TVSHOWS, TVShowsActivity.class); + activityItemIdMap.put(ACTIVITY_PVR, PVRActivity.class); activityItemIdMap.put(ACTIVITY_ADDONS, AddonsActivity.class); activityItemIdMap.put(ACTIVITY_SETTINGS, SettingsActivity.class); @@ -339,11 +346,8 @@ public class NavigationDrawerFragment extends Fragment { @Override public boolean onOptionsItemSelected(MenuItem item) { - if (mDrawerToggle.onOptionsItemSelected(item)) { - return true; - } + return mDrawerToggle.onOptionsItemSelected(item) || super.onOptionsItemSelected(item); - return super.onOptionsItemSelected(item); } /** @@ -396,10 +400,8 @@ public class NavigationDrawerFragment extends Fragment { R.attr.textColorOverPrimary }); Resources resources = context.getResources(); - selectedItemColor = styledAttributes.getColor(0, - resources.getColor(R.color.accent_default)); - hostItemColor = styledAttributes.getColor(1, - resources.getColor(R.color.white)); + selectedItemColor = styledAttributes.getColor(0, resources.getColor(R.color.accent_default)); + hostItemColor = styledAttributes.getColor(1, resources.getColor(R.color.white)); styledAttributes.recycle(); } diff --git a/app/src/main/java/org/xbmc/kore/ui/NowPlayingFragment.java b/app/src/main/java/org/xbmc/kore/ui/NowPlayingFragment.java index 9a9a3eb..4aa8a3b 100644 --- a/app/src/main/java/org/xbmc/kore/ui/NowPlayingFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/NowPlayingFragment.java @@ -708,6 +708,21 @@ public class NowPlayingFragment extends Fragment maxRating = null; votes = null; break; + case ListType.ItemsAll.TYPE_CHANNEL: + switchToPanel(R.id.media_panel); + + title = getItemResult.label; + underTitle = getItemResult.title; + art = getItemResult.fanart; + poster = getItemResult.thumbnail; + + genreSeason = Utils.listStringConcat(getItemResult.genre, ", "); + year = getItemResult.premiered; + descriptionPlot = getItemResult.plot; + rating = getItemResult.rating; + maxRating = null; + votes = null; + break; default: // Other type, just present basic info switchToPanel(R.id.media_panel); @@ -729,7 +744,7 @@ public class NowPlayingFragment extends Fragment mediaTitle.setText(title); mediaUndertitle.setText(underTitle); - setDurationInfo(getPropertiesResult.time, getPropertiesResult.totaltime, getPropertiesResult.speed); + setDurationInfo(getItemResult.type, getPropertiesResult.time, getPropertiesResult.totaltime, getPropertiesResult.speed); mediaSeekbar.setOnSeekBarChangeListener(seekbarChangeListener); if (!TextUtils.isEmpty(year) || !TextUtils.isEmpty(genreSeason)) { @@ -822,7 +837,7 @@ public class NowPlayingFragment extends Fragment } else { // No fanart, just present the poster mediaPoster.setVisibility(View.GONE); - UIUtils.loadImageIntoImageview(hostManager, poster, mediaArt, artWidth, artHeight); + UIUtils.loadImageWithCharacterAvatar(getActivity(), hostManager, poster, title, mediaArt, artWidth, artHeight); // Reset padding int paddingLeft = mediaTitle.getPaddingRight(), paddingRight = mediaTitle.getPaddingRight(), @@ -932,11 +947,12 @@ public class NowPlayingFragment extends Fragment /** * Sets the information about current media duration and sets seekbar + * @param type What is playing * @param time Current time * @param totalTime Total time * @param speed Media speed */ - private void setDurationInfo(GlobalType.Time time, GlobalType.Time totalTime, int speed) { + private void setDurationInfo(String type, GlobalType.Time time, GlobalType.Time totalTime, int speed) { mediaTotalTime = totalTime.hours * 3600 + totalTime.minutes * 60 + totalTime.seconds; @@ -951,7 +967,7 @@ public class NowPlayingFragment extends Fragment // Only update when its playing mediaSeekbar.removeCallbacks(seekBarUpdater); - if (speed == 1) { + if ((speed == 1) || (type.equals(ListType.ItemsAll.TYPE_CHANNEL))) { mediaSeekbar.postDelayed(seekBarUpdater, SEEK_BAR_UPDATE_INTERVAL); } } diff --git a/app/src/main/java/org/xbmc/kore/ui/PVRActivity.java b/app/src/main/java/org/xbmc/kore/ui/PVRActivity.java new file mode 100644 index 0000000..9d5a303 --- /dev/null +++ b/app/src/main/java/org/xbmc/kore/ui/PVRActivity.java @@ -0,0 +1,235 @@ +/* + * Copyright 2015 Synced Synapse. 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.annotation.TargetApi; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBar; +import android.support.v7.widget.Toolbar; +import android.transition.TransitionInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.Window; + +import org.xbmc.kore.R; +import org.xbmc.kore.utils.LogUtils; +import org.xbmc.kore.utils.Utils; + +/** + * Controls the presentation of Live TV/Radio information (list, details) + * All the information is presented by specific fragments + */ +public class PVRActivity extends BaseActivity + implements PVRListFragment.OnPVRSelectedListener { + private static final String TAG = LogUtils.makeLogTag(PVRActivity.class); + + public static final String CHANNELGROUPID = "channel_group_id"; + public static final String CHANNELGROUPTITLE = "channel_group_title"; + + public static final String CHANNELID = "channel_id"; + public static final String CHANNELTITLE = "channel_title"; + + private int selectedChannelGroupId = -1; + private String selectedChannelGroupTitle = null; + + private int selectedChannelId = -1; + private String selectedChannelTitle = null; + + private NavigationDrawerFragment navigationDrawerFragment; + + @TargetApi(21) + @Override + protected void onCreate(Bundle savedInstanceState) { + // Request transitions on lollipop + if (Utils.isLollipopOrLater()) { + getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); + } + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_generic_media); + + // Set up the drawer. + navigationDrawerFragment = (NavigationDrawerFragment)getSupportFragmentManager() + .findFragmentById(R.id.navigation_drawer); + navigationDrawerFragment.setUp(R.id.navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout)); + + if (savedInstanceState == null) { + PVRListFragment pvrListFragment = new PVRListFragment(); + + // Setup animations + if (Utils.isLollipopOrLater()) { + pvrListFragment.setExitTransition(null); + pvrListFragment.setReenterTransition(TransitionInflater + .from(this) + .inflateTransition(android.R.transition.fade)); + } + getSupportFragmentManager() + .beginTransaction() + .add(R.id.fragment_container, pvrListFragment) + .commit(); + } else { + selectedChannelGroupId = savedInstanceState.getInt(CHANNELGROUPID, -1); + selectedChannelGroupTitle = savedInstanceState.getString(CHANNELGROUPTITLE, null); + + selectedChannelId = savedInstanceState.getInt(CHANNELID, -1); + selectedChannelTitle = savedInstanceState.getString(CHANNELTITLE, null); + } + + setupActionBar(selectedChannelGroupTitle); + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + } + + @Override + protected void onSaveInstanceState (Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(CHANNELGROUPID, selectedChannelGroupId); + outState.putString(CHANNELGROUPTITLE, selectedChannelGroupTitle); + + outState.putInt(CHANNELID, selectedChannelId); + outState.putString(CHANNELTITLE, selectedChannelTitle); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { +// if (!navigationDrawerFragment.isDrawerOpen()) { +// getMenuInflater().inflate(R.menu.media_info, menu); +// } + getMenuInflater().inflate(R.menu.media_info, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_show_remote: + // Starts remote + Intent launchIntent = new Intent(this, RemoteActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + startActivity(launchIntent); + return true; + case android.R.id.home: + // If showing detail view, back up to list + if (selectedChannelId != -1) { + selectedChannelId = -1; + selectedChannelTitle = null; + setupActionBar(null); + getSupportFragmentManager().popBackStack(); + return true; + } else if (selectedChannelGroupId != -1) { + onBackPressed(); + return true; + } + break; + default: + break; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void onBackPressed() { + // If we are showing details in portrait, clear selected and show action bar + if (selectedChannelId != -1) { + selectedChannelId = -1; + selectedChannelTitle = null; + setupActionBar(null); + } else if (selectedChannelGroupId != -1) { + selectedChannelGroupId = -1; + selectedChannelGroupTitle = null; + setupActionBar(null); + Fragment listFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container); + if ((listFragment != null) && + (listFragment instanceof PVRListFragment)) { + ((PVRListFragment)listFragment).onBackPressed(); + } + return; + } + super.onBackPressed(); + } + + private void setupActionBar(String channelTitle) { + Toolbar toolbar = (Toolbar)findViewById(R.id.default_toolbar); + setSupportActionBar(toolbar); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar == null) return; + actionBar.setDisplayHomeAsUpEnabled(true); + if (channelTitle != null) { + navigationDrawerFragment.setDrawerIndicatorEnabled(false); + actionBar.setTitle(channelTitle); + } else { + navigationDrawerFragment.setDrawerIndicatorEnabled(true); + actionBar.setTitle(R.string.tv_radio); + } + } + + /** + * Callback from list fragment when a channel group is selected. + * Setup action bar + * @param channelGroupId Channel group selected + * @param channelGroupTitle Title + */ + public void onChannelGroupSelected(int channelGroupId, String channelGroupTitle) { + selectedChannelGroupId = channelGroupId; + selectedChannelGroupTitle = channelGroupTitle; + + setupActionBar(selectedChannelGroupTitle); + } + + /** + * Callback from list fragment when the channel guide should be displayed. + * Setup action bar and repolace list fragment + * @param channelId Channel selected + * @param channelTitle Title + */ + @TargetApi(21) + public void onChannelGuideSelected(int channelId, String channelTitle) { + selectedChannelId = channelId; + selectedChannelTitle = channelTitle; + +// // Replace list fragment +// PVRDetailsFragment pvrDetailsFragment = PVRDetailsFragment.newInstance(channelId); +// FragmentTransaction fragTrans = getSupportFragmentManager().beginTransaction(); +// +// // Set up transitions +// if (Utils.isLollipopOrLater()) { +// pvrDetailsFragment.setEnterTransition(TransitionInflater.from(this) +// .inflateTransition(R.transition.media_details)); +// pvrDetailsFragment.setReturnTransition(null); +// } else { +// fragTrans.setCustomAnimations(R.anim.fragment_details_enter, 0, +// R.anim.fragment_list_popenter, 0); +// } +// +// fragTrans.replace(R.id.fragment_container, pvrDetailsFragment) +// .addToBackStack(null) +// .commit(); + setupActionBar(selectedChannelTitle); + } +} diff --git a/app/src/main/java/org/xbmc/kore/ui/PVRListFragment.java b/app/src/main/java/org/xbmc/kore/ui/PVRListFragment.java new file mode 100644 index 0000000..11a32b0 --- /dev/null +++ b/app/src/main/java/org/xbmc/kore/ui/PVRListFragment.java @@ -0,0 +1,415 @@ +/* + * Copyright 2015 Synced Synapse. 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.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.Handler; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +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.HostManager; +import org.xbmc.kore.jsonrpc.ApiCallback; +import org.xbmc.kore.jsonrpc.method.PVR; +import org.xbmc.kore.jsonrpc.method.Player; +import org.xbmc.kore.jsonrpc.type.PVRType; +import org.xbmc.kore.jsonrpc.type.PlaylistType; +import org.xbmc.kore.utils.LogUtils; +import org.xbmc.kore.utils.UIUtils; + +import java.util.List; + +import butterknife.ButterKnife; +import butterknife.InjectView; + +/** + * Fragment that presents the movie list + */ +public class PVRListFragment extends Fragment + implements SwipeRefreshLayout.OnRefreshListener { + private static final String TAG = LogUtils.makeLogTag(PVRListFragment.class); + + public interface OnPVRSelectedListener { + public void onChannelGroupSelected(int channelGroupId, String channelGroupTitle); + public void onChannelGuideSelected(int channelId, String channelTitle); + } + + // Activity listener + private OnPVRSelectedListener listenerActivity; + + private HostManager hostManager; + + @InjectView(R.id.list) GridView gridView; + @InjectView(R.id.swipe_refresh_layout) SwipeRefreshLayout swipeRefreshLayout; + @InjectView(android.R.id.empty) TextView emptyView; + + /** + * Handler on which to post RPC callbacks + */ + private Handler callbackHandler = new Handler(); + + private ChannelGroupAdapter channelGroupAdapter = null; + private ChannelAdapter channelAdapter = null; + + private int selectedChannelGroupId = -1; + + @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); + + hostManager = HostManager.getInstance(getActivity()); + + swipeRefreshLayout.setOnRefreshListener(this); + + emptyView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onRefresh(); + } + }); + gridView.setEmptyView(emptyView); + + return root; + } + + + @Override + public void onActivityCreated (Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(false); + + if ((channelGroupAdapter == null) || + (channelGroupAdapter.getCount() == 0)) + browseChannelGroups(); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + listenerActivity = (OnPVRSelectedListener) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement OnPVRSelectedListener"); + } + } + + @Override + public void onDetach() { + super.onDetach(); + listenerActivity = null; + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + } + + /** + * Swipe refresh layout callback + */ + /** {@inheritDoc} */ + @Override + public void onRefresh () { + if (hostManager.getHostInfo() != null) { + if (selectedChannelGroupId == -1) { + browseChannelGroups(); + } else { + browseChannels(selectedChannelGroupId); + } + } else { + swipeRefreshLayout.setRefreshing(false); + Toast.makeText(getActivity(), R.string.no_xbmc_configured, Toast.LENGTH_SHORT) + .show(); + } + } + + /** + * Called by the enclosing activity + */ + public void onBackPressed() { + selectedChannelGroupId = -1; + browseChannelGroups(); + } + + /** + * Get the channel groups list and setup the gridview + */ + private void browseChannelGroups() { + // TODO: Make the channel type selectable + LogUtils.LOGD(TAG, "Getting channel groups"); + PVR.GetChannelGroups action = new PVR.GetChannelGroups(PVRType.ChannelType.TV); + action.execute(hostManager.getConnection(), new ApiCallback>() { + @Override + public void onSuccess(List result) { + if (!isAdded()) return; + LogUtils.LOGD(TAG, "Got channel groups"); + + // To prevent the empty text from appearing on the first load, set it now + emptyView.setText(getString(R.string.no_channel_groups_found_refresh)); + setupChannelGroupsGridview(result); + swipeRefreshLayout.setRefreshing(false); + } + + @Override + public void onError(int errorCode, String description) { + if (!isAdded()) return; + LogUtils.LOGD(TAG, "Error getting channel groups: " + description); + + // To prevent the empty text from appearing on the first load, set it now + emptyView.setText(String.format(getString(R.string.error_getting_pvr_info), description)); + Toast.makeText(getActivity(), + String.format(getString(R.string.error_getting_pvr_info), description), + Toast.LENGTH_SHORT).show(); + swipeRefreshLayout.setRefreshing(false); + } + }, callbackHandler); + } + + /** + * Called when we get the channel groups + * + * @param result ChannelGroups obtained + */ + private void setupChannelGroupsGridview(List result) { + if (channelGroupAdapter == null) { + channelGroupAdapter = new ChannelGroupAdapter(getActivity(), R.layout.grid_item_channel_group); + } + gridView.setAdapter(channelGroupAdapter); + gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + // Get the id from the tag + ChannelGroupViewHolder tag = (ChannelGroupViewHolder) view.getTag(); + selectedChannelGroupId = tag.channelGroupId; + // Notify the activity and show the channels + listenerActivity.onChannelGroupSelected(tag.channelGroupId, tag.channelGroupName); + browseChannels(tag.channelGroupId); + } + }); + + channelGroupAdapter.clear(); + channelGroupAdapter.addAll(result); + channelGroupAdapter.notifyDataSetChanged(); + } + + /** + * Gets and displays the channels of a channelgroup + * @param channelGroupId id + */ + private void browseChannels(int channelGroupId) { + String[] properties = PVRType.FieldsChannel.allValues; + LogUtils.LOGD(TAG, "Getting channels"); + + PVR.GetChannels action = new PVR.GetChannels(channelGroupId, properties); + action.execute(hostManager.getConnection(), new ApiCallback>() { + @Override + public void onSuccess(List result) { + if (!isAdded()) return; + LogUtils.LOGD(TAG, "Got channels"); + + // To prevent the empty text from appearing on the first load, set it now + emptyView.setText(getString(R.string.no_channels_found_refresh)); + setupChannelsGridview(result); + swipeRefreshLayout.setRefreshing(false); + } + + @Override + public void onError(int errorCode, String description) { + if (!isAdded()) return; + LogUtils.LOGD(TAG, "Error getting channels: " + description); + + // To prevent the empty text from appearing on the first load, set it now + emptyView.setText(String.format(getString(R.string.error_getting_pvr_info), description)); + Toast.makeText(getActivity(), + String.format(getString(R.string.error_getting_pvr_info), description), + Toast.LENGTH_SHORT).show(); + swipeRefreshLayout.setRefreshing(false); + } + }, callbackHandler); + + } + + /** + * Called when we get the channels + * + * @param result Channels obtained + */ + private void setupChannelsGridview(List result) { + if (channelAdapter == null) { + channelAdapter = new ChannelAdapter(getActivity(), R.layout.grid_item_channel); + } + gridView.setAdapter(channelAdapter); + gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + // Get the id from the tag + ChannelViewHolder tag = (ChannelViewHolder) view.getTag(); + + // Start the channel + Toast.makeText(getActivity(), + String.format(getString(R.string.channel_switching), tag.channelName), + Toast.LENGTH_SHORT).show(); + Player.Open action = new Player.Open(Player.Open.TYPE_CHANNEL, tag.channelId); + action.execute(hostManager.getConnection(), new ApiCallback() { + @Override + public void onSuccess(String result) { + if (!isAdded()) return; + LogUtils.LOGD(TAG, "Started channel"); + } + + @Override + public void onError(int errorCode, String description) { + if (!isAdded()) return; + LogUtils.LOGD(TAG, "Error starting channel: " + description); + + Toast.makeText(getActivity(), + String.format(getString(R.string.error_starting_channel), description), + Toast.LENGTH_SHORT).show(); + + } + }, callbackHandler); + + } + }); + + channelAdapter.clear(); + channelAdapter.addAll(result); + channelAdapter.notifyDataSetChanged(); + } + + private class ChannelGroupAdapter extends ArrayAdapter { + + public ChannelGroupAdapter(Context context, int resource) { + super(context, resource); + } + + /** {@inheritDoc} */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(getActivity()) + .inflate(R.layout.grid_item_channel_group, parent, false); + + // Setup View holder pattern + ChannelGroupViewHolder viewHolder = new ChannelGroupViewHolder(); + viewHolder.titleView = (TextView)convertView.findViewById(R.id.title); + convertView.setTag(viewHolder); + } + + final ChannelGroupViewHolder viewHolder = (ChannelGroupViewHolder)convertView.getTag(); + PVRType.DetailsChannelGroup channelGroupDetails = this.getItem(position); + + viewHolder.channelGroupId = channelGroupDetails.channelgroupid; + viewHolder.channelGroupName = channelGroupDetails.label; + + viewHolder.titleView.setText(viewHolder.channelGroupName); + return convertView; + } + } + + /** + * View holder pattern + */ + private static class ChannelGroupViewHolder { + TextView titleView; + + int channelGroupId; + String channelGroupName; + } + + private class ChannelAdapter extends ArrayAdapter { + + private HostManager hostManager; + private int artWidth, artHeight; + + public ChannelAdapter(Context context, int resource) { + super(context, resource); + this.hostManager = HostManager.getInstance(context); + + 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); + } + + /** {@inheritDoc} */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater + .from(getActivity()) + .inflate(R.layout.grid_item_channel, parent, false); + + // Setup View holder pattern + ChannelViewHolder viewHolder = new ChannelViewHolder(); + viewHolder.titleView = (TextView)convertView.findViewById(R.id.title); + viewHolder.detailsView = (TextView)convertView.findViewById(R.id.details); + viewHolder.artView = (ImageView)convertView.findViewById(R.id.art); + convertView.setTag(viewHolder); + } + + final ChannelViewHolder viewHolder = (ChannelViewHolder)convertView.getTag(); + PVRType.DetailsChannel channelDetails = this.getItem(position); + + viewHolder.channelId = channelDetails.channelid; + viewHolder.channelName = channelDetails.channel; + + viewHolder.titleView.setText(channelDetails.channel); + String details = (channelDetails.broadcastnow != null)? + channelDetails.broadcastnow.title : null; + viewHolder.detailsView.setText(details); + UIUtils.loadImageWithCharacterAvatar(getContext(), hostManager, + channelDetails.thumbnail, channelDetails.channel, + viewHolder.artView, artWidth, artHeight); + return convertView; + } + } + + /** + * View holder pattern + */ + private static class ChannelViewHolder { + TextView titleView, detailsView; + ImageView artView; + + int channelId; + String channelName; + } +} diff --git a/app/src/main/java/org/xbmc/kore/ui/PlaylistFragment.java b/app/src/main/java/org/xbmc/kore/ui/PlaylistFragment.java index 4b56802..b47497a 100644 --- a/app/src/main/java/org/xbmc/kore/ui/PlaylistFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/PlaylistFragment.java @@ -131,7 +131,7 @@ public class PlaylistFragment extends Fragment playlistGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { - Player.Open action = new Player.Open(currentPlaylistId, position); + Player.Open action = new Player.Open(Player.Open.TYPE_PLAYLIST, currentPlaylistId, position); action.execute(hostManager.getConnection(), defaultStringActionCallback, callbackHandler); } }); diff --git a/app/src/main/java/org/xbmc/kore/ui/RemoteActivity.java b/app/src/main/java/org/xbmc/kore/ui/RemoteActivity.java index 9fbab52..fa53727 100644 --- a/app/src/main/java/org/xbmc/kore/ui/RemoteActivity.java +++ b/app/src/main/java/org/xbmc/kore/ui/RemoteActivity.java @@ -433,7 +433,7 @@ public class RemoteActivity extends BaseActivity @Override public void onSuccess(String result ) { if (startPlaylist) { - Player.Open action = new Player.Open(PlaylistType.VIDEO_PLAYLISTID); + Player.Open action = new Player.Open(Player.Open.TYPE_PLAYLIST, PlaylistType.VIDEO_PLAYLISTID); action.execute(connection, new ApiCallback() { @Override public void onSuccess(String result) { diff --git a/app/src/main/java/org/xbmc/kore/ui/RemoteFragment.java b/app/src/main/java/org/xbmc/kore/ui/RemoteFragment.java index b5c893f..c24465c 100644 --- a/app/src/main/java/org/xbmc/kore/ui/RemoteFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/RemoteFragment.java @@ -588,6 +588,15 @@ public class RemoteFragment extends Fragment currentFastForwardIcon = fastForwardIcon; currentRewindIcon = rewindIcon; break; + case ListType.ItemsAll.TYPE_CHANNEL: + switchToPanel(R.id.media_panel, true); + + title = nowPlaying.label; + underTitle = nowPlaying.title; + thumbnailUrl = nowPlaying.thumbnail; + currentFastForwardIcon = fastForwardIcon; + currentRewindIcon = rewindIcon; + break; default: switchToPanel(R.id.media_panel, true); title = nowPlaying.label; diff --git a/app/src/main/res/drawable/ic_dvr_24dp.xml b/app/src/main/res/drawable/ic_dvr_24dp.xml new file mode 100644 index 0000000..1c1f4b1 --- /dev/null +++ b/app/src/main/res/drawable/ic_dvr_24dp.xml @@ -0,0 +1,6 @@ + + + + diff --git a/app/src/main/res/layout/grid_item_channel.xml b/app/src/main/res/layout/grid_item_channel.xml new file mode 100644 index 0000000..9c16580 --- /dev/null +++ b/app/src/main/res/layout/grid_item_channel.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/grid_item_channel_group.xml b/app/src/main/res/layout/grid_item_channel_group.xml new file mode 100644 index 0000000..e1f14ba --- /dev/null +++ b/app/src/main/res/layout/grid_item_channel_group.xml @@ -0,0 +1,38 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_navigation_drawer.xml b/app/src/main/res/layout/list_item_navigation_drawer.xml index 5f0902b..567981b 100644 --- a/app/src/main/res/layout/list_item_navigation_drawer.xml +++ b/app/src/main/res/layout/list_item_navigation_drawer.xml @@ -27,6 +27,7 @@ android:layout_marginLeft="@dimen/default_padding" android:layout_marginRight="@dimen/default_padding" android:layout_centerVertical="true" + android:tint="?attr/defaultButtonColorFilter" android:src="@drawable/ic_launcher"/> 146dp 146dp + 88dp + 88dp + \ No newline at end of file diff --git a/app/src/main/res/values/attr.xml b/app/src/main/res/values/attr.xml index 40fbd85..665cd90 100644 --- a/app/src/main/res/values/attr.xml +++ b/app/src/main/res/values/attr.xml @@ -35,6 +35,7 @@ + @@ -48,6 +49,7 @@ + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index aa92ea2..c5919b7 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -118,6 +118,9 @@ 52dp 76dp + 74dp + 74dp + 64dp 128dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cb24bc5..6533a1b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -36,6 +36,7 @@ Files Video File Browser + TV/Radio No media center configured Add Media Center @@ -239,6 +240,8 @@ No genres found\n\nSwipe down to refresh No addons found or not connected\n\nSwipe down to refresh No videos found\n\nSwipe down to refresh + No channel groups found or not connected\n\nSwipe down to refresh + No channel groups found or not connected\n\nSwipe down to refresh Pull to refresh No cast info to display @@ -334,4 +337,8 @@ Play on Kodi + An error occurred while getting channels info: %1$s + An error occurred starting channel playback: %1$s + Switching to channel %1$s + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index eec4ab7..ba4f241 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -69,6 +69,7 @@ @color/dark_separator @color/white + @color/white @@ -93,6 +94,7 @@ @drawable/ic_home_white_24dp @drawable/ic_extension_white_24dp @drawable/ic_folder_white_24dp + @drawable/ic_dvr_24dp @drawable/ic_add_box_white_24dp @drawable/ic_add_box_white_24dp @@ -180,6 +182,7 @@ @color/light_separator @color/black_dim_54pct + @color/black_dim_54pct @@ -214,6 +217,7 @@ @drawable/ic_home_black_24dp @drawable/ic_extension_black_24dp @drawable/ic_folder_black_24dp + @drawable/ic_dvr_24dp @drawable/ic_add_box_black_24dp @drawable/ic_add_box_white_24dp diff --git a/build.gradle b/build.gradle index 9405f3f..5de7450 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.2.3' + classpath 'com.android.tools.build:gradle:1.4.+' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files