public class MainApp extends MultiDexApplication {
    private static AppComponent sAppComponent;

    @Override
    public void onCreate() {
        super.onCreate();

        Stetho.initializeWithDefaults(this);

        CrashlyticsCore core = new CrashlyticsCore.Builder()
                .disabled(BuildConfig.DEBUG)
                .build();
        Fabric.with(this, new Crashlytics.Builder().core(core).build(), new Crashlytics());

        // ...

        if (BuildConfig.DEBUG) {
            Timber.plant(new Timber.DebugTree());
        }
        Timber.plant(new CrashlyticsTree());

        MainApp.init(getBaseContext());
    }

    public static void init(Context context) {
        sAppComponent = DaggerAppComponent
                .builder()
                .busModule(new BusModule())
                .navigationModule(new NavigationModule())
                .contextModule(new ContextModule(context))
                .preferencesModule(new PreferencesModule())
                .gsonModule(new GsonModule())
                .httpClientModule(new HttpClientModule())
                .retrofitModule(new RetrofitModule())
                .restApiModule(new RestApiModule())
                .webSocketModule(new WebSocketModule())
                .networkModule(new NetworkModule())
                .storageModule(new StorageModule())
                .mainModule(new MainModule())
                .build();
    }

    public static AppComponent getAppComponent() {
        return sAppComponent;
    }
} public class Screens {
    public static final String SIGN_IN_SCREEN = "sign_in_screen";
    public static final String SIGN_UP_SCREEN = "sign_up_screen";
    public static final String MAP_SCREEN = "map_screen";
    public static final String DEVICES_SCREEN = "devices_screen";
    public static final String USERS_SCREEN = "users_screen";
    public static final String SERVER_SCREEN = "server_screen";
    public static final String USER_SCREEN = "user_screen";
    public static final String DEVICE_SCREEN = "device_screen";
    public static final String PERMISSIONS_SCREEN = "permissions_screen";
    public static final String REPORT_SCREEN = "report_screen";
    public static final String ABOUT_SCREEN = "about_screen";

} @StorIOSQLiteType(table = ServersTable.TABLE)
public class Server implements Parcelable {

    @StorIOSQLiteColumn(name = ServersTable.COLUMN_ID, key = true)
    @SerializedName("id")
    @Expose
    long id;

    @StorIOSQLiteColumn(name = ServersTable.COLUMN_REGISTRATION)
    @Since(3.0)
    @SerializedName("registration")
    @Expose
    Boolean registration;

    @StorIOSQLiteColumn(name = ServersTable.COLUMN_LATITUDE)
    @Since(3.0)
    @SerializedName("latitude")
    @Expose
    Double latitude;

    @StorIOSQLiteColumn(name = ServersTable.COLUMN_LONGITUDE)
    @Since(3.0)
    @SerializedName("longitude")
    @Expose
    Double longitude;

    @StorIOSQLiteColumn(name = ServersTable.COLUMN_ZOOM)
    @Since(3.0)
    @SerializedName("zoom")
    @Expose
    Integer zoom;

    @StorIOSQLiteColumn(name = ServersTable.COLUMN_MAP)
    @Since(3.1)
    @SerializedName("map")
    @Expose
    String map;

    @StorIOSQLiteColumn(name = ServersTable.COLUMN_LANGUAGE)
    @Since(3.1)
    @SerializedName("language")
    @Expose
    String language;

    @StorIOSQLiteColumn(name = ServersTable.COLUMN_DISTANCE_UNIT)
    @Since(3.1)
    @SerializedName("distanceUnit")
    @Expose
    String distanceUnit;

    @StorIOSQLiteColumn(name = ServersTable.COLUMN_SPEED_UNIT)
    @Since(3.1)
    @SerializedName("speedUnit")
    @Expose
    String speedUnit;

    @StorIOSQLiteColumn(name = ServersTable.COLUMN_BING_KEY)
    @Since(3.2)
    @SerializedName("bingKey")
    @Expose
    String bingKey;

    @StorIOSQLiteColumn(name = ServersTable.COLUMN_MAP_URL)
    @Since(3.2)
    @SerializedName("mapUrl")
    @Expose
    String mapUrl;

    @StorIOSQLiteColumn(name = ServersTable.COLUMN_READONLY)
    @Since(3.4)
    @SerializedName("readonly")
    @Expose
    Boolean readonly;

    @StorIOSQLiteColumn(name = ServersTable.COLUMN_TWELVE_HOUR_FORMAT)
    @Since(3.5)
    @SerializedName("twelveHourFormat")
    @Expose
    Boolean twelveHourFormat;

    public Server() {}
    /**
     * @return The id
     */
    public long getId() {
        return id;
    }

    /**
     * @param id The id
     */
    public void setId(long id) {
        this.id = id;
    }

    /**
     * @return The registration
     */
    public Boolean getRegistration() {
        return registration;
    }

    /**
     * @param registration The registration
     */
    public void setRegistration(Boolean registration) {
        this.registration = registration;
    }

    /**
     * @return The readonly
     */
    public Boolean getReadonly() {
        return readonly;
    }

    /**
     * @param readonly The readonly
     */
    public void setReadonly(Boolean readonly) {
        this.readonly = readonly;
    }

    /**
     * @return The map
     */
    public String getMap() {
        return map;
    }

    /**
     * @param map The map
     */
    public void setMap(String map) {
        this.map = map;
    }

    /**
     * @return The language
     */
    public String getLanguage() {
        return language;
    }

    /**
     * @param language The map
     */
    public void setLanguage(String language) {
        this.language = language;
    }

    /**
     * @return The bingKey
     */
    public String getBingKey() {
        return bingKey;
    }

    /**
     * @param bingKey The bingKey
     */
    public void setBingKey(String bingKey) {
        this.bingKey = bingKey;
    }

    /**
     * @return The mapUrl
     */
    public String getMapUrl() {
        return mapUrl;
    }

    /**
     * @param mapUrl The mapUrl
     */
    public void setMapUrl(String mapUrl) {
        this.mapUrl = mapUrl;
    }

    /**
     * @return The distanceUnit
     */
    public String getDistanceUnit() {
        return distanceUnit;
    }

    /**
     * @param distanceUnit The distanceUnit
     */
    public void setDistanceUnit(String distanceUnit) {
        this.distanceUnit = distanceUnit;
    }

    /**
     * @return The speedUnit
     */
    public String getSpeedUnit() {
        return speedUnit;
    }

    /**
     * @param speedUnit The speedUnit
     */
    public void setSpeedUnit(String speedUnit) {
        this.speedUnit = speedUnit;
    }

    /**
     * @return The latitude
     */
    public Double getLatitude() {
        return latitude;
    }

    /**
     * @param latitude The latitude
     */
    public void setLatitude(Double latitude) {
        this.latitude = latitude;
    }

    /**
     * @return The longitude
     */
    public Double getLongitude() {
        return longitude;
    }

    /**
     * @param longitude The longitude
     */
    public void setLongitude(Double longitude) {
        this.longitude = longitude;
    }

    /**
     * @return The zoom
     */
    public Integer getZoom() {
        return zoom;
    }

    /**
     * @param zoom The zoom
     */
    public void setZoom(Integer zoom) {
        this.zoom = zoom;
    }

    /**
     * @return The twelveHourFormat
     */
    public Boolean getTwelveHourFormat() {
        return twelveHourFormat;
    }

    /**
     * @param twelveHourFormat The twelveHourFormat
     */
    public void setTwelveHourFormat(Boolean twelveHourFormat) {
        this.twelveHourFormat = twelveHourFormat;
    }


    @Override
    public String toString() {
        return "Server{" +
                "id=" + id +
                ", registration=" + registration +
                ", latitude=" + latitude +
                ", longitude=" + longitude +
                ", zoom=" + zoom +
                ", map='" + map + '\'' +
                ", language='" + language + '\'' +
                ", distanceUnit='" + distanceUnit + '\'' +
                ", speedUnit='" + speedUnit + '\'' +
                ", bingKey='" + bingKey + '\'' +
                ", mapUrl='" + mapUrl + '\'' +
                ", readonly=" + readonly +
                ", twelveHourFormat=" + twelveHourFormat +
                '}';
    }

    protected Server(Parcel in) {
        id = in.readLong();
        byte registrationVal = in.readByte();
        registration = registrationVal == 0x02 ? null : registrationVal != 0x00;
        byte readonlyVal = in.readByte();
        readonly = readonlyVal == 0x02 ? null : readonlyVal != 0x00;
        map = in.readString();
        language = in.readString();
        bingKey = in.readString();
        mapUrl = in.readString();
        distanceUnit = in.readString();
        speedUnit = in.readString();
        latitude = in.readByte() == 0x00 ? null : in.readDouble();
        longitude = in.readByte() == 0x00 ? null : in.readDouble();
        zoom = in.readByte() == 0x00 ? null : in.readInt();
        byte twelveHourFormatVal = in.readByte();
        twelveHourFormat = twelveHourFormatVal == 0x02 ? null : twelveHourFormatVal != 0x00;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeLong(id);
        if (registration == null) {
            dest.writeByte((byte) (0x02));
        } else {
            dest.writeByte((byte) (registration ? 0x01 : 0x00));
        }
        if (readonly == null) {
            dest.writeByte((byte) (0x02));
        } else {
            dest.writeByte((byte) (readonly ? 0x01 : 0x00));
        }
        dest.writeString(map);
        dest.writeString(language);
        dest.writeString(bingKey);
        dest.writeString(mapUrl);
        dest.writeString(distanceUnit);
        dest.writeString(speedUnit);
        if (latitude == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeDouble(latitude);
        }
        if (longitude == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeDouble(longitude);
        }
        if (zoom == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeInt(zoom);
        }
        if (twelveHourFormat == null) {
            dest.writeByte((byte) (0x02));
        } else {
            dest.writeByte((byte) (twelveHourFormat ? 0x01 : 0x00));
        }
    }

    @SuppressWarnings("unused")
    public static final Creator<Server> CREATOR = new Creator<Server>() {
        @Override
        public Server createFromParcel(Parcel in) {
            return new Server(in);
        }

        @Override
        public Server[] newArray(int size) {
            return new Server[size];
        }
    };
} @StorIOSQLiteType(table = UsersTable.TABLE)
public class User implements Parcelable {

    @StorIOSQLiteColumn(name = UsersTable.COLUMN_ID, key = true)
    @SerializedName("id")
    @Expose
    long id;

    @StorIOSQLiteColumn(name = UsersTable.COLUMN_NAME)
    @Since(3.0)
    @SerializedName("name")
    @Expose
    String name;

    @StorIOSQLiteColumn(name = UsersTable.COLUMN_EMAIL)
    @Since(3.0)
    @SerializedName("email")
    @Expose
    String email;

    @StorIOSQLiteColumn(name = UsersTable.COLUMN_PASSWORD)
    @Since(3.0)
    @SerializedName("password")
    @Expose
    String password;

    @StorIOSQLiteColumn(name = UsersTable.COLUMN_ADMIN)
    @Since(3.0)
    @SerializedName("admin")
    @Expose
    Boolean admin;

    @StorIOSQLiteColumn(name = UsersTable.COLUMN_MAP)
    @Since(3.1)
    @SerializedName("map")
    @Expose
    String map;

    @StorIOSQLiteColumn(name = UsersTable.COLUMN_LANGUAGE)
    @Since(3.1)
    @SerializedName("language")
    @Expose
    String language;

    @StorIOSQLiteColumn(name = UsersTable.COLUMN_DISTANCE_UNIT)
    @Since(3.1)
    @SerializedName("distanceUnit")
    @Expose
    String distanceUnit;

    @StorIOSQLiteColumn(name = UsersTable.COLUMN_SPEED_UNIT)
    @Since(3.1)
    @SerializedName("speedUnit")
    @Expose
    String speedUnit;

    @StorIOSQLiteColumn(name = UsersTable.COLUMN_LATITUDE)
    @Since(3.1)
    @SerializedName("latitude")
    @Expose
    Double latitude;

    @StorIOSQLiteColumn(name = UsersTable.COLUMN_LONGITUDE)
    @Since(3.1)
    @SerializedName("longitude")
    @Expose
    Double longitude;

    @StorIOSQLiteColumn(name = UsersTable.COLUMN_ZOOM)
    @Since(3.1)
    @SerializedName("zoom")
    @Expose
    Integer zoom;

    @StorIOSQLiteColumn(name = UsersTable.COLUMN_READONLY)
    @Since(3.4)
    @SerializedName("readonly")
    @Expose
    Boolean readonly;

    @StorIOSQLiteColumn(name = UsersTable.COLUMN_TWELVE_HOUR_FORMAT)
    @Since(3.5)
    @SerializedName("twelveHourFormat")
    @Expose
    Boolean twelveHourFormat;

    public User() {}
    /**
     *
     * @return
     * The id
     */
    public long getId() {
        return id;
    }

    /**
     *
     * @param id
     * The id
     */
    public void setId(long id) {
        this.id = id;
    }

    /**
     *
     * @return
     * The name
     */
    public String getName() {
        return name;
    }

    /**
     *
     * @param name
     * The name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     *
     * @return
     * The email
     */
    public String getEmail() {
        return email;
    }

    /**
     *
     * @param email
     * The email
     */
    public void setEmail(String email) {
        this.email = email;
    }

    /**
     *
     * @return
     * The readonly
     */
    public Boolean getReadonly() {
        return readonly;
    }

    /**
     *
     * @param readonly
     * The readonly
     */
    public void setReadonly(Boolean readonly) {
        this.readonly = readonly;
    }

    /**
     *
     * @return
     * The admin
     */
    public Boolean getAdmin() {
        return admin;
    }

    /**
     *
     * @param admin
     * The admin
     */
    public void setAdmin(Boolean admin) {
        this.admin = admin;
    }

    /**
     *
     * @return
     * The map
     */
    public String getMap() {
        return map;
    }

    /**
     *
     * @param map
     * The map
     */
    public void setMap(String map) {
        this.map = map;
    }

    public String getLanguage() {
        return language;
    }

    public void setLanguage(String language) {
        this.language = language;
    }

    /**
     *
     * @return
     * The distanceUnit
     */
    public String getDistanceUnit() {
        return distanceUnit;
    }

    /**
     *
     * @param distanceUnit
     * The distanceUnit
     */
    public void setDistanceUnit(String distanceUnit) {
        this.distanceUnit = distanceUnit;
    }

    /**
     *
     * @return
     * The speedUnit
     */
    public String getSpeedUnit() {
        return speedUnit;
    }

    /**
     *
     * @param speedUnit
     * The speedUnit
     */
    public void setSpeedUnit(String speedUnit) {
        this.speedUnit = speedUnit;
    }

    /**
     *
     * @return
     * The latitude
     */
    public Double getLatitude() {
        return latitude;
    }

    /**
     *
     * @param latitude
     * The latitude
     */
    public void setLatitude(Double latitude) {
        this.latitude = latitude;
    }

    /**
     *
     * @return
     * The longitude
     */
    public Double getLongitude() {
        return longitude;
    }

    /**
     *
     * @param longitude
     * The longitude
     */
    public void setLongitude(Double longitude) {
        this.longitude = longitude;
    }

    /**
     *
     * @return
     * The zoom
     */
    public Integer getZoom() {
        return zoom;
    }

    /**
     *
     * @param zoom
     * The zoom
     */
    public void setZoom(Integer zoom) {
        this.zoom = zoom;
    }

    /**
     *
     * @return
     * The twelveHourFormat
     */
    public Boolean getTwelveHourFormat() {
        return twelveHourFormat;
    }

    /**
     *
     * @param twelveHourFormat
     * The twelveHourFormat
     */
    public void setTwelveHourFormat(Boolean twelveHourFormat) {
        this.twelveHourFormat = twelveHourFormat;
    }

    /**
     *
     * @return
     * The password
     */
    public String getPassword() {
        return password;
    }

    /**
     *
     * @param password
     * The password
     */
    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", password='" + password + '\'' +
                ", admin=" + admin +
                ", map='" + map + '\'' +
                ", language='" + language + '\'' +
                ", distanceUnit='" + distanceUnit + '\'' +
                ", speedUnit='" + speedUnit + '\'' +
                ", latitude=" + latitude +
                ", longitude=" + longitude +
                ", zoom=" + zoom +
                ", readonly=" + readonly +
                ", twelveHourFormat=" + twelveHourFormat +
                '}';
    }

    protected User(Parcel in) {
        id = in.readLong();
        name = in.readString();
        email = in.readString();
        byte readonlyVal = in.readByte();
        readonly = readonlyVal == 0x02 ? null : readonlyVal != 0x00;
        byte adminVal = in.readByte();
        admin = adminVal == 0x02 ? null : adminVal != 0x00;
        map = in.readString();
        language = in.readString();
        distanceUnit = in.readString();
        speedUnit = in.readString();
        latitude = in.readByte() == 0x00 ? null : in.readDouble();
        longitude = in.readByte() == 0x00 ? null : in.readDouble();
        zoom = in.readByte() == 0x00 ? null : in.readInt();
        byte twelveHourFormatVal = in.readByte();
        twelveHourFormat = twelveHourFormatVal == 0x02 ? null : twelveHourFormatVal != 0x00;
        password = in.readString();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeLong(id);
        dest.writeString(name);
        dest.writeString(email);
        if (readonly == null) {
            dest.writeByte((byte) (0x02));
        } else {
            dest.writeByte((byte) (readonly ? 0x01 : 0x00));
        }
        if (admin == null) {
            dest.writeByte((byte) (0x02));
        } else {
            dest.writeByte((byte) (admin ? 0x01 : 0x00));
        }
        dest.writeString(map);
        dest.writeString(language);
        dest.writeString(distanceUnit);
        dest.writeString(speedUnit);
        if (latitude == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeDouble(latitude);
        }
        if (longitude == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeDouble(longitude);
        }
        if (zoom == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeInt(zoom);
        }
        if (twelveHourFormat == null) {
            dest.writeByte((byte) (0x02));
        } else {
            dest.writeByte((byte) (twelveHourFormat ? 0x01 : 0x00));
        }
        dest.writeString(password);
    }

    @SuppressWarnings("unused")
    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };

    public static User newUser(String name, String email, String password) {
        User user = new User();
        user.setName(name);
        user.setEmail(email);
        user.setPassword(password);
        return user;
    }
} @InjectViewState
public class CheckSessionPresenter extends BasePresenter<CheckSessionView> {

    @Inject
    MainService mMainService;

    public CheckSessionPresenter() {
        MainApp.getAppComponent().inject(this);
    }

    public void check() {
        getViewState().startCheckSession();

        // save session id
        Subscription subscription = mMainService.checkSession()
                .subscribe(user -> {
                    getViewState().finishCheckSession();
                    getViewState().successCheckSession(user);
                }, exception -> {
                    getViewState().finishCheckSession();
                    getViewState().failedCheckSession(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

    public void onErrorCancel() {
        getViewState().hideError();
    }
} @InjectViewState
public class GetServerPresenter extends BasePresenter<GetServerView> {

    @Inject
    MainService mMainService;

    public GetServerPresenter() {
        MainApp.getAppComponent().inject(this);
    }

    public void getServer() {
        getViewState().startGetServer();

        // save session id
        Subscription subscription = mMainService.getServer()
                .subscribe(server -> {
                    getViewState().finishGetServer();
                    getViewState().successGetServer(server);
                }, exception -> {
                    getViewState().finishGetServer();
                    getViewState().failedGetServer(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

    public void onErrorCancel() {
        getViewState().hideError();
    }
} @InjectViewState
public class SignOutPresenter extends BasePresenter<SignOutView> {

    @Inject
    MainService mMainService;

    public SignOutPresenter() {
        MainApp.getAppComponent().inject(this);
    }

    public void signOut() {

        getViewState().startSignOut();

        // save session id
        Subscription subscription = mMainService.signOut()
                .subscribe(avoid -> {
                    getViewState().finishSignOut();
                    getViewState().successSignOut();
                });

        unsubscribeOnDestroy(subscription);
    }

    public void onErrorCancel() {
        getViewState().hideError();
    }
} @StateStrategyType(AddToEndSingleStrategy.class)
public interface CheckSessionView extends MvpView {
    void startCheckSession();

    void finishCheckSession();

    void failedCheckSession(String message);

    void hideError();

    @StateStrategyType(SkipStrategy.class)
    void successCheckSession(User user);
} @StateStrategyType(AddToEndSingleStrategy.class)
public interface GetServerView extends MvpView {
    void startGetServer();

    void finishGetServer();

    void failedGetServer(String message);

    void hideError();
/*
    void hideFormError();

    void showFormError(Integer tokenError, Integer operateAsError);
*/
    @StateStrategyType(SkipStrategy.class)
    void successGetServer(Server server);
} @StateStrategyType(AddToEndSingleStrategy.class)
public interface SignOutView extends MvpView {
    void startSignOut();

    void finishSignOut();

    void hideError();

    @StateStrategyType(SkipStrategy.class)
    void successSignOut();
} public class AboutFragment extends Fragment {
    @BindView(R.id.about_title)
    TextView mAboutTitle;

    public static AboutFragment getNewInstance(Object data) {
        // Required empty public constructor
        AboutFragment  instance = new AboutFragment();

        return instance;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_about, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        try {
            String packageVersion = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0).versionName;
            mAboutTitle.setText(mAboutTitle.getText().toString() + " " + packageVersion);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
} public class DeviceFragment extends MvpAppCompatFragment implements GetDeviceView, CreateDeviceView, UpdateDeviceView {
    @BindView(R.id.form_layout)
    RelativeLayout mForm;

    @BindView(R.id.name_input_layout)
    TextInputLayout mNameInputLayout;
    @BindView(R.id.name_input)
    EditText mNameInput;

    @BindView(R.id.identifier_input_layout)
    TextInputLayout mIdentifierInputLayout;
    @BindView(R.id.identifier_input)
    EditText mIdentifierInput;

    @BindView(R.id.submit_input)
    FloatingActionButton mSubmitInput;

    @BindView(R.id.progress)
    ProgressBar mProgress;

    @InjectPresenter
    CacheDevicePresenter mCacheDevicePresenter;

    @InjectPresenter
    CreateDevicePresenter mCreateDevicePresenter;

    @InjectPresenter
    UpdateDevicePresenter mUpdateDevicePresenter;

    @Inject
    Router router;

    private AlertDialog mErrorDialog;

    public static DeviceFragment getNewInstance(Bundle args) {
        // Required empty public constructor
        DeviceFragment  instance = new DeviceFragment();
        instance.setArguments(args);
        return instance;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        MainApp.getAppComponent().inject(this);
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_device, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        Observable<Boolean> nameObservable = RxTextView.textChanges(mNameInput)
                .map(inputText -> !TextUtils.isEmpty(inputText) && NAME_PATTERN.matcher(inputText).matches())
                .distinctUntilChanged();

        nameObservable.subscribe(isValid -> {
            mNameInputLayout.setError("Invalid Name");
            mNameInputLayout.setErrorEnabled(!isValid);
        });

        Observable<Boolean> identifierObservable = RxTextView.textChanges(mIdentifierInput)
                .map(inputText -> !TextUtils.isEmpty(inputText) && IDENTIFIER_PATTERN.matcher(inputText).matches())
                .distinctUntilChanged();

        identifierObservable.subscribe(isValid -> {
            mIdentifierInputLayout.setError("Invalid Identifier");
            mIdentifierInputLayout.setErrorEnabled(!isValid);
        });

        Observable.combineLatest(
                nameObservable,
                identifierObservable,
                (nameValid, identifierValid) -> nameValid && identifierValid)
                .distinctUntilChanged()
                .subscribe(valid -> mSubmitInput.setEnabled(valid));

        RxView.clicks(mSubmitInput)
                .subscribe(aVoid -> {
                    Device device = getArguments().getParcelable("device");
                    device.setName(mNameInput.getText().toString());
                    device.setUniqueId(mIdentifierInput.getText().toString());
                    if (device.getId() == 0) {
                        mCreateDevicePresenter.save(device);
                    } else {
                        mUpdateDevicePresenter.save(device);
                    }
                });

        if (getArguments().getLong("deviceId", 0) == 0) {
            successGetDevice(new Device());
        } else {
            mCacheDevicePresenter.load(getArguments().getLong("deviceId", 0));
        }
    }

    private void toggleProgressVisibility(final boolean show) {
        mProgress.setVisibility(show ? View.VISIBLE : View.GONE);
        mForm.setVisibility(show ? View.GONE : View.VISIBLE);
    }

    @Override
    public void startGetDevice() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishGetDevice() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedGetDevice(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mCacheDevicePresenter.onErrorCancel())
                .show();
    }

    @Override
    public void startUpdateDevice() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishUpdateDevice() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedUpdateDevice(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mUpdateDevicePresenter.onErrorCancel())
                .show();
    }

    @Override
    public void startCreateDevice() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishCreateDevice() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedCreateDevice(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mCacheDevicePresenter.onErrorCancel())
                .show();
    }

    @Override
    public void hideError() {
        if (mErrorDialog != null && mErrorDialog.isShowing()) {
            mErrorDialog.cancel();
        }
    }

    @Override
    public void successCreateDevice() {
        router.backTo(null);
    }

    @Override
    public void successUpdateDevice() {
        router.backTo(null);
    }

    @Override
    public void successGetDevice(Device device) {
        try {
            Timber.d("Device: " + device);
            getArguments().putParcelable("device", device);
            mNameInput.setText(device.getName() != null ? device.getName() : "");
            mIdentifierInput.setText(device.getUniqueId() != null ? device.getUniqueId() : "");
        } catch (Exception e) {
            e.printStackTrace();
            getArguments().putParcelable("device", new Device());
        }
    }
} public class DevicesFragment  extends MvpAppCompatFragment implements CacheDevicesView, DeleteDeviceView {
    @BindView(R.id.devices)
    RecyclerView mDevices;
    @BindView(R.id.create_input)
    FloatingActionButton mCreateInput;

    @InjectPresenter
    CacheDevicesPresenter mCacheDevicesPresenter;

    @InjectPresenter
    DeleteDevicePresenter mDeleteDevicePresenter;

    @Inject
    Router router;

    private DevicesCursorAdapter mAdapter;
    private AlertDialog mConfirmDialog;
    private AlertDialog mErrorDialog;

    public static DevicesFragment getNewInstance(Bundle args) {
        // Required empty public constructor
        DevicesFragment  instance = new DevicesFragment();
        instance.setArguments(args);
        return instance;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        MainApp.getAppComponent().inject(this);
        super.onCreate(savedInstanceState);
        Timber.d("onCreate");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Timber.d("onCreateView");
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_devices, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Timber.d("onViewCreated");
        mDevices.setLayoutManager(new LinearLayoutManager(getContext()));
        mDevices.setHasFixedSize(true);
        mDevices.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));
        mDevices.addOnItemTouchListener(new RecyclerItemClickListener(getContext(), (view1, position) -> {
            Timber.d("Device position: " + position + " id: " + mAdapter.getItemId(position));
            PopupMenu popupMenu = new PopupMenu(getContext(), view1);
            popupMenu.inflate(R.menu.fragment_devices_popupmenu);
            popupMenu.setOnMenuItemClickListener(new OnExecDevicePopupMenu(mAdapter.getItemId(position)));
            popupMenu.show();
        }));

        RxView.clicks(mCreateInput)
                .subscribe(aVoid -> {
                    Bundle args = new Bundle();
                    args.putLong("deviceId", -1);
                    router.navigateTo(Screens.DEVICE_SCREEN, args);
                });

        mCacheDevicesPresenter.load();
    }

    @Override
    public void onDestroyView() {
        mAdapter = null;
        super.onDestroyView();
    }

    private void toggleProgressVisibility(boolean b) {
    }

    @Override
    public void startCacheDevices() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishCacheDevices() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedCacheDevices(String message) {

    }

    @Override
    public void startDeleteDevice() {

    }

    @Override
    public void finishDeleteDevice() {

    }

    @Override
    public void failedDeleteDevice(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mDeleteDevicePresenter.onErrorCancel())
                .show();
    }

    @Override
    public void hideError() {
        if (mErrorDialog != null && mErrorDialog.isShowing()) {
            mErrorDialog.cancel();
        }
    }

    @Override
    public void successDeleteDevice() {

    }

    @Override
    public void successCacheDevices(Cursor cursor) {
        Timber.d("Count devices: " + cursor.getCount());
        if (mAdapter == null) {
            mAdapter = new DevicesCursorAdapter(cursor);
            mDevices.setAdapter(mAdapter);
        } else {
            mAdapter.changeCursor(cursor);
        }
    }


    private class OnExecDevicePopupMenu implements PopupMenu.OnMenuItemClickListener {
        long mDeviceId;

        public OnExecDevicePopupMenu(long deviceId) {
            this.mDeviceId = deviceId;
        }

        @Override
        public boolean onMenuItemClick(MenuItem item) {
            Bundle args = new Bundle();
            switch (item.getItemId()) {
                case R.id.action_device_edit:
                    //mListener.editDevice(mDeviceId);
                    args.putLong("deviceId", mDeviceId);
                    router.navigateTo(Screens.DEVICE_SCREEN, args);
                    break;
                case R.id.action_device_remove:
                    //mListener.deleteDevice(mDeviceId);
                    mConfirmDialog = new AlertDialog.Builder(getContext())
                            .setTitle(R.string.deviceTitle)
                            .setMessage(R.string.sharedRemoveConfirm)
                            .setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
                                mDeleteDevicePresenter.delete(mDeviceId);
                            })
                            .setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> mConfirmDialog.dismiss())
                            .show();
                    break;
                case R.id.action_device_positions:
                    //mListener.showReport(mDeviceId);
                    args.putLong("deviceId", mDeviceId);
                    router.navigateTo(Screens.REPORT_SCREEN, args);
                    break;
                case R.id.action_show_on_map:
                    //mListener.showDeviceOnMap(mDeviceId);
                    args.putLong("deviceId", mDeviceId);
                    router.newRootScreen(Screens.MAP_SCREEN, args);
                    break;
                case R.id.action_send_command:
                    //mListener.sendCommandToDevice(mDeviceId);
                    SendCommandDialogFragment dialogFragment = SendCommandDialogFragment.newInstance(mDeviceId);
                    dialogFragment.show(getActivity().getSupportFragmentManager(), "send_command_dialog");
                    break;
            }
            return false;
        }
    }
} public class MapFragment extends MvpAppCompatFragment implements GetUserView, CacheDevicesAndPositionsView, OpenWebSocketView {
    @BindView(R.id.mapview)
    MapView mMapView;
    @BindView(R.id.myPlace)
    CheckBox myPlace;

    @BindDrawable(R.drawable.ic_arrow_offline)
    Drawable arrowOfflineDrawable;

    @BindDrawable(R.drawable.ic_arrow_online)
    Drawable arrowOnlineDrawable;

    @BindDrawable(R.drawable.ic_arrow_unknown)
    Drawable arrowUnknownDrawable;

    @Inject
    Router router;

    @Inject
    SharedPreferences preferences;

    @InjectPresenter
    CacheDevicesAndPositionsPresenter mCacheDevicesAndPositionsPresenter;

    @InjectPresenter
    OpenWebSocketPresenter mOpenWebSocketPresenter;

    @InjectPresenter
    CacheUserPresenter mCacheUserPresenter;

    BingMapTileSource bing;
    private AlertDialog mErrorDialog;


    private DevicesMarkerClusterer mRadiusMarkerClusterer;
    private Map<Long, MarkerWithLabel> mMarkers = new HashMap<>();
    private MyLocationNewOverlay mLocationOverlay;

    public static MapFragment getNewInstance(Bundle args) {
        // Required empty public constructor
        MapFragment  instance = new MapFragment();
        instance.setArguments(args);
        return instance;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        MainApp.getAppComponent().inject(this);
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_map, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        mMapView.setTilesScaledToDpi(true);
        mMapView.setMultiTouchControls(true);

        bing = new BingMapTileSource(null);

        RxView.clicks(myPlace)
                .compose(RxPermissions.getInstance(getContext()).ensure(Manifest.permission.ACCESS_COARSE_LOCATION))
                .subscribe(granted -> {
                    if (granted) {
                        if (myPlace.isChecked()) {
                            mLocationOverlay.enableFollowLocation();
                            mLocationOverlay.enableMyLocation();
                            mLocationOverlay.runOnFirstFix(() -> mMapView.post(() -> {
                                try {
                                    mMapView.getController().setZoom(15);
                                    mMapView.getController().animateTo(new GeoPoint(
                                            mLocationOverlay.getLastFix().getLatitude(),
                                            mLocationOverlay.getLastFix().getLongitude()
                                    ));
                                    mMapView.postInvalidate();
                                } catch (Exception e) {

                                }
                            }));
                        } else {
                            mLocationOverlay.disableFollowLocation();
                            mLocationOverlay.disableMyLocation();
                        }
                    } else {
                        myPlace.setChecked(false);
                        //makeToast(myPlace, getString(R.string.errorPermissionCoarseLocation))
                    }
                });

        mCacheUserPresenter.load(preferences.getLong("userId", -1));
        mCacheDevicesAndPositionsPresenter.load();
        mOpenWebSocketPresenter.open();
    }

    @Override
    public void onResume() {
        super.onResume();

        mRadiusMarkerClusterer = new DevicesMarkerClusterer(getContext());
        mRadiusMarkerClusterer.setIcon(BitmapFactory.decodeResource(getResources(), R.drawable.marker_cluster));
        mMapView.getOverlays().add(mRadiusMarkerClusterer);

        mLocationOverlay = new MyLocationNewOverlay(new GpsMyLocationProvider(getActivity()), mMapView);
        mLocationOverlay.disableFollowLocation();
        mLocationOverlay.disableMyLocation();
        mMapView.getOverlays().add(mLocationOverlay);
    }

    @Override
    public void onPause() {
        mLocationOverlay.disableFollowLocation();
        mLocationOverlay.disableMyLocation();

        mMapView.getOverlays().remove(mLocationOverlay);
        mMapView.getOverlays().remove(mRadiusMarkerClusterer);
        mMarkers.clear();
        super.onPause();
    }


    public void updateUnitMarker(Device device, Position position) {
        try {
            Timber.d("UPDATE MARKER: device: $device position: $position");
            MarkerWithLabel marker = mMarkers.get(device.getId());
            marker.setTitle(device.getName());

            if (position.getFixTime() != null) {
                marker.setSnippet(position.getFixTime().toString());
            }

            marker.setIcon(getStatusArrow(device.getStatus()));
            marker.setLabelAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM + 2.f);
            if (position.getCourse() != null) {
                marker.setRotation(position.getCourse());
            }
            marker.setPosition(new GeoPoint(position.getLatitude(), position.getLongitude()));
        } catch (Exception e) {
            Timber.w(e);
        }
    }

    public void addUnitMarker(Device device) {
        try {
            Timber.tag("MapFragment").d("ADD MARKER: device: $device");
            MarkerWithLabel marker = mMarkers.get(device.getId());
            if (marker == null) {
                marker = new MarkerWithLabel(mMapView);
                mRadiusMarkerClusterer.add(marker);
                mMarkers.put(device.getId(), marker);
            }
            marker.setIcon(getStatusArrow(device.getStatus()));
            marker.setLabelAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM + 2.f);
            marker.setRelatedObject(device);
            marker.setTitle(device.getName());
        } catch (Exception e) {
            Timber.w(e);
        }
    }

    private Drawable getStatusArrow(String status) {
        if (status == null) {
            return arrowUnknownDrawable;
        }
        switch (status) {
            case "online": return arrowOnlineDrawable;
            case "offline": return arrowOfflineDrawable;
            case "unknown": return arrowUnknownDrawable;
            default: return arrowUnknownDrawable;
        }
    }

    @Override
    public void startOpenWebSocket() {

    }

    @Override
    public void finishOpenWebSocket() {

    }

    @Override
    public void failedOpenWebSocket(String message) {

    }

    @Override
    public void startCacheDevicesAndPositions() {

    }

    @Override
    public void finishCacheDevicesAndPositions() {

    }

    @Override
    public void failedCacheDevicesAndPositions(String message) {

    }

    @Override
    public void startGetUser() {

    }

    @Override
    public void finishGetUser() {

    }

    @Override
    public void failedGetUser(String message) {

    }

    @Override
    public void hideError() {

    }

    @Override
    public void successGetUser(User user) {
        Timber.d("MAP TYPE: %s", user.getMap());
        // init map
        switch (user.getMap()) {
            case "bingAerial":
                bing.setStyle(BingMapTileSource.IMAGERYSET_AERIAL);
                mMapView.setTileSource(bing);
                break;
            case "bingRoad":
                bing.setStyle(BingMapTileSource.IMAGERYSET_ROAD);
                mMapView.setTileSource(bing);
                break;
            case "custom":
            default:
                mMapView.setTileSource(TileSourceFactory.DEFAULT_TILE_SOURCE);
        }
        mMapView.invalidate();
    }


    @Override
    public void successCacheDevicesAndPositions(List<Device> devices, List<Position> positions) {
        Timber.tag("MapFragment").d("successCacheDevicesAndPositions => devices: " + devices + " positions: " + positions);
        long deviceId = getArguments().getLong("deviceId", -1);
        Timber.tag("MapFragment").d("renderMap => deviceId: " + deviceId);
        renderMarkers(devices, positions);
    }

    @Override
    public void successOpenWebSocket(Event event) {
        Timber.tag("MapFragment").d("successOpenWebSocket => Event: " + event);
        renderMarkers(event.getDevices(), event.getPositions());
    }


    private void renderMarkers(List<Device> devices, List<Position> positions) {
        if (devices != null) {
            for (Device device : devices) {
                //Timber.tag("MapFragment").d("showEvent: device: $device");
                addUnitMarker(device);
            }
        }

        if (positions != null) {
            for (Position position : positions) {
                //Timber.tag("MapFragment").d("showEvent: position: $position");
                MarkerWithLabel marker = mMarkers.get(position.getDeviceId());
                if (marker != null) {
                    updateUnitMarker((Device) marker.getRelatedObject(), position);
                }
            }
        }

        long deviceId = getArguments().getLong("deviceId", -1);
        if (deviceId != -1 && mMarkers.get(deviceId) != null) {
            mMapView.getController().animateTo(mMarkers.get(deviceId).getPosition());
        }

        mRadiusMarkerClusterer.invalidate();
        mMapView.postInvalidate();
    }
} public class PermissionsFragment extends MvpAppCompatFragment implements
        GetPermissionsView,
        CreatePermissionView,
        DeletePermissionView {
    @BindView(R.id.devices)
    ListView mDevices;

    @BindView(R.id.progress)
    ProgressBar mProgress;

    @InjectPresenter
    GetPermissionsPresenter mGetPermissionsPresenter;

    @InjectPresenter
    CreatePermissionPresenter mCreatePermissionPresenter;

    @InjectPresenter
    DeletePermissionPresenter mDeletePermissionPresenter;

    private AlertDialog mErrorDialog;


    public static PermissionsFragment getNewInstance(Bundle args) {
        // Required empty public constructor
        PermissionsFragment instance = new PermissionsFragment();
        instance.setArguments(args);
        return instance;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        MainApp.getAppComponent().inject(this);
        super.onCreate(savedInstanceState);
        Timber.d("onCreate");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Timber.d("onCreateView");
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_permissions, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Timber.d("onViewCreated");
        mGetPermissionsPresenter.getDevices(getArguments().getLong("userId", -1));
    }

    private void toggleProgressVisibility(final boolean show) {
        mProgress.setVisibility(show ? View.VISIBLE : View.GONE);
        mDevices.setVisibility(show ? View.GONE : View.VISIBLE);
    }

    @Override
    public void startGetPermissions() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishGetPermissions() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedGetPermissions(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mGetPermissionsPresenter.onErrorCancel())
                .show();
    }

    @Override
    public void startCreatePermission() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishCreatePermission() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedCreatePermission(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mCreatePermissionPresenter.onErrorCancel())
                .show();
    }

    @Override
    public void startDeletePermission() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishDeletePermission() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedDeletePermission(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mDeletePermissionPresenter.onErrorCancel())
                .show();
    }

    @Override
    public void hideError() {
        if (mErrorDialog != null && mErrorDialog.isShowing()) {
            mErrorDialog.cancel();
        }
    }

    @Override
    public void successDeletePermission() {

    }

    @Override
    public void successCreatePermission() {

    }

    @Override
    public void successGetPermissions(List<Device> allDevices, List<Device> accessDevices) {
        mDevices.setAdapter(new DevicesPermissionsAdapter(getContext(), allDevices, accessDevices, new DevicesPermissionsAdapter.PermissionListener() {
            @Override
            public void createPermission(Device device) {
                mCreatePermissionPresenter.save(Permission.newPermission(
                        getArguments().getLong("userId", -1),
                        device.getId()
                ));
            }

            @Override
            public void deletePermission(Device device) {
                mDeletePermissionPresenter.delete(Permission.newPermission(
                        getArguments().getLong("userId", -1),
                        device.getId()
                ));
            }
        }));
    }
} public class ReportFragment extends MvpAppCompatFragment implements GetPositionsView {
    @BindView(R.id.tabHost)
    TabHost mTabHost;

    @BindView(R.id.mapview)
    MapView mMapView;

    @BindView(R.id.lv_positions)
    ExpandableListView mList;

    @BindView(R.id.create_input)
    FloatingActionButton mCreateInput;

    @BindView(R.id.progress)
    ProgressBar mProgress;

    @InjectPresenter
    GetPositionsPresenter mGetPositionsPresenter;

    Polyline pathOverlay;

    private AlertDialog mErrorDialog;
    private Dialog mDateTimeDialog;

    public static ReportFragment getNewInstance(Bundle args) {
        // Required empty public constructor
        ReportFragment instance = new ReportFragment();
        instance.setArguments(args);
        return instance;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        MainApp.getAppComponent().inject(this);
        super.onCreate(savedInstanceState);
        Timber.d("onCreate");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        Timber.d("onCreateView");
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_report, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Timber.d("onViewCreated");

        mTabHost.setup();
        addTabHost(R.id.ll_map_positions, R.string.mapTitle, "map_position");
        addTabHost(R.id.ll_list_positions, R.string.listTitle, "list_positions");

        RxView.clicks(mCreateInput)
                .subscribe(aVoid -> {
                    final View dialogView = LayoutInflater.from(getContext())
                            .inflate(R.layout.fragment_date_time_interval, null);

                    mDateTimeDialog = new AlertDialog.Builder(getContext(), R.style.AppTheme_Dialog)
                            .setTitle(R.string.sharedDateTimeInterval)
                            .setView(dialogView)
                            .setPositiveButton(android.R.string.ok,
                                    (dialog, whichButton) -> {
                                        DateTimePicker from = ButterKnife.findById(mDateTimeDialog, R.id.date_time_from);
                                        DateTimePicker to = ButterKnife.findById(mDateTimeDialog, R.id.date_time_to);
                                        Timber.d("Interval time: " + from.getTime() + " to: " + to.getTime());
                                        mGetPositionsPresenter.load(
                                                getArguments().getLong("deviceId", -1),
                                                from.getTime(),
                                                to.getTime()
                                        );
                                    }
                            )
                            .setNegativeButton(android.R.string.cancel, null)
                            .create();
                    mDateTimeDialog.show();

                });
    }
    @Override
    public void onResume() {
        super.onResume();
        pathOverlay = new Polyline(getContext());
        pathOverlay.setColor(ContextCompat.getColor(getContext(), R.color.colorPrimaryDark));
        pathOverlay.getPaint().setStrokeWidth(10.0f);
        mMapView.getOverlays().add(pathOverlay);
    }

    @Override
    public void onPause() {
        mMapView.getOverlays().remove(pathOverlay);
        super.onPause();
    }

    private void addTabHost(int contentId, int indicatorId, String tag) {
        TabHost.TabSpec spec = mTabHost.newTabSpec(tag);
        spec.setContent(contentId);
        spec.setIndicator(getString(indicatorId));
        mTabHost.addTab(spec);
    }
    private void toggleProgressVisibility(final boolean show) {
        mProgress.setVisibility(show ? View.VISIBLE : View.GONE);
        mTabHost.setVisibility(show ? View.GONE : View.VISIBLE);
    }

    @Override
    public void startGetPositions() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishGetPositions() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedGetPositions(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mGetPositionsPresenter.onErrorCancel())
                .show();
    }

    @Override
    public void hideError() {
        if (mErrorDialog != null && mErrorDialog.isShowing()) {
            mErrorDialog.cancel();
        }
    }

    @Override
    public void successGetPositions(List<Position> positions) {
        Timber.d("Interval positions: " + positions);

        ArrayList<GeoPoint> points = new ArrayList<>();
        for (Position position: positions) {
            points.add(new GeoPoint(position.getLatitude(), position.getLongitude()));
        }
        pathOverlay.setPoints(points);
        mMapView.zoomToBoundingBox(BoundingBox.fromGeoPoints(points), true);
        mMapView.postInvalidate();

        mList.setAdapter(new PositionsExpandableListAdapter(getContext(), 0, positions));
    }
} public class ServerFragment extends MvpAppCompatFragment implements GetServerView, UpdateServerView {
    @BindView(R.id.form_layout)
    RelativeLayout mForm;

    @BindView(R.id.registration_input_layout)
    TextInputLayout mRegistrationInputLayout;
    @BindView(R.id.registration_input)
    CheckBox mRegistrationInput;

    @BindView(R.id.readonly_input_layout)
    TextInputLayout mReadonlyInputLayer;
    @BindView(R.id.readonly_input)
    CheckBox mReadonlyInput;

    @BindView(R.id.map_input)
    MaterialSpinner mMapInput;

    @BindView(R.id.bing_key_input_layout)
    TextInputLayout mBingKeyInputLayer;
    @BindView(R.id.bing_key_input)
    EditText mBingKeyInput;

    @BindView(R.id.map_url_input_layout)
    TextInputLayout mMapUrlInputLayer;
    @BindView(R.id.map_url_input)
    EditText mMapUrlInput;

    @BindView(R.id.distance_unit_input)
    MaterialSpinner mDistanceUnitInput;

    @BindView(R.id.speed_unit_input)
    MaterialSpinner mSpeedUnitInput;

    @BindView(R.id.latitude_input_layout)
    TextInputLayout mLatitudeInputLayout;
    @BindView(R.id.latitude_input)
    EditText mLatitudeInput;

    @BindView(R.id.longitude_input_layout)
    TextInputLayout mLongitudeInputLayout;
    @BindView(R.id.longitude_input)
    EditText mLongitudeInput;

    @BindView(R.id.zoom_input_layout)
    TextInputLayout mZoomInputLayout;
    @BindView(R.id.zoom_input)
    EditText mZoomInput;

    @BindView(R.id.twelve_hour_format_input)
    CheckBox mTwelveHourFormatInput;

    @BindView(R.id.submit_input)
    FloatingActionButton mSubmitInput;

    @BindView(R.id.progress)
    ProgressBar mProgress;

    @BindArray(R.array.mapType)
    String[] mapType;

    @BindArray(R.array.distanceUnit)
    String[] distanceUnit;

    @BindArray(R.array.speedUnit)
    String[] speedUnit;


    @InjectPresenter
    CacheServerPresenter mCacheServerPresenter;

    @InjectPresenter
    UpdateServerPresenter mUpdateServerPresenter;

    @Inject
    Router router;

    private AlertDialog mErrorDialog;

    public static ServerFragment getNewInstance(Bundle args) {
        // Required empty public constructor
        ServerFragment  instance = new ServerFragment();
        instance.setArguments(args);
        return instance;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        MainApp.getAppComponent().inject(this);
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_server, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        Observable<Boolean> bingKeyObservable = RxTextView.textChanges(mBingKeyInput)
                .map(inputText -> TextUtils.isEmpty(inputText) || BING_KEY_PATTERN.matcher(inputText).matches())
                .distinctUntilChanged();

        bingKeyObservable.subscribe(isValid -> {
            mBingKeyInputLayer.setError("Invalid Bing Key");
            mBingKeyInputLayer.setErrorEnabled(!isValid);
        });

        Observable<Boolean> mapUrlObservable = RxTextView.textChanges(mMapUrlInput)
                .map(inputText -> TextUtils.isEmpty(inputText) || URLUtil.isValidUrl(inputText.toString()))
                .distinctUntilChanged();

        mapUrlObservable.subscribe(isValid -> {
            mMapUrlInputLayer.setError("Invalid Map Url");
            mMapUrlInputLayer.setErrorEnabled(!isValid);
        });


        Observable<Boolean> latitudeObservable = RxTextView.textChanges(mLatitudeInput)
                .map(inputText -> TextUtils.isEmpty(inputText) || LATITUDE_PATTERN.matcher(inputText).matches())
                .distinctUntilChanged();

        latitudeObservable.subscribe(isValid -> {
            mLatitudeInputLayout.setError("Invalid Latitude");
            mLatitudeInputLayout.setErrorEnabled(!isValid);
        });

        Observable<Boolean> longitudeObservable = RxTextView.textChanges(mLongitudeInput)
                .map(inputText -> TextUtils.isEmpty(inputText) || LONGITUDE_PATTERN.matcher(inputText).matches())
                .distinctUntilChanged();

        longitudeObservable.subscribe(isValid -> {
            mLongitudeInputLayout.setError("Invalid Longitude");
            mLongitudeInputLayout.setErrorEnabled(!isValid);
        });

        Observable<Boolean> zoomObservable = RxTextView.textChanges(mZoomInput)
                .map(inputText -> TextUtils.isEmpty(inputText) || ZOOM_PATTERN.matcher(inputText).matches())
                .distinctUntilChanged();

        zoomObservable.subscribe(isValid -> {
            mZoomInputLayout.setError("Invalid Zoom");
            mZoomInputLayout.setErrorEnabled(!isValid);
        });

        Observable.combineLatest(
                bingKeyObservable,
                mapUrlObservable,
                latitudeObservable,
                longitudeObservable,
                zoomObservable,
                (bingKeyValid, mapUrlValid, latitudeValid, longitudeValid, zoomValid) -> bingKeyValid && mapUrlValid && latitudeValid && longitudeValid && zoomValid)
                .distinctUntilChanged()
                .subscribe(valid -> mSubmitInput.setEnabled(valid));

        RxView.clicks(mSubmitInput)
                .subscribe(aVoid -> {
                    Server server = getArguments().getParcelable("server");
                    server.setRegistration(mRegistrationInput.isChecked());
                    server.setReadonly(mReadonlyInput.isChecked());
                    server.setMap(mapType[mMapInput.getSelectedItemPosition()]);
                    server.setBingKey(mBingKeyInput.getText().toString());
                    server.setMapUrl(mMapUrlInput.getText().toString());
                    server.setDistanceUnit(distanceUnit[mDistanceUnitInput.getSelectedItemPosition()]);
                    server.setSpeedUnit(speedUnit[mSpeedUnitInput.getSelectedItemPosition()]);
                    server.setLatitude(Double.parseDouble(mLatitudeInput.getText().toString()));
                    server.setLongitude(Double.parseDouble(mLongitudeInput.getText().toString()));
                    server.setZoom(Integer.parseInt(mZoomInput.getText().toString()));
                    server.setTwelveHourFormat(mTwelveHourFormatInput.isChecked());
                    mUpdateServerPresenter.save(server);
                });

        mCacheServerPresenter.load();
    }

    private void toggleProgressVisibility(final boolean show) {
        mProgress.setVisibility(show ? View.VISIBLE : View.GONE);
        mForm.setVisibility(show ? View.GONE : View.VISIBLE);
    }

    @Override
    public void startUpdateServer() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishUpdateServer() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedUpdateServer(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mUpdateServerPresenter.onErrorCancel())
                .show();
    }

    @Override
    public void startGetServer() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishGetServer() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedGetServer(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mCacheServerPresenter.onErrorCancel())
                .show();
    }

    @Override
    public void hideError() {
        if (mErrorDialog != null && mErrorDialog.isShowing()) {
            mErrorDialog.cancel();
        }
    }

    @Override
    public void successGetServer(Server server) {
        try {
            Timber.d("Server: " + server);
            getArguments().putParcelable("server", server);
            mRegistrationInput.setChecked(server.getRegistration());
            mReadonlyInput.setChecked(server.getReadonly());

            int mapTypeIndex = Arrays.asList(mapType).indexOf(server.getMap());
            int distanceUnitIndex = Arrays.asList(distanceUnit).indexOf(server.getDistanceUnit());
            int speedUnitIndex = Arrays.asList(speedUnit).indexOf(server.getSpeedUnit());

            mMapInput.setSelection(mapTypeIndex != -1 ? mapTypeIndex : 0);
            mDistanceUnitInput.setSelection(distanceUnitIndex != -1 ? distanceUnitIndex : 0);
            mSpeedUnitInput.setSelection(speedUnitIndex != -1 ? speedUnitIndex : 0);

            mBingKeyInput.setText(server.getBingKey());
            mMapUrlInput.setText(server.getMapUrl());
            mLatitudeInput.setText(server.getLatitude() != null ? String.valueOf(server.getLatitude()) : "");
            mLongitudeInput.setText(server.getLongitude() != null ? String.valueOf(server.getLongitude()) : "");
            mZoomInput.setText(server.getZoom() != null ? String.valueOf(server.getZoom()) : "");
            mTwelveHourFormatInput.setChecked(server.getTwelveHourFormat());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void successUpdateServer() {
        router.backTo(null);
    }
} public class SignInFragment extends MvpAppCompatFragment implements SignInView {
    @BindView(R.id.form_layout)
    LinearLayout mForm;
    @BindView(R.id.email_input_layout)
    TextInputLayout mEmailInputLayer;
    @BindView(R.id.email_input)
    EditText mEmailInput;
    @BindView(R.id.password_input_layout)
    TextInputLayout mPasswordInputLayer;
    @BindView(R.id.password_input)
    EditText mPasswordInput;
    @BindView(R.id.save_input)
    AppCompatCheckBox mSaveInput;
    @BindView(R.id.sign_in_button)
    Button mSignIn;
    @BindView(R.id.sign_up_button)
    TextView mSignUp;
    @BindView(R.id.settings_button)
    ImageView mSettings;

    @BindArray(R.array.protocol_version)
    String[] protocolVersions;

    @BindArray(R.array.protocol_version_value)
    String[] protocolVersionValues;

    @Inject
    Router router;

    @Inject
    SharedPreferences sharedPreferences;

    @InjectPresenter
    SignInPresenter mSignInPresenter;

    private Dialog mSettingsDialog;
    private AlertDialog mErrorDialog;
    private OnViewListener mListener;

    public interface OnViewListener {
        void successSignIn(User user);
    }

    public static SignInFragment getNewInstance(Object data) {
        // Required empty public constructor
        SignInFragment  instance = new SignInFragment();
        instance.setArguments(new Bundle());
        return instance;
    }

    // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface

        try {
            // Instantiate the NoticeDialogListener so we can send events to the host
            mListener = (OnViewListener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement SignInFragment.OnViewListener");
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        MainApp.getAppComponent().inject(this);
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_signin, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        RxView.clicks(mSettings)
                .subscribe(aVoid -> {
                    final View dialogView = LayoutInflater.from(getContext())
                            .inflate(R.layout.fragment_settings, null);

                    Timber.e("PREFS DNS: " + sharedPreferences.getString("dns", "web.erlymon.org"));

                    mSettingsDialog = new AlertDialog.Builder(getContext(), R.style.AppTheme_Dialog)
                            .setTitle(R.string.settingsTitle)
                            .setView(dialogView)
                            .setPositiveButton(android.R.string.ok,
                                    (dialog, whichButton) -> {
                                        AutoCompleteTextView dns = ButterKnife.findById((Dialog) dialog, R.id.dns);
                                        CheckBox sslOrTls = ButterKnife.findById((Dialog) dialog, R.id.sslOrTls);
                                        AppCompatSpinner protocolVersion  = ButterKnife.findById((Dialog) dialog, R.id.protocolVersion);
                                        sharedPreferences.edit()
                                                .putString("dns", dns.getText().toString())
                                                .putBoolean("sslOrTls", sslOrTls.isChecked())
                                                .putFloat("protocolVersion", Float.parseFloat(protocolVersionValues[protocolVersion.getSelectedItemPosition()]))
                                                .apply();
                                        MainApp.init(getContext());
                                    }
                            )
                            .setNegativeButton(android.R.string.cancel, null)
                            .create();


                    AutoCompleteTextView dns = ButterKnife.findById(dialogView, R.id.dns);
                    dns.setText(sharedPreferences.getString("dns", "web.erlymon.org"));

                    CheckBox sslOrTls = ButterKnife.findById(dialogView, R.id.sslOrTls);
                    sslOrTls.setChecked(sharedPreferences.getBoolean("sslOrTls", false));

                    List<String> pv = Arrays.asList(protocolVersionValues);
                    AppCompatSpinner protocolVersion  = ButterKnife.findById(dialogView, R.id.protocolVersion);
                    protocolVersion.setSelection(pv.indexOf(String.valueOf(sharedPreferences.getFloat("protocolVersion", 3.4f))));

                    mSettingsDialog.show();
                });

        RxView.clicks(mSignIn)
                .compose(RxPermissions.getInstance(getContext()).ensure(Manifest.permission.WRITE_EXTERNAL_STORAGE))
                .subscribe(granted -> {
                    if (granted) {
                        mSignInPresenter.signIn(mEmailInput.getText().toString(), mPasswordInput.getText().toString());
                    } else {
                        router.showSystemMessage(getString(R.string.errorPermissionWriteStorage));
                    }
                });

        RxView.clicks(mSignUp)
                .subscribe(aVoid -> router.navigateTo(Screens.SIGN_UP_SCREEN));

        if (sharedPreferences.getBoolean("save", false)) {
            mEmailInput.setText(sharedPreferences.getString("email", ""));
            mPasswordInput.setText(sharedPreferences.getString("password", ""));
            mSaveInput.setChecked(sharedPreferences.getBoolean("save", false));
            mSignInPresenter.signIn(sharedPreferences.getString("email", ""), sharedPreferences.getString("password", ""));
        }
    }

    private void toggleProgressVisibility(boolean b) {
    }

    @Override
    public void startSignIn() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishSignIn() {
        toggleProgressVisibility(false);
    }

    @Override
    public void successSignIn(User user) {
        Timber.d("successSignIn");
        //if (mSaveInput.isChecked()) {
            sharedPreferences.edit()
                    .putLong("userId", user.getId())
                    .putBoolean("save", mSaveInput.isChecked())
                    .putString("email", mEmailInput.getText().toString())
                    .putString("password", mPasswordInput.getText().toString())
                    .apply();
        //}
        //router.showSystemMessage("showToolbar");
        mListener.successSignIn(user);
    }


    @Override
    public void failedSignIn(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mSignInPresenter.onErrorCancel())
                .show();
    }

    @Override
    public void hideError() {
        if (mErrorDialog != null && mErrorDialog.isShowing()) {
            mErrorDialog.cancel();
        }
    }
} public class SignUpFragment extends MvpAppCompatFragment implements SignUpView {
    @BindView(R.id.form_layout)
    LinearLayout mForm;
    @BindView(R.id.name_input_layout)
    TextInputLayout mNameInputLayer;
    @BindView(R.id.name_input)
    EditText mNameInput;
    @BindView(R.id.email_input_layout)
    TextInputLayout mEmailInputLayer;
    @BindView(R.id.email_input)
    EditText mEmailInput;
    @BindView(R.id.password_input_layout)
    TextInputLayout mPasswordInputLayer;
    @BindView(R.id.password_input)
    EditText mPasswordInput;
    @BindView(R.id.fab_account_save)
    FloatingActionButton mFabAccountSave;
    @BindView(R.id.progress)
    ProgressBar mProgress;

    @InjectPresenter
    SignUpPresenter mSignUpPresenter;

    @Inject
    Router router;

    private AlertDialog mErrorDialog;

    public static SignUpFragment getNewInstance(Object data) {
        // Required empty public constructor
        SignUpFragment  instance = new SignUpFragment();

        return instance;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        MainApp.getAppComponent().inject(this);
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_signup, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        Observable<Boolean> nameObservable = RxTextView.textChanges(mNameInput)
                .map(inputText -> !TextUtils.isEmpty(inputText) && NAME_PATTERN.matcher(inputText).matches())
                .distinctUntilChanged();

        nameObservable.subscribe(isValid -> {
            mNameInputLayer.setError("Invalid Name");
            mNameInputLayer.setErrorEnabled(!isValid);
        });

        Observable<Boolean> emailObservable = RxTextView.textChanges(mEmailInput)
                .map(inputText -> !TextUtils.isEmpty(inputText) && android.util.Patterns.EMAIL_ADDRESS.matcher(inputText).matches())
                .distinctUntilChanged();

        emailObservable.subscribe(isValid -> {
            mEmailInputLayer.setError("Invalid Email");
            mEmailInputLayer.setErrorEnabled(!isValid);
        });

        Observable<Boolean> passwordObservable = RxTextView.textChanges(mPasswordInput)
                .map(inputText -> !TextUtils.isEmpty(inputText) || PASSWORD_PATTERN.matcher(inputText).matches())
                .distinctUntilChanged();

        passwordObservable.subscribe(isValid -> {
            mPasswordInputLayer.setError("Invalid Password");
            mPasswordInputLayer.setErrorEnabled(!isValid);
        });

        Observable.combineLatest(
                nameObservable,
                emailObservable,
                passwordObservable,
                (nameValid, emailValid, passwordValid) -> nameValid && emailValid && passwordValid)
                .distinctUntilChanged()
                .subscribe(valid -> mFabAccountSave.setEnabled(valid));

        RxView.clicks(mFabAccountSave)
                .subscribe(aVoid -> {
                    mSignUpPresenter.signUp(
                            mNameInput.getText().toString(),
                            mEmailInput.getText().toString(),
                            mPasswordInput.getText().toString()
                            );
                });
    }

    private void toggleProgressVisibility(final boolean show) {
        mProgress.setVisibility(show ? View.VISIBLE : View.GONE);
        mForm.setVisibility(show ? View.GONE : View.VISIBLE);
    }

    @Override
    public void startSignUp() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishSignUp() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedSignUp(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mSignUpPresenter.onErrorCancel())
                .show();
    }

    @Override
    public void hideError() {
        if (mErrorDialog != null && mErrorDialog.isShowing()) {
            mErrorDialog.cancel();
        }
    }

    @Override
    public void successSignUp() {
        router.backTo(null);
    }
} public class UserFragment  extends MvpAppCompatFragment implements GetUserView, CreateUserView, UpdateUserView {
    @BindView(R.id.form_layout)
    RelativeLayout mForm;

    @BindView(R.id.name_input_layout)
    TextInputLayout mNameInputLayout;
    @BindView(R.id.name_input)
    EditText mNameInput;

    @BindView(R.id.email_input_layout)
    TextInputLayout mEmailInputLayout;
    @BindView(R.id.email_input)
    EditText mEmailInput;

    @BindView(R.id.password_input_layout)
    TextInputLayout mPasswordInputLayout;
    @BindView(R.id.password_input)
    EditText mPasswordInput;

    @BindView(R.id.admin_input)
    CheckBox mAdminInput;

    @BindView(R.id.map_input)
    MaterialSpinner mMapInput;

    @BindView(R.id.distance_unit_input)
    MaterialSpinner mDistanceUnitInput;

    @BindView(R.id.speed_unit_input)
    MaterialSpinner mSpeedUnitInput;

    @BindView(R.id.latitude_input_layout)
    TextInputLayout mLatitudeInputLayout;
    @BindView(R.id.latitude_input)
    EditText mLatitudeInput;

    @BindView(R.id.longitude_input_layout)
    TextInputLayout mLongitudeInputLayout;
    @BindView(R.id.longitude_input)
    EditText mLongitudeInput;

    @BindView(R.id.zoom_input_layout)
    TextInputLayout mZoomInputLayout;
    @BindView(R.id.zoom_input)
    EditText mZoomInput;

    @BindView(R.id.twelve_hour_format_input)
    CheckBox mTwelveHourFormatInput;

    @BindView(R.id.submit_input)
    FloatingActionButton mSubmitInput;

    @BindView(R.id.progress)
    ProgressBar mProgress;

    @BindArray(R.array.mapType)
    String[] mapType;

    @BindArray(R.array.distanceUnit)
    String[] distanceUnit;

    @BindArray(R.array.speedUnit)
    String[] speedUnit;


    @InjectPresenter
    CacheUserPresenter mCacheUserPresenter;

    @InjectPresenter
    CreateUserPresenter mCreateUserPresenter;

    @InjectPresenter
    UpdateUserPresenter mUpdateUserPresenter;

    @Inject
    Router router;

    private AlertDialog mErrorDialog;

    public static UserFragment getNewInstance(Bundle args) {
        // Required empty public constructor
        UserFragment  instance = new UserFragment();
        instance.setArguments(args);
        return instance;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        MainApp.getAppComponent().inject(this);
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_user, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        Observable<Boolean> nameObservable = RxTextView.textChanges(mNameInput)
                .map(inputText -> !TextUtils.isEmpty(inputText) && NAME_PATTERN.matcher(inputText).matches())
                .distinctUntilChanged();

        nameObservable.subscribe(isValid -> {
            mNameInputLayout.setError("Invalid Name");
            mNameInputLayout.setErrorEnabled(!isValid);
        });

        Observable<Boolean> emailObservable = RxTextView.textChanges(mEmailInput)
                .map(inputText -> {
                    if (getArguments().getBoolean("isAdmin", false)) {
                        return true;
                    } else {
                        return !TextUtils.isEmpty(inputText) && android.util.Patterns.EMAIL_ADDRESS.matcher(inputText).matches();
                    }
                })
                .distinctUntilChanged();

        emailObservable.subscribe(isValid -> {
            mEmailInputLayout.setError("Invalid Email");
            mEmailInputLayout.setErrorEnabled(!isValid);
        });

        Observable<Boolean> passwordObservable = RxTextView.textChanges(mPasswordInput)
                .map(inputText -> {
                    if (getArguments().getLong("userId", -1) == -1) {
                        return !TextUtils.isEmpty(inputText) && PASSWORD_PATTERN.matcher(inputText).matches();
                    } else {
                        return TextUtils.isEmpty(inputText) || PASSWORD_PATTERN.matcher(inputText).matches();
                    }
                })
                .distinctUntilChanged();

        passwordObservable.subscribe(isValid -> {
            mPasswordInputLayout.setError("Invalid Password");
            mPasswordInputLayout.setErrorEnabled(!isValid);
        });


        Observable<Boolean> latitudeObservable = RxTextView.textChanges(mLatitudeInput)
                .map(inputText -> TextUtils.isEmpty(inputText) || LATITUDE_PATTERN.matcher(inputText).matches())
                .distinctUntilChanged();

        latitudeObservable.subscribe(isValid -> {
            mLatitudeInputLayout.setError("Invalid Latitude");
            mLatitudeInputLayout.setErrorEnabled(!isValid);
        });

        Observable<Boolean> longitudeObservable = RxTextView.textChanges(mLongitudeInput)
                .map(inputText -> TextUtils.isEmpty(inputText) || LONGITUDE_PATTERN.matcher(inputText).matches())
                .distinctUntilChanged();

        longitudeObservable.subscribe(isValid -> {
            mLongitudeInputLayout.setError("Invalid Longitude");
            mLongitudeInputLayout.setErrorEnabled(!isValid);
        });

        Observable<Boolean> zoomObservable = RxTextView.textChanges(mZoomInput)
                .map(inputText -> TextUtils.isEmpty(inputText) || ZOOM_PATTERN.matcher(inputText).matches())
                .distinctUntilChanged();

        zoomObservable.subscribe(isValid -> {
            mZoomInputLayout.setError("Invalid Zoom");
            mZoomInputLayout.setErrorEnabled(!isValid);
        });

        Observable.combineLatest(
                nameObservable,
                emailObservable,
                passwordObservable,
                latitudeObservable,
                longitudeObservable,
                zoomObservable,
                (nameValid, emailValid, passwordValid, latitudeValid, longitudeValid, zoomValid) -> nameValid && emailValid && passwordValid && latitudeValid && longitudeValid && zoomValid)
                .distinctUntilChanged()
                .subscribe(valid -> mSubmitInput.setEnabled(valid));

        RxView.clicks(mSubmitInput)
                .subscribe(aVoid -> {
                    User user = getArguments().getParcelable("user");
                    user.setName(mNameInput.getText().toString());
                    user.setEmail(mEmailInput.getText().toString());
                    user.setPassword(mPasswordInput.getText().length() > 0 ? mPasswordInput.getText().toString() : "");
                    user.setAdmin(mAdminInput.isChecked());
                    user.setLanguage(Locale.getDefault().getLanguage());
                    //user.setReadonly(mReadonlyInput.isChecked());
                    user.setMap(mapType[mMapInput.getSelectedItemPosition()]);
                    user.setDistanceUnit(distanceUnit[mDistanceUnitInput.getSelectedItemPosition()]);
                    user.setSpeedUnit(speedUnit[mSpeedUnitInput.getSelectedItemPosition()]);
                    user.setLatitude(mLatitudeInput.getText().length() > 0 ? Double.parseDouble(mLatitudeInput.getText().toString()) : null);
                    user.setLongitude(mLongitudeInput.getText().length() > 0 ? Double.parseDouble(mLongitudeInput.getText().toString()) : null);
                    user.setZoom(mZoomInput.getText().length() > 0 ? Integer.parseInt(mZoomInput.getText().toString()) : null);
                    user.setTwelveHourFormat(mTwelveHourFormatInput.isChecked()); // version > 3.4
                    if (user.getId() == 0) {
                        mCreateUserPresenter.save(user);
                    } else {
                        mUpdateUserPresenter.save(user);
                    }
                });

        User session = getArguments().getParcelable("session");
        if (session != null) {
            mAdminInput.setChecked(session.getAdmin());
            mAdminInput.setEnabled(session.getAdmin());
        }

        if (getArguments().getLong("userId", -1) == -1) {
            successGetUser(new User());
        } else {
            mCacheUserPresenter.load(getArguments().getLong("userId", -1));
        }
    }

    private void toggleProgressVisibility(final boolean show) {
        mProgress.setVisibility(show ? View.VISIBLE : View.GONE);
        mForm.setVisibility(show ? View.GONE : View.VISIBLE);
    }

    @Override
    public void startGetUser() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishGetUser() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedGetUser(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mCacheUserPresenter.onErrorCancel())
                .show();
    }

    @Override
    public void startUpdateUser() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishUpdateUser() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedUpdateUser(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mUpdateUserPresenter.onErrorCancel())
                .show();
    }

    @Override
    public void startCreateUser() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishCreateUser() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedCreateUser(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mCacheUserPresenter.onErrorCancel())
                .show();
    }

    @Override
    public void hideError() {
        if (mErrorDialog != null && mErrorDialog.isShowing()) {
            mErrorDialog.cancel();
        }
    }

    @Override
    public void successCreateUser() {
        router.backTo(null);
    }

    @Override
    public void successUpdateUser() {
        router.backTo(null);
    }

    @Override
    public void successGetUser(User user) {
        try {
            Timber.d("User: " + user);
            getArguments().putParcelable("user", user);
            mNameInput.setText(user.getName() != null ? user.getName() : "");
            mEmailInput.setText(user.getEmail() != null ? user.getEmail() : "");
            mAdminInput.setChecked(user.getAdmin() != null ? user.getAdmin() : false);

            int mapTypeIndex = Arrays.asList(mapType).indexOf(user.getMap());
            int distanceUnitIndex = Arrays.asList(distanceUnit).indexOf(user.getDistanceUnit());
            int speedUnitIndex = Arrays.asList(speedUnit).indexOf(user.getSpeedUnit());

            mMapInput.setSelection(mapTypeIndex != -1 ? mapTypeIndex : 0);
            mDistanceUnitInput.setSelection(distanceUnitIndex != -1 ? distanceUnitIndex : 0);
            mSpeedUnitInput.setSelection(speedUnitIndex != -1 ? speedUnitIndex : 0);
            mLatitudeInput.setText(user.getLatitude() != null ? String.valueOf(user.getLatitude()) : "");
            mLongitudeInput.setText(user.getLongitude() != null ? String.valueOf(user.getLongitude()) : "");
            mZoomInput.setText(user.getZoom() != null ? String.valueOf(user.getZoom()) : "");
            mTwelveHourFormatInput.setChecked(user.getTwelveHourFormat() != null ? user.getTwelveHourFormat() : false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
} public class UsersFragment  extends MvpAppCompatFragment implements CacheUsersView, DeleteUserView {
    @BindView(R.id.users)
    RecyclerView mUsers;
    @BindView(R.id.create_input)
    FloatingActionButton mCreateInput;

    @InjectPresenter
    CacheUsersPresenter mCacheUsersPresenter;

    @InjectPresenter
    DeleteUserPresenter mDeleteUserPresenter;

    @Inject
    Router router;

    private UsersCursorAdapter mAdapter;

    private AlertDialog mConfirmDialog;
    private AlertDialog mErrorDialog;

    public static UsersFragment getNewInstance(Bundle args) {
        // Required empty public constructor
        UsersFragment  instance = new UsersFragment();
        instance.setArguments(args);
        return instance;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        MainApp.getAppComponent().inject(this);
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_users, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mUsers.setLayoutManager(new LinearLayoutManager(getContext()));
        mUsers.setHasFixedSize(true);
        mUsers.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));
        mUsers.addOnItemTouchListener(new RecyclerItemClickListener(getContext(), (view1, position) -> {
            Timber.d("User position: " + position + " id: " + mAdapter.getItemId(position));
            PopupMenu popupMenu = new PopupMenu(getContext(), view1);
            popupMenu.inflate(R.menu.fragment_users_popupmenu);
            popupMenu.setOnMenuItemClickListener(new OnExecUserPopupMenu(mAdapter.getItemId(position)));
            popupMenu.show();
        }));
        RxView.clicks(mCreateInput)
                .subscribe(aVoid -> {
                    User session = getArguments().getParcelable("session");
                    Bundle args = new Bundle();
                    args.putLong("userId", -1);
                    args.putParcelable("session", session);
                    router.navigateTo(Screens.USER_SCREEN, args);
                });

        mCacheUsersPresenter.load();
    }

    @Override
    public void onDestroyView() {
        mAdapter = null;
        super.onDestroyView();
    }

    private void toggleProgressVisibility(boolean b) {
    }

    @Override
    public void startCacheUsers() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishCacheUsers() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedCacheUsers(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mCacheUsersPresenter.onErrorCancel())
                .show();
    }

    @Override
    public void startDeleteUser() {
        toggleProgressVisibility(true);
    }

    @Override
    public void finishDeleteUser() {
        toggleProgressVisibility(false);
    }

    @Override
    public void failedDeleteUser(String message) {
        mErrorDialog = new AlertDialog.Builder(getContext())
                .setTitle(R.string.app_name)
                .setMessage(message)
                .setOnCancelListener(dialog -> mDeleteUserPresenter.onErrorCancel())
                .show();
    }

    @Override
    public void hideError() {
        if (mErrorDialog != null && mErrorDialog.isShowing()) {
            mErrorDialog.cancel();
        }
    }

    @Override
    public void successDeleteUser() {

    }

    @Override
    public void successCacheUsers(Cursor cursor) {
        Timber.d("Count devices: " + cursor.getCount());
        if (mAdapter == null) {
            mAdapter = new UsersCursorAdapter(cursor);
            mUsers.setAdapter(mAdapter);
        } else {
            mAdapter.changeCursor(cursor);
        }
    }

    private class OnExecUserPopupMenu implements PopupMenu.OnMenuItemClickListener {
        long mUserId;

        public OnExecUserPopupMenu(long userId) {
            this.mUserId = userId;
        }

        @Override
        public boolean onMenuItemClick(MenuItem item) {
            User session = getArguments().getParcelable("session");
            Bundle args = new Bundle();
            args.putParcelable("session", session);
            switch (item.getItemId()) {
                case R.id.action_user_edit:
                    args.putLong("userId", mUserId);
                    args.putParcelable("session", session);
                    router.navigateTo(Screens.USER_SCREEN, args);
                    break;
                case R.id.action_user_remove:
                    mConfirmDialog = new AlertDialog.Builder(getContext())
                            .setTitle(R.string.userTitle)
                            .setMessage(R.string.sharedRemoveConfirm)
                            .setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
                                mDeleteUserPresenter.delete(mUserId);
                            })
                            .setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> mConfirmDialog.dismiss())
                            .show();
                    break;
                case R.id.action_user_devices:
                    args.putLong("userId", mUserId);
                    router.navigateTo(Screens.PERMISSIONS_SCREEN, args);
                    break;
            }
            return false;
        }
    }
}
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.design.widget.NavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.arellomobile.mvp.MvpAppCompatActivity;
import com.arellomobile.mvp.presenter.InjectPresenter;
import com.squareup.otto.Bus;
import org.erlymon.monitor.MainApp;
import org.erlymon.monitor.R;
import org.erlymon.monitor.Screens;
import org.erlymon.monitor.mvp.model.Server;
import org.erlymon.monitor.mvp.model.User;
import org.erlymon.monitor.mvp.presenter.CheckSessionPresenter;
import org.erlymon.monitor.mvp.presenter.GetServerPresenter;
import org.erlymon.monitor.mvp.presenter.SignOutPresenter;
import org.erlymon.monitor.mvp.view.CheckSessionView;
import org.erlymon.monitor.mvp.view.GetServerView;
import org.erlymon.monitor.mvp.view.SignOutView;
import org.erlymon.monitor.ui.fragment.AboutFragment;
import org.erlymon.monitor.ui.fragment.DeviceFragment;
import org.erlymon.monitor.ui.fragment.DevicesFragment;
import org.erlymon.monitor.ui.fragment.MapFragment;
import org.erlymon.monitor.ui.fragment.PermissionsFragment;
import org.erlymon.monitor.ui.fragment.ReportFragment;
import org.erlymon.monitor.ui.fragment.ServerFragment;
import org.erlymon.monitor.ui.fragment.SignInFragment;
import org.erlymon.monitor.ui.fragment.SignUpFragment;
import org.erlymon.monitor.ui.fragment.UserFragment;
import org.erlymon.monitor.ui.fragment.UsersFragment;
import org.osmdroid.tileprovider.tilesource.bing.BingMapTileSource;
import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
import ru.terrakok.cicerone.Navigator;
import ru.terrakok.cicerone.NavigatorHolder;
import ru.terrakok.cicerone.Router;
import ru.terrakok.cicerone.android.SupportFragmentNavigator;
import timber.log.Timber;


        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this, drawer, mToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.addDrawerListener(toggle);
        toggle.syncState();

        navigationView.setNavigationItemSelectedListener(this);

        navigatorHolder.setNavigator(navigator);
        mGetServerPresenter.getServer();

        Timber.d("CURRENT USER: " + getIntent().getParcelableExtra("session"));
    }

    @Override
    public void onResume() {
        super.onResume();
        bus.register(this);
    }

    @Override
    public void onPause() {
        bus.unregister(this);
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        navigatorHolder.removeNavigator();
        super.onDestroy();
    }

    @Override
    public void onBackPressed() {
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @SuppressWarnings("StatementWithEmptyBody")
    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.
        User session = getIntent().getParcelableExtra("session");
        Server server = getIntent().getParcelableExtra("server");
        Bundle args = new Bundle();
        args.putParcelable("session", session);
        args.putParcelable("server", server);
        switch (item.getItemId()) {
            case R.id.nav_map:
                router.newRootScreen(Screens.MAP_SCREEN, args);
                break;
            case R.id.nav_devices:
                router.newRootScreen(Screens.DEVICES_SCREEN, args);
                break;
            case R.id.nav_users:
                router.newRootScreen(Screens.USERS_SCREEN, args);
                break;
            case R.id.nav_server:
                router.navigateTo(Screens.SERVER_SCREEN, args);
                break;
            case R.id.nav_account:
                args.putLong("userId", session.getId());
                router.navigateTo(Screens.USER_SCREEN, args);
                break;
            case R.id.nav_about:
                router.navigateTo(Screens.ABOUT_SCREEN);
                break;
            case R.id.nav_sign_out:
                mSignOutPresenter.signOut();
                break;
        }

        drawer.closeDrawer(GravityCompat.START);
        return true;
    }

    private Navigator navigator = new SupportFragmentNavigator(getSupportFragmentManager(),
            R.id.container) {
        @Override
        protected Fragment createFragment(String screenKey, Object data) {
            switch(screenKey) {
                case Screens.SIGN_IN_SCREEN:
                    return SignInFragment.getNewInstance(data);
                case Screens.SIGN_UP_SCREEN:
                    return SignUpFragment.getNewInstance(data);
                case Screens.MAP_SCREEN:
                    return MapFragment.getNewInstance((Bundle) data);
                case Screens.DEVICES_SCREEN:
                    return DevicesFragment.getNewInstance((Bundle) data);
                case Screens.USERS_SCREEN:
                    return UsersFragment.getNewInstance((Bundle) data);
                case Screens.SERVER_SCREEN:
                    return ServerFragment.getNewInstance((Bundle) data);
                case Screens.USER_SCREEN:
                    return UserFragment.getNewInstance((Bundle) data);