@Module
public class BusModule {
    Bus bus;

    public BusModule() {
        bus = new Bus(ThreadEnforcer.MAIN);
    }

    @Provides
    @Singleton
    Bus provideBus() {
        return bus;
    }
} @Module
public class ContextModule {
    private Context mContext;

    public ContextModule(Context context) {
        mContext = context;
    }

    @Provides
    @Singleton
    public Context provideContext() {
        return mContext;
    }
} @Module(includes = {PreferencesModule.class})
public class GsonModule {
    @Provides
    @Singleton
    Gson provideGson(SharedPreferences sharedPreferences) {
        return new GsonBuilder()
                //.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
                .registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
                    final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.ENGLISH);
                    @Override
                    public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                        try {
                            return new Date(df.parse(json.getAsString()).getTime());
                        } catch (final java.text.ParseException e) {
                            //e.printStackTrace();
                            return null;
                        }
                    }
                })
                .setVersion(sharedPreferences.getFloat("protocolVersion", 3.4f))
                //.setVersion(3.4)
                .create();
    }
} @Module(includes = {ContextModule.class})
public class HttpClientModule {
    private static final int CONNECT_TIMEOUT = 5;
    private static final int READ_TIMEOUT = 5;
    private static final int WRITE_TIMEOUT = 5;

    @Provides
    @Singleton
    public OkHttpClient provideOkHttpClient(ClearableCookieJar cookieJar, HttpLoggingInterceptor interceptor) {
        return  new OkHttpClient().newBuilder()
                .connectTimeout(CONNECT_TIMEOUT, TimeUnit.MINUTES)
                .readTimeout(READ_TIMEOUT, TimeUnit.MINUTES)
                .writeTimeout(WRITE_TIMEOUT, TimeUnit.MINUTES)
                .cookieJar(cookieJar)
                //.addInterceptor(interceptor)
                .addNetworkInterceptor(new StethoInterceptor())
                .build();
    }

    @Provides
    @Singleton
    public ClearableCookieJar provideClearableCookieJar(Context context) {
        return new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context));
    }

    @Provides
    @Singleton
    public HttpLoggingInterceptor provideHttpLoggingInterceptor() {
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        return interceptor;
    }
} @Module(includes = {NetworkModule.class, WebSocketModule.class, StorageModule.class})
public class MainModule {
    @Provides
    @Singleton
    public MainService provideMainService(NetworkService networkService, RxObjectWebSockets rxObjectWebSockets, StorageService storageService) {
        return new MainService(networkService, rxObjectWebSockets, storageService);
    }
} @Module
public class NavigationModule {
    private Cicerone<Router> cicerone;

    public NavigationModule() {
        cicerone = Cicerone.create();
    }

    @Provides
    @Singleton
    Router provideRouter() {
        return cicerone.getRouter();
    }

    @Provides
    @Singleton
    NavigatorHolder provideNavigatorHolder() {
        return cicerone.getNavigatorHolder();
    }
} @Module(includes = {ContextModule.class})
public class PreferencesModule {
    @Provides
    @Singleton
    public SharedPreferences provideSharedPreferences(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context);
    }
} @Module(includes = {RetrofitModule.class})
public class RestApiModule {
    @Provides
    @Singleton
    public RestApi provideRestApi(Retrofit retrofit) {
        return retrofit.create(RestApi.class);
    }
} @Module(includes = {RestApiModule.class, WebSocketModule.class})
public class NetworkModule {
    @Provides
    @Singleton
    public NetworkService provideNetworkService(RestApi restApi, RxObjectWebSockets rxObjectWebSockets, Gson gson) {
        return new NetworkService(restApi, rxObjectWebSockets, gson);
    }
} @Module(includes = {
        ContextModule.class,
        HttpClientModule.class,
        PreferencesModule.class,
        GsonModule.class
})
public class RetrofitModule {

    @Provides
    @Singleton
    public Retrofit provideRetrofit(Retrofit.Builder builder, OkHttpClient client, SharedPreferences sharedPreferences) {
        boolean sslOrTls = sharedPreferences.getBoolean("sslOrTls", false);
        String dns = sharedPreferences.getString("dns", "web.erlymon.org");
        return builder
                //.baseUrl("http://192.168.100.7:8082/api/")
                .baseUrl("{protocol}://{dns}/api/".replace("{protocol}", sslOrTls ? "https" : "http").replace("{dns}", dns))
                .client(client)
                .build();
    }

    @Provides
    @Singleton
    public Retrofit.Builder provideRetrofitBuilder(Converter.Factory converterFactory) {
        return new Retrofit.Builder()
                .addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
                .addConverterFactory(converterFactory);
    }

    @Provides
    @Singleton
    public Converter.Factory provideConverterFactory(Gson gson) {
        return GsonConverterFactory.create(gson);
    }
} @Module(includes = {ContextModule.class})
public class StorageModule {
    @Provides
    @Singleton
    public StorageService provideStorageService(Context context) {
        return new StorageService(context);
    }
} @Module(includes = {HttpClientModule.class, GsonModule.class})
public class WebSocketModule {
    @Provides
    @Singleton
    public RxObjectWebSockets provideRxObjectWebSockets(RxWebSockets rxWebSockets, ObjectSerializer serializer) {
        return new RxObjectWebSockets(rxWebSockets, serializer);
    }

    @Provides
    @Singleton
    public RxWebSockets provideRxWebSockets(OkHttpClient client, SharedPreferences sharedPreferences) {
        boolean sslOrTls = sharedPreferences.getBoolean("sslOrTls", false);
        String dns = sharedPreferences.getString("dns", "web.erlymon.org");
        return new RxWebSockets(client, new Request.Builder()
                .get()
                .url("{socket}://{dns}/api/socket".replace("{socket}", sslOrTls? "wss" : "ws").replace("{dns}", dns))
                //.url("{socket}://{dns}/api/socket".replace("{socket}", mSslOrTls ? "wss" : "ws").replace("{dns}", mDns))
                .addHeader("Sec-WebSocket-Protocol", "chat")
                .build());

    }

    @Provides
    @Singleton
    public ObjectSerializer provideObjectSerializer(Gson gson) {
        return new GsonObjectSerializer(gson, Event.class);
    }
} @InjectViewState
public class CacheDevicePresenter extends BasePresenter<GetDeviceView> {

    @Inject
    MainService mMainService;

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

    public void load(long id) {
        getViewState().startGetDevice();

        // save session id
        Subscription subscription = mMainService.getDeviceByCache(id)
                .subscribe(device -> {
                    getViewState().finishGetDevice();
                    getViewState().successGetDevice(device);
                }, exception -> {
                    getViewState().finishGetDevice();
                    getViewState().failedGetDevice(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void load() {
        getViewState().startCacheDevicesAndPositions();

        Subscription subscription = mMainService.getDevicesAndPositions()
                .subscribe(data -> {
                    Timber.d("Count devices: " + data.first.size());
                    Timber.d("Count positions: " + data.second.size());
                    getViewState().finishCacheDevicesAndPositions();
                    getViewState().successCacheDevicesAndPositions(data.first, data.second);
                }, exception -> {
                    getViewState().finishCacheDevicesAndPositions();
                    getViewState().failedCacheDevicesAndPositions(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void load() {
        getViewState().startCacheDevices();

        Subscription subscription = mMainService.getDevicesCursor()
                .subscribe(cursor -> {
                    Timber.d("Count devices: " + cursor.getCount());
                    getViewState().finishCacheDevices();
                    getViewState().successCacheDevices(cursor);
                }, exception -> {
                    getViewState().finishCacheDevices();
                    getViewState().failedCacheDevices(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

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

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

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void load(long id) {
        getViewState().startGetUser();

        // save session id
        Subscription subscription = mMainService.getUserByCache(id)
                .subscribe(user -> {
                    getViewState().finishGetUser();
                    getViewState().successGetUser(user);
                }, exception -> {
                    getViewState().finishGetUser();
                    getViewState().failedGetUser(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void load() {
        getViewState().startCacheUsers();

        // save session id
        Subscription subscription = mMainService.getUsers()
                .subscribe(cursor -> {
                    getViewState().finishCacheUsers();
                    getViewState().successCacheUsers(cursor);
                }, exception -> {
                    getViewState().finishCacheUsers();
                    getViewState().failedCacheUsers(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

    public void onErrorCancel() {
        getViewState().hideError();
    }
} @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 CreateDevicePresenter extends BasePresenter<CreateDeviceView> {

    @Inject
    MainService mMainService;

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

    public void save(Device device) {
        getViewState().startCreateDevice();

        // save session id
        Subscription subscription = mMainService.createDevice(device)
                .subscribe(result -> {
                    getViewState().finishCreateDevice();
                    getViewState().successCreateDevice();
                }, exception -> {
                    getViewState().finishCreateDevice();
                    getViewState().failedCreateDevice(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void save(Permission permission) {
        getViewState().startCreatePermission();

        // save session id
        Subscription subscription = mMainService.createPermission(permission)
                .subscribe(result -> {
                    getViewState().finishCreatePermission();
                    getViewState().successCreatePermission();
                }, exception -> {
                    getViewState().finishCreatePermission();
                    getViewState().failedCreatePermission(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void save(User user) {
        getViewState().startCreateUser();

        // save session id
        Subscription subscription = mMainService.createUser(user)
                .subscribe(result -> {
                    getViewState().finishCreateUser();
                    getViewState().successCreateUser();
                }, exception -> {
                    getViewState().finishCreateUser();
                    getViewState().failedCreateUser(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void delete(long id) {
        getViewState().startDeleteDevice();

        // save session id
        Subscription subscription = mMainService.deleteDevice(id)
                .subscribe(result -> {
                    getViewState().finishDeleteDevice();
                    getViewState().successDeleteDevice();
                }, exception -> {
                    getViewState().finishDeleteDevice();
                    getViewState().failedDeleteDevice(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void delete(Permission permission) {
        getViewState().startDeletePermission();

        // save session id
        Subscription subscription = mMainService.deletePermission(permission)
                .subscribe(result -> {
                    getViewState().finishDeletePermission();
                    getViewState().successDeletePermission();
                }, exception -> {
                    getViewState().finishDeletePermission();
                    getViewState().failedDeletePermission(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void delete(long id) {
        getViewState().startDeleteUser();

        // save session id
        Subscription subscription = mMainService.deleteUser(id)
                .subscribe(result -> {
                    getViewState().finishDeleteUser();
                    getViewState().successDeleteUser();
                }, exception -> {
                    getViewState().finishDeleteUser();
                    getViewState().failedDeleteUser(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void getDevices(long userId) {
        getViewState().startGetPermissions();

        // save session id
        Subscription subscription = mMainService.getDevices(true, userId)
                .subscribe(pair -> {
                    getViewState().finishGetPermissions();
                    getViewState().successGetPermissions(pair.first, pair.second);
                }, exception -> {
                    getViewState().finishGetPermissions();
                    getViewState().failedGetPermissions(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void load(long deviceId, Date from, Date to) {
        getViewState().startGetPositions();

        // save session id
        Subscription subscription = mMainService.getPositions(deviceId, from, to)
                .subscribe(positions -> {
                    getViewState().finishGetPositions();
                    getViewState().successGetPositions(positions);
                }, exception -> {
                    getViewState().finishGetPositions();
                    getViewState().failedGetPositions(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 OpenWebSocketPresenter extends BasePresenter<OpenWebSocketView> {

    @Inject
    MainService mMainService;

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

    public void open() {
        getViewState().startOpenWebSocket();

        // save session id
        Subscription subscription = mMainService.openWebSocket()
                .subscribe(event -> {
                    getViewState().finishOpenWebSocket();
                    getViewState().successOpenWebSocket(event);
                }, exception -> {
                    getViewState().finishOpenWebSocket();
                    getViewState().failedOpenWebSocket(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void send(Command command) {
        getViewState().startSendCommand();

        // save session id
        Subscription subscription = mMainService.sendCommand(command)
                .subscribe(result -> {
                    getViewState().finishSendCommand();
                    getViewState().successSendCommand();
                }, exception -> {
                    getViewState().finishSendCommand();
                    getViewState().failedSendCommand(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void signIn(String email, String password) {
/*
        Integer emailError = null;

        getViewState().hideFormError();

        if (TextUtils.isEmpty(email)) {
            emailError = R.string.error_field_required;
        }

        if (tokenError != null) {
            getViewState().showFormError(tokenError, null);
            return;
        }
*/
        getViewState().startSignIn();

        // save session id
        Subscription subscription = mMainService.signIn(email, password)
                .subscribe(user -> {
                    getViewState().finishSignIn();
                    getViewState().successSignIn(user);
                }, exception -> {
                    getViewState().finishSignIn();
                    getViewState().failedSignIn(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();
    }
} @InjectViewState
public class SignUpPresenter extends BasePresenter<SignUpView> {

    @Inject
    MainService mMainService;

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

    public void signUp(String name, String email, String password) {
/*
        Integer emailError = null;

        getViewState().hideFormError();

        if (TextUtils.isEmpty(email)) {
            emailError = R.string.error_field_required;
        }

        if (tokenError != null) {
            getViewState().showFormError(tokenError, null);
            return;
        }
*/
        getViewState().startSignUp();

        // save session id
        Subscription subscription = mMainService.signUp(name, email, password)
                .subscribe(avoid -> {
                    getViewState().finishSignUp();
                    getViewState().successSignUp();
                }, exception -> {
                    getViewState().finishSignUp();
                    getViewState().failedSignUp(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void save(Device device) {
        getViewState().startUpdateDevice();

        // save session id
        Subscription subscription = mMainService.updateDevice(device)
                .subscribe(result -> {
                    getViewState().finishUpdateDevice();
                    getViewState().successUpdateDevice();
                }, exception -> {
                    getViewState().finishUpdateDevice();
                    getViewState().failedUpdateDevice(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void save(Server server) {
        getViewState().startUpdateServer();

        // save session id
        Subscription subscription = mMainService.updateServer(server)
                .subscribe(result -> {
                    getViewState().finishUpdateServer();
                    getViewState().successUpdateServer();
                }, exception -> {
                    getViewState().finishUpdateServer();
                    getViewState().failedUpdateServer(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

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

    @Inject
    MainService mMainService;

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

    public void save(User user) {
        getViewState().startUpdateUser();

        // save session id
        Subscription subscription = mMainService.updateUser(user)
                .subscribe(result -> {
                    getViewState().finishUpdateUser();
                    getViewState().successUpdateUser();
                }, exception -> {
                    getViewState().finishUpdateUser();
                    getViewState().failedUpdateUser(exception.getMessage());
                });

        unsubscribeOnDestroy(subscription);
    }

    public void onErrorCancel() {
        getViewState().hideError();
    }
} public class MainActivity extends MvpAppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener,
        SignInFragment.OnViewListener,
        GetServerView,
        CheckSessionView,
        SignOutView {
    @BindView(R.id.toolbar)
    Toolbar mToolbar;

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

    @BindView(R.id.container)
    FrameLayout mContainer;

    @BindView(R.id.drawer_layout)
    DrawerLayout drawer;

    @BindView(R.id.nav_view)
    NavigationView navigationView;

    TextView mLabelName;

    TextView mLabelEmail;

    @InjectPresenter
    GetServerPresenter mGetServerPresenter;

    @InjectPresenter
    CheckSessionPresenter mCheckSessionPresenter;

    @InjectPresenter
    SignOutPresenter mSignOutPresenter;

    @Inject
    NavigatorHolder navigatorHolder;

    @Inject
    Router router;

    @Inject
    SharedPreferences sharedPreferences;

    @Inject
    Bus bus;

    private AlertDialog mErrorDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        MainApp.getAppComponent().inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);

        setSupportActionBar(mToolbar);

        LinearLayout linearLayout = (LinearLayout) navigationView.getHeaderView(0);
        mLabelName = (TextView) linearLayout.getChildAt(1);
        mLabelEmail = (TextView) linearLayout.getChildAt(2);



        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);
                case Screens.DEVICE_SCREEN:
                    return DeviceFragment.getNewInstance((Bundle) data);
                case Screens.PERMISSIONS_SCREEN:
                    return PermissionsFragment.getNewInstance((Bundle) data);
                case Screens.REPORT_SCREEN:
                    return ReportFragment.getNewInstance((Bundle) data);
                case Screens.ABOUT_SCREEN:
                    return AboutFragment.getNewInstance(data);
                default:
                    throw new RuntimeException("Unknown screen key!");
            }
        }

        @Override
        protected void showSystemMessage(String message) {
            Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
        }

        @Override
        protected void exit() {
            finish();
        }
    };

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

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

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

    @Override
    public void failedCheckSession(String message) {
        mToolbar.setVisibility(View.GONE);
        router.newRootScreen(Screens.SIGN_IN_SCREEN);
    }

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

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

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

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

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

        router.newRootScreen(Screens.SIGN_IN_SCREEN);
    }

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


    @Override
    public void successGetServer(Server server) {
        getIntent().putExtra("server", server);
        BingMapTileSource.setBingKey(server.getBingKey());
        mCheckSessionPresenter.check();
    }

    @Override
    public void successSignOut() {
        sharedPreferences.edit()
                .putLong("userId", -1)
                .putBoolean("save", false)
                .putString("email", "")
                .putString("password", "")
                .apply();
        mToolbar.setVisibility(View.GONE);
        router.newRootScreen(Screens.SIGN_IN_SCREEN);
    }

    @Override
    public void successCheckSession(User user) {
        Timber.d("successCheckSession");
        successSignIn(user);
    }


    @Override
    public void successSignIn(User user) {
        getIntent().putExtra("session", user);

        mToolbar.setVisibility(View.VISIBLE);
        mLabelName.setText(user.getName());
        mLabelEmail.setText(user.getEmail());

        navigationView.getMenu().findItem(R.id.nav_users).setVisible(user.getAdmin());
        navigationView.getMenu().findItem(R.id.nav_server).setVisible(user.getAdmin());

        router.newRootScreen(Screens.MAP_SCREEN);
    }
} 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 org.erlymon.monitor.dagger.module.BusModule;
import org.erlymon.monitor.dagger.module.ContextModule;
import org.erlymon.monitor.dagger.module.GsonModule;
import org.erlymon.monitor.dagger.module.HttpClientModule;
import org.erlymon.monitor.dagger.module.MainModule;
import org.erlymon.monitor.dagger.module.NavigationModule;
import org.erlymon.monitor.dagger.module.PreferencesModule;
import org.erlymon.monitor.dagger.module.RestApiModule;
import org.erlymon.monitor.dagger.module.NetworkModule;
import org.erlymon.monitor.dagger.module.RetrofitModule;
import org.erlymon.monitor.dagger.module.StorageModule;
import org.erlymon.monitor.dagger.module.WebSocketModule;
import org.erlymon.monitor.mvp.presenter.CacheDevicePresenter;
import org.erlymon.monitor.mvp.presenter.CacheDevicesAndPositionsPresenter;
import org.erlymon.monitor.mvp.presenter.CacheDevicesPresenter;
import org.erlymon.monitor.mvp.presenter.CacheServerPresenter;
import org.erlymon.monitor.mvp.presenter.CacheUserPresenter;
import org.erlymon.monitor.mvp.presenter.CacheUsersPresenter;
import org.erlymon.monitor.mvp.presenter.CheckSessionPresenter;
import org.erlymon.monitor.mvp.presenter.CreateDevicePresenter;
import org.erlymon.monitor.mvp.presenter.CreatePermissionPresenter;
import org.erlymon.monitor.mvp.presenter.CreateUserPresenter;
import org.erlymon.monitor.mvp.presenter.DeleteDevicePresenter;
import org.erlymon.monitor.mvp.presenter.DeletePermissionPresenter;
import org.erlymon.monitor.mvp.presenter.DeleteUserPresenter;
import org.erlymon.monitor.mvp.presenter.GetPermissionsPresenter;
import org.erlymon.monitor.mvp.presenter.GetPositionsPresenter;
import org.erlymon.monitor.mvp.presenter.GetServerPresenter;
import org.erlymon.monitor.mvp.presenter.OpenWebSocketPresenter;
import org.erlymon.monitor.mvp.presenter.SendCommandPresenter;
import org.erlymon.monitor.mvp.presenter.SignInPresenter;
import org.erlymon.monitor.mvp.presenter.SignOutPresenter;
import org.erlymon.monitor.mvp.presenter.SignUpPresenter;
import org.erlymon.monitor.mvp.presenter.UpdateDevicePresenter;
import org.erlymon.monitor.mvp.presenter.UpdateServerPresenter;
import org.erlymon.monitor.mvp.presenter.UpdateUserPresenter;
import org.erlymon.monitor.ui.activity.MainActivity;
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 javax.inject.Singleton;
import dagger.Component;
/*
 * Copyright (c) 2016, Sergey Penkovsky <sergey.penkovsky@gmail.com>
 *
 * This file is part of Erlymon Monitor.
 *
 * Erlymon Monitor is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Erlymon Monitor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Erlymon Monitor.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.erlymon.monitor.dagger;




/**
 * Created by sergey on 30.03.17.
 */
@Singleton
@Component(modules = {
        BusModule.class,
        NavigationModule.class,
        ContextModule.class,
        PreferencesModule.class,
        HttpClientModule.class,
        GsonModule.class,
        RetrofitModule.class,
        RestApiModule.class,
        WebSocketModule.class,
        StorageModule.class,
        NetworkModule.class,
        MainModule.class
})
public interface AppComponent {
    void inject(MainActivity activity);

    void inject(SignInFragment fragment);
    void inject(SignUpFragment fragment);
    void inject(MapFragment fragment);
    void inject(DevicesFragment fragment);