de.grobox.transportr.locations.LocationView.java Source code

Java tutorial

Introduction

Here is the source code for de.grobox.transportr.locations.LocationView.java

Source

/*    Transportr
 *    Copyright (C) 2013 - 2016 Torsten Grote
 *
 *    This program is Free Software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as
 *    published by the Free Software Foundation, either version 3 of the
 *    License, or (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.grobox.transportr.locations;

import android.content.Context;
import android.content.res.TypedArray;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.view.ViewCompat;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.AutoCompleteTextView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;

import java.util.List;

import de.grobox.transportr.R;
import de.grobox.transportr.data.locations.FavoriteLocation;
import de.grobox.transportr.data.locations.FavoriteLocation.FavLocationType;
import de.grobox.transportr.data.locations.HomeLocation;
import de.grobox.transportr.data.locations.WorkLocation;
import de.grobox.transportr.locations.SuggestLocationsTask.SuggestLocationsTaskCallback;
import de.grobox.transportr.networks.TransportNetwork;
import de.schildbach.pte.dto.SuggestLocationsResult;

public class LocationView extends LinearLayout implements SuggestLocationsTaskCallback {

    private final static String LOCATION = "location";
    private final static String TEXT = "text";
    private final static String TEXT_POSITION = "textPosition";
    private final static int AUTO_COMPLETION_DELAY = 300;
    private final static String SUPER_STATE = "superState";

    private LocationAdapter adapter;
    private SuggestLocationsTask task;
    private @Nullable TransportNetwork transportNetwork;
    private WrapLocation location;
    private boolean suggestLocationsTaskPending = false;
    protected final LocationViewHolder ui;
    private LocationViewListener listener;
    protected final String hint;

    private FavLocationType type = FavLocationType.FROM;

    public LocationView(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LocationView, 0, 0);
        boolean showIcon = a.getBoolean(R.styleable.LocationView_showIcon, true);
        hint = a.getString(R.styleable.LocationView_hint);
        a.recycle();

        setOrientation(LinearLayout.HORIZONTAL);

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.location_view, this, true);
        ui = new LocationViewHolder(this);

        ui.location.setHint(hint);
        if (!isInEditMode()) {
            adapter = new LocationAdapter(getContext());
            ui.location.setAdapter(adapter);
        }
        ui.location.setOnItemClickListener((parent, view, position, rowId) -> {
            WrapLocation loc = getAdapter().getItem(position);
            if (loc != null)
                onLocationItemClick(loc);
        });
        ui.location.setOnFocusChangeListener(LocationView.this::onFocusChange);
        ui.location.setOnClickListener(view -> LocationView.this.onClick());

        if (showIcon) {
            ui.status.setOnClickListener(v -> {
                getAdapter().resetDropDownLocations();
                LocationView.this.post(this::onClick);
            });
        } else {
            ui.status.setVisibility(GONE);
        }

        // clear text button
        ui.clear.setOnClickListener(v -> clearLocationAndShowDropDown());

        // From text input changed
        ui.location.addTextChangedListener(new TextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if ((count == 1 && before == 0) || (count == 0 && before == 1))
                    handleTextChanged(s);
            }

            public void afterTextChanged(Editable s) {
            }

            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
        });
    }

    public LocationView(Context context) {
        this(context, null);
    }

    protected static class LocationViewHolder {
        public final ImageView status;
        public final AutoCompleteTextView location;
        final ProgressBar progress;
        public final ImageButton clear;

        private LocationViewHolder(View view) {
            status = view.findViewById(R.id.statusButton);
            location = view.findViewById(R.id.location);
            clear = view.findViewById(R.id.clearButton);
            progress = view.findViewById(R.id.progress);
        }
    }

    /* State Saving and Restoring */

    @Override
    public Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable(SUPER_STATE, super.onSaveInstanceState());
        bundle.putInt(TEXT_POSITION, ui.location.getSelectionStart());
        bundle.putSerializable(LOCATION, location);
        if (location == null && ui.location.getText().length() > 0) {
            bundle.putString(TEXT, ui.location.getText().toString());
        }
        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) { // implicit null check
            Bundle bundle = (Bundle) state;
            WrapLocation loc = (WrapLocation) bundle.getSerializable(LOCATION);
            String text = bundle.getString(TEXT);
            if (loc != null) {
                setLocation(loc);
            } else if (text != null && text.length() > 0) {
                ui.location.setText(text);
                ui.clear.setVisibility(View.VISIBLE);
            }
            int position = bundle.getInt(TEXT_POSITION);
            ui.location.setSelection(position);

            // replace state by super state
            state = bundle.getParcelable(SUPER_STATE);
        }
        super.onRestoreInstanceState(state);
    }

    @Override
    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
        // Makes sure that the state of the child views are not saved
        super.dispatchFreezeSelfOnly(container);
    }

    @Override
    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
        // Makes sure that the state of the child views are not restored
        super.dispatchThawSelfOnly(container);
    }

    public void setTransportNetwork(@NonNull TransportNetwork transportNetwork) {
        this.transportNetwork = transportNetwork;
    }

    public void setHomeLocation(@Nullable HomeLocation homeLocation) {
        adapter.setHomeLocation(homeLocation);
    }

    public void setWorkLocation(@Nullable WorkLocation workLocation) {
        adapter.setWorkLocation(workLocation);
    }

    public void setFavoriteLocations(List<FavoriteLocation> favoriteLocations) {
        adapter.setFavoriteLocations(favoriteLocations);
    }

    /* Auto-Completion */

    private void handleTextChanged(CharSequence s) {
        // show clear button
        if (s.length() > 0) {
            ui.clear.setVisibility(View.VISIBLE);
            // clear location tag
            setLocation(null, R.drawable.ic_location, false);

            if (s.length() >= LocationAdapter.TYPING_THRESHOLD) {
                onContentChanged();
            }
        } else {
            clearLocationAndShowDropDown();
        }
    }

    private void onContentChanged() {
        ui.progress.setVisibility(View.VISIBLE);
        startSuggestLocationsTaskDelayed();
    }

    private void startSuggestLocationsTaskDelayed() {
        if (transportNetwork == null) {
            stopSuggestLocationsTask();
            return;
        }
        if (suggestLocationsTaskPending)
            return;
        suggestLocationsTaskPending = true;
        postDelayed(() -> {
            if (task != null && task.getStatus() != AsyncTask.Status.FINISHED)
                task.cancel(true);
            task = new SuggestLocationsTask(transportNetwork, LocationView.this);
            task.execute(getText());
            suggestLocationsTaskPending = false;
        }, AUTO_COMPLETION_DELAY);
    }

    @Override
    public void onSuggestLocationsResult(@Nullable SuggestLocationsResult suggestLocationsResult) {
        ui.progress.setVisibility(GONE);
        if (suggestLocationsResult == null)
            return;

        if (getAdapter() != null) {
            getAdapter().swapSuggestedLocations(suggestLocationsResult.suggestedLocations,
                    ui.location.getText().toString());
        }
    }

    private void stopSuggestLocationsTask() {
        if (task != null)
            task.cancel(true);
        ui.progress.setVisibility(GONE);
    }

    /* Setter and Getter */

    private LocationAdapter getAdapter() {
        return (LocationAdapter) ui.location.getAdapter();
    }

    public void setLocation(WrapLocation loc, @DrawableRes int icon, boolean setText) {
        location = loc;

        if (setText) {
            if (loc != null) {
                ui.location.setText(loc.getName());
                ui.location.dismissDropDown();
                ui.clear.setVisibility(View.VISIBLE);
                stopSuggestLocationsTask();
            } else {
                ui.location.setText(null);
                ui.clear.setVisibility(GONE);
            }
        }
        ui.status.setImageResource(icon);
    }

    public void setLocation(@Nullable WrapLocation loc) {
        setLocation(loc, loc == null ? R.drawable.ic_location : loc.getDrawable(), true);
    }

    @Nullable
    public WrapLocation getLocation() {
        return this.location;
    }

    private String getText() {
        if (ui.location != null) {
            return ui.location.getText().toString();
        } else {
            return null;
        }
    }

    public void setType(FavLocationType type) {
        this.type = type;
        getAdapter().setSort(type);
    }

    public void setHint(@StringRes int hint) {
        ui.location.setHint(hint);
    }

    /* Behavior */

    private void onFocusChange(View v, boolean hasFocus) {
        if (v instanceof AutoCompleteTextView && ViewCompat.isAttachedToWindow(v)) {
            if (hasFocus) {
                ((AutoCompleteTextView) v).showDropDown();
            } else {
                hideSoftKeyboard();
            }
        }
    }

    private void onLocationItemClick(WrapLocation loc) {
        setLocation(loc); // TODO set via ViewModel
        ui.location.requestFocus();

        // hide soft-keyboard
        hideSoftKeyboard();

        if (listener != null)
            listener.onLocationItemClick(loc, type);
    }

    public void onClick() {
        if (getAdapter().getCount() > 0) {
            ui.location.showDropDown();
        }
    }

    public void clearLocation() {
        setLocation(null);
        if (getAdapter() != null) {
            getAdapter().resetSearchTerm();
        }
    }

    protected void clearLocationAndShowDropDown() {
        clearLocation();
        stopSuggestLocationsTask();
        reset();
        if (listener != null)
            listener.onLocationCleared(type);
        ui.clear.setVisibility(GONE);
        if (isShown()) {
            ui.location.requestFocus();
            ui.location.showDropDown();
        }
    }

    public void reset() {
        if (getAdapter() != null) {
            getAdapter().reset();
        }
    }

    public void resetIfEmpty() {
        if (ui.location.getText().length() == 0) {
            reset();
        }
    }

    private void hideSoftKeyboard() {
        InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm != null) {
            imm.hideSoftInputFromWindow(ui.location.getWindowToken(), 0);
        }
    }

    /* Listener */

    public void setLocationViewListener(LocationViewListener listener) {
        this.listener = listener;
    }

    public interface LocationViewListener {
        void onLocationItemClick(WrapLocation loc, FavLocationType type);

        void onLocationCleared(FavLocationType type);
    }

}