uk.ac.hutton.ics.buntata.adapter.NodeAdapter.java Source code

Java tutorial

Introduction

Here is the source code for uk.ac.hutton.ics.buntata.adapter.NodeAdapter.java

Source

/*
 * Copyright 2016 Information & Computational Sciences, The James Hutton Institute
 *
 * 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 uk.ac.hutton.ics.buntata.adapter;

import android.content.*;
import android.graphics.*;
import android.graphics.drawable.*;
import android.os.*;
import android.support.v4.content.*;
import android.support.v7.graphics.*;
import android.support.v7.widget.*;
import android.view.*;
import android.widget.*;

import com.squareup.picasso.*;

import java.io.*;
import java.util.*;

import butterknife.*;
import jhi.buntata.resource.*;
import uk.ac.hutton.ics.buntata.R;
import uk.ac.hutton.ics.buntata.activity.*;
import uk.ac.hutton.ics.buntata.database.entity.*;
import uk.ac.hutton.ics.buntata.database.manager.*;
import uk.ac.hutton.ics.buntata.util.*;

/**
 * The {@link NodeAdapter} takes care of the {@link BuntataNodeAdvanced} objects.
 *
 * @author Sebastian Raubach
 */
public abstract class NodeAdapter extends RecyclerView.Adapter<NodeAdapter.ViewHolder> implements Filterable {
    private static final boolean USE_DIRTY_HACK_TO_FIX_SHARED_ELEMENT_TRANSITION = true;

    private Context context;
    private RecyclerView parent;
    private BuntataDatasource datasource;
    private int parentMediaId;
    private List<BuntataNodeAdvanced> dataset;
    private UserFilter userFilter;
    private NodeManager nodeManager;

    private int defaultBackgroundColor;
    private int textColorLight;
    private int textColorDark;

    private int left = 0;
    private int right = 0;
    private int padding = 0;
    private int columnCount = 1;

    public void updateDimensions(int columnCount, int left, int right, int padding) {
        this.columnCount = columnCount;
        this.left = left;
        this.right = right;
        this.padding = padding;
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        View view;
        @BindView(R.id.node_view_layout)
        CardView layout;
        @BindView(R.id.node_view_image)
        ImageView image;
        @BindView(R.id.node_view_title)
        TextView title;

        ViewHolder(View v) {
            super(v);

            view = v;
            ButterKnife.bind(this, v);
        }
    }

    public NodeAdapter(Context context, RecyclerView parent, int datasourceId, int parentMediaId,
            List<BuntataNodeAdvanced> dataset) {
        this.context = context;
        this.parent = parent;
        this.dataset = dataset;
        this.nodeManager = new NodeManager(context, datasourceId);
        this.parentMediaId = parentMediaId;
        DatasourceManager manager = new DatasourceManager(context, datasourceId);
        this.datasource = manager.getById(datasourceId);

        /* Get some default color */
        this.defaultBackgroundColor = ContextCompat.getColor(context, R.color.cardview_light_background);
        this.textColorLight = ContextCompat.getColor(context, android.R.color.primary_text_dark);
        this.textColorDark = ContextCompat.getColor(context, android.R.color.primary_text_light);
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        /* Create a new view from the layout file */
        return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.node_view, parent, false));
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        final BuntataNodeAdvanced item = dataset.get(position);

        boolean foundImage = false;

        /* Try to find an image */
        File imagePath = null;
        BuntataMediaAdvanced medium = null;
        if (item.getMediaAdvanced().size() > 0) {
            /* First, check if we've got the same image as our parent. If so, show this one preferably */
            for (BuntataMediaAdvanced m : item.getMediaAdvanced()) {
                if (m.getId() == parentMediaId) {
                    medium = m;
                    foundImage = true;
                    imagePath = FileUtils.getFileForDatasource(context, datasource.getId(), m.getInternalLink());
                    break;
                }
            }

            /* If we don't, check all our media and pick the first "Image" */
            if (!foundImage) {
                BuntataMediaAdvanced m = item.getFirstImage();

                if (m != null) {
                    medium = m;
                    foundImage = true;
                    imagePath = FileUtils.getFileForDatasource(context, datasource.getId(), m.getInternalLink());
                }
            }
        }

        /* Get the width of the view */
        int viewWidth = (parent.getWidth() - left - right - (columnCount - 1) * padding) / columnCount;

        if (context instanceof MainActivity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            holder.image.setTransitionName(context.getString(R.string.transition_node_view));
        }
        /* If this is within the NodeDetailsActivity, then restrict the size of the items */
        if (context instanceof NodeDetailsActivity) {
            viewWidth = context.getResources().getDimensionPixelSize(R.dimen.node_image_height) / 2;
            holder.image.setMaxHeight(viewWidth);
            holder.image.setMaxWidth(viewWidth);
            holder.layout.getLayoutParams().width = viewWidth;
        }

        holder.image.setMinimumHeight(viewWidth);

        /* If no image is found */
        if (!foundImage) {
            Picasso.get().load(R.drawable.missing_image).error(R.drawable.missing_image)
                    .resize(viewWidth, viewWidth) // Resize to fit
                    .centerInside().into(holder.image);
        }
        /* If an image is found */
        else {
            /* Load the image */
            PaletteTransformation paletteTransformation;

            RequestCreator c = Picasso.get().load(imagePath); /* Load from file */

            final boolean showKeys = datasource.isShowKeyName();

            if (showKeys) {
                paletteTransformation = PaletteTransformation.instance();
                c.transform(paletteTransformation); /* Generate the palette based on the image */
            }

            c.resize(viewWidth, viewWidth) /* Resize to fit */
                    .onlyScaleDown() /* But only scale down */
                    .centerCrop() /* And respect the aspect ratio */
                    .into(holder.image, new Callback.EmptyCallback() /* When done, use the palette */
            {
                        @Override
                        public void onError(Exception e) {
                            /* Set the placeholder */
                            holder.image.setImageResource(R.drawable.missing_image);
                            holder.image.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
                        }

                        @Override
                        public void onSuccess() {
                            holder.image.setScaleType(ImageView.ScaleType.CENTER_CROP);

                            if (showKeys) {
                                /* Get back the bitmap */
                                Bitmap bitmap = ((BitmapDrawable) holder.image.getDrawable()).getBitmap(); /* Ew! */

                                /* Get the generated palette */
                                Palette palette = PaletteTransformation.getPalette(bitmap);

                                /* Get the vibrant color and a high-contrast text color */
                                /* Try a couple of fallback colors. Hopefully one of them is present. */
                                int vibrantColor = palette.getVibrantColor(defaultBackgroundColor);
                                if (vibrantColor == defaultBackgroundColor)
                                    vibrantColor = palette.getDarkVibrantColor(defaultBackgroundColor);
                                if (vibrantColor == defaultBackgroundColor)
                                    vibrantColor = palette.getLightVibrantColor(defaultBackgroundColor);
                                if (vibrantColor == defaultBackgroundColor)
                                    vibrantColor = palette.getMutedColor(defaultBackgroundColor);
                                if (vibrantColor == defaultBackgroundColor)
                                    vibrantColor = palette.getDarkMutedColor(defaultBackgroundColor);
                                if (vibrantColor == defaultBackgroundColor)
                                    vibrantColor = palette.getLightMutedColor(defaultBackgroundColor);
                                if (vibrantColor == defaultBackgroundColor)
                                    vibrantColor = palette.getDominantColor(defaultBackgroundColor);

                                int textColor = ColorUtils.isColorDark(vibrantColor) ? textColorLight
                                        : textColorDark;
                                holder.title.setBackgroundColor(vibrantColor);
                                holder.title.setTextColor(textColor);
                            }
                        }
                    });
        }

        holder.title.setText(item.getName());

        if (datasource.isShowKeyName() || !item.hasChildren())
            holder.title.setVisibility(View.VISIBLE);
        else
            holder.title.setVisibility(View.GONE);

        final BuntataMediaAdvanced m = medium;

        /* Listen to click events */
        holder.view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int top = v.getTop();

                if (top < 0 && USE_DIRTY_HACK_TO_FIX_SHARED_ELEMENT_TRANSITION) {
                    /*
                     * This is a very dirty hack to fix an issue where shared elements will overlap the toolbar and status bar
                     * when the node is hidden behind them on click. Solutions on Stackoverflow suggested to include the toolbar
                     * and status bar in the shared elements transition. This, however, didn't seem to work, thus this solution.
                     * The view is then scrolled "into view" and the shared elements transition is postponed until the scroll
                     * event finishes.
                     */
                    parent.smoothScrollToPosition(holder.getAdapterPosition());
                    parent.addOnScrollListener(new RecyclerView.OnScrollListener() {
                        @Override
                        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                                parent.removeOnScrollListener(this);
                                onNodeClicked(holder.image, holder.title, m,
                                        dataset.get(holder.getAdapterPosition()));
                            }
                        }
                    });
                } else {
                    onNodeClicked(holder.image, holder.title, m, dataset.get(holder.getAdapterPosition()));
                }
            }
        });
    }

    @Override
    public int getItemCount() {
        return dataset.size();
    }

    /**
     * Called when a node is clicked.
     *
     * @param transitionRoot The {@link View} showing the image. It's used to start the scene transition
     * @param node           The actual node that has been clicked.
     */
    public abstract void onNodeClicked(View transitionRoot, View title, BuntataMediaAdvanced medium,
            BuntataNodeAdvanced node);

    @Override
    public Filter getFilter() {
        if (userFilter == null)
            userFilter = new UserFilter(this, dataset);

        return userFilter;
    }

    private class UserFilter extends Filter {
        private final NodeAdapter adapter;

        private final List<BuntataNodeAdvanced> originalList;

        private final List<BuntataNodeAdvanced> filteredList;

        private UserFilter(NodeAdapter adapter, List<BuntataNodeAdvanced> originalList) {
            super();
            this.adapter = adapter;
            this.originalList = new LinkedList<>(originalList);
            this.filteredList = new ArrayList<>();
        }

        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            filteredList.clear();
            final FilterResults results = new FilterResults();

            final String filterPattern = constraint.toString();

            /* If the filter is empty, restore original list */
            if (constraint.length() == 0) {
                filteredList.addAll(originalList);
            } else {
                for (final BuntataNodeAdvanced item : originalList) {
                    /* Check if this item has a child that matches */
                    if (nodeManager.hasChildWithContent(item, filterPattern)) {
                        filteredList.add(item);
                    }
                }
            }
            results.values = filteredList;
            results.count = filteredList.size();
            return results;
        }

        @Override
        @SuppressWarnings("unchecked")
        protected void publishResults(CharSequence constraint, FilterResults results) {
            /* Get the new items that match the search query */
            List<BuntataNodeAdvanced> newDataset = (ArrayList<BuntataNodeAdvanced>) results.values;

            if (dataset.size() == newDataset.size())
                return;

            adapter.dataset = new ArrayList<>(newDataset);
            notifyDataSetChanged();

            //         /* If the new data is smaller (items have been removed), then tell the adapter */
            //         if (dataset.size() > newDataset.size())
            //         {
            //            /* Remove each item that is no longer in the dataset with the default transition */
            //            for (int i = dataset.size() - 1; i >= 0; i--)
            //            {
            //               if (!newDataset.contains(dataset.get(i)))
            //                  adapter.notifyItemRemoved(i);
            //            }
            //            adapter.dataset = new ArrayList<>(newDataset);
            //         }
            //         /* Else, the new data is larger (items have been added) */
            //         else
            //         {
            ////            adapter.dataset = new ArrayList<>(newDataset);
            ////            notifyDataSetChanged();
            //            /* Insert each item that's new to the dataset with the default transition */
            //            for (int i = 0; i < newDataset.size(); i++)
            //            {
            //               if (!adapter.dataset.contains(newDataset.get(i)))
            //               {
            //                  adapter.notifyItemInserted(i);
            //               }
            //            }
            //            adapter.dataset = new ArrayList<>(newDataset);
            //         }

            //         /* TODO: This is a very dirty hack, to make the recyclerview updates behave. Find a nicer solution if there is time. */
            //         Handler handler = new Handler(Looper.getMainLooper());
            //         handler.postDelayed(new Runnable() {
            //            public void run() {
            //               parent.scrollToPosition(0);
            //            }
            //         }, 100);
        }
    }
}