Initial commit

master
esensar 2016-12-07 02:14:08 +01:00
commit a9d1051aa8
106 changed files with 4095 additions and 0 deletions

9
.gitignore vendored 100644
View File

@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild

22
.idea/compiler.xml 100644
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>

View File

@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

View File

@ -0,0 +1,3 @@
<component name="ProjectDictionaryState">
<dictionary name="ensar" />
</component>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

19
.idea/gradle.xml 100644
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="LOCAL" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.14.1" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

96
.idea/misc.xml 100644
View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="MavenImportPreferences">
<option name="generalSettings">
<MavenGeneralSettings>
<option name="mavenHome" value="Bundled (Maven 3)" />
</MavenGeneralSettings>
</option>
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state>
<expanded-state>
<State>
<id />
</State>
</expanded-state>
<selected-state>
<State>
<id>Android</id>
</State>
</selected-state>
</profile-state>
</entry>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="masterDetails">
<states>
<state key="ProjectJDKs.UI">
<settings>
<last-edited>1.7</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="ScopeChooserConfigurable.UI">
<settings>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/RealTimeTalk.iml" filepath="$PROJECT_DIR$/RealTimeTalk.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

1
app/.gitignore vendored 100644
View File

@ -0,0 +1 @@
/build

44
app/build.gradle 100644
View File

@ -0,0 +1,44 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.3"
defaultConfig {
applicationId "com.smarthomies.realtimetalk"
minSdkVersion 16
targetSdkVersion 24
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
dataBinding {
enabled = true
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:design:24.2.1'
compile 'com.google.code.gson:gson:2.8.0'
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.okhttp:okhttp:2.6.0'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.6.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'io.reactivex:rxandroid:1.2.1'
compile 'io.reactivex:rxjava:1.2.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
compile 'com.squareup.picasso:picasso:2.5.2'
testCompile 'junit:junit:4.12'
}

17
app/proguard-rules.pro vendored 100644
View File

@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/ensar/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@ -0,0 +1,26 @@
package com.smarthomies.realtimetalk;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumentation test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.smarthomies.realtimetalk", appContext.getPackageName());
}
}

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.smarthomies.realtimetalk">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".RTTApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".views.activities.LoginActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustPan">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".views.activities.MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar"/>
<activity
android:name=".views.activities.SearchActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar"
android:parentActivityName=".views.activities.MainActivity"/>
<activity android:name=".views.activities.RegistrationActivity"
android:theme="@style/AppTheme.NoActionBar"/>
</application>
</manifest>

View File

@ -0,0 +1,98 @@
package com.smarthomies.realtimetalk;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.util.Pair;
import com.smarthomies.realtimetalk.utils.NavigationSubject;
import rx.functions.Action1;
import rx.subscriptions.CompositeSubscription;
/**
* Created by ensar on 23/10/16.
*/
public abstract class RTTActivity extends AppCompatActivity {
public static final String TAG = RTTActivity.class.getSimpleName();
public static final String EXTRA_BUNDLE = "EXTRA_BUNDLE";
private CompositeSubscription compositeSubscription;
@Override
protected void onResume() {
super.onResume();
compositeSubscription = new CompositeSubscription();
compositeSubscription.add(NavigationSubject.getInstance().subscribe(new Action1<Pair<Class<? extends RTTActivity>, Bundle>>() {
@Override
public void call(Pair<Class<? extends RTTActivity>, Bundle> classBundlePair) {
if(classBundlePair == null) {
finish();
return;
}
Class activity = classBundlePair.first;
Bundle bundle = classBundlePair.second;
Intent intent = new Intent(RTTActivity.this, activity);
intent.putExtra(EXTRA_BUNDLE, bundle);
startActivity(intent);
}
}));
compositeSubscription.add(NavigationSubject.getFragmentNavigationInstance().subscribe(new Action1<Pair<Class<? extends RTTFragment>, Bundle>>() {
@Override
public void call(Pair<Class<? extends RTTFragment>, Bundle> classBundlePair) {
RTTActivity.this.startFragment(classBundlePair.first, RTTActivity.this.getFragmentContainerRId(),classBundlePair.second);
}
}));
subscribeToSubjects();
}
@Override
protected void onPause() {
super.onPause();
if (compositeSubscription != null && !compositeSubscription.isUnsubscribed()) {
compositeSubscription.unsubscribe();
}
unsubscribeFromSubjects();
}
protected void subscribeToSubjects() {}
protected void unsubscribeFromSubjects() {}
protected int getFragmentContainerRId() {
return 0;
}
public void startFragment(Class<? extends RTTFragment> fragmentClass, int containerId, Bundle bundle) {
String fragmentName = fragmentClass.getCanonicalName();
RTTFragment fragment;
try {
FragmentManager fm = getSupportFragmentManager();
// Find fragment by name
fragment = (RTTFragment) fm.findFragmentByTag(fragmentName);
if(fragment == null) {
// Create new fragment
fragment = fragmentClass.newInstance();
// Check for bundle
if (bundle != null) {
fragment.setArguments(bundle);
}
}
fm.beginTransaction().replace(containerId, fragment, fragmentName).commit();
fm.executePendingTransactions();
} catch (Exception e) {
Log.e(TAG, "Error starting fragment : " + fragmentName, e);
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,19 @@
package com.smarthomies.realtimetalk;
import android.app.Application;
import com.smarthomies.realtimetalk.utils.RTTAppHelper;
/**
* Created by ensar on 31/10/16.
*/
public class RTTApp extends Application {
public static final String TAG = RTTApp.class.getSimpleName();
@Override
public void onCreate() {
super.onCreate();
RTTAppHelper.getInstance().initWithContext(getApplicationContext());
}
}

View File

@ -0,0 +1,26 @@
package com.smarthomies.realtimetalk;
import android.support.v4.app.Fragment;
/**
* Created by ensar on 23/10/16.
*/
public class RTTFragment extends Fragment {
public static final String TAG = RTTFragment.class.getSimpleName();
protected void subscribeToSubjects() {}
protected void unsubscribeFromSubjects() {}
@Override
public void onResume() {
super.onResume();
subscribeToSubjects();
}
@Override
public void onPause() {
super.onPause();
unsubscribeFromSubjects();
}
}

View File

@ -0,0 +1,63 @@
package com.smarthomies.realtimetalk.managers;
import com.smarthomies.realtimetalk.models.db.User;
import com.smarthomies.realtimetalk.models.network.AuthenticationResponse;
import com.smarthomies.realtimetalk.models.network.LoginRequest;
import com.smarthomies.realtimetalk.models.network.RegistrationRequest;
import com.smarthomies.realtimetalk.services.AuthenticationAPIService;
import com.smarthomies.realtimetalk.utils.RTTAppHelper;
import rx.Observable;
import rx.functions.Action1;
import rx.functions.Func1;
/**
* Created by ensar on 01/11/16.
*/
public class AuthenticationManager {
public static final String TAG = AuthenticationManager.class.getSimpleName();
public Observable<AuthenticationResponse> loginUser(String username, String password) {
return AuthenticationAPIService.getInstance().login(getLoginRequest(username, password)).doOnNext(processAuthenticationResponse);
}
public Observable<AuthenticationResponse> registerUser(User user, final String username, final String password) {
RegistrationRequest registrationRequest = getRegistrationRequest(user, username, password);
return AuthenticationAPIService.getInstance().register(registrationRequest)
.flatMap(new Func1<AuthenticationResponse, Observable<LoginRequest>>() {
@Override
public Observable<LoginRequest> call(AuthenticationResponse authenticationResponse) {
return Observable.just(getLoginRequest(username, password));
}
})
.flatMap(requestLogin).doOnNext(processAuthenticationResponse);
}
private Func1<LoginRequest, Observable<AuthenticationResponse>> requestLogin = new Func1<LoginRequest, Observable<AuthenticationResponse>>() {
@Override
public Observable<AuthenticationResponse> call(LoginRequest request) {
return AuthenticationAPIService.getInstance().login(request);
}
};
private Action1<AuthenticationResponse> processAuthenticationResponse = new Action1<AuthenticationResponse>() {
@Override
public void call(AuthenticationResponse authenticationResponse) {
RTTAppHelper.getInstance().saveToken(authenticationResponse.getToken());
}
};
private RegistrationRequest getRegistrationRequest(User user, String username, String password) {
RegistrationRequest registrationRequest = new RegistrationRequest();
registrationRequest.setUsername(username);
registrationRequest.setPassword(password);
registrationRequest.setEmail(user.getEmail());
registrationRequest.setFirstName(user.getFirstName());
registrationRequest.setLastName(user.getLastName());
return registrationRequest;
}
private LoginRequest getLoginRequest(String username, String password) {
return new LoginRequest(username, password);
}
}

View File

@ -0,0 +1,24 @@
package com.smarthomies.realtimetalk.managers;
import com.smarthomies.realtimetalk.models.network.SearchRequest;
import com.smarthomies.realtimetalk.models.network.UsersResponse;
import com.smarthomies.realtimetalk.services.ContactsAPIService;
import rx.Observable;
/**
* Created by ensar on 01/11/16.
*/
public class ContactsManager {
public static final String TAG = ContactsManager.class.getSimpleName();
public Observable<UsersResponse> searchForUsers(String searchString) {
return ContactsAPIService.getInstance().search(getSearchRequest(searchString));
}
private SearchRequest getSearchRequest(String searchString) {
SearchRequest searchRequest = new SearchRequest();
searchRequest.setSearch(searchString);
return searchRequest;
}
}

View File

@ -0,0 +1,51 @@
package com.smarthomies.realtimetalk.models.db;
import com.google.gson.annotations.SerializedName;
/**
* Created by ensar on 01/11/16.
*/
public class User{
public static final String TAG = User.class.getSimpleName();
@SerializedName("first_name")
private String firstName;
@SerializedName("last_name")
private String lastName;
@SerializedName("email")
private String email;
@SerializedName("profile_picture")
private String imageUrl;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
}

View File

@ -0,0 +1,18 @@
package com.smarthomies.realtimetalk.models.network;
/**
* Created by ensar on 31/10/16.
*/
public class AuthenticationResponse {
public static final String TAG = AuthenticationResponse.class.getSimpleName();
private String token;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}

View File

@ -0,0 +1,35 @@
package com.smarthomies.realtimetalk.models.network;
/**
* Created by ensar on 31/10/16.
*/
public class LoginRequest {
public static final String TAG = LoginRequest.class.getSimpleName();
public LoginRequest() {
}
public LoginRequest(String username, String password) {
this.username = username;
this.password = password;
}
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -0,0 +1,54 @@
package com.smarthomies.realtimetalk.models.network;
/**
* Created by ensar on 31/10/16.
*/
public class RegistrationRequest {
public static final String TAG = RegistrationRequest.class.getSimpleName();
private String username;
private String password;
private String email;
private String firstName;
private String lastName;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}

View File

@ -0,0 +1,18 @@
package com.smarthomies.realtimetalk.models.network;
/**
* Created by ensar on 06/12/16.
*/
public class SearchRequest {
public static final String TAG = SearchRequest.class.getSimpleName();
private String search;
public String getSearch() {
return search;
}
public void setSearch(String search) {
this.search = search;
}
}

View File

@ -0,0 +1,22 @@
package com.smarthomies.realtimetalk.models.network;
import com.smarthomies.realtimetalk.models.db.User;
import java.util.List;
/**
* Created by ensar on 06/12/16.
*/
public class UsersResponse {
public static final String TAG = UsersResponse.class.getSimpleName();
private List<User> data;
public List<User> getData() {
return data;
}
public void setData(List<User> data) {
this.data = data;
}
}

View File

@ -0,0 +1,55 @@
package com.smarthomies.realtimetalk.network;
import com.smarthomies.realtimetalk.network.exceptions.APIException;
import com.smarthomies.realtimetalk.network.exceptions.BadAPIRequestException;
import com.smarthomies.realtimetalk.network.exceptions.NetworkException;
import com.smarthomies.realtimetalk.network.exceptions.RemoteResourceNotFoundException;
import com.smarthomies.realtimetalk.network.exceptions.ResourceForbiddenException;
import com.smarthomies.realtimetalk.network.exceptions.UnauthorizedException;
import java.io.IOException;
import java.net.HttpURLConnection;
import retrofit2.adapter.rxjava.HttpException;
/**
* Created by ensar on 31/10/16.
*/
public class APIErrorHandler {
public static final String TAG = APIErrorHandler.class.getSimpleName();
public static void handleGeneralAPIErrors(Throwable throwable)
throws APIException {
if(throwable instanceof HttpException) {
HttpException httpException = ((HttpException)throwable);
switch (httpException.response().code()) {
case HttpURLConnection.HTTP_BAD_REQUEST:
throw new BadAPIRequestException(throwable);
case HttpURLConnection.HTTP_NOT_FOUND:
throw new RemoteResourceNotFoundException(throwable);
case HttpURLConnection.HTTP_FORBIDDEN:
throw new ResourceForbiddenException(throwable);
case HttpURLConnection.HTTP_UNAUTHORIZED:
throw new UnauthorizedException(throwable);
}
}
if(throwable instanceof IOException) {
throw new NetworkException(throwable);
}
}
public static void handleLoginErrors(Throwable throwable)
throws APIException {
handleGeneralAPIErrors(throwable);
}
public static void handleRegistrationErrors(Throwable throwable)
throws APIException {
handleGeneralAPIErrors(throwable);
}
public static void handleSearchErrors(Throwable throwable)
throws APIException {
handleGeneralAPIErrors(throwable);
}
}

View File

@ -0,0 +1,24 @@
package com.smarthomies.realtimetalk.network;
import com.smarthomies.realtimetalk.models.network.AuthenticationResponse;
import com.smarthomies.realtimetalk.models.network.LoginRequest;
import com.smarthomies.realtimetalk.models.network.RegistrationRequest;
import retrofit2.http.Body;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import rx.Observable;
/**
* Created by ensar on 31/10/16.
*/
public interface AuthenticationAPI {
@POST(NetworkingConstants.API_LOGIN_ENDPOINT)
Observable<AuthenticationResponse> login(@Body LoginRequest request);
@PUT(NetworkingConstants.API_REGISTRATION_ENDPOINT)
Observable<AuthenticationResponse> register(@Body RegistrationRequest request);
}

View File

@ -0,0 +1,20 @@
package com.smarthomies.realtimetalk.network;
import com.smarthomies.realtimetalk.models.network.SearchRequest;
import com.smarthomies.realtimetalk.models.network.UsersResponse;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;
import rx.Observable;
/**
* Created by ensar on 15/11/16.
*/
public interface ContactsAPI {
@POST(NetworkingConstants.API_SEARCH_ENDPOINT)
Observable<UsersResponse> searchUsers(@Body SearchRequest searchRequest);
}

View File

@ -0,0 +1,14 @@
package com.smarthomies.realtimetalk.network;
/**
* Created by ensar on 31/10/16.
*/
public class NetworkingConstants {
public static final String TAG = NetworkingConstants.class.getSimpleName();
public static final String API_BASE_URL = "https://realtimetalk.herokuapp.com/rest/";
public static final String API_LOGIN_ENDPOINT = "prijava";
public static final String API_REGISTRATION_ENDPOINT = "dodaj";
public static final String API_CONTACTS_ENDPOINT = "contacts";
public static final String API_SEARCH_ENDPOINT = "search";
}

View File

@ -0,0 +1,60 @@
package com.smarthomies.realtimetalk.network;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* Created by ensar on 31/10/16.
*/
public class RestClient {
public static final String TAG = RestClient.class.getSimpleName();
private static RestClient instance;
private Retrofit retrofit;
private AuthenticationAPI authenticationAPI;
private ContactsAPI contactsAPI;
private RestClient() {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
// set your desired log level
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
// add your other interceptors …
// add logging as last interceptor
httpClient.addInterceptor(logging); // <-- this is the important line!
retrofit = new Retrofit.Builder()
.baseUrl(NetworkingConstants.API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(httpClient.build())
.build();
}
public static RestClient getInstance() {
if(instance == null) {
instance = new RestClient();
}
return instance;
}
public AuthenticationAPI getAuthenticationAPI() {
if(authenticationAPI == null) {
authenticationAPI = retrofit.create(AuthenticationAPI.class);
}
return authenticationAPI;
}
public ContactsAPI getContactsAPI() {
if(contactsAPI == null) {
contactsAPI = retrofit.create(ContactsAPI.class);
}
return contactsAPI;
}
}

View File

@ -0,0 +1,17 @@
package com.smarthomies.realtimetalk.network.exceptions;
import java.net.HttpURLConnection;
/**
* Created by ensar on 01/11/16.
*/
public class APIException extends Exception {
public static final String TAG = APIException.class.getSimpleName();
public APIException() {
}
public APIException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,15 @@
package com.smarthomies.realtimetalk.network.exceptions;
/**
* Created by ensar on 01/11/16.
*/
public class BadAPIRequestException extends APIException {
public static final String TAG = BadAPIRequestException.class.getSimpleName();
public BadAPIRequestException() {
}
public BadAPIRequestException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,15 @@
package com.smarthomies.realtimetalk.network.exceptions;
/**
* Created by ensar on 01/11/16.
*/
public class NetworkException extends APIException {
public static final String TAG = NetworkException.class.getSimpleName();
public NetworkException() {
}
public NetworkException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,15 @@
package com.smarthomies.realtimetalk.network.exceptions;
/**
* Created by ensar on 01/11/16.
*/
public class RemoteResourceNotFoundException extends APIException {
public static final String TAG = RemoteResourceNotFoundException.class.getSimpleName();
public RemoteResourceNotFoundException(Throwable cause) {
super(cause);
}
public RemoteResourceNotFoundException() {
}
}

View File

@ -0,0 +1,15 @@
package com.smarthomies.realtimetalk.network.exceptions;
/**
* Created by ensar on 01/11/16.
*/
public class ResourceForbiddenException extends APIException {
public static final String TAG = ResourceForbiddenException.class.getSimpleName();
public ResourceForbiddenException() {
}
public ResourceForbiddenException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,15 @@
package com.smarthomies.realtimetalk.network.exceptions;
/**
* Created by ensar on 14/11/16.
*/
public class UnauthorizedException extends APIException {
public static final String TAG = UnauthorizedException.class.getSimpleName();
public UnauthorizedException(Throwable cause) {
super(cause);
}
public UnauthorizedException() {
}
}

View File

@ -0,0 +1,62 @@
package com.smarthomies.realtimetalk.services;
import com.smarthomies.realtimetalk.models.network.AuthenticationResponse;
import com.smarthomies.realtimetalk.models.network.LoginRequest;
import com.smarthomies.realtimetalk.models.network.RegistrationRequest;
import com.smarthomies.realtimetalk.network.APIErrorHandler;
import com.smarthomies.realtimetalk.network.RestClient;
import com.smarthomies.realtimetalk.network.exceptions.APIException;
import rx.Observable;
import rx.exceptions.Exceptions;
import rx.functions.Action1;
/**
* Created by ensar on 31/10/16.
*/
public class AuthenticationAPIService {
public static final String TAG = AuthenticationAPIService.class.getSimpleName();
private static AuthenticationAPIService instance;
private AuthenticationAPIService() {}
public static AuthenticationAPIService getInstance() {
if(instance == null) {
instance = new AuthenticationAPIService();
}
return instance;
}
public Observable<AuthenticationResponse> login(LoginRequest request) {
return RestClient.getInstance().getAuthenticationAPI().login(request)
.doOnError(handleLoginErrors);
}
public Observable<AuthenticationResponse> register(RegistrationRequest request) {
return RestClient.getInstance().getAuthenticationAPI().register(request)
.doOnError(handleRegistrationErrors);
}
private Action1<Throwable> handleLoginErrors = new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
try {
APIErrorHandler.handleLoginErrors(throwable);
} catch (APIException apiException) {
throw Exceptions.propagate(apiException);
}
}
};
private Action1<Throwable> handleRegistrationErrors = new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
try {
APIErrorHandler.handleRegistrationErrors(throwable);
} catch (APIException apiException) {
throw Exceptions.propagate(apiException);
}
}
};
}

View File

@ -0,0 +1,45 @@
package com.smarthomies.realtimetalk.services;
import com.smarthomies.realtimetalk.models.network.SearchRequest;
import com.smarthomies.realtimetalk.models.network.UsersResponse;
import com.smarthomies.realtimetalk.network.APIErrorHandler;
import com.smarthomies.realtimetalk.network.RestClient;
import com.smarthomies.realtimetalk.network.exceptions.APIException;
import rx.Observable;
import rx.exceptions.Exceptions;
import rx.functions.Action1;
/**
* Created by ensar on 31/10/16.
*/
public class ContactsAPIService {
public static final String TAG = ContactsAPIService.class.getSimpleName();
private static ContactsAPIService instance;
private ContactsAPIService() {}
public static ContactsAPIService getInstance() {
if(instance == null) {
instance = new ContactsAPIService();
}
return instance;
}
public Observable<UsersResponse> search(SearchRequest request) {
return RestClient.getInstance().getContactsAPI().searchUsers(request)
.doOnError(handleSearchErrors);
}
private Action1<Throwable> handleSearchErrors = new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
try {
APIErrorHandler.handleSearchErrors(throwable);
} catch (APIException apiException) {
throw Exceptions.propagate(apiException);
}
}
};
}

View File

@ -0,0 +1,28 @@
package com.smarthomies.realtimetalk.utils;
import android.os.Bundle;
import android.util.Pair;
import com.smarthomies.realtimetalk.RTTActivity;
import com.smarthomies.realtimetalk.RTTFragment;
import rx.subjects.PublishSubject;
/**
* Created by ensar on 04/11/16.
*/
public class NavigationSubject {
public static final String TAG = NavigationSubject.class.getSimpleName();
private static PublishSubject<Pair<Class<? extends RTTActivity>, Bundle>> navigationSubject = PublishSubject.create();
private static PublishSubject<Pair<Class<? extends RTTFragment>, Bundle>> fragmentNavigationSubject = PublishSubject.create();
public static PublishSubject<Pair<Class<? extends RTTActivity>, Bundle>> getInstance() {
return navigationSubject;
}
public static PublishSubject<Pair<Class<? extends RTTFragment>, Bundle>> getFragmentNavigationInstance() {
return fragmentNavigationSubject;
}
}

View File

@ -0,0 +1,55 @@
package com.smarthomies.realtimetalk.utils;
import android.content.Context;
import android.content.SharedPreferences;
/**
* Created by ensar on 31/10/16.
*/
public class RTTAppHelper {
public static final String TAG = RTTAppHelper.class.getSimpleName();
public static final String SHARED_PREFERENCES_USER_TOKEN = "SHARED_PREFERENCES_USER_TOKEN";
private static RTTAppHelper instance;
private Context context;
private RTTAppHelper() {
}
public static RTTAppHelper getInstance() {
if(instance == null) {
instance = new RTTAppHelper();
}
return instance;
}
public void initWithContext(Context context) {
this.context = context;
}
public void saveToken(String token) {
writeToSharedPrefs(SHARED_PREFERENCES_USER_TOKEN, token);
}
public String getToken() {
return readFromSharedPrefs(SHARED_PREFERENCES_USER_TOKEN);
}
private void writeToSharedPrefs(String key, String value) {
SharedPreferences.Editor editor = context.getSharedPreferences(TAG, Context.MODE_PRIVATE).edit();
editor.putString(key, value);
editor.apply();
}
private String readFromSharedPrefs(String key) {
return readFromSharedPrefs(key, null);
}
private String readFromSharedPrefs(String key, String defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences(TAG, Context.MODE_PRIVATE);
return sharedPreferences.getString(key, defaultValue);
}
}

View File

@ -0,0 +1,33 @@
package com.smarthomies.realtimetalk.utils;
import android.util.Log;
import android.widget.Toast;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.network.exceptions.BadAPIRequestException;
import com.smarthomies.realtimetalk.network.exceptions.NetworkException;
import com.smarthomies.realtimetalk.network.exceptions.RemoteResourceNotFoundException;
import com.smarthomies.realtimetalk.network.exceptions.ResourceForbiddenException;
import com.smarthomies.realtimetalk.network.exceptions.UnauthorizedException;
/**
* Created by ensar on 14/11/16.
*/
public class RTTErrorUtil {
public static final String TAG = RTTErrorUtil.class.getSimpleName();
public static int getErrorString(Throwable e) {
if (e instanceof BadAPIRequestException) {
return R.string.error_bad_request;
} else if (e instanceof RemoteResourceNotFoundException) {
return R.string.error_user_not_found;
} else if (e instanceof ResourceForbiddenException) {
return R.string.error_unknown;
} else if (e instanceof UnauthorizedException) {
return R.string.error_user_bad_credentials;
} else if (e instanceof NetworkException) {
return R.string.error_no_internet;
}
return 0;
}
}

View File

@ -0,0 +1,92 @@
package com.smarthomies.realtimetalk.utils;
import android.text.TextUtils;
import android.util.Patterns;
import com.smarthomies.realtimetalk.R;
import java.util.regex.Pattern;
/**
* Created by ensar on 03/11/16.
*/
public class RTTUtil {
public static final String TAG = RTTUtil.class.getSimpleName();
private static final Pattern hasUppercase = Pattern.compile("\\p{javaUpperCase}");
private static final Pattern hasLowercase = Pattern.compile("\\p{javaLowerCase}");
private static final Pattern hasNumber = Pattern.compile("\\p{javaDigit}");
private static final Pattern hasSpecialChar = Pattern.compile("\\p{javaLetterOrDigit}");
public static final int PASSWORD_MINIMUM_LENGTH = 6;
public static final int PASSWORD_MAXIMUM_LENGTH = 20;
public static boolean isPasswordValid(String password) {
if (TextUtils.isEmpty(password)) {
return false;
}
if (password.length() < 6 || password.length() > 20) {
return false;
}
boolean hasUppercase = RTTUtil.hasUppercase.matcher(password).matches();
boolean hasLowercase = RTTUtil.hasLowercase.matcher(password).matches();
boolean hasNumber = RTTUtil.hasNumber.matcher(password).matches();
boolean hasSpecialChar = RTTUtil.hasSpecialChar.matcher(password).matches();
if (!hasNumber || !(hasUppercase || hasLowercase)) {
return false;
}
return true;
}
public static int getRequiredFieldError(String value) {
if (TextUtils.isEmpty(value)) {
return R.string.error_required;
}
return 0;
}
public static int getPasswordError(String password) {
if (TextUtils.isEmpty(password)) {
return R.string.error_required;
}
if (password.length() < 6 || password.length() > 20) {
return R.string.error_password_short;
}
boolean hasUppercase = RTTUtil.hasUppercase.matcher(password).matches();
boolean hasLowercase = RTTUtil.hasLowercase.matcher(password).matches();
boolean hasNumber = RTTUtil.hasNumber.matcher(password).matches();
boolean hasSpecialChar = RTTUtil.hasSpecialChar.matcher(password).matches();
// if (!hasNumber || !(hasUppercase || hasLowercase)) {
// return R.string.error_password_invalid;
// }
return 0;
}
public static int getPasswordConfirmationError(String password, String passwordConfirmation) {
int confirmationError = getPasswordError(passwordConfirmation);
if(confirmationError == 0 && !passwordConfirmation.equalsIgnoreCase(password)) {
return R.string.error_password_confirmation;
}
return confirmationError;
}
public static boolean isEmailValid(String email) {
Pattern emailPattern = Patterns.EMAIL_ADDRESS;
return emailPattern.matcher(email).matches();
}
}

View File

@ -0,0 +1,138 @@
package com.smarthomies.realtimetalk.viewmodels;
import android.databinding.BaseObservable;
import android.databinding.ObservableField;
import android.os.Bundle;
import android.util.Pair;
import android.view.View;
import com.smarthomies.realtimetalk.RTTActivity;
import com.smarthomies.realtimetalk.managers.AuthenticationManager;
import com.smarthomies.realtimetalk.models.network.AuthenticationResponse;
import com.smarthomies.realtimetalk.utils.NavigationSubject;
import com.smarthomies.realtimetalk.utils.RTTUtil;
import com.smarthomies.realtimetalk.views.activities.MainActivity;
import com.smarthomies.realtimetalk.views.activities.RegistrationActivity;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.schedulers.Schedulers;
import rx.subjects.AsyncSubject;
/**
* Created by ensar on 01/11/16.
*/
public class LoginViewModel extends BaseObservable {
public static final String TAG = LoginViewModel.class.getSimpleName();
private AsyncSubject<AuthenticationResponse> loginSubject;
private ObservableField<String> username = new ObservableField<>();
private ObservableField<Integer> usernameError = new ObservableField<>();
private ObservableField<String> password = new ObservableField<>();
private ObservableField<Integer> passwordError = new ObservableField<>();
private ObservableField<Boolean> requestInProgress = new ObservableField<>();
public LoginViewModel() {
requestInProgress.set(false);
loginSubject = AsyncSubject.create();
}
public AsyncSubject<AuthenticationResponse> createLoginSubject() {
loginSubject = AsyncSubject.create();
return loginSubject;
}
public AsyncSubject<AuthenticationResponse> getLoginSubject() {
return loginSubject;
}
private void loginUser() {
requestInProgress.set(true);
new AuthenticationManager().loginUser(username.get(), password.get()).subscribeOn(Schedulers.io()).subscribe(loginSubject);
}
private boolean validateFields() {
clearErrors();
passwordError.set(RTTUtil.getPasswordError(password.get()));
usernameError.set(RTTUtil.getRequiredFieldError(username.get()));
return passwordError.get() == 0 && usernameError.get() == 0;
}
private void clearErrors() {
usernameError.set(0);
passwordError.set(0);
}
public View.OnClickListener onLoginClick() {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
// if(validateFields()) {
// loginUser();
// }
onLoginDone();
}
};
}
public View.OnClickListener onRegisterClick() {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
NavigationSubject.getInstance().onNext(new Pair<Class<? extends RTTActivity>, Bundle>(RegistrationActivity.class, null));
}
};
}
public void onLoginDone() {
NavigationSubject.getInstance().onNext(new Pair<Class<? extends RTTActivity>, Bundle>(MainActivity.class, null));
}
public void onRequestCompleted() {
requestInProgress.set(false);
}
public ObservableField<String> getUsername() {
return username;
}
public void setUsername(ObservableField<String> username) {
this.username = username;
}
public ObservableField<Integer> getUsernameError() {
return usernameError;
}
public void setUsernameError(ObservableField<Integer> usernameError) {
this.usernameError = usernameError;
}
public ObservableField<String> getPassword() {
return password;
}
public void setPassword(ObservableField<String> password) {
this.password = password;
}
public ObservableField<Integer> getPasswordError() {
return passwordError;
}
public void setPasswordError(ObservableField<Integer> passwordError) {
this.passwordError = passwordError;
}
public ObservableField<Boolean> getRequestInProgress() {
return requestInProgress;
}
public void setRequestInProgress(ObservableField<Boolean> requestInProgress) {
this.requestInProgress = requestInProgress;
}
}

View File

@ -0,0 +1,44 @@
package com.smarthomies.realtimetalk.viewmodels;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.os.Bundle;
import android.util.Pair;
import android.view.View;
import com.smarthomies.realtimetalk.RTTActivity;
import com.smarthomies.realtimetalk.models.db.User;
import com.smarthomies.realtimetalk.utils.NavigationSubject;
import com.smarthomies.realtimetalk.views.activities.MainActivity;
import com.smarthomies.realtimetalk.views.activities.SearchActivity;
/**
* Created by ensar on 15/11/16.
*/
public class MainViewModel extends BaseObservable {
public static final String TAG = MainViewModel.class.getSimpleName();
UserViewModel userViewModel;
public void setUser(User user) {
userViewModel = new UserViewModel(user);
}
@Bindable
public UserViewModel getUserViewModel() {
return userViewModel;
}
public void setUserViewModel(UserViewModel userViewModel) {
this.userViewModel = userViewModel;
}
public View.OnClickListener onSearchClick() {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
NavigationSubject.getInstance().onNext(new Pair<Class<? extends RTTActivity>, Bundle>(SearchActivity.class, null));
}
};
}
}

View File

@ -0,0 +1,265 @@
package com.smarthomies.realtimetalk.viewmodels;
import android.databinding.BaseObservable;
import android.databinding.ObservableField;
import android.os.Bundle;
import android.util.Pair;
import android.view.View;
import com.smarthomies.realtimetalk.RTTActivity;
import com.smarthomies.realtimetalk.RTTFragment;
import com.smarthomies.realtimetalk.managers.AuthenticationManager;
import com.smarthomies.realtimetalk.models.db.User;
import com.smarthomies.realtimetalk.models.network.AuthenticationResponse;
import com.smarthomies.realtimetalk.utils.NavigationSubject;
import com.smarthomies.realtimetalk.utils.RTTUtil;
import com.smarthomies.realtimetalk.views.activities.MainActivity;
import com.smarthomies.realtimetalk.views.fragments.registration.PasswordRegistrationFragment;
import com.smarthomies.realtimetalk.views.fragments.registration.UserNameRegistrationFragment;
import rx.schedulers.Schedulers;
import rx.subjects.AsyncSubject;
/**
* Created by ensar on 01/11/16.
*/
public class RegistrationViewModel extends BaseObservable {
public static final String TAG = RegistrationViewModel.class.getSimpleName();
private AsyncSubject<AuthenticationResponse> registrationSubject;
private ObservableField<String> username = new ObservableField<>();
private ObservableField<String> firstName = new ObservableField<>();
private ObservableField<String> lastName = new ObservableField<>();
private ObservableField<String> email = new ObservableField<>();
private ObservableField<Integer> usernameError = new ObservableField<>();
private ObservableField<Integer> firstNameError = new ObservableField<>();
private ObservableField<Integer> lastNameError = new ObservableField<>();
private ObservableField<Integer> emailError = new ObservableField<>();
private ObservableField<String> password = new ObservableField<>();
private ObservableField<String> passwordConfirmation = new ObservableField<>();
private ObservableField<Integer> passwordError = new ObservableField<>();
private ObservableField<Integer> passwordConfirmationError = new ObservableField<>();
public RegistrationViewModel() {
requestInProgress.set(false);
registrationSubject = AsyncSubject.create();
}
public AsyncSubject<AuthenticationResponse> createRegistrationSubject() {
registrationSubject = AsyncSubject.create();
return registrationSubject;
}
public AsyncSubject<AuthenticationResponse> getRegistrationSubject() {
return registrationSubject;
}
private void registerUser() {
requestInProgress.set(true);
User user = new User();
user.setEmail(email.get());
user.setFirstName(firstName.get());
user.setLastName(lastName.get());
new AuthenticationManager().registerUser(user, username.get(), password.get()).subscribeOn(Schedulers.io()).subscribe(registrationSubject);
}
public View.OnClickListener onRegisterClick() {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
if(validatePassword()) {
registerUser();
}
}
};
}
public View.OnClickListener onExitClick() {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
NavigationSubject.getInstance().onNext(null);
}
};
}
public View.OnClickListener onNamesNext() {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
if(validateNames()) {
NavigationSubject.getFragmentNavigationInstance().onNext(new Pair<Class<? extends RTTFragment>, Bundle>(PasswordRegistrationFragment.class, null));
}
}
};
}
public View.OnClickListener onInfoNext() {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
if(validateInfo()) {
NavigationSubject.getFragmentNavigationInstance().onNext(new Pair<Class<? extends RTTFragment>, Bundle>(UserNameRegistrationFragment.class, null));
}
}
};
}
private boolean validateNames() {
clearNamesErrors();
firstNameError.set(RTTUtil.getRequiredFieldError(firstName.get()));
lastNameError.set(RTTUtil.getRequiredFieldError(lastName.get()));
return firstNameError.get() == 0 && lastNameError.get() == 0;
}
private boolean validateInfo() {
clearInfoErrors();
usernameError.set(RTTUtil.getRequiredFieldError(username.get()));
emailError.set(RTTUtil.getRequiredFieldError(email.get()));
return usernameError.get() == 0 && emailError.get() == 0;
}
private boolean validatePassword() {
clearPasswordErrors();
passwordError.set(RTTUtil.getPasswordError(password.get()));
passwordConfirmationError.set(RTTUtil.getPasswordConfirmationError(password.get(), passwordConfirmation.get()));
return passwordError.get() == 0 && passwordConfirmationError.get() == 0;
}
private void clearInfoErrors() {
usernameError.set(0);
emailError.set(0);
}
private void clearNamesErrors() {
firstNameError.set(0);
lastNameError.set(0);
}
private void clearPasswordErrors() {
passwordError.set(0);
passwordConfirmationError.set(0);
}
public void onRegistrationDone() {
NavigationSubject.getInstance().onNext(new Pair<Class<? extends RTTActivity>, Bundle>(MainActivity.class, null));
}
public void onRequestCompleted() {
requestInProgress.set(false);
}
private ObservableField<Boolean> requestInProgress = new ObservableField<>();
public ObservableField<String> getUsername() {
return username;
}
public void setUsername(ObservableField<String> username) {
this.username = username;
}
public ObservableField<String> getFirstName() {
return firstName;
}
public void setFirstName(ObservableField<String> firstName) {
this.firstName = firstName;
}
public ObservableField<String> getLastName() {
return lastName;
}
public void setLastName(ObservableField<String> lastName) {
this.lastName = lastName;
}
public ObservableField<String> getEmail() {
return email;
}
public void setEmail(ObservableField<String> email) {
this.email = email;
}
public ObservableField<Integer> getUsernameError() {
return usernameError;
}
public void setUsernameError(ObservableField<Integer> usernameError) {
this.usernameError = usernameError;
}
public ObservableField<Integer> getFirstNameError() {
return firstNameError;
}
public void setFirstNameError(ObservableField<Integer> firstNameError) {
this.firstNameError = firstNameError;
}
public ObservableField<Integer> getLastNameError() {
return lastNameError;
}
public void setLastNameError(ObservableField<Integer> lastNameError) {
this.lastNameError = lastNameError;
}
public ObservableField<Integer> getEmailError() {
return emailError;
}
public void setEmailError(ObservableField<Integer> emailError) {
this.emailError = emailError;
}
public ObservableField<String> getPassword() {
return password;
}
public void setPassword(ObservableField<String> password) {
this.password = password;
}
public ObservableField<String> getPasswordConfirmation() {
return passwordConfirmation;
}
public void setPasswordConfirmation(ObservableField<String> passwordConfirmation) {
this.passwordConfirmation = passwordConfirmation;
}
public ObservableField<Integer> getPasswordError() {
return passwordError;
}
public void setPasswordError(ObservableField<Integer> passwordError) {
this.passwordError = passwordError;
}
public ObservableField<Integer> getPasswordConfirmationError() {
return passwordConfirmationError;
}
public void setPasswordConfirmationError(ObservableField<Integer> passwordConfirmationError) {
this.passwordConfirmationError = passwordConfirmationError;
}
public ObservableField<Boolean> getRequestInProgress() {
return requestInProgress;
}
public void setRequestInProgress(ObservableField<Boolean> requestInProgress) {
this.requestInProgress = requestInProgress;
}
}

View File

@ -0,0 +1,87 @@
package com.smarthomies.realtimetalk.viewmodels;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.databinding.Observable;
import android.databinding.ObservableField;
import android.util.Log;
import com.smarthomies.realtimetalk.managers.ContactsManager;
import com.smarthomies.realtimetalk.models.db.User;
import com.smarthomies.realtimetalk.models.network.UsersResponse;
import java.util.List;
import java.util.concurrent.TimeUnit;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
/**
* Created by ensar on 15/11/16.
*/
public class SearchViewModel extends BaseObservable {
public static final String TAG = SearchViewModel.class.getSimpleName();
private ObservableField<List<User>> users = new ObservableField<>();
private ObservableField<String> search = new ObservableField<>();
private rx.Observable<String> rxSearch;
private Subscription subscription;
public SearchViewModel() {
rxSearch = rx.Observable.create(new rx.Observable.OnSubscribe<String>() {
@Override
public void call(final Subscriber<? super String> subscriber) {
search.addOnPropertyChangedCallback(new OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable observable, int i) {
String query = ((ObservableField<String>) observable).get();
if(subscriber.isUnsubscribed()) {
} else {
subscriber.onNext(query);
}
}
});
}
});
rxSearch = rxSearch.debounce(1000, TimeUnit.MILLISECONDS);
subscription = rxSearch.flatMap(new Func1<String, rx.Observable<UsersResponse>>() {
@Override
public rx.Observable<UsersResponse> call(String s) {
return new ContactsManager().searchForUsers(s);
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<UsersResponse>() {
@Override
public void call(UsersResponse usersResponse) {
users.set(usersResponse.getData());
}
});
}
public ObservableField<List<User>> getUsers() {
return users;
}
public void setUsers(ObservableField<List<User>> users) {
this.users = users;
}
public ObservableField<String> getSearch() {
return search;
}
public void setSearch(ObservableField<String> search) {
this.search = search;
}
public void clear() {
if(subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
}
}

View File

@ -0,0 +1,57 @@
package com.smarthomies.realtimetalk.viewmodels;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.databinding.BindingAdapter;
import android.databinding.ObservableBoolean;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.models.db.User;
import com.squareup.picasso.Picasso;
/**
* Created by ensar on 15/11/16.
*/
public class UserViewModel extends BaseObservable {
public static final String TAG = UserViewModel.class.getSimpleName();
private User model;
private ObservableBoolean state = new ObservableBoolean();
public UserViewModel(User model) {
this.model = model;
}
@Bindable
public String getName() {
return model.getFirstName() + " " + model.getLastName();
}
@Bindable
public String getEmail() {
return model.getEmail();
}
@Bindable
public String getImageUrl() {
return model.getImageUrl();
}
public ObservableBoolean getState() {
return state;
}
public View.OnClickListener changeContactState() {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "onClick: ");
state.set(!state.get());
}
};
}
}

View File

@ -0,0 +1,92 @@
package com.smarthomies.realtimetalk.views.activities;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.widget.Toast;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.RTTActivity;
import com.smarthomies.realtimetalk.databinding.ActivityLoginBinding;
import com.smarthomies.realtimetalk.models.network.AuthenticationResponse;
import com.smarthomies.realtimetalk.utils.RTTErrorUtil;
import com.smarthomies.realtimetalk.viewmodels.LoginViewModel;
import com.smarthomies.realtimetalk.views.activities.bindingutils.OnErrorChangedCallback;
import rx.Observable;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.exceptions.CompositeException;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
public class LoginActivity extends RTTActivity {
private LoginViewModel viewModel;
private Subscription loginSubscription;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityLoginBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_login);
viewModel = new LoginViewModel();
binding.setViewModel(viewModel);
viewModel.getPasswordError().addOnPropertyChangedCallback(new OnErrorChangedCallback(binding.tilPassword));
viewModel.getUsernameError().addOnPropertyChangedCallback(new OnErrorChangedCallback(binding.tilUsername));
}
@Override
protected void subscribeToSubjects() {
super.subscribeToSubjects();
loginSubscription = viewModel.getLoginSubject().observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(new LoginSubscriber());
}
@Override
protected void unsubscribeFromSubjects() {
super.unsubscribeFromSubjects();
if(loginSubscription != null && !loginSubscription.isUnsubscribed()) {
loginSubscription.unsubscribe();
}
}
private void reconnectToLoginSubject() {
loginSubscription = viewModel.createLoginSubject().observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(new LoginSubscriber());
}
private class LoginSubscriber extends Subscriber<AuthenticationResponse> {
@Override
public void onCompleted() {
reconnectToLoginSubject();
viewModel.onRequestCompleted();
}
@Override
public void onError(Throwable e) {
reconnectToLoginSubject();
viewModel.onRequestCompleted();
if (e instanceof CompositeException) {
for (Throwable ex : ((CompositeException) e).getExceptions()) {
if(ex instanceof RuntimeException) {
handleException(ex.getCause());
}
}
} else {
handleException(e);
}
}
@Override
public void onNext(AuthenticationResponse authenticationResponse) {
viewModel.onLoginDone();
}
}
private void handleException(Throwable e) {
Toast.makeText(this, RTTErrorUtil.getErrorString(e), Toast.LENGTH_SHORT).show();
}
}

View File

@ -0,0 +1,143 @@
package com.smarthomies.realtimetalk.views.activities;
import android.content.Intent;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.RTTActivity;
import com.smarthomies.realtimetalk.databinding.ActivityMainBinding;
import com.smarthomies.realtimetalk.databinding.AppBarMainBinding;
import com.smarthomies.realtimetalk.databinding.NavHeaderMainBinding;
import com.smarthomies.realtimetalk.models.db.User;
import com.smarthomies.realtimetalk.viewmodels.UserViewModel;
import com.smarthomies.realtimetalk.views.adapters.UsersAdapter;
import com.smarthomies.realtimetalk.views.fragments.MainViewModelHolder;
import com.smarthomies.realtimetalk.views.fragments.registration.RegistrationViewModelHolder;
import java.util.ArrayList;
public class MainActivity extends RTTActivity
implements NavigationView.OnNavigationItemSelectedListener {
private MainViewModelHolder viewModelHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Add retained to fragment manager
if(getSupportFragmentManager().findFragmentByTag(MainViewModelHolder.class.getName()) == null) {
viewModelHolder = new MainViewModelHolder();
getSupportFragmentManager()
.beginTransaction()
.add(viewModelHolder, MainViewModelHolder.class.getName())
.commit();
}
else {
viewModelHolder = (MainViewModelHolder)getSupportFragmentManager().findFragmentByTag(MainViewModelHolder.class.getName());
}
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
AppBarMainBinding appBarMainBinding = binding.appBarMain;
User user = new User();
user.setFirstName("Ensar");
user.setLastName("Sarajcic");
user.setEmail("es.ensar@gmail.com");
user.setImageUrl("https://avatars3.githubusercontent.com/u/2764831?v=3&s=460");
viewModelHolder.getMainViewModel().setUser(user);
binding.setViewModel(viewModelHolder.getMainViewModel());
appBarMainBinding.setViewModel(viewModelHolder.getMainViewModel());
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rvUsersList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
UsersAdapter usersAdapter = new UsersAdapter(UsersAdapter.ViewType.LIST);
ArrayList<User> users = new ArrayList<User>();
users.add(user);
usersAdapter.setUsers(users);
recyclerView.setAdapter(usersAdapter);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.setDrawerListener(toggle);
toggle.syncState();
binding.navView.setNavigationItemSelectedListener(this);
}
@Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
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.
int id = item.getItemId();
if (id == R.id.nav_logout) {
// Handle the camera action
startActivity(new Intent(this, LoginActivity.class));
finish();
}
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
public MainViewModelHolder getViewModelHolder() {
return viewModelHolder;
}
}

View File

@ -0,0 +1,53 @@
package com.smarthomies.realtimetalk.views.activities;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.RTTActivity;
import com.smarthomies.realtimetalk.databinding.ActivityRegistrationBinding;
import com.smarthomies.realtimetalk.views.fragments.registration.RegistrationFragment;
import com.smarthomies.realtimetalk.views.fragments.registration.RegistrationViewModelHolder;
import com.smarthomies.realtimetalk.views.fragments.registration.UserInfoRegistrationFragment;
public class RegistrationActivity extends RTTActivity {
private RegistrationViewModelHolder viewModelHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Add retained to fragment manager
if(getSupportFragmentManager().findFragmentByTag(RegistrationViewModelHolder.class.getName()) == null) {
viewModelHolder = new RegistrationViewModelHolder();
getSupportFragmentManager()
.beginTransaction()
.add(viewModelHolder, RegistrationViewModelHolder.class.getName())
.commit();
}
else {
viewModelHolder = (RegistrationViewModelHolder)getSupportFragmentManager().findFragmentByTag(RegistrationViewModelHolder.class.getName());
}
ActivityRegistrationBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_registration);
binding.setViewModel(viewModelHolder.getRegistrationViewModel());
startFragment(UserInfoRegistrationFragment.class, null);
}
@Override
protected int getFragmentContainerRId() {
return R.id.fragment_container;
}
public void startFragment(Class<? extends RegistrationFragment> clazz, Bundle bundle) {
startFragment(clazz, R.id.fragment_container, bundle);
}
public RegistrationViewModelHolder getViewModelHolder() {
return viewModelHolder;
}
}

View File

@ -0,0 +1,117 @@
package com.smarthomies.realtimetalk.views.activities;
import android.app.SearchManager;
import android.content.Context;
import android.databinding.DataBindingUtil;
import android.databinding.Observable;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.databinding.ObservableField;
import java.util.List;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.RTTActivity;
import com.smarthomies.realtimetalk.databinding.ActivitySearchBinding;
import com.smarthomies.realtimetalk.models.db.User;
import com.smarthomies.realtimetalk.viewmodels.SearchViewModel;
import com.smarthomies.realtimetalk.views.adapters.UsersAdapter;
import com.smarthomies.realtimetalk.views.fragments.SearchViewModelHolder;
import java.util.ArrayList;
public class SearchActivity extends RTTActivity {
private SearchViewModelHolder viewModelHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Add retained to fragment manager
if(getSupportFragmentManager().findFragmentByTag(SearchViewModelHolder.class.getName()) == null) {
viewModelHolder = new SearchViewModelHolder();
getSupportFragmentManager()
.beginTransaction()
.add(viewModelHolder, SearchViewModelHolder.class.getName())
.commit();
}
else {
viewModelHolder = (SearchViewModelHolder)getSupportFragmentManager().findFragmentByTag(SearchViewModelHolder.class.getName());
}
ActivitySearchBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_search);
binding.setViewModel(viewModelHolder.getSearchViewModel());
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rvSearchList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
SearchViewModel searchViewModel = viewModelHolder.getSearchViewModel();
searchViewModel.getUsers().addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable observable, int i) {
List<User> users = ((ObservableField<List<User>>)observable).get();
UsersAdapter usersAdapter = new UsersAdapter(UsersAdapter.ViewType.SEARCH);
usersAdapter.setUsers(users);
recyclerView.setAdapter(usersAdapter);
}
});
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.search, menu);
final SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search));
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
getViewModelHolder().getSearchViewModel().getSearch().set(query);
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
getViewModelHolder().getSearchViewModel().getSearch().set(newText);
return false;
}
});
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.rvSearchList) {
return true;
}
return super.onOptionsItemSelected(item);
}
public SearchViewModelHolder getViewModelHolder() {
return viewModelHolder;
}
}

View File

@ -0,0 +1,34 @@
package com.smarthomies.realtimetalk.views.activities.bindingutils;
import android.databinding.BindingAdapter;
import android.support.design.widget.NavigationView;
import android.view.LayoutInflater;
import android.widget.ImageView;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.viewmodels.UserViewModel;
import com.squareup.picasso.Picasso;
import com.smarthomies.realtimetalk.databinding.NavHeaderMainBinding;
/**
* Created by ensar on 17/11/16.
*/
public class BindingAdapters {
public static final String TAG = BindingAdapters.class.getSimpleName();
@BindingAdapter("android:src")
public static void setImageUrl(ImageView view, String url) {
Picasso.with(view.getContext()).load(url).placeholder(R.mipmap.ic_launcher).into(view);
}
@BindingAdapter({"headerLayout"})
public static void setHeaderViewModel(NavigationView view, UserViewModel userViewModel) {
NavHeaderMainBinding navHeaderMainBinding = NavHeaderMainBinding.inflate(LayoutInflater.from(view.getContext()));
navHeaderMainBinding.setViewModel(userViewModel);
navHeaderMainBinding.executePendingBindings();
view.addHeaderView(navHeaderMainBinding.getRoot());
}
}

View File

@ -0,0 +1,29 @@
package com.smarthomies.realtimetalk.views.activities.bindingutils;
import android.databinding.Observable;
import android.databinding.ObservableField;
import android.support.design.widget.TextInputLayout;
import android.text.TextUtils;
/**
* Created by ensar on 01/11/16.
*/
public class OnErrorChangedCallback extends Observable.OnPropertyChangedCallback {
private TextInputLayout layout;
public OnErrorChangedCallback(TextInputLayout textInputLayout) {
layout = textInputLayout;
}
@Override
public void onPropertyChanged(Observable observable, int i) {
Integer errorRId = ((ObservableField<Integer>) observable).get();
if (errorRId == 0) {
layout.setErrorEnabled(false);
} else {
layout.setErrorEnabled(true);
layout.setError(layout.getContext().getString(errorRId));
}
}
}

View File

@ -0,0 +1,96 @@
package com.smarthomies.realtimetalk.views.adapters;
import android.databinding.DataBindingUtil;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.databinding.SearchUserListItemBinding;
import com.smarthomies.realtimetalk.databinding.UserListItemBinding;
import com.smarthomies.realtimetalk.models.db.User;
import com.smarthomies.realtimetalk.viewmodels.UserViewModel;
import java.util.List;
/**
* Created by ensar on 26/11/16.
*/
public class UsersAdapter extends RecyclerView.Adapter<UsersAdapter.UserViewHolder>{
public static final String TAG = UsersAdapter.class.getSimpleName();
public enum ViewType {
LIST,
SEARCH
}
private List<User> users;
private ViewType viewType;
public UsersAdapter(ViewType viewType) {
this.viewType = viewType;
}
@Override
public UserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (this.viewType) {
case LIST:
UserListItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.user_list_item, parent, false);
return new UserViewHolder(binding);
case SEARCH:
default:
SearchUserListItemBinding binding2 = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.search_user_list_item, parent, false);
return new UserViewHolder(binding2);
}
}
@Override
public void onBindViewHolder(UserViewHolder holder, int position) {
switch (this.viewType) {
case LIST:
UserListItemBinding binding = holder.getBinding();
binding.setViewModel(new UserViewModel(users.get(position)));
break;
case SEARCH:
SearchUserListItemBinding binding2 = holder.getSearchUserListItemBinding();
binding2.setViewModel(new UserViewModel(users.get(position)));
break;
}
}
@Override
public int getItemCount() {
return users.size();
}
public class UserViewHolder extends RecyclerView.ViewHolder {
private UserListItemBinding binding;
private SearchUserListItemBinding searchUserListItemBinding;
public UserViewHolder(UserListItemBinding userListItemBinding) {
super(userListItemBinding.getRoot());
this.binding = userListItemBinding;
}
public UserViewHolder(SearchUserListItemBinding userListItemBinding) {
super(userListItemBinding.getRoot());
this.searchUserListItemBinding = userListItemBinding;
}
public UserListItemBinding getBinding() {
return binding;
}
public SearchUserListItemBinding getSearchUserListItemBinding() {
return searchUserListItemBinding;
}
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}

View File

@ -0,0 +1,20 @@
package com.smarthomies.realtimetalk.views.fragments;
import com.smarthomies.realtimetalk.viewmodels.MainViewModel;
/**
* Created by ensar on 15/11/16.
*/
public class MainViewModelHolder extends ViewModelHolder {
public static final String TAG = MainViewModelHolder.class.getSimpleName();
private MainViewModel mainViewModel = new MainViewModel();
public MainViewModel getMainViewModel() {
return mainViewModel;
}
public void setMainViewModel(MainViewModel mainViewModel) {
this.mainViewModel = mainViewModel;
}
}

View File

@ -0,0 +1,26 @@
package com.smarthomies.realtimetalk.views.fragments;
import com.smarthomies.realtimetalk.viewmodels.SearchViewModel;
/**
* Created by ensar on 15/11/16.
*/
public class SearchViewModelHolder extends ViewModelHolder {
public static final String TAG = SearchViewModelHolder.class.getSimpleName();
private SearchViewModel searchViewModel = new SearchViewModel();
public SearchViewModel getSearchViewModel() {
return searchViewModel;
}
public void setSearchViewModel(SearchViewModel searchViewModel) {
this.searchViewModel = searchViewModel;
}
@Override
public void onDestroy() {
super.onDestroy();
searchViewModel.clear();
}
}

View File

@ -0,0 +1,17 @@
package com.smarthomies.realtimetalk.views.fragments;
import android.os.Bundle;
import android.support.v4.app.Fragment;
/**
* Created by ensar on 13/11/16.
*/
public abstract class ViewModelHolder extends Fragment {
public static final String TAG = ViewModelHolder.class.getSimpleName();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
}

View File

@ -0,0 +1,94 @@
package com.smarthomies.realtimetalk.views.fragments.registration;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.databinding.PasswordRegistrationBinding;
import com.smarthomies.realtimetalk.models.network.AuthenticationResponse;
import com.smarthomies.realtimetalk.utils.RTTErrorUtil;
import com.smarthomies.realtimetalk.views.activities.bindingutils.OnErrorChangedCallback;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.exceptions.CompositeException;
import rx.schedulers.Schedulers;
/**
* Created by ensar on 08/11/16.
*/
public class PasswordRegistrationFragment extends RegistrationFragment {
public static final String TAG = PasswordRegistrationFragment.class.getSimpleName();
private Subscription registrationSubscription;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
PasswordRegistrationBinding binding = DataBindingUtil.inflate(inflater, R.layout.password_registration, container, false);
View view = binding.getRoot();
binding.setViewModel(getViewModel());
getViewModel().getPasswordError().addOnPropertyChangedCallback(new OnErrorChangedCallback(binding.tilPassword));
getViewModel().getPasswordConfirmationError().addOnPropertyChangedCallback(new OnErrorChangedCallback(binding.tilConfirmPassword));
return view;
}
@Override
protected void subscribeToSubjects() {
super.subscribeToSubjects();
registrationSubscription = getViewModel().getRegistrationSubject().observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(new RegistrationSubscriber());
}
@Override
protected void unsubscribeFromSubjects() {
super.unsubscribeFromSubjects();
if(registrationSubscription != null && !registrationSubscription.isUnsubscribed()) {
registrationSubscription.unsubscribe();
}
}
private void reconnectToRegistrationSubject() {
registrationSubscription = getViewModel().createRegistrationSubject().observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(new RegistrationSubscriber());
}
private void handleException(Throwable e) {
Toast.makeText(getContext(), RTTErrorUtil.getErrorString(e), Toast.LENGTH_SHORT).show();
}
private class RegistrationSubscriber extends Subscriber<AuthenticationResponse> {
@Override
public void onCompleted() {
reconnectToRegistrationSubject();
getViewModel().onRequestCompleted();
}
@Override
public void onError(Throwable e) {
reconnectToRegistrationSubject();
getViewModel().onRequestCompleted();
if (e instanceof CompositeException) {
for (Throwable ex : ((CompositeException) e).getExceptions()) {
if(ex instanceof RuntimeException) {
handleException(ex.getCause());
}
}
} else {
handleException(e);
}
}
@Override
public void onNext(AuthenticationResponse authenticationResponse) {
getViewModel().onRegistrationDone();
}
}
}

View File

@ -0,0 +1,16 @@
package com.smarthomies.realtimetalk.views.fragments.registration;
import com.smarthomies.realtimetalk.RTTFragment;
import com.smarthomies.realtimetalk.viewmodels.RegistrationViewModel;
import com.smarthomies.realtimetalk.views.activities.RegistrationActivity;
/**
* Created by ensar on 08/11/16.
*/
public class RegistrationFragment extends RTTFragment {
public static final String TAG = RegistrationFragment.class.getSimpleName();
protected RegistrationViewModel getViewModel() {
return ((RegistrationActivity) getActivity()).getViewModelHolder().getRegistrationViewModel();
}
}

View File

@ -0,0 +1,21 @@
package com.smarthomies.realtimetalk.views.fragments.registration;
import com.smarthomies.realtimetalk.viewmodels.RegistrationViewModel;
import com.smarthomies.realtimetalk.views.fragments.ViewModelHolder;
/**
* Created by ensar on 13/11/16.
*/
public class RegistrationViewModelHolder extends ViewModelHolder {
public static final String TAG = RegistrationViewModelHolder.class.getSimpleName();
private RegistrationViewModel registrationViewModel = new RegistrationViewModel();
public RegistrationViewModel getRegistrationViewModel() {
return registrationViewModel;
}
public void setRegistrationViewModel(RegistrationViewModel registrationViewModel) {
this.registrationViewModel = registrationViewModel;
}
}

View File

@ -0,0 +1,33 @@
package com.smarthomies.realtimetalk.views.fragments.registration;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.databinding.UserInfoRegistrationBinding;
import com.smarthomies.realtimetalk.views.activities.bindingutils.OnErrorChangedCallback;
/**
* Created by ensar on 08/11/16.
*/
public class UserInfoRegistrationFragment extends RegistrationFragment {
public static final String TAG = UserInfoRegistrationFragment.class.getSimpleName();
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
UserInfoRegistrationBinding binding = DataBindingUtil.inflate(inflater, R.layout.user_info_registration, container, false);
View view = binding.getRoot();
binding.setViewModel(getViewModel());
getViewModel().getUsernameError().addOnPropertyChangedCallback(new OnErrorChangedCallback(binding.tilUsername));
getViewModel().getEmailError().addOnPropertyChangedCallback(new OnErrorChangedCallback(binding.tilEmail));
return view;
}
}

View File

@ -0,0 +1,32 @@
package com.smarthomies.realtimetalk.views.fragments.registration;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.databinding.UserNameRegistrationBinding;
import com.smarthomies.realtimetalk.views.activities.bindingutils.OnErrorChangedCallback;
/**
* Created by ensar on 08/11/16.
*/
public class UserNameRegistrationFragment extends RegistrationFragment {
public static final String TAG = UserNameRegistrationFragment.class.getSimpleName();
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
UserNameRegistrationBinding binding = DataBindingUtil.inflate(inflater, R.layout.user_name_registration, container, false);
View view = binding.getRoot();
binding.setViewModel(getViewModel());
getViewModel().getFirstNameError().addOnPropertyChangedCallback(new OnErrorChangedCallback(binding.tilFirstName));
getViewModel().getLastNameError().addOnPropertyChangedCallback(new OnErrorChangedCallback(binding.tilLastName));
return view;
}
}

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0" />
<path
android:fillColor="#FF000000"
android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-8,12.5v-9l6,4.5 -6,4.5z" />
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/colorPrimary"></solid>
<stroke android:color="@color/white" android:width="1dp"/>
<corners android:radius="2dp"/>
</shape>

View File

@ -0,0 +1,9 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="135"
android:centerColor="#4CAF50"
android:endColor="#2E7D32"
android:startColor="#81C784"
android:type="linear" />
</shape>

View File

@ -0,0 +1,144 @@
<layout>
<data>
<variable name="viewModel" type="com.smarthomies.realtimetalk.viewmodels.LoginViewModel"/>
<import type="android.view.View" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:background="@color/colorPrimary"
tools:context="com.smarthomies.realtimetalk.views.activities.LoginActivity">
<LinearLayout
android:id="@+id/llContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center">
<TextView
android:id="@+id/tvLoginTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:gravity="center"
android:textSize="48sp"
android:textColor="@color/colorAccent"
android:fontFamily="cursive"/>
<!-- Login progress -->
<ProgressBar
android:id="@+id/login_progress"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="@{viewModel.requestInProgress ? View.VISIBLE : View.INVISIBLE}"
tools:visibility="visible"
android:layout_gravity="center_horizontal" />
<LinearLayout
android:id="@+id/email_login_form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:id="@+id/tilUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="@color/white.50"
app:counterTextAppearance="@color/colorAccent">
<android.support.design.widget.TextInputEditText
android:id="@+id/tietUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt.username"
android:inputType="textEmailAddress"
android:maxLines="1"
android:textColor="@color/white"
android:textColorHint="@color/white.50"
android:textSize="16sp"
android:text="@={viewModel.username}"
android:freezesText="true"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/tilPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="@color/white.50"
app:counterTextAppearance="@color/colorAccent"
app:passwordToggleEnabled="true"
android:layout_marginTop="16dp"
app:passwordToggleTint="@color/white.50">
<android.support.design.widget.TextInputEditText
android:id="@+id/tietPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt.password"
android:imeActionId="@+id/btnLogin"
android:imeActionLabel="@string/action.sign_in"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:maxLines="1"
android:textColor="@color/white"
android:textColorHint="@color/white.50"
android:textSize="16sp"
android:text="@={viewModel.password}"
android:freezesText="true"/>
</android.support.design.widget.TextInputLayout>
<TextView
android:id="@+id/tvForgot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:textColor="@color/colorAccent"
android:layout_marginTop="16dp"
android:text="@string/action.forgot_password"/>
<Button
android:id="@+id/btnLogin"
style="?android:textAppearanceSmall"
android:background="@drawable/button_normal"
android:textColor="@color/colorAccent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action.login"
android:textStyle="bold"
android:onClick="@{viewModel.onLoginClick()}"
android:enabled="@{!viewModel.requestInProgress}"/>
<TextView
android:id="@+id/tvRegister"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorAccent"
android:layout_marginTop="16dp"
android:layout_gravity="center"
android:text="@string/action.register"
android:onClick="@{viewModel.onRegisterClick()}"
/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.smarthomies.realtimetalk.viewmodels.MainViewModel"
/>
</data>
<android.support.v4.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="@color/colorPrimary">
<include
android:id="@+id/app_bar_main"
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@{viewModel.userViewModel}"
app:menu="@menu/activity_main_drawer" />
</android.support.v4.widget.DrawerLayout>
</layout>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.smarthomies.realtimetalk.viewmodels.RegistrationViewModel" />
</data>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/colorPrimary"
tools:context="com.smarthomies.realtimetalk.views.activities.RegistrationActivity">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
android:id="@+id/ivBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:src="@android:drawable/ic_menu_close_clear_cancel"
android:onClick="@{viewModel.onExitClick()}"/>
</RelativeLayout>
</layout>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.smarthomies.realtimetalk.viewmodels.SearchViewModel"
/>
</data>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="@color/colorPrimary">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/rvSearchList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"/>
</android.support.design.widget.CoordinatorLayout>
</layout>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.smarthomies.realtimetalk.viewmodels.MainViewModel"/>
</data>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/main_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.smarthomies.realtimetalk.views.activities.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/ic_menu_search"
android:onClick="@{viewModel.onSearchClick()}"/>
</android.support.design.widget.CoordinatorLayout>
</layout>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.smarthomies.realtimetalk.viewmodels.MainViewModel" />
</data>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/content_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.smarthomies.realtimetalk.views.activities.MainActivity"
tools:showIn="@layout/app_bar_main">
<android.support.v7.widget.RecyclerView
android:id="@+id/rvUsersList"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
</layout>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.smarthomies.realtimetalk.viewmodels.UserViewModel"/>
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="@dimen/nav_header_height"
android:background="@drawable/side_nav_bar"
android:gravity="bottom"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<ImageView
android:id="@+id/imageView"
android:layout_width="@dimen/image.big"
android:layout_height="@dimen/image.big"
android:paddingTop="@dimen/nav_header_vertical_spacing"
android:src="@{viewModel.imageUrl}"
xmlns:app="http://schemas.android.com/apk/res-auto"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing"
android:text="@{viewModel.name}"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.email}" />
</LinearLayout>
</layout>

View File

@ -0,0 +1,124 @@
<layout>
<data>
<variable name="viewModel" type="com.smarthomies.realtimetalk.viewmodels.RegistrationViewModel"/>
<import type="android.view.View" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:background="@color/colorPrimary"
tools:context="com.smarthomies.realtimetalk.views.activities.LoginActivity">
<LinearLayout
android:id="@+id/llContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center">
<TextView
android:id="@+id/tvUserInfoTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/title.credentials"
android:gravity="center"
android:textSize="48sp"
android:textColor="@color/colorAccent"
android:fontFamily="cursive"/>
<TextView
android:id="@+id/tvUserInfoSubTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/subtitle.credentials"
android:gravity="center"
android:layout_marginTop="16dp"
android:textSize="36sp"
android:textColor="@color/colorAccent"
android:fontFamily="cursive"/>
<LinearLayout
android:id="@+id/llForm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="37dp">
<android.support.design.widget.TextInputLayout
android:id="@+id/tilPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="@color/white.50"
app:passwordToggleEnabled="true"
app:passwordToggleTint="@color/white.50"
app:counterTextAppearance="@color/colorAccent">
<android.support.design.widget.TextInputEditText
android:id="@+id/tietPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt.password"
android:inputType="textPassword"
android:maxLines="1"
android:textColor="@color/white"
android:textColorHint="@color/white.50"
android:textSize="16sp"
android:text="@={viewModel.password}"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/tilConfirmPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="@color/white.50"
app:passwordToggleEnabled="true"
app:passwordToggleTint="@color/white.50"
app:counterTextAppearance="@color/colorAccent"
android:layout_marginTop="16dp">
<android.support.design.widget.TextInputEditText
android:id="@+id/tietConfirmPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt.password_confirm"
android:imeActionId="@+id/login"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:maxLines="1"
android:textColor="@color/white"
android:textColorHint="@color/white.50"
android:textSize="16sp"
android:text="@={viewModel.passwordConfirmation}"/>
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/btnLogin"
style="?android:textAppearanceSmall"
android:background="@drawable/button_normal"
android:textColor="@color/colorAccent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="60dp"
android:text="@string/action.create_account"
android:textStyle="bold"
android:enabled="@{!viewModel.requestInProgress}"
android:onClick="@{viewModel.onRegisterClick()}"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.smarthomies.realtimetalk.viewmodels.UserViewModel"/>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">
<ImageView
android:id="@+id/ivUserImage"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@{viewModel.imageUrl}"
android:layout_centerVertical="true"
/>
<TextView
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:id="@+id/tvUserName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/ivUserImage"
android:layout_toLeftOf="@+id/ivState"
android:text="@{viewModel.name}"
android:textColor="@color/white"
android:textSize="14sp"
/>
<ImageView
android:id="@+id/ivState"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:src="@android:drawable/sym_action_call"
android:onClick="@{viewModel.changeContactState()}"
android:tint="@{viewModel.state ? @color/colorAccent : @color/white}"/>
</RelativeLayout>
</layout>

View File

@ -0,0 +1,120 @@
<layout>
<data>
<variable name="viewModel" type="com.smarthomies.realtimetalk.viewmodels.RegistrationViewModel"/>
<import type="android.view.View" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:background="@color/colorPrimary"
tools:context="com.smarthomies.realtimetalk.views.activities.LoginActivity">
<LinearLayout
android:id="@+id/llContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center">
<TextView
android:id="@+id/tvUserInfoTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/title.welcome"
android:gravity="center"
android:textSize="48sp"
android:textColor="@color/colorAccent"
android:fontFamily="cursive"/>
<TextView
android:id="@+id/tvUserInfoSubTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/subtitle.welcome"
android:gravity="center"
android:layout_marginTop="16dp"
android:textSize="36sp"
android:textColor="@color/colorAccent"
android:fontFamily="cursive"/>
<LinearLayout
android:id="@+id/llForm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="37dp">
<android.support.design.widget.TextInputLayout
android:id="@+id/tilUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="@color/white.50"
app:counterTextAppearance="@color/colorAccent">
<android.support.design.widget.TextInputEditText
android:id="@+id/tietUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt.username"
android:inputType="textVisiblePassword"
android:maxLines="1"
android:textColor="@color/white"
android:textColorHint="@color/white.50"
android:textSize="16sp"
android:text="@={viewModel.username}"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/tilEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="@color/white.50"
app:counterTextAppearance="@color/colorAccent"
app:passwordToggleTint="@color/white.50"
android:layout_marginTop="16dp">
<android.support.design.widget.TextInputEditText
android:id="@+id/tietEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt.email"
android:imeActionId="@+id/login"
android:imeOptions="actionDone"
android:inputType="textEmailAddress"
android:maxLines="1"
android:textColor="@color/white"
android:textColorHint="@color/white.50"
android:textSize="16sp"
android:text="@={viewModel.email}"/>
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/btnLogin"
style="?android:textAppearanceSmall"
android:background="@drawable/button_normal"
android:textColor="@color/colorAccent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="60dp"
android:text="@string/action.next"
android:textStyle="bold"
android:onClick="@{viewModel.onInfoNext()}"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.smarthomies.realtimetalk.viewmodels.UserViewModel"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">
<ImageView
android:id="@+id/ivUserImage"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@{viewModel.imageUrl}"
/>
<TextView
android:layout_marginLeft="16dp"
android:id="@+id/tvUserName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:text="@{viewModel.name}"
android:textColor="@color/white"
android:textSize="14sp"
/>
</LinearLayout>
</layout>

View File

@ -0,0 +1,122 @@
<layout>
<data>
<variable name="viewModel" type="com.smarthomies.realtimetalk.viewmodels.RegistrationViewModel"/>
<import type="android.view.View" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:background="@color/colorPrimary"
tools:context="com.smarthomies.realtimetalk.views.activities.LoginActivity">
<LinearLayout
android:id="@+id/llContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center">
<TextView
android:id="@+id/tvUserInfoTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/title.introduction"
android:gravity="center"
android:textSize="48sp"
android:textColor="@color/colorAccent"
android:fontFamily="cursive"/>
<TextView
android:id="@+id/tvUserInfoSubTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/subtitle.introduction"
android:gravity="center"
android:layout_marginTop="16dp"
android:textSize="36sp"
android:textColor="@color/colorAccent"
android:fontFamily="cursive"/>
<LinearLayout
android:id="@+id/llForm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="37dp">
<android.support.design.widget.TextInputLayout
android:id="@+id/tilFirstName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="@color/white.50"
app:counterTextAppearance="@color/colorAccent">
<android.support.design.widget.TextInputEditText
android:id="@+id/tierFirstName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt.first_name"
android:inputType="textPersonName"
android:maxLines="1"
android:textColor="@color/white"
android:textColorHint="@color/white.50"
android:textSize="16sp"
android:maxLength="30"
android:text="@={viewModel.firstName}"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/tilLastName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="@color/white.50"
app:counterTextAppearance="@color/colorAccent"
app:passwordToggleTint="@color/white.50"
android:layout_marginTop="16dp">
<android.support.design.widget.TextInputEditText
android:id="@+id/tierLastName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt.last_name"
android:imeActionId="@+id/login"
android:imeOptions="actionDone"
android:inputType="textPersonName"
android:maxLines="1"
android:textColor="@color/white"
android:textColorHint="@color/white.50"
android:textSize="16sp"
android:maxLength="30"
android:text="@={viewModel.lastName}"/>
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/btnLogin"
style="?android:textAppearanceSmall"
android:background="@drawable/button_normal"
android:textColor="@color/colorAccent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="60dp"
android:text="@string/action.next"
android:textStyle="bold"
android:onClick="@{viewModel.onNamesNext()}"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/nav_logout"
android:icon="@drawable/ic_menu_send"
android:title="Logout" />
</menu>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="@string/title.settings"
app:showAsAction="never" />
</menu>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:title="@string/title.search"
app:title="@string/title.search"
android:icon="@android:drawable/ic_menu_search"
app:showAsAction="always"
app:actionViewClass="android.support.v7.widget.SearchView" />
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -0,0 +1,8 @@
<resources>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
</style>
</resources>

View File

@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#303030</color>
<color name="colorPrimaryDark">#212121</color>
<color name="colorAccent">#80CBC4</color>
<!-- palette -->
<color name="white">#FFFFFF</color>
<color name="white.12">#1DFFFFFF</color>
<color name="white.50">#88FFFFFF</color>
<!-- text colors -->
<color name="text.hint">@color/white.50</color>
<color name="text">@color/white</color>
</resources>

View File

@ -0,0 +1,12 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="nav_header_vertical_spacing">16dp</dimen>
<dimen name="nav_header_height">160dp</dimen>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="image.big">64dp</dimen>
<dimen name="image.small">32dp</dimen>
</resources>

View File

@ -0,0 +1,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<item name="ic_menu_camera" type="drawable">@android:drawable/ic_menu_camera</item>
<item name="ic_menu_gallery" type="drawable">@android:drawable/ic_menu_gallery</item>
<item name="ic_menu_slideshow" type="drawable">@android:drawable/ic_menu_slideshow</item>
<item name="ic_menu_manage" type="drawable">@android:drawable/ic_menu_manage</item>
<item name="ic_menu_share" type="drawable">@android:drawable/ic_menu_share</item>
<item name="ic_menu_send" type="drawable">@android:drawable/ic_menu_send</item>
</resources>

View File

@ -0,0 +1,48 @@
<resources>
<string name="app_name">Real Time Talk</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="title.settings">Settings</string>
<string name="title.login">Sign in</string>
<string name="title.welcome">Welcome</string>
<string name="title.introduction">Tell us who you are</string>
<string name="title.credentials">Almost there</string>
<string name="title.search">Search</string>
<string name="subtitle.welcome">Input your login details</string>
<string name="subtitle.introduction">Input your full name</string>
<string name="subtitle.credentials">Choose your password</string>
<!-- Strings related to login -->
<string name="prompt.username">Username</string>
<string name="prompt.password">Password</string>
<string name="prompt.password_confirm">Confirm Password</string>
<string name="prompt.email">Email</string>
<string name="prompt.first_name">First Name</string>
<string name="prompt.last_name">Last Name</string>
<string name="action.sign_in_or_register">Sign in or register</string>
<string name="action.sign_in">Sign in</string>
<string name="action.login">Login</string>
<string name="action.register">No account yet? Create one</string>
<string name="action.forgot_password">Forgot username or password?</string>
<string name="action.next">Next</string>
<string name="action.create_account">Create Account</string>
<!-- Login errors -->
<string name="error.email.invalid">Email address is invalid</string>
<string name="error.email.incorrect">Email address doesn\'t exist</string>
<string name="error.password.short">Password should be at least 6 characters long</string>
<string name="error.password.invalid">Password should contain at least 1 letter and 1 number</string>
<string name="error.password.incorrect">This password is incorrect</string>
<string name="error.password.confirmation">Password does not match password confirmation</string>
<string name="error.required">This field is required</string>
<string name="error.no_internet">Check your internet connection!</string>
<string name="error.bad_request">Bad request, blame Dino Dizdarevic!</string>
<string name="error.user.not_found">User not found!</string>
<string name="error.user.bad_credentials">Please check your username and password!</string>
<string name="error.unknown">Unknown error!</string>
</resources>

View File

@ -0,0 +1,22 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorControlNormal">@color/white.12</item>
<item name="colorControlActivated">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

View File

@ -0,0 +1,17 @@
package com.smarthomies.realtimetalk;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

23
build.gradle 100644
View File

@ -0,0 +1,23 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

17
gradle.properties 100644
View File

@ -0,0 +1,17 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

Some files were not shown because too many files have changed in this diff Show More