org.xbmc.kore.ui.AbstractInfoFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.xbmc.kore.ui.AbstractInfoFragment.java

Source

/*
 * 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.Manifest;
import android.annotation.TargetApi;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.text.TextUtils;
import android.util.DisplayMetrics;
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.view.ViewTreeObserver;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

import com.melnykov.fab.FloatingActionButton;
import com.melnykov.fab.ObservableScrollView;

import org.xbmc.kore.R;
import org.xbmc.kore.Settings;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.ApiCallback;
import org.xbmc.kore.jsonrpc.method.Player;
import org.xbmc.kore.jsonrpc.type.PlaylistType;
import org.xbmc.kore.service.library.LibrarySyncService;
import org.xbmc.kore.service.library.SyncUtils;
import org.xbmc.kore.ui.generic.RefreshItem;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.SharedElementTransition;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;

import java.util.Locale;

import at.blogc.android.views.ExpandableTextView;
import butterknife.ButterKnife;
import butterknife.InjectView;

import static android.view.View.GONE;

abstract public class AbstractInfoFragment extends AbstractFragment implements SwipeRefreshLayout.OnRefreshListener,
        SyncUtils.OnServiceListener, SharedElementTransition.SharedElement {
    private static final String TAG = LogUtils.makeLogTag(AbstractInfoFragment.class);

    // Detail views
    @InjectView(R.id.swipe_refresh_layout)
    SwipeRefreshLayout swipeRefreshLayout;
    @InjectView(R.id.media_panel)
    ScrollView panelScrollView;
    @InjectView(R.id.art)
    ImageView artImageView;
    @InjectView(R.id.poster)
    ImageView posterImageView;
    @InjectView(R.id.media_title)
    TextView titleTextView;
    @InjectView(R.id.media_undertitle)
    TextView underTitleTextView;
    @InjectView(R.id.rating_container)
    LinearLayout ratingContainer;
    @InjectView(R.id.rating)
    TextView ratingTextView;
    @InjectView(R.id.rating_votes)
    TextView ratingVotesTextView;
    @InjectView(R.id.max_rating)
    TextView maxRatingTextView;
    @InjectView(R.id.media_details_right)
    TextView detailsRightTextView;
    @InjectView(R.id.media_details)
    LinearLayout mediaDetailsContainer;
    @InjectView(R.id.media_action_download)
    ImageButton downloadButton;
    @InjectView(R.id.media_action_pin_unpin)
    ImageButton pinUnpinButton;
    @InjectView(R.id.media_action_add_to_playlist)
    ImageButton addToPlaylistButton;
    @InjectView(R.id.media_action_seen)
    ImageButton seenButton;
    @InjectView(R.id.media_action_go_to_imdb)
    ImageButton imdbButton;
    @InjectView(R.id.media_actions_bar)
    LinearLayout mediaActionsBar;
    @InjectView(R.id.media_description)
    ExpandableTextView descriptionExpandableTextView;
    @InjectView(R.id.media_description_container)
    LinearLayout descriptionContainer;
    @InjectView(R.id.show_all)
    ImageView expansionImage;
    @InjectView(R.id.fab)
    ImageButton fabButton;
    @InjectView(R.id.exit_transition_view)
    View exitTransitionView;

    private HostManager hostManager;
    private HostInfo hostInfo;
    private ServiceConnection serviceConnection;
    private RefreshItem refreshItem;
    private boolean expandDescription;

    /**
     * Handler on which to post RPC callbacks
     */
    private Handler callbackHandler = new Handler();

    /**
     * Use {@link #setDataHolder(DataHolder)}
     * to provide the required info after creating a new instance of this Fragment
     */
    public AbstractInfoFragment() {
        super();
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        hostManager = HostManager.getInstance(getActivity());
        hostInfo = hostManager.getHostInfo();
    }

    @TargetApi(21)
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if (container == null) {
            // We're not being shown or there's nothing to show
            return null;
        }

        ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_info, container, false);
        ButterKnife.inject(this, root);

        // Setup dim the fanart when scroll changes. Full dim on 4 * iconSize dp
        Resources resources = getActivity().getResources();
        final int pixelsToTransparent = 4 * resources.getDimensionPixelSize(R.dimen.default_icon_size);
        panelScrollView.getViewTreeObserver()
                .addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
                    @Override
                    public void onScrollChanged() {
                        float y = panelScrollView.getScrollY();
                        float newAlpha = Math.min(1, Math.max(0, 1 - (y / pixelsToTransparent)));
                        artImageView.setAlpha(newAlpha);
                    }
                });

        DataHolder dataHolder = getDataHolder();

        if (!dataHolder.getSquarePoster()) {
            posterImageView.getLayoutParams().width = resources
                    .getDimensionPixelSize(R.dimen.detail_poster_width_nonsquare);
            posterImageView.getLayoutParams().height = resources
                    .getDimensionPixelSize(R.dimen.detail_poster_height_nonsquare);
        }

        if (getRefreshItem() != null) {
            swipeRefreshLayout.setOnRefreshListener(this);
        } else {
            swipeRefreshLayout.setEnabled(false);
        }

        FloatingActionButton fab = (FloatingActionButton) fabButton;
        fab.attachToScrollView((ObservableScrollView) panelScrollView);

        if (Utils.isLollipopOrLater()) {
            posterImageView.setTransitionName(dataHolder.getPosterTransitionName());
        }

        if (savedInstanceState == null) {
            FragmentManager fragmentManager = getChildFragmentManager();
            Fragment fragment = fragmentManager.findFragmentById(R.id.media_additional_info);
            if (fragment == null) {
                fragment = getAdditionalInfoFragment();
                if (fragment != null) {
                    fragmentManager.beginTransaction().add(R.id.media_additional_info, fragment).commit();
                }
            }
        }

        if (setupMediaActionBar()) {
            mediaActionsBar.setVisibility(View.VISIBLE);
        }

        if (setupFAB(fabButton)) {
            fabButton.setVisibility(View.VISIBLE);
        }

        updateView(dataHolder);
        return root;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setHasOptionsMenu(true);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.refresh_item, menu);
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public void onStart() {
        super.onStart();
        serviceConnection = SyncUtils.connectToLibrarySyncService(getActivity(), this);
    }

    @Override
    public void onResume() {
        // Force the exit view to invisible
        exitTransitionView.setVisibility(View.INVISIBLE);
        if (refreshItem != null) {
            refreshItem.register();
        }
        super.onResume();
    }

    @Override
    public void onPause() {
        if (refreshItem != null) {
            refreshItem.unregister();
        }
        super.onPause();
    }

    @Override
    public void onStop() {
        super.onStop();
        SyncUtils.disconnectFromLibrarySyncService(getActivity(), serviceConnection);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.action_refresh:
            onRefresh();
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * Swipe refresh layout callback
     */
    /** {@inheritDoc} */
    @Override
    public void onRefresh() {
        if (getRefreshItem() == null) {
            Toast.makeText(getActivity(), R.string.Refreshing_not_implemented_for_this_item, Toast.LENGTH_SHORT)
                    .show();
            swipeRefreshLayout.setRefreshing(false);
            return;
        }

        refreshItem.setSwipeRefreshLayout(swipeRefreshLayout);
        refreshItem.startSync(false);
    }

    @Override
    public void onServiceConnected(LibrarySyncService librarySyncService) {
        if (getRefreshItem() == null) {
            return;
        }

        if (SyncUtils.isLibrarySyncing(librarySyncService, HostManager.getInstance(getActivity()).getHostInfo(),
                refreshItem.getSyncType())) {
            UIUtils.showRefreshAnimation(swipeRefreshLayout);
            refreshItem.setSwipeRefreshLayout(swipeRefreshLayout);
            refreshItem.register();
        }
    }

    protected void setFabButtonState(boolean enable) {
        if (enable) {
            fabButton.setVisibility(View.VISIBLE);
        } else {
            fabButton.setVisibility(GONE);
        }
    }

    protected void fabActionPlayItem(PlaylistType.Item item) {
        if (item == null) {
            Toast.makeText(getActivity(), R.string.no_item_available_to_play, Toast.LENGTH_SHORT).show();
            return;
        }

        Player.Open action = new Player.Open(item);
        action.execute(HostManager.getInstance(getActivity()).getConnection(), new ApiCallback<String>() {
            @Override
            public void onSuccess(String result) {
                if (!isAdded())
                    return;
                // Check whether we should switch to the remote
                boolean switchToRemote = PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(
                        Settings.KEY_PREF_SWITCH_TO_REMOTE_AFTER_MEDIA_START,
                        Settings.DEFAULT_PREF_SWITCH_TO_REMOTE_AFTER_MEDIA_START);
                if (switchToRemote) {
                    int cx = (fabButton.getLeft() + fabButton.getRight()) / 2;
                    int cy = (fabButton.getTop() + fabButton.getBottom()) / 2;
                    UIUtils.switchToRemoteWithAnimation(getActivity(), cx, cy, exitTransitionView);
                }
            }

            @Override
            public void onError(int errorCode, String description) {
                if (!isAdded())
                    return;
                // Got an error, show toast
                Toast.makeText(getActivity(), R.string.unable_to_connect_to_xbmc, Toast.LENGTH_SHORT).show();
            }
        }, callbackHandler);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
            @NonNull int[] grantResults) {
        switch (requestCode) {
        case Utils.PERMISSION_REQUEST_WRITE_STORAGE:
            // If request is cancelled, the result arrays are empty.
            if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                downloadButton.performClick();
            } else {
                Toast.makeText(getActivity(), R.string.write_storage_permission_denied, Toast.LENGTH_SHORT).show();
            }
            break;
        }
    }

    @Override
    @TargetApi(21)
    public boolean isSharedElementVisible() {
        return UIUtils.isViewInBounds(panelScrollView, posterImageView);
    }

    protected void refreshAdditionInfoFragment() {
        Fragment fragment = getChildFragmentManager().findFragmentById(R.id.media_additional_info);
        if (fragment != null)
            ((AbstractAdditionalInfoFragment) fragment).refresh();
    }

    protected HostManager getHostManager() {
        return hostManager;
    }

    protected HostInfo getHostInfo() {
        return hostInfo;
    }

    /**
     * Call this when you are ready to provide the titleTextView, undertitle, details, descriptionExpandableTextView, etc. etc.
     */
    protected void updateView(DataHolder dataHolder) {
        titleTextView.setText(dataHolder.getTitle());
        underTitleTextView.setText(dataHolder.getUnderTitle());
        detailsRightTextView.setText(dataHolder.getDetails());

        if (!TextUtils.isEmpty(dataHolder.getDescription())) {
            Resources.Theme theme = getActivity().getTheme();
            TypedArray styledAttributes = theme
                    .obtainStyledAttributes(new int[] { R.attr.iconExpand, R.attr.iconCollapse });
            final int iconCollapseResId = styledAttributes.getResourceId(styledAttributes.getIndex(0),
                    R.drawable.ic_expand_less_white_24dp);
            final int iconExpandResId = styledAttributes.getResourceId(styledAttributes.getIndex(1),
                    R.drawable.ic_expand_more_white_24dp);
            styledAttributes.recycle();
            descriptionContainer.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    descriptionExpandableTextView.toggle();
                    expansionImage.setImageResource(
                            descriptionExpandableTextView.isExpanded() ? iconCollapseResId : iconExpandResId);
                }
            });
            descriptionExpandableTextView.setText(dataHolder.getDescription());
            if (expandDescription) {
                descriptionExpandableTextView.expand();
                expansionImage.setImageResource(iconExpandResId);
            }
            descriptionContainer.setVisibility(View.VISIBLE);
        } else {
            descriptionContainer.setVisibility(GONE);
        }

        // Images
        DisplayMetrics displayMetrics = new DisplayMetrics();
        getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);

        Resources resources = getActivity().getResources();

        if (dataHolder.getPosterUrl() != null) {
            int posterWidth;
            int posterHeight;
            if (dataHolder.getSquarePoster()) {
                posterWidth = resources.getDimensionPixelOffset(R.dimen.detail_poster_width_square);
                posterHeight = resources.getDimensionPixelOffset(R.dimen.detail_poster_height_square);
            } else {
                posterWidth = resources.getDimensionPixelOffset(R.dimen.detail_poster_width_nonsquare);
                posterHeight = resources.getDimensionPixelOffset(R.dimen.detail_poster_height_nonsquare);
            }

            UIUtils.loadImageWithCharacterAvatar(getActivity(), hostManager, dataHolder.getPosterUrl(),
                    dataHolder.getTitle(), posterImageView, posterWidth, posterHeight);
        } else {
            posterImageView.setVisibility(GONE);
            int padding = getActivity().getResources().getDimensionPixelSize(R.dimen.default_padding);
            titleTextView.setPadding(padding, padding, 0, 0);
            underTitleTextView.setPadding(padding, padding, 0, 0);
        }

        int artHeight = resources.getDimensionPixelOffset(R.dimen.detail_art_height);
        int artWidth = displayMetrics.widthPixels;

        UIUtils.loadImageIntoImageview(hostManager,
                TextUtils.isEmpty(dataHolder.getFanArtUrl()) ? dataHolder.getPosterUrl()
                        : dataHolder.getFanArtUrl(),
                artImageView, artWidth, artHeight);

        if (dataHolder.getRating() > 0) {
            ratingTextView.setText(String.format(Locale.getDefault(), "%01.01f", dataHolder.getRating()));
            if (dataHolder.getMaxRating() > 0) {
                maxRatingTextView.setText(
                        String.format(getString(R.string.max_rating), String.valueOf(dataHolder.getMaxRating())));
            }
            if (dataHolder.getVotes() > 0) {
                ratingVotesTextView
                        .setText(String.format(getString(R.string.votes), String.valueOf(dataHolder.getVotes())));
            }
            ratingContainer.setVisibility(View.VISIBLE);
        } else if (TextUtils.isEmpty(dataHolder.getDetails())) {
            mediaDetailsContainer.setVisibility(View.GONE);
        }
    }

    /**
     * Setting a listener for downloads will add the download button to the UI
     * @param listener to be called when user clicks the download button. Note that the View passed
     *                 into onClick from {@link android.view.View.OnClickListener} will be null
     *                 when the user is asked for storage permissions
     */
    protected void setOnDownloadListener(final View.OnClickListener listener) {
        downloadButton.setVisibility(View.VISIBLE);
        downloadButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (checkStoragePermission()) {
                    if (Settings.allowedDownloadNetworkTypes(getActivity()) != 0) {
                        listener.onClick(view);
                        UIUtils.highlightImageView(getActivity(), downloadButton, true);
                    } else {
                        Toast.makeText(getActivity(), R.string.no_connection_type_selected, Toast.LENGTH_SHORT)
                                .show();
                    }
                }
            }
        });
    }

    protected void setOnAddToPlaylistListener(View.OnClickListener listener) {
        addToPlaylistButton.setVisibility(View.VISIBLE);
        addToPlaylistButton.setOnClickListener(listener);
    }

    protected void setOnGoToImdbListener(View.OnClickListener listener) {
        imdbButton.setVisibility(View.VISIBLE);
        imdbButton.setOnClickListener(listener);
    }

    /**
     * Use {@link #setSeenButtonState(boolean)} to set the state of the seen button
     * @param listener
     */
    protected void setOnSeenListener(final View.OnClickListener listener) {
        setupToggleButton(seenButton, listener);
    }

    protected void setOnPinClickedListener(final View.OnClickListener listener) {
        setupToggleButton(pinUnpinButton, listener);
    }

    /**
     * Uses colors to show to the user the item has been downloaded
     * @param state true if item has been watched/listened too, false otherwise
     */
    protected void setDownloadButtonState(boolean state) {
        UIUtils.highlightImageView(getActivity(), downloadButton, state);
    }

    /**
     * Uses colors to show the seen state to the user
     * @param state true if item has been watched/listened too, false otherwise
     */
    protected void setSeenButtonState(boolean state) {
        setToggleButtonState(seenButton, state);
    }

    protected void setPinButtonState(boolean state) {
        setToggleButtonState(pinUnpinButton, state);
    }

    private void setToggleButtonState(ImageButton button, boolean state) {
        UIUtils.highlightImageView(getActivity(), button, state);
        button.setTag(state);
    }

    private void setupToggleButton(final ImageButton button, final View.OnClickListener listener) {
        button.setVisibility(View.VISIBLE);
        button.setTag(false);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                listener.onClick(view);
                // Boldly invert the state. We depend on the observer to correct the state
                // if Kodi or other service didn't honour our request
                setToggleButtonState(button, !(boolean) button.getTag());
            }
        });
    }

    private boolean checkStoragePermission() {
        boolean hasStoragePermission = ContextCompat.checkSelfPermission(getActivity(),
                Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;

        if (!hasStoragePermission) {
            requestPermissions(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
                    Utils.PERMISSION_REQUEST_WRITE_STORAGE);
            return false;
        }

        return true;
    }

    protected RefreshItem getRefreshItem() {
        if (refreshItem == null) {
            refreshItem = createRefreshItem();
        }
        return refreshItem;
    }

    protected void setExpandDescription(boolean expandDescription) {
        this.expandDescription = expandDescription;
    }

    abstract protected AbstractAdditionalInfoFragment getAdditionalInfoFragment();

    /**
     * Called when user commands the information to be renewed. Either through a swipe down
     * or a menu call.
     * <br/>
     * Note, that {@link AbstractAdditionalInfoFragment#refresh()} will be called for an
     * additional fragment, if available, automatically.
     * @return
     */
    abstract protected RefreshItem createRefreshItem();

    /**
     * Called when the media action bar actions are available and
     * you can use {@link #setOnAddToPlaylistListener(View.OnClickListener)},
     * {@link #setOnSeenListener(View.OnClickListener)},
     * {@link #setOnDownloadListener(View.OnClickListener)},
     * {@link #setOnGoToImdbListener(View.OnClickListener)},
     * and {@link #setOnPinClickedListener(View.OnClickListener)} to enable
     * one or more actions.
     * @return true if media action bar should be visible, false otherwise
     */
    abstract protected boolean setupMediaActionBar();

    /**
     * Called when the fab button is available
     * @return true to enable the Floating Action Button, false otherwise
     */
    abstract protected boolean setupFAB(ImageButton FAB);
}