@@ -0,0 +1,102 @@
package space.dotcat.assistant.repository.authRepository;

import android.content.SharedPreferences;
import android.support.test.runner.AndroidJUnit4;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import space.dotcat.assistant.AppDelegate;
import space.dotcat.assistant.BuildConfig;
import space.dotcat.assistant.repository.authRepository.localAuthDataSource.LocalAuthSource;

import static junit.framework.Assert.assertEquals;

@RunWith(AndroidJUnit4.class)
public class LocalAuthSourceTest {

private static final String URL = "https://10.10.10.10";

private static final String TOKEN = "TOKEN";

private static final String KEY = "KEY";

private static final String VALUE = "VALUE";

private static final String DEF_VALUE = "DEF_VALUE";

private SharedPreferences mSharedPreferences;

private LocalAuthSource mLocalAuthSource;

@Before
public void init() {
mSharedPreferences = AppDelegate.getInstance().getAppComponent().getFakeSharedPref();

mLocalAuthSource = AppDelegate.getInstance().plusDataLayerComponent().getFakeLocalAuthSource();
}

@After
public void clear() {
SharedPreferences.Editor editor = mSharedPreferences.edit();

editor.clear();

editor.commit();

mSharedPreferences = null;
mLocalAuthSource = null;
}

@Test
public void testSaveUrl() {
mLocalAuthSource.saveUrl(URL);

String url = mLocalAuthSource.getUrl();

assertEquals(URL, url);
}

@Test
public void testGetUrlWhenUrlDidNotSavedBefore() {
String url = mLocalAuthSource.getUrl();

assertEquals(BuildConfig.URL_DEFAULT_VALUE, url);
}

@Test
public void testSaveToken() {
mLocalAuthSource.saveToken(TOKEN);

assertEquals(mLocalAuthSource.getToken(), TOKEN);
}

@Test
public void testGetTokenWhenTokenDidNotSavedBefore() {
String token = mLocalAuthSource.getToken();

assertEquals(BuildConfig.TOKEN_DEFAULT_VALUE, token);
}

@Test
public void testGetSummaryByKeyWhenExist() {
SharedPreferences.Editor editor = mSharedPreferences.edit();

editor.putString(KEY, VALUE);

editor.commit();

String summary = mLocalAuthSource.getSummaryByKey(KEY, VALUE);

assertEquals(VALUE, summary);
}

@Test
public void testGetSummaryByKeyWithDefault() {
String summary = mLocalAuthSource.getSummaryByKey(KEY, DEF_VALUE);

assertEquals(DEF_VALUE, summary);
}
}
@@ -0,0 +1,74 @@
package space.dotcat.assistant.repository.authRepository;

import android.support.test.runner.AndroidJUnit4;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import space.dotcat.assistant.AppDelegate;
import space.dotcat.assistant.api.RequestMatcher;
import space.dotcat.assistant.content.ApiError;
import space.dotcat.assistant.content.Authorization;
import space.dotcat.assistant.content.AuthorizationAnswer;
import space.dotcat.assistant.repository.authRepository.localAuthDataSource.LocalAuthSource;
import space.dotcat.assistant.repository.authRepository.remoteDataSource.RemoteAuthSource;


import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;

@RunWith(AndroidJUnit4.class)
public class RemoteAuthSourceTest {

private RemoteAuthSource mRemoteAuthSource;

private LocalAuthSource mLocalAuthSource;

private final static String TOKEN = "90ff4ba085545c1735ab6c29a916f9cb8c0b7222";

private final static Authorization AUTHORIZATION_INFO = new Authorization("login", "pass");

@Before
public void init() {
mRemoteAuthSource = AppDelegate.getInstance().plusDataLayerComponent().getFakeRemoteAuthSource();

mLocalAuthSource = AppDelegate.getInstance().plusDataLayerComponent().getFakeLocalAuthSource();
}

@After
public void clear() {
mLocalAuthSource.deleteToken();
}

@Test
public void testRemoteAuthSourceCreated() {
assertNotNull(mRemoteAuthSource);
}

@Test
public void testSuccessAuth() {
AuthorizationAnswer authorizationAnswer = mRemoteAuthSource.authUser(AUTHORIZATION_INFO)
.blockingGet();

assertEquals(TOKEN, authorizationAnswer.getToken());
}

@Test
public void testErrorAuth() {
mLocalAuthSource.saveToken(RequestMatcher.ERROR);

mRemoteAuthSource.authUser(AUTHORIZATION_INFO)
.test()
.assertError(ApiError.class);
}

@Test
public void testDestroyService() {
mRemoteAuthSource.destroyApiService();

assertTrue(mRemoteAuthSource.isApiDestroyed());
}
}
@@ -0,0 +1,104 @@
package space.dotcat.assistant.repository.roomsRepository;

import android.arch.core.executor.testing.InstantTaskExecutorRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import io.reactivex.Flowable;
import space.dotcat.assistant.AppDelegate;
import space.dotcat.assistant.repository.roomsRepository.localRoomsDataSource.LocalRoomsSource;

@RunWith(AndroidJUnit4.class)
public class LocalRoomsSourceTest {

@Rule
public InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule();

private LocalRoomsSource mLocalRoomsSource;

private final List<space.dotcat.assistant.content.Room> ROOMS = createRoomsList();

@Before
public void init() {
mLocalRoomsSource = AppDelegate.getInstance().plusDataLayerComponent().getFakeLocalRoomsSource();
}

@After
public void clear() {
mLocalRoomsSource.deleteRoomsSync();

mLocalRoomsSource = null;
}

@Test
public void testGetRoomsWhenThereIsNoRoomsInDb() {
mLocalRoomsSource.getRooms()
.flatMap(Flowable::fromIterable)
.test()
.assertNoValues();
}

@Test
public void testSaveAndGetRooms() {
mLocalRoomsSource.addRoomsSync(ROOMS);

mLocalRoomsSource.getRooms()
.test()
.assertNoErrors()
.assertValue(list-> list.get(0).getId().equals("id1"));
}

@Test
public void testReplaceRoomsDuringInsert() {
mLocalRoomsSource.addRoomsSync(ROOMS);

space.dotcat.assistant.content.Room room = new space.dotcat.assistant.content.Room("corridor",
"id1", "path");

mLocalRoomsSource.addRoomsSync(Collections.singletonList(room));

mLocalRoomsSource.getRooms()
.flatMap(Flowable::fromIterable)
.filter(room1-> room1.getId().equals("id1"))
.test()
.assertValue(roomInstance-> roomInstance.getFriendlyName().equals("corridor"));
}

@Test
public void testDeleteAllRooms() {
mLocalRoomsSource.addRoomsSync(ROOMS);

mLocalRoomsSource.getRooms()
.flatMap(Flowable::fromIterable)
.test()
.assertValueCount(2);

mLocalRoomsSource.deleteRoomsSync();

mLocalRoomsSource.getRooms()
.flatMap(Flowable::fromIterable)
.test()
.assertValueCount(0);
}

private List<space.dotcat.assistant.content.Room> createRoomsList() {
space.dotcat.assistant.content.Room room1 = new space.dotcat.assistant.content.Room();

room1.setId("id1");

space.dotcat.assistant.content.Room room2 = new space.dotcat.assistant.content.Room();

room2.setId("id2");

return Arrays.asList(room1, room2);
}
}
@@ -0,0 +1,80 @@
package space.dotcat.assistant.repository.roomsRepository;

import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import io.reactivex.Flowable;
import space.dotcat.assistant.AppDelegate;
import space.dotcat.assistant.api.RequestMatcher;
import space.dotcat.assistant.content.ApiError;
import space.dotcat.assistant.di.appComponent.DaggerAppComponent;
import space.dotcat.assistant.di.appComponent.SharedPreferencesModule;
import space.dotcat.assistant.repository.authRepository.localAuthDataSource.LocalAuthSource;
import space.dotcat.assistant.repository.roomsRepository.remoteRoomsDataSource.RemoteRoomsSource;
import space.dotcat.assistant.utils.RxJavaTestRule;

@RunWith(AndroidJUnit4.class)
public class RemoteRoomsSourceTest {

@Rule
public RxJavaTestRule mRxJavaTestRule = new RxJavaTestRule();

private LocalAuthSource mLocalAuthSource;

private RemoteRoomsSource mRemoteRoomsSource;

@Before
public void init() {
mLocalAuthSource = AppDelegate.getInstance().plusDataLayerComponent().getFakeLocalAuthSource();

mRemoteRoomsSource = AppDelegate.getInstance().plusDataLayerComponent().getFakeRemoteRoomsSource();
}

@After
public void clear() {
mLocalAuthSource.deleteToken();

mLocalAuthSource = null;

mRemoteRoomsSource = null;
}

@Test
public void getRoomsSuccessfully() {
mRemoteRoomsSource.getRooms()
.flatMap(Flowable::fromIterable)
.test()
.assertValueCount(6)
.assertNoErrors()
.assertValueAt(0, room -> room.getFriendlyName().equals("Corridor"))
.assertValueAt(1, room -> room.getFriendlyName().equals("Kitchen"))
.assertValueAt(2, room -> room.getFriendlyName().equals("Bathroom"));
}

@Test
public void getEmptyRooms() {
mLocalAuthSource.saveToken(RequestMatcher.ERROR_EMPTY_ROOMS);

mRemoteRoomsSource.getRooms()
.flatMap(Flowable::fromIterable)
.test()
.assertNoErrors()
.assertValueCount(0);
}

@Test
public void getRoomsWithError() {
mLocalAuthSource.saveToken(RequestMatcher.ERROR);

mRemoteRoomsSource.getRooms()
.test()
.assertError(ApiError.class);
}

}
@@ -0,0 +1,142 @@
package space.dotcat.assistant.repository.thingsRepository;

import android.arch.core.executor.testing.InstantTaskExecutorRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Arrays;
import java.util.List;

import io.reactivex.Flowable;
import space.dotcat.assistant.AppDelegate;
import space.dotcat.assistant.content.Thing;
import space.dotcat.assistant.repository.thingsRepository.localThingsDataSource.LocalThingsSource;

@RunWith(AndroidJUnit4.class)
public class LocalThingsSourceTest {

@Rule
public InstantTaskExecutorRule mTaskExecutorRule = new InstantTaskExecutorRule();

private LocalThingsSource mLocalThingsSource;

public final String ROOM_ID = "R1";

private final List<Thing> THINGS = createThings();

@Before
public void init() {
mLocalThingsSource = AppDelegate.getInstance().plusDataLayerComponent().getFakeLocalThingsSource();
}

@After
public void clear() {
mLocalThingsSource.deleteAllThings();

mLocalThingsSource = null;
}

@Test
public void testGetThingsWhenThereIsNoThingsLocaly() {
mLocalThingsSource.getThingsById(ROOM_ID)
.flatMap(Flowable::fromIterable)
.test()
.assertValueCount(0);
}

@Test
public void testGetThings() {
mLocalThingsSource.addThingsSync(THINGS);

mLocalThingsSource.getThingsById(ROOM_ID)
.flatMap(Flowable::fromIterable)
.test()
.assertValueCount(3)
.assertValueAt(0, thing -> !thing.getIsActive())
.assertValueAt(1, thing -> !thing.getIsActive())
.assertValueAt(2, Thing::getIsActive) ;
}

@Test
public void testDeleteThings() {
mLocalThingsSource.addThingsSync(THINGS);

mLocalThingsSource.getThingsById(ROOM_ID)
.flatMap(Flowable::fromIterable)
.test()
.assertValueAt(0, thing -> !thing.getIsActive());

mLocalThingsSource.deleteAllThings();

mLocalThingsSource.getThingsById(ROOM_ID)
.flatMap(Flowable::fromIterable)
.test()
.assertValueCount(0);
}

@Test
public void testUpdateThings() {
mLocalThingsSource.addThingsSync(THINGS);

Thing updatedThing = THINGS.get(0);
updatedThing.setActive(true);

mLocalThingsSource.updateThing(updatedThing);
}

@Test
public void testInsertThingsWithReplacing() {
mLocalThingsSource.addThingsSync(THINGS);

Thing thing = new Thing();
thing.setId("id1");
thing.setPlacement(ROOM_ID);
thing.setActive(true);

Thing thing1 = new Thing();
thing1.setId("id2");
thing1.setPlacement(ROOM_ID);
thing1.setActive(false);

mLocalThingsSource.addThingsSync(Arrays.asList(thing, thing1));

mLocalThingsSource.getThingsById(ROOM_ID)
.flatMap(Flowable::fromIterable)
.test()
.assertValueAt(1, Thing::getIsActive)
.assertValueAt(2, t-> !t.getIsActive());
}


private List<Thing> createThings() {
Thing door = new Thing();

door.setPlacement(ROOM_ID);
door.setId("id");
door.setActive(false);

Thing light = new Thing();

light.setPlacement(ROOM_ID);
light.setId("id1");
light.setActive(false);

Thing player = new Thing();

player.setPlacement(ROOM_ID);
player.setId("id2");
player.setActive(true);

Thing anotherThing = new Thing();

anotherThing.setPlacement("PLACEMENT");
anotherThing.setId("id4");

return Arrays.asList(door, light, player, anotherThing);
}
}
@@ -0,0 +1,96 @@
package space.dotcat.assistant.repository.thingsRepository;

import android.support.test.runner.AndroidJUnit4;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import io.reactivex.Flowable;
import space.dotcat.assistant.AppDelegate;
import space.dotcat.assistant.api.RequestMatcher;
import space.dotcat.assistant.content.ApiError;
import space.dotcat.assistant.content.CommandArgs;
import space.dotcat.assistant.content.Message;
import space.dotcat.assistant.repository.authRepository.localAuthDataSource.LocalAuthSource;
import space.dotcat.assistant.repository.thingsRepository.remoteThingsDataSource.RemoteThingsSource;
import space.dotcat.assistant.utils.RxJavaTestRule;

@RunWith(AndroidJUnit4.class)
public class RemoteThingsSourceTest {

@Rule
public RxJavaTestRule mRxJavaTestRule = new RxJavaTestRule();

private LocalAuthSource mLocalAuthSource;

private RemoteThingsSource mRemoteThingsSource;

private final String ROOM_ID = "R1";

private final String THING_ID = "D1";

private final Message MESSAGE = new Message("toggle", new CommandArgs());

@Before
public void init() {
mLocalAuthSource = AppDelegate.getInstance().plusDataLayerComponent().getFakeLocalAuthSource();

mRemoteThingsSource = AppDelegate.getInstance().plusDataLayerComponent().getFakeRemoteThingsSource();
}

@After
public void clear() {
mLocalAuthSource.deleteToken();

mRemoteThingsSource = null;

mLocalAuthSource = null;
}

@Test
public void testLoadThingsByPlacementId() {
mRemoteThingsSource.loadThingsByPlacementId(ROOM_ID)
.flatMap(Flowable::fromIterable)
.filter(thing -> thing.getPlacement().equals(ROOM_ID))
.test()
.assertValueCount(2);
}

@Test
public void testLoadEmptyThings() {
mLocalAuthSource.saveToken(RequestMatcher.ERROR_EMPTY_THINGS);

mRemoteThingsSource.loadThingsByPlacementId(ROOM_ID)
.flatMap(Flowable::fromIterable)
.test()
.assertValueCount(0);
}

@Test
public void testLoadThingsWithError() {
mLocalAuthSource.saveToken(RequestMatcher.ERROR);

mRemoteThingsSource.loadThingsByPlacementId(ROOM_ID)
.test()
.assertError(ApiError.class);
}

@Test
public void testDoActionSuccessfully() {
mRemoteThingsSource.doActionOnThing(THING_ID, MESSAGE)
.test()
.assertValue(m-> m.getMessage().equals("accepted"));
}

@Test
public void testDoActionWithError() {
mLocalAuthSource.saveToken(RequestMatcher.ERROR);

mRemoteThingsSource.doActionOnThing(THING_ID, MESSAGE)
.test()
.assertError(ApiError.class);
}
}
@@ -12,7 +12,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;

import io.realm.Realm;
import space.dotcat.assistant.R;
import space.dotcat.assistant.content.AuthorizationAnswer;
import space.dotcat.assistant.content.Url;
@@ -53,9 +52,9 @@ public class AuthActivityTest {
public void init() {
Intents.init();

Realm.getDefaultInstance().executeTransaction(transaction -> {
/* Realm.getDefaultInstance().executeTransaction(transaction -> {
transaction.delete(Url.class);
});
});*/
}

@After
@@ -15,7 +15,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import io.realm.Realm;

import space.dotcat.assistant.content.Url;
import space.dotcat.assistant.repository.RepositoryProvider;
import space.dotcat.assistant.R;
@@ -42,9 +42,9 @@ public void init() {

@After
public void clear() {
Realm.getDefaultInstance().executeTransaction(transaction -> {
/* Realm.getDefaultInstance().executeTransaction(transaction -> {
transaction.delete(Url.class);
});
});*/
}

@Test
@@ -55,7 +55,7 @@ public void testNoThingsInRoom() throws Exception {
onView(withId(R.id.recyclerViewDetails))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.INVISIBLE)));

onView(withId(R.id.tv_error_message)).check(matches(withText(R.string.error_empty_actions)));
onView(withId(R.id.tv_error_message)).check(matches(withText(R.string.error_empty_things)));
}

private void launchActivity() {
@@ -13,7 +13,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;

import io.realm.Realm;
import space.dotcat.assistant.R;
import space.dotcat.assistant.content.AuthorizationAnswer;
import space.dotcat.assistant.content.Room;
@@ -41,7 +40,7 @@ public class EmptyRoomsActivityTest {
@Before
public void init() {
RepositoryProvider.provideAuthRepository().saveUrl(new Url("https://api.ks-cube.tk/"));
Realm.getDefaultInstance().executeTransaction(transaction -> transaction.delete(Room.class));
/* Realm.getDefaultInstance().executeTransaction(transaction -> transaction.delete(Room.class));*/
}

@After
@@ -4,17 +4,13 @@
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

import io.reactivex.Scheduler;
import io.reactivex.android.plugins.RxAndroidPlugins;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.schedulers.TestScheduler;


public class RxJavaTestRule implements TestRule {

private final TestScheduler mTestScheduler = new TestScheduler();

@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@@ -1,98 +1,78 @@
package space.dotcat.assistant.api;

import android.content.SharedPreferences;
import android.support.annotation.NonNull;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.lang.reflect.Type;
import javax.inject.Inject;

import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.internal.IOException;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import space.dotcat.assistant.content.RealmString;
import space.dotcat.assistant.repository.RepositoryProvider;
import space.dotcat.assistant.BuildConfig;

public final class ApiFactory {

private static volatile ApiService sService;
private volatile ApiService mService;

private static Type token = new TypeToken<RealmList<RealmString>>(){}.getType();
private SharedPreferences mSharedPreferences;

private static Gson gson = new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class);
}
private OkHttpFactory mOkHttpFactory;

@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.registerTypeAdapter(token, new TypeAdapter<RealmList<RealmString>>() {
private ErrorParser mErrorParser;

@Override
public void write(JsonWriter out, RealmList<RealmString> value) throws IOException {
// Ignore
}
public ApiFactory(SharedPreferences sharedPreferences, OkHttpFactory okHttpFactory,
ErrorParser errorParser) {
mSharedPreferences = sharedPreferences;

@Override
public RealmList<RealmString> read(JsonReader in) throws IOException, java.io.IOException {
RealmList<RealmString> list = new RealmList<>();
in.beginArray();
while (in.hasNext()) {
list.add(new RealmString(in.nextString()));
}
in.endArray();
return list;
}
})
.create();
mOkHttpFactory = okHttpFactory;

private ApiFactory() {
mErrorParser = errorParser;
}

@NonNull
public static ApiService getApiService() {
ApiService service = sService;
public ApiService getApiService() {
ApiService service = mService;

if(service == null) {
synchronized (ApiFactory.class) {
service = sService;
service = mService;

if(service == null) {
service = sService = buildRetrofit().create(ApiService.class);
service = mService = buildRetrofit().create(ApiService.class);
}
}
}

return service;
}

@NonNull
static Retrofit buildRetrofit() {
private Retrofit buildRetrofit() {
String base_url = mSharedPreferences.getString(BuildConfig.URL_KEY,
BuildConfig.URL_DEFAULT_VALUE);

OkHttpClient okHttpClient = mOkHttpFactory.provideClient();

return new Retrofit.Builder()
.baseUrl(RepositoryProvider.provideAuthRepository().url())
.client(OkHttpProvider.provideClient())
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(new RxJavaAdapterWithErrorHandling())
.baseUrl(base_url)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(new RxJavaAdapterWithErrorHandling(mErrorParser))
.build();
}

public static void recreate() {
OkHttpProvider.recreate();
sService = buildRetrofit().create(ApiService.class);
public void recreate() {
mOkHttpFactory.recreate();
mService = buildRetrofit().create(ApiService.class);
}

public boolean isServiceDeleted() {
return mService == null;
}

public static void deleteInstance() {
OkHttpProvider.deleteClient();
sService = null;
public void deleteInstance() {
mOkHttpFactory.deleteClient();
mService = null;
}
}
@@ -16,15 +16,44 @@

public interface ApiService {

/**
* Try to auth and get valid token for further operations.
*
* @param authorizationInfo - info that you need to try authorize. E.g login, password
* @return single with authorization answer, which contains answer from server
*/

@POST("auth")
Single<AuthorizationAnswer> auth(@Body Authorization authorizationInfo);

/**
* Load rooms from the server.
*
* @return single with room response object, which contains list of rooms inside
*/

@GET("placements/")
Single<RoomResponse> rooms();

/**
* Load things inside the placement.
*
* @param id - placement id, where you want to load things
* @return single with thing response object, which is simple wrapper of thing list
*/

@GET("things/")
Single<ThingResponse> things(@Query("placement") String id);

/**
* Do action with thing. Basically, you can do toggle with every thing, but pay attention that
* you have diversity of operations for things.
*
* @param id - thing id, that you want to execute
* @param message - info where you specify parameters how to execute thing
* @return - single with response action message, which contains info how execution was done
*/

@POST("things/{id}/execute")
Single<ResponseActionMessage> action(@Path("id") String id, @Body Message message);
}
@@ -1,29 +1,29 @@
package space.dotcat.assistant.api;

import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.text.TextUtils;

import java.io.IOException;

import javax.inject.Inject;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import space.dotcat.assistant.repository.RepositoryProvider;
import space.dotcat.assistant.BuildConfig;

class AuthenticationInterceptor implements Interceptor {
public class AuthenticationInterceptor implements Interceptor {

private final String mToken;

private AuthenticationInterceptor() {
mToken = RepositoryProvider.provideAuthRepository().token();
}

static Interceptor create() {
return new AuthenticationInterceptor();
public AuthenticationInterceptor(SharedPreferences sharedPreferences) {
mToken = sharedPreferences.getString(BuildConfig.TOKEN_KEY, "");
}

@Override
public Response intercept(Chain chain) throws IOException {
if(TextUtils.isEmpty(mToken)){
if(TextUtils.isEmpty(mToken)) {
return chain.proceed(chain.request());
}

@@ -0,0 +1,30 @@
package space.dotcat.assistant.api;


import java.io.IOException;
import java.lang.annotation.Annotation;


import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Response;
import retrofit2.Retrofit;
import space.dotcat.assistant.content.ApiError;

public class BasicErrorParser extends ErrorParser {

public ApiError parseError(Retrofit retrofit, Response response) {
Converter<ResponseBody, ApiError> converter = retrofit
.responseBodyConverter(ApiError.class, new Annotation[0]);

ApiError error;

try {
error = converter.convert(response.errorBody());
} catch (IOException e) {
return null;
}

return error;
}
}
@@ -1,32 +1,25 @@
package space.dotcat.assistant.api;


import java.io.IOException;
import java.lang.annotation.Annotation;


import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Response;
import retrofit2.Retrofit;
import space.dotcat.assistant.content.ApiError;

class ErrorParser {

private ErrorParser(){
}

static ApiError parseError(Response response){
Converter<ResponseBody, ApiError> converter = ApiFactory.buildRetrofit()
.responseBodyConverter(ApiError.class, new Annotation[0]);
/**
* Base class for error parser which will be provided to Handling Error RxJava2 Factory
*/

ApiError error;
public abstract class ErrorParser {

try {
error = converter.convert(response.errorBody());
} catch (IOException e) {
return null;
}
/**
* Try to parse error from Retrofit response into ApiError. If response can not be parsed, it will
* return null
*
* @param retrofit retrofit instance that you used for request
* @param response response that contains possible error body
* @return parsed error from response error body
*/

return error;
}
public abstract ApiError parseError(Retrofit retrofit, Response response);
}
@@ -6,6 +6,8 @@
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import javax.inject.Inject;

import io.reactivex.Single;
import retrofit2.Call;
import retrofit2.CallAdapter;
@@ -14,12 +16,16 @@
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import space.dotcat.assistant.content.ApiError;

class RxJavaAdapterWithErrorHandling extends CallAdapter.Factory {
public class RxJavaAdapterWithErrorHandling extends CallAdapter.Factory {

private final RxJava2CallAdapterFactory mFactory;

RxJavaAdapterWithErrorHandling() {
private final ErrorParser mErrorParser;

public RxJavaAdapterWithErrorHandling(ErrorParser errorParser) {
mFactory = RxJava2CallAdapterFactory.create();

mErrorParser = errorParser;
}

@Override
@@ -54,7 +60,7 @@ public Single<R> adapt(Call<R> call) {
if(throwable instanceof HttpException){
HttpException exception = (HttpException) t;

apiError = ErrorParser.parseError(exception.response());
apiError = mErrorParser.parseError(mRetrofit, exception.response());

if(apiError == null) {
apiError = new ApiError();
@@ -3,9 +3,8 @@

import com.google.gson.annotations.SerializedName;

import io.realm.RealmObject;

public class AuthorizationAnswer extends RealmObject {
public class AuthorizationAnswer {

@SerializedName("message")
private String mMessage;

This file was deleted.

@@ -1,26 +1,34 @@
package space.dotcat.assistant.content;

import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.Ignore;
import android.arch.persistence.room.PrimaryKey;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;

import com.google.gson.annotations.SerializedName;

import java.io.Serializable;

import io.realm.RealmObject;

public class Room extends RealmObject implements Parcelable {
@Entity(tableName = "Rooms")
public class Room implements Parcelable {

@SerializedName("friendly_name")
@ColumnInfo(name = "room_friendly_name")
private String mFriendlyName;

@PrimaryKey
@SerializedName("id")
@ColumnInfo(name = "room_id")
@NonNull
private String mId;

@SerializedName("image_url")
@ColumnInfo(name = "room_image_path")
private String mImagePath;

@Ignore
public Room() {
}

@@ -32,12 +40,14 @@ public Room(@NonNull String friendlyName, @NonNull String id, @NonNull String im
mImagePath = imagePath;
}

@Ignore
protected Room(Parcel in) {
mFriendlyName = in.readString();
mId = in.readString();
mImagePath = in.readString();
}

@Ignore
public static final Creator<Room> CREATOR = new Creator<Room>() {
@Override
public Room createFromParcel(Parcel in) {
@@ -51,7 +61,7 @@ public Room[] newArray(int size) {
};

@NonNull
public String GetId() {
public String getId() {
return mId;
}

@@ -14,14 +14,13 @@ public class RoomResponse {
private List<Room> mRooms = new ArrayList<>();

public RoomResponse(@NonNull List<Room> rooms) {

mRooms = rooms;
}

@NonNull
public List<Room> getRooms() { return mRooms; }

public void setRooms(List<Room> mRooms) {
mRooms = mRooms;
public void setRooms(List<Room> rooms) {
mRooms = rooms;
}
}
@@ -0,0 +1,34 @@
package space.dotcat.assistant.content;

import android.arch.persistence.room.TypeConverter;
import android.support.annotation.NonNull;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collector;

public class StringsConverter {

@TypeConverter
public String fromListToString(List<String> stringList) {
StringBuilder stringBuilder = new StringBuilder();

if(stringList == null)
return "";

for(String s : stringList) {
stringBuilder.append(s);
stringBuilder.append(" ");
}

return stringBuilder.toString();
}

@TypeConverter
public List<String> fromStringToList(@NonNull String string) {
String [] array = string.split(" ");

return Arrays.asList(array);
}

}
@@ -1,72 +1,93 @@
package space.dotcat.assistant.content;


import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.Ignore;
import android.arch.persistence.room.PrimaryKey;

import android.arch.persistence.room.TypeConverters;
import android.support.annotation.NonNull;

import com.google.gson.annotations.SerializedName;

import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;

public class Thing extends RealmObject{
import java.util.List;

@SerializedName("commands")
private RealmList<RealmString> mActions;
@Entity(tableName = "Things")
@TypeConverters(StringsConverter.class)
public class Thing {

@PrimaryKey
@SerializedName("id")
@ColumnInfo(name = "thing_id")
@NonNull
private String mId;

@SerializedName("capabilities")
@ColumnInfo(name = "thing_capabilities")
private List<String> mCapabilities;

@SerializedName("commands")
@ColumnInfo(name = "thing_commands")
private List<String> mCommands;

@SerializedName("is_active")
@ColumnInfo(name = "thing_is_active")
private Boolean mIsActive;

@SerializedName("is_available")
@ColumnInfo(name = "thing_is_available")
private Boolean mIsAvailable;

@SerializedName("is_enabled")
@ColumnInfo(name = "thing_is_enabled")
private Boolean mIsEnabled;

@SerializedName("placement")
@ColumnInfo(name = "thing_placement")
private String mPlacement;

@SerializedName("state")
@ColumnInfo(name = "thing_state")
private String mState;

@SerializedName("type")
@ColumnInfo(name = "thing_type")
private String mType;

@SerializedName("friendly_name")
@ColumnInfo(name = "thing_friendly_name")
private String mFriendlyName;

@Ignore
public Thing() {
}

public Thing(@NonNull RealmList<RealmString> actions, @NonNull String id,
@NonNull Boolean isActive, @NonNull Boolean isAvailable, @NonNull String placement,
@NonNull String state, @NonNull String type, @NonNull Boolean isEnabled,
public Thing(@NonNull String id, @NonNull List<String> capabilities, @NonNull List<String> commands,
@NonNull Boolean isActive, @NonNull Boolean isAvailable, @NonNull Boolean isEnabled,
@NonNull String placement, @NonNull String state, @NonNull String type,
@NonNull String friendlyName) {
mActions = actions;

mId = id;

mCapabilities = capabilities;
mCommands = commands;
mIsActive = isActive;

mIsAvailable = isAvailable;

mIsEnabled = isEnabled;
mPlacement = placement;

mState = state;

mType = type;

mIsEnabled = isEnabled;

mFriendlyName = friendlyName;
}

@NonNull
public RealmList<RealmString> getActions() { return mActions; }
public List<String> getCapabilities() {
return mCapabilities;
}

@NonNull
public List<String> getCommands() {
return mCommands;
}

@NonNull
public String getId() { return mId; }
@@ -87,7 +108,7 @@ public Thing(@NonNull RealmList<RealmString> actions, @NonNull String id,
public String getType() { return mType; }

@NonNull
public Boolean getEnabled() {
public Boolean getIsEnabled() {
return mIsEnabled;
}

@@ -96,10 +117,6 @@ public String getFriendlyName() {
return mFriendlyName;
}

public void setActions(RealmList<RealmString> actions) {
mActions = actions;
}

public void setId(String id) {
mId = id;
}
@@ -108,6 +125,14 @@ public void setActive(Boolean active) {
mIsActive = active;
}

public void setCommands(List<String> commands) {
mCommands = commands;
}

public void setCapabilities(List<String> capabilities) {
mCapabilities = capabilities;
}

public void setAvailable(Boolean available) {
mIsAvailable = available;
}
@@ -10,7 +10,7 @@
public class ThingResponse {

@SerializedName("things")
private List<Thing> mThings = new ArrayList<>();
private List<Thing> mThings;

private ThingResponse(@NonNull List<Thing> things) {
mThings = things;
@@ -3,9 +3,7 @@

import android.support.annotation.NonNull;

import io.realm.RealmObject;

public class Url extends RealmObject {
public class Url {

private String mUrl;

@@ -0,0 +1,18 @@
package space.dotcat.assistant.repository;


import android.arch.persistence.room.Database;
import android.arch.persistence.room.RoomDatabase;

import space.dotcat.assistant.content.Room;
import space.dotcat.assistant.content.Thing;
import space.dotcat.assistant.repository.roomsRepository.localRoomsDataSource.RoomsDao;
import space.dotcat.assistant.repository.thingsRepository.localThingsDataSource.ThingsDao;

@Database(entities = {Room.class, Thing.class}, version = 2, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {

public abstract RoomsDao roomsDao();

public abstract ThingsDao thingsDao();
}
@@ -6,15 +6,7 @@
import java.util.List;

import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.ObservableTransformer;
import io.reactivex.Single;
import io.reactivex.SingleSource;
import io.reactivex.SingleTransformer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import io.realm.Realm;
import io.realm.RealmResults;
import space.dotcat.assistant.api.ApiFactory;
import space.dotcat.assistant.content.Authorization;
import space.dotcat.assistant.content.AuthorizationAnswer;
@@ -32,7 +24,7 @@ public class DefaultApiRepository implements ApiRepository {
@NonNull
@Override
public Single<AuthorizationAnswer> auth(@NonNull Authorization authorizationInfo) {
return ApiFactory.getApiService()
/*return ApiFactory.getApiService()
.auth(authorizationInfo)
.flatMap(authorizationAnswer -> {
RepositoryProvider.provideAuthRepository()
@@ -43,13 +35,14 @@ public Single<AuthorizationAnswer> auth(@NonNull Authorization authorizationInfo
return Single.just(authorizationAnswer);
})
.doOnError(throwable -> ApiFactory.deleteInstance())
.compose(RxUtils.makeSingleAsyncWithUiCallback());
.compose(RxUtils.makeSingleAsyncWithUiCallback());*/
return null;
}

@NonNull
@Override
public Observable<List<Room>> rooms() {
return ApiFactory.getApiService()
/*return ApiFactory.getApiService()
.rooms()
.toObservable()
.map(RoomResponse::getRooms)
@@ -76,13 +69,15 @@ public Observable<List<Room>> rooms() {
return Observable.mergeDelayError(Observable.just(resultRooms),
Observable.error(throwable));
})
.compose(RxUtils.makeObservableAsyncWithUiCallback());
.compose(RxUtils.makeObservableAsyncWithUiCallback());*/

return null;
}

@NonNull
@Override
public Observable<List<Thing>> things(String id) {
return ApiFactory.getApiService()
/*return ApiFactory.getApiService()
.things(id)
.toObservable()
.map(ThingResponse::getThings)
@@ -105,14 +100,18 @@ public Observable<List<Thing>> things(String id) {
return Observable.mergeDelayError(Observable.just(resultThings),
Observable.error(throwable));
})
.compose(RxUtils.makeObservableAsyncWithUiCallback());
.compose(RxUtils.makeObservableAsyncWithUiCallback());*/

return null;
}

@NonNull
@Override
public Single<ResponseActionMessage> action(@NonNull String id, @NonNull Message message) {
return ApiFactory.getApiService()
/*return ApiFactory.getApiService()
.action(id, message)
.compose(RxUtils.makeSingleAsyncWithUiCallback());
.compose(RxUtils.makeSingleAsyncWithUiCallback());*/

return null;
}
}
@@ -2,7 +2,7 @@

import android.support.annotation.NonNull;

import io.realm.Realm;

import space.dotcat.assistant.content.AuthorizationAnswer;
import space.dotcat.assistant.content.Url;

@@ -11,55 +11,59 @@ public class DefaultAuthRepository implements AuthRepository {

@Override
public void saveAuthorizationAnswer(@NonNull AuthorizationAnswer answer) {
Realm realmInstance = Realm.getDefaultInstance();
/* Realm realmInstance = Realm.getDefaultInstance();
realmInstance.executeTransaction(transaction -> {
transaction.delete(AuthorizationAnswer.class);
transaction.insert(answer);
});
});*/
}

@Override
public String token() {
Realm realmInstance = Realm.getDefaultInstance();
/*Realm realmInstance = Realm.getDefaultInstance();
AuthorizationAnswer authorizationAnswer = realmInstance.where(AuthorizationAnswer.class)
.findFirst();
if(authorizationAnswer == null)
return "";
return authorizationAnswer.getToken();
return authorizationAnswer.getToken();*/

return null;
}

@Override
public void deleteToken() {
Realm realmInstance = Realm.getDefaultInstance();
/* Realm realmInstance = Realm.getDefaultInstance();
realmInstance.executeTransaction(transaction -> {
transaction.delete(AuthorizationAnswer.class);
});
});*/
}

@Override
public void saveUrl(@NonNull Url url) {
Realm realmInstance = Realm.getDefaultInstance();
/* Realm realmInstance = Realm.getDefaultInstance();
realmInstance.executeTransaction(realm -> {
realm.delete(Url.class);
realm.insert(url);
});
});*/
}

@NonNull
@Override
public String url() {
Realm realmInstance = Realm.getDefaultInstance();
/* Realm realmInstance = Realm.getDefaultInstance();
Url url = realmInstance.where(Url.class).findFirst();
if(url == null) return "";
return url.getUrl();
return url.getUrl();*/

return null;
}
}
@@ -2,38 +2,51 @@

import android.support.annotation.NonNull;

import space.dotcat.assistant.repository.roomsRepository.RoomRepository;
import space.dotcat.assistant.repository.roomsRepository.RoomRepositoryImpl;

public final class RepositoryProvider {

private static ApiRepository sApiRepository;

private static AuthRepository sAuthRepository;

private static RoomRepository sRoomRepository;

private RepositoryProvider() {
}

public static void setApiRepository(ApiRepository apiRepository){
public static void setApiRepository(ApiRepository apiRepository) {
sApiRepository = apiRepository;
}

public static void setAuthRepository(AuthRepository authRepository){
public static void setAuthRepository(AuthRepository authRepository) {
sAuthRepository = authRepository;
}

@NonNull
public static ApiRepository provideApiRepository() {
if(sApiRepository == null){
if(sApiRepository == null) {
sApiRepository = new DefaultApiRepository();
}

return sApiRepository;
}

@NonNull
public static AuthRepository provideAuthRepository(){
if(sAuthRepository == null){
public static AuthRepository provideAuthRepository() {
if(sAuthRepository == null) {
sAuthRepository = new DefaultAuthRepository();
}

return sAuthRepository;
}

/* public static RoomRepository provideRoomRepository() {
if(sRoomRepository == null) {
sRoomRepository = new RoomRepositoryImpl();
}
return sRoomRepository;
}*/
}
@@ -0,0 +1,77 @@
package space.dotcat.assistant.repository.authRepository;


import android.support.annotation.NonNull;

import io.reactivex.Completable;
import io.reactivex.Single;
import space.dotcat.assistant.content.Authorization;
import space.dotcat.assistant.content.AuthorizationAnswer;

public interface AuthRepository {

/**
* Save particular ulr into a phone's disk memory
*
* @param url - address of server which you want to connect with
*/

void saveUrl(@NonNull String url);

/**
* Get url value from the disk. Returns default value if url was not been saved
*
* @return address of server which you are now connected with
*/

String getUrl();

/**
* Save particular token into a phone's disk memory
*
* @param token - user token
*/

void saveToken(@NonNull String token);

/**
* Get token from the disk. Returns default value if token was not saved
*
* @return user token
*/

String getToken();

/**
* Delete user token from a phone's memory disk
*
*/

void deleteToken();

/**
* Get preference summary by given key, return default value as well if summary was not saved
*
* @param key - preference key
* @param defaultValue - default value for summary
* @return - summary for preference
*/

String getSummaryByKey(@NonNull String key, @NonNull String defaultValue);

/**
* Try to authorize as a user
*
* @param authorizationInfo - object that contains login and password
* @return - single with auth answer from the server
*/

Single<AuthorizationAnswer> authUser(@NonNull Authorization authorizationInfo);

/**
* Destroy api service instance in order to create it again when it be needed
*
*/

void destroyApiService();
}
@@ -0,0 +1,72 @@
package space.dotcat.assistant.repository.authRepository;


import android.support.annotation.NonNull;

import io.reactivex.Single;
import space.dotcat.assistant.content.Authorization;
import space.dotcat.assistant.content.AuthorizationAnswer;
import space.dotcat.assistant.repository.authRepository.localAuthDataSource.LocalAuthSource;
import space.dotcat.assistant.repository.authRepository.remoteDataSource.RemoteAuthSource;

public class AuthRepositoryImpl implements AuthRepository {

private LocalAuthSource mLocalAuthSource;

private RemoteAuthSource mRemoteAuthSource;

public AuthRepositoryImpl(LocalAuthSource authRepository, RemoteAuthSource remoteAuthSource) {
mLocalAuthSource = authRepository;

mRemoteAuthSource = remoteAuthSource;
}

@Override
public void saveUrl(@NonNull String url) {
mLocalAuthSource.saveUrl(url);
}

@Override
public String getUrl() {
return mLocalAuthSource.getUrl();
}

@Override
public void saveToken(@NonNull String token) {
mLocalAuthSource.saveToken(token);
}

@Override
public String getToken() {
return mLocalAuthSource.getToken();
}

@Override
public void deleteToken() {
mLocalAuthSource.deleteToken();
}

@Override
public String getSummaryByKey(@NonNull String key, @NonNull String defaultValue) {
return mLocalAuthSource.getSummaryByKey(key, defaultValue);
}

@Override
public Single<AuthorizationAnswer> authUser(@NonNull Authorization authorizationInfo) {
return mRemoteAuthSource.authUser(authorizationInfo)
.flatMap(answer -> {
String token = answer.getToken();

mLocalAuthSource.saveToken(token);

mRemoteAuthSource.destroyApiService();

return Single.just(answer);
});
}

@Override
public void destroyApiService() {
mRemoteAuthSource.destroyApiService();
}
}
@@ -0,0 +1,19 @@
package space.dotcat.assistant.repository.authRepository.localAuthDataSource;


import android.support.annotation.NonNull;

public interface LocalAuthSource {

void saveUrl(@NonNull String url);

String getUrl();

void saveToken(@NonNull String token);

String getToken();

void deleteToken();

String getSummaryByKey(@NonNull String key, @NonNull String defaultValue);
}
@@ -0,0 +1,62 @@
package space.dotcat.assistant.repository.authRepository.localAuthDataSource;


import android.content.SharedPreferences;
import android.support.annotation.NonNull;

import space.dotcat.assistant.BuildConfig;

public class LocalAuthSourceImpl implements LocalAuthSource {

private SharedPreferences mSharedPreferences;

private static final String URL_DEFAULT_VALUE = "https://api.ks-cube.tk/";

private static final String TOKEN_DEFAULT_VALUE = "";

public LocalAuthSourceImpl(SharedPreferences sharedPreferences) {
mSharedPreferences = sharedPreferences;
}

@Override
public void saveUrl(@NonNull String url) {
SharedPreferences.Editor editor = mSharedPreferences.edit();

editor.putString(BuildConfig.URL_KEY, url);

editor.apply();
}

@Override
public String getUrl() {
return mSharedPreferences.getString(BuildConfig.URL_KEY, URL_DEFAULT_VALUE);
}

@Override
public void saveToken(@NonNull String token) {
SharedPreferences.Editor editor = mSharedPreferences.edit();

editor.putString(BuildConfig.TOKEN_KEY, token);

editor.apply();
}

@Override
public String getToken() {
return mSharedPreferences.getString(BuildConfig.TOKEN_KEY, TOKEN_DEFAULT_VALUE);
}

@Override
public void deleteToken() {
SharedPreferences.Editor editor = mSharedPreferences.edit();

editor.remove(BuildConfig.TOKEN_KEY);

editor.apply();
}

@Override
public String getSummaryByKey(@NonNull String key, @NonNull String defaultValue) {
return mSharedPreferences.getString(key, defaultValue);
}
}
@@ -0,0 +1,30 @@
package space.dotcat.assistant.repository.roomsRepository;


import java.util.List;

import io.reactivex.Completable;
import io.reactivex.Flowable;
import space.dotcat.assistant.content.Room;

public interface RoomRepository {

/**
*
* Method is responsible for giving observable flowable of rooms. Firstly trying to load data
* from db, if there is no available rooms localy then fetching data via remote api. If some
* error occurs, then this method will return merged Flowable from local rooms and delayed error
*
* @return observable Flowable list of rooms
*/

Flowable<List<Room>> getRooms();

/**
* Refresh local data source asynchronously via remote api
*
* @return flowable which contains list of rooms
*/

Flowable<List<Room>> refreshRooms();
}
@@ -0,0 +1,71 @@
package space.dotcat.assistant.repository.roomsRepository;


import java.util.List;

import javax.inject.Inject;

import io.reactivex.Completable;
import io.reactivex.Flowable;
import space.dotcat.assistant.content.Room;
import space.dotcat.assistant.content.RoomResponse;
import space.dotcat.assistant.repository.roomsRepository.localRoomsDataSource.LocalRoomsSource;
import space.dotcat.assistant.repository.roomsRepository.remoteRoomsDataSource.RemoteRoomsSource;
import space.dotcat.assistant.utils.RxUtils;

public class RoomRepositoryImpl implements RoomRepository {

private LocalRoomsSource mLocalRoomsSource;

private RemoteRoomsSource mRemoteRoomsSource;

private Flowable<List<Room>> mCacheRooms;

public RoomRepositoryImpl(LocalRoomsSource localRoomsSource, RemoteRoomsSource remoteRoomsSource) {
mLocalRoomsSource = localRoomsSource;
mRemoteRoomsSource = remoteRoomsSource;
}

@Override
public Flowable<List<Room>> getRooms() {
if(mCacheRooms != null)
return mCacheRooms;

return mCacheRooms = mLocalRoomsSource.getRooms()
.flatMap(rooms-> {
if(rooms.isEmpty()) {
return loadRoomsRemotelyAndSaveToDb().onErrorResumeNext(throwable -> {
return Flowable.mergeDelayError(mLocalRoomsSource.getRooms(),
Flowable.error(throwable));
});
}

return Flowable.just(rooms);
});
}

@Override
public Flowable<List<Room>> refreshRooms() {
return loadRoomsRemotelyAndSaveToDb();
}

/**
* Trying to load rooms from remote server. If successfully loaded then it add sync all rooms from response
* to database.
*
* @return list of room from remote data source
*/

private Flowable<List<Room>> loadRoomsRemotelyAndSaveToDb() {
return mRemoteRoomsSource.getRooms()
.doOnNext(rooms-> {
if(rooms.isEmpty()) {
return;
}

mLocalRoomsSource.deleteRoomsSync();

mLocalRoomsSource.addRoomsSync(rooms);
});
}
}
@@ -0,0 +1,33 @@
package space.dotcat.assistant.repository.roomsRepository.localRoomsDataSource;


import java.util.List;

import io.reactivex.Flowable;
import space.dotcat.assistant.content.Room;
import space.dotcat.assistant.repository.roomsRepository.RoomRepository;

public interface LocalRoomsSource {

/**
* Return observable list of rooms from the db
*
* @return flowable with rooms
*/

Flowable<List<Room>> getRooms();

/**
* Add rooms to your db
*
* @param rooms that you want to add into your database
*/

void addRoomsSync(List<Room> rooms);

/**
* Delete all rooms from db
*/

void deleteRoomsSync();
}
@@ -0,0 +1,35 @@
package space.dotcat.assistant.repository.roomsRepository.localRoomsDataSource;


import android.support.annotation.NonNull;

import java.util.List;

import javax.inject.Inject;

import io.reactivex.Flowable;
import space.dotcat.assistant.content.Room;

public class LocalRoomsSourceImpl implements LocalRoomsSource {

private RoomsDao mRoomsDao;

public LocalRoomsSourceImpl(@NonNull RoomsDao roomsDao) {
mRoomsDao = roomsDao;
}

@Override
public Flowable<List<Room>> getRooms() {
return mRoomsDao.getRooms();
}

@Override
public void addRoomsSync(List<Room> rooms) {
mRoomsDao.addRoomsSync(rooms);
}

@Override
public void deleteRoomsSync() {
mRoomsDao.deleteRoomsSync();
}
}
@@ -0,0 +1,40 @@
package space.dotcat.assistant.repository.roomsRepository.localRoomsDataSource;

import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query;

import java.util.List;

import io.reactivex.Flowable;
import space.dotcat.assistant.content.Room;

@Dao
public interface RoomsDao {

/**
*
* @return flowable which contains list of rooms from database
*/

@Query("Select * from Rooms")
Flowable<List<Room>> getRooms();

/**
* Notice that this method inserts rooms in a sync way. Take care about it.
*
* @param rooms that you want to insert into your database.
*/

@Insert(onConflict = OnConflictStrategy.REPLACE)
void addRoomsSync(List<Room> rooms);

/**
* Delete all rooms from database. Notice that this method deletes rooms in a sync way.
*/

@Query("Delete from Rooms")
void deleteRoomsSync();
}
@@ -0,0 +1,18 @@
package space.dotcat.assistant.repository.roomsRepository.remoteRoomsDataSource;


import java.util.List;

import io.reactivex.Flowable;
import io.reactivex.Single;
import space.dotcat.assistant.content.Room;
import space.dotcat.assistant.content.RoomResponse;

public interface RemoteRoomsSource {
/**
* Return list of rooms from remote api
*
* @return list of rooms
*/
Flowable<List<Room>> getRooms();
}
@@ -0,0 +1,30 @@
package space.dotcat.assistant.repository.roomsRepository.remoteRoomsDataSource;


import java.util.List;

import javax.inject.Inject;

import io.reactivex.Flowable;
import io.reactivex.Single;
import space.dotcat.assistant.api.ApiFactory;
import space.dotcat.assistant.api.ApiService;
import space.dotcat.assistant.content.Room;
import space.dotcat.assistant.content.RoomResponse;

public class RemoteRoomsSourceImpl implements RemoteRoomsSource {

private ApiFactory mApiFactory;

public RemoteRoomsSourceImpl(ApiFactory apiFactory) {
mApiFactory = apiFactory;
}

@Override
public Flowable<List<Room>> getRooms() {
return mApiFactory.getApiService()
.rooms()
.toFlowable()
.map(RoomResponse::getRooms);
}
}
@@ -0,0 +1,54 @@
package space.dotcat.assistant.repository.thingsRepository;


import android.support.annotation.NonNull;

import java.util.List;

import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Single;
import space.dotcat.assistant.content.Message;
import space.dotcat.assistant.content.ResponseActionMessage;
import space.dotcat.assistant.content.Thing;

public interface ThingRepository {

/**
* Get things by placement's id. It checks if there are things locally, if not it will load it from
* the server and save to db
*
* @param id - placement's id
* @return flowable which contains list of things
*/

Flowable<List<Thing>> getThingsById(@NonNull String id);

/**
* Reload things from the remote api and update local things
*
* @param id placement's id
* @return flowable which contains list of things from the remote api
*/

Flowable<List<Thing>> refreshThings(@NonNull String id);

/**
* Do action with particular thing
*
* @param id - thing id
* @param message - info what should you do with thing
* @return single with answer from the server
*/

Single<ResponseActionMessage> doAction(@NonNull String id, @NonNull Message message);

/**
* Update thing in database
*
* @param thing - new updated thing
* @return - completable with info was update successful or not
*/

Completable updateThing(@NonNull Thing thing);
}
@@ -0,0 +1,75 @@
package space.dotcat.assistant.repository.thingsRepository;


import android.support.annotation.NonNull;

import java.util.List;

import javax.inject.Inject;

import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Single;
import space.dotcat.assistant.content.Message;
import space.dotcat.assistant.content.ResponseActionMessage;
import space.dotcat.assistant.content.Thing;
import space.dotcat.assistant.repository.thingsRepository.localThingsDataSource.LocalThingsSource;
import space.dotcat.assistant.repository.thingsRepository.remoteThingsDataSource.RemoteThingsSource;
import space.dotcat.assistant.utils.RxUtils;

public class ThingRepositoryImpl implements ThingRepository {

private LocalThingsSource mLocalThingsSource;

private RemoteThingsSource mRemoteThingsSource;

@Inject
public ThingRepositoryImpl(LocalThingsSource localThingsSource, RemoteThingsSource remoteThingsSource) {
mLocalThingsSource = localThingsSource;
mRemoteThingsSource = remoteThingsSource;
}

@Override
public Flowable<List<Thing>> getThingsById(@NonNull String id) {
return mLocalThingsSource.getThingsById(id)
.flatMap(things -> {
if(things.isEmpty()) {
return loadThingsAndSaveToDb(id).onErrorResumeNext(throwable-> {
Flowable<List<Thing>> localThings = mLocalThingsSource.getThingsById(id);

return Flowable.mergeDelayError(localThings, Flowable.error(throwable));
});
}

return Flowable.just(things);
});
}

@Override
public Flowable<List<Thing>> refreshThings(@NonNull String id) {
return loadThingsAndSaveToDb(id);
}

@Override
public Single<ResponseActionMessage> doAction(@NonNull String id, @NonNull Message message) {
return mRemoteThingsSource.doActionOnThing(id, message);
}

@Override
public Completable updateThing(@NonNull Thing thing) {
return Completable.fromAction(()-> mLocalThingsSource.updateThing(thing));
}

private Flowable<List<Thing>> loadThingsAndSaveToDb(@NonNull String id) {
return mRemoteThingsSource.loadThingsByPlacementId(id)
.doOnNext(things -> {
if(things.isEmpty()) {
return;
}

mLocalThingsSource.deleteAllThings();

mLocalThingsSource.addThingsSync(things);
});
}
}
@@ -0,0 +1,24 @@
package space.dotcat.assistant.repository.thingsRepository.localThingsDataSource;


import android.support.annotation.NonNull;

import java.util.List;

import io.reactivex.Flowable;
import space.dotcat.assistant.content.Thing;

public interface LocalThingsSource {

Flowable<List<Thing>> getThingsById(@NonNull String id);

void addThingsSync(@NonNull List<Thing> things);

void deleteAllThings();

void updateThing(@NonNull Thing thing);

void deleteThingById(String id);

void insertThing(Thing thing);
}
@@ -0,0 +1,51 @@
package space.dotcat.assistant.repository.thingsRepository.localThingsDataSource;


import android.support.annotation.NonNull;

import java.util.List;

import javax.inject.Inject;

import io.reactivex.Flowable;
import space.dotcat.assistant.content.Thing;

public class LocalThingsSourceImpl implements LocalThingsSource {

private ThingsDao mThingsDao;

@Inject
public LocalThingsSourceImpl(ThingsDao thingsDao) {
mThingsDao = thingsDao;
}

@Override
public Flowable<List<Thing>> getThingsById(@NonNull String id) {
return mThingsDao.getThingsById(id);
}

@Override
public void addThingsSync(@NonNull List<Thing> things) {
mThingsDao.insertThings(things);
}

@Override
public void deleteAllThings() {
mThingsDao.deleteAllThings();
}

@Override
public void updateThing(@NonNull Thing thing) {
mThingsDao.updateThing(thing);
}

@Override
public void deleteThingById(String id) {
mThingsDao.deleteThingById(id);
}

@Override
public void insertThing(Thing thing) {
mThingsDao.insertThing(thing);
}
}
@@ -0,0 +1,69 @@
package space.dotcat.assistant.repository.thingsRepository.localThingsDataSource;

import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query;
import android.arch.persistence.room.Update;
import android.support.annotation.NonNull;

import java.util.List;

import io.reactivex.Flowable;
import space.dotcat.assistant.content.Thing;

@Dao
public interface ThingsDao {

/**
* Get things by particular placement id from local database.
* This is observable query, so each time when database will be updated,
* flowable's onNext will be called
*
* @param id placement id
* @return flowable which contains list of things from database.
*/

@Query("Select * from Things where thing_placement = :id")
Flowable<List<Thing>> getThingsById(String id);


/**
* Insert things into database. If some conflict occurs, thing will be completely replaced
*
* @param things list of things that you want to insert into database.
*/

@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertThings(List<Thing> things);


@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertThing(Thing thing);

/**
* Delete all things from database
*/
@Query("Delete from Things")
void deleteAllThings();

/**
* Delete particular thing by its id
*
* @param id thing id ,that you want to delete
*/

@Query("Delete from Things where thing_id = :id")
void deleteThingById(String id);


/**
* Update your thing
*
* @param thing thing that you want to update
*/

@Update
void updateThing(Thing thing);
}
@@ -0,0 +1,19 @@
package space.dotcat.assistant.repository.thingsRepository.remoteThingsDataSource;


import android.support.annotation.NonNull;

import java.util.List;

import io.reactivex.Flowable;
import io.reactivex.Single;
import space.dotcat.assistant.content.Message;
import space.dotcat.assistant.content.ResponseActionMessage;
import space.dotcat.assistant.content.Thing;

public interface RemoteThingsSource {

Flowable<List<Thing>> loadThingsByPlacementId(@NonNull String id);

Single<ResponseActionMessage> doActionOnThing(@NonNull String id, @NonNull Message message);
}
@@ -0,0 +1,40 @@
package space.dotcat.assistant.repository.thingsRepository.remoteThingsDataSource;


import android.support.annotation.NonNull;

import java.util.List;

import javax.inject.Inject;

import io.reactivex.Flowable;
import io.reactivex.Single;
import space.dotcat.assistant.api.ApiFactory;
import space.dotcat.assistant.content.Message;
import space.dotcat.assistant.content.ResponseActionMessage;
import space.dotcat.assistant.content.Thing;
import space.dotcat.assistant.content.ThingResponse;

public class RemoteThingsSourceImpl implements RemoteThingsSource {

private ApiFactory mApiFactory;

@Inject
public RemoteThingsSourceImpl(ApiFactory apiFactory) {
mApiFactory = apiFactory;
}

@Override
public Flowable<List<Thing>> loadThingsByPlacementId(@NonNull String id) {
return mApiFactory.getApiService()
.things(id)
.toFlowable()
.map(ThingResponse::getThings);
}

@Override
public Single<ResponseActionMessage> doActionOnThing(@NonNull String id, @NonNull Message message) {
return mApiFactory.getApiService()
.action(id, message);
}
}
@@ -6,21 +6,28 @@
import android.support.annotation.NonNull;
import android.support.constraint.ConstraintLayout;
import android.support.design.widget.TextInputEditText;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Button;

import javax.inject.Inject;

import butterknife.BindView;
import butterknife.OnClick;
import space.dotcat.assistant.AppDelegate;
import space.dotcat.assistant.R;
import space.dotcat.assistant.di.activitiesComponents.authActivity.AuthActivityModule;
import space.dotcat.assistant.screen.general.BaseActivity;
import space.dotcat.assistant.screen.general.LoadingDialog;
import space.dotcat.assistant.screen.general.LoadingView;
import space.dotcat.assistant.screen.roomList.RoomsActivity;

public class AuthActivity extends BaseActivity implements AuthView {
public class AuthActivity extends BaseActivity implements AuthViewContract {

private LoadingView mLoadingView;
@Inject
LoadingView mLoadingView;

private AuthPresenter mAuthPresenter;
@Inject
AuthPresenter mAuthPresenter;

@BindView(R.id.bt_logIn)
Button mButton;
@@ -39,22 +46,19 @@ public class AuthActivity extends BaseActivity implements AuthView {

public static void start(@NonNull Activity activity){
Intent intent = new Intent(activity, AuthActivity.class);

activity.startActivity(intent);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_auth);
}

if(getSupportActionBar() != null)
getSupportActionBar().setTitle(getString(R.string.app_name));

mButton.setOnClickListener(this::tryLogIn);

mLoadingView = LoadingDialog.view(getSupportFragmentManager());

mAuthPresenter = new AuthPresenter(this);
@Override
protected void onStart() {
super.onStart();

mAuthPresenter.init();
}
@@ -66,6 +70,25 @@ protected void onStop() {
mAuthPresenter.unsubscribe();
}

@Override
protected void initDependencyGraph() {
AppDelegate.getInstance()
.plusDataLayerComponent()
.plusAuthComponent(new AuthActivityModule(this, getSupportFragmentManager()))
.inject(this);
}

@Override
protected void setupToolbar() {
Toolbar toolbar = getToolbar();

toolbar.setTitle(getString(R.string.app_name));

setNewToolbar(toolbar);

super.setupToolbar();
}

@Override
public void showLoading() {
mLoadingView.showLoading();
@@ -112,7 +135,8 @@ public void showAuthError(Throwable t) {
super.showBaseError(t, mContainer);
}

public void tryLogIn(View view){
@OnClick(R.id.bt_logIn)
public void tryLogIn(View view) {
mAuthPresenter.tryLogin(mUrl.getText().toString(), mLogin.getText().toString(),
mPassword.getText().toString());
}
@@ -3,64 +3,74 @@

import android.support.annotation.NonNull;

import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import space.dotcat.assistant.AppDelegate;
import space.dotcat.assistant.content.Authorization;
import space.dotcat.assistant.content.Url;
import space.dotcat.assistant.repository.RepositoryProvider;
import space.dotcat.assistant.repository.authRepository.AuthRepository;
import space.dotcat.assistant.screen.general.BasePresenter;
import space.dotcat.assistant.utils.TextUtils;
import space.dotcat.assistant.utils.UrlUtils;

public class AuthPresenter implements BasePresenter {

private final AuthView mAuthView;
private final AuthViewContract mAuthViewContract;

private AuthRepository mAuthRepository;

private CompositeDisposable mCompositeDisposable;

public AuthPresenter(@NonNull AuthView authView) {
mAuthView = authView;
public AuthPresenter(@NonNull AuthViewContract authView, @NonNull AuthRepository authRepository) {
mAuthViewContract = authView;

mAuthRepository = authRepository;

mCompositeDisposable = new CompositeDisposable();
}

public void init(){
String token = RepositoryProvider.provideAuthRepository().token();
public void init() {
String token = mAuthRepository.getToken();

if(!TextUtils.isEmpty(token))
mAuthView.showRoomList();
mAuthViewContract.showRoomList();

String url = RepositoryProvider.provideAuthRepository().url();
String url = mAuthRepository.getUrl();

if(!TextUtils.isEmpty(url)){
mAuthView.showExistingUrl(url);
if(!TextUtils.isEmpty(url)) {
mAuthViewContract.showExistingUrl(url);
}
}

public void tryLogin(@NonNull String url,@NonNull String login,@NonNull String password){
public void tryLogin(@NonNull String url, @NonNull String login, @NonNull String password) {
if(TextUtils.isEmpty(url)){
mAuthView.showUrlEmptyError();
mAuthViewContract.showUrlEmptyError();
} else if(!UrlUtils.isValidURL(url)) {
mAuthView.showUrlNotCorrectError();
mAuthViewContract.showUrlNotCorrectError();
} else if(TextUtils.isEmpty(login)){
mAuthView.showLoginError();
mAuthViewContract.showLoginError();
} else if(TextUtils.isEmpty(password)){
mAuthView.showPasswordError();
mAuthViewContract.showPasswordError();
} else {
Url urlForRequest = new Url(url);
mAuthRepository.saveUrl(url);

RepositoryProvider.provideAuthRepository().saveUrl(urlForRequest);
mAuthRepository.destroyApiService();

Authorization auth = new Authorization(login, password);

Disposable authorizationAnswer = RepositoryProvider.provideApiRepository()
.auth(auth)
.doOnSubscribe(disposable1 -> mAuthView.showLoading())
.doAfterTerminate(mAuthView::hideLoading)
.subscribe(answer -> mAuthView.showRoomList(),
mAuthView::showAuthError);
Disposable authorizationAnswer = mAuthRepository
.authUser(auth)
.doOnSubscribe(disposable1 -> mAuthViewContract.showLoading())
.doAfterTerminate(mAuthViewContract::hideLoading)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
answer -> mAuthViewContract.showRoomList(),

mAuthViewContract::showAuthError);

mCompositeDisposable.add(authorizationAnswer);
}
@@ -5,7 +5,7 @@

import space.dotcat.assistant.screen.general.LoadingView;

public interface AuthView extends LoadingView {
public interface AuthViewContract extends LoadingView {

void showRoomList();

@@ -8,51 +8,79 @@
import android.util.Log;
import android.view.View;

import javax.inject.Inject;

import butterknife.BindView;
import butterknife.ButterKnife;
import space.dotcat.assistant.R;
import space.dotcat.assistant.content.ApiError;
import space.dotcat.assistant.repository.RepositoryProvider;
import space.dotcat.assistant.repository.authRepository.AuthRepository;
import space.dotcat.assistant.screen.auth.AuthActivity;

public class BaseActivity extends AppCompatActivity {
public abstract class BaseActivity extends AppCompatActivity {

private static final String TAG = "BaseActivity";
@Inject
public AuthRepository mAuthRepository;

private static final int INVALID_ACCESS_TOKEN = 2101;
@BindView(R.id.toolbar)
Toolbar mToolbar;

private Snackbar mSnackbar;

private static final String TAG = "BaseActivity";

private static final int INVALID_ACCESS_TOKEN = 2101;

private final View.OnClickListener mInvalidAccessTokenHandler = onClick -> {
RepositoryProvider.provideAuthRepository().deleteToken();
mAuthRepository.deleteToken();

mAuthRepository.destroyApiService();

AuthActivity.start(this);

finish();
};

@BindView(R.id.toolbar)
Toolbar mToolbar;

@CallSuper
@Override
public void setContentView(@LayoutRes int layoutResID) {
super.setContentView(layoutResID);

initDependencyGraph();

ButterKnife.bind(this);

setupToolbar();
}

private void setupToolbar() {
if(mToolbar == null){
Log.d(TAG, "Toolbar not found");
abstract protected void initDependencyGraph();

protected void setupToolbar() {
if(mToolbar == null) {
Log.d(TAG, "Toolbar is not found");
}

setSupportActionBar(mToolbar);
}

public void showBaseError(Throwable throwable, View view){
//TODO look at this function later
protected void setNewToolbar(Toolbar toolbar) {
if(toolbar == null) {
Log.d(TAG, "Can not set nullable toolbar");
return;
}

mToolbar = toolbar;
}

protected Snackbar getSnackBar(){
return mSnackbar;
}

protected Toolbar getToolbar() {
return mToolbar;
}

public void showBaseError(Throwable throwable, View view){
mSnackbar = Snackbar.make(view, "Error",
Snackbar.LENGTH_INDEFINITE);

@@ -81,8 +109,4 @@ public void setErrorHandler(String actionName, View.OnClickListener action){

mSnackbar.setAction(actionName, action);
}

public Snackbar getSnackBar(){
return mSnackbar;
}
}
@@ -8,7 +8,7 @@
import space.dotcat.assistant.R;
import space.dotcat.assistant.screen.settings.SettingsActivity;

public class BaseActivityWithSettingsMenu extends BaseActivity {
public abstract class BaseActivityWithSettingsMenu extends BaseActivity {

@Override
public boolean onCreateOptionsMenu(Menu menu) {
@@ -11,29 +11,30 @@
import android.view.View;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

import butterknife.BindView;
import space.dotcat.assistant.AppDelegate;
import space.dotcat.assistant.R;
import space.dotcat.assistant.content.Room;
import space.dotcat.assistant.content.Thing;
import space.dotcat.assistant.di.activitiesComponents.roomDetails.RoomDetailsModule;
import space.dotcat.assistant.screen.general.BaseActivityWithSettingsMenu;
import space.dotcat.assistant.screen.general.LoadingDialog;
import space.dotcat.assistant.screen.general.LoadingView;

public class RoomDetailsActivity extends BaseActivityWithSettingsMenu implements RoomDetailsView,
public class RoomDetailsActivity extends BaseActivityWithSettingsMenu implements RoomDetailsViewContract,
RoomDetailsAdapter.OnItemChange, SwipeRefreshLayout.OnRefreshListener {

private final static String EXTRA_ROOM = "room";

private Room mRoom;
@Inject
LoadingView mLoadingView;

private LoadingView mLoadingView;
@Inject
RoomDetailsAdapter mRoomDetailsAdapter;

private RoomDetailsAdapter mRoomDetailsAdapter;

private RoomDetailsPresenter mRoomDetailsPresenter;
@Inject
RoomDetailsPresenter mRoomDetailsPresenter;

@BindView(R.id.swipeRefreshDetails)
SwipeRefreshLayout mSwipeRefreshLayout;
@@ -44,18 +45,14 @@ public class RoomDetailsActivity extends BaseActivityWithSettingsMenu implements
@BindView(R.id.tv_error_message)
TextView mErrorMessage;

private final View.OnClickListener mSnackBarButtonListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
showLoading();
mRoomDetailsPresenter.reloadData(mRoom.GetId());
hideLoading();
}
};
private Room mRoom;

private final static String EXTRA_ROOM = "room";

public static void start(@NonNull Activity activity, @NonNull Room room) {
Intent intent = new Intent(activity, RoomDetailsActivity.class);
intent.putExtra(EXTRA_ROOM, room);

activity.startActivity(intent);
}

@@ -69,22 +66,22 @@ protected void onCreate(Bundle savedInstanceState) {
android.R.color.holo_red_light,
android.R.color.holo_green_light);

mRoom = (Room) getIntent().getParcelableExtra(EXTRA_ROOM);
mRoom = getIntent().getParcelableExtra(EXTRA_ROOM);

if(getSupportActionBar() != null) {
getSupportActionBar().setTitle(mRoom.getFriendlyName());
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}

mLoadingView = LoadingDialog.view(getSupportFragmentManager());

mRoomDetailsAdapter = new RoomDetailsAdapter(new ArrayList<>(), this);

mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mRoomDetailsAdapter);
}

mRoomDetailsPresenter = new RoomDetailsPresenter( this);
mRoomDetailsPresenter.init(mRoom.GetId());
@Override
protected void onStart() {
super.onStart();

mRoomDetailsPresenter.init(mRoom.getId());
}

@Override
@@ -94,30 +91,43 @@ protected void onStop() {
mRoomDetailsPresenter.unsubscribe();
}

@Override
protected void initDependencyGraph() {
AppDelegate.getInstance()
.plusDataLayerComponent()
.plusRoomDetailsComponent(new RoomDetailsModule(this, this,
getSupportFragmentManager()))
.inject(this);
}

@Override
public void showThings(@NonNull List<Thing> things) {
if (!things.isEmpty()) {
if(getSnackBar() != null){
if(getSnackBar().isShown()){
getSnackBar().dismiss();
}
}

mRecyclerView.setVisibility(View.VISIBLE);
mErrorMessage.setVisibility(View.INVISIBLE);
mRoomDetailsAdapter.changeDataSet(things);
} else {
mRecyclerView.setVisibility(View.INVISIBLE);
mErrorMessage.setVisibility(View.VISIBLE);
mErrorMessage.setText(getString(R.string.error_empty_actions));
}
// if (getSnackBar() != null) {
// if (getSnackBar().isShown()) {
// getSnackBar().dismiss();
// }
// }

mRecyclerView.setVisibility(View.VISIBLE);

mErrorMessage.setVisibility(View.INVISIBLE);

mRoomDetailsAdapter.changeDataSet(things);
}

@Override
public void showError(Throwable throwable) {
super.showBaseError(throwable, mSwipeRefreshLayout);
}

@Override
public void showEmptyThingsError() {
mRecyclerView.setVisibility(View.INVISIBLE);

mErrorMessage.setVisibility(View.VISIBLE);
mErrorMessage.setText(getResources().getString(R.string.error_empty_things));
}

@Override
public void showLoading() {
mLoadingView.showLoading();
@@ -135,7 +145,8 @@ public void onItemChange(@NonNull Thing thing) {

@Override
public void onRefresh() {
mRoomDetailsPresenter.reloadData(mRoom.GetId());
mRoomDetailsPresenter.reloadThings(mRoom.getId());

mSwipeRefreshLayout.setRefreshing(false);
}

@@ -21,31 +21,36 @@ public class RoomDetailsAdapter extends RecyclerView.Adapter<RoomDetailsHolder>
@Override
public void onClick(View view) {
Thing thing = (Thing) view.getTag();

mOnItemChange.onItemChange(thing);
}
};

public RoomDetailsAdapter(@NonNull List<Thing> things, @NonNull OnItemChange onItemChange) {
mThings = things;

mOnItemChange = onItemChange;
}

public void changeDataSet(@NonNull List<Thing> things) {
mThings.clear();
mThings.addAll(things);

notifyDataSetChanged();
}

@NonNull
@Override
public RoomDetailsHolder onCreateViewHolder(ViewGroup parent, int viewType) {
public RoomDetailsHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());

View item = layoutInflater.inflate(R.layout.item_room_detail, parent, false);

return new RoomDetailsHolder(item);
}

@Override
public void onBindViewHolder(RoomDetailsHolder holder, int position) {
public void onBindViewHolder(@NonNull RoomDetailsHolder holder, int position) {
Thing thing = mThings.get(position);

holder.bind(thing);
@@ -58,7 +63,6 @@ public int getItemCount() {
return mThings.size();
}


public interface OnItemChange {
void onItemChange(@NonNull Thing thing);
}
@@ -18,6 +18,7 @@ public class RoomDetailsHolder extends RecyclerView.ViewHolder {

public RoomDetailsHolder(View itemView) {
super(itemView);

ButterKnife.bind(this, itemView);
}