public class Constants {

    public static final String ALBUM_NAME = "Rando";

    public static final String FILEPATH = "FILEPATH";
    public static final String TOTAL_RANDOS_NUMBER = "TOTAL_RANDOS_NUMBER";
    public static final String UPDATE_STATUS = "UPDATE_STATUS";
    public static final String UPDATED = "UPDATED";
    public static final String NOT_UPDATED = "NOT_UPDATED";

    /**
     * Default on-disk cache directory.
     */
    public static final String DEFAULT_CACHE_DIR = "volley";
    public static final int DEFAULT_CACHE_SIZE = 20 * 1024 * 1024;

    public static final String CAMERA_BROADCAST_EVENT = "RANDO_CAMERA_EVENT";
    public static final String SYNC_BROADCAST_EVENT = "Rando4MeSyncEvent";
    public static final String SYNC_STATISTICS_EVENT = "Rando4MeSyncStatEvent";
    public static final String UPLOAD_SERVICE_BROADCAST_EVENT = "Rando4MeUploadDoneEvent";
    public static final String AUTH_FAILURE_BROADCAST_EVENT = "Rando4MeAuthFailureEvent";
    public static final String LOGOUT_BROADCAST_EVENT = "Rando4MeLogoutEvent";
    public static final String AUTH_SUCCCESS_BROADCAST_EVENT = "Rando4MeAuthSuccessEvent";
    public static final String PUSH_NOTIFICATION_BROADCAST_EVENT = "Rando4MePushNotificationEvent";

    public static final String RANDO_PHOTO_PATH = "RANDO_PHOTO_PATH";

    public static final String IMAGE_PREFIX = "IMG_";
    public static final String IMAGE_POSTFIX = ".jpg";

    public static final long LOCATION_DETECT_TIMEOUT = 5 * 60 * 1000; //in milliseconds

    public static final int UNAUTHORIZED_CODE = 400;
    public static final int FORBIDDEN_CODE = 411;


    public static final int DESIRED_PICTURE_SIZE = 1200;
    public static final int SIZE_SMALL = 480;
    public static final int SIZE_MEDIUM = 800;
    public static final int SIZE_LARGE = 1200;

    public static final String SERVER_API_HOST = BuildConfig.RANDO_API_HOST;
    public static final String SERVER_HOST = BuildConfig.RANDO_HOST;
    public static final String SERVER_API_URL = "https://" + SERVER_API_HOST;
    public static final String SERVER_URL = "https://" + SERVER_HOST;
    public static final String PRIVACY_POLICY_URL = "https://github.com/RandoApp/Rando/wiki/Privacy-Policy";
    public static final int UPLOAD_CONNECTION_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(10);
    public static final int API_CONNECTION_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(2);

    public static final int UPLOAD_RETRY_TIMEOUT = 5 * 60 * 1000;

    //Shared Preferences
    public static final String PREFERENCES_FILE_NAME = "rando.prefs";
    public static final String AUTH_TOKEN = "auth.token";
    public static final String FIREBASE_INSTANCE_ID = "firebase.instance.id";
    public static final String UPDATE_PLAY_SETVICES_DIALOG_SHOWN_DATE = "play.services.update.dialog.shown.date";
    public static final String CAMERA_FACING_STRING = "camera.facing.string";
    public static final String CAMERA_GRID_STRING = "camera.grid.string";
    public static final String ENABLE_VIBRATE = "enable.vibrate";
    public static final String CAMERA_FLASH_MODE = "camera.flash.mode";
    public static final String USER_STATISTICS_LIKES = "likes";
    public static final String USER_STATISTICS_DISLIKES = "dislikes";
    public static final String ACCOUNT = "account";
    public static final String TRAINING_FRAGMENT_SHOWN = "training.fragment.shown";
    public static final String BAN_RESET_AT = "main.ban.reset.at";

    public static final String AUTH_TOKEN_PARAM = "token";
    public static final String SIGNUP_EMAIL_PARAM = "email";
    public static final String SIGNUP_PASSWORD_PARAM = "password";
    public static final String GOOGLE_EMAIL_PARAM = "email";
    public static final String GOOGLE_TOKEN_PARAM = "token";
    public static final String ANONYMOUS_ID_PARAM = "id";
    public static final String IMAGE_URL_PARAM = "imageURL";
    public static final String IMAGE_URL_SIZES_PARAM = "imageSizeURL";
    public static final String MAP_URL_SIZES_PARAM = "mapSizeURL";
    public static final String SMALL_PARAM = "small";
    public static final String MEDIUM_PARAM = "medium";
    public static final String LARGE_PARAM = "large";
    public static final String RANDO_ID_PARAM = "randoId";
    public static final String MAP_URL_PARAM = "mapURL";
    public static final String CREATION_PARAM = "creation";
    public static final String DETECTED_PARAM = "detected";
    public static final String RATING_PARAM = "rating";
    public static final String RANDO_PARAM = "rando";
    public static final String STATISTICS_PARAM = "statistics";
    public static final String NOTIFICATION_TYPE_PARAM = "notificationType";
    public static final String IN_RANDOS_PARAM = "in";
    public static final String OUT_RANDOS_PARAM = "out";
    public static final String EMAIL_PARAM = "email";
    public static final String LOCATION = "location";
    public static final String LATITUDE_PARAM = "latitude";
    public static final String LONGITUDE_PARAM = "longitude";
    public static final String FIREBASE_INSTANCE_ID_PARAM = "firebaseInstanceId";
    public static final String FIREBASE_INSTANCE_ID_HEADER = "FirebaseInstanceId";
    public static final String AUTHORIZATION_HEADER = "Authorization";

    public static final String PAGE = "page";

    public static final String ERROR_MESSAGE_PARAM = "message";
    public static final String ERROR_CODE_PARAM = "code";
    public static final String ERROR_STATUS_PARAM = "status";

    public static final String IMAGE_PARAM = "image";

    public static final String IMAGE_MIME_TYPE = "image/jpeg";
    public static final String SIGNUP_URL = SERVER_API_URL + "/user";
    public static final String STATISTICS_URL = SERVER_API_URL + "/user/statistics";
    public static final String GOOGLE_URL = SERVER_API_URL + "/google";
    public static final String ANONYMOUS_URL = SERVER_API_URL + "/anonymous";
    public static final String LOGOUT_URL = SERVER_API_URL + "/logout";
    public static final String FETCH_USER_URL = SERVER_API_URL + "/user";
    public static final String UPLOAD_RANDO_URL = SERVER_API_URL + "/image";
    public static final String DELETE_URL = SERVER_API_URL + "/delete/";
    public static final String REPORT_URL = SERVER_API_URL + "/report/";
    public static final String SHARE_URL = SERVER_URL + "/s/%s";
    public static final String RATE_URL = SERVER_API_URL + "/rate/";

    public static final String GOOGLE_AUTH_SCOPE = "oauth2:https://www.googleapis.com/auth/userinfo.profile";
    public static final String GOOGLE_USER_INFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo?access_token=";

    private static final int NUMBER_OF_IMAGES_IN_ONE_SET = 4; //stranger's + user's images and maps
    public static final int NUMBER_OF_IMAGES_FOR_CACHING = NUMBER_OF_IMAGES_IN_ONE_SET * 2; //2 visible image sets per screen

    public static final int GOOGLE_ACTIVITIES_AUTH_REQUEST_CODE = 483; //value is not matter

    public static final int CAMERA_ACTIVITY_UPLOAD_PRESSED_RESULT_CODE = 628;
    public static final int CAMERA_ACTIVITY_UPLOAD_PRESSED_REQUEST_CODE = 623;
    public static final int UPDATE_PLAY_SERVICES_REQUEST_CODE = 2404;
    public static final int CAMERA_ACTIVITY_CAMERA_PERMISSION_REQUIRED = 629;


    public static final int ALL_PERMISSIONS_REQUEST_CODE = 701;
    public static final int CAMERA_PERMISSION_REQUEST_CODE = 702;
    public static final int LOCATION_PERMISSION_REQUEST_CODE = 703;
    public static final int CONTACTS_PERMISSION_REQUEST_CODE = 704;
    public static final int STORAGE_PERMISSION_REQUEST_CODE = 705;

    public static final int GOOGLE_SIGN_IN = 801;

    public static final String LOGOUT_ACTIVITY = "LOGOUT";
    public static final int LOGOUT_ACTIVITY_RESULT = 901;

    public static final String PUSH_NOTIFICATION_LANDED = "landed";
    public static final String PUSH_NOTIFICATION_RECEIVED = "received";
    public static final String PUSH_NOTIFICATION_RATED = "rated";

    public static final String TO_UPLOAD_RANDO_ID = "randoIdToUpload";


} public class RandoListAdapter extends CursorRecyclerViewAdapter<RecyclerView.ViewHolder> {

    private boolean isStranger;
    private FirebaseAnalytics firebaseAnalytics;
    private int imageSize;
    private Context mContext;
    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MMMM d", Locale.UK);
    private static final DateFormat DATE_YEAR_FORMAT = new SimpleDateFormat("MMMM d, yyyy", Locale.UK);
    private static final long ONE_HOUR = TimeUnit.HOURS.toMillis(1);
    private static final long ONE_DAY = TimeUnit.DAYS.toMillis(1);
    private static final long ONE_WEEK = TimeUnit.DAYS.toMillis(7);

    private final Date nyDate = new GregorianCalendar(Calendar.getInstance().get(Calendar.YEAR), Calendar.JANUARY, 1).getTime();

    private static final int TYPE_HEADER = 0;
    private static final int TYPE_ITEM = 1;

    public boolean isStranger() {
        return isStranger;
    }

    public RandoListAdapter(Context context, boolean isStranger, FirebaseAnalytics firebaseAnalytics) {
        super(RandoDAO.getCursor(context, isStranger));
        mContext = context;
        this.isStranger = isStranger;
        setHasHeader(!isStranger);
        this.firebaseAnalytics = firebaseAnalytics;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup container, int position) {
        if (hasHeader() && position == TYPE_HEADER) {
            View v = LayoutInflater.from(container.getContext()).inflate(R.layout.header_item, container, false);
            return new HeaderViewHolder(v);
        } else {
            if (imageSize == 0) {
                imageSize = getRandoImageSize(container);
            }
            View convertView = LayoutInflater.from(container.getContext()).inflate(R.layout.rando_item, container, false);
            RandoViewHolder holder = new RandoViewHolder(convertView, imageSize);
            addListenersToHolder(holder);
            return holder;
        }
    }

    @Override
    public int getItemCount() {
        if (hasHeader()) {
            return super.getItemCount() + 1;
        } else return super.getItemCount();
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0 && hasHeader())
            return TYPE_HEADER;
        return TYPE_ITEM;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, Cursor cursor) {
        if (viewHolder instanceof RandoViewHolder) {
            RandoViewHolder randoHolder = (RandoViewHolder) viewHolder;
            recycle(randoHolder);

            randoHolder.rando = RandoDAO.cursorToRando(cursor);
            randoHolder.position = cursor.getPosition();

            String timestamp = randoHolder.rando.date.after(nyDate) ?
                    formatTimeStamp(randoHolder.rando.date) : DATE_YEAR_FORMAT.format(randoHolder.rando.date);
            randoHolder.timestamp.setText(timestamp.toUpperCase());

            setRatingIcon(randoHolder, false);
            loadImages(randoHolder.randoItemLayout.getContext(), randoHolder, randoHolder.rando);

            if (randoHolder.rando.isUnwanted()) {
                randoHolder.unwantedRandoView = new UnwantedRandoView(randoHolder.randoItemLayout.getContext());
                RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(imageSize, imageSize);
                //insert Unwanted view at index 1, right after "view_switcher"
                randoHolder.randoItemLayout.addView(randoHolder.unwantedRandoView, layoutParams);
            } else {
                if (randoHolder.rando.isToUpload()) {
                    randoHolder.uploadingProgress = new RoundProgress(randoHolder.randoItemLayout.getContext(), (float) (imageSize / 2.0) - 8);
                    RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(imageSize, imageSize);
                    randoHolder.randoItemLayout.addView(randoHolder.uploadingProgress, layoutParams);
                } else {
                    setAnimations(randoHolder);
                }
            }
        } else if (viewHolder instanceof HeaderViewHolder) {
            HeaderViewHolder headerHolder = (HeaderViewHolder) viewHolder;
            headerHolder.likes.setText(String.valueOf(
                    Preferences.getUserStatistics(mContext).getLikes()));
            headerHolder.dislikes.setText(String.valueOf(
                    Preferences.getUserStatistics(mContext).getDislikes()));
        }
    }

    public int findElementById(String randoId) {
        int randoIdColumn = mCursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_USER_RANDO_ID);
        for (mCursor.moveToFirst(); !mCursor.isAfterLast(); mCursor.moveToNext()) {
            if (mCursor.getString(randoIdColumn).equals(randoId)) {
                return mCursor.getPosition();
            }
        }
        return 0;
    }

    private String formatTimeStamp(Date date) {
        long diff = System.currentTimeMillis() - date.getTime();

        if (diff < ONE_HOUR) {
            long time = TimeUnit.MINUTES.convert(diff, TimeUnit.MILLISECONDS);
            return String.format("%s %s", time, time <= 1 ? "minute ago" : "minutes ago");
        } else if (diff < ONE_DAY) {
            long time = TimeUnit.HOURS.convert(diff, TimeUnit.MILLISECONDS);
            return String.format("%s %s", time, time <= 1 ? "hour ago" : "hours ago");
        } else if (diff < ONE_WEEK) {
            long time = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
            return String.format("%s %s", time, time <= 1 ? "day ago" : "days ago");
        } else return DATE_FORMAT.format(date);
    }

    private void addListenersToHolder(final RandoViewHolder holder) {
        View.OnClickListener randoOnClickListener = createRandoOnClickListener(holder);
        holder.image.setOnClickListener(randoOnClickListener);
        holder.map.setOnClickListener(randoOnClickListener);

        View.OnLongClickListener onLongClickListener = new RandoOnLongTapListener(this, holder, firebaseAnalytics);
        holder.image.setOnLongClickListener(onLongClickListener);
        holder.map.setOnLongClickListener(onLongClickListener);

        if (isStranger) {
            holder.rateButton.setOnClickListener(new RateButtonOnClickListener(this, holder, firebaseAnalytics));
        }
    }


    private View.OnClickListener createRandoOnClickListener(final RandoViewHolder holder) {
        return new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (holder.rando.isToUpload()) {
                    LayoutInflater inflater = (LayoutInflater) v.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    final View uploadingToast = inflater.inflate(R.layout.uploading_toast, null);

                    FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                    layoutParams.gravity = Gravity.CENTER;
                    ((FrameLayout) (holder.image.getParent())).addView(uploadingToast, 1, layoutParams);
                    Animator alphaAnimator = ObjectAnimator.ofFloat(uploadingToast, "alpha", 1, 0.2f).setDuration(700);
                    alphaAnimator.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            if (uploadingToast != null) {
                                ((FrameLayout) (holder.image.getParent())).removeView(uploadingToast);
                            }
                        }
                    });
                    alphaAnimator.setStartDelay(1500);
                    alphaAnimator.start();
                    return;
                }
                if (holder.rando.isUnwanted()) {
                    Analytics.logClickUnwantedRando(firebaseAnalytics);
                    AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext());
                    builder.setNegativeButton(R.string.delete_rando, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            Analytics.logDeleteUnwantedRandoDialog(firebaseAnalytics);
                            try {
                                holder.showSpinner(true);
                                API.delete(holder.rando.randoId, v.getContext(), new NetworkResultListener(mContext) {
                                    @Override
                                    public void onOk() {
                                        RandoDAO.deleteRandoByRandoId(v.getContext(), holder.rando.randoId);

                                        notifyItemRemoved(holder.position);

                                        makeText(v.getContext(), R.string.rando_deleted,
                                                Toast.LENGTH_LONG).show();
                                        holder.showSpinner(false);
                                    }

                                    @Override
                                    protected void onFail(Error error) {
                                        makeText(v.getContext(), R.string.error_unknown_err,
                                                Toast.LENGTH_LONG).show();
                                        holder.showSpinner(false);
                                    }
                                });
                            } catch (Exception e) {
                                makeText(v.getContext(), R.string.error_unknown_err,
                                        Toast.LENGTH_LONG).show();
                                holder.showSpinner(false);

                            }
                        }
                    });
                    builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            Analytics.logCancelUnwantedRandoDialog(firebaseAnalytics);
                            return;
                        }
                    }).setTitle(R.string.rando_excluded).setMessage(R.string.rando_excluded_text).create().show();

                } else {
                    if (holder.animationInProgress) return;
                    if (holder.circleMenu != null) {
                        holder.circleMenu.closeMenu();
                        return;
                    }
                    if (holder.ratingMenu != null) {
                        holder.ratingMenu.closeMenu();
                        return;
                    }
                    if (isStranger) {
                        Analytics.logTapStrangerRando(firebaseAnalytics);
                    } else {
                        Analytics.logTapOwnRando(firebaseAnalytics);
                    }
                    holder.viewSwitcher.showNext();
                    holder.isMap = !holder.isMap;
                    if (holder.rando.isMapEmpty() && holder.isMap && holder.landingImage == null) {
                        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams((int) (imageSize * 0.05f), (int) (imageSize * 0.05f));

                        final Animation anim = AnimationUtils.loadAnimation(holder.viewSwitcher.getContext(), R.anim.flow_map);
                        anim.setFillAfter(true);
                        final ImageView floatingRando = new android.support.v7.widget.AppCompatImageView(holder.randoItemLayout.getContext()) {

                            @Override
                            protected void onAnimationEnd() {
                                super.onAnimationEnd();
                                holder.landingImage.clearAnimation();
                                holder.landingImage.startAnimation(anim);
                            }
                        };
                        floatingRando.setImageResource(R.drawable.ic_launcher);
                        holder.landingImage = floatingRando;

                        layoutParams.leftMargin = (int) (imageSize * 0.09f);
                        layoutParams.topMargin = (int) (imageSize * 0.20f);
                        ((FrameLayout) (holder.map.getParent())).addView(floatingRando, 1, layoutParams);

                        floatingRando.startAnimation(anim);
                    }
                }
            }
        };
    }


    public void setRatingIcon(RandoViewHolder holder, boolean doAnimation) {
        if (holder.rando.rating == null || holder.rando.rating == 0 || holder.rando.rating == 2) {
            if (!isStranger) {
                holder.rateButton.setVisibility(View.GONE);
            } else {
                holder.rateButton.setVisibility(View.VISIBLE);
                holder.rateButton.setImageResource(R.drawable.ic_thumb_up_white_24dp);
                holder.rateButton.setBackgroundResource(R.drawable.round_button_grey);
            }
        } else {
            switch (holder.rando.rating) {
                case 1:
                    holder.rateButton.setVisibility(View.VISIBLE);
                    if (doAnimation) {
                        holder.rateButton.flipView(R.drawable.ic_thumb_down_white_24dp, R.drawable.round_button_red, null);
                    } else {
                        holder.rateButton.setImageResource(R.drawable.ic_thumb_down_white_24dp);
                        holder.rateButton.setBackgroundResource(R.drawable.round_button_red);
                    }
                    break;
                case 3:
                    holder.rateButton.setVisibility(View.VISIBLE);
                    if (doAnimation) {
                        holder.rateButton.flipView(R.drawable.ic_thumb_up_white_24dp, R.drawable.round_button_green, null);
                    } else {
                        holder.rateButton.setImageResource(R.drawable.ic_thumb_up_white_24dp);
                        holder.rateButton.setBackgroundResource(R.drawable.round_button_green);
                    }
                    break;
                default:
                    holder.rateButton.setVisibility(View.VISIBLE);
                    holder.rateButton.setImageResource(R.drawable.ic_thumb_up_white_24dp);
                    holder.rateButton.setBackgroundResource(R.drawable.round_button_grey);
                    break;
            }
        }
    }

    private void recycle(RandoViewHolder holder) {
        holder.animationInProgress = false;

        cancelRequests(holder);

        recycleViewSwitcher(holder.viewSwitcher);

        holder.image.setImageBitmap(null);
        holder.map.setImageBitmap(null);
        holder.isMap = false;

        holder.image.setAlpha(1f);
        holder.map.setAlpha(1f);
        holder.showSpinner(false);

        holder.recycleCircleMenu();
        holder.recycleRatingMenu();

        if (holder.unwantedRandoView != null) {
            holder.unwantedRandoView.clearAnimation();
            holder.randoItemLayout.removeView(holder.unwantedRandoView);
            holder.unwantedRandoView = null;
        }

        if (holder.uploadingProgress != null) {
            holder.uploadingProgress.clearAnimation();
            holder.randoItemLayout.removeView(holder.uploadingProgress);
            holder.uploadingProgress = null;
        }

        if (holder.landingImage != null) {
            holder.landingImage.clearAnimation();
            ((FrameLayout) (holder.map.getParent())).removeView(holder.landingImage);
            holder.landingImage = null;
        }

        holder.rando = null;
    }


    private void recycleViewSwitcher(ViewSwitcher viewSwitcher) {
        //disable animation for immediately and undetectable switching to zero child:
        viewSwitcher.clearAnimation();
        viewSwitcher.setInAnimation(null);
        viewSwitcher.setOutAnimation(null);
        viewSwitcher.setDisplayedChild(0);
    }

    private void cancelRequests(RandoViewHolder holder) {
        if (holder.randoContainer != null) {
            holder.randoContainer.cancelRequest();
            holder.randoContainer = null;
        }
        if (holder.mapContainer != null) {
            holder.mapContainer.cancelRequest();
            holder.mapContainer = null;
        }
    }

    private int getRandoImageSize(ViewGroup container) {
        return container.getWidth() - container.getContext().getResources().getDimensionPixelSize(R.dimen.rando_padding_portrait_column_left)
                - container.getContext().getResources().getDimensionPixelSize(R.dimen.rando_padding_portrait_column_right);
    }

    private void setAnimations(final RandoViewHolder holder) {
        final Animation[] leftToRightAnimation = AnimationFactory.flipAnimation(imageSize, AnimationFactory.FlipDirection.LEFT_RIGHT, 350, null);
        final Animation[] rightToLeftAnimation = AnimationFactory.flipAnimation(imageSize, AnimationFactory.FlipDirection.RIGHT_LEFT, 350, null);

        holder.viewSwitcher.setOutAnimation(leftToRightAnimation[0]);
        holder.viewSwitcher.setInAnimation(leftToRightAnimation[1]);

        Animation.AnimationListener outAnimationListener = new AnimationListenerAdapter() {
            @Override
            public void onAnimationStart(Animation animation) {
                holder.animationInProgress = true;
            }
        };
        leftToRightAnimation[0].setAnimationListener(outAnimationListener);
        leftToRightAnimation[1].setAnimationListener(new AnimationListenerAdapter() {
            @Override
            public void onAnimationEnd(Animation animation) {
                holder.viewSwitcher.setOutAnimation(rightToLeftAnimation[0]);
                holder.viewSwitcher.setInAnimation(rightToLeftAnimation[1]);
                holder.animationInProgress = false;
            }
        });

        rightToLeftAnimation[0].setAnimationListener(outAnimationListener);
        rightToLeftAnimation[1].setAnimationListener(new AnimationListenerAdapter() {
            @Override
            public void onAnimationEnd(Animation animation) {
                holder.viewSwitcher.setOutAnimation(leftToRightAnimation[0]);
                holder.viewSwitcher.setInAnimation(leftToRightAnimation[1]);
                holder.animationInProgress = false;
            }
        });
    }

    private void loadImages(final Context context, final RandoViewHolder holder, final Rando rando) {
        if (rando.imageURLSize.small != null && !URLUtil.isNetworkUrl(rando.imageURLSize.small) && !rando.imageURLSize.small.isEmpty()) {
            loadFile(holder, rando.imageURL);
            return;
        }

        loadImage(context, holder, rando.getBestImageUrlBySize(imageSize), Priority.HIGH);
        if (rando.isMapEmpty()) {
            holder.map.setImageResource(R.drawable.flat_map_for_vec);
        } else {
            loadMapImage(context, holder, rando.getBestMapUrlBySize(imageSize), Priority.LOW);
        }
    }

    private void loadFile(final RandoViewHolder holder, final String filePath) {
        if (holder.image != null) {
            holder.image.setImageBitmap(BitmapUtil.decodeSampledBitmap(filePath, imageSize, imageSize));
        }
        if (holder.map != null) {
            holder.map.setImageResource(R.drawable.rando_pairing);
        }
    }

    private void loadImage(final Context context, final RandoViewHolder viewHolder, final String url, Priority priority) {
        if (TextUtils.isEmpty(url)) {
            if (viewHolder.image != null) {
                viewHolder.image.setImageResource(R.drawable.rando_pairing);
            }
            return;
        }

        if (URLUtil.isValidUrl(url)) {
            Log.d(RandoListAdapter.class, "image url: ", url);
            viewHolder.randoContainer = VolleySingleton.getInstance(context).getImageLoader().get(url, new ImageLoader.ImageListener() {
                @Override
                public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
                    if (viewHolder.image != null && response.getBitmap() != null) {
                        viewHolder.image.setImageBitmap(response.getBitmap());
                    } else if (viewHolder.image != null && response.getBitmap() == null) {
                        viewHolder.image.setImageResource(R.drawable.rando_loading);
                    }
                }

                @Override
                public void onErrorResponse(VolleyError error) {
                    Log.e(RandoListAdapter.class, "VolleyError when load rando image: ", url, "with imageSize = ", String.valueOf(imageSize), " , because ", error.getMessage());
                    if (viewHolder.image != null) {
                        viewHolder.image.setImageResource(R.drawable.rando_error);
                    } else {
                        viewHolder.needSetImageError = true;
                    }
                }
            }, ImageView.ScaleType.CENTER, imageSize, imageSize, priority);
        } else {
            Log.e(RandoListAdapter.class, "Ignore rando image because url: ", url, " incorrect");
            if (viewHolder.image != null) {
                viewHolder.image.setImageResource(R.drawable.rando_error);
            } else {
                viewHolder.needSetImageError = true;
            }
        }
    }

    private void loadMapImage(final Context context, final RandoViewHolder viewHolder, final String url, Priority priority) {
        if (URLUtil.isValidUrl(url)) {
            Log.d(RandoListAdapter.class, "map url: ", url);
            viewHolder.mapContainer = VolleySingleton.getInstance(context).getImageLoader().get(url, new ImageLoader.ImageListener() {
                @Override
                public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
                    if (viewHolder.map != null && response.getBitmap() != null) {
                        viewHolder.map.setImageBitmap(response.getBitmap());
                    } else if (viewHolder.map != null && response.getBitmap() == null) {
                        viewHolder.map.setImageResource(R.drawable.rando_loading);
                    }
                }

                @Override
                public void onErrorResponse(VolleyError error) {
                    Log.e(RandoListAdapter.class, "VolleyError when load map image: ", url, "with imageSize = ", String.valueOf(imageSize), " , because ", error.getMessage());
                    if (viewHolder.map != null) {
                        viewHolder.map.setImageResource(R.drawable.rando_error);
                    } else {
                        viewHolder.needSetMapError = true;
                    }
                }
            }, ImageView.ScaleType.CENTER, imageSize, imageSize, priority);
        } else {
            Log.d(RandoListAdapter.class, "Ignore map image because url: ", url, " incorrect");
            if (viewHolder.map != null) {
                viewHolder.map.setImageResource(R.drawable.rando_error);
            } else {
                viewHolder.needSetMapError = true;
            }
        }
    }


    public static class HeaderViewHolder extends RecyclerView.ViewHolder {
        private TextView likes;
        private TextView dislikes;
        private TextView logo;

        public HeaderViewHolder(View itemView) {
            super(itemView);
            this.likes = itemView.findViewById(R.id.my_likes);
            this.dislikes = itemView.findViewById(R.id.my_dislikes);
            this.logo = itemView.findViewById(R.id.rando_logo);
        }
    }

    public static class RandoViewHolder extends RecyclerView.ViewHolder {
        public Rando rando;

        public int position;
        public int imageSize;

        public RelativeLayout randoItemLayout;

        public UnwantedRandoView unwantedRandoView;

        public RoundProgress uploadingProgress;
        public ImageView landingImage;

        public boolean animationInProgress = false;

        public ViewSwitcher viewSwitcher;
        public RoundedImageView image;
        public TextView timestamp;
        public RoundedImageView map;
        public boolean isMap;

        public CircleMenu circleMenu;
        public FlipImageView rateButton;
        public CircleMenu ratingMenu;

        public ProgressBar spinner;

        public ImageLoader.ImageContainer randoContainer;
        public ImageLoader.ImageContainer mapContainer;

        public boolean needSetImageError = false;
        public boolean needSetMapError = false;


        public RandoViewHolder(View itemView, int imageSize) {
            super(itemView);
            randoItemLayout = itemView.findViewWithTag("rando_item_layout");

            viewSwitcher = itemView.findViewWithTag("viewSwitcher");

            image = itemView.findViewWithTag("image");
            image.setTag(null);

            timestamp = itemView.findViewWithTag("timestamp");
            map = itemView.findViewWithTag("map");

            this.imageSize = imageSize;

            ViewSwitcher.LayoutParams randoImagesLayout = new ViewSwitcher.LayoutParams(imageSize, imageSize);
            image.setLayoutParams(randoImagesLayout);
            map.setLayoutParams(randoImagesLayout);

            rateButton = itemView.findViewWithTag("rating");
        }

        public void showSpinner(boolean show) {
            if (show) {
                spinner = new ProgressBar(randoItemLayout.getContext(), null, android.R.attr.progressBarStyleLarge);
                RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
                spinner.setIndeterminate(true);
                randoItemLayout.addView(spinner, randoItemLayout.getChildCount(), layoutParams);
            } else if (this.spinner != null) {
                randoItemLayout.removeView(spinner);
                spinner = null;
            }
        }

        public void recycleCircleMenu() {
            if (circleMenu != null) {
                circleMenu.closeMenu();
                randoItemLayout.removeView(circleMenu);
                circleMenu = null;
            }
        }

        public void recycleRatingMenu() {
            if (ratingMenu != null) {
                ratingMenu.closeMenu();
                randoItemLayout.removeView(ratingMenu);
                ratingMenu = null;
            }
        }
    }

} public class API {

    protected static final String PROTOCOL_CHARSET = "utf-8";

    public static void signup(final String email, final String password, final Context context, final NetworkResultListener resultListener) {
        Map<String, String> params = new HashMap<>();
        params.put(SIGNUP_EMAIL_PARAM, email);
        params.put(SIGNUP_PASSWORD_PARAM, password);
        params.put(FIREBASE_INSTANCE_ID_PARAM, Preferences.getFirebaseInstanceId(context));

        VolleySingleton.getInstance(context).getRequestQueue().add(new JsonObjectRequest(Request.Method.POST, SIGNUP_URL, new JSONObject(params), new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                try {
                    String authToken = response.getString(Constants.AUTH_TOKEN_PARAM);
                    Preferences.setAuthToken(context, authToken);
                } catch (JSONException e) {
                    Log.e(API.class, "Parse signup response failed", e);
                }

                if (resultListener != null) {
                    resultListener.onOk();
                }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Response<JSONObject> resp = parseNetworkResponse(error.networkResponse);
                if (resultListener != null) {
                    resultListener.onError(processServerError(resp.result));
                }
            }
        }));
    }

    public static void statistics(final Context context, final NetworkResultListener resultListener) {
        Log.d(API.class, "API.statistics");

        VolleySingleton.getInstance(context).getRequestQueue().add(new BackgroundPreprocessedRequest(Request.Method.GET, STATISTICS_URL, null, null, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                Preferences.setUserStatistics(context, Statistics.from(response));
                Intent intent = new Intent(Constants.SYNC_STATISTICS_EVENT);
                PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
                AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
                alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), pendingIntent);
                if (resultListener != null) {
                    resultListener.onOk();
                }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Response<JSONObject> resp = parseNetworkResponse(error.networkResponse);
                if (resultListener != null) {
                    resultListener.onError(processServerError(resp.result));
                }
            }
        }).setHeaders(getHeaders(context)));
    }


    public static void google(String email, String token, final Context context, final NetworkResultListener resultListener) {
        Map<String, String> params = new HashMap<>();
        params.put(GOOGLE_EMAIL_PARAM, email);
        params.put(GOOGLE_TOKEN_PARAM, token);
        params.put(FIREBASE_INSTANCE_ID_PARAM, Preferences.getFirebaseInstanceId(context));

        VolleySingleton.getInstance(context).getRequestQueue().add(new JsonObjectRequest(Request.Method.POST, GOOGLE_URL, new JSONObject(params), new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                try {
                    String authToken = response.getString(Constants.AUTH_TOKEN_PARAM);
                    Preferences.setAuthToken(context, authToken);
                } catch (JSONException e) {
                    Log.e(API.class, "Parse google login response failed", e);
                }
                if (resultListener != null) {
                    resultListener.onOk();
                }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Response<JSONObject> resp = parseNetworkResponse(error.networkResponse);
                if (resultListener != null) {
                    resultListener.onError(processServerError(resp.result));
                }
            }
        }));
    }

    public static void anonymous(String uuid, final Context context, final NetworkResultListener resultListener) {
        Map<String, String> params = new HashMap<>();
        params.put(ANONYMOUS_ID_PARAM, uuid);
        params.put(FIREBASE_INSTANCE_ID_PARAM, Preferences.getFirebaseInstanceId(context));

        VolleySingleton.getInstance(context).getRequestQueue().add(new JsonObjectRequest(Request.Method.POST, ANONYMOUS_URL, new JSONObject(params), new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                try {
                    String authToken = response.getString(Constants.AUTH_TOKEN_PARAM);
                    Preferences.setAuthToken(context, authToken);
                } catch (JSONException e) {
                    Log.e(API.class, "Parse anonymous login response failed", e);
                }
                if (resultListener != null) {
                    resultListener.onOk();
                }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Response<JSONObject> resp = parseNetworkResponse(error.networkResponse);
                if (resultListener != null) {
                    resultListener.onError(processServerError(resp.result));
                }
            }
        }));
    }

    public static void logout(final Context context, final NetworkResultListener resultListener) {
        BackgroundPreprocessedRequest request = new BackgroundPreprocessedRequest(Request.Method.POST, LOGOUT_URL, null, null, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                if (resultListener != null) {
                    resultListener.onOk();
                }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Response<JSONObject> resp = parseNetworkResponse(error.networkResponse);
                if (resultListener != null) {
                    resultListener.onError(processServerError(resp.result));
                }
            }
        });

        request.setHeaders(getHeaders(context));
        request.setRetryPolicy(new DefaultRetryPolicy(API_CONNECTION_TIMEOUT, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

        VolleySingleton.getInstance(context).getRequestQueue().add(request);
    }

    public static void syncUserAsync(final Context context, final Response.Listener<JSONObject> syncListener, ErrorResponseListener errorResponseListener) {
        Log.d(API.class, "API.syncUserAsync");
        BackgroundPreprocessedRequest request = new BackgroundPreprocessedRequest(Request.Method.GET, FETCH_USER_URL, null, new UserFetchResultListener(context, new OnFetchUser() {
            @Override
            public void onFetch(User user) {
                Log.d(API.class, "Fetched ", user.toString(), " user. and procesing it in background thread.");
                List<Rando> dbRandos = RandoDAO.getAllRandos(context);
                int totalUserRandos = user.randosIn.size() + user.randosOut.size();
                if (totalUserRandos != dbRandos.size()
                        || !(dbRandos.containsAll(user.randosIn)
                        && dbRandos.containsAll(user.randosOut))) {
                    RandoDAO.clearRandos(context);
                    RandoDAO.insertRandos(context, user.randosIn);
                    RandoDAO.insertRandos(context, user.randosOut);
                    Notification.sendSyncNotification(context, totalUserRandos, UPDATED);
                } else {
                    Notification.sendSyncNotification(context, totalUserRandos, NOT_UPDATED);
                }
            }
        }), syncListener, errorResponseListener);
        request.setHeaders(getHeaders(context));

        VolleySingleton.getInstance(context).getRequestQueue().add(request);
    }

    public static void uploadImage(final RandoUpload randoUpload, final Context context, final UploadRandoListener uploadListener, final Response.ErrorListener errorListener) {

        VolleyMultipartRequest uploadMultipart = new VolleyMultipartRequest(UPLOAD_RANDO_URL, getHeaders(context), new Response.Listener<NetworkResponse>() {
            @Override
            public void onResponse(NetworkResponse response) {
                Log.d(API.class, "Rando Uploaded Successfully:", randoUpload.toString());
                String resultResponse = new String(response.data);
                if (uploadListener != null) {
                    uploadListener.onUpload(Rando.fromJSON(resultResponse, Rando.Status.OUT));
                }
            }
        }, errorListener) {
            @Override
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<>(2);
                params.put(LATITUDE_PARAM, randoUpload.latitude);
                params.put(LONGITUDE_PARAM, randoUpload.longitude);
                return params;
            }

            @Override
            protected Map<String, DataPart> getByteData() {
                Map<String, DataPart> params = new HashMap<>(1);
                File randoFile = new File(randoUpload.file);
                params.put(IMAGE_PARAM, new DataPart(randoFile.getName(), FileUtil.readFile(randoFile), IMAGE_MIME_TYPE));

                return params;
            }

            @Override
            public Priority getPriority() {
                return Priority.LOW;
            }
        };
        uploadMultipart.setRetryPolicy(new DefaultRetryPolicy(UPLOAD_CONNECTION_TIMEOUT, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

        VolleySingleton.getInstance(context).getRequestQueue().add(uploadMultipart);
    }

    public static void delete(final String randoId, final Context context, final NetworkResultListener resultListener) throws Exception {
        Log.d(API.class, "Deleting Rando:", randoId);
        BackgroundPreprocessedRequest request = new BackgroundPreprocessedRequest(Request.Method.POST, DELETE_URL + randoId, null, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                try {
                    if ("delete".equals(response.getString("command")) &&
                            "done".equals(response.getString("result"))) {
                        Log.d(API.class, "Deleted Rando:", randoId);
                        RandoDAO.deleteRandoByRandoId(context, randoId);
                    }
                } catch (JSONException e) {
                    Log.e(API.class, "Error Deleting Rando", e);
                    resultListener.onError(null);
                }
            }
        }, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                resultListener.onOk();
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e(API.class, "Error Deleting Rando", error);
                Response<JSONObject> resp = parseNetworkResponse(error.networkResponse);
                if (resultListener != null) {
                    resultListener.onError(processServerError(resp.result));
                }
            }
        });

        request.setHeaders(getHeaders(context));
        request.setRetryPolicy(new DefaultRetryPolicy(API_CONNECTION_TIMEOUT, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

        VolleySingleton.getInstance(context).getRequestQueue().add(request);
    }

    public static void report(final String randoId, final Context context, final NetworkResultListener resultListener) throws Exception {
        Log.d(API.class, "Reporting Rando:", randoId);
        BackgroundPreprocessedRequest request = new BackgroundPreprocessedRequest(Request.Method.POST, REPORT_URL + randoId, null, null, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                try {
                    if ("report".equals(response.getString("command")) &&
                            "done".equals(response.getString("result"))) {
                        Log.d(API.class, "Reported Rando:", randoId);
                        resultListener.onOk();
                    }
                } catch (JSONException e) {
                    Log.e(API.class, "Error Reporting Rando", e);
                    resultListener.onError(null);
                }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e(API.class, "Error Reporting Rando", error);
                Response<JSONObject> resp = parseNetworkResponse(error.networkResponse);
                if (resultListener != null) {
                    resultListener.onError(processServerError(resp.result));
                }
            }
        });

        request.setHeaders(getHeaders(context));
        request.setRetryPolicy(new DefaultRetryPolicy(API_CONNECTION_TIMEOUT, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

        VolleySingleton.getInstance(context).getRequestQueue().add(request);
    }

    public static void rate(final String randoId, final Context context, final int rating, final NetworkResultListener reportRandoListener) {
        Log.d(API.class, "Rate Rando:", randoId);
        BackgroundPreprocessedRequest request = new BackgroundPreprocessedRequest(Request.Method.POST, RATE_URL + randoId + "?rating=" + rating, null, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                try {
                    if ("rate".equals(response.getString("command")) &&
                            "done".equals(response.getString("result"))) {
                        Log.d(API.class, "Rated Rando:", randoId);
                        Rando rando = RandoDAO.getRandoByRandoId(context, randoId);
                        rando.rating = rating;
                        RandoDAO.updateRando(context, rando);
                    }
                } catch (JSONException e) {
                    Log.e(API.class, "Error Rating Rando", e);
                    reportRandoListener.onError(null);
                }
            }
        }, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                reportRandoListener.onOk();
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e(API.class, "Error Rating Rando", error);
                reportRandoListener.onError(null);
            }
        });

        request.setHeaders(getHeaders(context));
        request.setRetryPolicy(new DefaultRetryPolicy(API_CONNECTION_TIMEOUT, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

        VolleySingleton.getInstance(context).getRequestQueue().add(request);
    }

    private static Map<String, String> getHeaders(Context context) {
        Map<String, String> headers = new HashMap<>(2);
        headers.put(AUTHORIZATION_HEADER, "Token " + Preferences.getAuthToken(context));
        if (!Preferences.getFirebaseInstanceId(context).isEmpty()) {
            headers.put(FIREBASE_INSTANCE_ID_HEADER, Preferences.getFirebaseInstanceId(context));
        }
        return headers;
    }

    private static Error processServerError(JSONObject json) {
        if (json == null) {
            return new Error().setCode(R.string.error_no_network);
        }

        try {
            switch (json.getInt(ERROR_CODE_PARAM)) {
                case Constants.UNAUTHORIZED_CODE:
                    return new Error().setCode(R.string.error_400);
                case Constants.FORBIDDEN_CODE: {
                    String resetTime = "";
                    try {
                        String message = json.getString("message");
                        Matcher matcher = Pattern.compile("\\d+").matcher(message);
                        if (matcher.find()) {
                            long resetTimeUnix = Long.parseLong(matcher.group());
                            resetTime = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT).format(new Date(resetTimeUnix));
                        }
                    } catch (Exception e) {
                        Log.w(API.class, "Error when try build ban message for user: ", e.getMessage());
                    }
                    return new Error().setCode(R.string.error_411).setMessage(resetTime);
                }
                default: {
                    //TODO: implement all code handling in switch and replace server "message" with default value.
                    return new Error().setMessage(json.getString("message"));
                }
            }
        } catch (JSONException exc) {
            return processError(exc);
        }
    }

    private static Error processError(Exception exc) {
        //We don't want to log Connectivity exceptions
        if (exc instanceof UnknownHostException
                || exc instanceof ConnectException) {
            return new Error().setCode(R.string.error_no_network);
        }
        Crashlytics.logException(exc);
        Log.e(API.class, "processError method", exc);
        return new Error().setCode(R.string.error_unknown_err);
    }

    public static Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
        if (response == null || response.data == null) {
            return Response.error(new ParseError(new Exception("Unknown error or no network")));
        }

        try {
            String jsonString = new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
            return Response.success(new JSONObject(jsonString),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JSONException je) {
            return Response.error(new ParseError(je));
        }
    }

} public class ErrorResponseListener implements Response.ErrorListener {

    private Context context;

    private ErrorResponseListener() {
        
    }

    public ErrorResponseListener(Context context) {
        super();
        this.context = context;
    }

    @Override
    public void onErrorResponse(VolleyError e) {
        if (e.networkResponse != null)
            Log.d(ErrorResponseListener.class, "Network Error", "" + e.networkResponse.statusCode + " " + String.valueOf(e.networkResponse.data));
        // Handle your error types accordingly.For Timeout & No connection error, you can show 'retry' button.
        // For AuthFailure, you can re login with user credentials.
        // For ClientError, 400 & 401, Errors happening on client side when sending api request.
        // In this case you can check how client is forming the api and debug accordingly.
        // For ServerError 5xx, you can do retry or handle accordingly.
        if (e instanceof AuthFailureError) {
            Intent intent = new Intent(Constants.AUTH_FAILURE_BROADCAST_EVENT);
            context.sendBroadcast(intent);
            Log.d(ErrorResponseListener.class, "AuthFailureError: ", e.getStackTrace().toString());
        } else if (e instanceof NetworkError) {
        } else if (e instanceof ServerError) {
        } else if (e instanceof ParseError) {
        } else if (e instanceof NoConnectionError) {
        } else if (e instanceof TimeoutError) {
        }
    }
} public class RandoDAO {

    private RandoDAO() {
    }

    public static synchronized RandoUpload addToUpload(final Context context, RandoUpload randoUpload) {
        if (randoUpload.file == null || randoUpload.date == null) {
            return null;
        }

        ContentValues values = new ContentValues();
        values.put(RandoDBHelper.RandoUploadTable.COLUMN_FILE, randoUpload.file);
        values.put(RandoDBHelper.RandoUploadTable.COLUMN_LATITUDE, randoUpload.latitude);
        values.put(RandoDBHelper.RandoUploadTable.COLUMN_LONGITUDE, randoUpload.longitude);
        values.put(RandoDBHelper.RandoUploadTable.COLUMN_DATE, randoUpload.date.getTime());
        values.put(RandoDBHelper.RandoUploadTable.COLUMN_LAST_TRY_DATE, randoUpload.lastTry.getTime());

        long id = getDB(context).insert(RandoDBHelper.RandoUploadTable.NAME, null, values);

        if (id > 0) {
            randoUpload.id = (int) id;
            return randoUpload;
        } else {
            return null;
        }
    }

    public static synchronized int countAllRandosToUpload(final Context context) {
        Cursor cursor = getDB(context).query(RandoDBHelper.RandoUploadTable.NAME,
                RandoDBHelper.RandoUploadTable.ALL_COLUMNS, null, null, null, null, null, null);
        int result = cursor.getCount();
        cursor.close();
        return result;
    }

    public static synchronized RandoUpload getNextRandoToUpload(final Context context) {
        String dateS = String.valueOf(System.currentTimeMillis() + Constants.UPLOAD_RETRY_TIMEOUT);
        Cursor cursor = getDB(context).query(RandoDBHelper.RandoUploadTable.NAME,
                RandoDBHelper.RandoUploadTable.ALL_COLUMNS, RandoDBHelper.RandoUploadTable.COLUMN_LAST_TRY_DATE
                        + "<" + dateS, null, null, null, RandoDBHelper.RandoUploadTable.COLUMN_DATE + " ASC", "1");

        cursor.moveToFirst();
        RandoUpload rando = null;
        if (!cursor.isAfterLast()) {
            rando = new RandoUpload();
            rando.id = cursor.getInt(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoUploadTable.COLUMN_ID));
            rando.file = cursor.getString(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoUploadTable.COLUMN_FILE));
            rando.latitude = cursor.getString(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoUploadTable.COLUMN_LATITUDE));
            rando.longitude = cursor.getString(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoUploadTable.COLUMN_LONGITUDE));
            long userDate = cursor.getLong(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoUploadTable.COLUMN_DATE));
            long lastTryDate = cursor.getLong(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoUploadTable.COLUMN_LAST_TRY_DATE));
            if (userDate > 0) {
                rando.date = new Date(userDate);
            } else {
                rando.date = new Date();
            }
            if (lastTryDate > 0) {
                rando.lastTry = new Date(lastTryDate);
            } else {
                rando.lastTry = new Date(0);
            }
            cursor.close();
        }
        return rando;
    }

    public static synchronized void updateRandoToUpload(final Context context, RandoUpload rando) {
        ContentValues values = new ContentValues();
        values.put(RandoDBHelper.RandoUploadTable.COLUMN_FILE, rando.file);
        values.put(RandoDBHelper.RandoUploadTable.COLUMN_LATITUDE, rando.latitude);
        values.put(RandoDBHelper.RandoUploadTable.COLUMN_LONGITUDE, rando.longitude);
        values.put(RandoDBHelper.RandoUploadTable.COLUMN_DATE, rando.date.getTime());
        values.put(RandoDBHelper.RandoUploadTable.COLUMN_LAST_TRY_DATE, rando.lastTry.getTime());

        getDB(context).update(RandoDBHelper.RandoUploadTable.NAME, values, RandoDBHelper.RandoTable.COLUMN_ID + " = " + rando.id, null);
    }

    /**
     * Removes RandoToUpload by id
     *
     * @param id of item to remove
     */
    public static synchronized void deleteRandoToUploadById(final Context context, int id) {
        getDB(context).delete(RandoDBHelper.RandoUploadTable.NAME, RandoDBHelper.RandoUploadTable.COLUMN_ID + " = " + id, null);
        Log.i(RandoDAO.class, "Rando to upload deleted with id: ", String.valueOf(id));
    }

    /**
     * Removes all rows from RandoToUpload table
     */

    public static synchronized void clearRandoToUpload(final Context context) {
        getDB(context).delete(RandoDBHelper.RandoUploadTable.NAME, "", null);
        Log.i(RandoDAO.class, "RandosToUpload cleared");
    }

    /**
     * Creates rando and returns instance of created rando.
     *
     * @param rando Rando to insert
     * @return returns instance of created rando or null
     */

    public static synchronized Rando createRando(final Context context, Rando rando) {
        if (rando == null) {
            return null;
        }
        ContentValues values = randoToContentValues(rando);
        long insertId = getDB(context).insert(RandoDBHelper.RandoTable.NAME, null,
                values);
        return getRandoById(context, insertId);
    }

    /**
     * Creates rando and returns instance of created rando.
     *
     * @param rando Rando to insert
     * @return returns instance of created rando or null
     */

    public static synchronized Rando createOrUpdateRandoCheckingByRandoId(final Context context, Rando rando) {
        if (rando == null) {
            return null;
        }
        Rando dbRando = getRandoByRandoId(context, rando.randoId);
        long dbId;
        if (dbRando != null) {
            rando.id = dbRando.id;
            dbId = dbRando.id;
            updateRando(context, rando);
        } else {
            ContentValues values = randoToContentValues(rando);
            dbId = getDB(context).insert(RandoDBHelper.RandoTable.NAME, null,
                    values);
        }
        return getRandoById(context, dbId);
    }

    /**
     * Finds rando instance.
     *
     * @param id Rando id to get
     * @return Rando instance or null if rando hasn't been found
     */

    public static synchronized Rando getRandoById(final Context context, long id) {
        Cursor cursor = getDB(context).query(RandoDBHelper.RandoTable.NAME,
                null, RandoDBHelper.RandoTable.COLUMN_ID + " = " + id, null,
                null, null, null);
        cursor.moveToFirst();
        Rando newRando = cursorToRando(cursor);
        cursor.close();
        return newRando;
    }

    /**
     * Finds rando instance.
     *
     * @param randoId Rando id to get
     * @return Rando instance or null if rando hasn't been found
     */

    public static synchronized Rando getRandoByRandoId(final Context context, String randoId) {
        Cursor cursor = getDB(context).query(RandoDBHelper.RandoTable.NAME,
                null, RandoDBHelper.RandoTable.COLUMN_USER_RANDO_ID + " = '" + randoId + "'", null,
                null, null, null);
        cursor.moveToFirst();
        Rando rando = cursorToRando(cursor);
        cursor.close();
        return rando;
    }

    /**
     * Deletes rando instance from DB by randoId.
     *
     * @param randoId randoId
     */
    public static synchronized void deleteRandoByRandoId(final Context context, String randoId) {
        getDB(context).delete(RandoDBHelper.RandoTable.NAME, RandoDBHelper.RandoTable.COLUMN_USER_RANDO_ID
                + " = '" + randoId + "'", null);
        Log.i(RandoDAO.class, "Rando deleted with randoId: ", String.valueOf(randoId));
    }

    /**
     * clear rando pairs Table in DB.
     */
    public static synchronized void clearRandos(final Context context) {
        getDB(context).delete(RandoDBHelper.RandoTable.NAME, "", null);
        Log.i(RandoDAO.class, "Randos table cleared");
    }

    /**
     * Updates rando instance from DB.
     *
     * @param rando Rando to update
     */
    public static synchronized void updateRando(final Context context, Rando rando) {
        long id = rando.id;

        ContentValues values = randoToContentValues(rando);

        getDB(context).update(RandoDBHelper.RandoTable.NAME, values, RandoDBHelper.RandoTable.COLUMN_ID + " = " + id, null);
        Log.w(RandoDAO.class, "Rando updated with id: ", String.valueOf(id));
    }

    /**
     * @return all rando instances found in DB
     */
    // ToDo: replace with cursor solution
    public static synchronized List<Rando> getAllRandos(final Context context) {
        List<Rando> randos = new LinkedList<>();

        Cursor cursor = getDB(context).query(RandoDBHelper.RandoTable.NAME,
                null, null, null, null, null, RandoDBHelper.RandoTable.COLUMN_USER_RANDO_DATE + " DESC", null);

        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            Rando rando = cursorToRando(cursor);
            randos.add(rando);
            cursor.moveToNext();
        }
        cursor.close();

        return randos;
    }

    public static synchronized Cursor getCursor(final Context context, boolean isStranger) {

        if (isStranger) {
            return getDB(context).query(RandoDBHelper.RandoTable.NAME,
                    null, RandoDBHelper.RandoTable.COLUMN_RANDO_STATUS
                            + " = '" + Rando.Status.IN.name() + "'", null, null, null, RandoDBHelper.RandoTable.COLUMN_USER_RANDO_DATE + " DESC", null);
        }
        else {
            // randos to upload
            Cursor cursor1 = getDB(context).query(RandoDBHelper.RandoUploadTable.NAME,
                    RandoDBHelper.RandoUploadTable.ALL_COLUMNS, null, null, null, null, RandoDBHelper.RandoUploadTable.COLUMN_DATE + " " + "DESC", null);

            // uploaded randos
            Cursor cursor2 = getDB(context).query(RandoDBHelper.RandoTable.NAME,
                    null, RandoDBHelper.RandoTable.COLUMN_RANDO_STATUS
                            + " = '" + Rando.Status.OUT.name() + "'", null, null, null, RandoDBHelper.RandoTable.COLUMN_USER_RANDO_DATE + " DESC", null);


            return new MergeCursor(new Cursor[] {cursor1, cursor2});
        }

    }

    /**
     * Inserts list of randos into DB
     *
     * @param randos Rando list to insert
     */
    public static synchronized void insertRandos(final Context context, List<Rando> randos) {
        getDB(context).beginTransaction();
        try {
            for (Rando rando : randos) {
                createRando(context, rando);
            }
            getDB(context).setTransactionSuccessful();
        } finally {
            getDB(context).endTransaction();
        }
    }

    private static synchronized SQLiteDatabase getDB(final Context context) {
        return RandoDBHelper.getDatabase(context);
    }

    /**
     * Extracts Rando object from Cursor object
     *
     * @param cursor Cursor pointing to row
     * @return rando object extracted from cursor
     */

    public static Rando cursorToRando(Cursor cursor) {
        if (cursor.getCount() == 0) {
            return null;
        } else {
            Rando rando = new Rando();
            rando.id = cursor.getInt(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_ID));

            rando.randoId = cursor.getString(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_USER_RANDO_ID));
            rando.imageURL = cursor.getString(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_USER_RANDO_URL));
            rando.imageURLSize.small = cursor.getString(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_USER_RANDO_URL_SMALL));
            rando.imageURLSize.medium = cursor.getString(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_USER_RANDO_URL_MEDIUM));
            rando.imageURLSize.large = cursor.getString(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_USER_RANDO_URL_LARGE));
            long userDate = cursor.getLong(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_USER_RANDO_DATE));
            rando.date = userDate != 0 ? new Date(userDate) : null;
            rando.mapURL = cursor.getString(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_USER_MAP_URL));
            rando.mapURLSize.small = cursor.getString(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_USER_MAP_URL_SMALL));
            rando.mapURLSize.medium = cursor.getString(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_USER_MAP_URL_MEDIUM));
            rando.mapURLSize.large = cursor.getString(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_USER_MAP_URL_LARGE));
            rando.status = Rando.Status.valueOf(cursor.getString(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_RANDO_STATUS)));
            rando.detected = cursor.getString(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_DETECTED));
            rando.rating = cursor.getInt(cursor.getColumnIndexOrThrow(RandoDBHelper.RandoTable.COLUMN_RATING));
            return rando;
        }
    }

    /**
     * Converts Rando object into ContentValues object
     *
     * @param rando Rando to convert
     * @return ContentValues representing rando
     */
    private static ContentValues randoToContentValues(Rando rando) {
        ContentValues values = new ContentValues();

        values.put(RandoDBHelper.RandoTable.COLUMN_USER_RANDO_ID, rando.randoId);
        values.put(RandoDBHelper.RandoTable.COLUMN_USER_RANDO_URL, rando.imageURL);
        values.put(RandoDBHelper.RandoTable.COLUMN_USER_RANDO_URL_SMALL, rando.imageURLSize.small);
        values.put(RandoDBHelper.RandoTable.COLUMN_USER_RANDO_URL_MEDIUM, rando.imageURLSize.medium);
        values.put(RandoDBHelper.RandoTable.COLUMN_USER_RANDO_URL_LARGE, rando.imageURLSize.large);
        values.put(RandoDBHelper.RandoTable.COLUMN_USER_RANDO_DATE, rando.date.getTime());
        values.put(RandoDBHelper.RandoTable.COLUMN_USER_MAP_URL, rando.mapURL);
        values.put(RandoDBHelper.RandoTable.COLUMN_USER_MAP_URL_SMALL, rando.mapURLSize.small);
        values.put(RandoDBHelper.RandoTable.COLUMN_USER_MAP_URL_MEDIUM, rando.mapURLSize.medium);
        values.put(RandoDBHelper.RandoTable.COLUMN_USER_MAP_URL_LARGE, rando.mapURLSize.large);
        values.put(RandoDBHelper.RandoTable.COLUMN_RANDO_STATUS, rando.status.name());
        if (rando.detected != null) {
            values.put(RandoDBHelper.RandoTable.COLUMN_DETECTED, rando.detected);
        }
        if (rando.rating != null) {
            values.put(RandoDBHelper.RandoTable.COLUMN_RATING, rando.rating);
        }

        return values;
    }
} public class Rando implements Serializable {

    public enum Status {
        IN, OUT
    }

    public int id;
    public String randoId;
    public String imageURL;
    public UrlSize imageURLSize = new UrlSize();
    public Date date;
    public String mapURL;
    public UrlSize mapURLSize = new UrlSize();
    public Status status;
    public String detected;
    public Integer rating;

    public boolean isToUpload() {
        return Constants.TO_UPLOAD_RANDO_ID.equals(randoId);
    }

    public boolean isUnwanted() {
        return detected != null && detected.contains("\"unwanted\"");
    }

    public static Rando fromJSON(String jsonRandoString, Rando.Status status) {
        try {
            return fromJSON(new JSONObject(jsonRandoString), status);
        } catch (JSONException e){
            return null;
        }
    }

    public static Rando fromJSON(JSONObject jsonRando, Rando.Status status) throws JSONException {
        Rando rando = new Rando();
        JSONObject userRandoUrlSizes = jsonRando.getJSONObject(IMAGE_URL_SIZES_PARAM);
        JSONObject userMapUrlSizes = jsonRando.getJSONObject(MAP_URL_SIZES_PARAM);

        rando.randoId = jsonRando.getString(RANDO_ID_PARAM);
        rando.imageURL = jsonRando.getString(IMAGE_URL_PARAM);
        rando.status = status;
        rando.imageURLSize.small = userRandoUrlSizes.getString(SMALL_PARAM);
        rando.imageURLSize.medium = userRandoUrlSizes.getString(MEDIUM_PARAM);
        rando.imageURLSize.large = userRandoUrlSizes.getString(LARGE_PARAM);

        rando.mapURL = jsonRando.getString(MAP_URL_PARAM);
        rando.mapURLSize.small = userMapUrlSizes.getString(SMALL_PARAM);
        rando.mapURLSize.medium = userMapUrlSizes.getString(MEDIUM_PARAM);
        rando.mapURLSize.large = userMapUrlSizes.getString(LARGE_PARAM);

        rando.date = new Date(jsonRando.getLong(CREATION_PARAM));

        if (jsonRando.has(DETECTED_PARAM)){
            JSONArray detected = jsonRando.getJSONArray(DETECTED_PARAM);
            rando.detected = detected.join(",");
        }

        if (jsonRando.has(RATING_PARAM)){
            rando.rating = jsonRando.getInt(RATING_PARAM);
        }
        return rando;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Rando rando = (Rando) o;

        if (imageURL != null ? !imageURL.equals(rando.imageURL) : rando.imageURL != null)
            return false;
        if (imageURLSize != null ? !imageURLSize.equals(rando.imageURLSize) : rando.imageURLSize != null)
            return false;
        if (mapURL != null ? !mapURL.equals(rando.mapURL) : rando.mapURL != null) return false;
        if (mapURLSize != null ? !mapURLSize.equals(rando.mapURLSize) : rando.mapURLSize != null)
            return false;
        if (randoId != null ? !randoId.equals(rando.randoId) : rando.randoId != null) return false;
        if (status != rando.status) return false;
        if (detected != null ? !detected.equals(rando.detected) : rando.detected != null)
            return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = randoId != null ? randoId.hashCode() : 0;
        result = 31 * result + (imageURL != null ? imageURL.hashCode() : 0);
        result = 31 * result + (imageURLSize != null ? imageURLSize.hashCode() : 0);
        result = 31 * result + (mapURL != null ? mapURL.hashCode() : 0);
        result = 31 * result + (mapURLSize != null ? mapURLSize.hashCode() : 0);
        result = 31 * result + (status != null ? status.hashCode() : 0);
        result = 31 * result + (detected != null ? detected.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "Rando{" +
                "id=" + id +
                ", randoId='" + randoId + '\'' +
                ", imageURL='" + imageURL + '\'' +
                ", imageURLSize=" + imageURLSize +
                ", date=" + date +
                ", mapURL='" + mapURL + '\'' +
                ", mapURLSize=" + mapURLSize +
                ", status=" + status +
                ", detected='" + detected + '\'' +
                ", rating=" + rating +
                ", toUpload=" + isToUpload() +
                '}';
    }

    public String getBestImageUrlBySize(int size) {
        return getBestUrlBySize(size, imageURLSize);
    }

    public String getBestMapUrlBySize(int size) {
        return getBestUrlBySize(size, mapURLSize);
    }

    private String getBestUrlBySize(int imageSize, Rando.UrlSize urls) {
        if (imageSize >= Constants.SIZE_LARGE) {
            return urls.large;
        } else if (imageSize >= Constants.SIZE_MEDIUM) {
            return urls.medium;
        }
        return urls.small;
    }



    public class UrlSize implements Serializable {
        public String small;
        public String medium;
        public String large;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof UrlSize)) return false;

            UrlSize urlSize = (UrlSize) o;

            if (large != null ? !large.equals(urlSize.large) : urlSize.large != null)
                return false;
            if (medium != null ? !medium.equals(urlSize.medium) : urlSize.medium != null)
                return false;
            if (small != null ? !small.equals(urlSize.small) : urlSize.small != null)
                return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = small != null ? small.hashCode() : 0;
            result = 31 * result + (medium != null ? medium.hashCode() : 0);
            result = 31 * result + (large != null ? large.hashCode() : 0);
            return result;
        }
    }

    public boolean isMapEmpty() {
        return mapURLSize == null || (TextUtils.isEmpty(mapURLSize.large) && TextUtils.isEmpty(mapURLSize.medium) && TextUtils.isEmpty(mapURLSize.small));
    }

    public static class DateComparator implements Comparator<Rando> {

        @Override
        public int compare(Rando lhs, Rando rhs) {
            Log.d(Rando.DateComparator.class, "Compare date: ", Long.toString(rhs.date.getTime()), " == ", Long.toString(lhs.date.getTime()), "  > ", Integer.toString((int) (rhs.date.getTime() - lhs.date.getTime())));
            return (int) (rhs.date.getTime() - lhs.date.getTime());
        }
    }
} public class Log {

    public static void i(Class clazz, String... msgs) {
        android.util.Log.i(clazz.getName(), concatenate(msgs));
    }

    public static void d(Class clazz, String... msgs) {
        android.util.Log.d(clazz.getName(), concatenate(msgs));
    }

    public static void w(Class clazz, String... msgs) {
        android.util.Log.w(clazz.getName(), concatenate(msgs));
    }

    public static void e(Class clazz, String... msgs) {
        android.util.Log.e(clazz.getName(), concatenate(msgs));
    }

    public static void e(Class clazz, String comment, Throwable throwable) {
        android.util.Log.e(clazz.getName(), comment + " error:", throwable);
    }

    public static void v(Class clazz, String... msgs) {
        android.util.Log.v(clazz.getName(), concatenate(msgs));
    }

    private static String concatenate(String[] msgs) {
        if (msgs == null) {
            return "";
        }

        StringBuilder sb = new StringBuilder();
        for (String msg : msgs) {
            sb.append(msg).append(" ");
        }
        return sb.toString();
    }

} public class UploadJobScheduler {


    public static void scheduleUpload(Context context) {
        Log.d(UploadJobScheduler.class, "Schedule Job to upload");
        new JobRequest.Builder(UploadJob.TAG)
                .setExecutionWindow(TimeUnit.SECONDS.toMillis(1), TimeUnit.SECONDS.toMillis(10))
                .setBackoffCriteria(TimeUnit.MINUTES.toMillis(2), JobRequest.BackoffPolicy.LINEAR)
                .setRequiresDeviceIdle(false)
                .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
                .setRequirementsEnforced(true)
                .setUpdateCurrent(true)
                .build()
                .schedule();
    }
} public class Analytics {

    private static String TAKE_RANDO = "take_rando";
    private static String UPLOAD_RANDO = "upload_rando";
    private static String SHARE_RANDO = "share_rando";
    private static String DELETE_RANDO = "delete_rando";
    private static String REPORT_RANDO = "report_rando";
    private static String TAP_STRANGER_RANDO = "tap_stranger_rando";
    private static String TAP_OWN_RANDO = "tap_own_rando";
    private static String FORCE_SYNC = "force_sync";
    private static String LOGOUT = "logout";
    private static String LOGIN_SKIP = "login_skip";
    private static String LOGIN_GOOGLE = "login_google";
    private static String LOGIN_EMAIL = "login_email";
    private static String OPEN_TAB_OWN_RANDOS = "open_tab_own_randos";
    private static String OPEN_TAB_STRANGER_RANDOS = "open_tab_stranger_randos";
    private static String OPEN_TAB_SETTINGS = "open_tab_settings";
    private static String SWITCH_CAMERA_TO_FRONT = "switch_camera_to_front";
    private static String SWITCH_CAMERA_TO_BACK = "switch_camera_to_back";
    private static String RATE_RANDO_UP = "rate_rando_up";
    private static String RATE_RANDO_NORMAL = "rate_rando_normal";
    private static String RATE_RANDO_DOWN = "rate_rando_down";

    //unwanted rando events
    private static String CLICK_UNWANTED_RANDO = "click_unwanted_rando";
    private static String DELETE_UNWANTED_RANDO_DIALOG = "delete_unwanted_rando_dialog";
    private static String CANCEL_UNWANTED_RANDO_DIALOG = "cancel_unwanted_rando_dialog";

    public static void logTakeRando(FirebaseAnalytics analytics) {
        analytics.logEvent(TAKE_RANDO, null);
    }

    public static void logUploadRando(FirebaseAnalytics analytics) {
        analytics.logEvent(UPLOAD_RANDO, null);
    }

    public static void logShareRando(FirebaseAnalytics analytics) {
        analytics.logEvent(SHARE_RANDO, null);
    }

    public static void logDeleteRando(FirebaseAnalytics analytics) {
        analytics.logEvent(DELETE_RANDO, null);
    }

    public static void logReportRando(FirebaseAnalytics analytics) {
        analytics.logEvent(REPORT_RANDO, null);
    }

    public static void logTapStrangerRando(FirebaseAnalytics analytics) {
        analytics.logEvent(TAP_STRANGER_RANDO, null);
    }

    public static void logTapOwnRando(FirebaseAnalytics analytics) {
        analytics.logEvent(TAP_OWN_RANDO, null);
    }

    public static void logForceSync(FirebaseAnalytics analytics) {
        analytics.logEvent(FORCE_SYNC, null);
    }

    public static void logLogout(FirebaseAnalytics analytics) {
        analytics.logEvent(LOGOUT, null);
    }

    public static void logLoginSkip(FirebaseAnalytics analytics) {
        analytics.logEvent(LOGIN_SKIP, null);
    }

    public static void logLoginEmail(FirebaseAnalytics analytics) {
        analytics.logEvent(LOGIN_EMAIL, null);
    }

    public static void logLoginGoogle(FirebaseAnalytics analytics) {
        analytics.logEvent(LOGIN_GOOGLE, null);
    }

    public static void logOpenTabOwnRandos(FirebaseAnalytics analytics) {
        analytics.logEvent(OPEN_TAB_OWN_RANDOS, null);
    }

    public static void logOpenTabStrangerRandos(FirebaseAnalytics analytics) {
        analytics.logEvent(OPEN_TAB_STRANGER_RANDOS, null);
    }

    public static void logOpenTabSettings(FirebaseAnalytics analytics) {
        analytics.logEvent(OPEN_TAB_SETTINGS, null);
    }

    public static void logClickUnwantedRando(FirebaseAnalytics analytics) {
        analytics.logEvent(CLICK_UNWANTED_RANDO, null);
    }

    public static void logDeleteUnwantedRandoDialog(FirebaseAnalytics analytics) {
        analytics.logEvent(DELETE_UNWANTED_RANDO_DIALOG, null);
    }

    public static void logCancelUnwantedRandoDialog(FirebaseAnalytics analytics) {
        analytics.logEvent(CANCEL_UNWANTED_RANDO_DIALOG, null);
    }

    public static void logSwitchCameraToFront(FirebaseAnalytics analytics) {
        analytics.logEvent(SWITCH_CAMERA_TO_FRONT, null);
    }

    public static void logSwitchCameraToBack(FirebaseAnalytics analytics) {
        analytics.logEvent(SWITCH_CAMERA_TO_BACK, null);
    }

    public static void logRateRandoGood(FirebaseAnalytics analytics) {
        analytics.logEvent(RATE_RANDO_UP, null);
    }

    public static void logRateRandoNormal(FirebaseAnalytics analytics) {
        analytics.logEvent(RATE_RANDO_NORMAL, null);
    }

    public static void logRateRandoBad(FirebaseAnalytics analytics) {
        analytics.logEvent(RATE_RANDO_DOWN, null);
    }

} public class GooglePlayServicesUtil {

    private  static int MIN_GPS_VERISON = 5000000;

    public static boolean isGPSVersionLowerThanRequired(PackageManager packageManager){

        int versionCode = 0;
        try {
             versionCode = packageManager.getPackageInfo(GoogleApiAvailability.GOOGLE_PLAY_SERVICES_PACKAGE, 0 ).versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            Log.d(GooglePlayServicesUtil.class, "Google Play Services not installed at all");
        }
        return versionCode <= MIN_GPS_VERISON;
    }
} public class NetworkUtil {
    public static int TYPE_WIFI = 1;
    public static int TYPE_MOBILE = 2;
    public static int TYPE_NOT_CONNECTED = 0;
    public static final int NETWORK_STATUS_NOT_CONNECTED=0,NETWORK_STAUS_WIFI=1,NETWORK_STATUS_MOBILE=2;

    public static int getConnectivityStatus(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
        if (null != activeNetwork) {
            if(activeNetwork.getType() == ConnectivityManager.TYPE_WIFI)
                return TYPE_WIFI;

            if(activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE)
                return TYPE_MOBILE;
        }
        return TYPE_NOT_CONNECTED;
    }

    public static boolean isOnline(Context context) {
        return getConnectivityStatus(context) != NETWORK_STATUS_NOT_CONNECTED;
    }
} public static final String PUSH_NOTIFICATION_BROADCAST_EVENT = "Rando4MePushNotificationEvent"; public static final String RANDO_ID_PARAM = "randoId"; public static final String STATISTICS_PARAM = "statistics"; public static final String SYNC_BROADCAST_EVENT = "Rando4MeSyncEvent"; public static final String SYNC_STATISTICS_EVENT = "Rando4MeSyncStatEvent"; public static final String UPLOAD_SERVICE_BROADCAST_EVENT = "Rando4MeUploadDoneEvent";
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
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.Button;
import android.widget.ImageView;
import android.widget.Toast;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.github.randoapp.Constants;
import com.github.randoapp.R;
import com.github.randoapp.adapter.RandoListAdapter;
import com.github.randoapp.api.API;
import com.github.randoapp.api.listeners.ErrorResponseListener;
import com.github.randoapp.db.RandoDAO;
import com.github.randoapp.db.model.Rando;
import com.github.randoapp.log.Log;
import com.github.randoapp.upload.UploadJobScheduler;
import com.github.randoapp.util.Analytics;
import com.github.randoapp.util.GooglePlayServicesUtil;
import com.github.randoapp.util.NetworkUtil;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.firebase.analytics.FirebaseAnalytics;
import org.json.JSONObject;
import static com.github.randoapp.Constants.PUSH_NOTIFICATION_BROADCAST_EVENT;
import static com.github.randoapp.Constants.RANDO_ID_PARAM;
import static com.github.randoapp.Constants.STATISTICS_PARAM;
import static com.github.randoapp.Constants.SYNC_BROADCAST_EVENT;
import static com.github.randoapp.Constants.SYNC_STATISTICS_EVENT;
import static com.github.randoapp.Constants.UPLOAD_SERVICE_BROADCAST_EVENT;
package com.github.randoapp.fragment;





public class HomeListFragment extends Fragment {

    private RandoListAdapter randoPairsAdapter;
    private boolean isStranger;
    private SwipeRefreshLayout swipeRefreshLayout;
    private FirebaseAnalytics mFirebaseAnalytics;

    private Rando scrollToRando;

    private BroadcastReceiver receiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            Log.i(BroadcastReceiver.class, "Recieved Update request");
            if (SYNC_BROADCAST_EVENT.equals(intent.getAction())) {
                randoPairsAdapter.changeCursor(RandoDAO.getCursor(context, isStranger));
                randoPairsAdapter.notifyDataSetChanged();
            } else if (UPLOAD_SERVICE_BROADCAST_EVENT.equals(intent.getAction())) {
                randoPairsAdapter.changeCursor(RandoDAO.getCursor(context, isStranger));
                randoPairsAdapter.notifyDataSetChanged();
            } else if (SYNC_STATISTICS_EVENT.equals(intent.getAction())) {
                randoPairsAdapter.notifyItemChanged(0);
            } else if (PUSH_NOTIFICATION_BROADCAST_EVENT.equals(intent.getAction())) {
                String randoId = intent.getStringExtra(RANDO_ID_PARAM);
                if (randoId != null && !randoPairsAdapter.isStranger()) {
                    randoPairsAdapter.changeCursor(RandoDAO.getCursor(context, isStranger));
                    randoPairsAdapter.notifyDataSetChanged();
                    if (intent.getBooleanExtra(STATISTICS_PARAM, false)) {
                        API.statistics(getContext(), null);
                    }
                } else {
                    randoPairsAdapter.changeCursor(RandoDAO.getCursor(context, isStranger));
                    randoPairsAdapter.notifyDataSetChanged();

                }
            }
        }
    };

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             final Bundle savedInstanceState) {

        Bundle bundle = getArguments();
        if (bundle != null) {
            isStranger = bundle.getInt(Constants.PAGE) == 1;
            scrollToRando = (Rando) bundle.getSerializable(Constants.RANDO_PARAM);
        }

        final View rootView;
        rootView = inflater.inflate(R.layout.home_list, container, false);

        ImageView icHome = (ImageView) rootView.findViewById(R.id.ic_home);
        if (isStranger) {
            icHome.setVisibility(View.GONE);
        } else {
            icHome.setVisibility(View.VISIBLE);
        }

        RecyclerView listView = rootView.findViewById(R.id.listView);
        listView.setHasFixedSize(true);

        LinearLayoutManager llm = new LinearLayoutManager(getContext());
        listView.setLayoutManager(llm);

        mFirebaseAnalytics = FirebaseAnalytics.getInstance(getActivity());
        randoPairsAdapter = new RandoListAdapter(getContext(), isStranger, mFirebaseAnalytics);
        randoPairsAdapter.setHasStableIds(true);

        listView.setAdapter(randoPairsAdapter);
        API.statistics(getContext(), null);

        //ToDo: fix position of rando
        if (scrollToRando != null) {
            listView.getLayoutManager().scrollToPosition(randoPairsAdapter.findElementById(scrollToRando.randoId));
        }

        swipeRefreshLayout = rootView.findViewById(R.id.swipe_refresh_layout);
        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                Analytics.logForceSync(mFirebaseAnalytics);
                if (NetworkUtil.isOnline(getContext())) {
                    API.syncUserAsync(getContext(), new Response.Listener<JSONObject>() {
                        @Override
                        public void onResponse(JSONObject response) {
                            swipeRefreshLayout.setRefreshing(false);
                        }
                    }, new ErrorResponseListener(getContext()) {
                        @Override
                        public void onErrorResponse(VolleyError e) {
                            swipeRefreshLayout.setRefreshing(false);
                        }
                    });

                    if (RandoDAO.getNextRandoToUpload(getContext()) != null) {
                        UploadJobScheduler.scheduleUpload(getContext());
                    }
                } else {
                    swipeRefreshLayout.setRefreshing(false);
                    Toast.makeText(getActivity(), R.string.error_no_network, Toast.LENGTH_LONG).show();
                }
            }
        });
        showForceSyncButtonIfNecessary(rootView);
        return rootView;
    }

    @Override
    public void onPause() {
        super.onPause();
        getActivity().unregisterReceiver(receiver);
    }

    @Override
    public void onResume() {
        super.onResume();
        randoPairsAdapter.changeCursor(RandoDAO.getCursor(getContext(), isStranger));
        randoPairsAdapter.notifyDataSetChanged();
        getActivity().registerReceiver(receiver, new IntentFilter(SYNC_BROADCAST_EVENT));
        if (!isStranger) {
            getActivity().registerReceiver(receiver, new IntentFilter(UPLOAD_SERVICE_BROADCAST_EVENT));
            getActivity().registerReceiver(receiver, new IntentFilter(SYNC_STATISTICS_EVENT));
        }
        getActivity().registerReceiver(receiver, new IntentFilter(PUSH_NOTIFICATION_BROADCAST_EVENT));
    }

    private void showForceSyncButtonIfNecessary(final View rootView) {
        GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();
        int status = googleApiAvailability.isGooglePlayServicesAvailable(getContext());
        Button forceSyncButton = rootView.findViewById(R.id.forceSyncButton);
        if (status != ConnectionResult.SUCCESS
                && (status == ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED && GooglePlayServicesUtil.isGPSVersionLowerThanRequired(getActivity().getPackageManager()))) {