io.v.android.apps.syncslides.DeckChooserFragment.java Source code

Java tutorial

Introduction

Here is the source code for io.v.android.apps.syncslides.DeckChooserFragment.java

Source

// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package io.v.android.apps.syncslides;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment;
import android.support.v4.provider.DocumentFile;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.Toast;

import com.google.common.base.Charsets;
import com.google.common.io.ByteStreams;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;

import io.v.android.apps.syncslides.db.DB;
import io.v.android.apps.syncslides.model.Deck;
import io.v.android.apps.syncslides.model.DeckFactory;
import io.v.android.apps.syncslides.model.Slide;
import io.v.android.apps.syncslides.model.SlideImpl;

/**
 * This fragment contains the list of decks as well as the FAB to create a new
 * deck.
 */
public class DeckChooserFragment extends Fragment {
    /**
     * The fragment argument representing the section number for this fragment.
     */
    private static final String ARG_SECTION_NUMBER = "section_number";
    private static final String TAG = "DeckChooserFragment";
    private static final int REQUEST_CODE_IMPORT_DECK = 1000;
    private RecyclerView mRecyclerView;
    private GridLayoutManager mLayoutManager;
    private DeckListAdapter mAdapter;

    /**
     * Returns a new instance of this fragment for the given section number.
     */
    public static DeckChooserFragment newInstance(int sectionNumber) {
        DeckChooserFragment fragment = new DeckChooserFragment();
        Bundle args = new Bundle();
        args.putInt(ARG_SECTION_NUMBER, sectionNumber);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_deck_chooser, container, false);
        FloatingActionButton fab = (FloatingActionButton) rootView.findViewById(R.id.new_deck_fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onImportDeck();
            }
        });
        mRecyclerView = (RecyclerView) rootView.findViewById(R.id.deck_grid);
        mRecyclerView.setHasFixedSize(true);

        // Statically set the span count (i.e. number of columns) for now...  See below.
        mLayoutManager = new GridLayoutManager(getContext(), 2);
        mRecyclerView.setLayoutManager(mLayoutManager);
        // Dynamically set the span based on the screen width.  Cribbed from
        // http://stackoverflow.com/questions/26666143/recyclerview-gridlayoutmanager-how-to-auto-detect-span-count
        mRecyclerView.getViewTreeObserver()
                .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        mRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        int viewWidth = mRecyclerView.getMeasuredWidth();
                        float cardViewWidth = getActivity().getResources().getDimension(R.dimen.deck_card_width);
                        int newSpanCount = (int) Math.floor(viewWidth / cardViewWidth);
                        mLayoutManager.setSpanCount(newSpanCount);
                        mLayoutManager.requestLayout();
                    }
                });

        return rootView;
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        case REQUEST_CODE_IMPORT_DECK:
            if (resultCode != Activity.RESULT_OK) {
                String errorStr = data != null && data.hasExtra(DocumentsContract.EXTRA_ERROR)
                        ? data.getStringExtra(DocumentsContract.EXTRA_ERROR)
                        : "";
                toast("Error selecting deck to import " + errorStr);
                break;
            }
            Uri uri = data.getData();
            importDeck(DocumentFile.fromTreeUri(getContext(), uri));
            break;
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        ((DeckChooserActivity) activity).onSectionAttached(getArguments().getInt(ARG_SECTION_NUMBER));
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.i(TAG, "Starting");
        DB db = DB.Singleton.get(getActivity().getApplicationContext());
        mAdapter = new DeckListAdapter(db);
        mAdapter.start(getActivity().getApplicationContext());
        mRecyclerView.setAdapter(mAdapter);
    }

    @Override
    public void onStop() {
        super.onStop();
        Log.i(TAG, "Stopping");
        mAdapter.stop();
        mAdapter = null;
    }

    /**
     * Import a deck so it shows up in the list of all decks.
     */
    private void onImportDeck() {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
        startActivityForResult(intent, REQUEST_CODE_IMPORT_DECK);
    }

    /**
     * Import a slide deck from the given (local) folder.
     *
     * The folder must contain a JSON metadata file 'deck.json' with the following format:
     * {
     *     "Title" : "<title>",
     *     "Thumb" : "<filename>,
     *     "Slides" : [
     *          {
     *              "Thumb" : "<thumb_filename1>",
     *              "Image" : "<image_filename1>",
     *              "Note" : "<note1>"
     *          },
     *          {
     *              "Thumb" : "<thumb_filename2>",
     *              "Image" : "<image_filename2>",
     *              "Note" : "<note2>"
     *          },
     *
     *          ...
     *     ]
     * }
     *
     * All the filenames must be local to the given folder.
     */
    private void importDeck(DocumentFile dir) {
        if (!dir.isDirectory()) {
            toast("Must import from a directory, got: " + dir);
            return;
        }
        // Read the deck metadata file.
        DocumentFile metadataFile = dir.findFile("deck.json");
        if (metadataFile == null) {
            toast("Couldn't find deck metadata file 'deck.json'");
            return;
        }
        JSONObject metadata = null;
        try {
            String data = new String(
                    ByteStreams
                            .toByteArray(getActivity().getContentResolver().openInputStream(metadataFile.getUri())),
                    Charsets.UTF_8);
            metadata = new JSONObject(data);
        } catch (FileNotFoundException e) {
            toast("Couldn't open deck metadata file: " + e.getMessage());
            return;
        } catch (IOException e) {
            toast("Couldn't read data from deck metadata file: " + e.getMessage());
            return;
        } catch (JSONException e) {
            toast("Couldn't parse deck metadata: " + e.getMessage());
            return;
        }

        try {
            String id = UUID.randomUUID().toString();
            String title = metadata.getString("Title");
            byte[] thumbData = readImage(dir, metadata.getString("Thumb"));
            Deck deck = DeckFactory.Singleton.get().make(title, thumbData, id);
            Slide[] slides = readSlides(dir, metadata);
            DB.Singleton.get(getActivity().getApplicationContext()).importDeck(deck, slides, null);
        } catch (JSONException e) {
            toast("Invalid format for deck metadata: " + e.getMessage());
            return;
        } catch (IOException e) {
            toast("Error interpreting deck metadata: " + e.getMessage());
            return;
        }
    }

    private Slide[] readSlides(DocumentFile dir, JSONObject metadata) throws JSONException, IOException {
        if (!metadata.has("Slides")) {
            return new Slide[0];
        }
        JSONArray slides = metadata.getJSONArray("Slides");
        Slide[] ret = new Slide[slides.length()];
        for (int i = 0; i < slides.length(); ++i) {
            JSONObject slide = slides.getJSONObject(i);
            byte[] thumbData = readImage(dir, slide.getString("Thumb"));
            byte[] imageData = thumbData;
            if (slide.has("Image")) {
                imageData = readImage(dir, slide.getString("Image"));
            }
            String note = slide.getString("Note");
            ret[i] = new SlideImpl(thumbData, imageData, note);
        }
        return ret;
    }

    private byte[] readImage(DocumentFile dir, String fileName) throws IOException {
        DocumentFile file = dir.findFile(fileName);
        if (file == null) {
            throw new FileNotFoundException("Image file doesn't exist: " + fileName);
        }
        return ByteStreams.toByteArray(getActivity().getContentResolver().openInputStream(file.getUri()));
    }

    private void toast(String msg) {
        Toast.makeText(getActivity(), msg, Toast.LENGTH_LONG).show();
    }
}