/**
* 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.os.AsyncTask;
import android.support.design.widget.Snackbar;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.afollestad.materialdialogs.MaterialDialog;
import com.google.common.base.Preconditions;
import org.envirocar.app.R;
import org.envirocar.app.view.trackdetails.TrackDetailsActivity;
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.NoMeasurementsException;
import org.envirocar.core.logging.Logger;
import org.envirocar.core.util.TrackMetadata;
import org.envirocar.core.util.Util;
import java.util.Collections;
import java.util.List;
import rx.Observable;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
/**
* TODO JavaDoc
*
* @author dewall
*/
public class TrackListLocalCardFragment extends AbstractTrackListCardFragment<
TrackListLocalCardAdapter> {
private static final Logger LOG = Logger.getLogger(TrackListLocalCardFragment.class);
/**
*
*/
interface OnTrackUploadedListener {
void onTrackUploaded(Track track);
}
private OnTrackUploadedListener onTrackUploadedListener;
private Subscription loadTracksSubscription;
private Subscription uploadTrackSubscription;
@Override
public void onResume() {
LOG.info("onResume()");
super.onResume();
mFAB.setOnClickListener(v -> DialogUtils.createDefaultDialogBuilder(getContext(),
R.string.track_list_upload_all_tracks_title,
R.drawable.ic_cloud_upload_white_24dp,
R.string.track_list_upload_all_tracks_content)
.positiveText(R.string.ok)
.negativeText(R.string.cancel)
.onPositive((materialDialog, dialogAction) -> uploadAllLocalTracks())
.show());
}
private void uploadAllLocalTracks() {
uploadTrackSubscription = Observable.defer(() -> mEnvirocarDB.getAllLocalTracks())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.first()
.concatMap(tracks -> uploadTracksWithDialogObservable(tracks))
.subscribe(new Subscriber<Track>() {
@Override
public void onCompleted() {
LOG.info("onCompleted()");
if (mRecyclerViewAdapter.getItemCount() <= 0) {
showNoLocalTracksInfo();
}
}
@Override
public void onError(Throwable e) {
LOG.warn(e.getMessage(), e);
}
@Override
public void onNext(Track track) {
LOG.info("Track successfully uploaded -> [%s]", track.getName());
// Update the lists.
mRecyclerViewAdapter.removeItem(track);
mRecyclerViewAdapter.notifyDataSetChanged();
onTrackUploadedListener.onTrackUploaded(track);
}
});
}
private void uploadTrack(Track track) {
uploadTrackSubscription = uploadTrackWithDialogObservable(track)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Track>() {
@Override
public void onCompleted() {
LOG.info("uploadTrack.onCompleted()");
showSnackbar(String.format(
getString(R.string.track_list_upload_track_success_template),
track.getName()));
}
@Override
public void onError(Throwable e) {
LOG.warn(e.getMessage(), e);
if (e.getCause() instanceof NoMeasurementsException) {
showSnackbar(R.string.track_list_upload_track_no_measurements);
} else {
showSnackbar(R.string.track_list_upload_track_general_error);
}
}
@Override
public void onNext(Track track) {
// Update the lists.
mRecyclerViewAdapter.removeItem(track);
mRecyclerViewAdapter.notifyDataSetChanged();
onTrackUploadedListener.onTrackUploaded(track);
}
});
}
@Override
public TrackListLocalCardAdapter getRecyclerViewAdapter() {
return new TrackListLocalCardAdapter(mTrackList, new OnTrackInteractionCallback() {
/**
* Inits the view transition to a {@link TrackDetailsActivity} showing the
* details for the given track.
*
* @param track the track to show the details for.
* @param transitionView the transitionView used for scene transition.
*/
@Override
public void onTrackDetailsClicked(Track track, View transitionView) {
LOG.info(String.format("onTrackDetailsClicked(%s)", track.getTrackID()
.toString()));
int trackID = (int) track.getTrackID().getId();
TrackDetailsActivity.navigate(getActivity(), transitionView, trackID);
}
@Override
public void onDeleteTrackClicked(Track track) {
LOG.info(String.format("onDeleteTrackClicked(%s)", track.getTrackID()));
// create a dialog
createDeleteTrackDialog(track);
}
@Override
public void onUploadTrackClicked(Track track) {
LOG.info(String.format("onUploadTrackClicked(%s)", track.getTrackID()));
// Upload the track
uploadTrack(track);
}
@Override
public void onExportTrackClicked(Track track) {
LOG.info(String.format("onExportTrackClicked(%s)", track.getTrackID()));
track.updateMetadata(new TrackMetadata(Util.getVersionString(getActivity()),
mUserManager.getUser().getTermsOfUseVersion()));
exportTrack(track);
}
@Override
public void onDownloadTrackClicked(Track track, AbstractTrackListCardAdapter
.TrackCardViewHolder holder) {
// NOT REQUIRED
}
});
}
@Override
protected void loadDataset() {
// Do not load the dataset twice.
if (!tracksLoaded) {
tracksLoaded = true;
new LoadLocalTracksTask().execute();
}
}
@Override
public void onDestroyView() {
LOG.info("onDestroyView()");
super.onDestroyView();
if (loadTracksSubscription != null && !loadTracksSubscription.isUnsubscribed()) {
loadTracksSubscription.unsubscribe();
}
if (uploadTrackSubscription != null && !uploadTrackSubscription.isUnsubscribed()) {
uploadTrackSubscription.unsubscribe();
}
}
private Observable<Track> uploadTrackWithDialogObservable(Track track) {
Preconditions.checkNotNull(track, "Input track cannot be null");
return Observable.create(new Observable.OnSubscribe<Track>() {
private MaterialDialog dialog;
private View contentView;
@Override
public void call(Subscriber<? super Track> subscriber) {
subscriber.add(mTrackUploadHandler.uploadTrackObservable(track, getActivity())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Track>() {
@Override
public void onStart() {
subscriber.onStart();
// Inflate the dialog content view and set the track name.
contentView = getActivity().getLayoutInflater().inflate(
R.layout.fragment_tracklist_uploading_single_dialog,
null, false);
TextView trackName = (TextView) contentView.findViewById(
R.id.fragment_tracklist_uploading_single_dialog_trackname);
trackName.setText(track.getName());
// Create the dialog to show.
AndroidSchedulers.mainThread().createWorker().schedule(() -> {
dialog = DialogUtils.createDefaultDialogBuilder(getContext(),
R.string.track_list_upload_track_uploading,
R.drawable.ic_cloud_upload_white_24dp,
contentView)
.cancelable(false)
.negativeText(R.string.cancel)
.onNegative((materialDialog, dialogAction) -> {
subscriber.unsubscribe();
unsubscribe();
})
.show();
});
}
@Override
public void onCompleted() {
subscriber.onCompleted();
if (dialog != null) dialog.dismiss();
}
@Override
public void onError(Throwable e) {
LOG.info("onError()");
subscriber.onError(e);
if (dialog != null) dialog.dismiss();
}
@Override
public void onNext(Track track) {
LOG.info("onNext() track has been successfully uploaded.");
subscriber.onNext(track);
subscriber.onCompleted();
}
}));
}
});
}
private Observable<Track> uploadTracksWithDialogObservable(List<Track> tracks) {
return Observable.create(new Observable.OnSubscribe<Track>() {
private int numberOfTracks = tracks.size();
private int numberOfSuccesses = 0;
private int numberOfFailures = 0;
private MaterialDialog dialog;
private View contentView;
@Override
public void call(Subscriber<? super Track> subscriber) {
subscriber.add(mTrackUploadHandler.uploadTracksObservable(tracks, false,
getActivity())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Track>() {
protected TextView infoText;
protected ProgressBar progressBar;
protected TextView percentageText;
protected TextView progressText;
@Override
public void onStart() {
subscriber.onStart();
// Create the custom dialog view
contentView = getActivity().getLayoutInflater().inflate(R.layout
.fragment_tracklist_uploading_dialog, null, false);
infoText = (TextView) contentView.findViewById(
R.id.fragment_tracklist_uploading_dialog_info);
progressBar = (ProgressBar) contentView.findViewById(
R.id.fragment_tracklist_uploading_dialog_progressbar);
percentageText = (TextView) contentView.findViewById(
R.id.fragment_tracklist_uploading_dialog_percentage);
progressText = (TextView) contentView.findViewById(
R.id.fragment_tracklist_uploading_dialog_progress);
// update the Progressbar
progressBar.setMax(numberOfTracks);
updateProgressView(0);
dialog = DialogUtils.createDefaultDialogBuilder(
getContext(), "Uploading Tracks...",
R.drawable.ic_cloud_upload_white_24dp, contentView)
.cancelable(false)
.negativeText(R.string.cancel)
.onNegative((dialog, dialogAction) -> {
subscriber.unsubscribe();
unsubscribe();
})
.show();
}
@Override
public void onCompleted() {
if (!subscriber.isUnsubscribed())
subscriber.onCompleted();
if (numberOfFailures > 0) {
showSnackbar(String.format("%s of %s tracks have been " +
"successfully uploaded. %s tracks had to less" +
" " +
"measurements to upload.", numberOfSuccesses,
numberOfTracks, numberOfFailures));
}
dialog.dismiss();
}
@Override
public void onError(Throwable e) {
if (!subscriber.isUnsubscribed())
subscriber.onError(e);
showSnackbar("An error occurred during the upload process.");
dialog.dismiss();
}
@Override
public void onNext(Track track) {
if (track == null)
numberOfFailures++;
else {
numberOfSuccesses++;
subscriber.onNext(track);
}
updateProgressView(numberOfFailures + numberOfSuccesses);
if ((numberOfFailures + numberOfSuccesses) == numberOfTracks) {
onCompleted();
}
}
private void updateProgressView(int progress) {
progressBar.setProgress(progress);
progressBar.setSecondaryProgress(progress + 1);
percentageText.setText(((int) ((progress / numberOfTracks) * 100)
) + "%");
progressText.setText(progress + " / " + numberOfTracks);
}
}));
}
});
}
private final class LoadLocalTracksTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
// Wait until the activity has been attached.
synchronized (attachingActivityLock) {
while (!isAttached) {
try {
attachingActivityLock.wait();
} catch (InterruptedException e) {
LOG.error(e.getMessage(), e);
}
}
}
loadTracksSubscription = mEnvirocarDB.getAllLocalTracks()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Track>>() {
@Override
public void onStart() {
LOG.info("onStart() allLocalTracks");
mMainThreadWorker.schedule(() -> {
mProgressView.setVisibility(View.VISIBLE);
mProgressText.setText(R.string.track_list_loading_tracks);
});
}
@Override
public void onCompleted() {
LOG.info("onCompleted() allLocalTracks");
}
@Override
public void onError(Throwable e) {
LOG.error(e.getMessage(), e);
showText(R.drawable.img_alert,
R.string.track_list_bg_error,
R.string.track_list_bg_error_sub);
Snackbar.make(getView(),
R.string.track_list_loading_tracks_error_snackbar,
Snackbar.LENGTH_LONG).show();
}
@Override
public void onNext(List<Track> tracks) {
LOG.info(String.format("onNext(%s)", tracks.size()));
boolean newTrackAdded = false;
for (Track track : tracks) {
if (!mTrackList.contains(track)) {
mTrackList.add(track);
newTrackAdded = true;
}
}
mProgressView.setVisibility(View.INVISIBLE);
if (newTrackAdded) {
Collections.sort(mTrackList);
mRecyclerView.setVisibility(View.VISIBLE);
infoView.setVisibility(View.GONE);
mRecyclerViewAdapter.notifyDataSetChanged();
ECAnimationUtils.animateShowView(getContext(), mFAB,
R.anim.translate_slide_in_bottom_fragment);
} else if (mTrackList.isEmpty()) {
showNoLocalTracksInfo();
}
}
});
return null;
}
}
private void showNoLocalTracksInfo() {
showText(R.drawable.img_tracks,
R.string.track_list_bg_no_local_tracks,
R.string.track_list_bg_no_local_tracks_sub);
}
/**
* Sets the {@link OnTrackUploadedListener}.
*
* @param listener the listener to set.
*/
public void setOnTrackUploadedListener(OnTrackUploadedListener listener) {
this.onTrackUploadedListener = listener;
}
}