diff --git a/BeWoPlanerChat/bewoplaner-chat-android/.gitignore b/BeWoPlanerChat/bewoplaner-chat-android/.gitignore new file mode 100644 index 0000000..30bf41e --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +.DS_Store +/.idea +/build +/captures +.externalNativeBuild diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/.gitignore b/BeWoPlanerChat/bewoplaner-chat-android/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/app-release.apk b/BeWoPlanerChat/bewoplaner-chat-android/app/app-release.apk new file mode 100644 index 0000000..59c1829 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/app-release.apk differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/build.gradle b/BeWoPlanerChat/bewoplaner-chat-android/app/build.gradle new file mode 100644 index 0000000..7655f00 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/build.gradle @@ -0,0 +1,142 @@ +def getGitSha() { + return 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim(); +} + +buildscript { + repositories { + mavenCentral() + maven { url 'https://maven.fabric.io/public' } + } + dependencies { + classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' + classpath 'io.fabric.tools:gradle:1.+' + } +} + +apply plugin: 'com.android.application' +apply plugin: 'io.fabric' + +repositories { + maven { url 'https://maven.fabric.io/public' } + maven { url "https://dl.bintray.com/hani-momanii/maven" } + maven { url "https://jitpack.io" } +} + +apply plugin: 'com.neenbedankt.android-apt' +apply plugin: 'me.tatarka.retrolambda' +apply plugin: 'realm-android' + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.2" + defaultConfig { + applicationId "com.p3.bewoplanner" + minSdkVersion 16 + targetSdkVersion 25 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + signingConfigs { + release { + storeFile file('../keystore.jks') + storePassword 'Qva/F637QEDfM)Z]' + keyAlias 'bewoplaner-chat' + keyPassword 'q7vWk(V&R6+NVjvg' + } + } + + buildTypes { + debug { + ext.enableCrashlytics = false + } + release { + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + buildConfigField "String", "GIT_VERSION_SHA", "\"" + getGitSha() + "\"" + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + lintOptions { + abortOnError false + disable 'InvalidPackage' + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + testCompile 'junit:junit:4.12' + + // Android support libraries + compile 'com.android.support:appcompat-v7:25.3.0' + compile 'com.android.support:design:25.3.0' + + // Butter Knife + compile 'com.jakewharton:butterknife:8.4.0' + apt 'com.jakewharton:butterknife-compiler:8.4.0' + + // Dagger + compile 'com.google.dagger:dagger:2.7' + apt 'com.google.dagger:dagger-compiler:2.7' + provided 'javax.annotation:jsr250-api:1.0' + + // Rx Java + compile 'io.reactivex:rxjava:1.1.6' + compile 'io.reactivex:rxandroid:1.2.1' + compile 'com.jakewharton.rxbinding:rxbinding:0.4.0' + + // Retrofit + compile 'com.squareup.retrofit2:retrofit:2.1.0' + compile 'com.squareup.retrofit2:converter-gson:2.1.0' + compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' + + // OkHttp + compile 'com.squareup.okhttp3:okhttp:3.4.1' + compile 'com.squareup.okhttp3:logging-interceptor:3.4.1' + + // Glide + compile 'com.github.bumptech.glide:glide:3.7.0' + + // Circle ImageView + compile 'de.hdodenhof:circleimageview:2.1.0' + compile('com.crashlytics.sdk.android:crashlytics:2.6.6@aar') { + transitive = true; + } + + // Secure Preferences + compile 'com.scottyab:secure-preferences-lib:0.1.4' + + // Firebase + compile 'com.google.firebase:firebase-messaging:10.2.0' + + // emojis + compile 'hani.momanii.supernova_emoji_library:supernova-emoji-library:0.0.2' + + // circle progress + compile 'com.github.lzyzsd:circleprogress:1.2.1' + + // event bus + compile 'org.greenrobot:eventbus:3.0.0' +} + +task adp2upload(type: Exec, dependsOn: "build") { + + ext { + channel = "bewoplaner-chat" + slug = "bewoplaner-chat" + versionCode = rootProject.versionCode + identifier = "com.p3.bewoplaner" + file = "build/outputs/apk/app-release.apk" + notes = "Nightly build for BeWoPlaner Chat" + } + commandLine 'upload_build.sh', "-c", "$channel", "-v", "$versionCode", "-p", "android", "-a", "$slug", "-f", "$file", "-i", "$identifier", "-n", "$notes" +} +apply plugin: 'com.google.gms.google-services' \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/fabric.properties b/BeWoPlanerChat/bewoplaner-chat-android/app/fabric.properties new file mode 100644 index 0000000..2047148 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/fabric.properties @@ -0,0 +1,3 @@ +#Contains API Secret used to validate your application. Commit to internal source control; avoid making secret public. +#Fri Feb 24 13:59:15 EET 2017 +apiSecret=29cdc918e050df28723e54a0bcd505ed7475be554b1ef8c4f8a30800f3e89a89 diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/google-services.json b/BeWoPlanerChat/bewoplaner-chat-android/app/google-services.json new file mode 100644 index 0000000..1efb1f0 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/google-services.json @@ -0,0 +1,63 @@ +{ + "project_info": { + "project_number": "1021949586645", + "firebase_url": "https://bewoplaner-chat-android.firebaseio.com", + "project_id": "bewoplaner-chat-android", + "storage_bucket": "bewoplaner-chat-android.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:1021949586645:android:113c5b5fc97fb0bb", + "android_client_info": { + "package_name": "com.p3.bewoplanner" + } + }, + "oauth_client": [ + { + "client_id": "1021949586645-vnidi5tjof2kgsal6qgqrbokel0q0hvi.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.p3.bewoplanner", + "certificate_hash": "349feb470feaf535c1074c91721714ced3d81f0c" + } + }, + { + "client_id": "1021949586645-ttle1pv465pp7gdigqh93futan4cmn8p.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.p3.bewoplanner", + "certificate_hash": "458abe7a8928c32f3ad3fbcc575373b613070aae" + } + }, + { + "client_id": "1021949586645-lu3hc8hjsul10dhpj22o3k6vfcg6ol9v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyAh9aJq_Hxjp9z9_Q3h5qNUg-l_Z4Grr98" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 2, + "other_platform_oauth_client": [ + { + "client_id": "1021949586645-lu3hc8hjsul10dhpj22o3k6vfcg6ol9v.apps.googleusercontent.com", + "client_type": 3 + } + ] + }, + "ads_service": { + "status": 2 + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/proguard-rules.pro b/BeWoPlanerChat/bewoplaner-chat-android/app/proguard-rules.pro new file mode 100644 index 0000000..c9b08be --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/proguard-rules.pro @@ -0,0 +1,111 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/rerash/Documents/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +-keepattributes EnclosingMethod + +### retrofit +-dontwarn retrofit2.** +-keep class retrofit2.** { *; } +-keepattributes Signature +-keepattributes Exceptions + +# Okio +-keep class sun.misc.Unsafe { *; } +-dontwarn java.nio.file.* +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement +-dontwarn okio.** + +# OkHttp +-keepattributes Signature +-keepattributes *Annotation* +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +### butterknife +-keep class butterknife.** { *; } +-dontwarn butterknife.compiler.** +-keep class **$$ViewBinder { *; } + +-keepclasseswithmembernames class * { + @butterknife.* ; +} + +-keepclasseswithmembernames class * { + @butterknife.* ; +} + +# Gson uses generic type information stored in a class file when working with fields. Proguard +# removes such information by default, so configure it to keep all of it. +-keepattributes Signature + +# For using GSON @Expose annotation +-keepattributes *Annotation* + +-keepattributes EnclosingMethod + +# Gson specific classes +-keep class sun.misc.Unsafe { *; } +-keep class com.google.gson.stream.** { *; } + +# For Crashlytics +-keepattributes SourceFile,LineNumberTable +-keep class com.crashlytics.** { *; } +-dontwarn com.crashlytics.** + +# For RxAndroid +-dontwarn rx.internal.util.unsafe.** +-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { + long producerIndex; + long consumerIndex; +} +-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { + long producerNode; + long consumerNode; +} + +# For retrolambda +-dontwarn java.lang.invoke.* + +# auto +-dontwarn com.google.auto.** +-dontwarn com.squareup.javapoet.** +-dontwarn com.google.common.** + +# EventBus +-keepattributes *Annotation* +-keepclassmembers class ** { + @org.greenrobot.eventbus.Subscribe ; +} +-keep enum org.greenrobot.eventbus.ThreadMode { *; } + +# Glide +-keep public class * implements com.bumptech.glide.module.GlideModule +-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { + **[] $VALUES; + public *; +} +-keep class com.bumptech.glide.integration.okhttp3.OkHttpGlideModule + +# Realm +-keep class io.realm.annotations.RealmModule +-keep @io.realm.annotations.RealmModule class * +-keep class io.realm.internal.Keep +-keep @io.realm.internal.Keep class * +-dontwarn javax.** +-dontwarn io.realm.** \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/androidTest/java/com/p3/bewoplaner/ExampleInstrumentedTest.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/androidTest/java/com/p3/bewoplaner/ExampleInstrumentedTest.java new file mode 100644 index 0000000..2dbe9bd --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/androidTest/java/com/p3/bewoplaner/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.p3.bewoplaner; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.p3.bewoplanner", appContext.getPackageName()); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/AndroidManifest.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f0c4cb6 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/AndroidManifest.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/assets/impressum.html b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/assets/impressum.html new file mode 100644 index 0000000..cef665e --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/assets/impressum.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + +
+

beyondSoft GmbH | Hochstadenstr. 1-3 | 50674 Köln
fon +49 221 606052-0
fax +49 221 606052-29
info@beyondsoft.de
 
Geschäftsführung | Mike Gahn
Christian Bäurle
Markus Bäurle
Kojo Bauer

Handelsregister | Amtsgericht Köln, HRB 36720

Steuernummer | DE214315054

Haftungsrechtliche Hinweise
beyondSoft prüft und aktualisiert die Informationen auf ihren Webseiten ständig. Trotz aller Sorgfalt können sich Daten und Informationen jeglicher Art inzwischen verändert haben. Eine Haftung, Garantie oder sonstiges Einstehen für die Aktualität, Richtigkeit und Vollständigkeit der zur Verfügung gestellten Informationen kann daher nicht übernommen werden. Gleiches gilt auch für alle Webseiten, auf die direkt mittels Hyperlink oder in sonstiger Weise verwiesen wird. BeyondSoft GmbH ist für den Inhalt der Webseiten, die aufgrund einer solchen Verbindung oder in sonstiger Weise erreicht werden, nicht verantwortlich. BeyondSoft GmbH übernimmt ausdrücklich keine Haftung – gleich welcher Art. Dies gilt insbesondere für die Vertragshaftung, Deliktshaftung, Gefährdungshaftung oder sonstige Haftung für direkten oder indirekten Schadensersatz, Ersatz des beiläufig entstandenen Schadens, Strafen einschließlich Schadensersatz oder konkrete Schäden, die daraus resultieren oder in Zusammenhang damit stehen, dass Seiten der BeyondSoft GmbH aufgerufen, benutzt oder nicht benutzt werden können. Dies gilt auch bei einem Leistungsausfall, einer Unterbrechung, einem Defekt, einer Übertragungsverzögerung, einem Computervirus oder sonstigen schädlichen Elementen oder einem Leitungs- und Systemausfall in Zusammenhang mit den Webseiten der BeyondSoft GmbH, unabhängig davon, ob BeyondSoft GmbH sich der Möglichkeiten solcher Schäden bewusst war oder nicht. Des Weiteren behält sich BeyondSoft GmbH das Recht vor, jederzeit Änderungen oder Ergänzungen der bereitgestellten Informationen vorzunehmen.

Urheberrechtshinweise
Alle Texte, Bilder, Grafiken sowie das Layout unserer Webseiten unterliegen weltweitem Urheberrecht. Die unerlaubte Verwendung, Reproduktion oder Weitergabe einzelner Inhalte oder vollständiger Seiten werden sowohl straf- als auch zivilrechtlich verfolgt. Dabei sind nach deutschem Zivilrecht Unterlassung und Schadenersatz, Überlassung oder Vernichtung der Vervielfältigungsvorrichtungen sowie die öffentliche Bekanntmachung des Urteils gegen den Urheberrechtsverletzer möglich. Unterlassungsansprüche werden vom Gericht mit Ordnungsgeldern bis zu Euro 250.000,00 oder Ordnungshaft bis zu sechs Monaten abgesichert. Bei strafrechtlicher Verfolgung drohen dem Verletzer im Einzelfall Freiheitsstrafen bis zu fünf Jahren. Die Rechtsverfolgung nach den Gesetzgebungen anderer Länder wird hiermit angedroht und kann noch schärfere Konsequenzen nach sich ziehen. Die Zitierung von Inhalten ist gem. § 51 UrhG nur zulässig, soweit sie zur Erläuterung eigener Ausführungen dient und diese nicht ersetzt. Bei Zitaten ist gem. § 63 UrhG als Quelle unser Impressum anzugeben.

+ \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/BewoplanerApplication.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/BewoplanerApplication.java new file mode 100644 index 0000000..0413438 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/BewoplanerApplication.java @@ -0,0 +1,51 @@ +package com.p3.bewoplaner; + +import com.crashlytics.android.Crashlytics; +import com.p3.bewoplaner.data.firebase.MessagingInstanceIdService; +import com.p3.bewoplaner.di.ApplicationComponent; +import com.p3.bewoplaner.di.ApplicationModule; +import com.p3.bewoplaner.di.DaggerApplicationComponent; +import com.p3.bewoplaner.di.InjectionHelper; + +import android.app.Application; + +import io.fabric.sdk.android.Fabric; +import io.realm.Realm; + +/** + * Created by imarneanu on 2/9/17. + */ +public class BewoplanerApplication extends Application { + + private ApplicationComponent mApplicationComponent; + + @Override + public void onCreate() { + super.onCreate(); + Realm.init(this); + if (!BuildConfig.DEBUG) { + Fabric.with(this, new Crashlytics()); + } + buildGraphAndInject(); + MessagingInstanceIdService.setApplicationContext(this); + } + + private void buildGraphAndInject() { + final ApplicationModule applicationModule = new ApplicationModule(this); + mApplicationComponent = DaggerApplicationComponent.builder() + .applicationModule(applicationModule) + .build(); + mApplicationComponent.inject(this); + } + + public ApplicationComponent getApplicationComponent() { + return mApplicationComponent; + } + + public void resetApplicationComponent() { + mApplicationComponent = null; + InjectionHelper.destroyMainComponents(); + buildGraphAndInject(); + } + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/AppPrefsConstants.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/AppPrefsConstants.java new file mode 100644 index 0000000..7aad374 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/AppPrefsConstants.java @@ -0,0 +1,18 @@ +package com.p3.bewoplaner.data; + +/** + * Created by imarneanu on 2/10/17. + */ +public interface AppPrefsConstants { + String LOGIN_TOKEN = "login_token"; + String SAVE_PASSWORD = "save_password"; + String API_ENDPOINT = "api_endpoint"; + String CHAT_CODE = "chat_code"; + String ACCOUNT_NO = "account_no"; + String USER_ID = "user_id"; + String AVATAR = "avatar"; + String NAME = "name"; + String PHONE_NUMBER = "phone_number"; + String CHAT_OPENED = "chat_opened"; + String APP_KILLED = "app_killed"; +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/ApiModule.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/ApiModule.java new file mode 100644 index 0000000..42644a7 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/ApiModule.java @@ -0,0 +1,154 @@ +package com.p3.bewoplaner.data.api; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import com.p3.bewoplaner.BewoplanerApplication; +import com.p3.bewoplaner.BuildConfig; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.prefs.StringPreference; +import com.p3.bewoplaner.di.P3DSEndpoint; +import com.p3.bewoplaner.di.VoucherEndpoint; +import com.p3.bewoplaner.di.scopes.ApplicationScope; +import com.p3.bewoplaner.utils.Constants; + +import java.io.File; +import java.util.concurrent.TimeUnit; + +import javax.inject.Named; + +import dagger.Module; +import dagger.Provides; +import okhttp3.Cache; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.CallAdapter; +import retrofit2.Converter; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; +import rx.schedulers.Schedulers; + +/** + * Created by laszloffiferenc on 2/21/17. + */ +@Module +public class ApiModule { + + @Provides + @ApplicationScope + Cache provideCache(BewoplanerApplication app) { + File httpCacheDirectory = new File(app.getCacheDir().getAbsolutePath(), "HttpCache"); + return new Cache(httpCacheDirectory, Constants.DISK_CACHE_SIZE); + } + + @Provides + @ApplicationScope + HttpLoggingInterceptor provideLoggingInterceptor() { + HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); + if (BuildConfig.DEBUG) { + interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); + } else { + interceptor.setLevel(HttpLoggingInterceptor.Level.NONE); + } + return interceptor; + } + + @Provides + @ApplicationScope + Interceptor provideHeaderInterceptor(@Named(AppPrefsConstants.LOGIN_TOKEN) final StringPreference token, @Named(AppPrefsConstants.ACCOUNT_NO) final StringPreference customerId) { + return chain -> { + final boolean isHeaderSet = token.isSet() || customerId.isSet(); + Request original = chain.request(); + Request.Builder builder = original.newBuilder(); + if (token.isSet()) { + builder.addHeader("Token", token.get()); + } + if (customerId.isSet()) { + builder.addHeader("CustomerID", customerId.get()); + } + if (isHeaderSet) { + return chain.proceed(builder.build()); + } + return chain.proceed(original.newBuilder().build()); + }; + } + + @Provides + @ApplicationScope + @P3DSEndpoint + OkHttpClient provideP3DSOkHttpClient(Cache cache, + HttpLoggingInterceptor loggingInterceptor, + Interceptor interceptor) { + return new OkHttpClient.Builder() + .cache(cache) + .connectTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .addInterceptor(loggingInterceptor) + .addInterceptor(interceptor) + .build(); + } + + @Provides + @ApplicationScope + @VoucherEndpoint + OkHttpClient provideVoucherkHttpClient(Cache cache, + HttpLoggingInterceptor loggingInterceptor) { + return new OkHttpClient.Builder() + .cache(cache) + .addInterceptor(loggingInterceptor) + .build(); + } + + @Provides + @ApplicationScope + CallAdapter.Factory provideCallAdapterFactory() { + return RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()); + } + + @Provides + @ApplicationScope + Gson provideGson() { + return new GsonBuilder() + .setDateFormat("yyyy-MM-dd HH:mm:ss") + .create(); + } + + @Provides + @ApplicationScope + Converter.Factory provideConverterFactory(Gson gson) { + return GsonConverterFactory.create(gson); + } + + @Provides + @ApplicationScope + @P3DSEndpoint + Retrofit provideP3DSRetrofit(@P3DSEndpoint final OkHttpClient okHttpClient, + @Named(AppPrefsConstants.API_ENDPOINT) final StringPreference endpoint, + final Converter.Factory converter, + final CallAdapter.Factory adapterFactory) { + return new Retrofit.Builder() + .baseUrl(endpoint.get() == null ? Constants.BEWOPLANER_ENDPOINT : endpoint.get()) + .addCallAdapterFactory(adapterFactory) + .addConverterFactory(converter) + .client(okHttpClient) + .build(); + } + + @Provides + @ApplicationScope + @VoucherEndpoint + Retrofit provideVoucherRetrofit(@VoucherEndpoint final OkHttpClient okHttpClient, + final Converter.Factory converter, + final CallAdapter.Factory adapterFactory) { + return new Retrofit.Builder() + .baseUrl(Constants.VOUCHER_ENDPOINT) + .addCallAdapterFactory(adapterFactory) + .addConverterFactory(converter) + .client(okHttpClient) + .build(); + } +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/common/ErrorHandlingResponseConverter.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/common/ErrorHandlingResponseConverter.java new file mode 100644 index 0000000..860be3d --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/common/ErrorHandlingResponseConverter.java @@ -0,0 +1,28 @@ +package com.p3.bewoplaner.data.api.common; + +import com.p3.bewoplaner.data.events.InvalidTokenEvent; +import com.p3.bewoplaner.model.common.BaseResponse; + +import org.greenrobot.eventbus.EventBus; + +import rx.Observable; +import rx.functions.Func1; + +/** + * Created by laszloffiferenc on 2/21/17. + */ +public class ErrorHandlingResponseConverter + implements Func1> { + + @Override + public Observable call(T t) { + if (!t.success) { + if (t.errors.token != null) { + EventBus.getDefault().post(new InvalidTokenEvent()); + return Observable.error(new ErrorThrowable(ErrorType.INVALID_TOKEN, t.errors)); + } + return Observable.error(new ErrorThrowable(ErrorType.DATA_INPUT, t.errors)); + } + return Observable.just(t); + } +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/common/ErrorThrowable.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/common/ErrorThrowable.java new file mode 100644 index 0000000..d288283 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/common/ErrorThrowable.java @@ -0,0 +1,27 @@ +package com.p3.bewoplaner.data.api.common; + +import com.p3.bewoplaner.model.common.BaseResponse; + +/** + * Created by laszloffiferenc on 2/21/17. + */ +public class ErrorThrowable extends RuntimeException { + + private final ErrorType mErrorType; + private final BaseResponse.Error mError; + + ErrorThrowable(ErrorType errorType, BaseResponse.Error error) { + super(); + mError = error; + mErrorType = errorType; + } + + public BaseResponse.Error getError() { + return mError; + } + + public ErrorType getErrorType() { + return mErrorType; + } + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/common/ErrorType.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/common/ErrorType.java new file mode 100644 index 0000000..48a04bc --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/common/ErrorType.java @@ -0,0 +1,22 @@ +package com.p3.bewoplaner.data.api.common; + +/** + * Created by laszloffiferenc on 2/21/17. + */ +public enum ErrorType { + + /** + * Unexpected/unknown error + */ + UNKNOWN, + /** + * Wrong query parameters + */ + DATA_INPUT, + + /** + * Expired/invalid token + */ + INVALID_TOKEN + +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/common/ErrorUtils.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/common/ErrorUtils.java new file mode 100644 index 0000000..a3e32da --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/common/ErrorUtils.java @@ -0,0 +1,20 @@ +package com.p3.bewoplaner.data.api.common; + +/** + * Created by laszloffiferenc on 2/23/17. + */ +public class ErrorUtils { + + private ErrorUtils() { + } + + public static ErrorType getErrorType(Throwable e) { + ErrorType errorType; + if (e instanceof ErrorThrowable) { + errorType = ((ErrorThrowable) e).getErrorType(); + } else { + errorType = ErrorType.UNKNOWN; + } + return errorType; + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/GroupResponse.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/GroupResponse.java new file mode 100644 index 0000000..003beec --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/GroupResponse.java @@ -0,0 +1,21 @@ +package com.p3.bewoplaner.data.api.responses; + +import com.google.gson.annotations.SerializedName; + +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.model.common.BaseResponse; + +import java.util.ArrayList; + +/** + * Created by imarneanu on 2/28/17. + */ +public class GroupResponse extends BaseResponse { + @SerializedName("response") + public Response response; + + public static class Response { + @SerializedName("groups") + public ArrayList groups; + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/MessageResponse.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/MessageResponse.java new file mode 100644 index 0000000..68c17a8 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/MessageResponse.java @@ -0,0 +1,21 @@ +package com.p3.bewoplaner.data.api.responses; + +import com.google.gson.annotations.SerializedName; + +import com.p3.bewoplaner.model.common.BaseResponse; + +/** + * Created by Rares Teodorescu on 08/03/2017. + */ +public class MessageResponse extends BaseResponse { + + @SerializedName("response") + public Response response; + + public static class Response { + + @SerializedName("message") + public com.p3.bewoplaner.model.Message message; + } + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/MessagesResponse.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/MessagesResponse.java new file mode 100644 index 0000000..02ccdd2 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/MessagesResponse.java @@ -0,0 +1,25 @@ +package com.p3.bewoplaner.data.api.responses; + +import com.google.gson.annotations.SerializedName; + +import com.p3.bewoplaner.model.common.BaseResponse; + +import java.util.ArrayList; + +/** + * Created by imarneanu on 3/1/17. + */ +public class MessagesResponse extends BaseResponse { + @SerializedName("response") + public Response response; + + public static class Response { + + @SerializedName("messages") + public ArrayList messages; + + @SerializedName("hasMorePages") + public boolean hasMorePages; + + } +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/ProfileResponse.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/ProfileResponse.java new file mode 100644 index 0000000..fce3631 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/ProfileResponse.java @@ -0,0 +1,21 @@ +package com.p3.bewoplaner.data.api.responses; + +import com.google.gson.annotations.SerializedName; + +import com.p3.bewoplaner.model.common.BaseResponse; +import com.p3.bewoplaner.model.User; + +/** + * Created by imarneanu on 2/23/17. + */ +public class ProfileResponse extends BaseResponse { + @SerializedName("response") + public Response response; + + public static class Response { + + @SerializedName("user") + public User user; + + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/RegisterResponse.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/RegisterResponse.java new file mode 100644 index 0000000..a1d5d8f --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/RegisterResponse.java @@ -0,0 +1,14 @@ +package com.p3.bewoplaner.data.api.responses; + +import com.google.gson.annotations.SerializedName; + +import com.p3.bewoplaner.model.common.BaseResponse; + +/** + * Created by laszloffiferenc on 2/28/17. + */ +public class RegisterResponse extends BaseResponse { + + @SerializedName("response") + public String response; +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/UploadImageResponse.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/UploadImageResponse.java new file mode 100644 index 0000000..eb2a513 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/responses/UploadImageResponse.java @@ -0,0 +1,20 @@ +package com.p3.bewoplaner.data.api.responses; + +import com.google.gson.annotations.SerializedName; +import com.p3.bewoplaner.model.common.BaseResponse; + +/** + * Created by Vlad on 28/02/2017. + */ +public class UploadImageResponse extends BaseResponse { + + @SerializedName("response") + public Response response; + + public static class Response { + + @SerializedName("picture") + public String picture; + + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/ChatService.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/ChatService.java new file mode 100644 index 0000000..405d26c --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/ChatService.java @@ -0,0 +1,42 @@ +package com.p3.bewoplaner.data.api.services; + +import com.p3.bewoplaner.data.api.responses.GroupResponse; +import com.p3.bewoplaner.data.api.responses.MessageResponse; +import com.p3.bewoplaner.data.api.responses.MessagesResponse; + +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import retrofit2.http.Field; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.GET; +import retrofit2.http.Multipart; +import retrofit2.http.POST; +import retrofit2.http.Part; +import retrofit2.http.Query; +import rx.Observable; + +/** + * Created by imarneanu on 3/1/17. + */ +public interface ChatService { + @GET("api/chat/groups") + Observable getGroups(); + + @GET("api/chat/messages") + Observable getMessages(@Query("groupid") long groupId, + @Query("page") int page); + + @Multipart + @POST("api/chat/messages/send") + Observable sendFile( + @Part("groupid") RequestBody groupid, + @Part MultipartBody.Part file + ); + + @FormUrlEncoded + @POST("api/chat/messages/send") + Observable sendMessage( + @Field("groupid") long groupId, + @Field("text") String text); + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/LoginService.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/LoginService.java new file mode 100644 index 0000000..f6c485f --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/LoginService.java @@ -0,0 +1,20 @@ +package com.p3.bewoplaner.data.api.services; + +import com.p3.bewoplaner.data.api.responses.ProfileResponse; + +import retrofit2.http.Field; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.POST; +import rx.Observable; + +/** + * Created by laszloffiferenc on 2/21/17. + */ +public interface LoginService { + @FormUrlEncoded + @POST("api/user/login") + Observable login( + @Field("username") String username, + @Field("password") String password + ); +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/ProfileService.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/ProfileService.java new file mode 100644 index 0000000..4b9f379 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/ProfileService.java @@ -0,0 +1,29 @@ +package com.p3.bewoplaner.data.api.services; + +import com.p3.bewoplaner.data.api.responses.ProfileResponse; +import com.p3.bewoplaner.data.api.responses.UploadImageResponse; + +import okhttp3.MultipartBody; +import retrofit2.http.Field; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.GET; +import retrofit2.http.Multipart; +import retrofit2.http.POST; +import retrofit2.http.Part; +import rx.Observable; + +/** + * Created by imarneanu on 2/23/17. + */ +public interface ProfileService { + @GET("api/user/profile") + Observable getProfile(); + + @FormUrlEncoded + @POST("api/user/profile") + Observable updateProfile(@Field("mobile") String phoneNumber); + + @POST("api/user/profile/image") + @Multipart + Observable uploadProfileImage(@Part MultipartBody.Part filePart); +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/RegisterService.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/RegisterService.java new file mode 100644 index 0000000..58e845c --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/RegisterService.java @@ -0,0 +1,25 @@ +package com.p3.bewoplaner.data.api.services; + +import com.p3.bewoplaner.data.api.responses.RegisterResponse; + +import retrofit2.http.Field; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Query; +import rx.Observable; + +/** + * Created by laszloffiferenc on 3/2/17. + */ +public interface RegisterService { + @FormUrlEncoded + @POST("api/user/register-device") + Observable registerDevice( + @Field("device_id") String deviceId, + @Field("fcm_token") String token + ); + + @GET("api/user/logout") + Observable unregisterDevice(@Query("device_id") String deviceId); +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/VoucherService.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/VoucherService.java new file mode 100644 index 0000000..7c92379 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/api/services/VoucherService.java @@ -0,0 +1,19 @@ +package com.p3.bewoplaner.data.api.services; + +import com.google.gson.JsonObject; + +import retrofit2.http.GET; +import retrofit2.http.Query; +import rx.Observable; + +/** + * Created by Rares Teodorescu on 24/03/2017. + */ +public interface VoucherService { + + @GET("voucher_check.php") + Observable checkVoucher( + @Query("k") String customerId, + @Query("Voucher") String voucher); + +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/events/GroupUpdatedEvent.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/events/GroupUpdatedEvent.java new file mode 100644 index 0000000..b037468 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/events/GroupUpdatedEvent.java @@ -0,0 +1,7 @@ +package com.p3.bewoplaner.data.events; + +/** + * Created by Rares Teodorescu on 31/03/2017. + */ +public class GroupUpdatedEvent { +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/events/InvalidTokenEvent.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/events/InvalidTokenEvent.java new file mode 100644 index 0000000..9af7ff6 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/events/InvalidTokenEvent.java @@ -0,0 +1,7 @@ +package com.p3.bewoplaner.data.events; + +/** + * Created by Rares Teodorescu on 27/03/2017. + */ +public class InvalidTokenEvent { +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/events/LogoutDirectlyEvent.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/events/LogoutDirectlyEvent.java new file mode 100644 index 0000000..f5fe0d2 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/events/LogoutDirectlyEvent.java @@ -0,0 +1,4 @@ +package com.p3.bewoplaner.data.events; + +public class LogoutDirectlyEvent { +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/firebase/HandleMessagesService.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/firebase/HandleMessagesService.java new file mode 100644 index 0000000..b3c60d8 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/firebase/HandleMessagesService.java @@ -0,0 +1,267 @@ +package com.p3.bewoplaner.data.firebase; + +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; + +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.prefs.LongPreference; +import com.p3.bewoplaner.data.repository.GroupRealmRepository; +import com.p3.bewoplaner.data.repository.MessageRealmRepository; +import com.p3.bewoplaner.data.repository.specifications.GroupByIdSpecification; +import com.p3.bewoplaner.data.repository.specifications.MessageByIdSpecification; +import com.p3.bewoplaner.di.InjectionHelper; +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.page.main.MainActivity; +import com.p3.bewoplaner.utils.Constants; +import com.p3.bewoplaner.utils.LoggedOutUtils; + +import android.app.Activity; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.RemoteInput; +import android.support.v4.content.ContextCompat; + +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Collections; +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Named; + +import static android.graphics.BitmapFactory.decodeStream; +import static com.p3.bewoplaner.utils.Constants.CHAT_IS_ALREADY_OPEN; + +/** + * Created by laszloffiferenc on 3/1/17. + */ +public class HandleMessagesService extends FirebaseMessagingService { + + @Inject + MessageRealmRepository mMessageRealmRepository; + + @Inject + GroupRealmRepository mGroupRealmRepository; + + @Inject + @Named(AppPrefsConstants.USER_ID) + LongPreference mUserIdReference; + + @Inject + LoggedOutUtils mLoggedOutUtils; + + @Override + public void onMessageReceived(RemoteMessage remoteMessage) { + InjectionHelper.getMessagingComponent(this).inject(HandleMessagesService.this); + if (mLoggedOutUtils.checkLoggedOutPrefs()) { + return; + } + customizeNotification(remoteMessage.getData()); + } + + private static Bitmap getBitmapFromUrl(String pictureUrl) { + try { + URL url = new URL(pictureUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setDoInput(true); + connection.connect(); + Bitmap bitmap = decodeStream(connection.getInputStream()); + return Bitmap.createScaledBitmap(bitmap, 240, 240, false); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private void customizeNotification(Map data) { + Bundle bundle = new Bundle(); + bundle.putString(Constants.MESSAGE_ID, data.get(Constants.MESSAGE_ID)); + bundle.putLong(Constants.GROUP_ID, Long.parseLong(data.get(Constants.GROUP_ID))); + bundle.putLong(Constants.CREATED_AT, Long.parseLong(data.get(Constants.CREATED_AT))); + bundle.putString(Constants.MESSAGE_TEXT, data.get(Constants.MESSAGE_TEXT)); + bundle.putString(Constants.FILE, data.get(Constants.FILE)); + bundle.putString(Constants.SMALLER_IMAGE, data.get(Constants.SMALLER_IMAGE)); + bundle.putString(Constants.FILENAME, data.get(Constants.FILENAME)); + bundle.putLong(Constants.USER_ID, Long.parseLong(data.get(Constants.USER_ID))); + bundle.putString(Constants.USERNAME, data.get(Constants.USERNAME)); + bundle.putString(Constants.USER_AVATAR, data.get(Constants.USER_AVATAR)); + + final Bitmap bitmap = getBitmapFromUrl(data.get(Constants.USER_AVATAR)); + Intent intent = new Intent(); + intent.putExtra(Constants.MESSAGE, bundle); + intent.setAction(Constants.NOTIFICATION_BROADCAST); + sendOrderedBroadcast(intent, null, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Bundle results = getResultExtras(true); + + if (!results.getBoolean(CHAT_IS_ALREADY_OPEN, false)) { + int groupId = (int) bundle.getLong(Constants.GROUP_ID); + int numberOfUsers = Integer.parseInt(data.get(Constants.GROUP_NR_USERS)); + intent = MainActivity.createIntent(context, bundle, numberOfUsers, groupId); + + PendingIntent pendingIntent = PendingIntent.getActivity(context, + groupId, + intent, + PendingIntent.FLAG_UPDATE_CURRENT); + + NotificationCompat.Action action = createAction(context, numberOfUsers, groupId, bundle); + + Bitmap bitmapToDisplay = bitmap; + if (bitmapToDisplay == null) { + bitmapToDisplay = BitmapFactory.decodeResource(context.getResources(), R.drawable.avatar); + } + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context) + .setSmallIcon(getNotificationIcon()) + .setContentTitle(setNotificationTitle(data, numberOfUsers)) + .setContentText(getTextFromNotification(data)) + .setAutoCancel(true) + .setStyle(new NotificationCompat.BigTextStyle().bigText(getTextFromNotification(data))) + .setPriority(Notification.PRIORITY_MAX) + .setContentIntent(pendingIntent) + .setLargeIcon(getCircleBitmap(bitmapToDisplay)) + .setGroup(String.valueOf(groupId)) + .setGroupSummary(true) + .setDefaults(Notification.DEFAULT_ALL) + .setColor(ContextCompat.getColor(context, R.color.colorDefaultBright)); + + if (action != null) { + builder.addAction(action); + } + + NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.notify(groupId, builder.build()); + + Message message = new Message(bundle.getString(Constants.MESSAGE_ID), + groupId, + bundle.getLong(Constants.CREATED_AT), + bundle.getString(Constants.MESSAGE_TEXT), + bundle.getString(Constants.FILE), + bundle.getString(Constants.SMALLER_IMAGE), + bundle.getString(Constants.FILENAME), + null, + bundle.getLong(Constants.USER_ID), + bundle.getString(Constants.USERNAME), + bundle.getString(Constants.USER_AVATAR), + Message.Status.DELIVERED, + false, + mUserIdReference.get(), + false); + storeMessageLocally(message); + storeGroupLocally(groupId, message); + } + } + }, null, Activity.RESULT_OK, null, null); + } + + private Bitmap getCircleBitmap(Bitmap bitmap) { + final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), + bitmap.getHeight(), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(output); + + final int color = Color.RED; + final Paint paint = new Paint(); + final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + final RectF rectF = new RectF(rect); + + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(color); + canvas.drawOval(rectF, paint); + + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(bitmap, rect, rect, paint); + + bitmap.recycle(); + + return output; + } + + private void storeGroupLocally(long groupId, Message message) { + Group group = new Group(); + group.id = groupId; + group.lastMessage = message; + mGroupRealmRepository.updateGroupMessage(group, new GroupByIdSpecification(mUserIdReference.get())).subscribe(); + } + + private void storeMessageLocally(Message message) { + MessageByIdSpecification messageByIdSpecification = new MessageByIdSpecification(mUserIdReference.get()); + messageByIdSpecification.setMessageId(message.id); + mMessageRealmRepository.update(Collections.singletonList(message), messageByIdSpecification) + .subscribe(); + } + + private String setNotificationTitle(Map data, int numberOfUsers) { + if (numberOfUsers > 2) { + return String.valueOf(data.get(Constants.USERNAME) + " @ " + data.get(Constants.GROUP_NAME)); + } else { + return String.valueOf(data.get(Constants.USERNAME)); + } + } + + private NotificationCompat.Action createAction(Context context, int noOfUsers, int groupId, Bundle bundle) { + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return null; + } + Intent replyIntent = ReplyNotificationReceiver.createReplyIntent(context, bundle, noOfUsers, groupId); + + PendingIntent replyPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), + groupId, + replyIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + + String replyLabel = getResources().getString(R.string.chat_reply_label); + RemoteInput remoteInput = new RemoteInput.Builder(Constants.KEY_TEXT_REPLY) + .setLabel(replyLabel) + .build(); + + return new NotificationCompat.Action.Builder(R.drawable.send_enabled, + getString(R.string.chat_reply_label), replyPendingIntent) + .addRemoteInput(remoteInput) + .build(); + } + + private String getTextFromNotification(Map data) { + String message = null; + if (data.get(Constants.MESSAGE_TEXT) != null) { + message = String.valueOf(data.get(Constants.MESSAGE_TEXT)); + } + if (!data.get(Constants.FILE).equals("")) { + message = getString(R.string.chat_file_received); + } + return message; + } + + public static int getNotificationIcon() { + boolean useWhiteIcon = (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP); + return useWhiteIcon ? R.drawable.icon_notification_transparent : R.drawable.notification_color; + } + + public static CharSequence getReplyMessage(Intent intent) { + Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); + if (remoteInput != null) { + return remoteInput.getCharSequence(Constants.KEY_TEXT_REPLY); + } + return null; + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/firebase/MessagingComponent.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/firebase/MessagingComponent.java new file mode 100644 index 0000000..6748f0a --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/firebase/MessagingComponent.java @@ -0,0 +1,21 @@ +package com.p3.bewoplaner.data.firebase; + +import com.p3.bewoplaner.di.ApplicationComponent; +import com.p3.bewoplaner.di.scopes.ActivityScope; +import com.p3.bewoplaner.page.chat.ChatModule; +import com.p3.bewoplaner.page.login.LoginModule; + +import dagger.Component; + +/** + * Created by laszloffiferenc on 3/2/17. + */ +@ActivityScope +@Component(dependencies = ApplicationComponent.class, modules = {LoginModule.class, ChatModule.class}) +public interface MessagingComponent { + void inject(MessagingInstanceIdService messagingInstanceIdService); + + void inject(HandleMessagesService handleMessagesService); + + void inject(ReplyNotificationReceiver replyNotificationReceiver); +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/firebase/MessagingInstanceIdService.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/firebase/MessagingInstanceIdService.java new file mode 100644 index 0000000..94a1f04 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/firebase/MessagingInstanceIdService.java @@ -0,0 +1,63 @@ +package com.p3.bewoplaner.data.firebase; + +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.iid.FirebaseInstanceIdService; + +import com.p3.bewoplaner.BewoplanerApplication; +import com.p3.bewoplaner.data.api.responses.RegisterResponse; +import com.p3.bewoplaner.data.api.services.RegisterService; +import com.p3.bewoplaner.di.InjectionHelper; + +import android.provider.Settings; +import android.util.Log; + +import javax.inject.Inject; + +import rx.Subscriber; +import rx.schedulers.Schedulers; + +/** + * Created by laszloffiferenc on 3/1/17. + */ +public class MessagingInstanceIdService extends FirebaseInstanceIdService { + + private static String TAG = "messaging"; + private static BewoplanerApplication sAndroidContext; + + @Inject + RegisterService mRegisterService; + + public static void setApplicationContext(BewoplanerApplication androidContext) { + sAndroidContext = androidContext; + } + + @Override + public void onTokenRefresh() { + InjectionHelper.getMessagingComponent(sAndroidContext).inject(this); + String refreshedToken = FirebaseInstanceId.getInstance().getToken(); + Log.d(TAG, "Refreshed token: " + refreshedToken); + sendRegistrationToServer(refreshedToken); + } + + private void sendRegistrationToServer(String token) { + mRegisterService.registerDevice(Settings.Secure.getString(sAndroidContext.getContentResolver(), + Settings.Secure.ANDROID_ID), token) + .subscribeOn(Schedulers.io()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + Log.d(TAG, "onCompleted: " + token); + } + + @Override + public void onError(Throwable e) { + Log.d(TAG, "onError: "); + } + + @Override + public void onNext(RegisterResponse registerResponse) { + Log.d(TAG, "onNext: "); + } + }); + } +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/firebase/ReplyNotificationReceiver.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/firebase/ReplyNotificationReceiver.java new file mode 100644 index 0000000..974d952 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/firebase/ReplyNotificationReceiver.java @@ -0,0 +1,182 @@ +package com.p3.bewoplaner.data.firebase; + +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.api.common.ErrorHandlingResponseConverter; +import com.p3.bewoplaner.data.api.responses.MessageResponse; +import com.p3.bewoplaner.data.api.services.ChatService; +import com.p3.bewoplaner.data.prefs.LongPreference; +import com.p3.bewoplaner.data.prefs.StringPreference; +import com.p3.bewoplaner.data.repository.GroupRealmRepository; +import com.p3.bewoplaner.data.repository.MessageRealmRepository; +import com.p3.bewoplaner.data.repository.RealmProvider; +import com.p3.bewoplaner.data.repository.specifications.GroupByIdSpecification; +import com.p3.bewoplaner.data.repository.specifications.MessageByIdSpecification; +import com.p3.bewoplaner.di.InjectionHelper; +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.page.chat.ChatPresenter; +import com.p3.bewoplaner.utils.Constants; +import com.p3.bewoplaner.utils.rx.RxUtils; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationManagerCompat; +import android.support.v4.content.ContextCompat; +import android.widget.Toast; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import javax.inject.Inject; +import javax.inject.Named; + +import rx.Observable; +import rx.Subscriber; + +import static com.p3.bewoplaner.utils.Constants.GROUP_ID; + +/** + * Created by laszloffiferenc on 3/20/17. + */ +public class ReplyNotificationReceiver extends BroadcastReceiver { + private static final String BUNDLE = "bundle"; + + @Inject + ChatService mChatService; + + @Inject + @Named(AppPrefsConstants.USER_ID) + LongPreference mUserIdPreference; + + @Inject + @Named(AppPrefsConstants.AVATAR) + StringPreference mAvatarPreference; + + @Inject + @Named(AppPrefsConstants.NAME) + StringPreference mProfileNamePreference; + + @Inject + RealmProvider mRealmProvider; + + @Inject + MessageRealmRepository mMessageRealmRepository; + + @Inject + GroupRealmRepository mGroupRealmRepository; + + @Inject + @Named(AppPrefsConstants.USER_ID) + LongPreference mUserIdReference; + + public ReplyNotificationReceiver() { + } + + public static Intent createReplyIntent(Context context, Bundle bundle, int numberOfUsers, int groupId) { + Intent intent = new Intent(context, ReplyNotificationReceiver.class); + intent.putExtra(GROUP_ID, groupId); + intent.putExtra(BUNDLE, bundle); + intent.putExtra(Constants.GROUP_NR_USERS, numberOfUsers); + return intent; + } + + @Override + public void onReceive(Context context, Intent intent) { + InjectionHelper.getMessagingComponent(context).inject(this); + CharSequence replyMessage = HandleMessagesService.getReplyMessage(intent); + + Bundle bundle = intent.getBundleExtra(BUNDLE); + long groupId = bundle.getLong(Constants.GROUP_ID); + if (replyMessage != null && replyMessage instanceof String) { + sendMessage(context, (String) replyMessage, (int) groupId); + } + } + + private void updateNotificationOnReply(Context context, String replyMessage, int groupId) { + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); + NotificationCompat.Builder builder = new NotificationCompat.Builder(context) + .setSmallIcon(HandleMessagesService.getNotificationIcon()) + .setColor(ContextCompat.getColor(context, R.color.colorDefaultBright)) + .setContentTitle(context.getResources().getString(R.string.chat_you_name)) + .setContentText(replyMessage); + + notificationManager.notify(groupId, builder.build()); + Observable.timer(2, TimeUnit.SECONDS).subscribe(aLong -> notificationManager.cancel(groupId)); + } + + public void sendMessage(Context context, String replyMessage, int groupId) { + mChatService.sendMessage(groupId, replyMessage) + .compose(RxUtils.provideDefaultTransformer()) + .flatMap(new ErrorHandlingResponseConverter<>()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + updateNotificationOnReply(context, replyMessage, groupId); + } + + @Override + public void onError(Throwable e) { + Message message = new Message(ChatPresenter.findUniqueRandomId(mRealmProvider, mUserIdPreference.get()), + groupId, + System.currentTimeMillis() / 1000, + replyMessage, + null, + null, + null, + null, + mUserIdPreference.get(), + mProfileNamePreference.get(), + mAvatarPreference.get(), + Message.Status.FAILED, + true, + mUserIdPreference.get(), + false); + storeMessageLocally(message); + storeGroupLocally(groupId, message); + + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); + notificationManager.cancel(groupId); + Toast.makeText(context, R.string.error_message_not_sent, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onNext(MessageResponse baseResponse) { + Message message = baseResponse.response.message; + message.status = Message.Status.DELIVERED; + storeMessageLocally(message); + storeGroupLocally(groupId, message); + Bundle bundle = new Bundle(); + bundle.putString(Constants.MESSAGE_ID, message.id); + bundle.putLong(Constants.GROUP_ID, message.groupId); + bundle.putLong(Constants.CREATED_AT, message.createdAt); + bundle.putString(Constants.MESSAGE_TEXT, message.message); + bundle.putString(Constants.FILE, message.filePath); + bundle.putLong(Constants.USER_ID, message.senderId); + bundle.putString(Constants.USERNAME, message.senderName); + bundle.putString(Constants.USER_AVATAR, message.senderAvatar); + Intent intent = new Intent(); + intent.putExtra(Constants.MESSAGE, bundle); + intent.setAction(Constants.NOTIFICATION_BROADCAST); + context.sendBroadcast(intent); + } + }); + } + + private void storeGroupLocally(long groupId, Message message) { + Group group = new Group(); + group.id = groupId; + group.lastMessage = message; + mGroupRealmRepository.updateGroupMessage(group, new GroupByIdSpecification(mUserIdPreference.get())).subscribe(); + } + + private void storeMessageLocally(Message message) { + MessageByIdSpecification messageByIdSpecification = new MessageByIdSpecification(mUserIdPreference.get()); + messageByIdSpecification.setMessageId(message.id); + mMessageRealmRepository.update(Collections.singletonList(message), messageByIdSpecification) + .subscribe(); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/AppPrefs.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/AppPrefs.java new file mode 100644 index 0000000..a28eb72 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/AppPrefs.java @@ -0,0 +1,14 @@ +package com.p3.bewoplaner.data.prefs; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +/** + * Created by imarneanu on 2/10/17. + */ +@Qualifier +@Retention(RetentionPolicy.SOURCE) +public @interface AppPrefs { +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/AppPrefsModule.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/AppPrefsModule.java new file mode 100644 index 0000000..1d334f4 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/AppPrefsModule.java @@ -0,0 +1,113 @@ +package com.p3.bewoplaner.data.prefs; + +import com.p3.bewoplaner.BewoplanerApplication; +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.di.scopes.ApplicationScope; +import com.securepreferences.SecurePreferences; + +import android.content.Context; +import android.content.SharedPreferences; + +import javax.inject.Named; + +import dagger.Module; +import dagger.Provides; + +/** + * Created by imarneanu on 2/10/17. + */ +@Module +public class AppPrefsModule { + @Provides + @ApplicationScope + @AppPrefs + SharedPreferences provideSharedPreferences(BewoplanerApplication app) { + return app.getSharedPreferences(app.getString(R.string.app_name), Context.MODE_PRIVATE); + } + + @Provides + @ApplicationScope + @Named(AppPrefsConstants.API_ENDPOINT) + StringPreference provideApiEndpoint(@AppPrefs SharedPreferences sharedPreferences) { + return new StringPreference(sharedPreferences, AppPrefsConstants.API_ENDPOINT); + } + + @Provides + @ApplicationScope + @AppPrefs + SecurePreferences provideSecuredPrefs(BewoplanerApplication application) { + return new SecurePreferences(application, "", "my_prefs_1000.xml"); + } + + @Provides + @ApplicationScope + @Named(AppPrefsConstants.LOGIN_TOKEN) + StringPreference provideLoginToken(@AppPrefs SharedPreferences sharedPreferences) { + return new StringPreference(sharedPreferences, AppPrefsConstants.LOGIN_TOKEN); + } + + @Provides + @ApplicationScope + @Named(AppPrefsConstants.SAVE_PASSWORD) + BooleanPreference provideSavePassword(@AppPrefs SecurePreferences sharedPreferences) { + return new BooleanPreference(sharedPreferences, AppPrefsConstants.SAVE_PASSWORD); + } + + @Provides + @ApplicationScope + @Named(AppPrefsConstants.CHAT_CODE) + StringPreference provideChatCode(@AppPrefs SecurePreferences sharedPreferences) { + return new StringPreference(sharedPreferences, AppPrefsConstants.CHAT_CODE); + } + + @Provides + @ApplicationScope + @Named(AppPrefsConstants.ACCOUNT_NO) + StringPreference provideAccountNumber(@AppPrefs SecurePreferences sharedPreferences) { + return new StringPreference(sharedPreferences, AppPrefsConstants.ACCOUNT_NO); + } + + @Provides + @ApplicationScope + @Named(AppPrefsConstants.USER_ID) + LongPreference provideUserId(@AppPrefs SharedPreferences sharedPreferences) { + return new LongPreference(sharedPreferences, AppPrefsConstants.USER_ID); + } + + @Provides + @ApplicationScope + @Named(AppPrefsConstants.AVATAR) + StringPreference provideAvatar(@AppPrefs SharedPreferences sharedPreferences) { + return new StringPreference(sharedPreferences, AppPrefsConstants.AVATAR); + } + + @Provides + @ApplicationScope + @Named(AppPrefsConstants.NAME) + StringPreference provideProfileName(@AppPrefs SharedPreferences sharedPreferences) { + return new StringPreference(sharedPreferences, AppPrefsConstants.NAME); + } + + @Provides + @ApplicationScope + @Named(AppPrefsConstants.PHONE_NUMBER) + StringPreference providePhoneNumber(@AppPrefs SharedPreferences sharedPreferences) { + return new StringPreference(sharedPreferences, AppPrefsConstants.PHONE_NUMBER); + } + + @Provides + @ApplicationScope + @Named(AppPrefsConstants.CHAT_OPENED) + BooleanPreference provideChatOpened(@AppPrefs SharedPreferences sharedPreferences) { + return new BooleanPreference(sharedPreferences, AppPrefsConstants.CHAT_OPENED); + } + + @Provides + @ApplicationScope + @Named(AppPrefsConstants.APP_KILLED) + BooleanPreference provideAppKilled(@AppPrefs SharedPreferences sharedPreferences) { + return new BooleanPreference(sharedPreferences, AppPrefsConstants.APP_KILLED); + } + +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/BasePreference.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/BasePreference.java new file mode 100644 index 0000000..e30b1cf --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/BasePreference.java @@ -0,0 +1,57 @@ +package com.p3.bewoplaner.data.prefs; + +import com.securepreferences.SecurePreferences; + +import android.content.SharedPreferences; + +import java.util.Map; + +/** + * Created by imarneanu on 2/10/17. + */ +abstract class BasePreference { + + final SharedPreferences mPreferences; + final String mKey; + final T mDefaultValue; + + BasePreference(SharedPreferences preferences, String key, T defaultValue) { + mPreferences = preferences; + mKey = key; + mDefaultValue = defaultValue; + } + + BasePreference(SecurePreferences preferences, String key, T defaultValue) { + mPreferences = preferences; + mKey = key; + mDefaultValue = defaultValue; + } + + public boolean isSet() { + return mPreferences.contains(mKey); + } + + public void delete() { + mPreferences.edit().remove(mKey).apply(); + } + + /** + * Method needs to be called when {@link SecurePreferences} are used. + */ + public void deleteFromSecurePreference() { + String secureKey = SecurePreferences.hashPrefKey(mKey); + for (Map.Entry entry : ((SecurePreferences) mPreferences).getAll().entrySet()) { + final String key = entry.getKey(); + //noinspection ConstantConditions + if (key.equals(secureKey)) { + mPreferences.edit().remove(mKey).apply(); + return; + } + } + } + + public abstract T get(); + + public abstract void set(T value); + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/BooleanPreference.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/BooleanPreference.java new file mode 100644 index 0000000..7b10d17 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/BooleanPreference.java @@ -0,0 +1,34 @@ +package com.p3.bewoplaner.data.prefs; + +import com.securepreferences.SecurePreferences; + +import android.content.SharedPreferences; +import android.support.annotation.NonNull; + +/** + * Created by Rares Teodorescu on 27/03/2017. + */ +public class BooleanPreference extends BasePreference { + + BooleanPreference(@NonNull SharedPreferences preferences, @NonNull String key) { + this(preferences, key, false); + } + + BooleanPreference(SharedPreferences preferences, String key, Boolean defaultValue) { + super(preferences, key, defaultValue); + } + + BooleanPreference(SecurePreferences preferences, String key, Boolean defaultValue) { + super(preferences, key, defaultValue); + } + + @Override + public Boolean get() { + return mPreferences.getBoolean(mKey, mDefaultValue); + } + + @Override + public void set(Boolean value) { + mPreferences.edit().putBoolean(mKey, value).apply(); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/LongPreference.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/LongPreference.java new file mode 100644 index 0000000..d77b7a3 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/LongPreference.java @@ -0,0 +1,30 @@ +package com.p3.bewoplaner.data.prefs; + +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * Created by imarneanu on 3/2/17. + */ +public class LongPreference extends BasePreference { + + public LongPreference(@NonNull SharedPreferences preferences, @NonNull String key) { + this(preferences, key, (long) 0); + } + + public LongPreference(@NonNull SharedPreferences preferences, @NonNull String key, @Nullable Long defaultValue) { + super(preferences, key, defaultValue); + } + + @Override + public Long get() { + return mPreferences.getLong(mKey, mDefaultValue); + } + + @Override + public void set(Long value) { + mPreferences.edit().putLong(mKey, value).apply(); + } +} + diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/StringPreference.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/StringPreference.java new file mode 100644 index 0000000..a9f389c --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/prefs/StringPreference.java @@ -0,0 +1,42 @@ +package com.p3.bewoplaner.data.prefs; + +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import com.securepreferences.SecurePreferences; + +/** + * Created by imarneanu on 2/10/17. + */ +public class StringPreference extends BasePreference { + + public StringPreference(@NonNull SharedPreferences preferences, @NonNull String key) { + this(preferences, key, null); + } + + public StringPreference(@NonNull SecurePreferences preferences, @NonNull String key) { + this(preferences, key, null); + } + + public StringPreference(@NonNull SharedPreferences preferences, @NonNull String key, @Nullable String defaultValue) { + super(preferences, key, defaultValue); + } + + public StringPreference(@NonNull SecurePreferences preferences, @NonNull String key, @Nullable String defaultValue) { + super(preferences, key, defaultValue); + } + + @Override + public String get() { + return mPreferences.getString(mKey, mDefaultValue); + } + + @Override + public void set(String value) { + if (!TextUtils.isEmpty(value)) { + mPreferences.edit().putString(mKey, value).apply(); + } + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/GroupRealmRepository.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/GroupRealmRepository.java new file mode 100644 index 0000000..160ad8c --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/GroupRealmRepository.java @@ -0,0 +1,264 @@ +package com.p3.bewoplaner.data.repository; + +import com.p3.bewoplaner.data.repository.mapper.Mapper; +import com.p3.bewoplaner.data.repository.models.GroupRealm; +import com.p3.bewoplaner.data.repository.models.UserRealm; +import com.p3.bewoplaner.data.repository.specifications.GroupByIdSpecification; +import com.p3.bewoplaner.data.repository.specifications.RealmSpecification; +import com.p3.bewoplaner.data.repository.specifications.Specification; +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.model.User; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + +import io.realm.RealmList; +import io.realm.Sort; +import rx.Completable; +import rx.Observable; +import rx.functions.Func1; + +/** + * Created by imarneanu on 3/6/17. + */ +public class GroupRealmRepository implements Repository { + + private final RealmProvider mRealmProvider; + private final Mapper mToRealmMapper; + private final Mapper mToGroupMapper; + + public GroupRealmRepository(RealmProvider realmProvider, + Mapper toRealmMapper, + Mapper toMessageMapper) { + mRealmProvider = realmProvider; + mToRealmMapper = toRealmMapper; + mToGroupMapper = toMessageMapper; + } + + @Override + public Completable add(Iterable items) { + final RealmWrapper realmWrapper = new RealmWrapper(); + return Observable.from(items) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .map(mToRealmMapper::from) + .doOnNext(groupRealm -> realmWrapper.realm.insert(groupRealm)) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }) + .toCompletable(); + } + + @Override + public Completable update(Group item, Specification specification) { + RealmWrapper realmWrapper = new RealmWrapper(); + GroupByIdSpecification realmSpecification = (GroupByIdSpecification) specification; + return Observable.just(item) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .map(mToRealmMapper::from) + .doOnNext(newGroup -> { + realmSpecification.setGroupId(newGroup.getId()); + GroupRealm groupToUpdate = + realmSpecification.toRealmQuery(realmWrapper.realm).findFirst(); + if (groupToUpdate != null) { + newGroup.setUniqueId(groupToUpdate.getUniqueId()); + } + realmWrapper.realm.copyToRealmOrUpdate(newGroup); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }) + .toCompletable(); + } + + @Override + public Completable update(Iterable items, Specification specification) { + final RealmWrapper realmWrapper = new RealmWrapper(); + GroupByIdSpecification realmSpecification = (GroupByIdSpecification) specification; + return Observable.from(items) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .map(mToRealmMapper::from) + .doOnNext(newGroup -> { + realmSpecification.setGroupId(newGroup.getId()); + GroupRealm groupToUpdate = + realmSpecification.toRealmQuery(realmWrapper.realm).findFirst(); + if (groupToUpdate != null) { + if (newGroup.getMessage() != null && groupToUpdate.getMessage() != null && + newGroup.getMessage().getCreatedAt() < groupToUpdate.getMessage().getCreatedAt()) { + return; + } + newGroup.setUniqueId(groupToUpdate.getUniqueId()); + if (groupToUpdate.getMessage() != null + && newGroup.getMessage() != null + && groupToUpdate.getMessage().getUniqueId().equals(newGroup.getMessage().getUniqueId()) + && groupToUpdate.getMessage().isRead()) { + newGroup.getMessage().setRead(true); + } + } + realmWrapper.realm.copyToRealmOrUpdate(newGroup); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }) + .toCompletable(); + } + + @Override + public Completable remove(Specification specification) { + return null; + } + + @Override + public Completable removeAll() { + RealmWrapper realmWrapper = new RealmWrapper(); + return Observable.fromCallable(new Callable() { + @Override + public Void call() throws Exception { + realmWrapper.realm.delete(GroupRealm.class); + return null; + } + }) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }) + .toCompletable(); + } + + @Override + public Observable queryForOneById(String id, Specification specification) { + RealmWrapper realmWrapper = new RealmWrapper(); + GroupByIdSpecification realmSpecification = (GroupByIdSpecification) specification; + ((GroupByIdSpecification) specification).setGroupId(Integer.parseInt(id)); + return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm).findFirst()) + .map(mToGroupMapper::from) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }); + } + + @Override + public Observable> queryForAll(Specification specification) { + RealmWrapper realmWrapper = new RealmWrapper(); + RealmSpecification realmSpecification = (RealmSpecification) specification; + return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm) + .findAllSorted(GroupRealm.CREATED_AT, Sort.DESCENDING)) + .flatMapIterable(groupsRealms -> groupsRealms) + .flatMap(new Func1>() { + @Override + public Observable call(GroupRealm groupRealm) { + return Observable.just(mToGroupMapper.from(groupRealm)); + } + }) + .toList() + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }); + } + + public Completable updateGroupMessage(Group group, Specification specification) { + final RealmWrapper realmWrapper = new RealmWrapper(); + GroupByIdSpecification realmSpecification = (GroupByIdSpecification) specification; + return Observable.just(group) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .map(mToRealmMapper::from) + .doOnNext(newGroup -> { + realmSpecification.setGroupId(newGroup.getId()); + GroupRealm groupToUpdate = + realmSpecification.toRealmQuery(realmWrapper.realm).findFirst(); + if (groupToUpdate == null) { + return; + } + if (groupToUpdate.getMessage() != null && + group.lastMessage.createdAt < (groupToUpdate.getMessage().getCreatedAt() / 1000)) { + return; + } + RealmList userRealms = groupToUpdate.getUserIdList(); + ArrayList users = null; + if (userRealms != null) { + users = new ArrayList<>(userRealms.size()); + for (int i = 0, size = userRealms.size(); i < size; i++) { + users.add(User.createFromUserRealm(userRealms.get(i))); + } + } + Message message = group.lastMessage; + if (groupToUpdate.getMessage() != null + && groupToUpdate.getMessage().getUniqueId().equals(message.getUniqueId()) + && groupToUpdate.getMessage().isRead()) { + message.isRead = true; + } + newGroup = new GroupRealm(groupToUpdate.getId(), + groupToUpdate.getName(), + groupToUpdate.getAvatar(), + group.lastMessage, + groupToUpdate.getMobile(), + groupToUpdate.isOnlyEmployees(), + users, + groupToUpdate.getUserId()); + realmWrapper.realm.copyToRealmOrUpdate(newGroup); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }) + .toCompletable(); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/MessageRealmRepository.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/MessageRealmRepository.java new file mode 100644 index 0000000..60a66c2 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/MessageRealmRepository.java @@ -0,0 +1,254 @@ +package com.p3.bewoplaner.data.repository; + +import com.p3.bewoplaner.data.repository.mapper.Mapper; +import com.p3.bewoplaner.data.repository.models.MessageRealm; +import com.p3.bewoplaner.data.repository.specifications.MessageByIdSpecification; +import com.p3.bewoplaner.data.repository.specifications.RealmSpecification; +import com.p3.bewoplaner.data.repository.specifications.Specification; +import com.p3.bewoplaner.model.Message; + +import java.util.List; +import java.util.concurrent.Callable; + +import io.realm.Sort; +import rx.Completable; +import rx.Observable; +import rx.functions.Func1; + +/** + * Created by Rares Teodorescu on 22/02/2017. + */ +public class MessageRealmRepository implements Repository { + + private final RealmProvider mRealmProvider; + private final Mapper mToRealmMapper; + private final Mapper mToMessageMapper; + + public MessageRealmRepository(RealmProvider realmProvider, + Mapper toRealmMapper, + Mapper toMessageMapper) { + mRealmProvider = realmProvider; + mToRealmMapper = toRealmMapper; + mToMessageMapper = toMessageMapper; + } + + @Override + public Completable add(Iterable items) { + final RealmWrapper realmWrapper = new RealmWrapper(); + return Observable.from(items) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .map(mToRealmMapper::from) + .doOnNext(messageRealm -> realmWrapper.realm.insert(messageRealm)) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }) + .toCompletable(); + } + + @Override + public Completable update(Message item, Specification specification) { + RealmWrapper realmWrapper = new RealmWrapper(); + MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification; + return Observable.just(item) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .map(mToRealmMapper::from) + .doOnNext(newMessage -> { + realmSpecification.setMessageId(newMessage.getId()); + MessageRealm messageToUpdate = + realmSpecification.toRealmQuery(realmWrapper.realm).findFirst(); + if (messageToUpdate != null) { + newMessage.setUniqueId(messageToUpdate.getUniqueId()); + } + realmWrapper.realm.copyToRealmOrUpdate(newMessage); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }) + .toCompletable(); + } + + @Override + public Completable update(Iterable items, Specification specification) { + final RealmWrapper realmWrapper = new RealmWrapper(); + MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification; + return Observable.from(items) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .map(mToRealmMapper::from) + .doOnNext(newMessage -> { + realmSpecification.setMessageId(newMessage.getId()); + MessageRealm messageToUpdate = + realmSpecification.toRealmQuery(realmWrapper.realm).findFirst(); + if (messageToUpdate != null) { + newMessage.setUniqueId(messageToUpdate.getUniqueId()); + newMessage.setFileDownloaded(messageToUpdate.isFileDownloaded()); + } + realmWrapper.realm.copyToRealmOrUpdate(newMessage); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }) + .toCompletable(); + } + + public Observable> updateMessages(Iterable items, Specification specification) { + final RealmWrapper realmWrapper = new RealmWrapper(); + MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification; + return Observable.from(items) + .flatMapIterable(new Func1>() { + @Override + public Iterable call(Message message) { + return items; + } + }) + .map(mToRealmMapper::from) + .doOnNext(newMessage -> { + realmSpecification.setMessageId(newMessage.getId()); + MessageRealm messageToUpdate = + realmSpecification.toRealmQuery(realmWrapper.realm).findFirst(); + if (messageToUpdate != null) { + newMessage.setUniqueId(messageToUpdate.getUniqueId()); + newMessage.setFileDownloaded(messageToUpdate.isFileDownloaded()); + } + realmWrapper.realm.copyToRealmOrUpdate(newMessage); + }) + .flatMap(new Func1>() { + @Override + public Observable call(MessageRealm messageRealm) { + return Observable.just(mToMessageMapper.from(messageRealm)); + } + }) + .toList() + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }); + } + + @Override + public Completable remove(Specification specification) { + RealmWrapper realmWrapper = new RealmWrapper(); + MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification; + return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm).findFirst()) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .doOnNext(messageRealm -> messageRealm.deleteFromRealm()) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }) + .toCompletable(); + } + + @Override + public Completable removeAll() { + RealmWrapper realmWrapper = new RealmWrapper(); + return Observable.fromCallable(new Callable() { + @Override + public Void call() throws Exception { + realmWrapper.realm.delete(MessageRealm.class); + return null; + } + }) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }) + .toCompletable(); + } + + @Override + public Observable queryForOneById(String id, Specification specification) { + RealmWrapper realmWrapper = new RealmWrapper(); + MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification; + ((MessageByIdSpecification) specification).setMessageId(id); + return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm).findFirst()) + .map(mToMessageMapper::from) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }); + } + + @Override + public Observable> queryForAll(Specification specification) { + RealmWrapper realmWrapper = new RealmWrapper(); + RealmSpecification realmSpecification = (RealmSpecification) specification; + return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm) + .findAllSorted(MessageRealm.CREATED_AT, Sort.DESCENDING)) + .flatMapIterable(talksRealms -> talksRealms) + .flatMap(new Func1>() { + @Override + public Observable call(MessageRealm userRealm) { + return Observable.just(mToMessageMapper.from(userRealm)); + } + }) + .toList() + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }); + } + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/RealmProvider.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/RealmProvider.java new file mode 100644 index 0000000..2690264 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/RealmProvider.java @@ -0,0 +1,21 @@ +package com.p3.bewoplaner.data.repository; + +import io.realm.Realm; +import io.realm.RealmConfiguration; + +/** + * Created by Rares Teodorescu on 11/01/2017. + */ +public class RealmProvider { + + private final RealmConfiguration realmConfiguration; + + RealmProvider(RealmConfiguration realmConfiguration) { + this.realmConfiguration = realmConfiguration; + } + + public Realm provide() { + return Realm.getInstance(realmConfiguration); + } + +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/RealmWrapper.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/RealmWrapper.java new file mode 100644 index 0000000..e0a221f --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/RealmWrapper.java @@ -0,0 +1,14 @@ +package com.p3.bewoplaner.data.repository; + +import io.realm.Realm; + +/** + * Created by Rares Teodorescu on 13/01/2017. + */ +final class RealmWrapper { + + Realm realm; + + RealmWrapper() { + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/Repository.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/Repository.java new file mode 100644 index 0000000..1055ebd --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/Repository.java @@ -0,0 +1,28 @@ +package com.p3.bewoplaner.data.repository; + +import com.p3.bewoplaner.data.repository.specifications.Specification; + +import java.util.List; + +import rx.Completable; +import rx.Observable; + +/** + * Created by Rares Teodorescu on 11/01/2017. + */ +interface Repository { + + Completable add(Iterable items); + + Completable update(T item, Specification specification); + + Completable update(Iterable items, Specification specification); + + Completable remove(Specification specification); + + Observable queryForOneById(String id, Specification specification); + + Observable> queryForAll(Specification specification); + + Completable removeAll(); +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/RepositoryModule.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/RepositoryModule.java new file mode 100644 index 0000000..0f80d72 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/RepositoryModule.java @@ -0,0 +1,96 @@ +package com.p3.bewoplaner.data.repository; + +import com.p3.bewoplaner.data.repository.mapper.Mapper; +import com.p3.bewoplaner.data.repository.models.GroupRealm; +import com.p3.bewoplaner.data.repository.models.MessageRealm; +import com.p3.bewoplaner.data.repository.models.UserRealm; +import com.p3.bewoplaner.di.scopes.ApplicationScope; +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.model.User; + +import dagger.Module; +import dagger.Provides; +import io.realm.RealmConfiguration; + +/** + * Created by Rares Teodorescu on 11/01/2017. + */ +@Module +public class RepositoryModule { + + @Provides + @ApplicationScope + RealmConfiguration provideRealmConfiguration() { + return new RealmConfiguration.Builder() + .deleteRealmIfMigrationNeeded() + .build(); + } + + @Provides + @ApplicationScope + RealmProvider provideRealm(RealmConfiguration realmConfiguration) { + return new RealmProvider(realmConfiguration); + } + + @Provides + @ApplicationScope + Mapper provideToGroupRealmMapper() { + return GroupRealm::createFromGroup; + } + + @Provides + @ApplicationScope + Mapper provideToGroupMapper() { + return Group::createFromGroupRealm; + } + + @Provides + @ApplicationScope + GroupRealmRepository provideGroupRealmRepository(RealmProvider realmProvider, + Mapper toRealmMapper, + Mapper toGroupMapper) { + return new GroupRealmRepository(realmProvider, toRealmMapper, toGroupMapper); + } + + @Provides + @ApplicationScope + Mapper provideToUserRealmMapper() { + return UserRealm::createFromUser; + } + + @Provides + @ApplicationScope + Mapper provideToUserMapper() { + return User::createFromUserRealm; + } + + @Provides + @ApplicationScope + UserRealmRepository provideUserRealmRepository(RealmProvider realmProvider, + Mapper toRealmMapper, + Mapper toUserMapper) { + return new UserRealmRepository(realmProvider, toRealmMapper, toUserMapper); + } + + @Provides + @ApplicationScope + Mapper provideToMessageRealmMapper() { + return MessageRealm::createFromMessage; + } + + @Provides + @ApplicationScope + Mapper provideToMessageMapper() { + return Message::createFromMessageRealm; + } + + @Provides + @ApplicationScope + MessageRealmRepository provideMessageRealmRepository(RealmProvider realmProvider, + Mapper toRealmMapper, + Mapper toUserMapper) { + return new MessageRealmRepository(realmProvider, toRealmMapper, toUserMapper); + } + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/UserRealmRepository.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/UserRealmRepository.java new file mode 100644 index 0000000..bcf2c05 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/UserRealmRepository.java @@ -0,0 +1,196 @@ +package com.p3.bewoplaner.data.repository; + +import com.p3.bewoplaner.data.repository.mapper.Mapper; +import com.p3.bewoplaner.data.repository.models.UserRealm; +import com.p3.bewoplaner.data.repository.specifications.RealmSpecification; +import com.p3.bewoplaner.data.repository.specifications.Specification; +import com.p3.bewoplaner.data.repository.specifications.UserByIdSpecification; +import com.p3.bewoplaner.model.User; + +import android.util.Log; + +import java.util.List; +import java.util.concurrent.Callable; + +import io.realm.Sort; +import rx.Completable; +import rx.Observable; +import rx.functions.Func1; + +/** + * Created by imarneanu on 3/6/17. + */ +public class UserRealmRepository implements Repository { + private final RealmProvider mRealmProvider; + private final Mapper mToRealmMapper; + private final Mapper mToUserMapper; + + public UserRealmRepository(RealmProvider realmProvider, + Mapper toRealmMapper, + Mapper toUserMapper) { + mRealmProvider = realmProvider; + mToRealmMapper = toRealmMapper; + mToUserMapper = toUserMapper; + } + + @Override + public Completable add(Iterable items) { + final RealmWrapper realmWrapper = new RealmWrapper(); + return Observable.from(items) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .map(mToRealmMapper::from) + .doOnNext(userRealm -> realmWrapper.realm.insert(userRealm)) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }) + .toCompletable(); + } + + @Override + public Completable update(User item, Specification specification) { + RealmWrapper realmWrapper = new RealmWrapper(); + UserByIdSpecification realmSpecification = (UserByIdSpecification) specification; + return Observable.just(item) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .map(mToRealmMapper::from) + .doOnNext(newUser -> { + realmSpecification.setUserId(newUser.getId()); + UserRealm userToUpdate = + realmSpecification.toRealmQuery(realmWrapper.realm).findFirst(); + if (userToUpdate != null) { + newUser.setId(userToUpdate.getId()); + } + realmWrapper.realm.copyToRealmOrUpdate(newUser); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }) + .toCompletable(); + } + + @Override + public Completable update(Iterable items, Specification specification) { + final RealmWrapper realmWrapper = new RealmWrapper(); + UserByIdSpecification realmSpecification = (UserByIdSpecification) specification; + return Observable.from(items) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .map(mToRealmMapper::from) + .doOnNext(newUser -> { + realmSpecification.setUserId(newUser.getId()); + UserRealm userToUpdate = + realmSpecification.toRealmQuery(realmWrapper.realm).findFirst(); + if (userToUpdate != null) { + newUser.setId(userToUpdate.getId()); + } + realmWrapper.realm.copyToRealmOrUpdate(newUser); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }) + .toCompletable(); + } + + @Override + public Completable remove(Specification specification) { + return null; + } + + @Override + public Observable queryForOneById(String id, Specification specification) { + RealmWrapper realmWrapper = new RealmWrapper(); + UserByIdSpecification realmSpecification = (UserByIdSpecification) specification; + Log.e("USER", "long value: " + Long.parseLong(id)); + ((UserByIdSpecification) specification).setUserId(Long.parseLong(id)); + return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm).findFirst()) + .map(mToUserMapper::from) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }); + } + + @Override + public Observable> queryForAll(Specification specification) { + RealmWrapper realmWrapper = new RealmWrapper(); + RealmSpecification realmSpecification = (RealmSpecification) specification; + return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm) + .findAllSorted(UserRealm.FIRST_NAME, Sort.ASCENDING)) + .flatMapIterable(userRealms -> userRealms) + .flatMap(new Func1>() { + @Override + public Observable call(UserRealm userRealm) { + return Observable.just(mToUserMapper.from(userRealm)); + } + }) + .toList() + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }); + } + + @Override + public Completable removeAll() { + RealmWrapper realmWrapper = new RealmWrapper(); + return Observable.fromCallable(new Callable() { + @Override + public Void call() throws Exception { + realmWrapper.realm.delete(UserRealm.class); + return null; + } + }) + .doOnSubscribe(() -> { + realmWrapper.realm = mRealmProvider.provide(); + realmWrapper.realm.beginTransaction(); + }) + .doOnError(throwable -> { + realmWrapper.realm.cancelTransaction(); + realmWrapper.realm.close(); + }) + .doOnCompleted(() -> { + realmWrapper.realm.commitTransaction(); + realmWrapper.realm.close(); + }) + .toCompletable(); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/mapper/Mapper.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/mapper/Mapper.java new file mode 100644 index 0000000..4f89b49 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/mapper/Mapper.java @@ -0,0 +1,10 @@ +package com.p3.bewoplaner.data.repository.mapper; + +/** + * Class responsible for mapping one object to another. + *

+ * Created by Rares Teodorescu on 11/01/2017. + */ +public interface Mapper { + T from(F from); +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/models/GroupRealm.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/models/GroupRealm.java new file mode 100644 index 0000000..cd6b878 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/models/GroupRealm.java @@ -0,0 +1,145 @@ +package com.p3.bewoplaner.data.repository.models; + +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.model.User; + +import java.util.ArrayList; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; + +/** + * Created by imarneanu on 3/6/17. + */ +public class GroupRealm extends RealmObject { + + public static final String ID = "id"; + public static final String NAME = "name"; + public static final String CREATED_AT = "createdAt"; + public static final String USER_ID = "userId"; + + @PrimaryKey + private String uniqueId; + private long id; + private String name; + private String avatar; + private MessageRealm message; + private String mobile; + private boolean onlyEmployees; + private RealmList userIdList; + private long createdAt; + private long userId; + + public GroupRealm() { + } + + public GroupRealm(long id, String name, String avatar, Message message, String mobile, boolean onlyEmployees, ArrayList userList, long userId) { + this.userIdList = new RealmList<>(); + this.id = id; + this.name = name; + this.avatar = avatar; + this.message = MessageRealm.createFromMessage(message); + this.createdAt = this.message != null ? this.message.getCreatedAt() : 0; + this.onlyEmployees = onlyEmployees; + this.mobile = mobile; + if (userList != null) { + for (User l : userList) { + this.userIdList.add(UserRealm.createFromUser(l)); + } + } + this.userId = userId; + this.uniqueId = id + "_" + userId; + } + + public long getUserId() { + return userId; + } + + public static GroupRealm createFromGroup(Group group) { + if (group == null) { + return null; + } + return new GroupRealm(group.id, group.name, group.avatar, group.lastMessage, group.mobile, group.onlyEmployees, group.users, group.userId); + } + + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + } + + public String getUniqueId() { + return uniqueId; + } + + public void setId(long id) { + this.id = id; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getAvatar() { + return avatar; + } + + public boolean isOnlyEmployees() { + return onlyEmployees; + } + + public MessageRealm getMessage() { + return message; + } + + public void setMessage(MessageRealm message) { + this.message = message; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public void setOnlyEmployees(boolean onlyEmployees) { + this.onlyEmployees = onlyEmployees; + } + + public String getMobile() { + return mobile; + } + + public void setName(String name) { + this.name = name; + } + + public void setMobile(String mobile) { + this.mobile = mobile; + } + + public RealmList getUserIdList() { + return userIdList; + } + + public void setUserIdList(RealmList userIdList) { + this.userIdList = userIdList; + } + + @Override + public String toString() { + return "GroupRealm{" + + "id=" + id + + ", name='" + name + '\'' + + ", uniqueId" + uniqueId + + ", avatar='" + avatar + '\'' + + ", message=" + message + + ", mobile='" + mobile + '\'' + + ", onlyEmployees=" + onlyEmployees + + ", userIdList=" + userIdList + + ", createdAt=" + createdAt + + '}'; + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/models/MessageRealm.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/models/MessageRealm.java new file mode 100644 index 0000000..6474fd6 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/models/MessageRealm.java @@ -0,0 +1,227 @@ +package com.p3.bewoplaner.data.repository.models; + +import com.p3.bewoplaner.model.Message; + +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; + +/** + * Created by Rares Teodorescu on 22/02/2017. + */ +public class MessageRealm extends RealmObject { + + public static final String ID = "id"; + public static final String GROUP_ID = "groupId"; + public static final String CREATED_AT = "createdAt"; + public static final String USER_ID = "userId"; + + @PrimaryKey + private String uniqueId; + private String id; + private long groupId; + private String message; + private String filePath; + private String smallerImage; + private String filename; + private String uri; + private long createdAt; + private String senderAvatar; + private String senderName; + private long senderId; + private int status; + private boolean isRead; + private long userId; + private boolean fileDownloaded; + + public MessageRealm() { + } + + public MessageRealm(String id, + int groupId, + String message, + String filePath, + String smallerImage, + String filename, + String uri, + long createdAt, + int senderId, + String senderName, + String senderAvatar, + Message.Status status, + boolean isRead, + long userId, + boolean fileDownloaded) { + this.id = id; + this.groupId = groupId; + this.message = message; + this.filePath = filePath; + this.smallerImage = smallerImage; + this.filename = filename; + this.uri = uri; + this.createdAt = createdAt; + this.senderId = senderId; + this.senderName = senderName; + this.senderAvatar = senderAvatar; + this.status = Message.Status.getValue(status); + this.isRead = isRead; + this.userId = userId; + this.fileDownloaded = fileDownloaded; + } + + public long getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(long createdAt) { + this.createdAt = createdAt; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public String getId() { + return id; + } + + public String getUniqueId() { + return uniqueId; + } + + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + } + + public void setId(String id) { + this.id = id; + } + + public static String getID() { + return ID; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSenderAvatar() { + return senderAvatar; + } + + public void setSenderAvatar(String senderAvatar) { + this.senderAvatar = senderAvatar; + } + + public long getSenderId() { + return senderId; + } + + public void setSenderId(long senderId) { + this.senderId = senderId; + } + + public String getSenderName() { + return senderName; + } + + public void setSenderName(String senderName) { + this.senderName = senderName; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public long getGroupId() { + return groupId; + } + + public void setGroupId(long groupId) { + this.groupId = groupId; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public String getUri() { + return uri; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public boolean isRead() { + return isRead; + } + + public void setRead(boolean read) { + isRead = read; + } + + public boolean isFileDownloaded() { + return fileDownloaded; + } + + public void setFileDownloaded(boolean fileDownloaded) { + this.fileDownloaded = fileDownloaded; + } + + public String getSmallerImage() { + return smallerImage; + } + + public void setSmallerImage(String smallerImage) { + this.smallerImage = smallerImage; + } + + public static MessageRealm createFromMessage(Message message) { + if (message == null) { + return null; + } + MessageRealm messageRealm = new MessageRealm(); + messageRealm.setId(message.id); + messageRealm.setGroupId(message.groupId); + messageRealm.setMessage(message.message); + messageRealm.setFilePath(message.filePath); + messageRealm.setSmallerImage(message.smallerImage); + messageRealm.setFilename(message.originalFilename); + messageRealm.setUri(message.uri); + messageRealm.setCreatedAt(message.createdAt * 1000); + messageRealm.setSenderId(message.senderId); + messageRealm.setSenderName(message.senderName); + messageRealm.setSenderAvatar(message.senderAvatar); + messageRealm.setStatus(Message.Status.getValue(message.status)); + messageRealm.setRead(message.isRead); + messageRealm.userId = message.userId; + messageRealm.uniqueId = getUniqueId(message.id, message.userId); + messageRealm.fileDownloaded = message.fileDownloaded; + return messageRealm; + } + + public static String getUniqueId(String messageId, long userId) { + return messageId + "_" + userId; + } + + public long getUserId() { + return userId; + } + +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/models/UserRealm.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/models/UserRealm.java new file mode 100644 index 0000000..86292bb --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/models/UserRealm.java @@ -0,0 +1,74 @@ +package com.p3.bewoplaner.data.repository.models; + +import com.p3.bewoplaner.model.User; + +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; + +/** + * Created by imarneanu on 3/6/17. + */ +public class UserRealm extends RealmObject { + public static final String ID = "id"; + public static final String FIRST_NAME = "firstName"; + + @PrimaryKey + private long id; + private String firstName; + private String lastName; + private String avatar; + private String mobile; + private boolean isEmployee; + + public UserRealm() { + } + + public UserRealm(long id, + String firstName, + String lastName, + String avatar, + String mobile, + boolean isEmployee) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.avatar = avatar; + this.mobile = mobile; + this.isEmployee = isEmployee; + } + + public void setId(long id) { + this.id = id; + } + + public long getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getAvatar() { + return avatar; + } + + public String getMobile() { + return mobile; + } + + public boolean isEmployee() { + return isEmployee; + } + + public static UserRealm createFromUser(User user) { + if (user == null) { + return null; + } + return new UserRealm(user.id, user.firstName, user.lastName, user.avatar, user.mobile, user.isEmployee); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/GroupByIdSpecification.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/GroupByIdSpecification.java new file mode 100644 index 0000000..678b491 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/GroupByIdSpecification.java @@ -0,0 +1,30 @@ +package com.p3.bewoplaner.data.repository.specifications; + +import com.p3.bewoplaner.data.repository.models.GroupRealm; + +import io.realm.Realm; +import io.realm.RealmQuery; + +/** + * Created by imarneanu on 3/6/17. + */ +public class GroupByIdSpecification implements RealmSpecification { + + private long mGroupId; + private final long mUserId; + + public GroupByIdSpecification(long userId) { + mUserId = userId; + } + + public void setGroupId(long groupId) { + mGroupId = groupId; + } + + @Override + public RealmQuery toRealmQuery(Realm realm) { + return realm.where(GroupRealm.class) + .equalTo(GroupRealm.ID, mGroupId) + .equalTo(GroupRealm.USER_ID, mUserId); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/GroupByNameSpecification.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/GroupByNameSpecification.java new file mode 100644 index 0000000..fbcb139 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/GroupByNameSpecification.java @@ -0,0 +1,28 @@ +package com.p3.bewoplaner.data.repository.specifications; + +import com.p3.bewoplaner.data.repository.models.GroupRealm; + +import io.realm.Case; +import io.realm.Realm; +import io.realm.RealmQuery; + +/** + * Created by Rares Teodorescu on 10/03/2017. + */ +public class GroupByNameSpecification implements RealmSpecification { + + private final String mName; + private final long mUserId; + + public GroupByNameSpecification(String name, long userId) { + mName = name; + mUserId = userId; + } + + @Override + public RealmQuery toRealmQuery(Realm realm) { + return realm.where(GroupRealm.class) + .equalTo(GroupRealm.USER_ID, mUserId) + .contains(GroupRealm.NAME, mName, Case.INSENSITIVE); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/GroupSpecification.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/GroupSpecification.java new file mode 100644 index 0000000..29cd170 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/GroupSpecification.java @@ -0,0 +1,23 @@ +package com.p3.bewoplaner.data.repository.specifications; + +import com.p3.bewoplaner.data.repository.models.GroupRealm; + +import io.realm.Realm; +import io.realm.RealmQuery; + +/** + * Created by imarneanu on 3/6/17. + */ +public class GroupSpecification implements RealmSpecification { + + private final long mUserId; + + public GroupSpecification(long userId) { + mUserId = userId; + } + + @Override + public RealmQuery toRealmQuery(Realm realm) { + return realm.where(GroupRealm.class).equalTo(GroupRealm.USER_ID, mUserId); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/MessageByGroupIdSpecification.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/MessageByGroupIdSpecification.java new file mode 100644 index 0000000..293361b --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/MessageByGroupIdSpecification.java @@ -0,0 +1,30 @@ +package com.p3.bewoplaner.data.repository.specifications; + +import com.p3.bewoplaner.data.repository.models.MessageRealm; + +import io.realm.Realm; +import io.realm.RealmQuery; + +/** + * Created by Rares Teodorescu on 22/02/2017. + */ +public class MessageByGroupIdSpecification implements RealmSpecification { + + private long mGroupId; + private final long mUserId; + + public MessageByGroupIdSpecification(long userId) { + mUserId = userId; + } + + public void setGroupId(long groupId) { + mGroupId = groupId; + } + + @Override + public RealmQuery toRealmQuery(Realm realm) { + return realm.where(MessageRealm.class) + .equalTo(MessageRealm.GROUP_ID, mGroupId) + .equalTo(MessageRealm.USER_ID, mUserId); + } +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/MessageByIdSpecification.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/MessageByIdSpecification.java new file mode 100644 index 0000000..976963b --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/MessageByIdSpecification.java @@ -0,0 +1,34 @@ +package com.p3.bewoplaner.data.repository.specifications; + +import com.p3.bewoplaner.data.repository.models.MessageRealm; + +import io.realm.Realm; +import io.realm.RealmQuery; + +/** + * Created by Rares Teodorescu on 22/02/2017. + */ +public class MessageByIdSpecification implements RealmSpecification { + + private String mMessageId; + private long mUserId; + + public MessageByIdSpecification(long userId) { + mUserId = userId; + } + + public long getUserId() { + return mUserId; + } + + public void setMessageId(String messageId) { + mMessageId = messageId; + } + + @Override + public RealmQuery toRealmQuery(Realm realm) { + return realm.where(MessageRealm.class) + .equalTo(MessageRealm.ID, mMessageId) + .equalTo(MessageRealm.USER_ID, mUserId); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/RealmSpecification.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/RealmSpecification.java new file mode 100644 index 0000000..d17fbd7 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/RealmSpecification.java @@ -0,0 +1,12 @@ +package com.p3.bewoplaner.data.repository.specifications; + +import io.realm.Realm; +import io.realm.RealmObject; +import io.realm.RealmQuery; + +/** + * Created by Rares Teodorescu on 11/01/2017. + */ +public interface RealmSpecification extends Specification { + RealmQuery toRealmQuery(Realm realm); +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/Specification.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/Specification.java new file mode 100644 index 0000000..bce08be --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/Specification.java @@ -0,0 +1,7 @@ +package com.p3.bewoplaner.data.repository.specifications; + +/** + * Created by Rares Teodorescu on 11/01/2017. + */ +public interface Specification { +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/UserByIdSpecification.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/UserByIdSpecification.java new file mode 100644 index 0000000..cbb4b65 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/data/repository/specifications/UserByIdSpecification.java @@ -0,0 +1,24 @@ +package com.p3.bewoplaner.data.repository.specifications; + +import com.p3.bewoplaner.data.repository.models.UserRealm; + +import io.realm.Realm; +import io.realm.RealmQuery; + +/** + * Created by imarneanu on 3/6/17. + */ +public class UserByIdSpecification implements RealmSpecification { + + private long mUserId; + + public void setUserId(long userId) { + mUserId = userId; + } + + @Override + public RealmQuery toRealmQuery(Realm realm) { + return realm.where(UserRealm.class) + .equalTo(UserRealm.ID, mUserId); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/ApplicationComponent.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/ApplicationComponent.java new file mode 100644 index 0000000..2911fb6 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/ApplicationComponent.java @@ -0,0 +1,98 @@ +package com.p3.bewoplaner.di; + +import com.bumptech.glide.Glide; +import com.p3.bewoplaner.BewoplanerApplication; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.api.ApiModule; +import com.p3.bewoplaner.data.prefs.AppPrefs; +import com.p3.bewoplaner.data.prefs.AppPrefsModule; +import com.p3.bewoplaner.data.prefs.BooleanPreference; +import com.p3.bewoplaner.data.prefs.LongPreference; +import com.p3.bewoplaner.data.prefs.StringPreference; +import com.p3.bewoplaner.data.repository.GroupRealmRepository; +import com.p3.bewoplaner.data.repository.MessageRealmRepository; +import com.p3.bewoplaner.data.repository.RealmProvider; +import com.p3.bewoplaner.data.repository.RepositoryModule; +import com.p3.bewoplaner.data.repository.UserRealmRepository; +import com.p3.bewoplaner.di.scopes.ApplicationScope; +import com.p3.bewoplaner.utils.SystemUtils; +import com.securepreferences.SecurePreferences; + +import android.app.NotificationManager; +import android.content.Context; +import android.content.SharedPreferences; + +import javax.inject.Named; + +import dagger.Component; +import retrofit2.Retrofit; + +/** + * Created by imarneanu on 2/9/17. + */ +@ApplicationScope +@Component(modules = {ApplicationModule.class, AppPrefsModule.class, ApiModule.class, RepositoryModule.class}) +public interface ApplicationComponent { + void inject(BewoplanerApplication conferenceApplication); + + @P3DSEndpoint + Retrofit provideP3DSRetrofit(); + + @VoucherEndpoint + Retrofit provideVoucherRetrofit(); + + SystemUtils provideSystemUtils(); + + @AppPrefs + SecurePreferences provideSecuredPrefs(); + + @Named(AppPrefsConstants.API_ENDPOINT) + StringPreference provideApiEndpoint(); + + @Named(AppPrefsConstants.LOGIN_TOKEN) + StringPreference provideLoginToken(); + + @Named(AppPrefsConstants.SAVE_PASSWORD) + BooleanPreference provideSavePassword(); + + @Named(AppPrefsConstants.CHAT_CODE) + StringPreference provideChatCode(); + + @Named(AppPrefsConstants.ACCOUNT_NO) + StringPreference provideAccountNumber(); + + @Named(AppPrefsConstants.USER_ID) + LongPreference provideUserId(); + + @Named(AppPrefsConstants.AVATAR) + StringPreference provideAvatar(); + + @Named(AppPrefsConstants.NAME) + StringPreference provideProfileName(); + + @Named(AppPrefsConstants.PHONE_NUMBER) + StringPreference providePhoneNumber(); + + @Named(AppPrefsConstants.CHAT_OPENED) + BooleanPreference provideChatOpened(); + + @Named(AppPrefsConstants.APP_KILLED) + BooleanPreference provideAppKilled(); + + NotificationManager provideNotificationManager(); + + GroupRealmRepository provideGroupRealmRepository(); + + UserRealmRepository provideUserRealmRepository(); + + MessageRealmRepository provideMessageRealmRepository(); + + RealmProvider provideRealmProvider(); + + @AppPrefs + SharedPreferences provideSharedPreferences(); + + Glide provideGlide(); + + Context provideContext(); +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/ApplicationModule.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/ApplicationModule.java new file mode 100644 index 0000000..80b0839 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/ApplicationModule.java @@ -0,0 +1,61 @@ +package com.p3.bewoplaner.di; + +import com.bumptech.glide.Glide; +import com.p3.bewoplaner.BewoplanerApplication; +import com.p3.bewoplaner.di.scopes.ApplicationScope; +import com.p3.bewoplaner.utils.SystemUtils; + +import android.app.NotificationManager; +import android.content.Context; +import android.net.ConnectivityManager; + +import dagger.Module; +import dagger.Provides; + +/** + * Created by imarneanu on 2/9/17. + */ +@Module +public class ApplicationModule { + private final Context mAppContext; + + public ApplicationModule(Context appContext) { + mAppContext = appContext.getApplicationContext(); + } + + @Provides + @ApplicationScope + Context provideContext() { + return mAppContext; + } + + @Provides + @ApplicationScope + BewoplanerApplication providesBewoplanerApplication() { + return (BewoplanerApplication) mAppContext; + } + + @Provides + @ApplicationScope + SystemUtils provideSystemUtils(ConnectivityManager connectivityManager) { + return new SystemUtils(connectivityManager); + } + + @Provides + @ApplicationScope + ConnectivityManager provideConnectivityManager() { + return (ConnectivityManager) mAppContext.getSystemService(Context.CONNECTIVITY_SERVICE); + } + + @Provides + @ApplicationScope + NotificationManager provideNotificationManager() { + return (NotificationManager) mAppContext.getSystemService(Context.NOTIFICATION_SERVICE); + } + + @Provides + @ApplicationScope + Glide provideGlide() { + return Glide.get(mAppContext); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/InjectionHelper.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/InjectionHelper.java new file mode 100644 index 0000000..3af5f8a --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/InjectionHelper.java @@ -0,0 +1,120 @@ +package com.p3.bewoplaner.di; + +import com.p3.bewoplaner.BewoplanerApplication; +import com.p3.bewoplaner.data.firebase.DaggerMessagingComponent; +import com.p3.bewoplaner.data.firebase.MessagingComponent; +import com.p3.bewoplaner.page.chat.ChatComponent; +import com.p3.bewoplaner.page.chat.DaggerChatComponent; +import com.p3.bewoplaner.page.contacts.ContactsComponent; +import com.p3.bewoplaner.page.contacts.DaggerContactsComponent; +import com.p3.bewoplaner.page.login.DaggerLoginComponent; +import com.p3.bewoplaner.page.login.LoginComponent; +import com.p3.bewoplaner.page.main.DaggerMainComponent; +import com.p3.bewoplaner.page.main.MainComponent; +import com.p3.bewoplaner.page.profile.DaggerProfileComponent; +import com.p3.bewoplaner.page.profile.ProfileComponent; +import com.p3.bewoplaner.page.splash.DaggerSplashComponent; +import com.p3.bewoplaner.page.splash.SplashComponent; + +import android.content.Context; + +/** + * Created by imarneanu on 2/9/17. + */ +public class InjectionHelper { + + private static SplashComponent sSplashComponent; + private static ContactsComponent sContactsComponent; + private static LoginComponent sLoginComponent; + private static MainComponent sMainComponent; + private static MessagingComponent sMessagingComponent; + private static ChatComponent sChatComponent; + private static ProfileComponent sProfileComponent; + + public static ApplicationComponent getApplicationComponent(Context context) { + return ((BewoplanerApplication) context.getApplicationContext()).getApplicationComponent(); + } + + public static SplashComponent getSplashComponent(Context context) { + if (sSplashComponent == null) { + sSplashComponent = DaggerSplashComponent.builder() + .applicationComponent(getApplicationComponent(context)) + .build(); + } + return sSplashComponent; + } + + public static LoginComponent getLoginComponent(Context context) { + if (sLoginComponent == null) { + sLoginComponent = DaggerLoginComponent.builder() + .applicationComponent(getApplicationComponent(context)) + .build(); + } + return sLoginComponent; + } + + public static MainComponent getMainComponent(Context context) { + if (sMainComponent == null) { + sMainComponent = DaggerMainComponent.builder() + .applicationComponent(getApplicationComponent(context)) + .build(); + } + return sMainComponent; + } + + public static ContactsComponent getContactsComponent(Context context) { + if (sContactsComponent == null) { + sContactsComponent = DaggerContactsComponent.builder() + .applicationComponent(getApplicationComponent(context)) + .build(); + } + return sContactsComponent; + } + + public static MessagingComponent getMessagingComponent(Context context) { + if (sMessagingComponent == null) { + sMessagingComponent = DaggerMessagingComponent.builder() + .applicationComponent(getApplicationComponent(context)) + .build(); + } + return sMessagingComponent; + } + + public static ProfileComponent getProfileComponent(Context context) { + if (sProfileComponent == null) { + sProfileComponent = DaggerProfileComponent.builder() + .applicationComponent(getApplicationComponent(context)) + .build(); + } + return sProfileComponent; + } + + public static ChatComponent getChatComponent(Context context) { + if (sChatComponent == null) { + sChatComponent = DaggerChatComponent.builder() + .applicationComponent(getApplicationComponent(context)) + .build(); + } + return sChatComponent; + } + + public static void destroySplashComponent() { + sSplashComponent = null; + } + + public static void destroyMainComponents() { + sContactsComponent = null; + sMainComponent = null; + sContactsComponent = null; + sProfileComponent = null; + sMessagingComponent = null; + } + + public static void destroyChatComponent() { + sChatComponent = null; + } + + public static void destroyLoginComponent() { + sLoginComponent = null; + } +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/P3DSEndpoint.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/P3DSEndpoint.java new file mode 100644 index 0000000..9b4b3b2 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/P3DSEndpoint.java @@ -0,0 +1,14 @@ +package com.p3.bewoplaner.di; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +/** + * Created by Rares Teodorescu on 24/03/2017. + */ +@Qualifier +@Retention(RetentionPolicy.SOURCE) +public @interface P3DSEndpoint { +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/VoucherEndpoint.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/VoucherEndpoint.java new file mode 100644 index 0000000..f9b2197 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/VoucherEndpoint.java @@ -0,0 +1,14 @@ +package com.p3.bewoplaner.di; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +/** + * Created by Rares Teodorescu on 24/03/2017. + */ +@Qualifier +@Retention(RetentionPolicy.SOURCE) +public @interface VoucherEndpoint { +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/scopes/ActivityScope.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/scopes/ActivityScope.java new file mode 100644 index 0000000..3717bdb --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/scopes/ActivityScope.java @@ -0,0 +1,17 @@ +package com.p3.bewoplaner.di.scopes; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Scope; + +/** + * Created by imarneanu on 2/9/17. + */ +@Scope +@Inherited +@Retention(RetentionPolicy.RUNTIME) +public @interface ActivityScope { +} + diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/scopes/ApplicationScope.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/scopes/ApplicationScope.java new file mode 100644 index 0000000..dd57a56 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/di/scopes/ApplicationScope.java @@ -0,0 +1,16 @@ +package com.p3.bewoplaner.di.scopes; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Scope; + +/** + * Created by imarneanu on 2/9/17. + */ +@Scope +@Inherited +@Retention(RetentionPolicy.RUNTIME) +public @interface ApplicationScope { +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/model/Group.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/model/Group.java new file mode 100644 index 0000000..4c40178 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/model/Group.java @@ -0,0 +1,148 @@ +package com.p3.bewoplaner.model; + +import com.google.gson.annotations.SerializedName; + +import com.p3.bewoplaner.data.repository.models.GroupRealm; +import com.p3.bewoplaner.data.repository.models.MessageRealm; +import com.p3.bewoplaner.data.repository.models.UserRealm; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; + +import io.realm.RealmList; + +/** + * Created by imarneanu on 2/21/17. + */ +public class Group implements Parcelable { + + @SerializedName("oid") + public long id; + @SerializedName("name") + public String name; + @SerializedName("avatar") + public String avatar; + @SerializedName("mobile") + public String mobile; + @SerializedName("lastmessage") + public Message lastMessage; + @SerializedName("users") + public ArrayList users; + @SerializedName("onlyemployees") + public boolean onlyEmployees; + + private boolean isRead; + public long userId; + + public boolean isHelpGroup() { + return !onlyEmployees; + } + + public Group() { + } + + public Group(long id, String name, String avatar, String mobile, MessageRealm lastMessage, RealmList users, boolean onlyEmployees) { + this.users = new ArrayList<>(); + this.id = id; + this.name = name; + this.avatar = avatar; + this.mobile = mobile; + this.lastMessage = Message.createFromMessageRealm(lastMessage); + this.isRead = this.lastMessage != null && this.lastMessage.isRead; + for (UserRealm user : users) { + this.users.add(new User(user.getId(), user.getFirstName(), user.getLastName(), user.getAvatar(), user.getMobile(), user.isEmployee())); + } + this.onlyEmployees = onlyEmployees; + } + + public String getContactsNames() { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < users.size(); i++) { + stringBuilder.append(users.get(i).getName()).append(", "); + } + int length = stringBuilder.length(); + if (length > 1) { + stringBuilder.delete(length - 2, length - 1); + } + return stringBuilder.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.id); + dest.writeString(this.name); + dest.writeByte(this.onlyEmployees ? (byte) 1 : (byte) 0); + dest.writeList(this.users); + dest.writeString(this.avatar); + dest.writeString(this.mobile); + } + + public boolean isRead() { + return lastMessage != null && lastMessage.isRead; + } + + public static Group createFromGroupRealm(GroupRealm groupRealm) { + if (groupRealm == null) { + return null; + } + return new Group(groupRealm.getId(), + groupRealm.getName(), + groupRealm.getAvatar(), + groupRealm.getMobile(), + groupRealm.getMessage(), + groupRealm.getUserIdList(), + groupRealm.isOnlyEmployees()); + } + + protected Group(Parcel in) { + this.id = in.readLong(); + this.name = in.readString(); + this.onlyEmployees = in.readByte() != 0; + this.users = new ArrayList<>(); + in.readList(this.users, User.class.getClassLoader()); + this.avatar = in.readString(); + this.mobile = in.readString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Group createFromParcel(Parcel source) { + return new Group(source); + } + + @Override + public Group[] newArray(int size) { + return new Group[size]; + } + }; + + @Override + public String toString() { + return "Group{" + + "id=" + id + + ", name='" + name + '\'' + + ", avatar='" + avatar + '\'' + + ", mobile='" + mobile + '\'' + + ", lastMessage=" + lastMessage + + ", users=" + users + + ", onlyEmployees=" + onlyEmployees + + ", isRead=" + isRead + + '}'; + } + + public boolean isEmployee(ArrayList users) { + for (int i = 0; i < users.size(); i++) { + if (name.equals(users.get(i).getName())) { + return users.get(i).isEmployee; + } + } + return false; + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/model/Message.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/model/Message.java new file mode 100644 index 0000000..3ff5e2b --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/model/Message.java @@ -0,0 +1,238 @@ +package com.p3.bewoplaner.model; + +import com.google.gson.annotations.SerializedName; + +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.data.repository.models.MessageRealm; + +import java.util.ArrayList; + +/** + * Created by imarneanu on 2/21/17. + */ +public class Message { + + private static final int STATUS_DELIVERED = 0; + private static final int STATUS_FAILED = 1; + private static final int STATUS_PENDING = 2; + + @SerializedName("id") + public String id; + @SerializedName("userid") + public long senderId; + @SerializedName("user_name") + public String senderName; + @SerializedName("user_picture") + public String senderAvatar; + @SerializedName("groupid") + public long groupId; + @SerializedName("text") + public String message; + @SerializedName("original_filename") + public String originalFilename; + @SerializedName("file") + public String filePath; + @SerializedName("smaller_image") + public String smallerImage; + @SerializedName("created_at") + public long createdAt; + + public Status status; + public String uri; + public boolean isRead; + public long userId; + public boolean fileDownloaded; + + public Message() { + } + + public Message(String id, + long groupId, + long createdAt, + String message, + String filePath, + String smallerImage, + String filename, + String uri, + long senderId, + String senderName, + String senderAvatar, + Status status, + boolean isRead, + long userId, + boolean fileDownloaded) { + this.id = id; + this.groupId = groupId; + this.createdAt = createdAt; + this.message = message; + this.filePath = filePath; + this.originalFilename = filename; + this.smallerImage = smallerImage; + this.uri = uri; + this.senderId = senderId; + this.senderName = senderName; + this.senderAvatar = senderAvatar; + this.status = status; + this.isRead = isRead; + this.userId = userId; + this.fileDownloaded = fileDownloaded; + } + + public String getUniqueId() { + return MessageRealm.getUniqueId(id, userId); + } + + public enum Status { + DELIVERED(R.drawable.message_delivered), FAILED(R.drawable.message_failed), PENDING(-1); + + private int resourceId; + + Status(int value) { + resourceId = value; + } + + public static Status getStatus(int value) { + switch (value) { + case STATUS_DELIVERED: + return DELIVERED; + case STATUS_FAILED: + return FAILED; + case STATUS_PENDING: + return PENDING; + default: + return FAILED; + } + } + + public static int getValue(Status status) { + if (status == null) { + return STATUS_DELIVERED; + } + switch (status) { + case DELIVERED: + return STATUS_DELIVERED; + case FAILED: + return STATUS_FAILED; + case PENDING: + return STATUS_PENDING; + } + return 1; + } + } + + public int getStatusResourceId() { + return status != null ? status.resourceId : 0; + } + + public static Message createFromMessageRealm(MessageRealm messageRealm) { + if (messageRealm == null) { + return null; + } + return new Message(messageRealm.getId(), + messageRealm.getGroupId(), + messageRealm.getCreatedAt(), + messageRealm.getMessage(), + messageRealm.getFilePath(), + messageRealm.getSmallerImage(), + messageRealm.getFilename(), + messageRealm.getUri(), + messageRealm.getSenderId(), + messageRealm.getSenderName(), + messageRealm.getSenderAvatar(), + Status.getStatus(messageRealm.getStatus()), + messageRealm.isRead(), + messageRealm.getUserId(), + messageRealm.isFileDownloaded()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Message message1 = (Message) o; + + if (senderId != message1.senderId) { + return false; + } + if (groupId != message1.groupId) { + return false; + } + if (createdAt != message1.createdAt && createdAt != message1.createdAt * 1000 && createdAt * 1000 != message1.createdAt) { + return false; + } + if (!id.equals(message1.id)) { + return false; + } + if (uri != null ? !uri.equals(message1.uri) : message1.uri != null) { + return false; + } + if (senderName != null ? !senderName.equals(message1.senderName) : message1.senderName != null) { + return false; + } + if (senderAvatar != null ? !senderAvatar.equals(message1.senderAvatar) : message1.senderAvatar != null) { + return false; + } + if (message != null ? !message.equals(message1.message) : message1.message != null) { + return false; + } + if (filePath != null ? !filePath.equals(message1.filePath) : message1.filePath != null) { + return false; + } + if (smallerImage != null ? !smallerImage.equals(message1.smallerImage) : message1.smallerImage != null) { + return false; + } + if (isRead != ((Message) o).isRead) { + return false; + } + return status == message1.status; + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + (int) (senderId ^ (senderId >>> 32)); + result = 31 * result + (senderName != null ? senderName.hashCode() : 0); + result = 31 * result + (senderAvatar != null ? senderAvatar.hashCode() : 0); + result = 31 * result + (uri != null ? uri.hashCode() : 0); + result = 31 * result + (int) (groupId ^ (groupId >>> 32)); + result = 31 * result + (message != null ? message.hashCode() : 0); + result = 31 * result + (filePath != null ? filePath.hashCode() : 0); + result = 31 * result + (smallerImage != null ? smallerImage.hashCode() : 0); + result = 31 * result + (int) (createdAt ^ (createdAt >>> 32)); + result = 31 * result + (status != null ? status.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Message{" + + "id='" + id + '\'' + + ", senderId=" + senderId + + ", senderName='" + senderName + '\'' + + ", senderAvatar='" + senderAvatar + '\'' + + ", groupId=" + groupId + + ", message='" + message + '\'' + + ", filePath='" + filePath + '\'' + + ", smallerImage='" + smallerImage + '\'' + + ", uri=" + uri + + ", createdAt=" + createdAt + + ", status=" + status + + ", isRead=" + isRead + + '}'; + } + + public boolean removeFromList(ArrayList messages) { + for (int index = 0, size = messages.size(); index < size; index++) { + if (id.equals(messages.get(index).id)) { + messages.remove(index); + return true; + } + } + return false; + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/model/User.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/model/User.java new file mode 100644 index 0000000..4536a3d --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/model/User.java @@ -0,0 +1,90 @@ +package com.p3.bewoplaner.model; + +import com.google.gson.annotations.SerializedName; + +import com.p3.bewoplaner.data.repository.models.UserRealm; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Created by laszloffiferenc on 2/21/17. + */ +public class User implements Parcelable { + + @SerializedName("oid") + public long id; + + @SerializedName("firstname") + public String firstName; + + @SerializedName("lastname") + public String lastName; + + @SerializedName("picture") + public String avatar; + + @SerializedName("mobile") + public String mobile; + + @SerializedName("token") + public String token; + + @SerializedName("is_employee") + public boolean isEmployee; + + public User(long id, String firstName, String lastName, String avatar, String mobile, boolean isEmployee) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.avatar = avatar; + this.mobile = mobile; + this.isEmployee = isEmployee; + } + + public String getName() { + return firstName.concat(" ").concat(lastName); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.firstName); + dest.writeString(this.lastName); + dest.writeString(this.avatar); + dest.writeString(this.mobile); + dest.writeByte(this.isEmployee ? (byte) 1 : (byte) 0); + } + + private User(Parcel in) { + this.firstName = in.readString(); + this.lastName = in.readString(); + this.avatar = in.readString(); + this.mobile = in.readString(); + this.isEmployee = in.readByte() != 0; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public User createFromParcel(Parcel source) { + return new User(source); + } + + @Override + public User[] newArray(int size) { + return new User[size]; + } + }; + + public static User createFromUserRealm(UserRealm userRealm) { + if (userRealm == null) { + return null; + } + return new User(userRealm.getId(), userRealm.getFirstName(), userRealm.getLastName(), + userRealm.getAvatar(), userRealm.getMobile(), userRealm.isEmployee()); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/model/common/BaseResponse.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/model/common/BaseResponse.java new file mode 100644 index 0000000..badc282 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/model/common/BaseResponse.java @@ -0,0 +1,39 @@ +package com.p3.bewoplaner.model.common; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * Created by laszloffiferenc on 2/21/17. + */ +public class BaseResponse { + @SerializedName("success") + public boolean success; + + @SerializedName("errors") + public Error errors; + + @Override + public String toString() { + return "BaseResponseModel{" + + ", success=" + success + + ", errors=" + errors + + '}'; + } + + public static class Error { + @SerializedName("login_failed") + public List loginFailed; + + @SerializedName("device_id") + public List deviceId; + + @SerializedName("fcm_token") + public List fcmToken; + + @SerializedName("token") + public String token; + + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/BaseChatActivity.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/BaseChatActivity.java new file mode 100644 index 0000000..86a3170 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/BaseChatActivity.java @@ -0,0 +1,490 @@ +package com.p3.bewoplaner.page.chat; + +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.events.InvalidTokenEvent; +import com.p3.bewoplaner.data.events.LogoutDirectlyEvent; +import com.p3.bewoplaner.data.prefs.BooleanPreference; +import com.p3.bewoplaner.data.prefs.LongPreference; +import com.p3.bewoplaner.di.InjectionHelper; +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.page.common.RuntimePermissionActivity; +import com.p3.bewoplaner.page.login.LoginPresenter; +import com.p3.bewoplaner.page.main.LogoutNavigation; +import com.p3.bewoplaner.utils.EndlessScrollListener; +import com.p3.bewoplaner.utils.FileUtils; +import com.p3.bewoplaner.utils.SystemUtils; +import com.p3.bewoplaner.utils.ui.EmptyLayout; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.graphics.Rect; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.PersistableBundle; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.Snackbar; +import android.support.v4.app.NotificationManagerCompat; +import android.support.v4.content.ContextCompat; +import android.support.v4.content.FileProvider; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.SimpleItemAnimator; +import android.text.Editable; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.File; +import java.util.ArrayList; + +import javax.inject.Inject; +import javax.inject.Named; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.OnTextChanged; +import hani.momanii.supernova_emoji_library.Actions.EmojIconActions; +import hani.momanii.supernova_emoji_library.Helper.EmojiconEditText; +import okhttp3.MediaType; + +import static com.p3.bewoplaner.data.AppPrefsConstants.USER_ID; + +/** + * Created by Rares Teodorescu on 21/02/2017. + */ +abstract class BaseChatActivity extends RuntimePermissionActivity + implements ChatView, BaseChatAdapter.MessageClickListener, LogoutNavigation { + + private final static int STORAGE_REQUEST_CODE = 9999; + private final static int SELECT_FILE = 5055; + private final static int VISIBLE_THRESHOLD = 25; + protected final static String MESSAGE_ARG = "message_arg"; + protected final static String GROUP_ARG = "group_arg"; + + @BindView(R.id.chat_toolbar_image_view) + ImageView mChatImageView; + + @BindView(R.id.chat_toolbar_single_title) + TextView mSingleChatTitleTextView; + + @BindView(R.id.chat_toolbar_group_title) + TextView mGroupTitleTextView; + + @BindView(R.id.chat_toolbar_group_members) + TextView mGroupMembersTextView; + + @BindView(R.id.chat_toolbar_extra_image) + ImageView mExtraImageView; + + @BindView(R.id.chat_message) + EmojiconEditText mChatMessageEmojiEditText; + + @BindView(R.id.chat_recycler_view) + RecyclerView mRecyclerView; + + @BindView(R.id.chat_empty_layout) + EmptyLayout mEmptyLayout; + + @BindView(R.id.emoji_button) + ImageView mEmojiButton; + + @BindView(R.id.chat_send_message) + ImageView mSendMessageImageView; + + @BindView(R.id.chat_coordinator_layout) + CoordinatorLayout mCoordinatorLayout; + + @Inject + @Named(USER_ID) + LongPreference mUserIdPreference; + + @Inject + ChatPresenter mChatPresenter; + + @Inject + SystemUtils mSystemUtils; + + @Inject + LoginPresenter mLoginPresenter; + + protected Group mGroup; + protected ChatView.Callback mCallback; + protected EndlessScrollListener mEndlessScrollListener; + protected BaseChatAdapter mBaseChatAdapter; + private Snackbar mSnackbar; + + @Inject + @Named(AppPrefsConstants.CHAT_OPENED) + BooleanPreference mChatOpenedPref; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + if (!getIntent().hasExtra(GROUP_ARG) && !getIntent().hasExtra(MESSAGE_ARG)) { + throw new RuntimeException("Arguments are missing. Please use createIntentMethodInstead;"); + } + super.onCreate(savedInstanceState); + EventBus.getDefault().register(this); + setContentView(R.layout.activity_chat); + ButterKnife.bind(this); + mSendMessageImageView.setEnabled(false); + InjectionHelper.getChatComponent(this).inject(this); + mBaseChatAdapter = getAdapter(); + mEmptyLayout.setOnTryAgainClickListener(v -> mCallback.fetchMoreMessagesByGroupId()); + setupRecyclerView(); + EmojIconActions emojIcon = new EmojIconActions(this, mCoordinatorLayout, mChatMessageEmojiEditText, mEmojiButton); + emojIcon.ShowEmojIcon(); + showLoading(); + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(mKeyboardListener); + } + + private void setupRecyclerView() { + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); + linearLayoutManager.setStackFromEnd(true); + linearLayoutManager.setReverseLayout(true); + mRecyclerView.setLayoutManager(linearLayoutManager); + mBaseChatAdapter.setMessageClickListener(this); + mRecyclerView.setAdapter(mBaseChatAdapter); + ((SimpleItemAnimator) mRecyclerView.getItemAnimator()).setSupportsChangeAnimations(false); + mEndlessScrollListener = new EndlessScrollListener(VISIBLE_THRESHOLD, linearLayoutManager) { + @Override + public boolean onLoad() { + if (mCallback == null || mCallback.isLoading()) { + return false; + } + mCallback.fetchMoreMessagesByGroupId(); + return true; + } + }; + mRecyclerView.addOnScrollListener(mEndlessScrollListener); + } + + @OnClick(R.id.chat_toolbar_extra_image) + void extraImageViewClicked() { + extraImageClicked(); + } + + protected abstract void extraImageClicked(); + + protected abstract void setToolbar(); + + protected abstract BaseChatAdapter getAdapter(); + + protected abstract void loadMessages(); + + @Override + public void setCallback(Callback callback) { + mCallback = callback; + } + + @Override + public void messagesLoaded(ArrayList messages, boolean isFirstPage) { + if (isFirstPage) { + mEndlessScrollListener.reset(); + mBaseChatAdapter.setMessages(messages); + mRecyclerView.scrollToPosition(0); + return; + } + mBaseChatAdapter.addMessages(messages); + } + + @Override + public void showError(@StringRes int errorRes) { + if (mSnackbar == null) { + mSnackbar = Snackbar.make(mCoordinatorLayout, errorRes, Snackbar.LENGTH_LONG); + mSnackbar.setActionTextColor(ContextCompat.getColor(this, R.color.colorDefault)); + } + mSnackbar.setText(errorRes); + mSnackbar.show(); + } + + @Override + public void showLoading() { + mEmptyLayout.showLoading(); + } + + @Override + public void hideLoading() { + mEmptyLayout.setVisibility(View.GONE); + } + + @Override + public void showMessagesLoading() { + mBaseChatAdapter.setLoading(true); + } + + @Override + public void hideMessagesLoading() { + mBaseChatAdapter.setLoading(false); + } + + @Override + public void showEmptyMessage() { + mEmptyLayout.setEmptyText(getString(R.string.chat_no_messages)); + mEmptyLayout.showEmpty(); + } + + @OnClick(R.id.chat_attachment) + void attachmentClicked() { + requestAppPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + R.string.permission_storage_explained, + STORAGE_REQUEST_CODE, + this::selectFile); + } + + @OnClick(R.id.chat_toolbar_back_arrow) + void backPressed() { + onBackPressed(); + } + + @OnClick(R.id.chat_send_message) + void messageSent() { + mLoginPresenter.checkIfVoucherStillAvailable(this); + mCallback.sendMessage(mChatMessageEmojiEditText.getText().toString()); + mChatMessageEmojiEditText.setText(""); + } + + @OnTextChanged(value = R.id.chat_message, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + void messageTextChanged(Editable editable) { + mSendMessageImageView.setEnabled(!TextUtils.isEmpty(editable.toString()) && editable.toString().trim().length() > 0); + } + + private void selectFile(int requestCode) { + Intent intent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + } else { + intent = new Intent(Intent.ACTION_GET_CONTENT); + } + intent.setType("*/*"); + intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + startActivityForResult(Intent.createChooser(intent, getResources().getString(R.string.chat_select_file)), SELECT_FILE); + } + + @Override + protected void onResume() { + super.onResume(); + mLoginPresenter.checkIfVoucherStillAvailable(this); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode != Activity.RESULT_OK) { + return; + } + if (null == data) { + return; + } + if (requestCode == SELECT_FILE) { + if (null == data.getData()) { + return; + } + final Uri uri = data.getData(); + if (Build.VERSION.SDK_INT >= 19) { + final int takeFlags = data.getFlags() + & (Intent.FLAG_GRANT_READ_URI_PERMISSION | + Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + // Check for the freshest data. + try { + getContentResolver().takePersistableUriPermission(uri, takeFlags); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (!mChatPresenter.isAttached()) { + mChatPresenter.attachView(this); + } + mCallback.sendFile(uri); + } + } + + @Override + public void logoutDirectly() { + EventBus.getDefault().post(new LogoutDirectlyEvent()); + finish(); + } + + @Override + public void showToastMessage(@StringRes int message) { + Toast.makeText(BaseChatActivity.this, message, Toast.LENGTH_SHORT).show(); + } + + @Override + public void fileClicked(Message message, int itemPosition) { + if (!mChatPresenter.isAttached()) { + mChatPresenter.attachView(this); + } + String[] split = message.filePath.split("/"); + String fileName = split[split.length - 1]; + File file = getExternalFilesDir(null); + if (file != null) { + mCallback.downloadFile(message, file + "/" + fileName, itemPosition); + return; + } + file = new File(Environment.getExternalStorageDirectory(), getString(R.string.app_name) + "/" + fileName); + //noinspection ResultOfMethodCallIgnored + file.mkdirs(); + mCallback.downloadFile(message, file.getAbsolutePath(), itemPosition); + } + + @Override + public void localFileClicked(Uri uri) { + File file = getFile(uri); + openFile(uri, FileUtils.getFileTypeFromFilePath(file.getAbsolutePath())); + } + + @Override + public void resendMessage(Message message) { + mLoginPresenter.checkIfVoucherStillAvailable(this); + if (!mChatPresenter.isAttached()) { + mChatPresenter.attachView(this); + } + mCallback.resendMessage(message); + } + + @Override + public MediaType getMediaType(Uri fileUri) { + return MediaType.parse(getContentResolver().getType(fileUri)); + } + + @Override + public File getFile(Uri fileUri) { + return FileUtils.getFile(this, fileUri); + } + + @Override + public void enableMessageRetry(String messageId) { + mBaseChatAdapter.enableMessageRetry(messageId); + } + + @Override + public void showItemProgress(int itemPosition, int progress) { + runOnUiThread(() -> mBaseChatAdapter.setItemProgress(itemPosition, progress)); + } + + @Override + public void stopItemProgress(int itemPosition) { + mBaseChatAdapter.stopItemProgress(itemPosition); + } + + @Override + public void fileDownloadedSuccessfully(String filePath, String fileType) { + openFile(FileProvider.getUriForFile(BaseChatActivity.this, + getApplicationContext().getPackageName() + ".provider", + new File(filePath)), + fileType); + } + + private void openFile(Uri uri, String fileType) { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setDataAndType(uri, + fileType); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + // Verify the original intent will resolve to at least one activity + if (intent.resolveActivity(getPackageManager()) != null) { + startActivity(intent); + } else { + showToastMessage(R.string.error_could_not_open_file); + } + } + + @Override + public void lastMessageUpdated(Message newMessage, String oldMessageId) { + mBaseChatAdapter.updateMessage(newMessage, oldMessageId); + } + + @Override + protected void onPostCreate(@Nullable Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + mChatOpenedPref.set(false); + } + + @Override + protected void onDestroy() { + mChatOpenedPref.set(false); + if (mEndlessScrollListener != null) { + mRecyclerView.removeOnScrollListener(mEndlessScrollListener); + } + getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(mKeyboardListener); + EventBus.getDefault().unregister(this); + InjectionHelper.destroyChatComponent(); + mRecyclerView.setAdapter(null); + mEmptyLayout.setOnTryAgainClickListener(null); + super.onDestroy(); + } + + @Override + public void addOneMessage(Message message) { + mBaseChatAdapter.addMessage(message); + if (mEndlessScrollListener.getVisibleItem() == 0) { + mBaseChatAdapter.notifyDataSetChanged(); + } + mRecyclerView.scrollToPosition(0); + } + + protected void setGroupFromArguments() { + mGroup = getIntent().getParcelableExtra(GROUP_ARG); + cancelNotification(); + loadMessages(); + setToolbar(); + } + + @SuppressWarnings("unused") + @Subscribe(threadMode = ThreadMode.MAIN) + public void onInvalidToken(InvalidTokenEvent invalidTokenEvent) { + finish(); + } + + protected void cancelNotification() { + if (mGroup == null) { + return; + } + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); + notificationManager.cancel((int) mGroup.id); + } + + private ViewTreeObserver.OnGlobalLayoutListener mKeyboardListener = new ViewTreeObserver.OnGlobalLayoutListener() { + + private boolean mKeyboardShown = false; + + @Override + public void onGlobalLayout() { + Rect r = new Rect(); + getWindow().getDecorView().getWindowVisibleDisplayFrame(r); + int screenHeight = getWindow().getDecorView().getHeight(); + + // r.bottom is the position above soft keypad or device button. + // if keypad is shown, the r.bottom is smaller than that before. + int keypadHeight = screenHeight - r.bottom; + + if (keypadHeight > screenHeight * 0.15 && !mKeyboardShown) { // 0.15 ratio is perhaps enough to determine keypad height. + // keyboard is opened + mRecyclerView.scrollToPosition(0); + mKeyboardShown = true; + } else { + mKeyboardShown = false; + } + } + }; +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/BaseChatAdapter.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/BaseChatAdapter.java new file mode 100644 index 0000000..2e22609 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/BaseChatAdapter.java @@ -0,0 +1,435 @@ +package com.p3.bewoplaner.page.chat; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.github.lzyzsd.circleprogress.DonutProgress; +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.utils.DateUtils; +import com.p3.bewoplaner.utils.ui.ViewUtils; + +import android.net.Uri; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Optional; +import hani.momanii.supernova_emoji_library.Helper.EmojiconTextView; + +/** + * Created by Rares Teodorescu on 21/02/2017. + */ +abstract class BaseChatAdapter extends RecyclerView.Adapter { + + final static int DATE_TYPE = 0; + final static int LEFT_CHAT_TYPE = 1; + final static int RIGHT_CHAT_TYPE = 2; + final static int LOADING_TYPE = 3; + + private long mUserId; + + final ArrayList mMessages = new ArrayList<>(); + private final List mDatePositions = new ArrayList<>(); + final List mDateList = new ArrayList<>(); + final List mPositionOffset = new ArrayList<>(); + private boolean mIsLoading; + private int mLastOffset = 0; + private Calendar mCurrentDay; + private Calendar mLastDay; + private MessageClickListener mMessageClickListener; + private boolean mIsRetrying; + protected HashMap mDownloadProgressMap = new HashMap<>(); + + BaseChatAdapter(long userId) { + mUserId = userId; + mCurrentDay = Calendar.getInstance(); + mLastDay = Calendar.getInstance(); + } + + void setMessages(ArrayList messages) { + mMessages.clear(); + mDatePositions.clear(); + mDateList.clear(); + mPositionOffset.clear(); + mIsLoading = false; + mLastOffset = 0; + mCurrentDay = Calendar.getInstance(); + mLastDay = Calendar.getInstance(); + mIsRetrying = false; + if (!messages.isEmpty()) { + mPositionOffset.add(0); + mDateList.add(new Date(messages.get(0).createdAt)); + } + int messageSize = messages.size(); + for (int i = 1; i < messageSize; i++) { + mCurrentDay.setTimeInMillis(messages.get(i).createdAt); + mLastDay.setTimeInMillis(messages.get(i - 1).createdAt); + if (mCurrentDay.get(Calendar.DAY_OF_YEAR) != mLastDay.get(Calendar.DAY_OF_YEAR)) { + mDatePositions.add(i + mLastOffset); + mPositionOffset.add(0); + mDateList.add(mLastDay.getTime()); + mLastOffset++; + } + mPositionOffset.add(mLastOffset); + mDateList.add(new Date(messages.get(i).createdAt)); + } + mMessages.addAll(messages); + notifyDataSetChanged(); + } + + void addMessages(ArrayList messages) { + if (messages == null || messages.isEmpty() || mMessages.equals(messages)) { + return; + } + int currentMessagesCount = mMessages.size(); + int lastOffsetBeforeParsing = mLastOffset; + int datesInserted = 0; + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(messages.get(0).createdAt); + if (mCurrentDay.get(Calendar.DAY_OF_YEAR) != calendar.get(Calendar.DAY_OF_YEAR)) { + mDatePositions.add(currentMessagesCount + mLastOffset); + datesInserted++; + mDateList.add(new Date(messages.get(0).createdAt)); + mPositionOffset.add(0); + mLastOffset++; + } + mPositionOffset.add(mLastOffset); + mDateList.add(new Date(messages.get(0).createdAt)); + int messageSize = messages.size(); + for (int i = 1; i < messageSize; i++) { + mCurrentDay.setTimeInMillis(messages.get(i).createdAt); + mLastDay.setTimeInMillis(messages.get(i - 1).createdAt); + if (mCurrentDay.get(Calendar.DAY_OF_YEAR) != mLastDay.get(Calendar.DAY_OF_YEAR)) { + mDatePositions.add(currentMessagesCount + mLastOffset + i); + datesInserted++; + mPositionOffset.add(0); + mDateList.add(mLastDay.getTime()); + mLastOffset++; + } + mPositionOffset.add(mLastOffset); + mDateList.add(new Date(messages.get(i).createdAt)); + } + mMessages.addAll(messages); + notifyItemRangeInserted(currentMessagesCount + lastOffsetBeforeParsing, messages.size() + datesInserted); + } + + void setItemProgress(int itemPosition, int progress) { + mDownloadProgressMap.put(itemPosition, progress); + notifyItemChanged(itemPosition); + } + + void stopItemProgress(int itemPosition) { + mDownloadProgressMap.remove(itemPosition); + notifyItemChanged(itemPosition); + } + + void resetItemsProgress() { + mDownloadProgressMap.clear(); + notifyDataSetChanged(); + } + + void addMessage(Message message) { + if (mMessages.contains(message)) { + return; + } + + ArrayList currentMessages = new ArrayList<>(); + currentMessages.add(message); + currentMessages.addAll(mMessages); + mMessages.clear(); + mMessages.addAll(currentMessages); + + Calendar lastDay = Calendar.getInstance(); + if (mMessages.size() > 1) { + lastDay.setTimeInMillis(mMessages.get(1).createdAt); + } + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(message.createdAt); + if (calendar.get(Calendar.DAY_OF_YEAR) != lastDay.get(Calendar.DAY_OF_YEAR)) { + // New date inserted + setMessages(new ArrayList<>(mMessages)); + return; + } + + List positionOffset = new ArrayList<>(); + positionOffset.add(0); + positionOffset.addAll(mPositionOffset); + mPositionOffset.clear(); + mPositionOffset.addAll(positionOffset); + + List currentDateList = new ArrayList<>(); + currentDateList.add(new Date(message.createdAt)); + currentDateList.addAll(mDateList); + mDateList.clear(); + mDateList.addAll(currentDateList); + + List currentDatePositions = new ArrayList<>(); + boolean newDateAdded = false; + if (calendar.get(Calendar.DAY_OF_YEAR) != lastDay.get(Calendar.DAY_OF_YEAR)) { + currentDatePositions.add(1); + newDateAdded = true; + } + for (int i = 0, size = mDatePositions.size(); i < size; i++) { + currentDatePositions.add(mDatePositions.get(i) + (newDateAdded ? 2 : 1)); + } + mDatePositions.clear(); + mDatePositions.addAll(currentDatePositions); + + notifyItemInserted(0); + } + + void setLoading(boolean loading) { + mIsLoading = loading; + if (loading) { + notifyItemRangeInserted(getItemCount(), 1); + } else { + notifyItemRemoved(getItemCount() - 1); + } + } + + @Override + public int getItemCount() { + return mMessages.size() + mLastOffset + (mIsLoading ? 1 : 0); + } + + void updateMessage(Message newMessage, String oldMessageId) { + int index = -1; + for (int i = 0; i < mMessages.size(); i++) { + if (oldMessageId.equals(mMessages.get(i).id)) { + index = i; + break; + } + } + if (index == -1) { + Log.e("BaseChatAdapter", "message not found!! " + newMessage.toString()); + return; + } + mMessages.set(index, newMessage); + notifyItemChanged(index); + } + + @Override + public int getItemViewType(int position) { + if (mDatePositions.contains(position)) { + return DATE_TYPE; + } + if (position == mMessages.size() + mLastOffset) { + return LOADING_TYPE; + } + return mMessages.get(position - mPositionOffset.get(position)).senderId == mUserId ? + RIGHT_CHAT_TYPE : LEFT_CHAT_TYPE; + } + + void setMessageClickListener(MessageClickListener messageClickListener) { + mMessageClickListener = messageClickListener; + } + + void enableMessageRetry(String messageId) { + mIsRetrying = false; + Message myMessage = null; + int index = -1; + for (int i = 0; i < mMessages.size(); i++) { + Message message = mMessages.get(i); + if (messageId.equals(message.id)) { + index = i; + myMessage = message; + myMessage.status = Message.Status.FAILED; + break; + } + } + if (myMessage == null) { + Log.e("BaseChatAdapter", "message not found!! " + messageId); + return; + } + mMessages.set(index, myMessage); + notifyItemChanged(index); + } + + void setSingleMessage(MessageViewHolder viewHolder, Message message, int position, boolean isLeft) { + setMessage(viewHolder, message, position, false, isLeft); + } + + void setGroupMessage(MessageViewHolder viewHolder, Message message, int position, boolean isLeft) { + setMessage(viewHolder, message, position, true, isLeft); + } + + private void setMessage(MessageViewHolder viewHolder, Message message, int position, boolean isGroup, boolean isLeft) { + if (isGroup) { + viewHolder.timeTextView.setText(String.format(Locale.getDefault(), + "%s | %s", isLeft ? message.senderName : viewHolder.timeTextView.getContext().getString(R.string.chat_you_name), + DateUtils.toHourFormatHour(message.createdAt))); + if (TextUtils.isEmpty(message.senderAvatar)) { + //noinspection ConstantConditions + viewHolder.contactImageView.setImageResource(R.drawable.avatar); + } else { + //noinspection ConstantConditions + Glide.with(viewHolder.contactImageView.getContext()) + .load(message.senderAvatar) + .placeholder(R.drawable.avatar) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(viewHolder.contactImageView); + } + } else { + viewHolder.timeTextView.setText(DateUtils.toHourFormatHour(message.createdAt)); + } + if (TextUtils.isEmpty(message.message)) { + viewHolder.messageTextView.setVisibility(View.GONE); + if (mDownloadProgressMap.containsKey(position)) { + viewHolder.fileImageView.setVisibility(View.GONE); + viewHolder.donutProgress.setVisibility(View.VISIBLE); + viewHolder.donutProgress.setProgress(mDownloadProgressMap.get(position)); + viewHolder.donutProgress.setText(String.format(Locale.getDefault(), "%d %%", mDownloadProgressMap.get(position))); + if (!message.filePath.endsWith(".png") + && !message.filePath.endsWith(".jpg") + && !message.filePath.endsWith(".JPG")) { + viewHolder.filenameTextView.setVisibility(View.VISIBLE); + viewHolder.filenameTextView.setText(message.originalFilename); + } else { + viewHolder.filenameTextView.setVisibility(View.GONE); + } + } else if (message.filePath.endsWith(".png") + || message.filePath.endsWith(".jpg") + || message.filePath.endsWith(".JPG")) { + viewHolder.donutProgress.setVisibility(View.GONE); + viewHolder.fileImageView.setVisibility(View.VISIBLE); + viewHolder.filenameTextView.setVisibility(View.GONE); + viewHolder.fileImageView.setImageDrawable(null); + Glide.with(viewHolder.fileImageView.getContext()) + .load(message.smallerImage) + .placeholder(R.drawable.document) + .dontAnimate() + .override(500, 500) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(viewHolder.fileImageView); + } else { + viewHolder.donutProgress.setVisibility(View.GONE); + viewHolder.fileImageView.setVisibility(View.VISIBLE); + viewHolder.filenameTextView.setVisibility(View.VISIBLE); + viewHolder.fileImageView.setImageDrawable(null); + viewHolder.fileImageView.setImageResource(R.drawable.document); + viewHolder.filenameTextView.setText(message.originalFilename); + } + } else { + viewHolder.donutProgress.setVisibility(View.GONE); + viewHolder.fileImageView.setVisibility(View.GONE); + viewHolder.filenameTextView.setVisibility(View.GONE); + viewHolder.messageTextView.setVisibility(View.VISIBLE); + viewHolder.messageTextView.setText(message.message); + } + } + + static class DateViewHolder extends RecyclerView.ViewHolder { + + @BindView(R.id.chat_item_date) + TextView dateTextView; + + DateViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } + + class MessageViewHolder extends RecyclerView.ViewHolder { + + @Nullable + @BindView(R.id.chat_item_message_status_progress_bar) + ProgressBar statusProgressBar; + + @Nullable + @BindView(R.id.chat_item_message_status) + ImageView statusImageView; + + @Nullable + @BindView(R.id.chat_item_sending_failed) + TextView sendingFailedTextView; + + @Nullable + @BindView(R.id.chat_item_contact_image) + ImageView contactImageView; + + @BindView(R.id.chat_item_time) + TextView timeTextView; + + @BindView(R.id.chat_item_message) + EmojiconTextView messageTextView; + + @BindView(R.id.chat_item_file) + ImageView fileImageView; + + @BindView(R.id.chat_item_progress) + DonutProgress donutProgress; + + @BindView(R.id.chat_item_file_name) + TextView filenameTextView; + + MessageViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + + @Optional + @OnClick(R.id.chat_item_sending_failed) + void retrySendingClicked() { + if (mIsRetrying) { + return; + } + mIsRetrying = true; + int currentPosition = getAdapterPosition(); + Message messageToResend = mMessages.get(currentPosition - mPositionOffset.get(currentPosition)); + //noinspection ConstantConditions + statusProgressBar.setVisibility(View.VISIBLE); + ViewUtils.setProgressBarColorToOrange(statusProgressBar); + //noinspection ConstantConditions + statusImageView.setVisibility(View.GONE); + //noinspection ConstantConditions + sendingFailedTextView.setVisibility(View.GONE); + mMessageClickListener.resendMessage(messageToResend); + } + + @Optional + @OnClick(R.id.chat_item_file) + void fileClicked() { + int currentPosition = getAdapterPosition(); + Message message = mMessages.get(currentPosition - mPositionOffset.get(currentPosition)); + if (message.status != Message.Status.DELIVERED) { + mMessageClickListener.localFileClicked(Uri.parse(message.uri)); + return; + } + if (mDownloadProgressMap.containsKey(currentPosition)) { + return; + } + mDownloadProgressMap.put(currentPosition, 0); + mMessageClickListener.fileClicked(message, getAdapterPosition()); + } + } + + static class ProgressViewHolder extends RecyclerView.ViewHolder { + + ProgressViewHolder(View itemView) { + super(itemView); + } + } + + interface MessageClickListener { + void fileClicked(Message message, int itemPosition); + + void localFileClicked(Uri uri); + + void resendMessage(Message message); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/ChatComponent.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/ChatComponent.java new file mode 100644 index 0000000..732077f --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/ChatComponent.java @@ -0,0 +1,17 @@ +package com.p3.bewoplaner.page.chat; + +import com.p3.bewoplaner.di.ApplicationComponent; +import com.p3.bewoplaner.di.scopes.ActivityScope; +import com.p3.bewoplaner.page.login.LoginModule; + +import dagger.Component; + +/** + * Created by Rares Teodorescu on 21/02/2017. + */ +@ActivityScope +@Component(dependencies = ApplicationComponent.class, + modules = {LoginModule.class, ChatModule.class}) +public interface ChatComponent { + void inject(BaseChatActivity baseChatActivity); +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/ChatModule.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/ChatModule.java new file mode 100644 index 0000000..4155e08 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/ChatModule.java @@ -0,0 +1,23 @@ +package com.p3.bewoplaner.page.chat; + +import com.p3.bewoplaner.data.api.services.ChatService; +import com.p3.bewoplaner.di.P3DSEndpoint; +import com.p3.bewoplaner.di.scopes.ActivityScope; + +import dagger.Module; +import dagger.Provides; +import retrofit2.Retrofit; + +/** + * Created by imarneanu on 3/1/17. + */ +@Module +public class ChatModule { + + @Provides + @ActivityScope + ChatService provideChatService(@P3DSEndpoint Retrofit retrofit) { + return retrofit.create(ChatService.class); + } + +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/ChatPresenter.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/ChatPresenter.java new file mode 100644 index 0000000..dd47b60 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/ChatPresenter.java @@ -0,0 +1,979 @@ +package com.p3.bewoplaner.page.chat; + +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.api.common.ErrorHandlingResponseConverter; +import com.p3.bewoplaner.data.api.responses.MessageResponse; +import com.p3.bewoplaner.data.api.responses.MessagesResponse; +import com.p3.bewoplaner.data.api.services.ChatService; +import com.p3.bewoplaner.data.events.GroupUpdatedEvent; +import com.p3.bewoplaner.data.prefs.LongPreference; +import com.p3.bewoplaner.data.prefs.StringPreference; +import com.p3.bewoplaner.data.repository.GroupRealmRepository; +import com.p3.bewoplaner.data.repository.MessageRealmRepository; +import com.p3.bewoplaner.data.repository.RealmProvider; +import com.p3.bewoplaner.data.repository.models.MessageRealm; +import com.p3.bewoplaner.data.repository.specifications.GroupByIdSpecification; +import com.p3.bewoplaner.data.repository.specifications.MessageByGroupIdSpecification; +import com.p3.bewoplaner.data.repository.specifications.MessageByIdSpecification; +import com.p3.bewoplaner.di.scopes.ActivityScope; +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.page.mvp.MVPAbstractPresenter; +import com.p3.bewoplaner.utils.SystemUtils; +import com.p3.bewoplaner.utils.rx.RxUtils; + +import org.greenrobot.eventbus.EventBus; + +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.v4.util.Pair; +import android.text.TextUtils; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import javax.inject.Inject; +import javax.inject.Named; + +import io.realm.Realm; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import rx.Completable; +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +import static com.p3.bewoplaner.data.AppPrefsConstants.USER_ID; +import static com.p3.bewoplaner.utils.FileUtils.getFileTypeFromFilePath; + +/** + * Created by Rares Teodorescu on 21/02/2017. + */ +@ActivityScope +public class ChatPresenter extends MVPAbstractPresenter + implements ChatView.Callback { + + private static final int FIRST_PAGE = 1; + private static final int GROUP_ID_NOT_SET = -999; + private static final int LIMIT = 25; + + @Inject + ChatService mChatService; + + @Inject + MessageRealmRepository mMessageRealmRepository; + + @Inject + GroupRealmRepository mGroupRealmRepository; + + @Inject + SystemUtils mSystemUtils; + + @Inject + @Named(USER_ID) + LongPreference mUserIdPreference; + + @Inject + @Named(AppPrefsConstants.AVATAR) + StringPreference mAvatarPreference; + + @Inject + @Named(AppPrefsConstants.NAME) + StringPreference mProfileNamePreference; + + @Inject + RealmProvider mRealmProvider; + + @Inject + ChatPresenter() { + } + + private long mGroupId = GROUP_ID_NOT_SET; + private boolean mLoading = false; + private int mPage = FIRST_PAGE; + private int mOffset; + private boolean mFetchMore; + private ArrayList mMessages = new ArrayList<>(); + private ArrayList> mMessageToUpdate = new ArrayList<>(); + + /** + * Get all messages from the server with a specific groupId. + */ + @Override + public void loadMessagesFromServerByGroupId(long groupId) { + if (!mFetchMore) { + getView().hideMessagesLoading(); + return; + } + mGroupId = groupId; + Subscription subscription = mChatService.getMessages(groupId, mPage) + .compose(RxUtils.provideDefaultTransformer()) + .flatMap(new ErrorHandlingResponseConverter<>()) + .flatMap(new Func1>>() { + @Override + public Observable> call(MessagesResponse messageResponse) { + ArrayList messages = messageResponse.response.messages; + for (int i = 0, size = messages.size(); i < size; i++) { + messages.get(i).status = Message.Status.DELIVERED; + messages.get(i).isRead = true; + messages.get(i).userId = mUserIdPreference.get(); + } + mFetchMore = messageResponse.response.hasMorePages; + return Observable.just(messageResponse.response.messages); + } + }) + .subscribe(this::messagesSuccess, this::messagesError); + addSubscription(subscription); + } + + /** + * Get messages from page 0. + */ + @Override + public void fetchNewMessages(long groupId) { + mGroupId = groupId; + Subscription subscription = mChatService.getMessages(mGroupId, 0) + .compose(RxUtils.provideDefaultTransformer()) + .flatMap(new ErrorHandlingResponseConverter<>()) + .flatMap(new Func1>>() { + @Override + public Observable> call(MessagesResponse messageResponse) { + if (messageResponse.response.messages == null || messageResponse.response.messages.isEmpty()) { + return Observable.just(null); + } + Message lastMessage = messageResponse.response.messages.get(messageResponse.response.messages.size() - 1); + lastMessage.status = Message.Status.DELIVERED; + lastMessage.isRead = true; + if (!mMessages.contains(lastMessage)) { + long groupId = mGroupId; + resetMessages(); + loadMessagesFromServerByGroupId(groupId); + return Observable.just(null); + } + for (int i = 0; i < mMessageToUpdate.size(); i++) { + Pair messageToUpdate = mMessageToUpdate.get(i); + if (isAttached()) { + getView().lastMessageUpdated(messageToUpdate.first, messageToUpdate.second); + } + } + mMessageToUpdate.clear(); + ArrayList messages = new ArrayList<>(); + for (int i = messageResponse.response.messages.size() - 1; i >= 0; i--) { + Message message = messageResponse.response.messages.get(i); + message.status = Message.Status.DELIVERED; + message.isRead = true; + message.userId = mUserIdPreference.get(); + if (mMessages.contains(message)) { + continue; + } + messages.add(message); + } + return mMessageRealmRepository.updateMessages(messages, new MessageByIdSpecification(mUserIdPreference.get())); + } + }) + .subscribe(this::fetchNewMessagesSuccess, this::fetchNewMessagesError); + addSubscription(subscription); + } + + /** + * Send messages to view. + */ + private void fetchNewMessagesSuccess(List messages) { + if (messages == null) { + return; + } + for (int i = 0; i < messages.size(); i++) { + if (!isAttached()) { + break; + } + Message message = messages.get(i); + mMessages.add(message); + getView().addOneMessage(message); + } + } + /** + * Send error messages to view. + */ + private void fetchNewMessagesError(Throwable throwable) { + Log.e("fetchNewMessages", "onError", throwable); + if (!isAttached()) { + return; + } + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + return; + } + getView().showError(R.string.error_something_went_wrong); + } + + @Override + public void resetMessages() { + mGroupId = GROUP_ID_NOT_SET; + mLoading = false; + mPage = FIRST_PAGE; + mOffset = 0; + mFetchMore = true; + } + + /** + * Save messages to local database. + */ + private void messagesSuccess(ArrayList messages) { + mMessageRealmRepository.update(messages, new MessageByIdSpecification(mUserIdPreference.get())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + loadMessagesFromDBByGroupId(mGroupId, true); + } + + @Override + public void onError(Throwable e) { + Log.e(ChatPresenter.class.getSimpleName(), e.getMessage(), e); + } + + @Override + public void onNext(Object o) { + // nop here + } + }); + } + + private void messagesError(Throwable throwable) { + Log.e(ChatPresenter.class.getSimpleName(), throwable.getMessage(), throwable); + getView().hideLoading(); + getView().hideMessagesLoading(); + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + return; + } + getView().showError(R.string.error_something_went_wrong); + } + + @Override + public void loadMessagesFromDBByGroupId(long groupId, boolean fetchWhenFinished) { + mGroupId = groupId; + loadMessagesFromDBByGroupId(); + } + + /** + * Get messages from database using a specific groupId. + */ + private void loadMessagesFromDBByGroupId() { + MessageByGroupIdSpecification messageByGroupIdSpecification = new MessageByGroupIdSpecification(mUserIdPreference.get()); + messageByGroupIdSpecification.setGroupId(mGroupId); + mMessageRealmRepository.queryForAll(messageByGroupIdSpecification) + .map(ArrayList::new) + .compose(RxUtils.provideDefaultTransformer()) + .subscribe(this::loadMessagesFromDBSuccessful, this::loadMessagesError); + } + + /** + * Send messages to view. + */ + private void loadMessagesFromDBSuccessful(ArrayList messages) { + if (!isAttached()) { + return; + } + mMessages.clear(); + mMessages.addAll(messages); + mLoading = false; + getView().hideLoading(); + getView().hideMessagesLoading(); + if (!mMessages.isEmpty()) { + int endOffset = mOffset + LIMIT > mMessages.size() ? mMessages.size() : mOffset + LIMIT; + if (mOffset > endOffset) { + return; + } + boolean isFirstPage = mPage == FIRST_PAGE; + getView().messagesLoaded(new ArrayList<>(mMessages.subList(mOffset, endOffset)), isFirstPage); + if (isFirstPage && mSystemUtils.isNetworkUnavailable()) { + Message firstMessage = mMessages.get(0); + Message message = new Message(firstMessage.id, + firstMessage.groupId, + firstMessage.createdAt / 1000, + firstMessage.message, + firstMessage.filePath, + firstMessage.smallerImage, + firstMessage.originalFilename, + firstMessage.uri, + firstMessage.senderId, + firstMessage.senderName, + firstMessage.senderAvatar, + firstMessage.status, + true, + firstMessage.userId, + firstMessage.fileDownloaded); + storeGroupLocally(mGroupId, message); + } + mPage++; + mOffset += LIMIT; + return; + } + getView().showEmptyMessage(); + } + + private void loadMessagesError(Throwable throwable) { + if (!isAttached()) { + return; + } + mLoading = false; + getView().hideLoading(); + getView().hideMessagesLoading(); + } + + /** + * If mMessages size is bigger then 25 and network is available, get more messages from the server. + */ + @Override + public void fetchMoreMessagesByGroupId() { + if (mMessages.size() < 25) { + getView().hideMessagesLoading(); + return; + } + mLoading = true; + if (mPage == FIRST_PAGE) { + getView().showLoading(); + } else { + getView().showMessagesLoading(); + } + if (mSystemUtils.isNetworkUnavailable()) { + loadMessagesFromDBByGroupId(); + getView().showError(R.string.error_no_internet_connection); + return; + } + loadMessagesFromServerByGroupId(mGroupId); + } + + @Override + public boolean isLoading() { + return mLoading; + } + + /** + * Create message with a specific file path, save it to the local database. + * Send the file to the server. + */ + @Override + public void sendFile(Uri fileUri) { + File file = getView().getFile(fileUri); + if (file == null || !file.exists()) { + getView().showToastMessage(R.string.error_file_not_found); + return; + } + String[] split = file.getAbsolutePath().split("/"); + + Message message = new Message(findUniqueRandomId(mRealmProvider, mUserIdPreference.get()), + mGroupId, + System.currentTimeMillis() / 1000, + null, + file.getPath(), + file.getPath(), + split[split.length - 1], + fileUri.toString(), + mUserIdPreference.get(), + mProfileNamePreference.get(), + mAvatarPreference.get(), + Message.Status.PENDING, + true, + mUserIdPreference.get(), + false); + + storeMessageLocally(message) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + message.status = Message.Status.FAILED; + message.isRead = true; + storeMessageLocally(message, message.id, true); + storeGroupLocally(mGroupId, message); + return; + } + checkForEmptyMessageAndHideIt(); + message.createdAt *= 1000; + getView().addOneMessage(message); + + // create RequestBody instance from file + RequestBody requestFile = RequestBody.create(getView().getMediaType(fileUri), file); + + // MultipartBody.Part is used to send also the actual file name + MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile); + + // add another part within the multipart request + String groupIdString = String.valueOf(mGroupId); + RequestBody groupIdBody = RequestBody.create(okhttp3.MultipartBody.FORM, groupIdString); + + mChatService.sendFile(groupIdBody, body) + .compose(RxUtils.provideDefaultTransformer()) + .flatMap(new ErrorHandlingResponseConverter<>()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + Log.e("sendFile", "onError", e); + @SuppressWarnings("UnnecessaryLocalVariable") Message newMessage = message; + newMessage.createdAt /= 1000; + newMessage.status = Message.Status.FAILED; + newMessage.isRead = true; + storeMessageLocally(newMessage, message.id, false); + storeGroupLocally(mGroupId, message); + if (!isAttached()) { + return; + } + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + return; + } + getView().showError(R.string.error_something_went_wrong); + } + + @Override + public void onNext(MessageResponse baseResponse) { + deleteLocalMessage(message); + baseResponse.response.message.status = Message.Status.DELIVERED; + baseResponse.response.message.isRead = true; + storeMessageLocally(baseResponse.response.message, message.id, false); + storeGroupLocally(mGroupId, baseResponse.response.message); + } + }); + } + + @Override + public void onError(Throwable e) { + Log.e("sendFile", "onError", e); + } + + @Override + public void onNext(Object o) { + + } + }); + } + + /** + * Download file and notify the activity. + */ + @Override + public void downloadFile(Message message, String filePath, int itemPosition) { + if (message.fileDownloaded) { + getView().stopItemProgress(itemPosition); + getView().fileDownloadedSuccessfully(filePath, getFileTypeFromFilePath(filePath)); + return; + } + downloadFileFromUrl(message.filePath, filePath, itemPosition) + .compose(RxUtils.provideDefaultTransformer()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + Log.e("ChatPresenter", "downloadFile", e); + if (!isAttached()) { + return; + } + getView().stopItemProgress(itemPosition); + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + return; + } + getView().showError(R.string.error_something_went_wrong); + } + + @Override + public void onNext(String fileType) { + if (!isAttached()) { + return; + } + getView().stopItemProgress(itemPosition); + getView().fileDownloadedSuccessfully(filePath, fileType); + message.fileDownloaded = true; + message.createdAt /= 1000; + storeMessageLocally(message).subscribe(); + } + }); + } + + @Override + public void resendMessage(Message message) { + if (mSystemUtils.isNetworkUnavailable()) { + getView().enableMessageRetry(message.id); + getView().showError(R.string.error_no_internet_connection); + return; + } + if (TextUtils.isEmpty(message.message)) { + Uri fileUri = Uri.parse(message.uri); + File file = getView().getFile(fileUri); + + if (file == null || !file.exists()) { + getView().showError(R.string.error_file_does_not_exist_anymore); + return; + } + + // create RequestBody instance from file + RequestBody requestFile = RequestBody.create(getView().getMediaType(fileUri), file); + + // MultipartBody.Part is used to send also the actual file name + MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile); + + // add another part within the multipart request + String groupIdString = String.valueOf(mGroupId); + RequestBody groupIdBody = RequestBody.create(okhttp3.MultipartBody.FORM, groupIdString); + + mChatService.sendFile(groupIdBody, body) + .compose(RxUtils.provideDefaultTransformer()) + .flatMap(new ErrorHandlingResponseConverter<>()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + Log.e("resendMessage", "onError", e); + if (!isAttached()) { + return; + } + getView().enableMessageRetry(message.id); + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + return; + } + getView().showError(R.string.error_retry_failed); + } + + @Override + public void onNext(MessageResponse baseResponse) { + deleteLocalMessage(message); + baseResponse.response.message.status = Message.Status.DELIVERED; + baseResponse.response.message.isRead = true; + storeMessageLocally(baseResponse.response.message, message.id, true); + storeGroupLocally(mGroupId, baseResponse.response.message); + } + }); + return; + } + mChatService.sendMessage(message.groupId, message.message) + .compose(RxUtils.provideDefaultTransformer()) + .flatMap(new ErrorHandlingResponseConverter<>()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + Log.e("resendMessage", "onError", e); + if (!isAttached()) { + return; + } + getView().enableMessageRetry(message.id); + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + return; + } + getView().showError(R.string.error_retry_failed); + } + + @Override + public void onNext(MessageResponse baseResponse) { + deleteLocalMessage(message); + baseResponse.response.message.status = Message.Status.DELIVERED; + storeMessageLocally(baseResponse.response.message, message.id, true); + storeGroupLocally(mGroupId, baseResponse.response.message); + } + }); + } + + @Override + public void sendMessage(String text) { + Message message = new Message(findUniqueRandomId(mRealmProvider, mUserIdPreference.get()), + mGroupId, + System.currentTimeMillis() / 1000, + text, + null, + null, + null, + null, + mUserIdPreference.get(), + mProfileNamePreference.get(), + mAvatarPreference.get(), + Message.Status.PENDING, + true, + mUserIdPreference.get(), + false); + storeMessageLocally(message) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + message.status = Message.Status.FAILED; + message.isRead = true; + storeMessageLocally(message, message.id, true); + storeGroupLocally(mGroupId, message); + return; + } + checkForEmptyMessageAndHideIt(); + message.createdAt *= 1000; + getView().addOneMessage(message); + + mChatService.sendMessage(mGroupId, text) + .compose(RxUtils.provideDefaultTransformer()) + .flatMap(new ErrorHandlingResponseConverter<>()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + Log.e("sendMessage", "onError", e); + @SuppressWarnings("UnnecessaryLocalVariable") Message newMessage = message; + newMessage.status = Message.Status.FAILED; + newMessage.createdAt /= 1000; + newMessage.isRead = true; + storeMessageLocally(newMessage, message.id, false); + storeGroupLocally(mGroupId, message); + if (!isAttached()) { + return; + } + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + return; + } + getView().showError(R.string.error_something_went_wrong); + } + + @Override + public void onNext(MessageResponse baseResponse) { + deleteLocalMessage(message); + baseResponse.response.message.status = Message.Status.DELIVERED; + baseResponse.response.message.isRead = true; + storeMessageLocally(baseResponse.response.message, message.id, false); + storeGroupLocally(mGroupId, baseResponse.response.message); + } + }); + } + + @Override + public void onError(Throwable e) { + Log.e("sendMessage", "onError", e); + } + + @Override + public void onNext(Object object) { + } + }); + } + + private void storeMessageLocally(Message newMessage, String oldMessageId, boolean queryLocalDBWhenFinished) { + newMessage.userId = mUserIdPreference.get(); + MessageByIdSpecification messageByIdSpecification = new MessageByIdSpecification(mUserIdPreference.get()); + messageByIdSpecification.setMessageId(newMessage.id); + mMessageRealmRepository.update(Collections.singletonList(newMessage), messageByIdSpecification) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + newMessage.createdAt *= 1000; + if (!queryLocalDBWhenFinished) { + Message messageToDelete = new Message(); + messageToDelete.id = oldMessageId; + messageToDelete.removeFromList(mMessages); + mMessages.add(newMessage); + if (!isAttached()) { + mMessageToUpdate.add(new Pair<>(newMessage, oldMessageId)); + return; + } + getView().lastMessageUpdated(newMessage, oldMessageId); + return; + } + mPage = FIRST_PAGE; + mOffset = 0; + loadMessagesFromDBByGroupId(); + } + + @Override + public void onError(Throwable e) { + Log.e("addMessage", "onError", e); + } + + @Override + public void onNext(Object o) { + } + }); + } + + private void deleteLocalMessage(Message message) { + MessageByIdSpecification messageByIdSpecification = new MessageByIdSpecification(mUserIdPreference.get()); + messageByIdSpecification.setMessageId(message.id); + mMessageRealmRepository.remove(messageByIdSpecification) + .subscribeOn(Schedulers.io()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + Log.e("deleteLocalMessage", "onError", e); + } + + @Override + public void onNext(Object o) { + + } + }); + } + + @Override + public void saveMessage(Message message) { + storeMessageLocally(message) + .subscribe(() -> { + if (!isAttached()) { + return; + } + checkForEmptyMessageAndHideIt(); + message.createdAt *= 1000; + mMessages.add(message); + getView().addOneMessage(message); + }); + storeGroupLocally(mGroupId, message); + } + + private void checkForEmptyMessageAndHideIt() { + if (mMessages.isEmpty()) { + getView().hideLoading(); + } + } + + private Completable storeMessageLocally(Message message) { + MessageByIdSpecification messageByIdSpecification = new MessageByIdSpecification(mUserIdPreference.get()); + messageByIdSpecification.setMessageId(message.id); + return mMessageRealmRepository.update(message, messageByIdSpecification) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } + + /** + * Save last message to local database. + */ + private void storeGroupLocally(long groupId, Message message) { + Group group = new Group(); + group.id = groupId; + group.lastMessage = message; + mGroupRealmRepository.updateGroupMessage(group, new GroupByIdSpecification(mUserIdPreference.get())) + .subscribe(() -> { + if (!isAttached()) { + EventBus.getDefault().post(new GroupUpdatedEvent()); + } + }); + } + + @Override + public void findGroupById() { + Subscription subscription = mGroupRealmRepository.queryForOneById(String.valueOf(mGroupId), new GroupByIdSpecification(mUserIdPreference.get())) + .compose(RxUtils.provideDefaultTransformer()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onNext(Group group) { + if (group != null) { + getView().setGroup(group); + return; + } + loadGroups(); + } + }); + addSubscription(subscription); + } + + private void loadGroups() { + Subscription subscription = mChatService.getGroups() + .compose(RxUtils.provideDefaultTransformer()) + .flatMap(new ErrorHandlingResponseConverter<>()) + .flatMap(chatResponse -> getGroupById(chatResponse.response.groups)) + .subscribe(this::groupsSuccess, this::groupsError); + addSubscription(subscription); + } + + private Observable getGroupById(ArrayList groups) { + Group group = null; + for (Group group1 : groups) { + if (group1.id == mGroupId) { + group = group1; + break; + } + } + return Observable.just(group); + } + + private void groupsError(Throwable throwable) { + Log.e(ChatPresenter.class.getSimpleName(), throwable.getMessage(), throwable); + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + return; + } + getView().showError(R.string.error_something_went_wrong); + } + + private void groupsSuccess(Group group) { + getView().setGroup(group); + mGroupRealmRepository.update(group, new GroupByIdSpecification(mUserIdPreference.get())).subscribe(); + } + + @Override + public long getGroupId() { + return mGroupId; + } + + @Override + protected void afterAttachView(@NonNull ChatView view) { + super.afterAttachView(view); + view.setCallback(this); + mFetchMore = true; + } + + @Override + protected void beforeDetachView(@NonNull ChatView view) { + view.setCallback(null); + super.beforeDetachView(view); + } + + private Observable downloadFileFromUrl(@NonNull String fileUrl, @NonNull String filePath, int itemPosition) { + return Observable.create(subscriber -> { + try { + //set the download URL, a url that points to a file on the internet + //this is the file to be downloaded + URL url = new URL(fileUrl); + //create the new connection + HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); + //set up some things on the connection + urlConnection.setRequestMethod("GET"); + urlConnection.setDoOutput(true); + + FileOutputStream fileOutput; + InputStream inputStream; + File file = new File(filePath); + + if (file.length() > 0) { + urlConnection.setRequestProperty("Range", "bytes=" + file.length() + "-"); + //this will be used to append the downloaded data into the file already existing + fileOutput = new FileOutputStream(file, true); + } else { + //this will be used to write the downloaded data into the file we created + fileOutput = new FileOutputStream(file); + } + + //and connect! + urlConnection.connect(); + + //this is the total size of the file + int totalSize = urlConnection.getContentLength(); + + if (file.length() == totalSize || totalSize == -1) { + subscriber.onNext(getFileTypeFromFilePath(filePath)); + subscriber.onCompleted(); + return; + } + + //this will be used in reading the data from the internet + inputStream = urlConnection.getInputStream(); + + //variable to store total downloaded bytes + long downloadedSize = file.length(); + totalSize += downloadedSize; + + //create a buffer... + byte[] buffer = new byte[1024]; + int bufferLength = 0; + int lastPercentage = 0; + int percentage = 0; + + //now, read through the input buffer and write the contents to the file + while ((bufferLength = inputStream.read(buffer, 0, 1024)) >= 0) { + //add the data in the buffer to the file in the file output stream (the file on the sd card + fileOutput.write(buffer, 0, bufferLength); + //add up the size so we know how much is downloaded + downloadedSize += bufferLength; + percentage = (int) (downloadedSize * 100 / totalSize); + if (percentage - lastPercentage > 1) { + lastPercentage = percentage; + if (isAttached()) { + getView().showItemProgress(itemPosition, percentage); + } + } + } + //close the output stream when done + fileOutput.close(); + + subscriber.onNext(getFileTypeFromFilePath(filePath)); + subscriber.onCompleted(); + + } catch (IOException e) { + e.printStackTrace(); + //noinspection ResultOfMethodCallIgnored + subscriber.onError(e); + subscriber.onCompleted(); + } + }); + } + + public static String findUniqueRandomId(RealmProvider realmProvider, long userId) { + String randomId = generateRandomId(); + MessageByIdSpecification messageByIdSpecification = new MessageByIdSpecification(userId); + messageByIdSpecification.setMessageId(randomId); + Realm realm = realmProvider.provide(); + realm.beginTransaction(); + boolean goOn = true; + while (goOn) { + MessageRealm messageRealm = messageByIdSpecification.toRealmQuery(realm).findFirst(); + if (messageRealm == null) { + goOn = false; + } else { + randomId = generateRandomId(); + messageByIdSpecification.setMessageId(randomId); + } + } + realm.commitTransaction(); + realm.close(); + return randomId; + } + + private static String generateRandomId() { + return "_" + new Random().nextInt(); + } + +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/ChatView.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/ChatView.java new file mode 100644 index 0000000..3189457 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/ChatView.java @@ -0,0 +1,85 @@ +package com.p3.bewoplaner.page.chat; + +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.page.mvp.MVPView; + +import android.net.Uri; +import android.support.annotation.StringRes; + +import java.io.File; +import java.util.ArrayList; + +import okhttp3.MediaType; + +/** + * Created by Rares Teodorescu on 21/02/2017. + */ +interface ChatView extends MVPView { + + void setCallback(Callback callback); + + void messagesLoaded(ArrayList messages, boolean isFirstPage); + + void showLoading(); + + void hideLoading(); + + void showMessagesLoading(); + + void hideMessagesLoading(); + + void showEmptyMessage(); + + void fileDownloadedSuccessfully(String filePath, String fileType); + + void lastMessageUpdated(Message newMessage, String oldMessageId); + + void showError(@StringRes int errorRes); + + void setGroup(Group group); + + void addOneMessage(Message message); + + MediaType getMediaType(Uri fileUri); + + File getFile(Uri fileUri); + + void enableMessageRetry(String messageId); + + void showItemProgress(int itemPosition, int progress); + + void stopItemProgress(int itemPosition); + + void showToastMessage(@StringRes int message); + + interface Callback { + + void loadMessagesFromServerByGroupId(long groupId); + + void loadMessagesFromDBByGroupId(long groupId, boolean fetchWhenFinished); + + void fetchMoreMessagesByGroupId(); + + boolean isLoading(); + + void saveMessage(Message message); + + void resetMessages(); + + void sendFile(Uri fileUri); + + void sendMessage(String message); + + void resendMessage(Message message); + + void downloadFile(Message message, String filePath, int itemPosition); + + void findGroupById(); + + long getGroupId(); + + void fetchNewMessages(long groupId); + } + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/GroupChatActivity.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/GroupChatActivity.java new file mode 100644 index 0000000..129391a --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/GroupChatActivity.java @@ -0,0 +1,159 @@ +package com.p3.bewoplaner.page.chat; + +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.page.chatmembers.ChatMembersActivity; +import com.p3.bewoplaner.utils.Constants; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.view.View; + +/** + * Created by Rares Teodorescu on 21/02/2017. + */ +public class GroupChatActivity extends BaseChatActivity { + + public static Intent createIntent(Context context, Group group) { + Intent intent = new Intent(context, GroupChatActivity.class); + intent.putExtra(GROUP_ARG, group); + return intent; + } + + public static Intent createIntent(Context context, Bundle groupId) { + Intent intent = new Intent(context, GroupChatActivity.class); + intent.putExtra(MESSAGE_ARG, groupId); + return intent; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mChatPresenter.attachView(this); + mCallback.resetMessages(); + if (getIntent().hasExtra(MESSAGE_ARG)) { + Bundle bundle = getIntent().getBundleExtra(MESSAGE_ARG); + long groupId = bundle.getLong(Constants.GROUP_ID); + if (mSystemUtils.isNetworkUnavailable()) { + mCallback.loadMessagesFromDBByGroupId(groupId, true); + showError(R.string.error_no_internet_connection); + } else { + mCallback.loadMessagesFromServerByGroupId(groupId); + } + mCallback.findGroupById(); + } else { + setGroupFromArguments(); + } + } + + @Override + protected void onResume() { + if (!mChatPresenter.isAttached()) { + mChatPresenter.attachView(this); + mBaseChatAdapter.resetItemsProgress(); + if (mSystemUtils.isNetworkUnavailable()) { + mCallback.loadMessagesFromDBByGroupId(mGroup.id, true); + showError(R.string.error_no_internet_connection); + } else { + if (mGroup == null) { + if (getIntent().hasExtra(MESSAGE_ARG)) { + long groupId = getIntent().getBundleExtra(MESSAGE_ARG).getLong(Constants.GROUP_ID); + mCallback.fetchNewMessages(groupId); + return; + } + setGroupFromArguments(); + mCallback.fetchNewMessages(mGroup.id); + } else { + mCallback.fetchNewMessages(mGroup.id); + } + } + } + cancelNotification(); + IntentFilter filter = new IntentFilter(Constants.NOTIFICATION_BROADCAST); + registerReceiver(mReceiver, filter); + super.onResume(); + } + + @Override + protected void loadMessages() { + if (mSystemUtils.isNetworkUnavailable()) { + mCallback.loadMessagesFromDBByGroupId(mGroup.id, true); + showError(R.string.error_no_internet_connection); + } else { + mCallback.loadMessagesFromServerByGroupId(mGroup.id); + } + } + + @Override + protected void setToolbar() { + mSingleChatTitleTextView.setVisibility(View.GONE); + mGroupTitleTextView.setVisibility(View.VISIBLE); + mGroupMembersTextView.setVisibility(View.VISIBLE); + mExtraImageView.setVisibility(View.VISIBLE); + mGroupMembersTextView.setText(mGroup.getContactsNames()); + mExtraImageView.setImageResource(R.drawable.vertical_dots); + mChatImageView.setImageResource(mGroup.isHelpGroup() ? R.drawable.avatar_help_group : R.drawable.avatar_team_group); + mGroupTitleTextView.setText(mGroup.name); + mGroupTitleTextView.setTextColor(ContextCompat.getColor(this, + mGroup.isHelpGroup() ? R.color.colorHelpGroup : R.color.colorTeamGroup)); + } + + @Override + protected void onPause() { + mChatPresenter.detachView(); + unregisterReceiver(mReceiver); + super.onPause(); + } + + @Override + protected void extraImageClicked() { + startActivity(ChatMembersActivity.createIntent(this, mGroup)); + } + + @Override + protected BaseChatAdapter getAdapter() { + return new GroupChatAdapter(mUserIdPreference.get()); + } + + @Override + public void setGroup(Group group) { + mGroup = group; + setToolbar(); + } + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Bundle bundle = intent.getBundleExtra(Constants.MESSAGE); + if (mCallback == null) { + mChatPresenter.attachView(GroupChatActivity.this); + } + if (mCallback.getGroupId() == bundle.getLong(Constants.GROUP_ID)) { + Message message = new Message(bundle.getString(Constants.MESSAGE_ID), + bundle.getLong(Constants.GROUP_ID), + bundle.getLong(Constants.CREATED_AT), + bundle.getString(Constants.MESSAGE_TEXT), + bundle.getString(Constants.FILE), + bundle.getString(Constants.SMALLER_IMAGE), + bundle.getString(Constants.FILENAME), + null, + bundle.getLong(Constants.USER_ID), + bundle.getString(Constants.USERNAME), + bundle.getString(Constants.USER_AVATAR), + Message.Status.DELIVERED, + true, + mUserIdPreference.get(), + false); + mCallback.saveMessage(message); + Bundle results = getResultExtras(true); + results.putBoolean(Constants.CHAT_IS_ALREADY_OPEN, true); + } + } + }; +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/GroupChatAdapter.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/GroupChatAdapter.java new file mode 100644 index 0000000..296a42d --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/GroupChatAdapter.java @@ -0,0 +1,85 @@ +package com.p3.bewoplaner.page.chat; + +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.utils.DateUtils; +import com.p3.bewoplaner.utils.ui.ViewUtils; + +import android.support.v7.widget.RecyclerView; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ProgressBar; + +/** + * Created by Rares Teodorescu on 21/02/2017. + */ +class GroupChatAdapter extends BaseChatAdapter { + + GroupChatAdapter(long userId) { + super(userId); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == LEFT_CHAT_TYPE) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_group_chat_left, parent, false); + return new MessageViewHolder(view); + } else if (viewType == RIGHT_CHAT_TYPE) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_group_chat_right, parent, false); + return new MessageViewHolder(view); + } else if (viewType == DATE_TYPE) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chat_date, parent, false); + return new DateViewHolder(view); + } + ProgressBar progressBar = new ProgressBar(parent.getContext()); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); + params.gravity = Gravity.CENTER; + params.setMargins(0, (int) parent.getContext().getResources().getDimension(R.dimen.messages_progress_top_margin), 0, 0); + progressBar.setLayoutParams(params); + ViewUtils.setProgressBarColorToOrange(progressBar); + return new ProgressViewHolder(progressBar); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + int viewType = getItemViewType(position); + switch (viewType) { + case LOADING_TYPE: + break; + case DATE_TYPE: + ((DateViewHolder) holder).dateTextView.setText(DateUtils.toDayOfYearString(mDateList.get(position))); + break; + case LEFT_CHAT_TYPE: + final Message leftMessage = mMessages.get(position - mPositionOffset.get(position)); + MessageViewHolder leftMessageViewHolder = (MessageViewHolder) holder; + setGroupMessage(leftMessageViewHolder, leftMessage, position, true); + break; + case RIGHT_CHAT_TYPE: + final Message rightMessage = mMessages.get(position - mPositionOffset.get(position)); + MessageViewHolder rightMessageViewHolder = (MessageViewHolder) holder; + setGroupMessage(rightMessageViewHolder, rightMessage, position, false); + if (rightMessage.status == Message.Status.DELIVERED) { + rightMessageViewHolder.statusProgressBar.setVisibility(View.GONE); + rightMessageViewHolder.statusImageView.setVisibility(View.VISIBLE); + rightMessageViewHolder.statusImageView.setImageResource(R.drawable.delivered); + rightMessageViewHolder.sendingFailedTextView.setVisibility(View.GONE); + } else if (rightMessage.status == Message.Status.FAILED) { + rightMessageViewHolder.statusProgressBar.setVisibility(View.GONE); + rightMessageViewHolder.statusImageView.setVisibility(View.VISIBLE); + rightMessageViewHolder.statusImageView.setImageResource(R.drawable.failed); + rightMessageViewHolder.sendingFailedTextView.setVisibility(View.VISIBLE); + } else { + ViewUtils.setProgressBarColorToOrange(rightMessageViewHolder.statusProgressBar); + rightMessageViewHolder.statusProgressBar.setVisibility(View.VISIBLE); + rightMessageViewHolder.sendingFailedTextView.setVisibility(View.GONE); + rightMessageViewHolder.statusImageView.setVisibility(View.GONE); + } + break; + } + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/SingleChatActivity.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/SingleChatActivity.java new file mode 100644 index 0000000..cb6336f --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/SingleChatActivity.java @@ -0,0 +1,165 @@ +package com.p3.bewoplaner.page.chat; + +import com.bumptech.glide.Glide; +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.utils.Constants; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.text.TextUtils; +import android.view.View; + +/** + * Created by Rares Teodorescu on 21/02/2017. + */ +public class SingleChatActivity extends BaseChatActivity { + + public static Intent createIntent(Context context, Group contact) { + Intent intent = new Intent(context, SingleChatActivity.class); + intent.putExtra(GROUP_ARG, contact); + return intent; + } + + public static Intent createIntent(Context context, Bundle bundle) { + Intent intent = new Intent(context, SingleChatActivity.class); + intent.putExtra(MESSAGE_ARG, bundle); + return intent; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mChatPresenter.attachView(this); + mCallback.resetMessages(); + if (getIntent().hasExtra(MESSAGE_ARG)) { + Bundle bundle = getIntent().getBundleExtra(MESSAGE_ARG); + mGroup = new Group(); + mGroup.id = bundle.getLong(Constants.GROUP_ID); + mGroup.name = bundle.getString(Constants.USERNAME); + mGroup.avatar = bundle.getString(Constants.USER_AVATAR); + + if (mSystemUtils.isNetworkUnavailable()) { + mCallback.loadMessagesFromDBByGroupId(mGroup.id, true); + showError(R.string.error_no_internet_connection); + } else { + mCallback.loadMessagesFromServerByGroupId(mGroup.id); + } + mCallback.findGroupById(); + } else { + setGroupFromArguments(); + } + } + + @Override + protected void onResume() { + if (!mChatPresenter.isAttached()) { + mChatPresenter.attachView(this); + mBaseChatAdapter.resetItemsProgress(); + if (mSystemUtils.isNetworkUnavailable()) { + mCallback.loadMessagesFromDBByGroupId(mGroup.id, true); + showError(R.string.error_no_internet_connection); + } else { + mCallback.fetchNewMessages(mGroup.id); + } + } + cancelNotification(); + IntentFilter filter = new IntentFilter(Constants.NOTIFICATION_BROADCAST); + registerReceiver(mReceiver, filter); + super.onResume(); + } + + @Override + protected void onPause() { + unregisterReceiver(mReceiver); + mChatPresenter.detachView(); + super.onPause(); + } + + @Override + protected void loadMessages() { + if (!TextUtils.isEmpty(mGroup.mobile)) { + mExtraImageView.setVisibility(View.VISIBLE); + mExtraImageView.setImageResource(R.drawable.call); + } + if (mSystemUtils.isNetworkUnavailable()) { + mCallback.loadMessagesFromDBByGroupId(mGroup.id, true); + showError(R.string.error_no_internet_connection); + } else { + mCallback.loadMessagesFromServerByGroupId(mGroup.id); + } + } + + @Override + protected void setToolbar() { + mSingleChatTitleTextView.setVisibility(View.VISIBLE); + mSingleChatTitleTextView.setText(mGroup.name); + mSingleChatTitleTextView.setTextColor(ContextCompat.getColor(this, + mGroup.isEmployee(mGroup.users) ? R.color.colorHelpGroup : R.color.colorBlack)); + if (TextUtils.isEmpty(mGroup.avatar)) { + mChatImageView.setImageResource(R.drawable.avatar); + } else { + Glide.with(this).load(mGroup.avatar) + .placeholder(R.drawable.avatar) + .dontAnimate() + .into(mChatImageView); + } + } + + @Override + protected void extraImageClicked() { + Intent intent = new Intent(Intent.ACTION_DIAL, Uri.fromParts("tel", + mGroup.mobile, + null)); + startActivity(intent); + } + + @Override + protected BaseChatAdapter getAdapter() { + return new SingleChatAdapter(mUserIdPreference.get()); + } + + @Override + public void setGroup(Group groups) { + mGroup = groups; + loadMessages(); + setToolbar(); + } + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Bundle bundle = intent.getBundleExtra(Constants.MESSAGE); + if (mCallback == null) { + mChatPresenter.attachView(SingleChatActivity.this); + } + if (mCallback.getGroupId() == bundle.getLong(Constants.GROUP_ID)) { + Message message = new Message(bundle.getString(Constants.MESSAGE_ID), + bundle.getLong(Constants.GROUP_ID), + bundle.getLong(Constants.CREATED_AT), + bundle.getString(Constants.MESSAGE_TEXT), + bundle.getString(Constants.FILE), + bundle.getString(Constants.SMALLER_IMAGE), + bundle.getString(Constants.FILENAME), + null, + bundle.getLong(Constants.USER_ID), + bundle.getString(Constants.USERNAME), + bundle.getString(Constants.USER_AVATAR), + Message.Status.DELIVERED, + true, + mUserIdPreference.get(), + false); + mCallback.saveMessage(message); + Bundle results = getResultExtras(true); + results.putBoolean(Constants.CHAT_IS_ALREADY_OPEN, true); + } + } + }; +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/SingleChatAdapter.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/SingleChatAdapter.java new file mode 100644 index 0000000..1877c38 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chat/SingleChatAdapter.java @@ -0,0 +1,86 @@ +package com.p3.bewoplaner.page.chat; + +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.utils.DateUtils; +import com.p3.bewoplaner.utils.ui.ViewUtils; + +import android.support.v7.widget.RecyclerView; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ProgressBar; + +/** + * Created by Rares Teodorescu on 21/02/2017. + */ +class SingleChatAdapter extends BaseChatAdapter { + + SingleChatAdapter(long userId) { + super(userId); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == LEFT_CHAT_TYPE) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_single_chat_left, parent, false); + return new MessageViewHolder(view); + } else if (viewType == RIGHT_CHAT_TYPE) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_single_chat_right, parent, false); + return new MessageViewHolder(view); + } else if (viewType == DATE_TYPE) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chat_date, parent, false); + return new DateViewHolder(view); + } + ProgressBar progressBar = new ProgressBar(parent.getContext()); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); + params.gravity = Gravity.CENTER; + params.setMargins(0, (int) parent.getContext().getResources().getDimension(R.dimen.messages_progress_top_margin), 0, 0); + progressBar.setLayoutParams(params); + ViewUtils.setProgressBarColorToOrange(progressBar); + return new ProgressViewHolder(progressBar); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + int viewType = getItemViewType(position); + switch (viewType) { + case LOADING_TYPE: + break; + case DATE_TYPE: + ((DateViewHolder) holder).dateTextView.setText(DateUtils.toDayOfYearString(mDateList.get(position))); + break; + case LEFT_CHAT_TYPE: + MessageViewHolder leftChatHolder = (MessageViewHolder) holder; + final Message leftMessage = mMessages.get(position - mPositionOffset.get(position)); + setSingleMessage(leftChatHolder, leftMessage, position, true); + break; + case RIGHT_CHAT_TYPE: + MessageViewHolder rightChatHolder = (MessageViewHolder) holder; + final Message rightMessage = mMessages.get(position - mPositionOffset.get(position)); + setSingleMessage(rightChatHolder, rightMessage, position, false); + if (rightMessage.status == Message.Status.DELIVERED) { + rightChatHolder.statusProgressBar.setVisibility(View.GONE); + rightChatHolder.statusImageView.setVisibility(View.VISIBLE); + rightChatHolder.statusImageView.setImageResource(R.drawable.delivered); + rightChatHolder.sendingFailedTextView.setVisibility(View.GONE); + } else if (rightMessage.status == Message.Status.FAILED) { + rightChatHolder.statusProgressBar.setVisibility(View.GONE); + rightChatHolder.statusImageView.setVisibility(View.VISIBLE); + rightChatHolder.statusImageView.setImageResource(R.drawable.failed); + rightChatHolder.sendingFailedTextView.setVisibility(View.VISIBLE); + } else { + ViewUtils.setProgressBarColorToOrange(rightChatHolder.statusProgressBar); + rightChatHolder.statusProgressBar.setVisibility(View.VISIBLE); + rightChatHolder.sendingFailedTextView.setVisibility(View.GONE); + rightChatHolder.statusImageView.setVisibility(View.GONE); + } + break; + } + } + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chatmembers/ChatMembersActivity.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chatmembers/ChatMembersActivity.java new file mode 100644 index 0000000..2982a55 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chatmembers/ChatMembersActivity.java @@ -0,0 +1,91 @@ +package com.p3.bewoplaner.page.chatmembers; + +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.utils.ui.HorizontalDividerItemDecoration; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +/** + * Created by imarneanu on 2/24/17. + */ +public class ChatMembersActivity extends AppCompatActivity { + protected final static String GROUP_ARG = "group_arg"; + + @BindView(R.id.chat_toolbar_image_view) + ImageView mChatImageView; + + @BindView(R.id.chat_toolbar_group_title) + TextView mGroupTitleTextView; + + @BindView(R.id.chat_toolbar_group_members) + TextView mGroupMembersTextView; + + @BindView(R.id.chat_toolbar_extra_image) + ImageView mExtraImageView; + + @BindView(R.id.chat_members_recycler_view) + RecyclerView mChatMembersRecyclerView; + + private RecyclerView.ItemDecoration mHorizontalDividerItemDecoration; + + public static Intent createIntent(Context context, Group group) { + Intent intent = new Intent(context, ChatMembersActivity.class); + intent.putExtra(GROUP_ARG, group); + return intent; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_chat_members); + ButterKnife.bind(this); + Group group = getIntent().getParcelableExtra(GROUP_ARG); + setToolbar(group); + + mHorizontalDividerItemDecoration = new HorizontalDividerItemDecoration.Builder(this) + .colorResId(R.color.colorFill).build(); + mChatMembersRecyclerView.addItemDecoration(mHorizontalDividerItemDecoration); + mChatMembersRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + + ChatMembersAdapter contactsAdapter = new ChatMembersAdapter(group.users); + mChatMembersRecyclerView.setAdapter(contactsAdapter); + } + + private void setToolbar(Group group) { + mGroupTitleTextView.setVisibility(View.VISIBLE); + mGroupMembersTextView.setVisibility(View.GONE); + mExtraImageView.setVisibility(View.GONE); + mChatImageView.setImageResource(group.isHelpGroup() ? R.drawable.avatar_help_group + : R.drawable.avatar_team_group); + mGroupTitleTextView.setText(group.name); + mGroupTitleTextView.setTextColor(ContextCompat.getColor(this, + group.isHelpGroup() ? R.color.colorHelpGroup : R.color.colorTeamGroup)); + } + + @OnClick(R.id.chat_toolbar_back_arrow) + void backPressed() { + onBackPressed(); + } + + @Override + public void onDestroy() { + mChatMembersRecyclerView.setAdapter(null); + mChatMembersRecyclerView.removeItemDecoration(mHorizontalDividerItemDecoration); + super.onDestroy(); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chatmembers/ChatMembersAdapter.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chatmembers/ChatMembersAdapter.java new file mode 100644 index 0000000..0b88c5a --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/chatmembers/ChatMembersAdapter.java @@ -0,0 +1,91 @@ +package com.p3.bewoplaner.page.chatmembers; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.model.User; + +import android.content.Intent; +import android.net.Uri; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Locale; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import de.hdodenhof.circleimageview.CircleImageView; + +/** + * Created by imarneanu on 2/24/17. + */ +class ChatMembersAdapter extends RecyclerView.Adapter { + + private ArrayList mUsers; + + ChatMembersAdapter(ArrayList users) { + mUsers = users; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chat_member, parent, + false); + return new ContactViewHolder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + ContactViewHolder viewHolder = (ContactViewHolder) holder; + User user = mUsers.get(position); + viewHolder.contactTextView.setText(String.format(Locale.getDefault(), "%s %s", user.firstName, user.lastName)); + viewHolder.contactTextView.setTextColor(ContextCompat.getColor(viewHolder.contactTextView.getContext(), + user.isEmployee ? R.color.colorBlack : R.color.colorHelpGroup)); + viewHolder.phoneImageView.setVisibility(TextUtils.isEmpty(user.mobile) ? View.GONE : View.VISIBLE); + if (TextUtils.isEmpty(user.avatar)) { + viewHolder.avatarCircleImageView.setImageResource(R.drawable.avatar_list); + return; + } + Glide.with(viewHolder.avatarCircleImageView.getContext()) + .load(user.avatar) + .placeholder(R.drawable.avatar) + .dontAnimate() + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(viewHolder.avatarCircleImageView); + } + + @Override + public int getItemCount() { + return mUsers.size(); + } + + class ContactViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.chat_member_avatar) + CircleImageView avatarCircleImageView; + @BindView(R.id.chat_member_name) + TextView contactTextView; + @BindView(R.id.chat_member_phone) + ImageView phoneImageView; + + ContactViewHolder(View view) { + super(view); + ButterKnife.bind(this, view); + } + + @OnClick(R.id.chat_member_phone) + void onPhoneClicked() { + Intent intent = new Intent(Intent.ACTION_DIAL, Uri.fromParts("tel", + mUsers.get(getAdapterPosition()).mobile, + null)); + phoneImageView.getContext().startActivity(intent); + } + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/common/BaseFragment.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/common/BaseFragment.java new file mode 100644 index 0000000..7bcd467 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/common/BaseFragment.java @@ -0,0 +1,22 @@ +package com.p3.bewoplaner.page.common; + +import android.support.v4.app.Fragment; + +import butterknife.Unbinder; + +/** + * Created by imarneanu on 2/22/17. + */ +public class BaseFragment extends Fragment { + + protected Unbinder mUnbinder; + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (mUnbinder == null) { + return; + } + mUnbinder.unbind(); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/common/PermissionGrantedListener.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/common/PermissionGrantedListener.java new file mode 100644 index 0000000..818cb0c --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/common/PermissionGrantedListener.java @@ -0,0 +1,17 @@ +package com.p3.bewoplaner.page.common; + +/** + * Callback for requesting permission. + *

+ * Created by Rares on 29/08/16. + */ +public interface PermissionGrantedListener { + + /** + * Method called when permission is granted. + * + * @param requestCode For given permission + */ + void onPermissionsGranted(int requestCode); + +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/common/RuntimePermissionActivity.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/common/RuntimePermissionActivity.java new file mode 100644 index 0000000..b0d731e --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/common/RuntimePermissionActivity.java @@ -0,0 +1,109 @@ +package com.p3.bewoplaner.page.common; + +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.utils.DialogHandler; + +import android.app.Dialog; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.util.SparseIntArray; + + +/** + * This class handles runtime permissions from activities. + *

+ * Created by Rares on 29/08/16. + */ +public abstract class RuntimePermissionActivity extends AppCompatActivity { + + private SparseIntArray mErrorString; + private PermissionGrantedListener mPermissionGrantedListener; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mErrorString = new SparseIntArray(); + } + + /** + * @param requestedPermissions the permissions that we need to grant + * @param stringId the id of the message that we show while requesting + * @param requestCode the request code for this permission request + * @param permissionGrantedListener the listener which it's method will be called when + * permission is granted + */ + public void requestAppPermissions(final String[] requestedPermissions, + final int stringId, + final int requestCode, + @NonNull PermissionGrantedListener permissionGrantedListener) { + mPermissionGrantedListener = permissionGrantedListener; + mErrorString.put(requestCode, stringId); + int permissionCheck = PackageManager.PERMISSION_GRANTED; + boolean shouldShowRequestPermissionRationale = false; + for (String permission : requestedPermissions) { + permissionCheck = permissionCheck + ContextCompat.checkSelfPermission(this, permission); + shouldShowRequestPermissionRationale = shouldShowRequestPermissionRationale || ActivityCompat.shouldShowRequestPermissionRationale(this, permission); + } + if (permissionCheck != PackageManager.PERMISSION_GRANTED) { + if (shouldShowRequestPermissionRationale) { + Dialog dialog = DialogHandler.createNoTitleDialog(this, + stringId, + true, + R.string.permission_grant, + (dialogInterface, i) -> requestPermissionCorrectly(requestedPermissions, requestCode)); + dialog.show(); + + } else { + requestPermissionCorrectly(requestedPermissions, requestCode); + } + } else { + mPermissionGrantedListener.onPermissionsGranted(requestCode); + } + } + + protected void requestPermissionCorrectly(String[] requestedPermissions, int requestCode) { + ActivityCompat.requestPermissions(this, requestedPermissions, requestCode); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + if (mErrorString.size() == 0 || mErrorString.get(requestCode) == 0) { + return; + } + + int permissionCheck = PackageManager.PERMISSION_GRANTED; + for (int permission : grantResults) { + permissionCheck = permissionCheck + permission; + } + if ((grantResults.length > 0) && permissionCheck == PackageManager.PERMISSION_GRANTED) { + mPermissionGrantedListener.onPermissionsGranted(requestCode); + } else { + Dialog dialog = DialogHandler.createNoTitleDialog(this, + mErrorString.get(requestCode), + true, + R.string.permission_enable, + (dialogInterface, i) -> { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setData(Uri.parse("package:" + getPackageName())); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); + intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + startActivity(intent); + }); + dialog.show(); + } + } + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsAdapter.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsAdapter.java new file mode 100644 index 0000000..b6dfd73 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsAdapter.java @@ -0,0 +1,174 @@ +package com.p3.bewoplaner.page.contacts; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.utils.DateUtils; +import com.p3.bewoplaner.utils.ui.ViewUtils; + +import android.graphics.Typeface; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import java.util.ArrayList; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import de.hdodenhof.circleimageview.CircleImageView; +import hani.momanii.supernova_emoji_library.Helper.EmojiconTextView; + +/** + * Created by imarneanu on 2/21/17. + */ +class ContactsAdapter extends RecyclerView.Adapter { + + private static final int GROUP_TYPE = 0; + private static final int CONTACT_TYPE = 1; + + private ArrayList mGroups; + private OnOpenClickListener mItemClickListener; + private long mUserId; + + interface OnOpenClickListener { + void onOpenGroupMessage(Group group); + + void onOpenPrivateMessage(Group contact); + } + + ContactsAdapter(OnOpenClickListener openClickListener, long userId) { + mItemClickListener = openClickListener; + mGroups = new ArrayList<>(); + mUserId = userId; + } + + void setGroups(ArrayList groups) { + mGroups.clear(); + if (groups != null) { + mGroups.addAll(groups); + } + notifyDataSetChanged(); + } + + @Override + public final int getItemViewType(int position) { + if (mGroups.get(position).users.size() > 2) { + return GROUP_TYPE; + } + return CONTACT_TYPE; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_contact, parent, + false); + return new ContactViewHolder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + Group group = mGroups.get(position); + ContactViewHolder viewHolder = (ContactViewHolder) holder; + setLastMessage(group, viewHolder); + + int type = getItemViewType(position); + if (type == GROUP_TYPE) { + viewHolder.name.setText(group.name); + viewHolder.name.setTextColor(ContextCompat.getColor(viewHolder.name.getContext(), + group.isHelpGroup() ? R.color.colorHelpGroup : R.color.colorTeamGroup)); + viewHolder.avatar.setImageResource(group.isHelpGroup() ? + R.drawable.avatar_help_group : R.drawable.avatar_team_group); + return; + } + if(type == CONTACT_TYPE) { + viewHolder.name.setTextColor(ContextCompat.getColor(viewHolder.name.getContext(), + group.isEmployee(mGroups.get(position).users) ? R.color.colorBlack : R.color.colorHelpGroup)); + } + viewHolder.name.setText(group.name); + Glide.with(viewHolder.avatar.getContext()).load(group.avatar) + .placeholder(R.drawable.avatar_list) + .dontAnimate() + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(viewHolder.avatar); + } + + private void setLastMessage(Group group, ContactViewHolder viewHolder) { + if (group.lastMessage == null) { + viewHolder.lastMessage.setText(null); + viewHolder.messageTimestamp.setText(null); + viewHolder.messageStatus.setImageResource(0); + return; + } + boolean isMessageSentByUser = group.lastMessage.senderId == mUserId; + if (!group.isRead() && !isMessageSentByUser) { + viewHolder.lastMessage.setTypeface(null, Typeface.BOLD); + viewHolder.lastMessage.setTextColor(ContextCompat.getColor(viewHolder.lastMessage.getContext(), R.color.colorDefaultBright)); + } else { + viewHolder.lastMessage.setTypeface(null); + viewHolder.lastMessage.setTextColor(ContextCompat.getColor(viewHolder.lastMessage.getContext(), R.color.colorSubtitle)); + } + viewHolder.lastMessage.setText(TextUtils.isEmpty(group.lastMessage.message) ? group.lastMessage.originalFilename : group.lastMessage.message); + viewHolder.messageTimestamp.setText(DateUtils.toHourOrDateFormatHour(group.lastMessage.createdAt)); + if (!isMessageSentByUser) { + viewHolder.messageStatus.setImageResource(0); + return; + } + int resourceId = group.lastMessage.getStatusResourceId(); + if (resourceId != 0) { + viewHolder.messageStatusPending.setVisibility(resourceId == -1 ? View.VISIBLE : View.GONE); + if (resourceId != -1) { + viewHolder.messageStatus.setImageResource(group.lastMessage.getStatusResourceId()); + } else { + ViewUtils.setProgressBarColor(viewHolder.messageStatusPending, R.color.colorHint); + } + } + } + + @Override + public int getItemCount() { + return mGroups.size(); + } + + class ContactViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.contact_avatar) + CircleImageView avatar; + + @BindView(R.id.contact_name) + TextView name; + + @BindView(R.id.contact_message_last) + EmojiconTextView lastMessage; + + @BindView(R.id.contact_message_timestamp) + TextView messageTimestamp; + + @BindView(R.id.contact_message_status) + ImageView messageStatus; + + @BindView(R.id.contact_message_pending) + ProgressBar messageStatusPending; + + ContactViewHolder(View view) { + super(view); + ButterKnife.bind(this, view); + } + + @OnClick(R.id.contact_container) + void onClickContact() { + int type = getItemViewType(); + if (type == GROUP_TYPE) { + mItemClickListener.onOpenGroupMessage(mGroups.get(getAdapterPosition())); + return; + } + mItemClickListener.onOpenPrivateMessage(mGroups.get(getAdapterPosition())); + } + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsComponent.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsComponent.java new file mode 100644 index 0000000..d58cc13 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsComponent.java @@ -0,0 +1,17 @@ +package com.p3.bewoplaner.page.contacts; + +import com.p3.bewoplaner.di.ApplicationComponent; +import com.p3.bewoplaner.di.scopes.ActivityScope; +import com.p3.bewoplaner.page.chat.ChatModule; + +import dagger.Component; + +/** + * Created by imarneanu on 2/20/17. + */ +@ActivityScope +@Component(dependencies = ApplicationComponent.class, modules = ChatModule.class) +public interface ContactsComponent { + void inject(ContactsFragment contactsFragment); + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsFragment.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsFragment.java new file mode 100644 index 0000000..524b94c --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsFragment.java @@ -0,0 +1,257 @@ +package com.p3.bewoplaner.page.contacts; + +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.events.GroupUpdatedEvent; +import com.p3.bewoplaner.data.prefs.LongPreference; +import com.p3.bewoplaner.di.InjectionHelper; +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.page.chat.GroupChatActivity; +import com.p3.bewoplaner.page.chat.SingleChatActivity; +import com.p3.bewoplaner.page.common.BaseFragment; +import com.p3.bewoplaner.utils.Constants; +import com.p3.bewoplaner.utils.ui.EmptyLayout; +import com.p3.bewoplaner.utils.ui.HorizontalDividerItemDecoration; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.Snackbar; +import android.support.v4.content.ContextCompat; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.SearchView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; + +import javax.inject.Inject; +import javax.inject.Named; + +import butterknife.BindView; +import butterknife.ButterKnife; + +/** + * Created by imarneanu on 2/13/17. + */ +public class ContactsFragment extends BaseFragment + implements ContactsView, ContactsAdapter.OnOpenClickListener, SearchView.OnQueryTextListener { + + @BindView(R.id.contacts_recycler_view) + RecyclerView mContactsRecyclerView; + + @BindView(R.id.contacts_empty_layout) + EmptyLayout mEmptyLayout; + + @BindView(R.id.contacts_coordinator_layout) + CoordinatorLayout mCoordinatorLayout; + + @BindView(R.id.contacts_swipe_refresh_layout) + SwipeRefreshLayout mSwipeRefreshLayout; + + @Inject + ContactsPresenter mContactsPresenter; + + @Inject + @Named(AppPrefsConstants.USER_ID) + LongPreference mUserIdPreference; + + private Callback mCallback; + private ContactsAdapter mContactsAdapter; + private RecyclerView.ItemDecoration mHorizontalDividerItemDecoration; + private Snackbar mSnackbar; + private SearchView mSearchView; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + InjectionHelper.getContactsComponent(context).inject(this); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_contacts, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mUnbinder = ButterKnife.bind(this, view); + getActivity().setTitle(R.string.title_contacts); + EventBus.getDefault().register(this); + mContactsPresenter.attachView(this); + IntentFilter filter = new IntentFilter(Constants.NOTIFICATION_BROADCAST); + getActivity().registerReceiver(mReceiver, filter); + + mHorizontalDividerItemDecoration = new HorizontalDividerItemDecoration.Builder(getActivity()) + .colorResId(R.color.colorFill).build(); + mContactsRecyclerView.addItemDecoration(mHorizontalDividerItemDecoration); + mContactsRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + + mContactsAdapter = new ContactsAdapter(this, mUserIdPreference.get()); + mContactsRecyclerView.setAdapter(mContactsAdapter); + + mSwipeRefreshLayout.setColorSchemeResources(R.color.colorDefault, R.color.colorDefaultBright); + mSwipeRefreshLayout.setOnRefreshListener(this::refresh); + } + + private void refresh() { + mSwipeRefreshLayout.setRefreshing(false); + resetSearch(); + mCallback.loadGroups(); + } + + @Override + public void onResume() { + super.onResume(); + resetSearch(); + mCallback.loadGroups(); + mCallback.loadGroupsFromDB(); + } + + private void resetSearch() { + if (mSearchView != null && !mSearchView.isIconified()) { + mSearchView.setQuery(null, false); + mSearchView.setIconified(true); + } + } + + @Override + public void showError(@StringRes int message) { + if (mSnackbar == null) { + mSnackbar = Snackbar.make(mCoordinatorLayout, message, Snackbar.LENGTH_LONG); + mSnackbar.setActionTextColor(ContextCompat.getColor(getActivity(), R.color.colorDefault)); + } + mSnackbar.setText(message); + mSnackbar.show(); + } + + @Override + public void onDestroyView() { + EventBus.getDefault().unregister(this); + mContactsAdapter = null; + mContactsRecyclerView.setAdapter(null); + mContactsRecyclerView.removeItemDecoration(mHorizontalDividerItemDecoration); + mSwipeRefreshLayout.setOnRefreshListener(null); + super.onDestroyView(); + getActivity().unregisterReceiver(mReceiver); + mContactsPresenter.detachView(); + } + + @Override + public void setCallback(Callback callback) { + mCallback = callback; + } + + @Override + public void showProgress() { + mEmptyLayout.showLoading(); + } + + @Override + public void hideProgress() { + mEmptyLayout.setVisibility(View.GONE); + } + + @Override + public void setGroups(ArrayList groups) { + mContactsAdapter.setGroups(groups); + } + + @Override + public void onOpenGroupMessage(Group group) { + if (TextUtils.isEmpty(mSearchView.getQuery())) { + mSearchView.setIconified(true); + } + startActivity(GroupChatActivity.createIntent(getActivity(), group)); + } + + @Override + public void onOpenPrivateMessage(Group group) { + startActivity(SingleChatActivity.createIntent(getActivity(), group)); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.activity_main_menu, menu); + + MenuItem menuItem = menu.findItem(R.id.action_search); + mSearchView = (SearchView) menuItem.getActionView(); + mSearchView.setOnQueryTextListener(this); + } + + @Override + public boolean onQueryTextSubmit(String query) { + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + if (TextUtils.isEmpty(newText)) { + mCallback.loadGroupsFromDB(); + } else { + mCallback.loadGroupsContainingName(newText); + } + return false; + } + + @SuppressWarnings("unused") + @Subscribe(threadMode = ThreadMode.MAIN) + public void onInvalidToken(GroupUpdatedEvent groupUpdatedEvent) { + if (mCallback == null) { + mContactsPresenter.attachView(this); + } + mCallback.loadGroupsFromDB(); + } + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mCallback == null) { + return; + } + Bundle bundle = intent.getBundleExtra(Constants.MESSAGE); + Message message = new Message(bundle.getString(Constants.MESSAGE_ID), + bundle.getLong(Constants.GROUP_ID), + bundle.getLong(Constants.CREATED_AT), + bundle.getString(Constants.MESSAGE_TEXT), + bundle.getString(Constants.FILE), + bundle.getString(Constants.SMALLER_IMAGE), + bundle.getString(Constants.FILENAME), + null, + bundle.getLong(Constants.USER_ID), + bundle.getString(Constants.USERNAME), + bundle.getString(Constants.USER_AVATAR), + Message.Status.DELIVERED, + false, + mUserIdPreference.get(), + false); + mCallback.storeGroupLocally(bundle.getLong(Constants.GROUP_ID), message); + } + }; +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsPresenter.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsPresenter.java new file mode 100644 index 0000000..81edf6f --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsPresenter.java @@ -0,0 +1,197 @@ +package com.p3.bewoplaner.page.contacts; + +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.api.common.ErrorHandlingResponseConverter; +import com.p3.bewoplaner.data.api.responses.GroupResponse; +import com.p3.bewoplaner.data.api.services.ChatService; +import com.p3.bewoplaner.data.prefs.LongPreference; +import com.p3.bewoplaner.data.repository.GroupRealmRepository; +import com.p3.bewoplaner.data.repository.UserRealmRepository; +import com.p3.bewoplaner.data.repository.specifications.GroupByIdSpecification; +import com.p3.bewoplaner.data.repository.specifications.GroupByNameSpecification; +import com.p3.bewoplaner.data.repository.specifications.GroupSpecification; +import com.p3.bewoplaner.data.repository.specifications.UserByIdSpecification; +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.page.mvp.MVPAbstractPresenter; +import com.p3.bewoplaner.utils.SystemUtils; +import com.p3.bewoplaner.utils.rx.RxUtils; + +import android.support.annotation.NonNull; +import android.util.Log; + +import java.util.ArrayList; + +import javax.inject.Inject; +import javax.inject.Named; + +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +/** + * Created by imarneanu on 2/20/17. + */ +class ContactsPresenter extends MVPAbstractPresenter + implements ContactsView.Callback { + + @Inject + ChatService mChatService; + + @Inject + GroupRealmRepository mGroupRealmRepository; + + @Inject + UserRealmRepository mUserRealmRepository; + + @Inject + SystemUtils mSystemUtils; + + @Inject + @Named(AppPrefsConstants.USER_ID) + LongPreference mUserIdReference; + + @Inject + ContactsPresenter() { + } + + @Override + protected void afterAttachView(@NonNull ContactsView view) { + super.afterAttachView(view); + view.setCallback(this); + view.showProgress(); + } + + @Override + protected void beforeDetachView(@NonNull ContactsView view) { + view.setCallback(null); + super.beforeDetachView(view); + } + + /** + * Get all user groups from server. + */ + + @Override + public void loadGroups() { + Subscription subscription = mChatService.getGroups() + .flatMap(new ErrorHandlingResponseConverter<>()) + .flatMap(new Func1>>() { + @Override + public Observable> call(GroupResponse groupResponse) { + if (groupResponse.response.groups != null) { + for (Group group : groupResponse.response.groups) { + group.userId = mUserIdReference.get(); + if (group.lastMessage != null) { + group.lastMessage.status = Message.Status.DELIVERED; + group.lastMessage.userId = mUserIdReference.get(); + } + mUserRealmRepository.update(group.users, new UserByIdSpecification()).subscribe(); + } + } + return Observable.just(groupResponse.response.groups); + } + }) + .compose(RxUtils.provideDefaultTransformer()) + .subscribe(this::groupsSuccess, this::groupsError); + addSubscription(subscription); + } + + /** + * Send error messages to view. + */ + private void groupsError(Throwable throwable) { + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + return; + } + getView().showError(R.string.error_something_went_wrong); + } + + private void groupsSuccess(ArrayList groups) { + mGroupRealmRepository.update(groups, new GroupByIdSpecification(mUserIdReference.get())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + loadGroupsFromDB(); + } + + @Override + public void onError(Throwable e) { + Log.e(ContactsPresenter.class.getSimpleName(), e.getMessage(), e); + } + + @Override + public void onNext(Object o) { + // nop here + } + }); + } + + /** + * Load groups from local database. + */ + + @Override + public void loadGroupsFromDB() { + GroupSpecification groupSpecification = new GroupSpecification(mUserIdReference.get()); + mGroupRealmRepository.queryForAll(groupSpecification) + .map(ArrayList::new) + .compose(RxUtils.provideDefaultTransformer()) + .subscribe(this::loadGroupsFromDBSuccessful, this::loadGroupsError); + } + + /** + * Load groups by name. + */ + @Override + public void loadGroupsContainingName(String name) { + GroupByNameSpecification groupByNameSpecification = new GroupByNameSpecification(name, mUserIdReference.get()); + mGroupRealmRepository.queryForAll(groupByNameSpecification) + .map(ArrayList::new) + .compose(RxUtils.provideDefaultTransformer()) + .subscribe(this::loadGroupsFromDBSuccessful, this::loadGroupsError); + } + + private void loadGroupsFromDBSuccessful(ArrayList groups) { + if (!isAttached()) { + return; + } + getView().hideProgress(); + getView().setGroups(groups); + } + + @Override + public void storeGroupLocally(long groupId, Message message) { + Group group = new Group(); + group.id = groupId; + group.lastMessage = message; + mGroupRealmRepository.updateGroupMessage(group, new GroupByIdSpecification(mUserIdReference.get())) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + loadGroupsFromDB(); + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onNext(Object o) { + + } + }); + } + + private void loadGroupsError(Throwable throwable) { + Log.e(ContactsPresenter.class.getSimpleName(), throwable.getMessage(), throwable); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsView.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsView.java new file mode 100644 index 0000000..f044996 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/contacts/ContactsView.java @@ -0,0 +1,35 @@ +package com.p3.bewoplaner.page.contacts; + +import com.p3.bewoplaner.model.Group; +import com.p3.bewoplaner.model.Message; +import com.p3.bewoplaner.page.mvp.MVPView; + +import android.support.annotation.StringRes; + +import java.util.ArrayList; + +/** + * Created by imarneanu on 2/20/17. + */ +interface ContactsView extends MVPView { + + void setCallback(Callback setCallback); + + void showProgress(); + + void hideProgress(); + + void setGroups(ArrayList groups); + + void showError(@StringRes int message); + + interface Callback { + void storeGroupLocally(long groupId, Message message); + + void loadGroups(); + + void loadGroupsFromDB(); + + void loadGroupsContainingName(String name); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/impressum/ImpressumActivity.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/impressum/ImpressumActivity.java new file mode 100644 index 0000000..06fed75 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/impressum/ImpressumActivity.java @@ -0,0 +1,73 @@ +package com.p3.bewoplaner.page.impressum; + +import com.p3.bewoplaner.R; + +import android.annotation.TargetApi; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.webkit.WebResourceRequest; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +/** + * Created by laszloffiferenc on 2/23/17. + */ +public class ImpressumActivity extends AppCompatActivity { + + @BindView(R.id.impressum_web_view) + WebView mWebView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_web_view); + ButterKnife.bind(this); + mWebView.setWebViewClient(new Callback()); + mWebView.loadUrl("file:///android_asset/impressum.html"); + } + + @OnClick(R.id.impressum_toolbar_back_arrow) + void backButton() { + onBackPressed(); + } + + private class Callback extends WebViewClient { + @SuppressWarnings("deprecation") + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + final Uri uri = Uri.parse(url); + return handleUri(uri); + } + + @TargetApi(Build.VERSION_CODES.N) + @Override + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + final Uri uri = request.getUrl(); + return handleUri(uri); + } + + private boolean handleUri(final Uri uri) { + String url = uri.toString(); + if (url.startsWith("mailto:")) { + Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse(url)); + if (getPackageManager().queryIntentActivities(intent, 0).size() > 0) { + startActivity(intent); + } + } + if (url.startsWith("tel:")) { + Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url)); + if (getPackageManager().queryIntentActivities(intent, 0).size() > 0) { + startActivity(intent); + } + } + return true; + } + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginActivity.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginActivity.java new file mode 100644 index 0000000..2af370c --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginActivity.java @@ -0,0 +1,197 @@ +package com.p3.bewoplaner.page.login; + +import com.google.firebase.iid.FirebaseInstanceId; + +import com.p3.bewoplaner.BewoplanerApplication; +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.prefs.AppPrefs; +import com.p3.bewoplaner.di.InjectionHelper; +import com.p3.bewoplaner.page.main.MainActivity; +import com.p3.bewoplaner.utils.AppStoppedService; +import com.p3.bewoplaner.utils.DialogHandler; +import com.p3.bewoplaner.utils.ui.ViewUtils; +import com.securepreferences.SecurePreferences; + +import android.content.Intent; +import android.os.Bundle; +import android.provider.Settings; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.design.widget.Snackbar; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ProgressBar; + +import java.util.Map; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.OnEditorAction; + +/** + * Created by imarneanu on 2/10/17. + */ +public class LoginActivity extends AppCompatActivity + implements LoginView { + + @BindView(R.id.login_chat_code) + EditText mChatCodeEditText; + @BindView(R.id.login_account) + EditText mAccountEditText; + @BindView(R.id.login_username) + EditText mUsernameEditText; + @BindView(R.id.login_password) + EditText mPasswordEditText; + @BindView(R.id.login_button) + Button mLoginButton; + @BindView(R.id.login_progress_bar) + ProgressBar mProgressBar; + @BindView(R.id.login_save_password_checkBox) + CheckBox mSavePasswordCheckbox; + + @Inject + LoginPresenter mLoginPresenter; + + @Inject + @AppPrefs + SecurePreferences mSecuredPrefs; + + private Snackbar mSnackbar; + private Callback mCallback; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + InjectionHelper.getLoginComponent(this).inject(this); + ButterKnife.bind(this); + mLoginPresenter.attachView(this); + decryptPreference(); + ViewUtils.setProgressBarColor(mProgressBar, R.color.colorDefault); + mCallback.logout(Settings.Secure.getString(this.getContentResolver(), + Settings.Secure.ANDROID_ID), true, null); + } + + @OnEditorAction(R.id.login_password) + public boolean passwordDoneClicked(int actionId) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + ViewUtils.hideKeyboard(this); + } + return false; + } + + @OnClick(R.id.login_button) + void loginButtonClicked() { + mCallback.checkVoucher(mAccountEditText.getText().toString().trim(), + mChatCodeEditText.getText().toString().trim(), + mPasswordEditText.getText().toString(), + mSavePasswordCheckbox.isChecked()); + } + + @Override + public void voucherIsNotValid() { + DialogHandler.createNoTitleDialog(this, + R.string.login_voucher_code_is_not_valid, + false, + android.R.string.ok, + (dialog, which) -> dialog.dismiss()).show(); + } + + @Override + public void voucherIsValid() { + ((BewoplanerApplication) getApplication()).resetApplicationComponent(); + verifyIfIsChecked(); + } + + private void verifyIfIsChecked() { + mLoginPresenter.detachView(); + InjectionHelper.destroyLoginComponent(); + InjectionHelper.getLoginComponent(this).inject(this); + mLoginPresenter.attachView(this); + mCallback.tryToLogin(mUsernameEditText.getText().toString().trim(), mPasswordEditText.getText().toString()); + } + + @Override + public void loginSuccessful() { + mCallback.tryToRegisterDevice(Settings.Secure.getString(this.getContentResolver(), + Settings.Secure.ANDROID_ID), FirebaseInstanceId.getInstance().getToken()); + if (!mSavePasswordCheckbox.isChecked()) { + startService(new Intent(getBaseContext(), AppStoppedService.class)); + } + Intent intent = new Intent(this, MainActivity.class); + startActivity(intent); + finish(); + } + + private void decryptPreference() { + for (Map.Entry entry : mSecuredPrefs.getAll().entrySet()) { + final String key = entry.getKey(); + if (key == null) { + continue; + } else if (key.equals(SecurePreferences.hashPrefKey(AppPrefsConstants.CHAT_CODE))) { + mChatCodeEditText.setText(String.valueOf(mSecuredPrefs.getString(AppPrefsConstants.CHAT_CODE, null))); + } else if (key.equals(SecurePreferences.hashPrefKey(AppPrefsConstants.ACCOUNT_NO))) { + mAccountEditText.setText(String.valueOf(mSecuredPrefs.getString(AppPrefsConstants.ACCOUNT_NO, null))); + } + } + } + + @Override + public void setCallback(Callback callback) { + mCallback = callback; + } + + @Override + public void showLoading() { + mSavePasswordCheckbox.setEnabled(false); + mUsernameEditText.setEnabled(false); + mPasswordEditText.setEnabled(false); + mChatCodeEditText.setEnabled(false); + mAccountEditText.setEnabled(false); + mProgressBar.setVisibility(View.VISIBLE); + mLoginButton.setVisibility(View.GONE); + } + + @Override + public void showContent() { + mSavePasswordCheckbox.setEnabled(true); + mUsernameEditText.setEnabled(true); + mPasswordEditText.setEnabled(true); + mChatCodeEditText.setEnabled(true); + mAccountEditText.setEnabled(true); + mProgressBar.setVisibility(View.GONE); + mLoginButton.setVisibility(View.VISIBLE); + } + + @Override + public void showError(@StringRes int messageRes) { + showError(getString(messageRes)); + } + + @Override + public void showError(String message) { + if (mSnackbar == null) { + mSnackbar = Snackbar.make(findViewById(R.id.login_coordinator_layout), message, Snackbar.LENGTH_LONG) + .setAction("Retry", view -> verifyIfIsChecked()); + mSnackbar.setActionTextColor(ContextCompat.getColor(this, R.color.colorDefault)); + } + mSnackbar.setText(message); + mSnackbar.show(); + } + + @Override + protected void onDestroy() { + InjectionHelper.destroyLoginComponent(); + mLoginPresenter.detachView(); + super.onDestroy(); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginComponent.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginComponent.java new file mode 100644 index 0000000..d22652e --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginComponent.java @@ -0,0 +1,15 @@ +package com.p3.bewoplaner.page.login; + +import com.p3.bewoplaner.di.ApplicationComponent; +import com.p3.bewoplaner.di.scopes.ActivityScope; + +import dagger.Component; + +/** + * Created by laszloffiferenc on 2/21/17. + */ +@ActivityScope +@Component(dependencies = ApplicationComponent.class, modules = LoginModule.class) +public interface LoginComponent { + void inject(LoginActivity loginActivity); +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginModule.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginModule.java new file mode 100644 index 0000000..78548a4 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginModule.java @@ -0,0 +1,37 @@ +package com.p3.bewoplaner.page.login; + +import com.p3.bewoplaner.data.api.services.LoginService; +import com.p3.bewoplaner.data.api.services.RegisterService; +import com.p3.bewoplaner.data.api.services.VoucherService; +import com.p3.bewoplaner.di.P3DSEndpoint; +import com.p3.bewoplaner.di.VoucherEndpoint; +import com.p3.bewoplaner.di.scopes.ActivityScope; + +import dagger.Module; +import dagger.Provides; +import retrofit2.Retrofit; + +/** + * Created by laszloffiferenc on 2/21/17. + */ +@Module +public class LoginModule { + + @Provides + @ActivityScope + LoginService provideLoginService(@P3DSEndpoint Retrofit retrofit) { + return retrofit.create(LoginService.class); + } + + @Provides + @ActivityScope + RegisterService provideRegisterService(@P3DSEndpoint Retrofit retrofit) { + return retrofit.create(RegisterService.class); + } + + @Provides + @ActivityScope + VoucherService provideVoucherService(@VoucherEndpoint Retrofit retrofit) { + return retrofit.create(VoucherService.class); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginPresenter.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginPresenter.java new file mode 100644 index 0000000..1c8cf7b --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginPresenter.java @@ -0,0 +1,473 @@ +package com.p3.bewoplaner.page.login; + +import com.google.gson.JsonObject; + +import com.bumptech.glide.Glide; +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.api.common.ErrorHandlingResponseConverter; +import com.p3.bewoplaner.data.api.common.ErrorThrowable; +import com.p3.bewoplaner.data.api.common.ErrorType; +import com.p3.bewoplaner.data.api.common.ErrorUtils; +import com.p3.bewoplaner.data.api.responses.RegisterResponse; +import com.p3.bewoplaner.data.api.services.LoginService; +import com.p3.bewoplaner.data.api.services.RegisterService; +import com.p3.bewoplaner.data.api.services.VoucherService; +import com.p3.bewoplaner.data.prefs.AppPrefs; +import com.p3.bewoplaner.data.prefs.BooleanPreference; +import com.p3.bewoplaner.data.prefs.LongPreference; +import com.p3.bewoplaner.data.prefs.StringPreference; +import com.p3.bewoplaner.data.repository.GroupRealmRepository; +import com.p3.bewoplaner.data.repository.MessageRealmRepository; +import com.p3.bewoplaner.data.repository.RealmProvider; +import com.p3.bewoplaner.data.repository.UserRealmRepository; +import com.p3.bewoplaner.model.User; +import com.p3.bewoplaner.page.main.LogoutNavigation; +import com.p3.bewoplaner.page.main.MainNavigation; +import com.p3.bewoplaner.page.mvp.MVPAbstractPresenter; +import com.p3.bewoplaner.utils.SystemUtils; +import com.p3.bewoplaner.utils.rx.RxUtils; +import com.securepreferences.SecurePreferences; + +import android.app.NotificationManager; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.Log; +import android.widget.Toast; + +import java.io.File; +import java.net.SocketTimeoutException; + +import javax.inject.Inject; +import javax.inject.Named; + +import io.realm.Realm; +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +/** + * Created by laszloffiferenc on 2/21/17. + */ +public class LoginPresenter extends MVPAbstractPresenter + implements LoginView.Callback { + + private static final String TAG = "error"; + + @Inject + LoginService mLoginService; + + @Inject + RegisterService mRegisterService; + + @Inject + VoucherService mVoucherService; + + @Inject + @Named(AppPrefsConstants.API_ENDPOINT) + StringPreference mApiEndpointPreference; + + @Inject + @Named(AppPrefsConstants.LOGIN_TOKEN) + StringPreference mLoginTokenPreference; + + @Inject + @Named(AppPrefsConstants.CHAT_CODE) + StringPreference mChatCodePreference; + + @Inject + @Named(AppPrefsConstants.SAVE_PASSWORD) + BooleanPreference mSavePasswordPreference; + + @Inject + @Named(AppPrefsConstants.USER_ID) + LongPreference mUserIdReference; + + @Inject + @Named(AppPrefsConstants.ACCOUNT_NO) + StringPreference mAccountNumberPreference; + + @Inject + @Named(AppPrefsConstants.AVATAR) + StringPreference mAvatarPreference; + + @Inject + @Named(AppPrefsConstants.NAME) + StringPreference mProfileNamePreference; + + @Inject + @Named(AppPrefsConstants.PHONE_NUMBER) + StringPreference mPhoneNumberPreference; + + @Inject + @AppPrefs + SharedPreferences mSharedPreferences; + + @Inject + @AppPrefs + SecurePreferences mSecurePreferences; + + @Inject + SystemUtils mSystemUtils; + + @Inject + GroupRealmRepository mGroupRealmRepository; + + @Inject + MessageRealmRepository mMessageRealmRepository; + + @Inject + UserRealmRepository mUserRealmRepository; + + @Inject + RealmProvider mRealmProvider; + + @Inject + NotificationManager mNotificationManager; + + @Inject + Glide mGlide; + + @Inject + Context mContext; + + private String mLastVoucher; + + @Inject + LoginPresenter() { + + } + + @Override + protected void afterAttachView(@NonNull LoginView view) { + super.afterAttachView(view); + view.setCallback(this); + } + + @Override + protected void beforeDetachView(@NonNull LoginView view) { + view.setCallback(null); + super.beforeDetachView(view); + } + + /** + * Try to login with the given parameters + * @param username login username as {@code String} + * @param password login password as {@code String} + */ + private void loginCall(String username, String password) { + getView().showLoading(); + Subscription subscription = mLoginService.login(username, password) + .compose(RxUtils.provideDefaultTransformer()) + .flatMap(new ErrorHandlingResponseConverter<>()) + .map(loginResponse -> loginResponse.response.user) + .subscribe(this::loginSuccess, this::loginError); + addSubscription(subscription); + } + + /** + * Error to show in view if something went wrong during the login. + */ + + private void loginError(Throwable throwable) { + getView().showContent(); + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + return; + } + if (ErrorUtils.getErrorType(throwable) == ErrorType.DATA_INPUT) { + ErrorThrowable errorThrowable = (ErrorThrowable) throwable; + if (errorThrowable.getError().loginFailed != null && errorThrowable.getError().loginFailed.size() > 0) { + getView().showError(errorThrowable.getError().loginFailed.get(0)); + return; + } + } + getView().showError(R.string.error_something_went_wrong); + } + + /** + * Set user data in preferences if login is successful. + * @param user {@code User} the current logged in user. + */ + + private void loginSuccess(User user) { + mLoginTokenPreference.set(user.token); + mUserIdReference.set(user.id); + mAvatarPreference.set(user.avatar); + mPhoneNumberPreference.set(user.mobile); + mProfileNamePreference.set(user.firstName.concat(" ").concat(user.lastName)); + getView().loginSuccessful(); + } + + /** + *Checks the {@code String} username and {@code String} password + * if are corresponding to criteria. + */ + + @Override + public void tryToLogin(String username, String password) { + if (!checkPassword(password)) { + return; + } + if (!checkUsername(username)) { + return; + } + loginCall(username, password); + } + + /** + * Register the current device. Used if the login is successful. + */ + + @Override + public void tryToRegisterDevice(String deviceId, String fcmToken) { + Subscription subscription = mRegisterService.registerDevice(deviceId, fcmToken) + .compose(RxUtils.provideDefaultTransformer()) + .flatMap(new ErrorHandlingResponseConverter<>()) + .map(registerResponse -> registerResponse.response) + .subscribe(this::registerSuccessful, this::registerError); + addSubscription(subscription); + } + + /** + * Checks if the voucher necessary for login is correct. + */ + + @Override + public void checkVoucher(String customerId, String voucher, String password, boolean isChecked) { + mLastVoucher = voucher; + mAccountNumberPreference.set(customerId); + mSavePasswordPreference.set(isChecked); + if (!checkPassword(password)) { + return; + } + if (TextUtils.isEmpty(voucher)) { + getView().showError(R.string.error_voucher_code_must_not_be_null); + return; + } + if (TextUtils.isEmpty(customerId)) { + getView().showError(R.string.error_customer_id_must_not_be_null); + return; + } + getView().showLoading(); + Subscription subscription = mVoucherService.checkVoucher(customerId, voucher) + .flatMap(new Func1>() { + @Override + public Observable call(JsonObject response) { + if (response.has(voucher) && response.get(voucher) != null) { + JsonObject voucherObject = response.getAsJsonObject(voucher); + if (voucherObject.has("CustomerID") + && !TextUtils.isEmpty(voucherObject.get("CustomerID").getAsString()) + && voucherObject.has("ServerName") + && !TextUtils.isEmpty(voucherObject.get("ServerName").getAsString())) { + String serverName = voucherObject.get("ServerName").getAsString(); + if (!serverName.startsWith("https")) { + serverName = "https://" + serverName + "/"; + } + mApiEndpointPreference.set(serverName); + return Observable.just(true); + } + } + return Observable.just(false); + } + }) + .compose(RxUtils.provideDefaultTransformer()) + .subscribe(this::checkVoucherSuccessful, this::checkVoucherError); + addSubscription(subscription); + } + + private void checkVoucherError(Throwable throwable) { + getView().showContent(); + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + return; + } + getView().showError(R.string.error_something_went_wrong); + } + + private void checkVoucherSuccessful(Boolean vouchIsValid) { + getView().showContent(); + if (vouchIsValid) { + mChatCodePreference.set(mLastVoucher); + getView().voucherIsValid(); + return; + } + deleteData(true); + getView().voucherIsNotValid(); + } + + /** + * Logout from application deletes all login preferences and redirects to login screen. + */ + + @Override + public void logout(String deviceId, boolean clearAllPrefs, MainNavigation mainNavigation) { + mNotificationManager.cancelAll(); + if (!mLoginTokenPreference.isSet()) { + if (mainNavigation != null) { + mainNavigation.goToLoginActivity(); + } + return; + } + mRegisterService.unregisterDevice(deviceId) + .compose(RxUtils.provideDefaultTransformer()) + .flatMap(new ErrorHandlingResponseConverter<>()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + //after device unregister we remove also the token + mLoginTokenPreference.delete(); + deleteData(clearAllPrefs); + if (mainNavigation != null) { + mainNavigation.goToLoginActivity(); + } + } + + @Override + public void onError(Throwable e) { + Log.d(TAG, "onError: " + e); + //after device unregister we remove also the token + mLoginTokenPreference.delete(); + deleteData(clearAllPrefs); + if (mainNavigation != null) { + mainNavigation.goToLoginActivity(); + } + } + + @Override + public void onNext(RegisterResponse registerResponse) { + Log.d(TAG, "onNext: " + registerResponse); + } + }); + } + + private void registerError(Throwable throwable) { + if (ErrorUtils.getErrorType(throwable) == ErrorType.DATA_INPUT) { + ErrorThrowable errorThrowable = (ErrorThrowable) throwable; + if (errorThrowable.getError().fcmToken != null && errorThrowable.getError().fcmToken.size() > 0) { + Log.d(TAG, "fcmToken error: " + errorThrowable.getError().fcmToken.get(0)); + return; + } + if (errorThrowable.getError().deviceId != null && errorThrowable.getError().deviceId.size() > 0) { + Log.d(TAG, "fcmToken error: " + errorThrowable.getError().deviceId.get(0)); + } + } + } + + @Override + public void tryToLogout(String deviceId, MainNavigation mainNavigation) { + if (mSavePasswordPreference.get()) { + return; + } + logout(deviceId, false, mainNavigation); + } + + @Override + public void checkIfVoucherStillAvailable(LogoutNavigation logoutNavigation) { + if (mAccountNumberPreference.get() == null) { + return; + } + String voucher = mChatCodePreference.get(); + String customerId = mAccountNumberPreference.get(); + Subscription subscription = mVoucherService.checkVoucher(customerId, voucher) + .compose(RxUtils.provideDefaultTransformer()) + .subscribe(new Subscriber() { + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + if (mSystemUtils.isNetworkUnavailable() || e instanceof SocketTimeoutException) { + return; + } + Toast.makeText(mContext, "Please make sure you have internet connection", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onNext(JsonObject response) { + if (!response.has(voucher) || response.get(voucher) == null) { + logoutNavigation.logoutDirectly(); + return; + } + JsonObject voucherObject = response.getAsJsonObject(voucher); + if (!voucherObject.has("CustomerID") + || TextUtils.isEmpty(voucherObject.get("CustomerID").getAsString()) + || !voucherObject.has("ServerName") + || TextUtils.isEmpty(voucherObject.get("ServerName").getAsString())) { + logoutNavigation.logoutDirectly(); + } + } + }); + addSubscription(subscription); + } + + /** + * Clears all login preferences. + */ + + private void deleteData(boolean clearAllPrefs) { + if (clearAllPrefs) { + Observable.create(subscriber -> mGlide.clearDiskCache()) + .subscribeOn(Schedulers.io()) + .subscribe(); + mGlide.clearMemory(); + mSharedPreferences.edit().clear().apply(); + mAccountNumberPreference.deleteFromSecurePreference(); + mChatCodePreference.deleteFromSecurePreference(); + mSavePasswordPreference.deleteFromSecurePreference(); + File dir = mContext.getExternalFilesDir(null); + if (dir != null) { + if (dir.isDirectory()) { + String[] children = dir.list(); + for (String aChildren : children) { + //noinspection ResultOfMethodCallIgnored + new File(dir, aChildren).delete(); + } + } else { + Log.e("deleteData", dir.getAbsolutePath() + " is not a directory!"); + } + } else { + dir = new File(Environment.getExternalStorageDirectory(), "BeWoPlaner"); + if (dir.isDirectory()) { + String[] children = dir.list(); + for (String aChildren : children) { + //noinspection ResultOfMethodCallIgnored + new File(dir, aChildren).delete(); + } + } else { + Log.e("deleteData", dir.getAbsolutePath() + " is not a directory!"); + } + } + Realm realm = mRealmProvider.provide(); + realm.executeTransaction(realm1 -> realm1.deleteAll()); + } else { + mApiEndpointPreference.delete(); + } + } + + private void registerSuccessful(String s) { + } + + private boolean checkPassword(String password) { + if (password.length() >= 8) { + return true; + } + getView().showError(R.string.error_password_too_short); + return false; + } + + private boolean checkUsername(String username) { + if (username.length() >= 4) { + return true; + } + getView().showError(R.string.error_username_too_short); + return false; + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginView.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginView.java new file mode 100644 index 0000000..7c60d4f --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/login/LoginView.java @@ -0,0 +1,33 @@ +package com.p3.bewoplaner.page.login; + +import com.p3.bewoplaner.page.main.LogoutNavigation; +import com.p3.bewoplaner.page.main.MainNavigation; +import com.p3.bewoplaner.page.mvp.MVPLceView; + +/** + * Created by laszloffiferenc on 2/21/17. + */ +interface LoginView extends MVPLceView { + void loginSuccessful(); + + void setCallback(Callback callback); + + void voucherIsValid(); + + void voucherIsNotValid(); + + interface Callback { + + void checkVoucher(String customerId, String voucher, String password, boolean isChecked); + + void tryToLogin(String email, String password); + + void tryToRegisterDevice(String deviceId, String fcmToken); + + void logout(String deviceId, boolean clearAllPrefs, MainNavigation mainNavigation); + + void tryToLogout(String deviceId, MainNavigation mainNavigation); + + void checkIfVoucherStillAvailable(LogoutNavigation logoutNavigation); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/LogoutNavigation.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/LogoutNavigation.java new file mode 100644 index 0000000..6a09905 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/LogoutNavigation.java @@ -0,0 +1,7 @@ +package com.p3.bewoplaner.page.main; + +public interface LogoutNavigation { + + void logoutDirectly(); + +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/MainActivity.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/MainActivity.java new file mode 100644 index 0000000..25dc5aa --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/MainActivity.java @@ -0,0 +1,279 @@ +package com.p3.bewoplaner.page.main; + +import com.bumptech.glide.Glide; +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.events.InvalidTokenEvent; +import com.p3.bewoplaner.data.events.LogoutDirectlyEvent; +import com.p3.bewoplaner.data.prefs.BooleanPreference; +import com.p3.bewoplaner.data.prefs.StringPreference; +import com.p3.bewoplaner.di.InjectionHelper; +import com.p3.bewoplaner.page.chat.GroupChatActivity; +import com.p3.bewoplaner.page.chat.SingleChatActivity; +import com.p3.bewoplaner.page.contacts.ContactsFragment; +import com.p3.bewoplaner.page.impressum.ImpressumActivity; +import com.p3.bewoplaner.page.login.LoginActivity; +import com.p3.bewoplaner.page.login.LoginPresenter; +import com.p3.bewoplaner.page.profile.ProfileFragment; +import com.p3.bewoplaner.utils.Constants; +import com.p3.bewoplaner.utils.DialogHandler; +import com.p3.bewoplaner.utils.SystemUtils; +import com.p3.bewoplaner.utils.Utils; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.NavigationView; +import android.support.v4.app.Fragment; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.Toast; + +import javax.inject.Inject; +import javax.inject.Named; + +import butterknife.BindView; +import butterknife.ButterKnife; +import de.hdodenhof.circleimageview.CircleImageView; + +/** + * Created by imarneanu on 2/9/17. + */ +public class MainActivity extends AppCompatActivity + implements MainNavigation, LogoutNavigation { + + private static final String REDIRECT_TO_CHAT = "redirect_to_chat"; + private static final String BUNDLE = "bundle"; + private static final String LOGOUT = "logout"; + + @BindView(R.id.toolbar) + Toolbar mToolbar; + @BindView(R.id.drawer_layout) + DrawerLayout mDrawer; + @BindView(R.id.nav_view) + NavigationView mNavigationView; + @BindView(R.id.nav_listView) + ListView mNavigationListView; + + @Inject + LoginPresenter mLoginPresenter; + + @Inject + @Named(AppPrefsConstants.AVATAR) + StringPreference mAvatarPreference; + + @Inject + @Named(AppPrefsConstants.LOGIN_TOKEN) + StringPreference mLoginTokenPreference; + + @Inject + @Named(AppPrefsConstants.CHAT_OPENED) + BooleanPreference mChatOpenedPref; + + @Inject + SystemUtils mSystemUtils; + + private boolean mLogoutClicked; + + public static Intent createIntent(Context context, Bundle bundle, int numberOfUsers, int groupId) { + Intent intent = new Intent(context, MainActivity.class); + intent.putExtra(REDIRECT_TO_CHAT, true); + intent.putExtra(BUNDLE, bundle); + intent.putExtra(Constants.GROUP_ID, groupId); + intent.putExtra(Constants.GROUP_NR_USERS, numberOfUsers); + intent.putExtra(LOGOUT, false); + return intent; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + ButterKnife.bind(this); + InjectionHelper.getMainComponent(this).inject(this); + EventBus.getDefault().register(this); + setSupportActionBar(mToolbar); + ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( + this, mDrawer, mToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) { + @Override + public void onDrawerOpened(View drawerView) { + super.onDrawerOpened(drawerView); + Utils.hideKeyboard(MainActivity.this); + } + }; + mDrawer.closeDrawer(GravityCompat.START); + mDrawer.addDrawerListener(toggle); + View headerLayout = mNavigationView.getHeaderView(0); + CircleImageView avatar = (CircleImageView) headerLayout.findViewById(R.id.menu_profile_imageView); + Glide.with(this).load(mAvatarPreference.get()).into(avatar); + mDrawer.setOnTouchListener((view, motionEvent) -> { + Glide.with(view.getContext()).load(mAvatarPreference.get()).into(avatar); + return false; + }); + toggle.syncState(); + ArrayAdapter adapter = new NavigationMenuAdapter(this, + getResources().getStringArray(R.array.nav_menu), this); + mNavigationListView.setAdapter(adapter); + switchFragment(new ContactsFragment()); + onNewIntent(getIntent()); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (intent.hasExtra(REDIRECT_TO_CHAT) && !mChatOpenedPref.get()) { + Bundle message = intent.getBundleExtra(BUNDLE); + Intent startIntent; + if (intent.getIntExtra(Constants.GROUP_NR_USERS, 2) > 2) { + startIntent = GroupChatActivity.createIntent(this, message); + } else { + startIntent = SingleChatActivity.createIntent(this, message); + } + mChatOpenedPref.set(true); + startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); + startActivity(startIntent); + intent.removeExtra(REDIRECT_TO_CHAT); + setIntent(intent); + } + } + + @Override + protected void onResume() { + super.onResume(); + mLoginPresenter.checkIfVoucherStillAvailable(this); + } + + private void switchFragment(@NonNull Fragment fragment) { + switchFragment(fragment, true); + } + + private void switchFragment(@NonNull Fragment fragment, boolean clearBackStack) { + Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.content_main); + if (fragment.getClass().isInstance(currentFragment)) { + return; + } + if (clearBackStack) { + Utils.clearFullBackStack(this); + } + addFragmentToBackStack(fragment); + } + + private void addFragmentToBackStack(@NonNull Fragment fragment) { + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.content_main, fragment, fragment.getTag()) + .addToBackStack(fragment.getClass().getSimpleName()) + .commitAllowingStateLoss(); + } + + @Override + public void onBackPressed() { + if (mDrawer.isDrawerOpen(GravityCompat.START)) { + mDrawer.closeDrawer(GravityCompat.START); + } + if (getSupportFragmentManager().getBackStackEntryCount() <= 1) { + finish(); + } + mLoginPresenter.checkIfVoucherStillAvailable(this); + super.onBackPressed(); + } + + @Override + public void goToContacts() { + mLoginPresenter.checkIfVoucherStillAvailable(this); + mDrawer.closeDrawer(GravityCompat.START); + switchFragment(new ContactsFragment()); + } + + @Override + public void goToProfile() { + mLoginPresenter.checkIfVoucherStillAvailable(this); + mDrawer.closeDrawer(GravityCompat.START); + switchFragment(new ProfileFragment(), false); + } + + @Override + public void logout() { + Dialog alertDialog = DialogHandler.createNoTitleDialog(this, + R.string.logout_confirmation, + false, + android.R.string.yes, + android.R.string.no, + (dialog, which) -> { + mLogoutClicked = true; + mDrawer.closeDrawer(GravityCompat.START); + mLoginPresenter.logout(Settings.Secure.getString(MainActivity.this.getContentResolver(), + Settings.Secure.ANDROID_ID), false, this); + }, + (dialog, which) -> dialog.dismiss()); + if (!isFinishing()) { + alertDialog.show(); + } + } + + @Override + public void goToImpressum() { + mDrawer.closeDrawer(GravityCompat.START); + Intent intent = new Intent(this, ImpressumActivity.class); + startActivity(intent); + } + + @Override + public void logoutDirectly() { + mLoginPresenter.logout(Settings.Secure.getString(MainActivity.this.getContentResolver(), + Settings.Secure.ANDROID_ID), true, this); + } + + @Override + public void goToLoginActivity() { + if (isFinishing()) { + return; + } + startActivity(new Intent(MainActivity.this, LoginActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)); + finish(); + } + + @SuppressWarnings("unused") + @Subscribe(threadMode = ThreadMode.MAIN) + public void onInvalidToken(InvalidTokenEvent invalidTokenEvent) { + Toast.makeText(this, R.string.login_again, Toast.LENGTH_LONG).show(); + logoutDirectly(); + } + + @SuppressWarnings("unused") + @Subscribe(threadMode = ThreadMode.MAIN) + public void onLogoutDirectlyEvent(LogoutDirectlyEvent logoutDirectlyEvent) { + logoutDirectly(); + } + + @Override + protected void onDestroy() { + EventBus.getDefault().unregister(this); + mChatOpenedPref.set(false); + if (!mLogoutClicked && getIntent().getBooleanExtra(LOGOUT, true)) { + mLoginPresenter.tryToLogout(Settings.Secure.getString(this.getContentResolver(), + Settings.Secure.ANDROID_ID), this); + } + getIntent().removeExtra(LOGOUT); + InjectionHelper.destroyMainComponents(); + super.onDestroy(); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/MainComponent.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/MainComponent.java new file mode 100644 index 0000000..ac894c9 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/MainComponent.java @@ -0,0 +1,21 @@ +package com.p3.bewoplaner.page.main; + +import com.p3.bewoplaner.di.ApplicationComponent; +import com.p3.bewoplaner.di.scopes.ActivityScope; +import com.p3.bewoplaner.page.chat.ChatModule; +import com.p3.bewoplaner.page.login.LoginModule; +import com.p3.bewoplaner.utils.AppStoppedService; + +import dagger.Component; + +/** + * Created by laszloffiferenc on 2/22/17. + */ +@ActivityScope +@Component(dependencies = ApplicationComponent.class, modules = {LoginModule.class, ChatModule.class}) +public interface MainComponent { + void inject(MainActivity mainActivity); + + void inject(AppStoppedService appStoppedService); + +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/MainNavigation.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/MainNavigation.java new file mode 100644 index 0000000..7562a95 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/MainNavigation.java @@ -0,0 +1,18 @@ +package com.p3.bewoplaner.page.main; + +/** + * Created by imarneanu on 2/10/17. + */ +public interface MainNavigation { + + void goToContacts(); + + void goToProfile(); + + void logout(); + + void goToImpressum(); + + void goToLoginActivity(); + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/NavigationMenuAdapter.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/NavigationMenuAdapter.java new file mode 100644 index 0000000..9bce47a --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/main/NavigationMenuAdapter.java @@ -0,0 +1,81 @@ +package com.p3.bewoplaner.page.main; + +import com.p3.bewoplaner.R; + +import android.app.Activity; +import android.content.Context; +import android.support.v4.content.ContextCompat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +/** + * Created by imarneanu on 2/13/17. + */ +class NavigationMenuAdapter extends ArrayAdapter { + private final static int IMPRESSUM = 3; + + private Context mContext; + private String[] mData; + private static MainNavigation mListener; + + NavigationMenuAdapter(Context context, String[] data, MainNavigation listener) { + super(context, R.layout.item_nav_list, data); + mContext = context; + mData = data; + mListener = listener; + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + ViewHolder holder; + if (view != null) { + holder = (ViewHolder) view.getTag(); + } else { + LayoutInflater inflater = ((Activity) mContext).getLayoutInflater(); + view = inflater.inflate(R.layout.item_nav_list, parent, false); + holder = new ViewHolder(view); + view.setTag(holder); + } + holder.action.setText(mData[position]); + holder.action.setTextSize(position < IMPRESSUM ? 21.0f : 12.0f); + holder.action.setTextColor(ContextCompat.getColor(mContext, (position < IMPRESSUM ? + R.color.colorWhite : R.color.colorImpressum))); + holder.position = position; + return view; + } + + static class ViewHolder { + @BindView(R.id.nav_action) + TextView action; + int position; + + ViewHolder(View view) { + ButterKnife.bind(this, view); + } + + @OnClick(R.id.nav_action) + void actionClicked() { + switch (position) { + case 0: + mListener.goToContacts(); + break; + case 1: + mListener.goToProfile(); + break; + case 2: + mListener.logout(); + break; + case 3: + mListener.goToImpressum(); + break; + } + } + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/mvp/MVPAbstractPresenter.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/mvp/MVPAbstractPresenter.java new file mode 100644 index 0000000..c724015 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/mvp/MVPAbstractPresenter.java @@ -0,0 +1,93 @@ +package com.p3.bewoplaner.page.mvp; + +import android.support.annotation.CallSuper; +import android.support.annotation.NonNull; + +import rx.Subscription; +import rx.subscriptions.CompositeSubscription; + +import static com.p3.bewoplaner.utils.Preconditions.checkNotNull; +import static com.p3.bewoplaner.utils.Preconditions.checkNull; +import static com.p3.bewoplaner.utils.Preconditions.checkState; + +/** + * Created by imarneanu on 2/9/17. + */ +public abstract class MVPAbstractPresenter + implements MVPPresenter { + + private static final int METHOD_AFTER_ATTACH = 1; + private static final int METHOD_BEFORE_DETACH = 2; + + private V mView; + private int mCalledThroughSuper = 0; + private CompositeSubscription mCompositeSubscription = new CompositeSubscription(); + + protected void addSubscription(Subscription subscription) { + mCompositeSubscription.add(subscription); + } + + @Override + public final void attachView(@NonNull V view) { + checkNull(mView, "view is already attached"); + mView = view; + afterAttachView(view); + checkState((mCalledThroughSuper & METHOD_AFTER_ATTACH) == METHOD_AFTER_ATTACH, + "super.afterAttachView() not called"); + } + + @Override + public final void detachView() { + checkNotNull(mCompositeSubscription, "subscriptionList is NULL"); + mCompositeSubscription.clear(); + checkNotNull(mView, "view is not attached"); + beforeDetachView(mView); + checkState((mCalledThroughSuper & METHOD_BEFORE_DETACH) == METHOD_BEFORE_DETACH, + "super.beforeDetachView() not called"); + mView = null; + mCalledThroughSuper = 0; + } + + @Override + public boolean isAttached() { + return mView != null; + } + + /** + * Returns the view of this presenter, once it has been attached + * + * @return the view + */ + @NonNull + protected V getView() { + checkNotNull(mView, "view is not attached"); + return mView; + } + + /** + * Override this to interact with the view after {@link #attachView(MVPView)} is called. + *

+ * Ensure that you call through super, usually as the last + * call in your implementation. + * + * @param view The view + */ + @CallSuper + protected void afterAttachView(@NonNull V view) { + mCalledThroughSuper |= METHOD_AFTER_ATTACH; + } + + /** + * Override this to interact with the view before {@link #detachView()} is called. + *

+ * Ensure that you call through super, usually as the first + * call in your implementation. + * + * @param view The view + */ + @CallSuper + protected void beforeDetachView(@NonNull V view) { + mCalledThroughSuper |= METHOD_BEFORE_DETACH; + } + +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/mvp/MVPLceView.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/mvp/MVPLceView.java new file mode 100644 index 0000000..c4fc056 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/mvp/MVPLceView.java @@ -0,0 +1,34 @@ +package com.p3.bewoplaner.page.mvp; + +import android.support.annotation.StringRes; + +/** + * Created by imarneanu on 2/9/17. + */ +public interface MVPLceView extends MVPView { + + /** + * Display a loading indicator. + */ + void showLoading(); + + /** + * Show the content view. + */ + void showContent(); + + /** + * Show the error view. + * + * @param messageRes The error string resource to be displayed + */ + void showError(@StringRes int messageRes); + + /** + * Show the error view. + * + * @param message The error string to be displayed + */ + void showError(String message); + +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/mvp/MVPPresenter.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/mvp/MVPPresenter.java new file mode 100644 index 0000000..465bd4c --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/mvp/MVPPresenter.java @@ -0,0 +1,25 @@ +package com.p3.bewoplaner.page.mvp; + +import android.support.annotation.NonNull; + +/** + * Created by imarneanu on 2/9/17. + */ +interface MVPPresenter { + /** + * Attach the view to this presenter + */ + void attachView(@NonNull final T view); + + /** + * Detach the view from this presenter + */ + void detachView(); + + /** + * Returns true if the view is attached, false otherwise + * + * @return True or false + */ + boolean isAttached(); +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/mvp/MVPView.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/mvp/MVPView.java new file mode 100644 index 0000000..503b1f6 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/mvp/MVPView.java @@ -0,0 +1,7 @@ +package com.p3.bewoplaner.page.mvp; + +/** + * Created by imarneanu on 2/9/17. + */ +public interface MVPView { +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfileComponent.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfileComponent.java new file mode 100644 index 0000000..1d69a9a --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfileComponent.java @@ -0,0 +1,16 @@ +package com.p3.bewoplaner.page.profile; + +/** + * Created by imarneanu on 2/22/17. + */ + +import com.p3.bewoplaner.di.ApplicationComponent; +import com.p3.bewoplaner.di.scopes.ActivityScope; + +import dagger.Component; + +@ActivityScope +@Component(dependencies = ApplicationComponent.class, modules = ProfileModule.class) +public interface ProfileComponent { + void inject(ProfileFragment profileFragment); +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfileFragment.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfileFragment.java new file mode 100644 index 0000000..5ac866a --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfileFragment.java @@ -0,0 +1,239 @@ +package com.p3.bewoplaner.page.profile; + +import com.bumptech.glide.Glide; +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.prefs.StringPreference; +import com.p3.bewoplaner.di.InjectionHelper; +import com.p3.bewoplaner.model.User; +import com.p3.bewoplaner.utils.DialogHandler; +import com.p3.bewoplaner.utils.FileUtils; +import com.p3.bewoplaner.utils.RuntimePermissionBaseFragment; + +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.File; + +import javax.inject.Inject; +import javax.inject.Named; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.OnTextChanged; +import de.hdodenhof.circleimageview.CircleImageView; + +/** + * Created by imarneanu on 2/13/17. + */ +public class ProfileFragment extends RuntimePermissionBaseFragment implements ProfileView { + + private final static int SELECT_FILE = 5055; + + @BindView(R.id.profile_avatar) + CircleImageView mAvatarImageview; + + @BindView(R.id.profile_name) + TextView mNameTextView; + + @BindView(R.id.profile_phone_number) + EditText mPhoneNumberEditText; + + @BindView(R.id.profile_change_button) + Button mProfileChangeButton; + + @BindView(R.id.profile_update_button) + Button mUpdateProfileButton; + + @Inject + @Named(AppPrefsConstants.NAME) + StringPreference mProfileNamePreference; + + @Inject + @Named(AppPrefsConstants.AVATAR) + StringPreference mAvatarPreference; + + @Inject + @Named(AppPrefsConstants.PHONE_NUMBER) + StringPreference mPhoneNumberPreference; + + @Inject + ProfilePresenter mProfilePresenter; + + private Callback mCallback; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + InjectionHelper.getProfileComponent(context).inject(this); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_profile, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + ButterKnife.bind(this, view); + getActivity().setTitle(R.string.title_profile); + Glide.with(getActivity()) + .load(mAvatarPreference.get()) + .into(mAvatarImageview); + + if (!TextUtils.isEmpty(mPhoneNumberPreference.get())) { + mPhoneNumberEditText.setText(mPhoneNumberPreference.get()); + } + if (!TextUtils.isEmpty(mProfileNamePreference.get())) { + mNameTextView.setText(mProfileNamePreference.get()); + } + mProfilePresenter.attachView(this); + mCallback.getProfile(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode != Activity.RESULT_OK) { + return; + } + if (null == data) { + return; + } + if (requestCode == SELECT_FILE) { + if (null == data.getData()) { + return; + } + final Uri uri = data.getData(); + if (Build.VERSION.SDK_INT >= 19) { + final int takeFlags = data.getFlags() + & (Intent.FLAG_GRANT_READ_URI_PERMISSION | + Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + // Check for the freshest data. + try { + getActivity().getContentResolver().takePersistableUriPermission(uri, takeFlags); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (!mProfilePresenter.isAttached()) { + mProfilePresenter.attachView(this); + } + mCallback.uploadImage(uri); + } + } + + @Override + public File getFile(Uri uri) { + return FileUtils.getFile(getActivity(), uri); + } + + @Override + public void setCallback(Callback callback) { + mCallback = callback; + } + + @Override + public void setProfile(User user) { + if (!TextUtils.isEmpty(user.avatar)) { + Glide.with(getActivity()).load(user.avatar).into(mAvatarImageview); + } + mProfileChangeButton.setEnabled(true); + mNameTextView.setText(user.firstName.concat(" ").concat(user.lastName)); + mPhoneNumberEditText.setText(user.mobile); + mAvatarPreference.set(user.avatar); + mProfileNamePreference.set(user.firstName.concat(" ").concat(user.lastName)); + mPhoneNumberPreference.set(user.mobile); + } + + @Override + public void showError(@StringRes int message) { + Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show(); + } + + @Override + public void refreshImage(String imageUrl) { + Glide.with(getContext()) + .load(imageUrl) + .error(R.drawable.avatar) + .into(mAvatarImageview); + mAvatarPreference.set(imageUrl); + } + + @OnClick(R.id.profile_change_button) + public void onChangeAvatar() { + requestPermission(Manifest.permission.READ_EXTERNAL_STORAGE, MY_STORAGE_PERMISSION); + } + + public void choosePicture() { + selectFile(); + } + + @OnClick(R.id.profile_update_button) + public void onProfileUpdate() { + mProfileChangeButton.setEnabled(false); + mCallback.updateProfile(mPhoneNumberEditText.getText().toString()); + } + + @Override + public void onPermissionGranted(int permissionCode) { + if (MY_STORAGE_PERMISSION == permissionCode) { + choosePicture(); + } + } + + @OnTextChanged(value = R.id.profile_phone_number, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + void phoneNumberChanged() { + mUpdateProfileButton.setEnabled(!TextUtils.isEmpty(mPhoneNumberEditText.getText().toString()) + && mPhoneNumberEditText.getText().toString().trim().length() > 0); + } + + @Override + public void onPermissionDenied(int permissionCode) { + if (MY_STORAGE_PERMISSION == permissionCode) { + DialogHandler.showSimpleDialog(getActivity(), + R.string.error, + R.string.change_avatar_permisssion, + android.R.string.ok, + null).show(); + } + } + + private void selectFile() { + Intent intent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + } else { + intent = new Intent(Intent.ACTION_GET_CONTENT); + } + intent.setType("image/*"); + intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + startActivityForResult(Intent.createChooser(intent, getResources().getString(R.string.chat_select_file)), SELECT_FILE); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + mProfilePresenter.detachView(); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfileModule.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfileModule.java new file mode 100644 index 0000000..8b77487 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfileModule.java @@ -0,0 +1,23 @@ +package com.p3.bewoplaner.page.profile; + +import com.p3.bewoplaner.data.api.services.ProfileService; +import com.p3.bewoplaner.di.P3DSEndpoint; +import com.p3.bewoplaner.di.scopes.ActivityScope; + +import dagger.Module; +import dagger.Provides; +import retrofit2.Retrofit; + +/** + * Created by imarneanu on 2/23/17. + */ +@Module +class ProfileModule { + + @Provides + @ActivityScope + ProfileService provideProfileService(@P3DSEndpoint Retrofit retrofit) { + return retrofit.create(ProfileService.class); + } +} + diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfilePresenter.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfilePresenter.java new file mode 100644 index 0000000..771dd5e --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfilePresenter.java @@ -0,0 +1,131 @@ +package com.p3.bewoplaner.page.profile; + +import com.p3.bewoplaner.R; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.api.common.ErrorHandlingResponseConverter; +import com.p3.bewoplaner.data.api.services.ProfileService; +import com.p3.bewoplaner.data.prefs.StringPreference; +import com.p3.bewoplaner.di.scopes.ActivityScope; +import com.p3.bewoplaner.model.User; +import com.p3.bewoplaner.page.mvp.MVPAbstractPresenter; +import com.p3.bewoplaner.utils.SystemUtils; +import com.p3.bewoplaner.utils.rx.RxUtils; + +import android.net.Uri; +import android.support.annotation.NonNull; + +import java.io.File; + +import javax.inject.Inject; +import javax.inject.Named; + +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import rx.Subscription; + +/** + * Created by imarneanu on 2/22/17. + */ +@ActivityScope +class ProfilePresenter extends MVPAbstractPresenter implements ProfileView.Callback { + + @Inject + SystemUtils mSystemUtils; + + @Inject + ProfileService mProfileService; + + @Inject + @SuppressWarnings("WeakerAccess") + @Named(AppPrefsConstants.LOGIN_TOKEN) + StringPreference mToken; + + @Inject + ProfilePresenter() { + } + + @Override + protected void afterAttachView(@NonNull ProfileView view) { + super.afterAttachView(view); + view.setCallback(this); + } + + @Override + protected void beforeDetachView(@NonNull ProfileView view) { + view.setCallback(null); + super.beforeDetachView(view); + } + + /** + * Get logged in user profile. + */ + + @Override + public void getProfile() { + Subscription subscription = mProfileService.getProfile() + .compose(RxUtils.provideDefaultTransformer()) + .flatMap(new ErrorHandlingResponseConverter<>()) + .map(profileResponse -> profileResponse.response.user) + .subscribe(this::getProfileSuccess, this::profileError); + addSubscription(subscription); + } + + /** + * Update phoneNumber for the logged in user. + */ + + @Override + public void updateProfile(String phoneNumber) { + Subscription subscription = mProfileService.updateProfile(phoneNumber) + .compose(RxUtils.provideDefaultTransformer()) + .flatMap(new ErrorHandlingResponseConverter<>()) + .map(profileResponse -> profileResponse.response.user) + .subscribe(this::profileSuccess, this::profileError); + addSubscription(subscription); + } + + /** + * Upload image for the current user. + */ + + @Override + public void uploadImage(Uri uri) { + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + return; + } + + File file = getView().getFile(uri); + RequestBody requestBody = RequestBody.create(MediaType.parse("image/*"), file); + MultipartBody.Part part = MultipartBody.Part.createFormData("picture", file.getName(), requestBody); + final Subscription subscription = mProfileService.uploadProfileImage(part) + .compose(RxUtils.provideDefaultTransformer()) + .map(uploadImageResponse -> uploadImageResponse.response.picture) + .subscribe(this::uploadImageSuccess, this::profileError); + addSubscription(subscription); + } + + private void profileError(Throwable throwable) { + if (mSystemUtils.isNetworkUnavailable()) { + getView().showError(R.string.error_no_internet_connection); + return; + } + getView().showError(R.string.error_something_went_wrong); + } + + private void getProfileSuccess(User user) { + getView().setProfile(user); + } + + private void profileSuccess(User user) { + getView().setProfile(user); + getView().showError(R.string.profile_updated); + } + + private void uploadImageSuccess(String imageResponse) { + getView().refreshImage(imageResponse); + getView().showError(R.string.profile_updated); + } + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfileView.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfileView.java new file mode 100644 index 0000000..bff011c --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/profile/ProfileView.java @@ -0,0 +1,34 @@ +package com.p3.bewoplaner.page.profile; + +import com.p3.bewoplaner.model.User; +import com.p3.bewoplaner.page.mvp.MVPView; + +import android.net.Uri; +import android.support.annotation.StringRes; + +import java.io.File; + +/** + * Created by imarneanu on 2/22/17. + */ +interface ProfileView extends MVPView { + + void setCallback(Callback callback); + + void setProfile(User user); + + void showError(@StringRes int message); + + void refreshImage(String imageUrl); + + File getFile(Uri uri); + + interface Callback{ + void getProfile(); + + void updateProfile(String phoneNumber); + + void uploadImage(Uri uri); + } + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/splash/SplashActivity.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/splash/SplashActivity.java new file mode 100644 index 0000000..0bea94d --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/splash/SplashActivity.java @@ -0,0 +1,74 @@ +package com.p3.bewoplaner.page.splash; + +import com.p3.bewoplaner.di.InjectionHelper; +import com.p3.bewoplaner.page.login.LoginActivity; +import com.p3.bewoplaner.page.main.MainActivity; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; + +import javax.inject.Inject; + +/** + * Created by imarneanu on 2/9/17. + */ +public class SplashActivity extends AppCompatActivity implements SplashView { + + @Inject + SplashPresenter mSplashPresenter; + + private SplashView.Callbacks mCallbacks; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { + // Activity was brought to front and not created, + // Thus finishing this will get us to the last viewed activity + finish(); + Log.e("Splash", "FINISH"); + return; + } + InjectionHelper.getSplashComponent(this).inject(this); + mSplashPresenter.attachView(this); + mCallbacks.startTimer(); + } + + @Override + protected void onDestroy() { + if (mSplashPresenter != null && mSplashPresenter.isAttached()) { + mSplashPresenter.detachView(); + } + InjectionHelper.destroySplashComponent(); + super.onDestroy(); + } + + private void goToMainActivity() { + Intent intent = new Intent(this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + finish(); + } + + private void goToLoginActivity() { + startActivity(new Intent(this, LoginActivity.class)); + finish(); + } + + @Override + public void userLoggedIn(boolean isUserLoggedIn) { + if (isUserLoggedIn) { + goToMainActivity(); + return; + } + goToLoginActivity(); + } + + @Override + public void setCallbacks(Callbacks callbacks) { + mCallbacks = callbacks; + } +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/splash/SplashComponent.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/splash/SplashComponent.java new file mode 100644 index 0000000..a4b5fa9 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/splash/SplashComponent.java @@ -0,0 +1,15 @@ +package com.p3.bewoplaner.page.splash; + +import com.p3.bewoplaner.di.ApplicationComponent; +import com.p3.bewoplaner.di.scopes.ActivityScope; + +import dagger.Component; + +/** + * Created by imarneanu on 2/9/17. + */ +@ActivityScope +@Component(dependencies = ApplicationComponent.class) +public interface SplashComponent { + void inject(SplashActivity splashActivity); +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/splash/SplashPresenter.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/splash/SplashPresenter.java new file mode 100644 index 0000000..6d99256 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/splash/SplashPresenter.java @@ -0,0 +1,69 @@ +package com.p3.bewoplaner.page.splash; + +import com.p3.bewoplaner.BuildConfig; +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.prefs.BooleanPreference; +import com.p3.bewoplaner.data.prefs.StringPreference; +import com.p3.bewoplaner.page.mvp.MVPAbstractPresenter; +import com.p3.bewoplaner.utils.rx.RxUtils; + +import android.support.annotation.NonNull; + +import java.util.concurrent.TimeUnit; + +import javax.inject.Inject; +import javax.inject.Named; + +import rx.Subscription; + +/** + * Created by imarneanu on 2/9/17. + */ +class SplashPresenter extends MVPAbstractPresenter + implements SplashView.Callbacks { + + private static final int SPLASH_DELAY_MILLIS = BuildConfig.DEBUG ? 0 : 1500; + + @Inject + @Named(AppPrefsConstants.LOGIN_TOKEN) + StringPreference mLoginTokenPreference; + + @Inject + @Named(AppPrefsConstants.SAVE_PASSWORD) + BooleanPreference mSavePasswordPreference; + + @Inject + SplashPresenter() { + } + + /** + * Start timer for splash screen. + */ + + @Override + public void startTimer() { + Subscription subscription = rx.Observable.timer(SPLASH_DELAY_MILLIS, TimeUnit.MILLISECONDS) + .compose(RxUtils.provideDefaultTransformer()) + .subscribe(aLong -> checkUserLoggedIn()); + addSubscription(subscription); + } + + /** + * Check if the user is already logged in and saved the passwords. + */ + private void checkUserLoggedIn() { + getView().userLoggedIn(mLoginTokenPreference.isSet() && mSavePasswordPreference.get()); + } + + @Override + protected void afterAttachView(@NonNull SplashView view) { + super.afterAttachView(view); + view.setCallbacks(this); + } + + @Override + protected void beforeDetachView(@NonNull SplashView view) { + view.setCallbacks(null); + super.beforeDetachView(view); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/splash/SplashView.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/splash/SplashView.java new file mode 100644 index 0000000..7c0e604 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/page/splash/SplashView.java @@ -0,0 +1,17 @@ +package com.p3.bewoplaner.page.splash; + +import com.p3.bewoplaner.page.mvp.MVPView; + +/** + * Created by imarneanu on 2/9/17. + */ +interface SplashView extends MVPView { + + void userLoggedIn(boolean isUserLoggedIn); + + void setCallbacks(Callbacks callbacks); + + interface Callbacks { + void startTimer(); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/AppStoppedService.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/AppStoppedService.java new file mode 100644 index 0000000..3eaae86 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/AppStoppedService.java @@ -0,0 +1,53 @@ +package com.p3.bewoplaner.utils; + +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.prefs.BooleanPreference; +import com.p3.bewoplaner.di.InjectionHelper; + +import android.app.NotificationManager; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.support.annotation.Nullable; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * Service should be started only when "Password Save" + * is not checked in {@link com.p3.bewoplaner.page.login.LoginActivity}; + */ +public class AppStoppedService extends Service { + + @Inject + @Named(AppPrefsConstants.APP_KILLED) + BooleanPreference mAppKilledPreference; + + @Inject + @Named(AppPrefsConstants.SAVE_PASSWORD) + BooleanPreference mSavedPasswordPref; + + @Inject + NotificationManager mNotificationManager; + + @Override + public void onCreate() { + super.onCreate(); + InjectionHelper.getMainComponent(this).inject(this); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onTaskRemoved(Intent rootIntent) { + mAppKilledPreference.set(true); + if (!mSavedPasswordPref.get()) { + mNotificationManager.cancelAll(); + } + stopSelf(); + } +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/Constants.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/Constants.java new file mode 100644 index 0000000..4d94864 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/Constants.java @@ -0,0 +1,27 @@ +package com.p3.bewoplaner.utils; + +/** + * Created by laszloffiferenc on 2/21/17. + */ +public interface Constants { + String BEWOPLANER_ENDPOINT = "https://www.p3ds.net/bewoplaner/public/"; + String VOUCHER_ENDPOINT = "https://bewoplaner.beyondsoft.de/"; + int DISK_CACHE_SIZE = 10 * 1024; + String GROUP_NR_USERS = "group_nb_users"; + String GROUP_ID = "groupid"; + String NOTIFICATION_BROADCAST = "not_broadcast"; + String MESSAGE = "message"; + String MESSAGE_ID = "id"; + String CREATED_AT = "created_at"; + String MESSAGE_TEXT = "text"; + String FILE = "file"; + String SMALLER_IMAGE = "smaller_image"; + String FILENAME = "original_filename"; + String USER_ID = "userid"; + String USERNAME = "user_name"; + String USER_AVATAR = "user_picture"; + String GROUP_NAME = "groupname"; + String CHAT_IS_ALREADY_OPEN = "chat_is_already_open"; + String KEY_TEXT_REPLY = "key_text_reply"; + +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/DateUtils.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/DateUtils.java new file mode 100644 index 0000000..69e85a1 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/DateUtils.java @@ -0,0 +1,68 @@ +package com.p3.bewoplaner.utils; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +/** + * Created by BoldijarPaul on 10/10/16. + */ +public abstract class DateUtils { + + static private DateFormat sHourDateFormat; + static private DateFormat sDayFormat; + static private DateFormat sDayOfYearFormat; + + private DateUtils() { + + } + + /** + * Convert date object to string dd MMMM yyyy format. + */ + public static String toDayOfYearString(Date date) { + if (sDayOfYearFormat == null) { + sDayOfYearFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()); + } + return sDayOfYearFormat.format(date); + } + + /** + * Convert long object to string HH:mm format. + */ + public static String toHourFormatHour(long timestamp) { + if (sHourDateFormat == null) { + sHourDateFormat = new SimpleDateFormat("HH:mm", Locale.getDefault()); + } + return sHourDateFormat.format(new Date(timestamp)); + } + + /** + * Convert long object to string dd MMMM format. + */ + public static String toHourOrDateFormatHour(long timestamp) { + if (isToday(timestamp)) { + return toHourFormatHour(timestamp); + } + if (sDayFormat == null) { + sDayFormat = new SimpleDateFormat("dd MMMM", Locale.getDefault()); + } + return sDayFormat.format(new Date(timestamp)); + } + + /** + * Check if a date long object is today or not. + */ + private static boolean isToday(long timestamp) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(timestamp); + Calendar today = Calendar.getInstance(); + calendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH)); + return calendar.get(Calendar.DATE) == today.get(Calendar.DATE) + && calendar.get(Calendar.MONTH) == today.get(Calendar.MONTH) + && calendar.get(Calendar.YEAR) == today.get(Calendar.YEAR); + } + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/DialogHandler.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/DialogHandler.java new file mode 100644 index 0000000..4dee81f --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/DialogHandler.java @@ -0,0 +1,96 @@ +package com.p3.bewoplaner.utils; + +import com.p3.bewoplaner.R; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.support.annotation.StringRes; +import android.support.v7.app.AlertDialog; + +/** + * Created by Rares Teodorescu on 22/02/2017. + */ +public final class DialogHandler { + + /** + * Create a simple dialog with no title + * + * @param context The context from which the dialog will be shown + * @param message The string resource for the message + * @param cancelable True if the dialog should be cancelable, false otherwise + * @param positiveButton The string resource for the positive button + * @param callback The callback for the positive button action + * + * @return The dialog made + */ + public static Dialog createNoTitleDialog( + Context context, + @StringRes int message, + boolean cancelable, + @StringRes int positiveButton, + DialogInterface.OnClickListener callback) { + + return createSimpleDialog(context, 0, message, cancelable, positiveButton, callback); + } + + public static Dialog createNoTitleDialog( + Context context, + @StringRes int message, + boolean cancelable, + @StringRes int positiveButton, + @StringRes int negativeButton, + DialogInterface.OnClickListener posCallback, + DialogInterface.OnClickListener negCallback) { + if (context == null) { + return null; + } + final AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.StyledDialog); + return builder + .setMessage(message) + .setPositiveButton(positiveButton, posCallback) + .setNegativeButton(negativeButton, negCallback) + .setCancelable(cancelable) + .create(); + } + + /** + * Create a simple dialog + * + * @param context The context from which the dialog will be shown + * @param title The string resource for the title, 0 for none + * @param message The string resource for the message + * @param cancelable True if the dialog should be cancelable, false otherwise + * @param positiveButton The string resource for the positive button + * @param callback The callback for the positive button action + * + * @return The dialog that was just shown + */ + private static Dialog createSimpleDialog( + Context context, + @StringRes int title, + @StringRes int message, + boolean cancelable, + @StringRes int positiveButton, + DialogInterface.OnClickListener callback) { + + final AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.StyledDialog); + if (title != 0) { + builder.setTitle(title); + } + return builder + .setMessage(message) + .setPositiveButton(positiveButton, callback) + .setCancelable(cancelable) + .create(); + } + + public static Dialog showSimpleDialog(Context context, + int title, + int text, + int posBtn, + DialogInterface.OnClickListener callback) { + + return createSimpleDialog(context, title, text, true, posBtn, callback); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/EndlessScrollListener.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/EndlessScrollListener.java new file mode 100644 index 0000000..5abd6ff --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/EndlessScrollListener.java @@ -0,0 +1,54 @@ +package com.p3.bewoplaner.utils; + +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; + +public abstract class EndlessScrollListener extends RecyclerView.OnScrollListener { + + private int mPreviousTotal = 0; + private int mVisibleThreshold = 5; + private boolean mLoading = false; + private final LinearLayoutManager mLayoutManager; + private int mVisibleItem; + + public void reset() { + mPreviousTotal = 0; + } + + protected EndlessScrollListener(int visibleThreshold, LinearLayoutManager layoutManager) { + mVisibleThreshold = visibleThreshold; + mLayoutManager = layoutManager; + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + int visibleItemCount = mLayoutManager.getChildCount(); + int totalItemCount = mLayoutManager.getItemCount(); + mVisibleItem = mLayoutManager.findFirstVisibleItemPosition(); + + if (mLoading) { + if (totalItemCount > mPreviousTotal) { + mLoading = false; + mPreviousTotal = totalItemCount; + } + } + if (recyclerView == null) { + return; + } + recyclerView.post(() -> { + if (!mLoading + && (totalItemCount - visibleItemCount) <= (mVisibleItem + mVisibleThreshold)) { + mLoading = onLoad(); + } + }); + } + + /** + * @return true if loading was started now, false otherwise + */ + public abstract boolean onLoad(); + + public int getVisibleItem() { + return mVisibleItem; + } +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/FileUtils.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/FileUtils.java new file mode 100644 index 0000000..4e45588 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/FileUtils.java @@ -0,0 +1,564 @@ +package com.p3.bewoplaner.utils;/* + * Copyright (C) 2007-2008 OpenIntents.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.util.Log; +import android.webkit.MimeTypeMap; + +import java.io.File; +import java.io.FileFilter; +import java.text.DecimalFormat; +import java.util.Comparator; + +/** + * @author Peli + * @author paulburke (ipaulpro) + * @version 2013-12-11 + */ +public class FileUtils { + private FileUtils() { + } //private constructor to enforce Singleton pattern + + /** TAG for log messages. */ + static final String TAG = "FileUtils"; + private static final boolean DEBUG = false; // Set to true to enable logging + + public static final String MIME_TYPE_AUDIO = "audio/*"; + public static final String MIME_TYPE_TEXT = "text/*"; + public static final String MIME_TYPE_IMAGE = "image/*"; + public static final String MIME_TYPE_VIDEO = "video/*"; + public static final String MIME_TYPE_APP = "application/*"; + + public static final String HIDDEN_PREFIX = "."; + + /** + * Gets the extension of a file name, like ".png" or ".jpg". + * + * @return Extension including the dot("."); "" if there is no extension; + * null if uri was null. + */ + public static String getExtension(String uri) { + if (uri == null) { + return null; + } + + int dot = uri.lastIndexOf("."); + if (dot >= 0) { + return uri.substring(dot); + } else { + // No extension. + return ""; + } + } + + /** + * @return Whether the URI is a local one. + */ + public static boolean isLocal(String url) { + if (url != null && !url.startsWith("http://") && !url.startsWith("https://")) { + return true; + } + return false; + } + + /** + * @return True if Uri is a MediaStore Uri. + * @author paulburke + */ + public static boolean isMediaUri(Uri uri) { + return "media".equalsIgnoreCase(uri.getAuthority()); + } + + /** + * Convert File into Uri. + * + * @return uri + */ + public static Uri getUri(File file) { + if (file != null) { + return Uri.fromFile(file); + } + return null; + } + + /** + * Returns the path only (without file name). + */ + public static File getPathWithoutFilename(File file) { + if (file != null) { + if (file.isDirectory()) { + // no file to be split off. Return everything + return file; + } else { + String filename = file.getName(); + String filepath = file.getAbsolutePath(); + + // Construct path without file name. + String pathwithoutname = filepath.substring(0, + filepath.length() - filename.length()); + if (pathwithoutname.endsWith("/")) { + pathwithoutname = pathwithoutname.substring(0, pathwithoutname.length() - 1); + } + return new File(pathwithoutname); + } + } + return null; + } + + /** + * @return The MIME type for the given file. + */ + public static String getMimeType(File file) { + + String extension = getExtension(file.getName()); + + if (extension.length() > 0) { + return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1)); + } + + return "application/octet-stream"; + } + + /** + * @return The MIME type for the give Uri. + */ + public static String getMimeType(Context context, Uri uri) { + File file = new File(getPath(context, uri)); + return getMimeType(file); + } + + /** + * @param uri The Uri to check. + * + * @return Whether the Uri authority is ExternalStorageProvider. + * @author paulburke + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * + * @return Whether the Uri authority is DownloadsProvider. + * @author paulburke + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * + * @return Whether the Uri authority is MediaProvider. + * @author paulburke + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * + * @return Whether the Uri authority is Google Photos. + */ + public static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * + * @return Whether the Uri authority is Google Photos. + */ + public static boolean isGoogleDriveUri(Uri uri) { + return "com.google.android.apps.docs.storage".equals(uri.getAuthority()); + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * + * @return The value of the _data column, which is typically a file path. + * @author paulburke + */ + public static String getDataColumn(Context context, Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, + null); + if (cursor != null && cursor.moveToFirst()) { + if (DEBUG) { + DatabaseUtils.dumpCursor(cursor); + } + + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + /** + * Get a file path from a Uri. This will get the the path for Storage Access + * Framework Documents, as well as the _data field for the MediaStore and + * other file-based ContentProviders.
+ *
+ * Callers should check whether the path is local before assuming it + * represents a local file. + * + * @param context The context. + * @param uri The Uri to query. + * + * @author paulburke + * @see #isLocal(String) + * @see #getFile(Context, Uri) + */ + public static String getPath(final Context context, final Uri uri) { + + if (DEBUG) { + Log.d(TAG + " File -", + "Authority: " + uri.getAuthority() + + ", Fragment: " + uri.getFragment() + + ", Port: " + uri.getPort() + + ", Query: " + uri.getQuery() + + ", Scheme: " + uri.getScheme() + + ", Host: " + uri.getHost() + + ", Segments: " + uri.getPathSegments().toString() + ); + } + + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // LocalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + + // TODO handle non-primary volumes + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(context, contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{ + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + + // Return the remote address + if (isGooglePhotosUri(uri)) { + return uri.getLastPathSegment(); + } + + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + /** + * Convert Uri into File, if possible. + * + * @return file A local file that the Uri was pointing to, or null if the + * Uri is unsupported or pointed to a remote resource. + * @author paulburke + * @see #getPath(Context, Uri) + */ + public static File getFile(Context context, Uri uri) { + if (uri != null) { + String path = getPath(context, uri); + if (path != null && isLocal(path)) { + return new File(path); + } + } + return null; + } + + /** + * Get the file size in a human-readable string. + * + * @author paulburke + */ + public static String getReadableFileSize(int size) { + final int BYTES_IN_KILOBYTES = 1024; + final DecimalFormat dec = new DecimalFormat("###.#"); + final String KILOBYTES = " KB"; + final String MEGABYTES = " MB"; + final String GIGABYTES = " GB"; + float fileSize = 0; + String suffix = KILOBYTES; + + if (size > BYTES_IN_KILOBYTES) { + fileSize = size / BYTES_IN_KILOBYTES; + if (fileSize > BYTES_IN_KILOBYTES) { + fileSize = fileSize / BYTES_IN_KILOBYTES; + if (fileSize > BYTES_IN_KILOBYTES) { + fileSize = fileSize / BYTES_IN_KILOBYTES; + suffix = GIGABYTES; + } else { + suffix = MEGABYTES; + } + } + } + return String.valueOf(dec.format(fileSize) + suffix); + } + + /** + * Attempt to retrieve the thumbnail of given File from the MediaStore. This + * should not be called on the UI thread. + * + * @author paulburke + */ + public static Bitmap getThumbnail(Context context, File file) { + return getThumbnail(context, getUri(file), getMimeType(file)); + } + + /** + * Attempt to retrieve the thumbnail of given Uri from the MediaStore. This + * should not be called on the UI thread. + * + * @author paulburke + */ + public static Bitmap getThumbnail(Context context, Uri uri) { + return getThumbnail(context, uri, getMimeType(context, uri)); + } + + /** + * Attempt to retrieve the thumbnail of given Uri from the MediaStore. This + * should not be called on the UI thread. + * + * @author paulburke + */ + public static Bitmap getThumbnail(Context context, Uri uri, String mimeType) { + if (DEBUG) { + Log.d(TAG, "Attempting to get thumbnail"); + } + + if (!isMediaUri(uri)) { + Log.e(TAG, "You can only retrieve thumbnails for images and videos."); + return null; + } + + Bitmap bm = null; + if (uri != null) { + final ContentResolver resolver = context.getContentResolver(); + Cursor cursor = null; + try { + cursor = resolver.query(uri, null, null, null, null); + if (cursor.moveToFirst()) { + final int id = cursor.getInt(0); + if (DEBUG) { + Log.d(TAG, "Got thumb ID: " + id); + } + + if (mimeType.contains("video")) { + bm = MediaStore.Video.Thumbnails.getThumbnail( + resolver, + id, + MediaStore.Video.Thumbnails.MINI_KIND, + null); + } else if (mimeType.contains(FileUtils.MIME_TYPE_IMAGE)) { + bm = MediaStore.Images.Thumbnails.getThumbnail( + resolver, + id, + MediaStore.Images.Thumbnails.MINI_KIND, + null); + } + } + } catch (Exception e) { + if (DEBUG) { + Log.e(TAG, "getThumbnail", e); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + return bm; + } + + /** + * File and folder comparator. TODO Expose sorting option method + * + * @author paulburke + */ + public static Comparator sComparator = new Comparator() { + @Override + public int compare(File f1, File f2) { + // Sort alphabetically by lower case, which is much cleaner + return f1.getName().toLowerCase().compareTo( + f2.getName().toLowerCase()); + } + }; + + /** + * File (not directories) filter. + * + * @author paulburke + */ + public static FileFilter sFileFilter = new FileFilter() { + @Override + public boolean accept(File file) { + final String fileName = file.getName(); + // Return files only (not directories) and skip hidden files + return file.isFile() && !fileName.startsWith(HIDDEN_PREFIX); + } + }; + + /** + * Folder (directories) filter. + * + * @author paulburke + */ + public static FileFilter sDirFilter = new FileFilter() { + @Override + public boolean accept(File file) { + final String fileName = file.getName(); + // Return directories only and skip hidden directories + return file.isDirectory() && !fileName.startsWith(HIDDEN_PREFIX); + } + }; + + /** + * Get the Intent for selecting content to be used in an Intent Chooser. + * + * @return The intent for opening a file with Intent.createChooser() + * @author paulburke + */ + public static Intent createGetContentIntent() { + // Implicitly allow the user to select a particular kind of data + final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + // The MIME data type filter + intent.setType("*/*"); + // Only return URIs that can be opened with ContentResolver + intent.addCategory(Intent.CATEGORY_OPENABLE); + return intent; + } + + public static String getFileTypeFromFilePath(String filePath) { + String extension = ""; + int i = filePath.lastIndexOf('.'); + if (i > 0) { + extension = filePath.substring(i + 1); + } + if (extension.contains("doc") || extension.contains("docx")) { + // Word document + extension = "application/msword"; + } else if (extension.contains("pdf")) { + // PDF file + extension = "application/pdf"; + } else if (extension.contains("ppt") || extension.contains("pptx")) { + // Powerpoint file + extension = "application/vnd.ms-powerpoint"; + } else if (extension.contains("xls") || extension.contains("xlsx")) { + // Excel file + extension = "application/vnd.ms-excel"; + } else if (extension.contains("zip") || extension.contains("rar")) { + // WAV audio file + extension = "application/x-wav"; + } else if (extension.contains("rtf")) { + // RTF file + extension = "application/rtf"; + } else if (extension.contains("wav") || extension.contains("mp3")) { + // WAV audio file + extension = "audio/x-wav"; + } else if (extension.contains("gif")) { + // GIF file + extension = "image/gif"; + } else if (extension.contains("jpg") || extension.contains("JPG") ||extension.contains("jpeg") || extension.contains("png")) { + // JPG file + extension = "image/jpeg"; + } else if (extension.contains("txt")) { + // Text file + extension = "text/plain"; + } else if (extension.contains("3gp") || extension.contains("mpg") || + extension.contains("mpeg") || extension.contains("mpe") || extension.contains("mp4") || extension.contains("avi")) { + // Video files + extension = "video/*"; + } else { + extension = "*/*"; + } + return extension; + } + +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/LoggedOutUtils.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/LoggedOutUtils.java new file mode 100644 index 0000000..12709d9 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/LoggedOutUtils.java @@ -0,0 +1,51 @@ +package com.p3.bewoplaner.utils; + +import com.p3.bewoplaner.data.AppPrefsConstants; +import com.p3.bewoplaner.data.prefs.BooleanPreference; +import com.p3.bewoplaner.di.scopes.ActivityScope; +import com.p3.bewoplaner.page.login.LoginPresenter; + +import android.app.NotificationManager; +import android.content.Context; +import android.provider.Settings; + +import javax.inject.Inject; +import javax.inject.Named; + +@ActivityScope +public class LoggedOutUtils { + + @Inject + @Named(AppPrefsConstants.APP_KILLED) + BooleanPreference mAppKilledPreference; + + @Inject + @Named(AppPrefsConstants.SAVE_PASSWORD) + BooleanPreference mSavePasswordPreference; + + @Inject + NotificationManager mNotificationManager; + + @Inject + LoginPresenter mLoginPresenter; + + @Inject + Context mContext; + + @Inject + LoggedOutUtils() { + } + + /** + * Check if the user is still logged in. + */ + public boolean checkLoggedOutPrefs() { + if (!mSavePasswordPreference.get() && mAppKilledPreference.get()) { + mNotificationManager.cancelAll(); + mLoginPresenter.logout(Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.ANDROID_ID), true, null); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/Preconditions.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/Preconditions.java new file mode 100644 index 0000000..0085bbe --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/Preconditions.java @@ -0,0 +1,89 @@ +package com.p3.bewoplaner.utils; + +import android.support.annotation.NonNull; + +/** + * Created by imarneanu on 2/9/17. + */ +public final class Preconditions { + + private Preconditions() { + } + + /** + * Throws an {@link IllegalStateException} if {#condition} is false + * + * @param condition The condition + * @param message The exception message + * @param args Optional args + */ + public static void checkState(boolean condition, String message, Object... args) { + if (!condition) { + throw new IllegalStateException(formatMessage(message, args)); + } + } + + /** + * Throws a {@link IllegalArgumentException} if {#condition} is false + * + * @param condition The condition + * @param message The exception message + * @param args Optional args + */ + public static void checkArgument(boolean condition, String message, Object... args) { + if (!condition) { + throw new IllegalArgumentException(formatMessage(message, args)); + } + } + + /** + * Throws a {@link NullPointerException} if {#thing} is null + * + * @param thing The thing + * @param message The exception message + * @param args Optional args + */ + @NonNull + public static T checkNotNull(T thing, String message, Object... args) { + if (thing == null) { + throw new NullPointerException(formatMessage(message, args)); + } + return thing; + } + + /** + * Throws a {@link IllegalStateException} if {#thing} is not null + * + * @param thing The thing + * @param message The exception message + * @param args Optional args + */ + public static void checkNull(Object thing, String message, Object... args) { + if (thing != null) { + throw new IllegalStateException(formatMessage(message, args)); + } + } + + /** + * Throws an {@link IllegalStateException} if {#condition} is false + * + * @param condition The condition + */ + public static void checkState(boolean condition) { + checkState(condition, "condition not met"); + } + + /** + * Throws an {@link NullPointerException} if thing is null + * + * @param thing The thing + */ + @NonNull + public static T checkNotNull(T thing) { + return checkNotNull(thing, "condition not met"); + } + + private static String formatMessage(String message, Object... args) { + return String.format(message, args); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/RuntimePermissionBaseFragment.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/RuntimePermissionBaseFragment.java new file mode 100644 index 0000000..c97e336 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/RuntimePermissionBaseFragment.java @@ -0,0 +1,52 @@ +package com.p3.bewoplaner.utils; + + +import android.content.pm.PackageManager; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; + +/** + * Created by Vlad on 27/02/2017. + */ +public abstract class RuntimePermissionBaseFragment extends Fragment { + + protected static final int MY_STORAGE_PERMISSION = 1; + + /** + * Request permission. + */ + public void requestPermission(String permission, int permissionCode) { + if (permissionGranted(permission)) { + onPermissionGranted(permissionCode); + return; + } + requestPermissions(new String[]{permission}, permissionCode); + } + + /** + * Check if already got the permission. + */ + private boolean permissionGranted(String permission) { + return getActivity() != null && + ContextCompat.checkSelfPermission(getActivity(), permission) == PackageManager.PERMISSION_GRANTED; + } + + /** + * Response for permission. + */ + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + onPermissionGranted(requestCode); + } else { + onPermissionDenied(requestCode); + } + } + + abstract public void onPermissionGranted(int permissionCode); + + abstract public void onPermissionDenied(int permissionCode); + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/SystemUtils.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/SystemUtils.java new file mode 100644 index 0000000..21e7f57 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/SystemUtils.java @@ -0,0 +1,22 @@ +package com.p3.bewoplaner.utils; + +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +/** Check if network is available. + *

+ * Created by laszloffiferenc on 2/21/17. + */ +public class SystemUtils { + + private final ConnectivityManager mConnectivityManager; + + public SystemUtils(ConnectivityManager connectivityManager) { + mConnectivityManager = connectivityManager; + } + + public boolean isNetworkUnavailable() { + NetworkInfo activeNetwork = mConnectivityManager.getActiveNetworkInfo(); + return activeNetwork == null || !activeNetwork.isConnectedOrConnecting(); + } +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/Utils.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/Utils.java new file mode 100644 index 0000000..1a62f4b --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/Utils.java @@ -0,0 +1,36 @@ +package com.p3.bewoplaner.utils; + +import android.app.Activity; +import android.support.v4.app.FragmentManager; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +/** + * Created by imarneanu on 2/13/17. + */ +public class Utils { + /** + * Clears activity's backstack + * + * @param appCompatActivity The activity which will have no fragments in backstack + */ + public static void clearFullBackStack(AppCompatActivity appCompatActivity) { + FragmentManager fragmentManager = appCompatActivity.getSupportFragmentManager(); + if (fragmentManager.getBackStackEntryCount() > 0) { + // clear the fragment back stack + FragmentManager.BackStackEntry first = fragmentManager.getBackStackEntryAt(0); + fragmentManager.popBackStackImmediate(first.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE); + } + } + + public static void hideKeyboard(Activity activity) { + InputMethodManager imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE); + View view = activity.getCurrentFocus(); + if (view == null) { + view = new View(activity); + } + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/rx/RxUtils.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/rx/RxUtils.java new file mode 100644 index 0000000..5c063ac --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/rx/RxUtils.java @@ -0,0 +1,26 @@ +package com.p3.bewoplaner.utils.rx; + +import rx.Observable; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +/** + * Created by imarneanu on 2/9/17. + */ +public abstract class RxUtils { + + private RxUtils() { + + } + + /** + * This method applies the io(background)thread for the observable + * and makes the subscriber work on the main thread one. + * + * @return Observable. + */ + public static Observable.Transformer provideDefaultTransformer() { + return observable -> observable.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/EmptyLayout.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/EmptyLayout.java new file mode 100644 index 0000000..ef2a2cb --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/EmptyLayout.java @@ -0,0 +1,122 @@ +package com.p3.bewoplaner.utils.ui; + +import com.p3.bewoplaner.R; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +import static com.p3.bewoplaner.utils.ui.ViewUtils.*; + +/** + * Custom layout to show loading, try again and error messages. + *

+ * Created by Rares on 20/09/16. + */ +public class EmptyLayout extends FrameLayout { + + @BindView(R.id.empty_layout_progress_bar) + ProgressBar mProgressBar; + + @BindView(R.id.empty_layout_try_again_layout) + View mTryAgainLayout; + + @BindView(R.id.empty_layout_error_text) + TextView mErrorText; + + @BindView(R.id.empty_layout_empty_text) + TextView mEmptyText; + + @BindView(R.id.empty_layout_try_again_button) + TextView mRetryButton; + + private OnClickListener mOnTryAgainClickListener; + + public EmptyLayout(Context context) { + super(context); + } + + public EmptyLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs); + } + + public EmptyLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs); + } + + private void init(AttributeSet attrs) { + inflate(getContext(), R.layout.empty_layout, this); + ButterKnife.bind(this); + + setProgressBarColorToOrange(mProgressBar); + + TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.EmptyLayout, 0, 0); + + try { + final CharSequence errorText = a.getText(R.styleable.EmptyLayout_emptyLayoutErrorText); + if (errorText != null) { + mErrorText.setText(errorText); + } + final int errorTextMaxWith = a.getDimensionPixelSize(R.styleable.EmptyLayout_emptyLayoutErrorTextMaxWidth, 0); + if (errorTextMaxWith != 0) { + mErrorText.setMaxWidth(errorTextMaxWith); + } + } finally { + a.recycle(); + } + } + + @OnClick(R.id.empty_layout_try_again_button) + void tryAgain(View view) { + if (mOnTryAgainClickListener != null) { + mOnTryAgainClickListener.onClick(view); + } + } + + public void showLoading() { + setVisibility(VISIBLE); + mProgressBar.setVisibility(VISIBLE); + mTryAgainLayout.setVisibility(GONE); + mEmptyText.setVisibility(GONE); + } + + public void showError() { + setVisibility(VISIBLE); + mProgressBar.setVisibility(GONE); + mTryAgainLayout.setVisibility(VISIBLE); + mEmptyText.setVisibility(GONE); + } + + public void setErrorText(String errorText) { + mErrorText.setText(errorText); + } + + public void setOnTryAgainClickListener(OnClickListener onTryAgainClickListener) { + mOnTryAgainClickListener = onTryAgainClickListener; + } + + public void setEmptyText(String text) { + mEmptyText.setText(text); + } + + public void hideRetryButton() { + mRetryButton.setVisibility(GONE); + } + + public void showEmpty() { + setVisibility(VISIBLE); + mProgressBar.setVisibility(GONE); + mTryAgainLayout.setVisibility(GONE); + mEmptyText.setVisibility(VISIBLE); + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/FlexibleDividerDecoration.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/FlexibleDividerDecoration.java new file mode 100644 index 0000000..00def26 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/FlexibleDividerDecoration.java @@ -0,0 +1,439 @@ +package com.p3.bewoplaner.utils.ui; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.annotation.ColorRes; +import android.support.annotation.DimenRes; +import android.support.annotation.DrawableRes; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Created by imarneanu on 2/22/17. + */ +public abstract class FlexibleDividerDecoration extends RecyclerView.ItemDecoration { + + private static final int DEFAULT_SIZE = 2; + private static final int[] ATTRS = new int[]{ + android.R.attr.listDivider + }; + + protected enum DividerType { + DRAWABLE, PAINT, COLOR + } + + protected DividerType mDividerType = DividerType.DRAWABLE; + protected VisibilityProvider mVisibilityProvider; + protected PaintProvider mPaintProvider; + protected ColorProvider mColorProvider; + protected DrawableProvider mDrawableProvider; + protected SizeProvider mSizeProvider; + protected boolean mShowLastDivider; + protected boolean mPositionInsideItem; + private Paint mPaint; + + protected FlexibleDividerDecoration(Builder builder) { + if (builder.mPaintProvider != null) { + mDividerType = DividerType.PAINT; + mPaintProvider = builder.mPaintProvider; + } else if (builder.mColorProvider != null) { + mDividerType = DividerType.COLOR; + mColorProvider = builder.mColorProvider; + mPaint = new Paint(); + setSizeProvider(builder); + } else { + mDividerType = DividerType.DRAWABLE; + if (builder.mDrawableProvider == null) { + TypedArray a = builder.mContext.obtainStyledAttributes(ATTRS); + final Drawable divider = a.getDrawable(0); + a.recycle(); + mDrawableProvider = new DrawableProvider() { + @Override + public Drawable drawableProvider(int position, RecyclerView parent) { + return divider; + } + }; + } else { + mDrawableProvider = builder.mDrawableProvider; + } + mSizeProvider = builder.mSizeProvider; + } + + mVisibilityProvider = builder.mVisibilityProvider; + mShowLastDivider = builder.mShowLastDivider; + mPositionInsideItem = builder.mPositionInsideItem; + } + + private void setSizeProvider(Builder builder) { + mSizeProvider = builder.mSizeProvider; + if (mSizeProvider == null) { + mSizeProvider = new SizeProvider() { + @Override + public int dividerSize(int position, RecyclerView parent) { + return DEFAULT_SIZE; + } + }; + } + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + RecyclerView.Adapter adapter = parent.getAdapter(); + if (adapter == null) { + return; + } + + int itemCount = adapter.getItemCount(); + int lastDividerOffset = getLastDividerOffset(parent); + int validChildCount = parent.getChildCount(); + int lastChildPosition = -1; + for (int i = 0; i < validChildCount; i++) { + View child = parent.getChildAt(i); + int childPosition = parent.getChildAdapterPosition(child); + + if (childPosition < lastChildPosition) { + // Avoid remaining divider when animation starts + continue; + } + lastChildPosition = childPosition; + + if (!mShowLastDivider && childPosition >= itemCount - lastDividerOffset) { + // Don't draw divider for last line if mShowLastDivider = false + continue; + } + + if (wasDividerAlreadyDrawn(childPosition, parent)) { + // No need to draw divider again as it was drawn already by previous column + continue; + } + + int groupIndex = getGroupIndex(childPosition, parent); + if (mVisibilityProvider.shouldHideDivider(groupIndex, parent)) { + continue; + } + + Rect bounds = getDividerBound(groupIndex, parent, child); + switch (mDividerType) { + case DRAWABLE: + Drawable drawable = mDrawableProvider.drawableProvider(groupIndex, parent); + drawable.setBounds(bounds); + drawable.draw(c); + break; + case PAINT: + mPaint = mPaintProvider.dividerPaint(groupIndex, parent); + c.drawLine(bounds.left, bounds.top, bounds.right, bounds.bottom, mPaint); + break; + case COLOR: + mPaint.setColor(mColorProvider.dividerColor(groupIndex, parent)); + mPaint.setStrokeWidth(mSizeProvider.dividerSize(groupIndex, parent)); + c.drawLine(bounds.left, bounds.top, bounds.right, bounds.bottom, mPaint); + break; + } + } + } + + @Override + public void getItemOffsets(Rect rect, View v, RecyclerView parent, RecyclerView.State state) { + int position = parent.getChildAdapterPosition(v); + int itemCount = parent.getAdapter().getItemCount(); + int lastDividerOffset = getLastDividerOffset(parent); + if (!mShowLastDivider && position >= itemCount - lastDividerOffset) { + // Don't set item offset for last line if mShowLastDivider = false + return; + } + + int groupIndex = getGroupIndex(position, parent); + if (mVisibilityProvider.shouldHideDivider(groupIndex, parent)) { + return; + } + + setItemOffsets(rect, groupIndex, parent); + } + + /** + * Check if recyclerview is reverse layout + * + * @param parent RecyclerView + * @return true if recyclerview is reverse layout + */ + protected boolean isReverseLayout(RecyclerView parent) { + RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); + if (layoutManager instanceof LinearLayoutManager) { + return ((LinearLayoutManager) layoutManager).getReverseLayout(); + } else { + return false; + } + } + + /** + * In the case mShowLastDivider = false, + * Returns offset for how many views we don't have to draw a divider for, + * for LinearLayoutManager it is as simple as not drawing the last child divider, + * but for a GridLayoutManager it needs to take the span count for the last items into account + * until we use the span count configured for the grid. + * + * @param parent RecyclerView + * @return offset for how many views we don't have to draw a divider or 1 if its a + * LinearLayoutManager + */ + private int getLastDividerOffset(RecyclerView parent) { + if (parent.getLayoutManager() instanceof GridLayoutManager) { + GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager(); + GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup(); + int spanCount = layoutManager.getSpanCount(); + int itemCount = parent.getAdapter().getItemCount(); + for (int i = itemCount - 1; i >= 0; i--) { + if (spanSizeLookup.getSpanIndex(i, spanCount) == 0) { + return itemCount - i; + } + } + } + + return 1; + } + + /** + * Determines whether divider was already drawn for the row the item is in, + * effectively only makes sense for a grid + * + * @param position current view position to draw divider + * @param parent RecyclerView + * @return true if the divider can be skipped as it is in the same row as the previous one. + */ + private boolean wasDividerAlreadyDrawn(int position, RecyclerView parent) { + if (parent.getLayoutManager() instanceof GridLayoutManager) { + GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager(); + GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup(); + int spanCount = layoutManager.getSpanCount(); + return spanSizeLookup.getSpanIndex(position, spanCount) > 0; + } + + return false; + } + + /** + * Returns a group index for GridLayoutManager. + * for LinearLayoutManager, always returns position. + * + * @param position current view position to draw divider + * @param parent RecyclerView + * @return group index of items + */ + private int getGroupIndex(int position, RecyclerView parent) { + if (parent.getLayoutManager() instanceof GridLayoutManager) { + GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager(); + GridLayoutManager.SpanSizeLookup spanSizeLookup = layoutManager.getSpanSizeLookup(); + int spanCount = layoutManager.getSpanCount(); + return spanSizeLookup.getSpanGroupIndex(position, spanCount); + } + + return position; + } + + protected abstract Rect getDividerBound(int position, RecyclerView parent, View child); + + protected abstract void setItemOffsets(Rect outRect, int position, RecyclerView parent); + + /** + * Interface for controlling divider visibility + */ + public interface VisibilityProvider { + + /** + * Returns true if divider should be hidden. + * + * @param position Divider position (or group index for GridLayoutManager) + * @param parent RecyclerView + * @return True if the divider at position should be hidden + */ + boolean shouldHideDivider(int position, RecyclerView parent); + } + + /** + * Interface for controlling paint instance for divider drawing + */ + public interface PaintProvider { + + /** + * Returns {@link android.graphics.Paint} for divider + * + * @param position Divider position (or group index for GridLayoutManager) + * @param parent RecyclerView + * @return Paint instance + */ + Paint dividerPaint(int position, RecyclerView parent); + } + + /** + * Interface for controlling divider color + */ + public interface ColorProvider { + + /** + * Returns {@link android.graphics.Color} value of divider + * + * @param position Divider position (or group index for GridLayoutManager) + * @param parent RecyclerView + * @return Color value + */ + int dividerColor(int position, RecyclerView parent); + } + + /** + * Interface for controlling drawable object for divider drawing + */ + public interface DrawableProvider { + + /** + * Returns drawable instance for divider + * + * @param position Divider position (or group index for GridLayoutManager) + * @param parent RecyclerView + * @return Drawable instance + */ + Drawable drawableProvider(int position, RecyclerView parent); + } + + /** + * Interface for controlling divider size + */ + public interface SizeProvider { + + /** + * Returns size value of divider. + * Height for horizontal divider, width for vertical divider + * + * @param position Divider position (or group index for GridLayoutManager) + * @param parent RecyclerView + * @return Size of divider + */ + int dividerSize(int position, RecyclerView parent); + } + + public static class Builder { + + private Context mContext; + protected Resources mResources; + private PaintProvider mPaintProvider; + private ColorProvider mColorProvider; + private DrawableProvider mDrawableProvider; + private SizeProvider mSizeProvider; + private VisibilityProvider mVisibilityProvider = new VisibilityProvider() { + @Override + public boolean shouldHideDivider(int position, RecyclerView parent) { + return false; + } + }; + private boolean mShowLastDivider = false; + private boolean mPositionInsideItem = false; + + public Builder(Context context) { + mContext = context; + mResources = context.getResources(); + } + + public T paint(final Paint paint) { + return paintProvider(new PaintProvider() { + @Override + public Paint dividerPaint(int position, RecyclerView parent) { + return paint; + } + }); + } + + public T paintProvider(PaintProvider provider) { + mPaintProvider = provider; + return (T) this; + } + + public T color(final int color) { + return colorProvider(new ColorProvider() { + @Override + public int dividerColor(int position, RecyclerView parent) { + return color; + } + }); + } + + public T colorResId(@ColorRes int colorId) { + return color(ContextCompat.getColor(mContext, colorId)); + } + + public T colorProvider(ColorProvider provider) { + mColorProvider = provider; + return (T) this; + } + + public T drawable(@DrawableRes int id) { + return drawable(ContextCompat.getDrawable(mContext, id)); + } + + public T drawable(final Drawable drawable) { + return drawableProvider(new DrawableProvider() { + @Override + public Drawable drawableProvider(int position, RecyclerView parent) { + return drawable; + } + }); + } + + public T drawableProvider(DrawableProvider provider) { + mDrawableProvider = provider; + return (T) this; + } + + public T size(final int size) { + return sizeProvider(new SizeProvider() { + @Override + public int dividerSize(int position, RecyclerView parent) { + return size; + } + }); + } + + public T sizeResId(@DimenRes int sizeId) { + return size(mResources.getDimensionPixelSize(sizeId)); + } + + public T sizeProvider(SizeProvider provider) { + mSizeProvider = provider; + return (T) this; + } + + public T visibilityProvider(VisibilityProvider provider) { + mVisibilityProvider = provider; + return (T) this; + } + + public T showLastDivider() { + mShowLastDivider = true; + return (T) this; + } + + public T positionInsideItem(boolean positionInsideItem) { + mPositionInsideItem = positionInsideItem; + return (T) this; + } + + protected void checkBuilderParams() { + if (mPaintProvider != null) { + if (mColorProvider != null) { + throw new IllegalArgumentException( + "Use setColor method of Paint class to specify line color. Do not provider ColorProvider if you set PaintProvider."); + } + if (mSizeProvider != null) { + throw new IllegalArgumentException( + "Use setStrokeWidth method of Paint class to specify line size. Do not provider SizeProvider if you set PaintProvider."); + } + } + } + } +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/HorizontalDividerItemDecoration.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/HorizontalDividerItemDecoration.java new file mode 100644 index 0000000..b7e28c1 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/HorizontalDividerItemDecoration.java @@ -0,0 +1,174 @@ +package com.p3.bewoplaner.utils.ui; + +import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.annotation.DimenRes; +import android.support.v4.view.ViewCompat; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Created by imarneanu on 2/22/17. + */ +public class HorizontalDividerItemDecoration extends FlexibleDividerDecoration { + + private MarginProvider mMarginProvider; + + protected HorizontalDividerItemDecoration(Builder builder) { + super(builder); + mMarginProvider = builder.mMarginProvider; + } + + @Override + protected Rect getDividerBound(int position, RecyclerView parent, View child) { + Rect bounds = new Rect(0, 0, 0, 0); + int transitionX = (int) ViewCompat.getTranslationX(child); + int transitionY = (int) ViewCompat.getTranslationY(child); + RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); + bounds.left = parent.getPaddingLeft() + + mMarginProvider.dividerLeftMargin(position, parent) + transitionX; + bounds.right = parent.getWidth() - parent.getPaddingRight() - + mMarginProvider.dividerRightMargin(position, parent) + transitionX; + + int dividerSize = getDividerSize(position, parent); + boolean isReverseLayout = isReverseLayout(parent); + if (mDividerType == DividerType.DRAWABLE) { + // set top and bottom position of divider + if (isReverseLayout) { + bounds.bottom = child.getTop() - params.topMargin + transitionY; + bounds.top = bounds.bottom - dividerSize; + } else { + bounds.top = child.getBottom() + params.bottomMargin + transitionY; + bounds.bottom = bounds.top + dividerSize; + } + } else { + // set center point of divider + int halfSize = dividerSize / 2; + if (isReverseLayout) { + bounds.top = child.getTop() - params.topMargin - halfSize + transitionY; + } else { + bounds.top = child.getBottom() + params.bottomMargin + halfSize + transitionY; + } + bounds.bottom = bounds.top; + } + + if (mPositionInsideItem) { + if (isReverseLayout) { + bounds.top += dividerSize; + bounds.bottom += dividerSize; + } else { + bounds.top -= dividerSize; + bounds.bottom -= dividerSize; + } + } + + return bounds; + } + + @Override + protected void setItemOffsets(Rect outRect, int position, RecyclerView parent) { + if (mPositionInsideItem) { + outRect.set(0, 0, 0, 0); + return; + } + + if (isReverseLayout(parent)) { + outRect.set(0, getDividerSize(position, parent), 0, 0); + } else { + outRect.set(0, 0, 0, getDividerSize(position, parent)); + } + } + + private int getDividerSize(int position, RecyclerView parent) { + if (mPaintProvider != null) { + return (int) mPaintProvider.dividerPaint(position, parent).getStrokeWidth(); + } else if (mSizeProvider != null) { + return mSizeProvider.dividerSize(position, parent); + } else if (mDrawableProvider != null) { + Drawable drawable = mDrawableProvider.drawableProvider(position, parent); + return drawable.getIntrinsicHeight(); + } + throw new RuntimeException("failed to get size"); + } + + /** + * Interface for controlling divider margin + */ + public interface MarginProvider { + + /** + * Returns left margin of divider. + * + * @param position Divider position (or group index for GridLayoutManager) + * @param parent RecyclerView + * @return left margin + */ + int dividerLeftMargin(int position, RecyclerView parent); + + /** + * Returns right margin of divider. + * + * @param position Divider position (or group index for GridLayoutManager) + * @param parent RecyclerView + * @return right margin + */ + int dividerRightMargin(int position, RecyclerView parent); + } + + public static class Builder extends FlexibleDividerDecoration.Builder { + + private MarginProvider mMarginProvider = new MarginProvider() { + @Override + public int dividerLeftMargin(int position, RecyclerView parent) { + return 0; + } + + @Override + public int dividerRightMargin(int position, RecyclerView parent) { + return 0; + } + }; + + public Builder(Context context) { + super(context); + } + + public Builder margin(final int leftMargin, final int rightMargin) { + return marginProvider(new MarginProvider() { + @Override + public int dividerLeftMargin(int position, RecyclerView parent) { + return leftMargin; + } + + @Override + public int dividerRightMargin(int position, RecyclerView parent) { + return rightMargin; + } + }); + } + + public Builder margin(int horizontalMargin) { + return margin(horizontalMargin, horizontalMargin); + } + + public Builder marginResId(@DimenRes int leftMarginId, @DimenRes int rightMarginId) { + return margin(mResources.getDimensionPixelSize(leftMarginId), + mResources.getDimensionPixelSize(rightMarginId)); + } + + public Builder marginResId(@DimenRes int horizontalMarginId) { + return marginResId(horizontalMarginId, horizontalMarginId); + } + + public Builder marginProvider(MarginProvider provider) { + mMarginProvider = provider; + return this; + } + + public HorizontalDividerItemDecoration build() { + checkBuilderParams(); + return new HorizontalDividerItemDecoration(this); + } + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/LayoutBehavior.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/LayoutBehavior.java new file mode 100644 index 0000000..229237a --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/LayoutBehavior.java @@ -0,0 +1,33 @@ +package com.p3.bewoplaner.utils.ui; + +import android.content.Context; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.Snackbar; +import android.util.AttributeSet; +import android.view.View; + +/** + * Custom behavior for View. + *

+ * Created by laszloffiferenc on 2/21/17. + */ +public class LayoutBehavior extends CoordinatorLayout.Behavior { + + public LayoutBehavior(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { + float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight()); + child.setTranslationY(translationY); + return true; + } + + @Override + public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { + // we only want to trigger the change + // only when the changes is from a snackbar + return dependency instanceof Snackbar.SnackbarLayout; + } +} \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/LinearLayoutBehavior.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/LinearLayoutBehavior.java new file mode 100644 index 0000000..31ff42b --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/LinearLayoutBehavior.java @@ -0,0 +1,32 @@ +package com.p3.bewoplaner.utils.ui; + +import android.content.Context; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.Snackbar; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; + +/** + * Custom behavior for LinearLayout. + *

+ * Created by Rares Teodorescu on 20/03/2017. + */ +public class LinearLayoutBehavior extends CoordinatorLayout.Behavior { + + public LinearLayoutBehavior(Context context, AttributeSet attrs) { + } + + @Override + public boolean layoutDependsOn(CoordinatorLayout parent, LinearLayout child, View dependency) { + + return dependency instanceof Snackbar.SnackbarLayout; + } + + @Override + public boolean onDependentViewChanged(CoordinatorLayout parent, LinearLayout child, View dependency) { + float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight()); + child.setTranslationY(translationY); + return true; + } +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/ViewUtils.java b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/ViewUtils.java new file mode 100644 index 0000000..0b8bc25 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/java/com/p3/bewoplaner/utils/ui/ViewUtils.java @@ -0,0 +1,53 @@ +package com.p3.bewoplaner.utils.ui; + +import com.p3.bewoplaner.R; + +import android.app.Activity; +import android.content.Context; +import android.graphics.PorterDuff; +import android.os.Build; +import android.support.v4.content.ContextCompat; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.ProgressBar; + + +/** + * Helper methods for view related classes + *

+ * Created by BoldijarPaul on 03/08/16. + */ +public abstract class ViewUtils { + + private ViewUtils() { + + } + + public static void setProgressBarColorToOrange(ProgressBar progressBar) { + setProgressBarColor(progressBar, R.color.colorDefault); + } + + public static void setProgressBarColor(ProgressBar progressBar, int color) { + if (progressBar == null) { + return; + } + Context context = progressBar.getContext(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + progressBar.getIndeterminateDrawable().mutate() + .setColorFilter(ContextCompat.getColor(context, color), PorterDuff.Mode.SRC_IN); + return; + } + progressBar.getIndeterminateDrawable().setColorFilter( + ContextCompat.getColor(context, color), PorterDuff.Mode.SRC_IN); + } + + public static void hideKeyboard(Activity activity) { + InputMethodManager imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE); + View view = activity.getCurrentFocus(); + if (view == null) { + view = new View(activity); + } + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + +} diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/avatar.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/avatar.png new file mode 100644 index 0000000..cde4724 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/avatar.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/avatar_help_group.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/avatar_help_group.png new file mode 100644 index 0000000..4645db3 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/avatar_help_group.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/avatar_list.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/avatar_list.png new file mode 100644 index 0000000..c011e5b Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/avatar_list.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/avatar_team_group.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/avatar_team_group.png new file mode 100644 index 0000000..1ef0177 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/avatar_team_group.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/chat_left.9.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/chat_left.9.png new file mode 100644 index 0000000..6790c49 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/chat_left.9.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/chat_right.9.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/chat_right.9.png new file mode 100644 index 0000000..2b3fef1 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/chat_right.9.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/checkmark.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/checkmark.png new file mode 100644 index 0000000..386cd82 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/checkmark.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/checkmark_checked.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/checkmark_checked.png new file mode 100644 index 0000000..228705a Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/checkmark_checked.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/document.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/document.png new file mode 100644 index 0000000..3765013 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/document.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/icon_notification_transparent.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/icon_notification_transparent.png new file mode 100644 index 0000000..e2b74db Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/icon_notification_transparent.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/message_delivered.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/message_delivered.png new file mode 100644 index 0000000..1cbea9d Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/message_delivered.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/message_failed.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/message_failed.png new file mode 100644 index 0000000..7170e9e Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/message_failed.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/notification_color.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/notification_color.png new file mode 100644 index 0000000..8e040a4 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/notification_color.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/splash.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/splash.png new file mode 100644 index 0000000..2b8635c Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-hdpi/splash.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-ldpi/document.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-ldpi/document.png new file mode 100644 index 0000000..b522796 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-ldpi/document.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/avatar.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/avatar.png new file mode 100644 index 0000000..3329daf Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/avatar.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/avatar_help_group.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/avatar_help_group.png new file mode 100644 index 0000000..ccb5d7a Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/avatar_help_group.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/avatar_list.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/avatar_list.png new file mode 100644 index 0000000..b5bd30e Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/avatar_list.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/avatar_team_group.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/avatar_team_group.png new file mode 100644 index 0000000..caf5dd2 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/avatar_team_group.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/chat_left.9.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/chat_left.9.png new file mode 100644 index 0000000..b98a86c Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/chat_left.9.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/chat_right.9.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/chat_right.9.png new file mode 100644 index 0000000..60b0f18 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/chat_right.9.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/checkmark.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/checkmark.png new file mode 100644 index 0000000..66a8257 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/checkmark.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/checkmark_checked.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/checkmark_checked.png new file mode 100644 index 0000000..f506339 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/checkmark_checked.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/document.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/document.png new file mode 100644 index 0000000..428e61b Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/document.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/icon_notification_transparent.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/icon_notification_transparent.png new file mode 100644 index 0000000..0f67873 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/icon_notification_transparent.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/message_delivered.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/message_delivered.png new file mode 100644 index 0000000..f572220 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/message_delivered.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/message_failed.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/message_failed.png new file mode 100644 index 0000000..f59c2ba Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/message_failed.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/notification_color.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/notification_color.png new file mode 100644 index 0000000..0b21993 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/notification_color.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/splash.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/splash.png new file mode 100644 index 0000000..584565f Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-mdpi/splash.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/avatar.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/avatar.png new file mode 100644 index 0000000..db9f52c Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/avatar.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/avatar_help_group.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/avatar_help_group.png new file mode 100644 index 0000000..d34e402 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/avatar_help_group.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/avatar_list.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/avatar_list.png new file mode 100644 index 0000000..c12dc68 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/avatar_list.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/avatar_team_group.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/avatar_team_group.png new file mode 100644 index 0000000..ac28906 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/avatar_team_group.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/chat_left.9.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/chat_left.9.png new file mode 100644 index 0000000..1ee1022 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/chat_left.9.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/chat_right.9.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/chat_right.9.png new file mode 100644 index 0000000..2a40c98 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/chat_right.9.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/checkmark.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/checkmark.png new file mode 100644 index 0000000..e3ed0c8 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/checkmark.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/checkmark_checked.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/checkmark_checked.png new file mode 100644 index 0000000..373d29e Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/checkmark_checked.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/document.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/document.png new file mode 100644 index 0000000..715df2b Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/document.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/icon_notification_transparent.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/icon_notification_transparent.png new file mode 100644 index 0000000..df5592b Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/icon_notification_transparent.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/message_delivered.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/message_delivered.png new file mode 100644 index 0000000..32a8e6d Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/message_delivered.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/message_failed.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/message_failed.png new file mode 100644 index 0000000..151592d Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/message_failed.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/notification_color.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/notification_color.png new file mode 100644 index 0000000..5e3069f Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/notification_color.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/splash.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/splash.png new file mode 100644 index 0000000..b392fca Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xhdpi/splash.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/avatar.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/avatar.png new file mode 100644 index 0000000..321ef5a Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/avatar.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/avatar_help_group.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/avatar_help_group.png new file mode 100644 index 0000000..a52507b Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/avatar_help_group.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/avatar_list.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/avatar_list.png new file mode 100644 index 0000000..5d28370 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/avatar_list.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/avatar_team_group.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/avatar_team_group.png new file mode 100644 index 0000000..723077d Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/avatar_team_group.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/chat_left.9.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/chat_left.9.png new file mode 100644 index 0000000..7cd7380 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/chat_left.9.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/chat_right.9.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/chat_right.9.png new file mode 100644 index 0000000..73b7e14 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/chat_right.9.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/checkmark.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/checkmark.png new file mode 100644 index 0000000..01a88b0 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/checkmark.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/checkmark_checked.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/checkmark_checked.png new file mode 100644 index 0000000..1b0492e Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/checkmark_checked.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/document.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/document.png new file mode 100644 index 0000000..6c70a78 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/document.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/icon_notification_transparent.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/icon_notification_transparent.png new file mode 100644 index 0000000..68371f4 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/icon_notification_transparent.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/message_delivered.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/message_delivered.png new file mode 100644 index 0000000..48fc3fc Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/message_delivered.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/message_failed.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/message_failed.png new file mode 100644 index 0000000..5d7ca3e Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/message_failed.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/notification_color.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/notification_color.png new file mode 100644 index 0000000..4765637 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/notification_color.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/splash.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/splash.png new file mode 100644 index 0000000..b6963f2 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxhdpi/splash.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/avatar.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/avatar.png new file mode 100644 index 0000000..c6f0a45 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/avatar.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/avatar_help_group.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/avatar_help_group.png new file mode 100644 index 0000000..9f1a985 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/avatar_help_group.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/avatar_list.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/avatar_list.png new file mode 100644 index 0000000..0308f31 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/avatar_list.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/avatar_team_group.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/avatar_team_group.png new file mode 100644 index 0000000..f548f12 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/avatar_team_group.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/chat_left.9.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/chat_left.9.png new file mode 100644 index 0000000..7402089 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/chat_left.9.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/chat_right.9.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/chat_right.9.png new file mode 100644 index 0000000..4e895d5 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/chat_right.9.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/checkmark.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/checkmark.png new file mode 100644 index 0000000..e7206d8 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/checkmark.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/checkmark_checked.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/checkmark_checked.png new file mode 100644 index 0000000..452a31e Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/checkmark_checked.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/document.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/document.png new file mode 100644 index 0000000..c54d63c Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/document.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/message_delivered.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/message_delivered.png new file mode 100644 index 0000000..7af6fa0 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/message_delivered.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/message_failed.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/message_failed.png new file mode 100644 index 0000000..88593ef Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/message_failed.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/splash.png b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/splash.png new file mode 100644 index 0000000..c0b4672 Binary files /dev/null and b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable-xxxhdpi/splash.png differ diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/attachment.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/attachment.xml new file mode 100644 index 0000000..d02c221 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/attachment.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/back.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/back.xml new file mode 100644 index 0000000..3f9a97d --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/back.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/btn_shape.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/btn_shape.xml new file mode 100644 index 0000000..9d43039 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/btn_shape.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/btn_shape_round.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/btn_shape_round.xml new file mode 100644 index 0000000..d2dace9 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/btn_shape_round.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/call.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/call.xml new file mode 100644 index 0000000..731b986 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/call.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/delivered.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/delivered.xml new file mode 100644 index 0000000..cf8db71 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/delivered.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/failed.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/failed.xml new file mode 100644 index 0000000..86c2480 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/failed.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/selector_checkbox.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/selector_checkbox.xml new file mode 100644 index 0000000..34193d2 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/selector_checkbox.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/send.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/send.xml new file mode 100644 index 0000000..ef8c1c4 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/send.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/send_disabled.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/send_disabled.xml new file mode 100644 index 0000000..eeee3fa --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/send_disabled.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/send_enabled.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/send_enabled.xml new file mode 100644 index 0000000..4516d5b --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/send_enabled.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/shape_round_bottom_corners.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/shape_round_bottom_corners.xml new file mode 100644 index 0000000..c2775b8 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/shape_round_bottom_corners.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/shape_round_top_corners.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/shape_round_top_corners.xml new file mode 100644 index 0000000..d5e5f5d --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/shape_round_top_corners.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/vertical_dots.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/vertical_dots.xml new file mode 100644 index 0000000..8dcaa12 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/drawable/vertical_dots.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/layout/activity_chat.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/layout/activity_chat.xml new file mode 100644 index 0000000..4b693a5 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/layout/activity_chat.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/layout/activity_chat_members.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/layout/activity_chat_members.xml new file mode 100644 index 0000000..ad702bc --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/layout/activity_chat_members.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/layout/activity_login.xml b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..cc8ec41 --- /dev/null +++ b/BeWoPlanerChat/bewoplaner-chat-android/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + +