/** * Copyright (C) 2013 - 2015 the enviroCar community * <p> * This file is part of the enviroCar app. * <p> * The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * <p> * The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * <p> * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ package org.envirocar.app.view.tracklist; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import org.envirocar.app.R; import org.envirocar.app.handler.TermsOfUseManager; import org.envirocar.app.handler.TrackDAOHandler; import org.envirocar.app.handler.TrackUploadHandler; import org.envirocar.app.handler.UserHandler; import org.envirocar.app.view.utils.DialogUtils; import org.envirocar.app.view.utils.ECAnimationUtils; import org.envirocar.core.entity.Track; import org.envirocar.core.exception.NotConnectedException; import org.envirocar.core.exception.UnauthorizedException; import org.envirocar.core.injection.BaseInjectorFragment; import org.envirocar.core.injection.Injector; import org.envirocar.core.logging.Logger; import org.envirocar.remote.DAOProvider; import org.envirocar.remote.serializer.TrackSerializer; import org.envirocar.storage.EnviroCarDB; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.inject.Inject; import butterknife.ButterKnife; import butterknife.InjectView; import rx.Observable; import rx.Scheduler; import rx.Subscriber; import rx.android.schedulers.AndroidSchedulers; import rx.exceptions.OnErrorThrowable; import rx.functions.Action0; import rx.functions.Func1; import rx.schedulers.Schedulers; /** * @author dewall */ public abstract class AbstractTrackListCardFragment<E extends RecyclerView.Adapter> extends BaseInjectorFragment { private static final Logger LOG = Logger.getLogger(AbstractTrackListCardFragment.class); @Inject protected UserHandler mUserManager; @Inject protected EnviroCarDB mEnvirocarDB; @Inject protected TermsOfUseManager mTermsOfUseManager; @Inject protected DAOProvider mDAOProvider; @Inject protected TrackDAOHandler mTrackDAOHandler; @Inject protected TrackUploadHandler mTrackUploadHandler; @InjectView(R.id.fragment_tracklist_info) protected View infoView; @InjectView(R.id.fragment_tracklist_info_img) protected ImageView infoImg; @InjectView(R.id.fragment_tracklist_info_text) protected TextView infoText; @InjectView(R.id.fragment_tracklist_info_subtext) protected TextView infoSubtext; @InjectView(R.id.fragment_tracklist_progress_view) protected View mProgressView; @InjectView(R.id.fragment_tracklist_progress_text) protected TextView mProgressText; @InjectView(R.id.fragment_tracklist_progress_progressBar) protected ProgressBar mProgressBar; @InjectView(R.id.fragment_tracklist_recycler_view) protected RecyclerView mRecyclerView; @InjectView(R.id.fragment_tracklist_fab) protected FloatingActionButton mFAB; protected E mRecyclerViewAdapter; protected RecyclerView.LayoutManager mRecylcerViewLayoutManager; protected boolean tracksLoaded = false; protected final List<Track> mTrackList = Collections.synchronizedList(new ArrayList<>()); // Different workers for main and background threads. protected Scheduler.Worker mMainThreadWorker = AndroidSchedulers.mainThread().createWorker(); protected Scheduler.Worker mBackgroundWorker = Schedulers.computation().createWorker(); protected final Object attachingActivityLock = new Object(); protected boolean isAttached = false; @Override public void onAttach(Activity activity) { super.onAttach(activity); ((Injector) activity).injectObjects(this); } @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); // Inflate the view and inject the annotated view. View view = inflater.inflate(R.layout.fragment_tracklist, container, false); ButterKnife.inject(this, view); // Initiate the recyclerview // mRecyclerView.setHasFixedSize(true); mRecylcerViewLayoutManager = new LinearLayoutManager(getActivity()); mRecyclerView.setLayoutManager(mRecylcerViewLayoutManager); // setup the adapter for the recyclerview. mRecyclerViewAdapter = getRecyclerViewAdapter(); mRecyclerView.setAdapter(mRecyclerViewAdapter); // notify the waiting thread that the activity has been attached. synchronized (attachingActivityLock) { isAttached = true; attachingActivityLock.notifyAll(); } return view; } /** * @return */ public abstract E getRecyclerViewAdapter(); /** * This method is responsible for loading the track dataset into the cardlist. */ protected abstract void loadDataset(); protected void exportTrack(Track track) { try { // Create an sharing intent. Intent sharingIntent = new Intent(Intent.ACTION_SEND); sharingIntent.setType("application/json"); Uri shareBody = Uri.fromFile(TrackSerializer.exportTrack(track).getFile()); sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "EnviroCar Track " + track.getName()); sharingIntent.putExtra(android.content.Intent.EXTRA_STREAM, shareBody); // Wrap the intent with a chooser. startActivity(Intent.createChooser(sharingIntent, "Share via")); } catch (IOException e) { LOG.warn(e.getMessage(), e); Snackbar.make(getView(), R.string.general_error_please_report, Snackbar.LENGTH_LONG).show(); } } /** * Creates a Dialog for the deletion of a track. On a positive click, the track gets deleted. * * @param track the track to delete. */ protected void createDeleteTrackDialog(Track track) { // Get the up to date reference of the current track. // Create a dialog that deletes on click on the positive button the track. final Track upToDateRef = mEnvirocarDB.getTrack(track.getTrackID()) .toBlocking() .first(); View contentView = getActivity().getLayoutInflater().inflate( R.layout.fragment_tracklist_delete_track_dialog, null, false); ((TextView) contentView.findViewById( R.id.fragment_tracklist_delete_track_dialog_trackname)).setText(track.getName()); // Create a dialog that deletes on click on the positive button the track. DialogUtils.createDefaultDialogBuilder(getContext(), R.string.trackviews_delete_track_dialog_headline, R.drawable.ic_delete_white_24dp, contentView) .positiveText(R.string.ok) .negativeText(R.string.cancel) .onPositive((materialDialog, dialogAction) -> mBackgroundWorker.schedule(() -> { // On a positive button click, then delete the track. if (upToDateRef.isLocalTrack()) deleteLocalTrack(track); else deleteRemoteTrack(track); })) .show(); } protected void showText(int imgResource, int textResource, int subtextResource) { if (mTrackList.isEmpty()) { mMainThreadWorker.schedule(new Action0() { @Override public void call() { infoImg.setImageResource(imgResource); infoText.setText(textResource); infoSubtext.setText(subtextResource); ECAnimationUtils.animateShowView(getContext(), infoView, R.anim.fade_in); } }); } } protected void deleteRemoteTrack(Track track) { LOG.info("deleteRemoteTrack()"); mEnvirocarDB.getTrack(track.getTrackID()) .map(new Func1<Track, Boolean>() { @Override public Boolean call(Track upToDateRef) { if (upToDateRef.isLocalTrack()) { LOG.info("Track to delete is a local track"); return false; } try { mTrackDAOHandler.deleteRemoteTrack(upToDateRef); return true; } catch (Exception e) { throw OnErrorThrowable.from(e); } } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(getDeleteTrackSubscriber(track)); } /** * Deletes a local track in the database. * * @param track */ protected void deleteLocalTrack(final Track track) { // Get the up to date reference of the current track and delete it Observable.defer(() -> mEnvirocarDB.getTrack(track.getTrackID())) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .map(upToDateRef -> { // If the track is a local track, then delete and return whether it was // successful. return upToDateRef.isLocalTrack() && mTrackDAOHandler.deleteLocalTrack(upToDateRef.getTrackID()); }) .subscribe(getDeleteTrackSubscriber(track)); } protected Subscriber<Boolean> getDeleteTrackSubscriber(final Track track) { return new Subscriber<Boolean>() { @Override public void onStart() { LOG.info(String.format("onStart() delete track -> [%s]", track.getName())); showProgressView(getString(R.string.track_list_deleting_track)); } @Override public void onCompleted() { LOG.info(String.format("onCompleted() delete track -> [%s]", track.getName())); } @Override public void onError(Throwable e) { LOG.error(String.format("onError() delete track -> [%s]", track.getName()), e); if (e instanceof UnauthorizedException) { LOG.error("The logged in user is not authorized to do that.", e); showSnackbar(R.string.track_list_deleting_track_unauthorized); } else if (e instanceof NotConnectedException) { LOG.error("Not connected", e); showSnackbar(R.string.track_list_communication_error); } else { showSnackbar(String.format( getString(R.string.track_list_delete_track_error_template), track.getName())); } hideProgressView(); } @Override public void onNext(Boolean success) { LOG.info("onNext() -> " + track.getName()); if (success) { LOG.info("deleteLocalTrack: Successfully deleted track with" + " id=" + track.getTrackID()); mTrackList.remove(track); mRecyclerViewAdapter.notifyDataSetChanged(); showSnackbar(String.format(getString(R.string .track_list_delete_track_success_template), track.getName())); hideProgressView(); } else { showSnackbar(String.format( getString(R.string.track_list_delete_track_error_template), track.getName())); } } }; } protected void showSnackbar(final int message) { mMainThreadWorker.schedule(() -> { if (getView() != null) { Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).show(); } }); } protected void showSnackbar(final String message) { mMainThreadWorker.schedule(() -> { if (getView() != null) { Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).show(); } }); } protected void showProgressView(String text) { mMainThreadWorker.schedule(() -> { mProgressView.setVisibility(View.VISIBLE); mProgressText.setText(text); }); } protected void hideProgressView() { ECAnimationUtils.animateHideView(getContext(), mProgressView, R.anim.fade_out); } }