Java tutorial
/* * Copyright (C) 2014 Antew * * 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 com.antew.redditinpictures.library.ui; import android.app.ActionBar; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.support.v13.app.FragmentStatePagerAdapter; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.view.ViewPager; import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnSystemUiVisibilityChangeListener; import android.view.WindowManager.LayoutParams; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.antew.redditinpictures.library.Constants; import com.antew.redditinpictures.library.animation.FadeInThenOut; import com.antew.redditinpictures.library.dialog.SaveImageDialogFragment; import com.antew.redditinpictures.library.dialog.SaveImageDialogFragment.SaveImageDialogListener; import com.antew.redditinpictures.library.event.RequestCompletedEvent; import com.antew.redditinpictures.library.event.RequestInProgressEvent; import com.antew.redditinpictures.library.interfaces.SystemUiStateProvider; import com.antew.redditinpictures.library.ui.base.BaseFragmentActivity; import com.antew.redditinpictures.library.util.BundleUtil; import com.antew.redditinpictures.library.util.ImageDownloader; import com.antew.redditinpictures.library.widget.CustomViewPager; import com.antew.redditinpictures.pro.R; import com.google.analytics.tracking.android.EasyTracker; import com.google.analytics.tracking.android.MapBuilder; import com.squareup.otto.Subscribe; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; public abstract class ImageViewerActivity extends BaseFragmentActivity implements SaveImageDialogListener, SystemUiStateProvider { /** * 8 is a great number! Not only is it divisible by 4, but it is also equivalent to 2 * 2 * 2 you just can't beat that! */ protected static final int POST_LOAD_OFFSET = 8; private static final String IMAGE_CACHE_DIR = "images"; @Inject public ImageDownloader mImageDownloader; /** * The Adapter for the ViewPager */ protected FragmentStatePagerAdapter mAdapter; /** * The ViewPager which holds the fragments */ protected CustomViewPager mPager; /** * This BroadcastReceiver handles toggling between fullscreen/windowed mode */ private BroadcastReceiver mToggleFullscreenReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { boolean isSystemUiVisible = intent.getBooleanExtra(Constants.Extra.EXTRA_IS_SYSTEM_UI_VISIBLE, false); if (mPager != null) { if (isSystemUiVisible) { goFullscreen(); } else { exitFullscreen(); } } } }; /** * The lock menu item, so we can change it between a unlocked icon and a highlighted locked * icon. */ protected MenuItem lockViewPagerItem; /** * The images for the adapter */ protected List<? extends Parcelable> mImages = null; /** * The 'crouton' TextView for displaying messages to the user. */ protected TextView mCrouton; /** * The calculated height of the Action Bar */ protected int mActionBarHeight; /** * The wrapper view */ protected RelativeLayout mWrapper; /** * Whether swiping on the ViewPager is enabled */ private boolean mSwipingEnabled = true; /** * The page in the ViewPager that was requested */ private int mRequestedPage = 0; protected boolean mRequestInProgress; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.image_detail_pager); // Set up activity to go full screen getWindow().addFlags(LayoutParams.FLAG_FULLSCREEN); mCrouton = (TextView) findViewById(R.id.crouton); mWrapper = (RelativeLayout) findViewById(R.id.wrapper); registerLocalBroadcastReceivers(); getExtras(); initializeAdapter(); initializeActionBar(); initializeViewPager(); invalidateOptionsMenu(); } /** * Register BroadcastReceivers with the LocalBroadcastManager */ private void registerLocalBroadcastReceivers() { LocalBroadcastManager.getInstance(this).registerReceiver(mToggleFullscreenReceiver, new IntentFilter(Constants.Broadcast.BROADCAST_TOGGLE_FULLSCREEN)); } /** * Get the extras from the intent and do whatever necessary */ public void getExtras() { mRequestedPage = BundleUtil.getInt(getIntent().getExtras(), Constants.Extra.EXTRA_IMAGE, 0); } /** * Initialize the Adapter and ViewPager for the ViewPager */ public void initializeAdapter() { mAdapter = getPagerAdapter(); mPager = (CustomViewPager) findViewById(R.id.pager); mPager.setAdapter(mAdapter); mPager.setPageMargin((int) getResources().getDimension(R.dimen.image_detail_pager_margin)); mPager.setPageMarginDrawable(R.drawable.background_holo_dark); mPager.setOffscreenPageLimit(2); mPager.setOnPageChangeListener(getViewPagerOnPageChangeListener()); } /** * Enable some additional newer visibility and ActionBar features to create a more * immersive photo viewing experience. * <p/> * Initialize the mActionBarHeight variable. */ private void initializeActionBar() { final ActionBar actionBar = getActionBar(); // Hide title text and set home as up actionBar.setDisplayShowTitleEnabled(true); actionBar.setTitle(R.string.reddit_in_pictures); actionBar.setDisplayHomeAsUpEnabled(true); // Calculate ActionBar height TypedValue tv = new TypedValue(); if (getTheme() != null && getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) { mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics()); } } private void initializeViewPager() { // Hide and show the ActionBar as the visibility changes mPager.setOnSystemUiVisibilityChangeListener(getOnSystemUiVisibilityChangeListener()); } /** * Get the adapter for the ViewPager. It is expected that it will return a subclass of * FragmentStatePagerAdapter * * @return The adapter for the ViewPager */ public abstract FragmentStatePagerAdapter getPagerAdapter(); /** * Get the page change listener for the ViewPager. By default it changes between * fullscreen/windowed mode * * @return */ protected ViewPager.OnPageChangeListener getViewPagerOnPageChangeListener() { ViewPager.OnPageChangeListener viewPagerOnPageChangeListener = new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } @Override public void onPageSelected(int position) { mRequestedPage = position; updateDisplay(position); if (!mRequestInProgress && position >= (mAdapter.getCount() - POST_LOAD_OFFSET)) { reachedCloseToLastPage(); } } @Override public void onPageScrollStateChanged(int arg0) { } }; return viewPagerOnPageChangeListener; } public OnSystemUiVisibilityChangeListener getOnSystemUiVisibilityChangeListener() { return new OnSystemUiVisibilityChangeListener() { @Override public void onSystemUiVisibilityChange(int visibility) { if ((visibility & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0) { getActionBar().hide(); } else { getActionBar().show(); } } }; } /** * Update the display when the user switches pages in the ViewPager. See * {@link ImageViewerActivity#getViewPagerOnPageChangeListener()} * * @param position */ protected abstract void updateDisplay(int position); /** * Called upon reaching the last page present in the ViewPager */ public abstract void reachedCloseToLastPage(); @Override public void onResume() { super.onResume(); setSwipingState(mSwipingEnabled, false); } @Override protected void onPause() { super.onPause(); } @Override protected void onDestroy() { LocalBroadcastManager.getInstance(this).unregisterReceiver(mToggleFullscreenReceiver); super.onDestroy(); } /** * Set whether swiping is enabled on the ViewPager. * * @param swipingEnabled * Whether swiping should be enabled * @param showMessageToUser * Whether to display a message to the user, set this to true if the user took direct action to change the state. */ private void setSwipingState(boolean swipingEnabled, boolean showMessageToUser) { if (mAdapter != null && mPager != null) { mSwipingEnabled = swipingEnabled; mPager.setSwipingEnabled(mSwipingEnabled); if (showMessageToUser) { mCrouton.setText(mSwipingEnabled ? getString(R.string.swiping_enabled) : getString(R.string.swiping_disabled)); FadeInThenOut.fadeInThenOut(mCrouton, 1500); } invalidateOptionsMenu(); } } public int getRequestedPage() { return mRequestedPage; } /** * Set the current item in the ViewPager */ protected void moveViewPagerToPosition(int position) { if (mPager != null && mPager.getCurrentItem() != position) { mPager.setCurrentItem(position); } } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (savedInstanceState.containsKey(Constants.Extra.EXTRA_ENTRIES)) { mImages = savedInstanceState.getParcelableArrayList(Constants.Extra.EXTRA_ENTRIES); } if (savedInstanceState.containsKey(Constants.Extra.EXTRA_IS_SWIPING_ENABLED)) { mSwipingEnabled = savedInstanceState.getBoolean(Constants.Extra.EXTRA_IS_SWIPING_ENABLED); } if (savedInstanceState.containsKey(Constants.Extra.EXTRA_IMAGE)) { mRequestedPage = savedInstanceState.getInt(Constants.Extra.EXTRA_IMAGE); } } @Override protected void onSaveInstanceState(Bundle outState) { outState.putParcelableArrayList(Constants.Extra.EXTRA_ENTRIES, (ArrayList<? extends Parcelable>) mImages); outState.putBoolean(Constants.Extra.EXTRA_IS_SWIPING_ENABLED, mPager.isSwipingEnabled()); outState.putInt(Constants.Extra.EXTRA_IMAGE, mPager.getCurrentItem()); super.onSaveInstanceState(outState); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.image_view_menu, menu); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { // We save the icon for locking the view pager so that we can reference // it when we receive a broadcast message to toggle the ViewPager lock state lockViewPagerItem = menu.findItem(R.id.lock_viewpager); // Update the icon depending on whether swiping is enabled or disabled if (mSwipingEnabled) { lockViewPagerItem.setTitle(R.string.disable_swiping); lockViewPagerItem.setIcon(R.drawable.ic_action_lock_open_dark); } else { lockViewPagerItem.setTitle(R.string.enable_swiping); lockViewPagerItem.setIcon(R.drawable.ic_action_lock_closed_dark); } return true; } /** * Handler for when the user selects an item from the ActionBar. * <p> * The default functionality implements:<br> * - Toggling the swipe lock on the ViewPager via toggleViewPagerLock()<br> * - Sharing the post via the Android ACTION_SEND intent, the URL shared is provided by * subclasses via {@link #getUrlForSharing()}<br> * - Viewing the post in a Web browser (the URL is provided by subclasses from * {@link #getPostUri()} <br> * - Displaying a dialog to get the filename to use when saving an image, subclasses will * implement {@link #onFinishSaveImageDialog(String)} * </p> */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: EasyTracker.getInstance(this) .send(MapBuilder.createEvent(Constants.Analytics.Category.ACTION_BAR_ACTION, Constants.Analytics.Action.HOME, null, null).build()); finish(); return true; case R.id.lock_viewpager: if (mPager.isSwipingEnabled()) { EasyTracker.getInstance(this) .send(MapBuilder.createEvent(Constants.Analytics.Category.ACTION_BAR_ACTION, Constants.Analytics.Action.TOGGLE_SWIPING, Constants.Analytics.Label.DISABLED, null) .build()); } else { EasyTracker.getInstance(this) .send(MapBuilder.createEvent(Constants.Analytics.Category.ACTION_BAR_ACTION, Constants.Analytics.Action.TOGGLE_SWIPING, Constants.Analytics.Label.ENABLED, null) .build()); } // Lock or unlock swiping in the ViewPager setSwipingState(!mPager.isSwipingEnabled(), true); return true; case R.id.share_post: EasyTracker.getInstance(this) .send(MapBuilder.createEvent(Constants.Analytics.Category.ACTION_BAR_ACTION, Constants.Analytics.Action.SHARE_POST, getSubreddit(), null).build()); String subject = getString(R.string.check_out_this_image); Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_SUBJECT, subject); intent.putExtra(Intent.EXTRA_TEXT, subject + " " + getUrlForSharing()); startActivity(Intent.createChooser(intent, getString(R.string.share_using_))); return true; case R.id.view_post: EasyTracker .getInstance(this).send( MapBuilder .createEvent(Constants.Analytics.Category.ACTION_BAR_ACTION, Constants.Analytics.Action.OPEN_POST_EXTERNAL, getSubreddit(), null) .build()); Intent browserIntent = new Intent(Intent.ACTION_VIEW, getPostUri()); startActivity(browserIntent); return true; case R.id.save_post: EasyTracker.getInstance(this) .send(MapBuilder.createEvent(Constants.Analytics.Category.ACTION_BAR_ACTION, Constants.Analytics.Action.SAVE_POST, getSubreddit(), null).build()); handleSaveImage(); return true; case R.id.report_image: EasyTracker.getInstance(this) .send(MapBuilder.createEvent(Constants.Analytics.Category.ACTION_BAR_ACTION, Constants.Analytics.Action.REPORT_POST, getSubreddit(), null).build()); new Thread(new Runnable() { @Override public void run() { reportCurrentItem(); } }).start(); Toast.makeText(this, R.string.image_display_issue_reported, Toast.LENGTH_LONG).show(); return true; default: return false; } } /** * Retrieve the current subreddit. * * @return the current subreddit */ public abstract String getSubreddit(); /** * Get the URL of the current image in the ViewPager. Used in * {@link ImageViewerActivity#onOptionsItemSelected(MenuItem)} * * @return The URL of the current image in the ViewPager. */ public abstract String getUrlForSharing(); /** * Get the Uri for the page of the current post in the ViewPager. * * @return The Uri for the page */ protected abstract Uri getPostUri(); /** * Subclasses can choose how to handle the click of the 'Save' icon in the Action Bar. * The default action is to pop up a dialog prompting for a filename to save the image as * * @see ImageViewerActivity#getFilenameForSave() * @see SaveImageDialogFragment */ public void handleSaveImage() { SaveImageDialogFragment saveImageDialog = SaveImageDialogFragment.newInstance(getFilenameForSave()); saveImageDialog.show(getFragmentManager(), Constants.Dialog.DIALOG_GET_FILENAME); } /** * Get the JSON representation of the current image/post in the ViewPager to report an error. * * @return The JSON representation of the currently viewed object. */ protected abstract void reportCurrentItem(); /** * Get the initial value for the filename prompt, by default it is an empty string * * @return The initial filename */ public String getFilenameForSave() { return ""; } /** * This method is expected to perform the actual saving of the image with the filename that was * returned. * * @param filename * The filename that should be used when saving the image (without the extension) */ @Override public abstract void onFinishSaveImageDialog(String filename); public void goFullscreen() { EasyTracker.getInstance(this) .send(MapBuilder.createEvent(Constants.Analytics.Category.UI_ACTION, Constants.Analytics.Action.TOGGLE_DETAILS, Constants.Analytics.Label.GO_FULLSCREEN, null) .build()); mPager.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); } public void exitFullscreen() { EasyTracker.getInstance(this) .send(MapBuilder.createEvent(Constants.Analytics.Category.UI_ACTION, Constants.Analytics.Action.TOGGLE_DETAILS, Constants.Analytics.Label.EXIT_FULLSCREEN, null) .build()); mPager.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); } @Subscribe public void requestInProgress(RequestInProgressEvent event) { mRequestInProgress = true; } @Subscribe public void requestCompleted(RequestCompletedEvent event) { mRequestInProgress = false; } @Override public boolean isSystemUiVisible() { final int vis = mPager.getSystemUiVisibility(); if ((vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0) { return false; } return true; } }