Add caching, profile editing and call testing feature

master
esensar 2017-01-07 17:19:40 +01:00
parent a9d1051aa8
commit 997b43f2e2
60 changed files with 2160 additions and 115 deletions

6
.idea/vcs.xml 100644
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,4 +1,5 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'realm-android'
android { android {
compileSdkVersion 24 compileSdkVersion 24
@ -40,5 +41,6 @@ dependencies {
compile 'io.reactivex:rxjava:1.2.0' compile 'io.reactivex:rxjava:1.2.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.3.1' compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
compile 'com.squareup.picasso:picasso:2.5.2' compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.android.support:support-v4:24.2.1'
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
} }

View File

@ -2,7 +2,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.smarthomies.realtimetalk"> package="com.smarthomies.realtimetalk">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.RECORD_AUDIO"/>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application <application
android:name=".RTTApp" android:name=".RTTApp"
@ -15,24 +22,39 @@
android:name=".views.activities.LoginActivity" android:name=".views.activities.LoginActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar" android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustPan"> android:windowSoftInputMode="adjustPan"/>
<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:parentActivityName=".views.activities.MainActivity"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".views.activities.ProfileActivity"
android:label="@string/app_name"
android:parentActivityName=".views.activities.MainActivity"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".views.activities.RegistrationActivity"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".views.activities.CallActivity"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".views.activities.SplashScreenActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </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> </application>
</manifest> </manifest>

View File

@ -4,6 +4,8 @@ import android.app.Application;
import com.smarthomies.realtimetalk.utils.RTTAppHelper; import com.smarthomies.realtimetalk.utils.RTTAppHelper;
import io.realm.Realm;
/** /**
* Created by ensar on 31/10/16. * Created by ensar on 31/10/16.
*/ */
@ -15,5 +17,7 @@ public class RTTApp extends Application {
super.onCreate(); super.onCreate();
RTTAppHelper.getInstance().initWithContext(getApplicationContext()); RTTAppHelper.getInstance().initWithContext(getApplicationContext());
Realm.init(this);
} }
} }

View File

@ -0,0 +1,79 @@
package com.smarthomies.realtimetalk.database;
import java.util.List;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmResults;
/**
* Created by ensar on 12/12/16.
*/
public class RealmDAO<T extends RealmObject> {
public static final String TAG = RealmDAO.class.getSimpleName();
protected Class<T> entityClass;
public RealmDAO(Class<T> entityClass) {
this.entityClass = entityClass;
}
public List<T> load(Realm realm) {
RealmResults result = realm.where(entityClass).findAll();
return realm.copyFromRealm(result);
}
public void cleanSave(Realm realm, List objects) {
deleteAll(realm);
realm.beginTransaction();
for (Object realmObject : objects) {
realm.copyToRealm((RealmObject) realmObject);
}
realm.commitTransaction();
}
public void save(Realm realm, List objects) {
realm.beginTransaction();
for (Object realmObject : objects) {
realm.copyToRealm((RealmObject) realmObject);
}
realm.commitTransaction();
}
public void updateOrCreate(Realm realm, T object) {
realm.beginTransaction();
realm.copyToRealmOrUpdate(object);
realm.commitTransaction();
}
public void updateOrCreate(Realm realm, List<? extends RealmObject> objects, boolean deleteIfMissing) {
if(objects.isEmpty()) {
return;
}
Class clazz = objects.get(0).getClass();
realm.beginTransaction();
List liveObjects = realm.copyToRealmOrUpdate(objects);
// If true, all objects in DB which are not updated or added will be deleted
if(deleteIfMissing) {
RealmResults allDbObjects = realm.where(entityClass).findAll();
for(int i=0; i<allDbObjects.size(); i++) {
if(!liveObjects.contains(allDbObjects.get(i))) {
allDbObjects.deleteFromRealm(i);
}
}
}
realm.commitTransaction();
}
public void deleteAll(Realm realm) {
realm.beginTransaction();
realm.where(entityClass).findAll().deleteAllFromRealm();
realm.commitTransaction();
}
}

View File

@ -0,0 +1,26 @@
package com.smarthomies.realtimetalk.database;
import com.smarthomies.realtimetalk.models.db.User;
import io.realm.Realm;
/**
* Created by ensar on 12/12/16.
*/
public class UserDAO extends RealmDAO<User> {
public static final String TAG = UserDAO.class.getSimpleName();
public UserDAO() {
super(User.class);
}
public User findUserById(Realm realm, int id) {
return realm.where(entityClass).equalTo(User.PRIMARY_KEY, id).findFirst();
}
public void deleteById(Realm realm, int id) {
realm.beginTransaction();
realm.where(entityClass).equalTo(User.PRIMARY_KEY, id).findFirst().deleteFromRealm();
realm.commitTransaction();
}
}

View File

@ -0,0 +1,103 @@
package com.smarthomies.realtimetalk.managers;
import com.smarthomies.realtimetalk.database.UserDAO;
import com.smarthomies.realtimetalk.models.db.User;
import com.smarthomies.realtimetalk.models.network.PasswordChangeRequest;
import com.smarthomies.realtimetalk.models.network.RegistrationRequest;
import com.smarthomies.realtimetalk.models.network.UserResponse;
import com.smarthomies.realtimetalk.models.network.UsersResponse;
import com.smarthomies.realtimetalk.services.AccountAPIService;
import com.smarthomies.realtimetalk.utils.RTTAppHelper;
import io.realm.Realm;
import rx.Observable;
import rx.functions.Action1;
/**
* Created by ensar on 01/11/16.
*/
public class AccountManager {
public static final String TAG = AccountManager.class.getSimpleName();
public Observable<UserResponse> getProfile() {
return Observable.just(getUserFromDb()).concatWith(AccountAPIService.getInstance().getProfile()
.doOnNext(saveProfile));
}
public Observable<Object> updateProfile(User newProfile) {
return AccountAPIService.getInstance().updateProfile(getUpdateRequest(newProfile))
.doOnNext(new UpdateProfileDb(newProfile));
}
public Observable<Object> changePassword(String oldPassword, String newPassword) {
return AccountAPIService.getInstance().changePassword(getPasswordChangeRequest(oldPassword, newPassword));
}
private PasswordChangeRequest getPasswordChangeRequest(String oldPassword, String newPassword) {
PasswordChangeRequest passwordChangeRequest = new PasswordChangeRequest();
passwordChangeRequest.setCurrentPassword(oldPassword);
passwordChangeRequest.setNewPassword(newPassword);
return passwordChangeRequest;
}
private RegistrationRequest getUpdateRequest(User user) {
RegistrationRequest registrationRequest = new RegistrationRequest();
registrationRequest.setEmail(user.getEmail());
registrationRequest.setFirstName(user.getFirstName());
registrationRequest.setLastName(user.getLastName());
return registrationRequest;
}
private Action1<UserResponse> saveProfile = new Action1<UserResponse>() {
@Override
public void call(UserResponse userResponse) {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
new UserDAO().updateOrCreate(realm, userResponse.getData());
} finally {
if(realm != null) {
realm.close();
}
}
}
};
private UserResponse getUserFromDb() {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
UserResponse userResponse = new UserResponse();
userResponse.setData(new UserDAO().findUserById(realm, RTTAppHelper.getInstance().getUserId()));
return userResponse;
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if(realm != null) {
realm.close();
}
}
return null;
}
private class UpdateProfileDb implements Action1<Object> {
private User contact;
public UpdateProfileDb(User contact) {
this.contact = contact;
}
@Override
public void call(Object o) {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
new UserDAO().updateOrCreate(realm, contact);
} finally {
if(realm != null) {
realm.close();
}
}
}
}
}

View File

@ -8,6 +8,7 @@ import com.smarthomies.realtimetalk.services.AuthenticationAPIService;
import com.smarthomies.realtimetalk.utils.RTTAppHelper; import com.smarthomies.realtimetalk.utils.RTTAppHelper;
import rx.Observable; import rx.Observable;
import rx.functions.Action0;
import rx.functions.Action1; import rx.functions.Action1;
import rx.functions.Func1; import rx.functions.Func1;
@ -18,7 +19,8 @@ public class AuthenticationManager {
public static final String TAG = AuthenticationManager.class.getSimpleName(); public static final String TAG = AuthenticationManager.class.getSimpleName();
public Observable<AuthenticationResponse> loginUser(String username, String password) { public Observable<AuthenticationResponse> loginUser(String username, String password) {
return AuthenticationAPIService.getInstance().login(getLoginRequest(username, password)).doOnNext(processAuthenticationResponse); return AuthenticationAPIService.getInstance().login(getLoginRequest(username, password))
.doOnNext(processAuthenticationResponse);
} }
public Observable<AuthenticationResponse> registerUser(User user, final String username, final String password) { public Observable<AuthenticationResponse> registerUser(User user, final String username, final String password) {
@ -30,7 +32,14 @@ public class AuthenticationManager {
return Observable.just(getLoginRequest(username, password)); return Observable.just(getLoginRequest(username, password));
} }
}) })
.flatMap(requestLogin).doOnNext(processAuthenticationResponse); .flatMap(requestLogin)
.doOnNext(processAuthenticationResponse);
}
public Observable<Object> logout() {
return AuthenticationAPIService.getInstance().logout()
.doOnNext(processLogoutResponse)
.doOnError(processLogoutFailure);
} }
private Func1<LoginRequest, Observable<AuthenticationResponse>> requestLogin = new Func1<LoginRequest, Observable<AuthenticationResponse>>() { private Func1<LoginRequest, Observable<AuthenticationResponse>> requestLogin = new Func1<LoginRequest, Observable<AuthenticationResponse>>() {
@ -44,9 +53,29 @@ public class AuthenticationManager {
@Override @Override
public void call(AuthenticationResponse authenticationResponse) { public void call(AuthenticationResponse authenticationResponse) {
RTTAppHelper.getInstance().saveToken(authenticationResponse.getToken()); RTTAppHelper.getInstance().saveToken(authenticationResponse.getToken());
RTTAppHelper.getInstance().saveUserId(authenticationResponse.getId());
} }
}; };
private Action1<Object> processLogoutResponse = new Action1<Object>() {
@Override
public void call(Object object) {
clearToken();
}
};
private Action1<Throwable> processLogoutFailure = new Action1<Throwable>() {
@Override
public void call(Throwable object) {
clearToken();
}
};
private void clearToken() {
RTTAppHelper.getInstance().saveToken("");
RTTAppHelper.getInstance().saveUserId(-1);
}
private RegistrationRequest getRegistrationRequest(User user, String username, String password) { private RegistrationRequest getRegistrationRequest(User user, String username, String password) {
RegistrationRequest registrationRequest = new RegistrationRequest(); RegistrationRequest registrationRequest = new RegistrationRequest();
registrationRequest.setUsername(username); registrationRequest.setUsername(username);

View File

@ -1,10 +1,19 @@
package com.smarthomies.realtimetalk.managers; package com.smarthomies.realtimetalk.managers;
import android.util.Log;
import com.smarthomies.realtimetalk.database.UserDAO;
import com.smarthomies.realtimetalk.models.db.User;
import com.smarthomies.realtimetalk.models.network.ContactRequest;
import com.smarthomies.realtimetalk.models.network.SearchRequest; import com.smarthomies.realtimetalk.models.network.SearchRequest;
import com.smarthomies.realtimetalk.models.network.UsersResponse; import com.smarthomies.realtimetalk.models.network.UsersResponse;
import com.smarthomies.realtimetalk.services.ContactsAPIService; import com.smarthomies.realtimetalk.services.ContactsAPIService;
import java.util.List;
import io.realm.Realm;
import rx.Observable; import rx.Observable;
import rx.functions.Action1;
/** /**
* Created by ensar on 01/11/16. * Created by ensar on 01/11/16.
@ -16,9 +25,91 @@ public class ContactsManager {
return ContactsAPIService.getInstance().search(getSearchRequest(searchString)); return ContactsAPIService.getInstance().search(getSearchRequest(searchString));
} }
public Observable<UsersResponse> getContacts() {
return Observable.just(getUsersFromDb()).concatWith(ContactsAPIService.getInstance().getContacts()
.doOnNext(saveContacts));
}
public Observable<Object> saveContact(User contact) {
return ContactsAPIService.getInstance().saveContact(getContactRequest(contact))
.doOnNext(new UpdateContactDb(contact, true));
}
public Observable<Object> deleteContact(User contact) {
return ContactsAPIService.getInstance().deleteContact(getContactRequest(contact))
.doOnNext(new UpdateContactDb(contact, false));
}
private SearchRequest getSearchRequest(String searchString) { private SearchRequest getSearchRequest(String searchString) {
SearchRequest searchRequest = new SearchRequest(); SearchRequest searchRequest = new SearchRequest();
searchRequest.setSearch(searchString); searchRequest.setSearch(searchString);
return searchRequest; return searchRequest;
} }
private ContactRequest getContactRequest(User user) {
ContactRequest contactRequest = new ContactRequest();
contactRequest.setUsername(user.getUsername());
return contactRequest;
}
private UsersResponse getUsersFromDb() {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
UsersResponse usersResponse = new UsersResponse();
usersResponse.setData(new UserDAO().load(realm));
return usersResponse;
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if(realm != null) {
realm.close();
}
}
return null;
}
private Action1<UsersResponse> saveContacts = new Action1<UsersResponse>() {
@Override
public void call(UsersResponse usersResponse) {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
new UserDAO().updateOrCreate(realm, usersResponse.getData(), true);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if(realm != null) {
realm.close();
}
}
}
};
private class UpdateContactDb implements Action1<Object> {
private User contact;
private boolean add;
public UpdateContactDb(User contact, boolean add) {
this.contact = contact;
this.add = add;
}
@Override
public void call(Object o) {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
if(add) {
new UserDAO().updateOrCreate(realm, contact);
} else {
new UserDAO().deleteById(realm, contact.getId());
}
} finally {
if(realm != null) {
realm.close();
}
}
}
}
} }

View File

@ -2,12 +2,19 @@ package com.smarthomies.realtimetalk.models.db;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
/** /**
* Created by ensar on 01/11/16. * Created by ensar on 01/11/16.
*/ */
public class User{ public class User extends RealmObject{
public static final String TAG = User.class.getSimpleName(); public static final String TAG = User.class.getSimpleName();
public static final String PRIMARY_KEY = "username";
@PrimaryKey
private int id;
@SerializedName("first_name") @SerializedName("first_name")
private String firstName; private String firstName;
@SerializedName("last_name") @SerializedName("last_name")
@ -16,6 +23,15 @@ public class User{
private String email; private String email;
@SerializedName("profile_picture") @SerializedName("profile_picture")
private String imageUrl; private String imageUrl;
private String username;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() { public String getFirstName() {
return firstName; return firstName;
@ -48,4 +64,12 @@ public class User{
public void setImageUrl(String imageUrl) { public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl; this.imageUrl = imageUrl;
} }
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
} }

View File

@ -7,6 +7,7 @@ public class AuthenticationResponse {
public static final String TAG = AuthenticationResponse.class.getSimpleName(); public static final String TAG = AuthenticationResponse.class.getSimpleName();
private String token; private String token;
private int id;
public String getToken() { public String getToken() {
return token; return token;
@ -15,4 +16,12 @@ public class AuthenticationResponse {
public void setToken(String token) { public void setToken(String token) {
this.token = token; this.token = token;
} }
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
} }

View File

@ -0,0 +1,18 @@
package com.smarthomies.realtimetalk.models.network;
/**
* Created by ensar on 11/12/16.
*/
public class ContactRequest {
public static final String TAG = ContactRequest.class.getSimpleName();
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}

View File

@ -0,0 +1,31 @@
package com.smarthomies.realtimetalk.models.network;
import com.google.gson.annotations.SerializedName;
/**
* Created by ensar on 31/10/16.
*/
public class PasswordChangeRequest {
public static final String TAG = PasswordChangeRequest.class.getSimpleName();
@SerializedName("current_password")
private String currentPassword;
@SerializedName("new_password")
private String newPassword;
public String getCurrentPassword() {
return currentPassword;
}
public void setCurrentPassword(String currentPassword) {
this.currentPassword = currentPassword;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
}

View File

@ -1,5 +1,7 @@
package com.smarthomies.realtimetalk.models.network; package com.smarthomies.realtimetalk.models.network;
import com.google.gson.annotations.SerializedName;
/** /**
* Created by ensar on 31/10/16. * Created by ensar on 31/10/16.
*/ */
@ -9,7 +11,9 @@ public class RegistrationRequest {
private String username; private String username;
private String password; private String password;
private String email; private String email;
@SerializedName("first_name")
private String firstName; private String firstName;
@SerializedName("last_name")
private String lastName; private String lastName;
public String getUsername() { public String getUsername() {

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 UserResponse {
public static final String TAG = UserResponse.class.getSimpleName();
private User data;
public User getData() {
return data;
}
public void setData(User data) {
this.data = data;
}
}

View File

@ -1,10 +1,12 @@
package com.smarthomies.realtimetalk.network; package com.smarthomies.realtimetalk.network;
import com.smarthomies.realtimetalk.network.exceptions.APIConflictException;
import com.smarthomies.realtimetalk.network.exceptions.APIException; import com.smarthomies.realtimetalk.network.exceptions.APIException;
import com.smarthomies.realtimetalk.network.exceptions.BadAPIRequestException; import com.smarthomies.realtimetalk.network.exceptions.BadAPIRequestException;
import com.smarthomies.realtimetalk.network.exceptions.NetworkException; import com.smarthomies.realtimetalk.network.exceptions.NetworkException;
import com.smarthomies.realtimetalk.network.exceptions.RemoteResourceNotFoundException; import com.smarthomies.realtimetalk.network.exceptions.RemoteResourceNotFoundException;
import com.smarthomies.realtimetalk.network.exceptions.ResourceForbiddenException; import com.smarthomies.realtimetalk.network.exceptions.ResourceForbiddenException;
import com.smarthomies.realtimetalk.network.exceptions.ServerErrorException;
import com.smarthomies.realtimetalk.network.exceptions.UnauthorizedException; import com.smarthomies.realtimetalk.network.exceptions.UnauthorizedException;
import java.io.IOException; import java.io.IOException;
@ -31,25 +33,16 @@ public class APIErrorHandler {
throw new ResourceForbiddenException(throwable); throw new ResourceForbiddenException(throwable);
case HttpURLConnection.HTTP_UNAUTHORIZED: case HttpURLConnection.HTTP_UNAUTHORIZED:
throw new UnauthorizedException(throwable); throw new UnauthorizedException(throwable);
case HttpURLConnection.HTTP_CONFLICT:
throw new APIConflictException(throwable);
case HttpURLConnection.HTTP_UNAVAILABLE:
case HttpURLConnection.HTTP_INTERNAL_ERROR:
throw new ServerErrorException(throwable);
} }
} }
if(throwable instanceof IOException) { if(throwable instanceof IOException) {
throw new NetworkException(throwable); 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

@ -1,20 +0,0 @@
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

@ -7,8 +7,14 @@ public class NetworkingConstants {
public static final String TAG = NetworkingConstants.class.getSimpleName(); 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_BASE_URL = "https://realtimetalk.herokuapp.com/rest/";
public static final String API_LOGIN_ENDPOINT = "prijava"; public static final String API_LOGIN_ENDPOINT = "prijava";
public static final String API_LOGOUT_ENDPOINT = "odjava";
public static final String API_REGISTRATION_ENDPOINT = "dodaj"; public static final String API_REGISTRATION_ENDPOINT = "dodaj";
public static final String API_CONTACTS_ENDPOINT = "contacts"; public static final String API_CONTACTS_ENDPOINT = "contacts";
public static final String API_PROFILE_ENDPOINT = "profile";
public static final String API_PASSWORD_UPDATE_ENDPOINT = API_PROFILE_ENDPOINT + "/password";
public static final String API_SEARCH_ENDPOINT = "search"; public static final String API_SEARCH_ENDPOINT = "search";
public static final String AUTHORIZATION_HEADER = "Authorization";
} }

View File

@ -0,0 +1,32 @@
package com.smarthomies.realtimetalk.network.apis;
import com.smarthomies.realtimetalk.models.network.ContactRequest;
import com.smarthomies.realtimetalk.models.network.PasswordChangeRequest;
import com.smarthomies.realtimetalk.models.network.RegistrationRequest;
import com.smarthomies.realtimetalk.models.network.SearchRequest;
import com.smarthomies.realtimetalk.models.network.UserResponse;
import com.smarthomies.realtimetalk.models.network.UsersResponse;
import com.smarthomies.realtimetalk.network.NetworkingConstants;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import rx.Observable;
/**
* Created by ensar on 15/11/16.
*/
public interface AccountAPI {
@GET(NetworkingConstants.API_PROFILE_ENDPOINT)
Observable<UserResponse> getProfile();
@PUT(NetworkingConstants.API_PROFILE_ENDPOINT)
Observable<Object> updateProfile(@Body RegistrationRequest request);
@PUT(NetworkingConstants.API_PASSWORD_UPDATE_ENDPOINT)
Observable<Object> changePassword(@Body PasswordChangeRequest request);
}

View File

@ -1,12 +1,13 @@
package com.smarthomies.realtimetalk.network; package com.smarthomies.realtimetalk.network.apis;
import com.smarthomies.realtimetalk.models.network.AuthenticationResponse; import com.smarthomies.realtimetalk.models.network.AuthenticationResponse;
import com.smarthomies.realtimetalk.models.network.LoginRequest; import com.smarthomies.realtimetalk.models.network.LoginRequest;
import com.smarthomies.realtimetalk.models.network.RegistrationRequest; import com.smarthomies.realtimetalk.models.network.RegistrationRequest;
import com.smarthomies.realtimetalk.network.NetworkingConstants;
import retrofit2.http.Body; import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.POST; import retrofit2.http.POST;
import retrofit2.http.PUT;
import rx.Observable; import rx.Observable;
/** /**
@ -18,7 +19,10 @@ public interface AuthenticationAPI {
@POST(NetworkingConstants.API_LOGIN_ENDPOINT) @POST(NetworkingConstants.API_LOGIN_ENDPOINT)
Observable<AuthenticationResponse> login(@Body LoginRequest request); Observable<AuthenticationResponse> login(@Body LoginRequest request);
@PUT(NetworkingConstants.API_REGISTRATION_ENDPOINT) @POST(NetworkingConstants.API_REGISTRATION_ENDPOINT)
Observable<AuthenticationResponse> register(@Body RegistrationRequest request); Observable<AuthenticationResponse> register(@Body RegistrationRequest request);
@DELETE(NetworkingConstants.API_LOGOUT_ENDPOINT)
Observable<Object> logout();
} }

View File

@ -0,0 +1,32 @@
package com.smarthomies.realtimetalk.network.apis;
import com.smarthomies.realtimetalk.models.network.ContactRequest;
import com.smarthomies.realtimetalk.models.network.SearchRequest;
import com.smarthomies.realtimetalk.models.network.UsersResponse;
import com.smarthomies.realtimetalk.network.NetworkingConstants;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
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);
@GET(NetworkingConstants.API_CONTACTS_ENDPOINT)
Observable<UsersResponse> getContacts();
@POST(NetworkingConstants.API_CONTACTS_ENDPOINT)
Observable<Object> saveContact(@Body ContactRequest contactRequest);
@DELETE(NetworkingConstants.API_CONTACTS_ENDPOINT)
Observable<Object> deleteContact(@Body ContactRequest contactRequest);
}

View File

@ -1,4 +1,10 @@
package com.smarthomies.realtimetalk.network; package com.smarthomies.realtimetalk.network.clients;
import com.smarthomies.realtimetalk.network.NetworkingConstants;
import com.smarthomies.realtimetalk.network.apis.AccountAPI;
import com.smarthomies.realtimetalk.network.apis.AuthenticationAPI;
import com.smarthomies.realtimetalk.network.apis.ContactsAPI;
import com.smarthomies.realtimetalk.network.interceptors.AuthorizationInterceptor;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor; import okhttp3.logging.HttpLoggingInterceptor;
@ -17,6 +23,7 @@ public class RestClient {
private Retrofit retrofit; private Retrofit retrofit;
private AuthenticationAPI authenticationAPI; private AuthenticationAPI authenticationAPI;
private ContactsAPI contactsAPI; private ContactsAPI contactsAPI;
private AccountAPI accountAPI;
private RestClient() { private RestClient() {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
@ -25,6 +32,7 @@ public class RestClient {
OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
// add your other interceptors … // add your other interceptors …
httpClient.addInterceptor(new AuthorizationInterceptor());
// add logging as last interceptor // add logging as last interceptor
httpClient.addInterceptor(logging); // <-- this is the important line! httpClient.addInterceptor(logging); // <-- this is the important line!
@ -57,4 +65,11 @@ public class RestClient {
} }
return contactsAPI; return contactsAPI;
} }
public AccountAPI getAccountAPI() {
if(accountAPI == null) {
accountAPI = retrofit.create(AccountAPI.class);
}
return accountAPI;
}
} }

View File

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

View File

@ -0,0 +1,26 @@
package com.smarthomies.realtimetalk.network.interceptors;
import com.smarthomies.realtimetalk.network.NetworkingConstants;
import com.smarthomies.realtimetalk.utils.RTTAppHelper;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
/**
* Created by ensar on 11/12/16.
*/
public class AuthorizationInterceptor implements Interceptor {
public static final String TAG = AuthorizationInterceptor.class.getSimpleName();
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain
.request()
.newBuilder()
.addHeader(NetworkingConstants.AUTHORIZATION_HEADER, RTTAppHelper.getInstance().getToken()).build();
return chain.proceed(request);
}
}

View File

@ -0,0 +1,56 @@
package com.smarthomies.realtimetalk.services;
import com.smarthomies.realtimetalk.models.network.PasswordChangeRequest;
import com.smarthomies.realtimetalk.models.network.RegistrationRequest;
import com.smarthomies.realtimetalk.models.network.UserResponse;
import com.smarthomies.realtimetalk.network.APIErrorHandler;
import com.smarthomies.realtimetalk.network.clients.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 AccountAPIService {
public static final String TAG = AccountAPIService.class.getSimpleName();
private static AccountAPIService instance;
private AccountAPIService() {}
public static AccountAPIService getInstance() {
if(instance == null) {
instance = new AccountAPIService();
}
return instance;
}
public Observable<UserResponse> getProfile() {
return RestClient.getInstance().getAccountAPI().getProfile()
.doOnError(handleGeneralErrors);
}
public Observable<Object> updateProfile(RegistrationRequest request) {
return RestClient.getInstance().getAccountAPI().updateProfile(request)
.doOnError(handleGeneralErrors);
}
public Observable<Object> changePassword(PasswordChangeRequest request) {
return RestClient.getInstance().getAccountAPI().changePassword(request)
.doOnError(handleGeneralErrors);
}
private Action1<Throwable> handleGeneralErrors = new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
try {
APIErrorHandler.handleGeneralAPIErrors(throwable);
} catch (APIException apiException) {
throw Exceptions.propagate(apiException);
}
}
};
}

View File

@ -4,7 +4,7 @@ import com.smarthomies.realtimetalk.models.network.AuthenticationResponse;
import com.smarthomies.realtimetalk.models.network.LoginRequest; import com.smarthomies.realtimetalk.models.network.LoginRequest;
import com.smarthomies.realtimetalk.models.network.RegistrationRequest; import com.smarthomies.realtimetalk.models.network.RegistrationRequest;
import com.smarthomies.realtimetalk.network.APIErrorHandler; import com.smarthomies.realtimetalk.network.APIErrorHandler;
import com.smarthomies.realtimetalk.network.RestClient; import com.smarthomies.realtimetalk.network.clients.RestClient;
import com.smarthomies.realtimetalk.network.exceptions.APIException; import com.smarthomies.realtimetalk.network.exceptions.APIException;
import rx.Observable; import rx.Observable;
@ -30,30 +30,24 @@ public class AuthenticationAPIService {
public Observable<AuthenticationResponse> login(LoginRequest request) { public Observable<AuthenticationResponse> login(LoginRequest request) {
return RestClient.getInstance().getAuthenticationAPI().login(request) return RestClient.getInstance().getAuthenticationAPI().login(request)
.doOnError(handleLoginErrors); .doOnError(handleGeneralErrors);
} }
public Observable<AuthenticationResponse> register(RegistrationRequest request) { public Observable<AuthenticationResponse> register(RegistrationRequest request) {
return RestClient.getInstance().getAuthenticationAPI().register(request) return RestClient.getInstance().getAuthenticationAPI().register(request)
.doOnError(handleRegistrationErrors); .doOnError(handleGeneralErrors);
} }
private Action1<Throwable> handleLoginErrors = new Action1<Throwable>() { public Observable<Object> logout() {
@Override return RestClient.getInstance().getAuthenticationAPI().logout()
public void call(Throwable throwable) { .doOnError(handleGeneralErrors);
try { }
APIErrorHandler.handleLoginErrors(throwable);
} catch (APIException apiException) {
throw Exceptions.propagate(apiException);
}
}
};
private Action1<Throwable> handleRegistrationErrors = new Action1<Throwable>() { private Action1<Throwable> handleGeneralErrors = new Action1<Throwable>() {
@Override @Override
public void call(Throwable throwable) { public void call(Throwable throwable) {
try { try {
APIErrorHandler.handleRegistrationErrors(throwable); APIErrorHandler.handleGeneralAPIErrors(throwable);
} catch (APIException apiException) { } catch (APIException apiException) {
throw Exceptions.propagate(apiException); throw Exceptions.propagate(apiException);
} }

View File

@ -1,9 +1,10 @@
package com.smarthomies.realtimetalk.services; package com.smarthomies.realtimetalk.services;
import com.smarthomies.realtimetalk.models.network.ContactRequest;
import com.smarthomies.realtimetalk.models.network.SearchRequest; import com.smarthomies.realtimetalk.models.network.SearchRequest;
import com.smarthomies.realtimetalk.models.network.UsersResponse; import com.smarthomies.realtimetalk.models.network.UsersResponse;
import com.smarthomies.realtimetalk.network.APIErrorHandler; import com.smarthomies.realtimetalk.network.APIErrorHandler;
import com.smarthomies.realtimetalk.network.RestClient; import com.smarthomies.realtimetalk.network.clients.RestClient;
import com.smarthomies.realtimetalk.network.exceptions.APIException; import com.smarthomies.realtimetalk.network.exceptions.APIException;
import rx.Observable; import rx.Observable;
@ -29,14 +30,29 @@ public class ContactsAPIService {
public Observable<UsersResponse> search(SearchRequest request) { public Observable<UsersResponse> search(SearchRequest request) {
return RestClient.getInstance().getContactsAPI().searchUsers(request) return RestClient.getInstance().getContactsAPI().searchUsers(request)
.doOnError(handleSearchErrors); .doOnError(handleApiErrors);
} }
private Action1<Throwable> handleSearchErrors = new Action1<Throwable>() { public Observable<UsersResponse> getContacts() {
return RestClient.getInstance().getContactsAPI().getContacts()
.doOnError(handleApiErrors);
}
public Observable<Object> saveContact(ContactRequest request) {
return RestClient.getInstance().getContactsAPI().saveContact(request)
.doOnError(handleApiErrors);
}
public Observable<Object> deleteContact(ContactRequest request) {
return RestClient.getInstance().getContactsAPI().deleteContact(request)
.doOnError(handleApiErrors);
}
private Action1<Throwable> handleApiErrors = new Action1<Throwable>() {
@Override @Override
public void call(Throwable throwable) { public void call(Throwable throwable) {
try { try {
APIErrorHandler.handleSearchErrors(throwable); APIErrorHandler.handleGeneralAPIErrors(throwable);
} catch (APIException apiException) { } catch (APIException apiException) {
throw Exceptions.propagate(apiException); throw Exceptions.propagate(apiException);
} }

View File

@ -0,0 +1,94 @@
package com.smarthomies.realtimetalk.utils;
import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.views.activities.CallActivity;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.Properties;
public class MediaStreamClient {
boolean isRecording;
int recBufSize;
//ServerSocket sockfd;
Socket connfd;
AudioRecord audioRecord;
private static final String TAG = "MyActivity";
public MediaStreamClient(final Context ctx, final String ip) {
Properties prop = new Properties();
try {
InputStream inputStream = ctx.getResources().openRawResource(R.raw.config);
prop.load(inputStream);
} catch (FileNotFoundException e) {
System.out.println("Can't finde config");
} catch (IOException e) {
System.out.println("Can't load config");
}
final int frequency = Integer.parseInt(prop.getProperty("frequency"));
final int channelConfiguration = Integer.parseInt(prop.getProperty("channal_server"));
final int audioEncoding = Integer.parseInt(prop.getProperty("audio_encoding"));
final int SERVERPORT = Integer.parseInt(prop.getProperty("serverport_server"));
recBufSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
//Log.v(TAG,String.valueOf(AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_CONFIGURATION_MONO , AudioFormat.ENCODING_PCM_16BIT)));
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, recBufSize);
new Thread() {
byte[] buffer = new byte[recBufSize];
public void run() {
try { connfd = new Socket(ip, SERVERPORT); }
catch (Exception e) {
e.printStackTrace();
CallActivity.toast("Can't connect!",ctx);
return;
}
audioRecord.startRecording();
isRecording = true;
while (isRecording) {
int readSize = audioRecord.read(buffer, 0, recBufSize);
try { connfd.getOutputStream().write(buffer, 0, readSize);
}
catch (Exception e) {
e.printStackTrace();
CallActivity.toast("Closed stream by revicer!",ctx);
break;
}
}
audioRecord.stop();
//audioRecord.release();
try { connfd.close(); }
catch (Exception e) {
e.printStackTrace();
CallActivity.toast("Can't close connection!",ctx);
}
}
}.start();
}
public void stop(Context ctx) {
isRecording = false;
/*try { connfd.close(); }
catch (Exception e) {
e.printStackTrace();
MainActivity.toast("Can't close connection!",ctx);
}*/
}
}

View File

@ -0,0 +1,108 @@
package com.smarthomies.realtimetalk.utils;
import android.content.Context;
import android.content.Intent;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.views.activities.CallActivity;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Properties;
public class MediaStreamServer implements Runnable {
boolean isPlaying;
int playBufSize;
Socket connfd;
ServerSocket sockfd;
AudioTrack audioTrack;
private static final String TAG = "MyActivity";
final int SERVERPORT;
Context ctx;
public MediaStreamServer(final Context ctx) {
Properties prop = new Properties();
try {
InputStream inputStream = ctx.getResources().openRawResource(R.raw.config);
prop.load(inputStream);
} catch (FileNotFoundException e) {
System.out.println("Can't finde config");
} catch (IOException e) {
System.out.println("Can't load config");
}
final int frequency = Integer.parseInt(prop.getProperty("frequency"));
final int channelConfiguration = Integer.parseInt(prop.getProperty("channal_client"));
final int audioEncoding = Integer.parseInt(prop.getProperty("audio_encoding"));
SERVERPORT = Integer.parseInt(prop.getProperty("serverport_client"));
playBufSize=AudioTrack.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
audioTrack = new AudioTrack(AudioManager.STREAM_VOICE_CALL, frequency, channelConfiguration, audioEncoding, playBufSize, AudioTrack.MODE_STREAM);
audioTrack.setStereoVolume(1f, 1f);
this.ctx=ctx;
}
public void stop() {
isPlaying = false;
}
public void setVolume(float lvol, float rvol) {
audioTrack.setStereoVolume(lvol, rvol);
}
@Override
public void run() {
byte[] buffer = new byte[playBufSize];
try { sockfd = new ServerSocket(SERVERPORT); }
catch (Exception e) {
e.printStackTrace();
CallActivity.toast("Port unavailable",ctx);
return;
}
while(true) {
try {
connfd = sockfd.accept();
} catch (Exception e) {
e.printStackTrace();
CallActivity.toast("Connection not accepted",ctx);
continue;
}
CallActivity.toast("Connected",ctx);
audioTrack.play();
isPlaying = true;
while (isPlaying) {
int readSize = 0;
try {
readSize = connfd.getInputStream().read(buffer);
} catch (Exception e) {
Log.v(TAG, "palo");
e.printStackTrace();
CallActivity.toast("Closed stream by sender",ctx);
break;
}
audioTrack.write(buffer, 0, readSize);
}
audioTrack.stop();
audioTrack.flush();
try {
connfd.close();
} catch (Exception e) {
e.printStackTrace();
CallActivity.toast("Can't close connection!",ctx);
}
}
}
}

View File

@ -10,6 +10,7 @@ public class RTTAppHelper {
public static final String TAG = RTTAppHelper.class.getSimpleName(); public static final String TAG = RTTAppHelper.class.getSimpleName();
public static final String SHARED_PREFERENCES_USER_TOKEN = "SHARED_PREFERENCES_USER_TOKEN"; public static final String SHARED_PREFERENCES_USER_TOKEN = "SHARED_PREFERENCES_USER_TOKEN";
public static final String SHARED_PREFERENCES_USER_ID = "SHARED_PREFERENCES_USER_ID";
private static RTTAppHelper instance; private static RTTAppHelper instance;
private Context context; private Context context;
@ -34,7 +35,21 @@ public class RTTAppHelper {
} }
public String getToken() { public String getToken() {
return readFromSharedPrefs(SHARED_PREFERENCES_USER_TOKEN); return readFromSharedPrefs(SHARED_PREFERENCES_USER_TOKEN, "");
}
public void saveUserId(int userId) {
writeToSharedPrefs(SHARED_PREFERENCES_USER_ID, userId);
}
public int getUserId() {
return readFromSharedPrefs(SHARED_PREFERENCES_USER_ID, -1);
}
private void writeToSharedPrefs(String key, int value) {
SharedPreferences.Editor editor = context.getSharedPreferences(TAG, Context.MODE_PRIVATE).edit();
editor.putInt(key, value);
editor.apply();
} }
private void writeToSharedPrefs(String key, String value) { private void writeToSharedPrefs(String key, String value) {
@ -52,4 +67,9 @@ public class RTTAppHelper {
return sharedPreferences.getString(key, defaultValue); return sharedPreferences.getString(key, defaultValue);
} }
private int readFromSharedPrefs(String key, int defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences(TAG, Context.MODE_PRIVATE);
return sharedPreferences.getInt(key, defaultValue);
}
} }

View File

@ -4,10 +4,12 @@ import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import com.smarthomies.realtimetalk.R; import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.network.exceptions.APIConflictException;
import com.smarthomies.realtimetalk.network.exceptions.BadAPIRequestException; import com.smarthomies.realtimetalk.network.exceptions.BadAPIRequestException;
import com.smarthomies.realtimetalk.network.exceptions.NetworkException; import com.smarthomies.realtimetalk.network.exceptions.NetworkException;
import com.smarthomies.realtimetalk.network.exceptions.RemoteResourceNotFoundException; import com.smarthomies.realtimetalk.network.exceptions.RemoteResourceNotFoundException;
import com.smarthomies.realtimetalk.network.exceptions.ResourceForbiddenException; import com.smarthomies.realtimetalk.network.exceptions.ResourceForbiddenException;
import com.smarthomies.realtimetalk.network.exceptions.ServerErrorException;
import com.smarthomies.realtimetalk.network.exceptions.UnauthorizedException; import com.smarthomies.realtimetalk.network.exceptions.UnauthorizedException;
/** /**
@ -25,8 +27,12 @@ public class RTTErrorUtil {
return R.string.error_unknown; return R.string.error_unknown;
} else if (e instanceof UnauthorizedException) { } else if (e instanceof UnauthorizedException) {
return R.string.error_user_bad_credentials; return R.string.error_user_bad_credentials;
} else if (e instanceof APIConflictException) {
return R.string.error_user_already_exists;
} else if (e instanceof NetworkException) { } else if (e instanceof NetworkException) {
return R.string.error_no_internet; return R.string.error_no_internet;
} else if (e instanceof ServerErrorException) {
return R.string.error_server;
} }
return 0; return 0;
} }

View File

@ -71,10 +71,9 @@ public class LoginViewModel extends BaseObservable {
return new View.OnClickListener() { return new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// if(validateFields()) { if(validateFields()) {
// loginUser(); loginUser();
// } }
onLoginDone();
} }
}; };
} }
@ -90,6 +89,7 @@ public class LoginViewModel extends BaseObservable {
public void onLoginDone() { public void onLoginDone() {
NavigationSubject.getInstance().onNext(new Pair<Class<? extends RTTActivity>, Bundle>(MainActivity.class, null)); NavigationSubject.getInstance().onNext(new Pair<Class<? extends RTTActivity>, Bundle>(MainActivity.class, null));
NavigationSubject.getInstance().onNext(null);
} }
public void onRequestCompleted() { public void onRequestCompleted() {

View File

@ -2,35 +2,124 @@ package com.smarthomies.realtimetalk.viewmodels;
import android.databinding.BaseObservable; import android.databinding.BaseObservable;
import android.databinding.Bindable; import android.databinding.Bindable;
import android.databinding.ObservableField;
import android.os.Bundle; import android.os.Bundle;
import android.util.Pair; import android.util.Pair;
import android.view.View; import android.view.View;
import com.smarthomies.realtimetalk.RTTActivity; import com.smarthomies.realtimetalk.RTTActivity;
import com.smarthomies.realtimetalk.managers.AccountManager;
import com.smarthomies.realtimetalk.managers.AuthenticationManager;
import com.smarthomies.realtimetalk.managers.ContactsManager;
import com.smarthomies.realtimetalk.models.db.User; import com.smarthomies.realtimetalk.models.db.User;
import com.smarthomies.realtimetalk.models.network.AuthenticationResponse;
import com.smarthomies.realtimetalk.models.network.UserResponse;
import com.smarthomies.realtimetalk.models.network.UsersResponse;
import com.smarthomies.realtimetalk.utils.NavigationSubject; import com.smarthomies.realtimetalk.utils.NavigationSubject;
import com.smarthomies.realtimetalk.views.activities.LoginActivity;
import com.smarthomies.realtimetalk.views.activities.MainActivity; import com.smarthomies.realtimetalk.views.activities.MainActivity;
import com.smarthomies.realtimetalk.views.activities.ProfileActivity;
import com.smarthomies.realtimetalk.views.activities.SearchActivity; import com.smarthomies.realtimetalk.views.activities.SearchActivity;
import java.util.List;
import rx.Observable;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import rx.subjects.AsyncSubject;
import rx.subscriptions.CompositeSubscription;
/** /**
* Created by ensar on 15/11/16. * Created by ensar on 15/11/16.
*/ */
public class MainViewModel extends BaseObservable { public class MainViewModel extends BaseObservable {
public static final String TAG = MainViewModel.class.getSimpleName(); public static final String TAG = MainViewModel.class.getSimpleName();
UserViewModel userViewModel; private AsyncSubject<Object> logoutSubject;
public void setUser(User user) { private CompositeSubscription subscription;
userViewModel = new UserViewModel(user);
private ObservableField<List<User>> contacts = new ObservableField<>();
private ObservableField<UserViewModel> userViewModel = new ObservableField<>();
public MainViewModel() {
logoutSubject = AsyncSubject.create();
subscription = new CompositeSubscription();
userViewModel.set(new UserViewModel());
}
public void loadContacts() {
subscription.add(new ContactsManager().getContacts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<UsersResponse>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(UsersResponse usersResponse) {
contacts.set(usersResponse.getData());
}
}));
}
public void loadProfile() {
subscription.add(new AccountManager().getProfile()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<UserResponse>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(UserResponse userResponse) {
userViewModel.get().setModel(userResponse.getData());
userViewModel.notifyChange();
}
}));
}
public AsyncSubject<Object> createLogoutSubject() {
logoutSubject = AsyncSubject.create();
return logoutSubject;
}
public AsyncSubject<Object> getLogoutSubject() {
return logoutSubject;
}
private void logoutUser() {
new AuthenticationManager().logout().subscribeOn(Schedulers.io()).subscribe(logoutSubject);
}
public ObservableField<List<User>> getContacts() {
return contacts;
}
public void setContacts(ObservableField<List<User>> contacts) {
this.contacts = contacts;
} }
@Bindable @Bindable
public UserViewModel getUserViewModel() { public UserViewModel getUserViewModel() {
return userViewModel; return userViewModel.get();
}
public void setUserViewModel(UserViewModel userViewModel) {
this.userViewModel = userViewModel;
} }
public View.OnClickListener onSearchClick() { public View.OnClickListener onSearchClick() {
@ -41,4 +130,22 @@ public class MainViewModel extends BaseObservable {
} }
}; };
} }
public void onLogoutClick() {
logoutUser();
}
public void onProfileClick() {
NavigationSubject.getInstance().onNext(new Pair<Class<? extends RTTActivity>, Bundle>(ProfileActivity.class, null));
}
public void onLogoutDone() {
NavigationSubject.getInstance().onNext(new Pair<Class<? extends RTTActivity>, Bundle>(LoginActivity.class, null));
}
public void clear() {
if(subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
}
} }

View File

@ -0,0 +1,282 @@
package com.smarthomies.realtimetalk.viewmodels;
import android.databinding.BaseObservable;
import android.databinding.ObservableBoolean;
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.AccountManager;
import com.smarthomies.realtimetalk.managers.AuthenticationManager;
import com.smarthomies.realtimetalk.models.db.User;
import com.smarthomies.realtimetalk.models.network.AuthenticationResponse;
import com.smarthomies.realtimetalk.models.network.UserResponse;
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.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import rx.subjects.AsyncSubject;
import rx.subscriptions.CompositeSubscription;
/**
* Created by ensar on 01/11/16.
*/
public class ProfileViewModel extends BaseObservable {
public static final String TAG = ProfileViewModel.class.getSimpleName();
private ObservableField<String> firstName = new ObservableField<>();
private ObservableField<String> lastName = new ObservableField<>();
private ObservableField<Integer> firstNameError = new ObservableField<>();
private ObservableField<Integer> lastNameError = new ObservableField<>();
private ObservableField<String> oldPassword = new ObservableField<>();
private ObservableField<String> password = new ObservableField<>();
private ObservableField<String> passwordConfirmation = new ObservableField<>();
private ObservableField<Integer> oldPasswordError = new ObservableField<>();
private ObservableField<Integer> passwordError = new ObservableField<>();
private ObservableField<Integer> passwordConfirmationError = new ObservableField<>();
private ObservableField<String> email = new ObservableField<>();
private ObservableField<Integer> emailError = new ObservableField<>();
private ObservableBoolean passwordsVisibility = new ObservableBoolean();
private CompositeSubscription subscription;
public ProfileViewModel() {
subscription = new CompositeSubscription();
}
private boolean validateNames() {
clearNamesErrors();
firstNameError.set(RTTUtil.getRequiredFieldError(firstName.get()));
lastNameError.set(RTTUtil.getRequiredFieldError(lastName.get()));
emailError.set(RTTUtil.getRequiredFieldError(email.get()));
return firstNameError.get() == 0 && lastNameError.get() == 0 && emailError.get() == 0;
}
private boolean validatePassword() {
clearPasswordErrors();
oldPasswordError.set(RTTUtil.getPasswordError(oldPassword.get()));
passwordError.set(RTTUtil.getPasswordError(password.get()));
passwordConfirmationError.set(RTTUtil.getPasswordConfirmationError(password.get(), passwordConfirmation.get()));
return passwordError.get() == 0 && passwordConfirmationError.get() == 0 && oldPasswordError.get() == 0;
}
private void clearNamesErrors() {
firstNameError.set(0);
lastNameError.set(0);
emailError.set(0);
}
private void clearPasswordErrors() {
oldPasswordError.set(0);
passwordError.set(0);
passwordConfirmationError.set(0);
}
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<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<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 ObservableBoolean getPasswordsVisibility() {
return passwordsVisibility;
}
public void setPasswordsVisibility(ObservableBoolean passwordsVisibility) {
this.passwordsVisibility = passwordsVisibility;
}
public ObservableField<String> getOldPassword() {
return oldPassword;
}
public void setOldPassword(ObservableField<String> oldPassword) {
this.oldPassword = oldPassword;
}
public ObservableField<Integer> getOldPasswordError() {
return oldPasswordError;
}
public void setOldPasswordError(ObservableField<Integer> oldPasswordError) {
this.oldPasswordError = oldPasswordError;
}
public ObservableField<String> getEmail() {
return email;
}
public void setEmail(ObservableField<String> email) {
this.email = email;
}
public ObservableField<Integer> getEmailError() {
return emailError;
}
public void setEmailError(ObservableField<Integer> emailError) {
this.emailError = emailError;
}
public void onSaveClicked() {
saveProfile();
}
public void loadProfile() {
subscription.add(new AccountManager().getProfile()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<UserResponse>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(UserResponse userResponse) {
User user = userResponse.getData();
if(user != null) {
firstName.set(user.getFirstName());
lastName.set(user.getLastName());
email.set(user.getEmail());
clearPasswordErrors();
clearNamesErrors();
}
}
}));
}
public void saveProfile() {
if (validateNames()) {
if(passwordsVisibility.get() && validatePassword()) {
subscription.add(new AccountManager().changePassword(oldPassword.get(), password.get())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Object>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Object o) {
}
}));
}
User user = new User();
user.setFirstName(firstName.get());
user.setLastName(lastName.get());
user.setEmail(email.get());
subscription.add(new AccountManager().updateProfile(user)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Object>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Object o) {
if(!passwordsVisibility.get() || validatePassword()) {
NavigationSubject.getInstance().onNext(null);
}
}
}));
}
}
public void clear() {
if(subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
}
}

View File

@ -49,7 +49,7 @@ public class SearchViewModel extends BaseObservable {
}); });
} }
}); });
rxSearch = rxSearch.debounce(1000, TimeUnit.MILLISECONDS); rxSearch = rxSearch.debounce(500, TimeUnit.MILLISECONDS);
subscription = rxSearch.flatMap(new Func1<String, rx.Observable<UsersResponse>>() { subscription = rxSearch.flatMap(new Func1<String, rx.Observable<UsersResponse>>() {
@Override @Override
public rx.Observable<UsersResponse> call(String s) { public rx.Observable<UsersResponse> call(String s) {

View File

@ -1,17 +1,28 @@
package com.smarthomies.realtimetalk.viewmodels; package com.smarthomies.realtimetalk.viewmodels;
import android.content.Intent;
import android.databinding.BaseObservable; import android.databinding.BaseObservable;
import android.databinding.Bindable; import android.databinding.Bindable;
import android.databinding.BindingAdapter; import android.databinding.BindingAdapter;
import android.databinding.ObservableBoolean; import android.databinding.ObservableBoolean;
import android.support.design.widget.Snackbar;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import com.smarthomies.realtimetalk.R; import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.managers.ContactsManager;
import com.smarthomies.realtimetalk.models.db.User; import com.smarthomies.realtimetalk.models.db.User;
import com.smarthomies.realtimetalk.views.activities.CallActivity;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import java.util.concurrent.TimeUnit;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
/** /**
* Created by ensar on 15/11/16. * Created by ensar on 15/11/16.
*/ */
@ -21,10 +32,22 @@ public class UserViewModel extends BaseObservable {
private User model; private User model;
private ObservableBoolean state = new ObservableBoolean(); private ObservableBoolean state = new ObservableBoolean();
public UserViewModel() {
this.model = new User();
}
public UserViewModel(User model) { public UserViewModel(User model) {
this.model = model; this.model = model;
} }
public void setModel(User model) {
this.model = model;
if (model == null) {
this.model = new User();
}
notifyChange();
}
@Bindable @Bindable
public String getName() { public String getName() {
return model.getFirstName() + " " + model.getLastName(); return model.getFirstName() + " " + model.getLastName();
@ -48,8 +71,48 @@ public class UserViewModel extends BaseObservable {
return new View.OnClickListener() { return new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
final Subscription subscription = new ContactsManager()
.saveContact(model)
.delaySubscription(3000, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Object>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
state.set(!state.get());
}
@Override
public void onNext(Object o) {
}
});
Log.d(TAG, "onClick: "); Log.d(TAG, "onClick: ");
state.set(!state.get()); state.set(!state.get());
Snackbar.make(v, model.getFirstName() + " " + model.getLastName() + " added to conctacts.", Snackbar.LENGTH_LONG)
.setDuration(3000)
.setAction("Undo", new View.OnClickListener() {
@Override
public void onClick(View v) {
if(subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
state.set(!state.get());
}
}
}).show();
}
};
}
public View.OnClickListener call() {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
v.getContext().startActivity(new Intent(v.getContext(), CallActivity.class));
} }
}; };
} }

View File

@ -0,0 +1,78 @@
package com.smarthomies.realtimetalk.views.activities;
import android.content.Context;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.utils.MediaStreamClient;
import com.smarthomies.realtimetalk.utils.MediaStreamServer;
public class CallActivity extends AppCompatActivity {
private TextView serverStatus;
private EditText serverIp;
private Button nazovi;
private MediaStreamClient mss;
private MediaStreamServer msc;
// DESIGNATE A PORT
boolean isRecording;
private static Handler handler = new Handler();
Thread t=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_call);
nazovi = (Button) findViewById(R.id.nazovi);
serverIp = (EditText) findViewById(R.id.ipAdress);
serverStatus=(TextView) findViewById(R.id.labela);
nazovi.setOnTouchListener(nazoviL);
isRecording=false;
msc=new MediaStreamServer(CallActivity.this);
t=new Thread(msc);
t.start();
}
private View.OnTouchListener nazoviL=new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
String ip=serverIp.getText().toString();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
msc.stop();
mss=new MediaStreamClient(CallActivity.this,ip);
break;
case MotionEvent.ACTION_UP:
mss.stop(CallActivity.this);
break;
}
return false;
}
};
public static void toast(final String msg, final Context ctx){
handler.post(new Runnable() {
@Override
public void run() {
Context context = ctx;
CharSequence text =msg;
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
}
});
}
}

View File

@ -2,13 +2,11 @@ package com.smarthomies.realtimetalk.views.activities;
import android.content.Intent; import android.content.Intent;
import android.databinding.DataBindingUtil; import android.databinding.DataBindingUtil;
import android.databinding.Observable;
import android.databinding.ObservableField;
import android.os.Bundle; 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.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.support.design.widget.NavigationView; import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat; import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout;
@ -16,25 +14,35 @@ import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.Toast;
import com.smarthomies.realtimetalk.R; import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.RTTActivity; import com.smarthomies.realtimetalk.RTTActivity;
import com.smarthomies.realtimetalk.databinding.ActivityMainBinding; import com.smarthomies.realtimetalk.databinding.ActivityMainBinding;
import com.smarthomies.realtimetalk.databinding.AppBarMainBinding; import com.smarthomies.realtimetalk.databinding.AppBarMainBinding;
import com.smarthomies.realtimetalk.databinding.NavHeaderMainBinding;
import com.smarthomies.realtimetalk.models.db.User; import com.smarthomies.realtimetalk.models.db.User;
import com.smarthomies.realtimetalk.viewmodels.UserViewModel; import com.smarthomies.realtimetalk.models.network.AuthenticationResponse;
import com.smarthomies.realtimetalk.utils.RTTErrorUtil;
import com.smarthomies.realtimetalk.views.adapters.UsersAdapter; import com.smarthomies.realtimetalk.views.adapters.UsersAdapter;
import com.smarthomies.realtimetalk.views.fragments.MainViewModelHolder; import com.smarthomies.realtimetalk.views.fragments.MainViewModelHolder;
import com.smarthomies.realtimetalk.views.fragments.registration.RegistrationViewModelHolder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.exceptions.CompositeException;
import rx.schedulers.Schedulers;
public class MainActivity extends RTTActivity public class MainActivity extends RTTActivity
implements NavigationView.OnNavigationItemSelectedListener { implements NavigationView.OnNavigationItemSelectedListener {
private MainViewModelHolder viewModelHolder; private MainViewModelHolder viewModelHolder;
private Subscription logoutSubscription;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -54,24 +62,25 @@ public class MainActivity extends RTTActivity
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
AppBarMainBinding appBarMainBinding = binding.appBarMain; 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()); binding.setViewModel(viewModelHolder.getMainViewModel());
appBarMainBinding.setViewModel(viewModelHolder.getMainViewModel()); appBarMainBinding.setViewModel(viewModelHolder.getMainViewModel());
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rvUsersList); viewModelHolder.getMainViewModel().loadContacts();
viewModelHolder.getMainViewModel().loadProfile();
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rvUsersList);
recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setLayoutManager(new LinearLayoutManager(this));
UsersAdapter usersAdapter = new UsersAdapter(UsersAdapter.ViewType.LIST); viewModelHolder.getMainViewModel().getContacts().addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
ArrayList<User> users = new ArrayList<User>(); @Override
users.add(user); public void onPropertyChanged(Observable observable, int i) {
usersAdapter.setUsers(users); List<User> users = ((ObservableField<List<User>>)observable).get();
UsersAdapter usersAdapter = new UsersAdapter(UsersAdapter.ViewType.LIST);
recyclerView.setAdapter(usersAdapter); usersAdapter.setUsers(users);
recyclerView.setAdapter(usersAdapter);
}
});
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
@ -87,6 +96,59 @@ public class MainActivity extends RTTActivity
binding.navView.setNavigationItemSelectedListener(this); binding.navView.setNavigationItemSelectedListener(this);
} }
@Override
protected void subscribeToSubjects() {
super.subscribeToSubjects();
logoutSubscription = getViewModelHolder().getMainViewModel().getLogoutSubject().observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(new LogoutSubscriber());
}
@Override
protected void unsubscribeFromSubjects() {
super.unsubscribeFromSubjects();
if(logoutSubscription != null && !logoutSubscription.isUnsubscribed()) {
logoutSubscription.unsubscribe();
}
}
private void reconnectToLogoutSubject() {
logoutSubscription = getViewModelHolder().getMainViewModel().createLogoutSubject().observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(new LogoutSubscriber());
}
@Override
protected void onResume() {
super.onResume();
}
private class LogoutSubscriber extends Subscriber<Object> {
@Override
public void onCompleted() {
reconnectToLogoutSubject();
getViewModelHolder().getMainViewModel().onLogoutDone();
}
@Override
public void onError(Throwable e) {
reconnectToLogoutSubject();
getViewModelHolder().getMainViewModel().onLogoutDone();
if (e instanceof CompositeException) {
for (Throwable ex : ((CompositeException) e).getExceptions()) {
if(ex instanceof RuntimeException) {
handleException(ex.getCause());
}
}
} else {
handleException(e);
}
}
@Override
public void onNext(Object object) {
getViewModelHolder().getMainViewModel().onLogoutDone();
}
}
@Override @Override
public void onBackPressed() { public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
@ -127,8 +189,11 @@ public class MainActivity extends RTTActivity
if (id == R.id.nav_logout) { if (id == R.id.nav_logout) {
// Handle the camera action // Handle the camera action
startActivity(new Intent(this, LoginActivity.class)); getViewModelHolder().getMainViewModel().onLogoutClick();
finish(); }
if (id == R.id.nav_profile) {
getViewModelHolder().getMainViewModel().onProfileClick();
} }
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
@ -140,4 +205,8 @@ public class MainActivity extends RTTActivity
return viewModelHolder; return viewModelHolder;
} }
private void handleException(Throwable e) {
Toast.makeText(this, RTTErrorUtil.getErrorString(e), Toast.LENGTH_SHORT).show();
}
} }

View File

@ -0,0 +1,97 @@
package com.smarthomies.realtimetalk.views.activities;
import android.app.SearchManager;
import android.databinding.DataBindingUtil;
import android.databinding.Observable;
import android.databinding.ObservableField;
import android.os.Bundle;
import android.support.v4.view.MenuItemCompat;
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 com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.RTTActivity;
import com.smarthomies.realtimetalk.databinding.ActivityProfileBinding;
import com.smarthomies.realtimetalk.databinding.ActivitySearchBinding;
import com.smarthomies.realtimetalk.models.db.User;
import com.smarthomies.realtimetalk.viewmodels.SearchViewModel;
import com.smarthomies.realtimetalk.views.activities.bindingutils.OnErrorChangedCallback;
import com.smarthomies.realtimetalk.views.adapters.UsersAdapter;
import com.smarthomies.realtimetalk.views.fragments.ProfileViewModelHolder;
import com.smarthomies.realtimetalk.views.fragments.SearchViewModelHolder;
import java.util.List;
public class ProfileActivity extends RTTActivity {
private ProfileViewModelHolder viewModelHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Add retained to fragment manager
if(getSupportFragmentManager().findFragmentByTag(ProfileViewModelHolder.class.getName()) == null) {
viewModelHolder = new ProfileViewModelHolder();
getSupportFragmentManager()
.beginTransaction()
.add(viewModelHolder, SearchViewModelHolder.class.getName())
.commit();
}
else {
viewModelHolder = (ProfileViewModelHolder)getSupportFragmentManager().findFragmentByTag(ProfileViewModelHolder.class.getName());
}
ActivityProfileBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_profile);
binding.setViewModel(viewModelHolder.getProfileViewModel());
getViewModelHolder().getProfileViewModel().getFirstNameError().addOnPropertyChangedCallback(new OnErrorChangedCallback(binding.tilFirstName));
getViewModelHolder().getProfileViewModel().getLastNameError().addOnPropertyChangedCallback(new OnErrorChangedCallback(binding.tilLastName));
getViewModelHolder().getProfileViewModel().getEmailError().addOnPropertyChangedCallback(new OnErrorChangedCallback(binding.tilEmail));
getViewModelHolder().getProfileViewModel().getOldPasswordError().addOnPropertyChangedCallback(new OnErrorChangedCallback(binding.tilCurrentPassword));
getViewModelHolder().getProfileViewModel().getPasswordError().addOnPropertyChangedCallback(new OnErrorChangedCallback(binding.tilPassword));
getViewModelHolder().getProfileViewModel().getPasswordConfirmationError().addOnPropertyChangedCallback(new OnErrorChangedCallback(binding.tilConfirmPassword));
viewModelHolder.getProfileViewModel().loadProfile();
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.profile, 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_save) {
getViewModelHolder().getProfileViewModel().onSaveClicked();
return true;
}
return super.onOptionsItemSelected(item);
}
public ProfileViewModelHolder getViewModelHolder() {
return viewModelHolder;
}
}

View File

@ -0,0 +1,38 @@
package com.smarthomies.realtimetalk.views.activities;
import android.annotation.SuppressLint;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.RTTActivity;
import com.smarthomies.realtimetalk.utils.NavigationSubject;
import com.smarthomies.realtimetalk.utils.RTTAppHelper;
public class SplashScreenActivity extends RTTActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash_screen);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if(TextUtils.isEmpty(RTTAppHelper.getInstance().getToken())) {
NavigationSubject.getInstance().onNext(new Pair<Class<? extends RTTActivity>, Bundle>(LoginActivity.class, null));
} else {
NavigationSubject.getInstance().onNext(new Pair<Class<? extends RTTActivity>, Bundle>(MainActivity.class, null));
}
NavigationSubject.getInstance().onNext(null);
}
}, 3000);
}
}

View File

@ -1,9 +1,15 @@
package com.smarthomies.realtimetalk.views.activities.bindingutils; package com.smarthomies.realtimetalk.views.activities.bindingutils;
import android.app.Activity;
import android.databinding.BindingAdapter; import android.databinding.BindingAdapter;
import android.support.design.widget.NavigationView; import android.support.design.widget.NavigationView;
import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView;
import com.smarthomies.realtimetalk.R; import com.smarthomies.realtimetalk.R;
import com.smarthomies.realtimetalk.viewmodels.UserViewModel; import com.smarthomies.realtimetalk.viewmodels.UserViewModel;
@ -27,8 +33,27 @@ public class BindingAdapters {
NavHeaderMainBinding navHeaderMainBinding = NavHeaderMainBinding.inflate(LayoutInflater.from(view.getContext())); NavHeaderMainBinding navHeaderMainBinding = NavHeaderMainBinding.inflate(LayoutInflater.from(view.getContext()));
navHeaderMainBinding.setViewModel(userViewModel); navHeaderMainBinding.setViewModel(userViewModel);
navHeaderMainBinding.executePendingBindings(); navHeaderMainBinding.executePendingBindings();
for(int i = 0; i < view.getHeaderCount(); i++) {
view.removeHeaderView(view.getHeaderView(i));
}
view.addHeaderView(navHeaderMainBinding.getRoot()); view.addHeaderView(navHeaderMainBinding.getRoot());
} }
@BindingAdapter("android:imeActionId")
public static void setActionId(EditText view, int id) {
if (view.getContext() instanceof Activity) {
final View actionView = ((Activity) view.getContext()).findViewById(id);
view.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if(actionId == EditorInfo.IME_ACTION_DONE) {
actionView.performClick();
}
return false;
}
});
}
}
} }

View File

@ -17,4 +17,10 @@ public class MainViewModelHolder extends ViewModelHolder {
public void setMainViewModel(MainViewModel mainViewModel) { public void setMainViewModel(MainViewModel mainViewModel) {
this.mainViewModel = mainViewModel; this.mainViewModel = mainViewModel;
} }
@Override
public void onDestroy() {
super.onDestroy();
mainViewModel.clear();
}
} }

View File

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

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<size android:width="300dp"
android:height="300dp"/>
<solid android:color="@color/colorAccent"/>
</shape>
</item>
</selector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/clicked_rounde_shape" android:state_pressed="true"/>
<item android:drawable="@drawable/round_shape"/>
<item android:drawable="@drawable/round_shape" android:state_pressed="false"/>
</selector>

View File

@ -0,0 +1,9 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<size android:width="300dp"
android:height="300dp"/>
<solid android:color="#01579B"/>
</shape>
</item>
</selector>

View File

@ -2,8 +2,8 @@
android:shape="rectangle"> android:shape="rectangle">
<gradient <gradient
android:angle="135" android:angle="135"
android:centerColor="#4CAF50" android:centerColor="@color/colorPrimaryDark"
android:endColor="#2E7D32" android:endColor="@color/colorPrimary"
android:startColor="#81C784" android:startColor="@color/colorPrimary"
android:type="linear" /> android:type="linear" />
</shape> </shape>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<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: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"
android:clickable="false">
<Button
android:id="@+id/nazovi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nazovi"
android:textSize="30sp"
android:background="@drawable/mjenjanje"
android:textColor="@color/white"
android:clickable="true"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/colorAccent"
android:text="@string/ipAdresa"
android:id="@+id/labela"
android:layout_alignParentTop="true"
android:layout_alignLeft="@id/nazovi" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FFFFFF"
android:id="@+id/ipAdress"
android:layout_below="@+id/labela"
android:layout_alignLeft="@+id/labela"
android:layout_alignStart="@+id/labela"
android:layout_marginTop="41dp"
android:layout_alignRight="@+id/nazovi"
android:layout_alignEnd="@+id/nazovi" />
</RelativeLayout>

View File

@ -0,0 +1,220 @@
<?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.ProfileViewModel"
/>
<import type="android.view.View" />
</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>
<ScrollView
android:layout_marginTop="?attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
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:animateLayoutChanges="true">
<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>
<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>
<CheckBox
android:layout_marginTop="16dp"
android:id="@+id/passwordChange"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Check to edit password too"
android:textColor="@color/colorAccent"
android:checked="@={viewModel.passwordsVisibility}"/>
<LinearLayout
android:id="@+id/llPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="@{viewModel.passwordsVisibility ? View.VISIBLE : View.GONE}"
android:animateLayoutChanges="true">
<android.support.design.widget.TextInputLayout
android:id="@+id/tilCurrentPassword"
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/tietCurrentPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt.old_password"
android:inputType="textPassword"
android:maxLines="1"
android:textColor="@color/white"
android:textColorHint="@color/white.50"
android:textSize="16sp"
android:text="@={viewModel.oldPassword}"/>
</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:passwordToggleEnabled="true"
app:passwordToggleTint="@color/white.50"
app:counterTextAppearance="@color/colorAccent"
android:layout_marginTop="16dp">
<android.support.design.widget.TextInputEditText
android:id="@+id/tietPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt.new_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>
</LinearLayout>
</LinearLayout>
</ScrollView>
</android.support.design.widget.CoordinatorLayout>
</layout>

View File

@ -0,0 +1,15 @@
<FrameLayout 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:background="@color/colorPrimary"
tools:context="com.smarthomies.realtimetalk.views.activities.SplashScreenActivity">
<ImageView
android:id="@+id/ivLogo"
android:src="@mipmap/ic_launcher"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center"/>
</FrameLayout>

View File

@ -12,7 +12,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"> android:layout_marginBottom="8dp"
android:onClick="@{viewModel.call()}">
<ImageView <ImageView
android:id="@+id/ivUserImage" android:id="@+id/ivUserImage"
android:layout_width="40dp" android:layout_width="40dp"

View File

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

View File

@ -0,0 +1,10 @@
<?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_save"
android:title="@string/title.save"
app:title="@string/title.save"
android:icon="@android:drawable/ic_menu_save"
app:showAsAction="always" />
</menu>

View File

@ -0,0 +1,6 @@
frequency = 44100
serverport_client = 8087
serverport_server = 8083
audio_encoding=2
channal_client=4
channal_server=2

View File

@ -0,0 +1,12 @@
<resources>
<!-- Declare custom theme attributes that allow changing which styles are
used for button bars depending on the API level.
?android:attr/buttonBarStyle is new as of API 11 so this is
necessary to support previous API levels. -->
<declare-styleable name="ButtonBarContainerTheme">
<attr name="metaButtonBarStyle" format="reference" />
<attr name="metaButtonBarButtonStyle" format="reference" />
</declare-styleable>
</resources>

View File

@ -13,4 +13,6 @@
<color name="text.hint">@color/white.50</color> <color name="text.hint">@color/white.50</color>
<color name="text">@color/white</color> <color name="text">@color/white</color>
<color name="black_overlay">#66000000</color>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="title.introduction">Tell us who you are</string> <string name="title.introduction">Tell us who you are</string>
<string name="title.credentials">Almost there</string> <string name="title.credentials">Almost there</string>
<string name="title.search">Search</string> <string name="title.search">Search</string>
<string name="title.save">Save</string>
<string name="subtitle.welcome">Input your login details</string> <string name="subtitle.welcome">Input your login details</string>
<string name="subtitle.introduction">Input your full name</string> <string name="subtitle.introduction">Input your full name</string>
@ -18,6 +19,8 @@
<!-- Strings related to login --> <!-- Strings related to login -->
<string name="prompt.username">Username</string> <string name="prompt.username">Username</string>
<string name="prompt.password">Password</string> <string name="prompt.password">Password</string>
<string name="prompt.old_password">Old Password</string>
<string name="prompt.new_password">New Password</string>
<string name="prompt.password_confirm">Confirm Password</string> <string name="prompt.password_confirm">Confirm Password</string>
<string name="prompt.email">Email</string> <string name="prompt.email">Email</string>
<string name="prompt.first_name">First Name</string> <string name="prompt.first_name">First Name</string>
@ -30,6 +33,8 @@
<string name="action.forgot_password">Forgot username or password?</string> <string name="action.forgot_password">Forgot username or password?</string>
<string name="action.next">Next</string> <string name="action.next">Next</string>
<string name="action.create_account">Create Account</string> <string name="action.create_account">Create Account</string>
<string name="action.logout">Logout</string>
<string name="action.profile">Profile</string>
<!-- Login errors --> <!-- Login errors -->
<string name="error.email.invalid">Email address is invalid</string> <string name="error.email.invalid">Email address is invalid</string>
@ -43,6 +48,14 @@
<string name="error.bad_request">Bad request, blame Dino Dizdarevic!</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.not_found">User not found!</string>
<string name="error.user.bad_credentials">Please check your username and password!</string> <string name="error.user.bad_credentials">Please check your username and password!</string>
<string name="error.user.already_exists">User with same username already exists!</string>
<string name="error.server">Service is currently unavailable!</string>
<string name="error.unknown">Unknown error!</string> <string name="error.unknown">Unknown error!</string>
<string name="title_activity_splash_screen">SplashScreenActivity</string>
<string name="dummy_button">Dummy Button</string>
<string name="dummy_content">DUMMY\nCONTENT</string>
<string name="nazovi">Nazovi</string>
<string name="ipAdresa">Ip adresa</string>
</resources> </resources>

View File

@ -19,4 +19,16 @@
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
<style name="FullscreenTheme" parent="AppTheme">
<item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item>
<item name="android:windowActionBarOverlay">true</item>
<item name="android:windowBackground">@null</item>
<item name="metaButtonBarStyle">?android:attr/buttonBarStyle</item>
<item name="metaButtonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
</style>
<style name="FullscreenActionBarStyle" parent="Widget.AppCompat.ActionBar">
<item name="android:background">@color/black_overlay</item>
</style>
</resources> </resources>

View File

@ -6,6 +6,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.2.0' classpath 'com.android.tools.build:gradle:2.2.0'
classpath "io.realm:realm-gradle-plugin:2.2.1"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files