Commit ca907495 authored by Vladislav Bogdashkin's avatar Vladislav Bogdashkin 🎣

merge actual develop

parents 0e5dd9ee 7db1a09f
...@@ -13,3 +13,10 @@ ...@@ -13,3 +13,10 @@
/captures /captures
.externalNativeBuild .externalNativeBuild
.cxx .cxx
/app/libs
/app/src/main/assets
/app/src/main/jniLibs
/app/release
...@@ -10,14 +10,28 @@ apply from: '../dependencies.gradle' ...@@ -10,14 +10,28 @@ apply from: '../dependencies.gradle'
apply plugin: 'kotlinx-serialization' apply plugin: 'kotlinx-serialization'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
//apply plugin: 'io.fabric' //apply plugin: 'io.fabric'
// Create a variable called keystorePropertiesFile, and initialize it to your
// keystore.properties file, in the rootProject folder.
// Initialize a new Properties() object called keystoreProperties.
def keystoreProperties = new Properties()
// Load your keystore.properties file into the keystoreProperties object.
android { android {
compileSdkVersion compileSdkVersion_RoomPark compileSdkVersion compileSdkVersion_RoomPark
defaultConfig { defaultConfig {
applicationId $APPLICATION_ID applicationId $APPLICATION_ID
ndk { ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86' abiFilters 'armeabi-v7a', 'arm64-v8a'
} }
minSdkVersion minSdkVersion_RoomPark minSdkVersion minSdkVersion_RoomPark
targetSdkVersion targetSdkVersion_RoomPark targetSdkVersion targetSdkVersion_RoomPark
...@@ -26,19 +40,57 @@ android { ...@@ -26,19 +40,57 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
signingConfigs {
release {
def keystorePropertiesFile = rootProject.file("../roomParkKeystore.properties")
if ( keystorePropertiesFile.exists() ){
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
// else throw new GradleException('Plesase, provide keystore properties file! ')
// You need to specify either an absolute path or include the
// keystore file in the same directory as the build.gradle file.
}
}
buildTypes { buildTypes {
debug { debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
aaptOptions.cruncherEnabled = false aaptOptions.cruncherEnabled = false
ext.alwaysUpdateBuildId = false ext.alwaysUpdateBuildId = false
} }
release { release {
minifyEnabled false signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
debuggable false debuggable false
} }
} }
aaptOptions {
noCompress '.unity3d', '.ress', '.resource', '.obb'
}
task renameBundle(type: Copy) {
from "$buildDir/outputs/bundle/release"
into "../../"
String name = findProperty('newBundleName')
rename 'app-release.aab', "${name}.aab"
}
task publishRelease(type: GradleBuild) {
tasks = ['bundleRelease']
}
androidExtensions { androidExtensions {
experimental = true experimental = true
} }
...@@ -56,6 +108,10 @@ android { ...@@ -56,6 +108,10 @@ android {
targetCompatibility 1.8 targetCompatibility 1.8
sourceCompatibility 1.8 sourceCompatibility 1.8
} }
lintOptions {
disable 'BinaryOperationInTimber'
}
} }
kapt { kapt {
...@@ -80,11 +136,6 @@ dependencies { ...@@ -80,11 +136,6 @@ dependencies {
//Logger: Timber //Logger: Timber
implementation "com.jakewharton.timber:timber:$timberVersion" implementation "com.jakewharton.timber:timber:$timberVersion"
//Crashlytics
implementation('com.crashlytics.sdk.android:crashlytics:2.10.0@aar') {
transitive = true;
}
//RxJava //RxJava
implementation "io.reactivex.rxjava3:rxjava:$rxJavaVersion" implementation "io.reactivex.rxjava3:rxjava:$rxJavaVersion"
...@@ -162,6 +213,19 @@ dependencies { ...@@ -162,6 +213,19 @@ dependencies {
implementation 'jp.wasabeef:glide-transformations:4.0.0' implementation 'jp.wasabeef:glide-transformations:4.0.0'
kapt "com.github.bumptech.glide:compiler:$glideVersion" kapt "com.github.bumptech.glide:compiler:$glideVersion"
//Firebase
implementation "com.google.firebase:firebase-analytics:$fireBaseVersion"
implementation 'com.google.firebase:firebase-messaging:20.1.3'
//CrshLytics
implementation "com.google.firebase:firebase-crashlytics:$firebaseCrashlyticsVersion"
//RxKotlin
implementation("io.reactivex.rxjava2:rxkotlin:$rxKotlinVersion")
//Arch Lifecycle
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
//Tests //Tests
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:runner:1.2.0'
......
{
"project_info": {
"project_number": "740988198336",
"firebase_url": "https://room-park-9cc38.firebaseio.com",
"project_id": "room-park-9cc38",
"storage_bucket": "room-park-9cc38.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:740988198336:android:0fd03bfcfa85b78dae3c25",
"android_client_info": {
"package_name": "com.biganto.visual.roompark"
}
},
"oauth_client": [
{
"client_id": "740988198336-e2vel467jbici47mkokmmtuhg07gfl8k.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.biganto.visual.roompark",
"certificate_hash": "f23a8c722c050ddb5e6015b9f254535ddf5387ff"
}
},
{
"client_id": "740988198336-8c08r8mikgulg2s08p0fgi5j028rhn4u.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyBvrvu2Iz2FGEf7S_DVaSwQmhuijIuW2SU"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "740988198336-8c08r8mikgulg2s08p0fgi5j028rhn4u.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}
\ No newline at end of file
...@@ -19,3 +19,202 @@ ...@@ -19,3 +19,202 @@
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
-dontwarn com.franmontiel.persistentcookiejar.**
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
-dontwarn com.google.vr.ndk.base.DaydreamAp
-keepattributes *Annotation*
-keepattributes SourceFile,LineNumberTable
# prevent Crashlytics obfuscation
-dontwarn com.crashlytics.**
-dontwarn org.kobjects.**
-dontwarn org.ksoap2.**
-dontwarn org.kxml2.**
-dontwarn org.xmlpull.v1.**
-keep class com.biganto.visual.roompark.data.repository.db.requrey.model** { *; }
-keep class com.biganto.visual.roompark.player.unity_utils** { *; }
-keepclassmembers class com.biganto.visual.roompark.player.unity_utils** { *; }
-keep class com.biganto.visual.roompark.player.BigantoPlayerActivity { *; }
-keepclassmembers class com.biganto.visual.roompark.player.BigantoPlayerActivity** { *; }
-keep public class * implements android.os.Parcelable
# keep CREATOR for referenced parcelables since it is accessed via reflection
-keepclassmembers class * implements android.os.Parcelable {
static ** CREATOR;
}
-dontobfuscate -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*,!code/allocation/variable
# could be refined, but if it works you got the idea
-keepnames class * implements android.os.Parcelable { *; }
-keepclassmembers class * implements android.os.Parcelable { *; }
-keepnames class * implements com.bluelinelabs.conductor** { *; }
-keepclassmembers class * implements com.bluelinelabs.conductor** { *; }
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
-keep class com.biganto.visual.roompark.data.repository.api.biganto.raw** { *; }
-keepclassmembers class com.biganto.visual.roompark.data.repository.api.biganto.raw** { *; }
-keep class com.biganto.visual.roompark.data.repository.api.room_park.raw** { *; }
-keepclassmembers class com.biganto.visual.roompark.data.repository.api.room_park.raw** { *; }
# prevent Requery obfuscation
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontoptimize
-dontpreverify
-dontwarn java.lang.FunctionalInterface
-dontwarn java.util.**
-dontwarn java.time.**
-dontwarn javax.annotation.**
-dontwarn javax.cache.**
-dontwarn javax.naming.**
-dontwarn javax.transaction.**
-dontwarn java.sql.**
-dontwarn javax.sql.**
-dontwarn androidx.**
-dontwarn io.requery.cache.**
-dontwarn io.requery.rx.**
-dontwarn io.requery.reactivex.**
-dontwarn io.requery.reactor.**
-dontwarn io.requery.query.**
-dontwarn io.requery.android.sqlcipher.**
-dontwarn io.requery.android.sqlitex.**
-keepclassmembers enum io.requery** {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# google vr sdk
# Don't obfuscate any NDK/SDK code. This makes the debugging of stack traces
# in release builds easier.
-keepnames class com.google.vr.ndk** { *; }
-keepnames class com.google.vr.sdk** { *; }
# These are part of the Java <-> native interfaces for GVR.
-keepclasseswithmembernames,includedescriptorclasses class com.google.vr** {
native <methods>;
}
# The SDK configuration protos use reflection.
-keep class com.google.vr.sdk.proto** {
*;
}
-keep class com.google.common.logging.Vr$VREvent$SdkConfigurationParams** {
*;
}
-keep class com.google.common.logging.nano.Vr$VREvent$SdkConfigurationParams** {
*;
}
-keep class com.google.vr.cardboard.UsedByNative
-keep @com.google.vr.cardboard.UsedByNative class *
-keepclassmembers class * {
@com.google.vr.cardboard.UsedByNative *;
}
-keep class com.google.vr.cardboard.annotations.UsedByNative
-keep @com.google.vr.cardboard.annotations.UsedByNative class *
-keepclassmembers class * {
@com.google.vr.cardboard.annotations.UsedByNative *;
}
-keep class com.google.vr.cardboard.annotations.UsedByReflection
-keep @com.google.vr.cardboard.annotations.UsedByReflection class *
-keepclassmembers class * {
@com.google.vr.cardboard.annotations.UsedByReflection *;
}
-dontwarn sun.misc.Unsafe
-dontwarn libcore.io.Memory
# end googlevr sdk
-dontusemixedcaseclassnames
-dontwarn android.support.**
-verbose
-dontoptimize
-dontpreverify
-keep class com.unity3d** { *; }
-keep class org.fmod** { *; }
-keep class bitter.jnibridge** { *; }
-keepclassmembers class com.tms.rarus.videoserver.* { *; }
-keepclassmembers class com.unity3d.player** { *; }
-keepclassmembers class org.fmod** { *; }
-keepclasseswithmembernames class * {
native <methods>;
}
-keep class com.google.firebase.crashlytics** { *; }
-dontwarn com.google.firebase.crashlytics.**
-keep class com.google.android.gms** { *; }
-dontwarn com.google.android.gms.**
-keep class com.biganto.androidplayer** { *; }
-keepclassmembers class com.biganto.androidplayer.* { *; }
-libraryjars ./libs/unity-classes.jar
#-libraryjars ./libs/libprotobuf-java-nano.jar
-keep interface kotlin.reflect.jvm.internal.impl.builtins.BuiltInsLoader
-keep class kotlin.reflect.jvm.internal.impl.serialization.deserialization.builtins.BuiltInsLoaderImpl
-keep class com.biganto.visual.roompark.presentation.screen.settings.util** { *; }
-keepclassmembers class com.biganto.visual.roompark.presentation.screen.settings.util** { *; }
-dontwarn kotlin.reflect.jvm.internal.**
-keep class kotlin** {
public protected *;
}
-keepclassmembers class * { public <init>(...); }
\ No newline at end of file
...@@ -2,19 +2,26 @@ ...@@ -2,19 +2,26 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.biganto.visual.roompark"> package="com.biganto.visual.roompark">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:name=".base.RoomParkApplication" android:name=".base.RoomParkApplication"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:screenOrientation="userPortrait"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme.Launch"> android:theme="@style/AppTheme.Launch">
<activity android:name=".base.RoomParkMainActivity" <activity
android:name=".base.RoomParkMainActivity"
android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden|adjustPan"> android:windowSoftInputMode="stateHidden|adjustPan">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
...@@ -22,6 +29,47 @@ ...@@ -22,6 +29,47 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".player.BigantoPlayerActivity" android:screenOrientation="fullSensor"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density"
android:largeHeap="true"
android:hardwareAccelerated="true"
android:process=":UnityKillsMe">
</activity>
<service
android:name=".data.RoomParkMessageService"
android:enabled="true"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
<!--
Set custom default icon. This is used when no icon is set for incoming notification messages.
See README(https://goo.gl/l4GJaQ) for more.-->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_bell_on" />
<!--
Set color used with incoming notification messages. This is used when no color is set for the incoming
notification message. See README(https://goo.gl/6BKBk7) for more.
-->
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/colorAccent" />
</service>
<service
android:name=".data.service.download.DownloadManagerService"
android:enabled="true"
android:exported="false" />
</application> </application>
</manifest> </manifest>
\ No newline at end of file
...@@ -4,10 +4,13 @@ import android.util.Log ...@@ -4,10 +4,13 @@ import android.util.Log
import com.biganto.visual.roompark.BuildConfig import com.biganto.visual.roompark.BuildConfig
import com.biganto.visual.roompark.di.dagger.AppComponent import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.DaggerAppComponent import com.biganto.visual.roompark.di.dagger.DaggerAppComponent
import com.crashlytics.android.Crashlytics
import dagger.android.AndroidInjector import dagger.android.AndroidInjector
import dagger.android.DaggerApplication import dagger.android.DaggerApplication
import io.reactivex.exceptions.UndeliverableException
import io.reactivex.plugins.RxJavaPlugins
import kotlinx.io.IOException
import timber.log.Timber import timber.log.Timber
import java.net.SocketException
/** /**
* Created by Vladislav Bogdashkin on 03.09.2019. * Created by Vladislav Bogdashkin on 03.09.2019.
...@@ -34,15 +37,46 @@ class RoomParkApplication : DaggerApplication() { ...@@ -34,15 +37,46 @@ class RoomParkApplication : DaggerApplication() {
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree()) if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
else Timber.plant(CrashlyticsTree()) else Timber.plant(CrashlyticsTree())
RxJavaPlugins.setErrorHandler { e ->
when (e) {
is UndeliverableException -> {
}
// fine, irrelevant network problem or API that throws on cancellation
is IOException -> return@setErrorHandler
is SocketException -> return@setErrorHandler
// fine, some blocking code was interrupted by a dispose call
is InterruptedException ->
return@setErrorHandler
// that's likely a bug in the application
is NullPointerException -> {
Thread.currentThread().uncaughtExceptionHandler
.uncaughtException(Thread.currentThread(), e)
return@setErrorHandler
}
is IllegalArgumentException -> {
Thread.currentThread().uncaughtExceptionHandler
.uncaughtException(Thread.currentThread(), e)
return@setErrorHandler
}
// that's a bug in RxJava or in a custom operator
is IllegalStateException -> {
Thread.currentThread().uncaughtExceptionHandler
.uncaughtException(Thread.currentThread(), e)
return@setErrorHandler
}
}
Timber.w(e, "Undeliverable exception received, not sure what to do")
}
} }
} }
private class CrashlyticsTree : Timber.Tree() { private class CrashlyticsTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, throwable: Throwable?) { override fun log(priority: Int, tag: String?, message: String, throwable: Throwable?) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) return if (priority == Log.VERBOSE || priority == Log.DEBUG) return
Crashlytics.log(priority, tag, message)
throwable?.let { Crashlytics.logException(it) }
} }
} }
\ No newline at end of file
...@@ -9,7 +9,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout ...@@ -9,7 +9,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isGone import androidx.core.view.isGone
import butterknife.BindView import butterknife.BindView
import butterknife.ButterKnife import butterknife.ButterKnife
import com.biganto.visual.roompark.BuildConfig
import com.biganto.visual.roompark.R import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.presentation.screen.splash.SplashScreenController import com.biganto.visual.roompark.presentation.screen.splash.SplashScreenController
import com.biganto.visual.roompark.util.extensions.setGone import com.biganto.visual.roompark.util.extensions.setGone
...@@ -17,12 +16,11 @@ import com.biganto.visual.roompark.util.view_utils.app_bar.DragControlAppBarLayo ...@@ -17,12 +16,11 @@ import com.biganto.visual.roompark.util.view_utils.app_bar.DragControlAppBarLayo
import com.bluelinelabs.conductor.Conductor import com.bluelinelabs.conductor.Conductor
import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.crashlytics.android.Crashlytics
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
import io.fabric.sdk.android.Fabric
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.bell_switch_view.view.*
import kotlinx.android.synthetic.main.status_layout_toolbar.view.* import kotlinx.android.synthetic.main.status_layout_toolbar.view.*
import kotlinx.android.synthetic.main.switch_toolbar.view.* import kotlinx.android.synthetic.main.switch_toolbar.view.*
import timber.log.Timber import timber.log.Timber
...@@ -53,7 +51,7 @@ class RoomParkMainActivity( ...@@ -53,7 +51,7 @@ class RoomParkMainActivity(
setTheme(R.style.AppTheme) setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (!BuildConfig.DEBUG) Fabric.with(this, Crashlytics()) // if (!BuildConfig.DEBUG) Fabric.with(this, Crashlytics())
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
...@@ -108,7 +106,7 @@ class RoomParkMainActivity( ...@@ -108,7 +106,7 @@ class RoomParkMainActivity(
headerToolbarBack.text = it.backTitle?:"" headerToolbarBack.text = it.backTitle?:""
headerToolbar.toolbar_title.text = it.title?:"" headerToolbar.toolbar_title.text = it.title?:""
headerToolbar.include13.setGone(!(it.switcher?:false)) headerToolbar.bell_container.setGone(!(it.switcher?:false))
} }
status?.let { status?.let {
statusToolbar.status_icon.setGone(it.state == null) statusToolbar.status_icon.setGone(it.state == null)
......
...@@ -102,6 +102,7 @@ abstract class BigantoBaseController<VS : BigantoBaseViewState,V: BigantoBaseCon ...@@ -102,6 +102,7 @@ abstract class BigantoBaseController<VS : BigantoBaseViewState,V: BigantoBaseCon
} }
override fun handleBack(): Boolean { override fun handleBack(): Boolean {
detachDisposable.clear()
router.popController(this) router.popController(this)
return true return true
// return super.handleBack() // return super.handleBack()
......
...@@ -30,12 +30,11 @@ abstract class BigantoBasePresenter<V : MvpView, VS> ...@@ -30,12 +30,11 @@ abstract class BigantoBasePresenter<V : MvpView, VS>
open fun parseError(t: Throwable) : VS = open fun parseError(t: Throwable) : VS =
when (t) { when (t) {
is CustomApiException ->{ is CustomApiException -> {
Timber.e("CustomApiException ${t.messageStringId} / ${t.customMessage}") Timber.e("CustomApiException ${t.messageStringId} / ${t.customMessage}")
parse(t) parse(t)
} }
is NoNetworkException -> {parse(t)}
is NoNetworkException -> parse(t)
else -> {Timber.e(t);parse(t)} else -> {Timber.e(t);parse(t)}
} }
......
...@@ -25,7 +25,7 @@ import timber.log.Timber ...@@ -25,7 +25,7 @@ import timber.log.Timber
* Created by Vladislav Bogdashkin on 09.04.2019. * Created by Vladislav Bogdashkin on 09.04.2019.
*/ */
internal const val PHOTOS_KEY = "CHHOSE_PHOTO_LIST_KEY" internal const val PHOTOS_KEY = "CHOOSE_PHOTO_LIST_KEY"
private fun formBundle(photos: ArrayList<PhotoResolutionModel>): Bundle { private fun formBundle(photos: ArrayList<PhotoResolutionModel>): Bundle {
val b = Bundle() val b = Bundle()
...@@ -66,7 +66,6 @@ class ChooseResolutionDialogController : Controller { ...@@ -66,7 +66,6 @@ class ChooseResolutionDialogController : Controller {
detachDisposable.add( detachDisposable.add(
(recyclerView.adapter as PhotoSizeAdapter).onItemClicked.subscribe { (recyclerView.adapter as PhotoSizeAdapter).onItemClicked.subscribe {
Timber.d("gonna shit : $it")
router.replaceTopController(RouterTransaction.with( router.replaceTopController(RouterTransaction.with(
PhotoDialogController( PhotoDialogController(
it.url it.url
......
package com.biganto.visual.roompark.conductor.dialogs
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.annotation.LayoutRes
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.BaseRoomParkActivity
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.ActivityModule
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import com.biganto.visual.roompark.domain.interactor.SettingsInteractor
import com.biganto.visual.roompark.domain.use_case.DownloadUseCase
import com.biganto.visual.roompark.util.view_utils.snackbar.ISnackBarProvider
import com.bluelinelabs.conductor.Controller
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.FitCenter
import com.google.android.material.textview.MaterialTextView
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import jp.wasabeef.glide.transformations.BlurTransformation
import jp.wasabeef.glide.transformations.ColorFilterTransformation
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
class DownloadPlansDialogController : Controller {
constructor():super()
constructor(args: Bundle) : super(args)
lateinit var progressBarDownload: ProgressBar
lateinit var downloaderBg: ImageView
lateinit var downloadTourTitleText: MaterialTextView
lateinit var cancelDownloadText: MaterialTextView
lateinit var downloadingContentText: MaterialTextView
override fun onContextAvailable(context: Context) {
super.onContextAvailable(context)
DaggerPlansDownloaderScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
}
private var downloadToken = DownloadUseCase.CancellationToken(false)
@Inject
lateinit var downloader: SettingsInteractor
@Inject
lateinit var rpActivity: RoomParkMainActivity
lateinit var snackbar: ISnackBarProvider
private val disposables = CompositeDisposable()
override fun onDetach(view: View) {
disposables.clear()
super.onDetach(view)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
val view = inflater.inflate(getLayoutId(), container, false)
snackbar = ActivityModule.provideSnackBar(rpActivity)
progressBarDownload = view.findViewById(R.id.downloadProgress)
downloaderBg = view.findViewById(R.id.backgroundDownloader)
cancelDownloadText = view.findViewById(R.id.cancelDownloadButton)
downloadTourTitleText = view.findViewById(R.id.tourToDownloadTitle)
downloadingContentText = view.findViewById(R.id.downloadingTitle)
downloadingContentText.text = resources?.getString(R.string.download_plan_types_screen_title)
// progress.visibility = View.VISIBLE
downloadToken = DownloadUseCase.CancellationToken(false)
downloadTourTitleText.text = ""
Glide.with(view)
.load(R.drawable.flat_placeholder)
.transform(BlurTransformation(13, 4)
,ColorFilterTransformation(0x99000000.toInt())
,FitCenter())
.into(downloaderBg)
disposables.add(
downloader.downloadTourPlans(downloadToken)
// .onErrorReturn { parseError(it);TourPreviewEntity() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe (
{ model ->
downloadTourTitleText.text = "${model.currentProgress}/${model.totalProgress}"
updateProgressBar(model.currentProgress, model.totalProgress)
if (model.currentProgress == model.totalProgress){
snackbar.showSnackBar(R.string.plan_types_download_completed)
handleBack()
}
}
,{error ->
Timber.e(error)
snackbar.showSnackBar(error.localizedMessage)
handleBack()
}
))
cancelDownloadText.setOnClickListener {handleBack() }
// downloadTour(it.tour.tour_id, downloadToken)
// view.findViewById<View>(R.id.close_current_button).setOnClickListener { handleBack() }
return view
}
private fun updateProgressBar(curr:Int, max:Int){
activity?.runOnUiThread {
progressBarDownload.max = max
progressBarDownload.progress = curr
}
}
@LayoutRes
fun getLayoutId() = R.layout.download_tour_layout
override fun handleBack(): Boolean {
downloadToken.isCancelled = true
return router.popController(this)
}
}
@PerScreen
@Component(
modules = [PlansDownloaderScreenModule::class],
dependencies = [AppComponent::class])
interface PlansDownloaderScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): PlansDownloaderScreenComponent
}
//
// val presenter: ArticlesScreenPresenter
fun inject(controller: DownloadPlansDialogController)
}
@Module
abstract class PlansDownloaderScreenModule{
@PerScreen
@Binds
abstract fun provideActivity(activity: RoomParkMainActivity): BaseRoomParkActivity
}
package com.biganto.visual.roompark.conductor.dialogs package com.biganto.visual.roompark.conductor.dialogs
import android.annotation.SuppressLint
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ProgressBar import android.widget.ProgressBar
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.recyclerview.widget.RecyclerView
import com.biganto.visual.roompark.R import com.biganto.visual.roompark.R
import com.bluelinelabs.conductor.Controller import com.bluelinelabs.conductor.Controller
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
...@@ -20,6 +21,7 @@ import com.bumptech.glide.request.RequestListener ...@@ -20,6 +21,7 @@ import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.github.chrisbanes.photoview.PhotoView import com.github.chrisbanes.photoview.PhotoView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import timber.log.Timber
/** /**
...@@ -35,15 +37,14 @@ class PhotoDialogController : Controller { ...@@ -35,15 +37,14 @@ class PhotoDialogController : Controller {
constructor(photoUrl: String) : super(bundleOf(PHOTO_URL_KEY to photoUrl)) constructor(photoUrl: String) : super(bundleOf(PHOTO_URL_KEY to photoUrl))
lateinit var recyclerView : RecyclerView
@SuppressLint("SourceLockedOrientationActivity")
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
val view = inflater.inflate(getLayoutId(), container, false) val view = inflater.inflate(getLayoutId(), container, false)
val progress = view.findViewById<ProgressBar>(R.id.photo_load_progress_bar) val progress = view.findViewById<ProgressBar>(R.id.photo_load_progress_bar)
progress.visibility = View.VISIBLE progress.visibility = View.VISIBLE
view.findViewById<View>(R.id.close_current_button).setOnClickListener { handleBack() }
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
...@@ -79,12 +80,20 @@ class PhotoDialogController : Controller { ...@@ -79,12 +80,20 @@ class PhotoDialogController : Controller {
}) })
.into(photoView) .into(photoView)
} }
view.findViewById<ImageView>(R.id.close_current_button).setOnClickListener {
Timber.d("Clicked")
handleBack()
}
return view return view
} }
@LayoutRes @LayoutRes
fun getLayoutId() = R.layout.photo_viewer fun getLayoutId() = R.layout.photo_viewer
@SuppressLint("SourceLockedOrientationActivity")
override fun handleBack(): Boolean { override fun handleBack(): Boolean {
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
return router.popCurrentController() return router.popCurrentController()
......
package com.biganto.visual.roompark.conductor.dialogs
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import androidx.annotation.LayoutRes
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.BaseRoomParkActivity
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.data.service.network.INetworkMonitor
import com.biganto.visual.roompark.di.dagger.ActivityModule
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import com.biganto.visual.roompark.domain.interactor.SettingsInteractor
import com.biganto.visual.roompark.domain.model.bytesToSize
import com.biganto.visual.roompark.util.view_utils.snackbar.ISnackBarProvider
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.view.clicks
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
private const val TYPICAL_PLAN_SIZE = (1.2f*1024L*1024L).toLong()
class StartPlansDownloadingDialogController : Controller {
constructor() : super()
constructor(args: Bundle) : super(args)
private val detachDisposable = CompositeDisposable()
override fun onDetach(view: View) {
detachDisposable.clear()
super.onDetach(view)
}
@Inject
lateinit var interactor: SettingsInteractor
@Inject
lateinit var file: FileModule
lateinit var snackbar: ISnackBarProvider
@Inject
lateinit var rpActivity: RoomParkMainActivity
@Inject
lateinit var networkMonitor: INetworkMonitor
override fun onContextAvailable(context: Context) {
super.onContextAvailable(context)
DaggerStartPlansDownloadingDialogScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
val view = inflater.inflate(getLayoutId(), container, false)
snackbar = ActivityModule.provideSnackBar(rpActivity)
view.findViewById<MaterialTextView>(R.id.download_dialog_title)
.text = resources?.getString(R.string.download_all_plans_notice_text)
view.findViewById<MaterialTextView>(R.id.all_tours_size_textView)
.text = resources?.getString(R.string.download_all_plans_size_title
,"0")
file.freeSpace.bytesToSize().let {
view.findViewById<MaterialTextView>(R.id.disk_size_textView)
.text = resources?.getString(R.string.download_all_tours_disk_size_title,it)
}
view.findViewById<Button>(R.id.alert_dismiss_button)
.setOnClickListener { handleBack() }
view.findViewById<ImageView>(R.id.close_current_button)
.setOnClickListener { handleBack() }
detachDisposable.add(
interactor.fetchPlanTypesSizes()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe ({
view.findViewById<MaterialTextView>(R.id.all_tours_size_textView)
.text = resources?.getString(R.string.download_all_plans_size_title
,(it* TYPICAL_PLAN_SIZE).bytesToSize())
},
{err ->
Timber.e(err)
snackbar.showSnackBar(R.string.download_all_plans_errors_snackbar_message)
})
)
detachDisposable.add(
view.findViewById<Button>(R.id.alert_ok_button)
.clicks()
.filter{
if (!networkMonitor.isConnected){
snackbar.showSnackBar(R.string.no_network_error)
}
networkMonitor.isConnected
}
.debounce(200, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
router.replaceTopController(
RouterTransaction.with(DownloadPlansDialogController())
.pushChangeHandler(DialogChangeHandler())
.popChangeHandler(DialogChangeHandler())
)
}
)
view.findViewById<View>(R.id.close_current_button).setOnClickListener { handleBack() }
// view.setOnClickListener { handleBack() }
return view
}
@LayoutRes
fun getLayoutId() = R.layout.tours_download_dialog_screen
override fun handleBack(): Boolean {
return router.popCurrentController()
}
}
@PerScreen
@Component(
modules = [StartPlansDownloadingDialogScreenModule::class],
dependencies = [AppComponent::class])
interface StartPlansDownloadingDialogScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): StartPlansDownloadingDialogScreenComponent
}
fun inject(controller: StartPlansDownloadingDialogController)
}
@Module
abstract class StartPlansDownloadingDialogScreenModule{
@PerScreen
@Binds
abstract fun provideActivity(activity: RoomParkMainActivity): BaseRoomParkActivity
}
package com.biganto.visual.roompark.conductor.dialogs.tour_chooser
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.annotation.LayoutRes
import androidx.core.os.bundleOf
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.IBottomNavigation
import com.biganto.visual.roompark.base.ICollapsingToolBar
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.ActivityModule
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import com.biganto.visual.roompark.domain.interactor.ToursInteractor
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.domain.model.fromEntity
import com.biganto.visual.roompark.domain.use_case.DownloadUseCase
import com.biganto.visual.roompark.util.view_utils.snackbar.ISnackBarProvider
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import com.bluelinelabs.conductor.Controller
import com.bumptech.glide.Glide
import com.google.android.material.textview.MaterialTextView
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import jp.wasabeef.glide.transformations.BlurTransformation
import jp.wasabeef.glide.transformations.ColorFilterTransformation
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
internal const val DOWNLOAD_TOUR_ID_KEY = "DOWNLOAD_TOUR_ID_KEY"
class DownloadTourDialogController : Controller {
constructor(args: Bundle) : super(args)
constructor(tourArg: TourModel) : super(bundleOf(DOWNLOAD_TOUR_ID_KEY to tourArg))
lateinit var progressBarDownload: ProgressBar
lateinit var downloaderBg: ImageView
lateinit var downloadTourTitleText: MaterialTextView
lateinit var cancelDownloadText: MaterialTextView
override fun onContextAvailable(context: Context) {
super.onContextAvailable(context)
DaggerDownloaderScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
}
private val tour:TourModel by lazy {
args.getParcelable<TourModel>(DOWNLOAD_TOUR_ID_KEY)
}
private var downloadToken = DownloadUseCase.CancellationToken(false)
@Inject
lateinit var downloader: ToursInteractor
@Inject
lateinit var rpActivity: RoomParkMainActivity
lateinit var snackbar: ISnackBarProvider
private val disposables = CompositeDisposable()
override fun onDetach(view: View) {
disposables.clear()
super.onDetach(view)
}
private val bottomNavigationController: IBottomNavigation by lazy {rpActivity}
private val toolbar: ICollapsingToolBar by lazy {rpActivity}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
val view = inflater.inflate(getLayoutId(), container, false)
snackbar = ActivityModule.provideSnackBar(rpActivity)
progressBarDownload = view.findViewById(R.id.downloadProgress)
downloaderBg = view.findViewById(R.id.backgroundDownloader)
cancelDownloadText = view.findViewById(R.id.cancelDownloadButton)
downloadTourTitleText = view.findViewById(R.id.tourToDownloadTitle)
// progress.visibility = View.VISIBLE
downloadToken = DownloadUseCase.CancellationToken(false)
downloadTourTitleText.text = tour.title
Glide.with(view)
.load(tour.previewUrl)
.transform(BlurTransformation(13, 8)
,ColorFilterTransformation(0x99000000.toInt()))
.into(downloaderBg)
disposables.add(
downloader.downloadTour(tour.tour_id, downloadToken)
.onErrorReturn { Timber.e(it);TourPreviewEntity() }
// .onErrorReturn { parseError(it);TourPreviewEntity() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe (
{ model ->
updateProgressBar(model.downloadedFiles, model.overallFiles)
if (model.overallFiles == model.downloadedFiles)
activity?.let{
startPlayer(it, fromEntity(model))
handleBack()
}
}
,{error ->
Timber.e(error)
snackbar.showSnackBar(error.localizedMessage)
}
))
cancelDownloadText.setOnClickListener {handleBack() }
// downloadTour(it.tour.tour_id, downloadToken)
// view.findViewById<View>(R.id.close_current_button).setOnClickListener { handleBack() }
return view
}
private fun updateProgressBar(curr:Int, max:Int){
activity?.runOnUiThread {
progressBarDownload.max = max
progressBarDownload.progress = curr
}
}
@LayoutRes
fun getLayoutId() = R.layout.download_tour_layout
override fun handleBack(): Boolean {
downloadToken.isCancelled = true
return router.popController(this)
}
}
@PerScreen
@Component(
modules = [DownloaderScreenModule::class],
dependencies = [AppComponent::class])
interface DownloaderScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): DownloaderScreenComponent
}
//
// val presenter: ArticlesScreenPresenter
fun inject(controller: DownloadTourDialogController)
}
@Module
abstract class DownloaderScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
}
package com.biganto.visual.roompark.conductor.dialogs.tour_chooser
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import androidx.annotation.LayoutRes
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.BaseRoomParkActivity
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.data.service.network.INetworkMonitor
import com.biganto.visual.roompark.data.service.network.NoNetworkException
import com.biganto.visual.roompark.di.dagger.ActivityModule
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import com.biganto.visual.roompark.domain.interactor.SettingsInteractor
import com.biganto.visual.roompark.domain.model.bytesToSize
import com.biganto.visual.roompark.util.view_utils.snackbar.ISnackBarProvider
import com.bluelinelabs.conductor.Controller
import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.view.clicks
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
class StartToursDownloadingDialogController : Controller {
constructor():super()
constructor(args: Bundle):super(args)
private val detachDisposable = CompositeDisposable()
override fun onDetach(view: View) {
detachDisposable.clear()
super.onDetach(view)
}
@Inject
lateinit var downloader: SettingsInteractor
@Inject
lateinit var file: FileModule
lateinit var snackbar: ISnackBarProvider
@Inject
lateinit var rpActivity: RoomParkMainActivity
@Inject
lateinit var networkMonitor: INetworkMonitor
override fun onContextAvailable(context: Context) {
super.onContextAvailable(context)
DaggerStartToursDownloadingDialogScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
val view = inflater.inflate(getLayoutId(), container, false)
view.findViewById<MaterialTextView>(R.id.download_dialog_title)
.text = resources?.getString(R.string.download_all_tours_notice_text)
view.findViewById<MaterialTextView>(R.id.all_tours_size_textView)
.text = resources?.getString(R.string.download_all_tours_size_title
,"0")
snackbar = ActivityModule.provideSnackBar(rpActivity)
file.freeSpace.bytesToSize().let {
view.findViewById<MaterialTextView>(R.id.disk_size_textView)
.text = resources?.getString(R.string.download_all_tours_disk_size_title,it)
}
view.findViewById<Button>(R.id.alert_dismiss_button)
.setOnClickListener { handleBack() }
view.findViewById<ImageView>(R.id.close_current_button)
.setOnClickListener { handleBack() }
detachDisposable.add(
downloader.fetchToursSizes()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
view.findViewById<MaterialTextView>(R.id.all_tours_size_textView)
.text = resources?.getString(R.string.download_all_tours_size_title
,it.bytesToSize())
}
)
detachDisposable.add(
view.findViewById<Button>(R.id.alert_ok_button)
.clicks()
.filter{
if (!networkMonitor.isConnected){
snackbar.showSnackBar(R.string.no_network_error)
}
networkMonitor.isConnected
}
.debounce(200, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.io())
.flatMap{ downloader.startToursDownloading()}
.observeOn(AndroidSchedulers.mainThread())
.subscribe ({
snackbar.showSnackBar(R.string.download_all_tours_start_snackbar_message)
handleBack()
},{
Timber.e(it)
snackbar.showSnackBar(R.string.download_all_tours_errors_snackbar_message)
handleBack()
}
)
)
view.findViewById<View>(R.id.close_current_button).setOnClickListener { handleBack() }
// view.setOnClickListener { handleBack() }
return view
}
@LayoutRes
fun getLayoutId() = R.layout.tours_download_dialog_screen
override fun handleBack(): Boolean {
return router.popCurrentController()
}
}
@PerScreen
@Component(
modules = [StartToursDownloadingDialogScreenModule::class],
dependencies = [AppComponent::class])
interface StartToursDownloadingDialogScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): StartToursDownloadingDialogScreenComponent
}
fun inject(controller: StartToursDownloadingDialogController)
}
@Module
abstract class StartToursDownloadingDialogScreenModule{
@PerScreen
@Binds
abstract fun provideActivity(activity: RoomParkMainActivity): BaseRoomParkActivity
}
package com.biganto.visual.roompark.conductor.dialogs.tour_chooser
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyclerAdapter
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder
import com.biganto.visual.roompark.util.extensions.setGone
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.RouterTransaction
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.textview.MaterialTextView
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
internal const val TOUR_MODEL_LIST = "CHOOSE_TOUR_LIST_KEY"
private fun formBundle(photos: ArrayList<TourModel>): Bundle {
val b = Bundle()
b.putParcelableArrayList(TOUR_MODEL_LIST,photos)
return b
}
class ChooseTourDialogController : Controller {
constructor(args: Bundle) : super(args)
constructor(items: ArrayList<TourModel>) : super(formBundle(items))
private lateinit var recyclerView : RecyclerView
private val detachDisposable = CompositeDisposable()
override fun onDetach(view: View) {
detachDisposable.clear()
super.onDetach(view)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
val view = inflater.inflate(getLayoutId(), container, false)
recyclerView = view.findViewById(R.id.toursList)
recyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
recyclerView.adapter = TourChooserAdapter()
recyclerView.itemAnimator = null
recyclerView.addItemDecoration(
DividerItemDecoration(activity,DividerItemDecoration.VERTICAL)
)
args.getParcelableArrayList<TourModel>(TOUR_MODEL_LIST)?.let {
(recyclerView.adapter as TourChooserAdapter).setItems(it)
}
detachDisposable.add(
(recyclerView.adapter as TourChooserAdapter)
.onItemClicked
.debounce(300L,TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(::onTourClicked)
)
// view.findViewById<View>(R.id.close_current_button).setOnClickListener { handleBack() }
view.setOnClickListener { handleBack() }
return view
}
private fun onTourClicked(tour: TourModel) {
when (tour.downloadState) {
DownloadState.Downloaded -> {
activity?.let { startPlayer(it, tour) }
handleBack()
}
else -> {
router.replaceTopController(
RouterTransaction.with(DownloadTourDialogController(tour))
.popChangeHandler(DialogChangeHandler())
.pushChangeHandler(DialogChangeHandler())
)
}
}
}
@LayoutRes
fun getLayoutId() = R.layout.tours_chooser_screen
override fun handleBack(): Boolean {
return router.popCurrentController()
}
}
internal class TourChooserAdapter:CommonRecyclerAdapter<TourChooserViewHolder,TourModel>(){
override val vhKlazz = TourChooserViewHolder::class
override fun getVhLayout(): Int = R.layout.tour_chooser_viewholder
}
internal class TourChooserViewHolder(itemView: View) :CommonViewHolder<TourModel>(itemView){
@BindView(R.id.tour_preview_imageView)
lateinit var tourPreview : ImageView
@BindView(R.id.tour_name)
lateinit var tourName : MaterialTextView
@BindView(R.id.tour_status_imageView)
lateinit var tourStatus : ImageView
@Inject
lateinit var activity:Context
override fun onViewBound(model: TourModel) {
tourName.text = model.title
Glide.with(tourPreview)
.load(model.previewUrl)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(tourPreview)
tourStatus.setGone(model.downloadState == DownloadState.Downloaded)
tourStatus.setImageDrawable(
itemView.context.resources.getDrawable(
when(model.downloadState){
DownloadState.Downloaded -> R.drawable.ic_download
DownloadState.Suspended -> R.drawable.ic_download
DownloadState.DownloadQueue -> R.drawable.ic_download
else -> R.drawable.ic_download
}
,null)
)
}
}
package com.biganto.visual.roompark.conductor.dialogs.tour_chooser
import android.content.Context
import android.content.Intent
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.player.BigantoPlayerActivity
import com.biganto.visual.roompark.player.unity_utils.LoadTourConfig
import java.io.File
/**
* Created by Vladislav Bogdashkin on 07.04.2020.
*/
const val PLAY_TOUR_DATA_EXTRAS="BIGANTO_PLAYER_TOUR_DATA"
fun startPlayer(context:Context,tour: TourModel)
{
// hideToursList()
val playerIntent= Intent(context, BigantoPlayerActivity::class.java)
val tourConfig = LoadTourConfig(
tour.tour_id.toInt(),
tour.targetResolution,
FileModule.assetsDirectory(context).plus(File.separator).plus(tour.footageUri),
tour.metaPredict,
tour.previewUrl,
context.resources?.getBoolean(R.bool.isTablet)?.not()?:true,
true
)
playerIntent.putExtra(PLAY_TOUR_DATA_EXTRAS,tourConfig)
context.startActivity(playerIntent)
}
\ No newline at end of file
...@@ -60,7 +60,7 @@ class BigantoMviConductorLifecycleListener<V : MvpView, P : MviPresenter<V, *>> ...@@ -60,7 +60,7 @@ class BigantoMviConductorLifecycleListener<V : MvpView, P : MviPresenter<V, *>>
} }
val viewMpv = callback?.mvpView ?: throw NullPointerException( val viewMpv = callback?.mvpView ?: throw NullPointerException(
"MvpView returned from getMvpView() is null. Returned by " + controller.activity!!) "MvpView returned from getMvpView() is null. Returned by ${controller.activity}")
if (viewStateWillBeRestored) { if (viewStateWillBeRestored) {
callback!!.setRestoringViewState(true) callback!!.setRestoringViewState(true)
......
package com.biganto.visual.roompark.data
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.data.service.notification.INotificationCenter
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import timber.log.Timber
import javax.inject.Inject
class RoomParkMessageService : FirebaseMessagingService() {
@Inject
lateinit var notyCenter: INotificationCenter
init {
notyCenter = RoomParkApplication.component.provideNotifivations()
}
override fun onNewToken(p0: String) {
super.onNewToken(p0)
Timber.d("NEW TOKEN REGISTERED: ${p0}")
}
override fun onMessageReceived(remoteMessage: RemoteMessage) { // ...
// TODO(developer): Handle FCM messages here.
// Not getting messages here? See why this may be: https://goo.gl/39bRNJ
Timber.d("From: ${remoteMessage.from}")
// Check if message contains a data payload.
if (remoteMessage.data.size > 0) {
Timber.d("Message data payload: %s", remoteMessage.data)
if ( /* Check if data needs to be processed by long running job */true) { // For long-running tasks (10 seconds or more) use Firebase Job Dispatcher.
// scheduleJob()
} else { // Handle message within 10 seconds
// handleNow()
}
}
// Check if message contains a notification payload.
if (remoteMessage.notification != null) {
Timber.d("Message Notification Body: %s", remoteMessage.notification?.body)
notyCenter.showPushNotifyMessage(remoteMessage.notification?.body?:"Уведомление")
}
// Also if you intend on generating your own notifications as a result of a received FCM
// message, here is where that should be initiated. See sendNotification method below.
}
}
\ No newline at end of file
package com.biganto.visual.roompark.data.data_provider package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.db.IDb import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.ImageAlbumJunctionEntity import com.biganto.visual.roompark.data.repository.db.requrey.model.ImageAlbumJunctionEntity
import com.biganto.visual.roompark.data.repository.mapper.fromRaw import com.biganto.visual.roompark.data.repository.mapper.fromRaw
...@@ -73,7 +73,9 @@ class AlbumsContractModule @Inject constructor( ...@@ -73,7 +73,9 @@ class AlbumsContractModule @Inject constructor(
arrayListOf(fetchTopLevelAlbumsDb,fetchTopLevelAlbumsApi) arrayListOf(fetchTopLevelAlbumsDb,fetchTopLevelAlbumsApi)
) )
.doOnNext { Timber.d("got entity $it") } .doOnNext { Timber.d("got entity $it") }
.map { fromEntity(it,::fromEntity) } .map { fromEntity(it,::fromEntity).sortedByDescending{ album -> album.published } }
//endregion allAlbums //endregion allAlbums
//region concrete Albums //region concrete Albums
......
...@@ -2,11 +2,15 @@ package com.biganto.visual.roompark.data.data_provider ...@@ -2,11 +2,15 @@ package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.androidplayer.data.repository.local.ILocalStore import com.biganto.visual.androidplayer.data.repository.local.ILocalStore
import com.biganto.visual.roompark.data.local.UserState import com.biganto.visual.roompark.data.local.UserState
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.db.IDb import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.SubscriptionEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import com.biganto.visual.roompark.data.repository.mapper.fromRaw import com.biganto.visual.roompark.data.repository.mapper.fromRaw
import com.biganto.visual.roompark.domain.contract.AuthContract import com.biganto.visual.roompark.domain.contract.AuthContract
import com.biganto.visual.roompark.domain.custom_exception.CustomApiException
import com.biganto.visual.roompark.domain.model.AuthInfoModel import com.biganto.visual.roompark.domain.model.AuthInfoModel
import com.biganto.visual.roompark.domain.model.SubscriptionTopic
import com.biganto.visual.roompark.domain.model.fromEntity import com.biganto.visual.roompark.domain.model.fromEntity
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Observable import io.reactivex.Observable
...@@ -35,6 +39,19 @@ class AuthContractModule @Inject constructor( ...@@ -35,6 +39,19 @@ class AuthContractModule @Inject constructor(
api.authenticate(email,password) api.authenticate(email,password)
.map ( ::fromRaw ) .map ( ::fromRaw )
.flatMap{ db.upsertUser(it) } .flatMap{ db.upsertUser(it) }
.flatMap {user ->
val userSubs = user.subscriptions?.map { it as SubscriptionEntity }
var newSubs = defaultSubsList(user)
if (!userSubs.isNullOrEmpty()){
newSubs = newSubs.filter {defSub ->
!userSubs.map { it.topic }.contains(defSub.topic)
}.toList()
}
if (newSubs.isNullOrEmpty()) return@flatMap Observable.just(user)
return@flatMap db.upsert(newSubs)
.toObservable()
.flatMap { db.refreshUser(user) }
}
.doOnNext{ Timber.d("user id: ${it.uuid}")} .doOnNext{ Timber.d("user id: ${it.uuid}")}
.doOnNext { local.setRecentUser(it.uuid.toString()).blockingAwait() } .doOnNext { local.setRecentUser(it.uuid.toString()).blockingAwait() }
.map(::fromEntity) .map(::fromEntity)
...@@ -46,4 +63,53 @@ class AuthContractModule @Inject constructor( ...@@ -46,4 +63,53 @@ class AuthContractModule @Inject constructor(
else ->false else ->false
} } } }
override fun currentUser(): Observable<UserEntity> =
local.recentUser()
.doOnNext { Timber.w("recentUSer: $it") }
.flatMap{ when(it){
is UserState.NotAuthenticated -> throw CustomApiException.NotAuthorizedException()
is UserState.Authenticated -> db.fetchUser(it.uuid.toInt())
.doOnError { Timber.e(it) }
.doOnNext {u-> "got usr:$u" }
else -> throw CustomApiException.NotAuthorizedException()
} }
} }
private fun defaultSubsList(owner:UserEntity) : List<SubscriptionEntity> = arrayListOf(
SubscriptionEntity().apply {
setTopic(SubscriptionTopic.Progress_1().topicName)
setOwner(owner)
setState(false)
}
,SubscriptionEntity().apply {
setTopic(SubscriptionTopic.Progress_2().topicName)
setOwner(owner)
setState(false)
}
,SubscriptionEntity().apply {
setTopic(SubscriptionTopic.Progress_3().topicName)
setOwner(owner)
setState(false)
}
,SubscriptionEntity().apply {
setTopic(SubscriptionTopic.Progress_kindergarden().topicName)
setOwner(owner)
setState(false)
}
,SubscriptionEntity().apply {
setTopic(SubscriptionTopic.News().topicName)
setOwner(owner)
setState(false)
}
,SubscriptionEntity().apply {
setTopic(SubscriptionTopic.Blog().topicName)
setOwner(owner)
setState(false)
}
,SubscriptionEntity().apply {
setTopic(SubscriptionTopic.Construction().topicName)
setOwner(owner)
setState(false)
}
)
\ No newline at end of file
package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.roompark.domain.contract.DeviceUtilsContract
import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.iid.FirebaseInstanceId
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 26.03.2020.
*/
class DeviceUtilsRepository @Inject constructor(
) : DeviceUtilsContract {
override fun getDeviceId(): Observable<String> =
Observable.create<String> {emitter ->
Timber.d("Creat observer")
FirebaseInstanceId.getInstance().instanceId
.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
Timber.w(task.exception, "getInstanceId failed")
emitter.onError(task.exception!!)
return@OnCompleteListener
}
// Get new Instance ID token
task.result?.token?.let { emitter.onNext(it) }
emitter.onComplete()
// Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
})
}.subscribeOn(Schedulers.computation())
}
\ No newline at end of file
...@@ -2,22 +2,20 @@ package com.biganto.visual.roompark.data.data_provider ...@@ -2,22 +2,20 @@ package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.androidplayer.data.repository.local.ILocalStore import com.biganto.visual.androidplayer.data.repository.local.ILocalStore
import com.biganto.visual.roompark.data.local.UserState import com.biganto.visual.roompark.data.local.UserState
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.db.IDb import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.DealEntity import com.biganto.visual.roompark.data.repository.db.requrey.model.DealEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.EstateEntity import com.biganto.visual.roompark.data.repository.db.requrey.model.EstateEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.PlanPresetEntity import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.data.repository.mapper.fromRaw import com.biganto.visual.roompark.data.repository.mapper.fromRaw
import com.biganto.visual.roompark.data.repository.mapper.fromRawList import com.biganto.visual.roompark.data.repository.mapper.fromRawList
import com.biganto.visual.roompark.domain.contract.DealContract import com.biganto.visual.roompark.domain.contract.DealContract
import com.biganto.visual.roompark.domain.custom_exception.CustomApiException import com.biganto.visual.roompark.domain.custom_exception.CustomApiException
import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.EstateModel import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.PlanPresetModel
import com.biganto.visual.roompark.domain.model.fromEntity import com.biganto.visual.roompark.domain.model.fromEntity
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.rxkotlin.Observables
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
...@@ -27,14 +25,11 @@ import javax.inject.Inject ...@@ -27,14 +25,11 @@ import javax.inject.Inject
*/ */
// //
const val TEST_DEAL_TOKEN =
"183|iauhqAKpzrex9g1JYFYcp4443_PU5tm8ioce8G-QhtQbjNP-_a8CnW0WBw8O7dj4Rm2xdRGTuqvkfAF2zyMUSg=="
class EstateRepository @Inject constructor( class EstateRepository @Inject constructor(
private val local: ILocalStore, local: ILocalStore,
private val api: IRoomParkApi, private val api: IRoomParkApi,
private val db: IDb, private val db: IDb
private val file: FileModule
): DealContract { ): DealContract {
...@@ -42,50 +37,32 @@ class EstateRepository @Inject constructor( ...@@ -42,50 +37,32 @@ class EstateRepository @Inject constructor(
Timber.d("Estate Repository Created $this") Timber.d("Estate Repository Created $this")
} }
private val getFavoritesApi: Observable<List<EstateEntity>> = override fun fetchFavorites(user:UserEntity): Observable<List<EstateEntity>> =
local.recentUser() Observables.zip(
.flatMap { api.getFavorites(user.authToken)
when (it) { .doOnError(Timber::e)
is UserState.Authenticated -> db.fetchUser(it.uuid.toInt()) .map { fromRawList(it, ::fromRaw) }
else -> throw CustomApiException.NotAuthorizedException() ,db.getUserFavorites(user.uuid)
} .toList().toObservable()
} .doOnError(Timber::e)
.flatMap { user -> ){apiList,dbList ->
api.getFavorites(user.authToken) apiList.forEach { estate ->
.doOnError(Timber::e) estate.setFavorite(true)
.map { fromRawList(it, ::fromRaw) } estate.user = user
.doOnNext {
it.forEach { estate ->
estate.setFavorite(true)
estate.user = user
}
}
.doOnNext(db::blockingUpsert)
}
private val getFavoritesDb: Observable<List<EstateEntity>> =
local.recentUser()
.flatMap {
when (it) {
is UserState.Authenticated -> db.fetchUser(it.uuid.toInt()).take(1)
else -> throw CustomApiException.NotAuthorizedException()
}
}
.flatMap {
db.getUserFavorites(it.uuid)
.doOnError(Timber::e)
.toList().toObservable()
} }
dbList
.filterNotNull()
.filter { dbFav ->!apiList.map{it.id}.contains(dbFav.id) }
.let{ db.deleteEstate(it) }
return@zip apiList
}
.flatMap(db::upsertEstates)
.map { it.toList() }
.doOnNext{ db.refreshUser(user) }
override fun getFavorites(): Observable<List<EstateModel>> { override fun fetchDeals(user:UserEntity): Observable<List<DealEntity>> =
return Observable.mergeDelayError( getDealsApi(user)
arrayListOf(
getFavoritesApi, getFavoritesDb
)
).map { fromEntity(it, ::fromEntity) }
.doOnError(Timber::e)
}
private fun fetchEstateDb(id: Int) = db.getEstate(id) private fun fetchEstateDb(id: Int) = db.getEstate(id)
...@@ -95,128 +72,30 @@ class EstateRepository @Inject constructor( ...@@ -95,128 +72,30 @@ class EstateRepository @Inject constructor(
} }
private fun getPlanTypesApi(estateId: Int): Observable<List<PlanPresetEntity>> =
api.getEstatePlanTypes(estateId)
.doOnNext { Timber.d("raw0 $it") }
.map { fromRawList(it, ::fromRaw) }
.map {
it.onEach { plan ->
val e = EstateEntity()
e.setId(estateId)
plan.estateId = e
}.toList()
}
.doOnNext(db::blockingUpsert)
.subscribeOn(Schedulers.io())
override fun getPlanTypes(estateId: Int): Observable<List<PlanPresetModel>> =
Observable.mergeDelayError(
arrayListOf(getPlanTypesApi(estateId))
).map { l -> List(l.size) { fromEntity(l[it]) } }
private fun getPlanApi(estateId: Int
, planId:Int
, furniture:Boolean? = null
, sizes:Boolean? = null
, walls:Boolean? = null
, electric:Boolean? = null) =
api.getDirectPlan(estateId, planId,
furniture?:false
,sizes?:false
,walls?:false
,electric?:false)
.map{
val sFile = getPlanFile(
estateId = estateId,
planId = planId,
furniture = furniture,
walls = walls,
sizes = sizes,
electric = electric)
file.saveFileToDisk(sFile,it)
sFile.path
}
.subscribeOn(Schedulers.io())
private fun getPlanFile(estateId: Int
, planId:Int
, furniture:Boolean? = null
, sizes:Boolean? = null
, walls:Boolean? = null
, electric:Boolean? = null)
= FileModule.getDirectory(file.context
,FileModule.FileDirectory.PlanTypeDir(
estateId = estateId,
planId = planId,
furniture = furniture,
walls = walls,
sizes = sizes,
electric = electric
))
override fun getEmptyPlan(estateId: Int
,planId:Int): Observable<String> =
Observable.mergeDelayError(
arrayListOf(getPlanApi(estateId,planId))
)
override fun getPlan(estateId: Int
,planId:Int
, furniture:Boolean?
, sizes:Boolean?
, walls:Boolean?
, electric:Boolean?): Observable<String> =
Observable.fromCallable { getPlanFile(
estateId = estateId,
planId = planId,
furniture = furniture,
walls = walls,
sizes = sizes,
electric = electric) }.switchMap {
if (it.exists()) Observable.just(it.path)
else getPlanApi(estateId
,planId
, furniture
, sizes
, walls
, electric
)
}
override fun getDealsApi(user: UserEntity): Observable<List<DealEntity>> =
// fun getPlanRequestString(estateId: Int Observables.zip(
// , planId:Int api.getDeals(user.authToken)
// , furniture:Boolean .doOnError(Timber::e)
// , sizes:Boolean .map { List(it.size){index -> fromRaw(it[index],user)} }
// , walls:Boolean ,db.getUserDeals(user.uuid)
// , electric:Boolean .toList().toObservable()
// ) = api.getDirectPlan(estateId,planId,furniture,sizes,electric). .doOnError(Timber::e)
){apiList,dbList ->
apiList.forEach { deal ->
private val getDealsApi: Observable<List<DealEntity>> = dbList?.firstOrNull { dbDeal-> dbDeal?.id == deal.id }?.let {
local.recentUser() deal.setRead(it.read)
.doOnError (Timber::e)
.flatMap {
when (it) {
is UserState.Authenticated -> db.fetchUser(it.uuid.toInt())
else -> throw CustomApiException.NotAuthorizedException()
} }
deal.user = user
} }
.doOnError (Timber::e) dbList
.flatMap { user -> .filterNotNull()
api.getDeals(TEST_DEAL_TOKEN)//api.getDeals(user.authToken) .filter { dbFav ->!apiList.map{it.id}.contains(dbFav.id) }
.doOnError(Timber::e) .let{ db.deleteDeal(it) }
.map { fromRawList(it, ::fromRaw) } return@zip apiList
.doOnNext { }
it.forEach { deal -> .doOnNext(db::blockingUpsert)
deal.user = user .doOnNext { db.refreshUser(user) }
}
}
.doOnNext(db::blockingUpsert)
}
private val getDealsDb: Observable<List<DealEntity>> = private val getDealsDb: Observable<List<DealEntity>> =
local.recentUser() local.recentUser()
...@@ -229,21 +108,11 @@ class EstateRepository @Inject constructor( ...@@ -229,21 +108,11 @@ class EstateRepository @Inject constructor(
.map { it.deals?.map {deal -> deal as DealEntity } } .map { it.deals?.map {deal -> deal as DealEntity } }
override fun getDeals(): Observable<List<DealModel>> {
return Observable.mergeDelayError(
arrayListOf(
getDealsDb,
getDealsApi
)
)
.map { fromEntity(it, ::fromEntity) }
.doOnError(Timber::e)
.subscribeOn (Schedulers.io())
}
override fun setDealRead(dealId: String): Completable = override fun setDealRead(dealId: String): Completable =
db.setDealReadState(dealId,true) db.setDealReadState(dealId,true)
.doOnError { Timber.e(it) }
......
package com.biganto.visual.roompark.data.data_provider package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.db.IDb import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.ArticleEntity import com.biganto.visual.roompark.data.repository.db.requrey.model.ArticleEntity
import com.biganto.visual.roompark.data.repository.mapper.fromRaw import com.biganto.visual.roompark.data.repository.mapper.fromRaw
......
package com.biganto.visual.roompark.data.data_provider package com.biganto.visual.roompark.data.data_provider
import android.app.Application import android.app.Application
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.db.IDb import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.file.FileModule import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.domain.contract.FilesContract import com.biganto.visual.roompark.domain.contract.FilesContract
......
package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.androidplayer.data.repository.local.ILocalStore
import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.EstateEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.PlanPresetEntity
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.data.repository.mapper.fromRaw
import com.biganto.visual.roompark.data.repository.mapper.fromRawList
import com.biganto.visual.roompark.domain.contract.FlatPlanContract
import com.biganto.visual.roompark.domain.model.FeatureModel
import com.biganto.visual.roompark.domain.model.PlanPresetModel
import com.biganto.visual.roompark.domain.model.fromEntity
import com.biganto.visual.roompark.domain.use_case.DownloadUseCase
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.util.*
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 20.04.2020.
*/
class PlanRepository @Inject constructor(
private val local: ILocalStore,
private val api: IRoomParkApi,
private val db: IDb,
private val file: FileModule
)
: FlatPlanContract {
private fun getPlanTypesApi(estateId: Int): Observable<List<PlanPresetEntity>> =
api.getEstatePlanTypes(estateId)
.doOnNext { Timber.d("raw0 $it") }
.map { fromRawList(it, ::fromRaw) }
.map {
it.onEach { plan ->
val e = EstateEntity()
e.setId(estateId)
plan.estateId = e
}.toList()
}
.doOnNext(db::blockingUpsert)
.subscribeOn(Schedulers.io())
override fun getPlansObservable(list:List<PlanFeaturesVariant>
,cancellationToken: DownloadUseCase.CancellationToken)
: Observable<String> {
return Observable.create { emitter ->
list
.asSequence()
.chunked(12)
.also { System.gc() }
.flatten()
.filter { !cancellationToken.isCancelled }
.forEach { variant->
val file = FileModule.getDirectory(
file.context
, FileModule.FileDirectory.PlanTypeDir(
estateId = variant.estateId,
planId = variant.planId,
furniture = variant.furniture,
walls = variant.walls,
sizes = variant.sizes,
electric = variant.electric
))
if (file.exists())
emitter.onNext(file.absolutePath)
else{
try {
Timber.w("Cancellation is: ${cancellationToken.isCancelled}")
if (cancellationToken.isCancelled){
emitter.onComplete()
}
file.parentFile.mkdirs()
file.writeText(
api.getDirectPlan(variant.estateId, variant.planId,
variant.furniture?:false
,variant.sizes?:false
,variant.walls?:false
,variant.electric?:false).blockingFirst()
)
emitter.onNext(file.absolutePath)
}
catch (e:Throwable){emitter.onError(e)}
}
}
emitter.onComplete()
}
}
override fun getPlanTypes(estateId: Int): Observable<List<PlanPresetModel>> =
Observable.mergeDelayError(
arrayListOf(getPlanTypesApi(estateId))
).map { l -> List(l.size) { fromEntity(l[it]) } }
private fun getPlanApi(estateId: Int
, planId:Int
, furniture:Boolean? = null
, sizes:Boolean? = null
, walls:Boolean? = null
, electric:Boolean? = null) =
api.getDirectPlan(estateId, planId,
furniture?:false
,sizes?:false
,walls?:false
,electric?:false)
.map {
val sFile = getPlanFile(
estateId = estateId,
planId = planId,
furniture = furniture,
walls = walls,
sizes = sizes,
electric = electric
)
file.saveFileToDisk(sFile, it)
sFile.absolutePath
}
.subscribeOn(Schedulers.io())
override fun getPlanFile(featuresVariant: PlanFeaturesVariant)
= getPlanFile(
estateId = featuresVariant.estateId,
planId = featuresVariant.planId,
furniture = featuresVariant.furniture,
sizes = featuresVariant.sizes,
walls = featuresVariant.walls,
electric = featuresVariant.electric
)
private fun getPlanFile(estateId: Int
, planId:Int
, furniture:Boolean? = null
, sizes:Boolean? = null
, walls:Boolean? = null
, electric:Boolean? = null)
= FileModule.getDirectory(file.context
, FileModule.FileDirectory.PlanTypeDir(
estateId = estateId,
planId = planId,
furniture = furniture,
walls = walls,
sizes = sizes,
electric = electric
))
// override fun getEmptyPlan(estateId: Int
// ,planId:Int): Observable<String> =
// Observable.mergeDelayError(
// arrayListOf(getPlanApi(estateId,planId))
// )
override fun getPlan(featuresVariant: PlanFeaturesVariant)
= getPlan(
estateId = featuresVariant.estateId,
planId = featuresVariant.planId,
furniture = featuresVariant.furniture,
sizes = featuresVariant.sizes,
walls = featuresVariant.walls,
electric = featuresVariant.electric
)
override fun getPlan(estateId: Int
,planId:Int
, furniture:Boolean?
, sizes:Boolean?
, walls:Boolean?
, electric:Boolean?): Observable<String> =
Observable.fromCallable { getPlanFile(
estateId = estateId,
planId = planId,
furniture = furniture,
walls = walls,
sizes = sizes,
electric = electric) }.flatMap {
if (it.exists()) Observable.just(it.path)
else getPlanApi(estateId
,planId
, furniture
, sizes
, walls
, electric
)
}
// fun getPlanRequestString(estateId: Int
// , planId:Int
// , furniture:Boolean
// , sizes:Boolean
// , walls:Boolean
// , electric:Boolean
// ) = api.getDirectPlan(estateId,planId,furniture,sizes,electric).
}
private fun BitSet.getOrNull(index:Int) =
if (index<0 || index>=this.size()) null
else get(index)
private fun Int.getBit(index: Int): Boolean? {
if (index<0) return null
return Integer.toBinaryString(shr(index)).last()=='1'
}
val PlanPresetModel.featuresVariants : List<PlanFeaturesVariant>
get() {
if (this.features.isNullOrEmpty()) return arrayListOf()
val maxInd = this.features.size
val f = features.indexOfFirst { it is FeatureModel.Furniture }
val s = features.indexOfFirst { it is FeatureModel.Sizes }
val e = features.indexOfFirst { it is FeatureModel.Electric }
val w = features.indexOfFirst { it is FeatureModel.Walls }
val resList = mutableListOf<PlanFeaturesVariant>()
var increment = 0
var allTrue: Boolean
do{
val v = PlanFeaturesVariant(
estateId,planId,
furniture = increment.getBit(f),
sizes = increment.getBit(s),
electric = increment.getBit(e),
walls = increment.getBit(w)
)
allTrue = v.furniture?:true && v.electric?:true && v.sizes?:true && v.walls?:true
resList.add(v)
increment++
}while (!allTrue)
return resList
}
data class PlanFeaturesVariant(
val estateId: Int,
val planId:Int,
val furniture:Boolean?,
val sizes:Boolean?,
val walls:Boolean?,
val electric:Boolean?
)
\ No newline at end of file
package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.api.room_park.raw.SubscriptionStatusRaw
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.SubscriptionEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import com.biganto.visual.roompark.domain.contract.SubscriptionContract
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.model.fromEntity
import io.reactivex.Completable
import io.reactivex.Observable
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 25.03.2020.
*/
private const val SUBSCRIPTION_RESULT_STATUS = "OK"
class SubscriptionRepository @Inject constructor(
private val api: IRoomParkApi,
private val db: IDb
): SubscriptionContract{
override fun saveSubscribeState(sub:SubscriptionEntity): Observable<SubscriptionEntity> {
return saveSubscribeState(
sub.owner as UserEntity,
sub.id,
sub.topic,
sub.number,
sub.state
)
}
override fun saveSubscribeState(
userEntity: UserEntity,
subInnerId:Int?,
topic: String,
topic_id: String?,
newState:Boolean
): Observable<SubscriptionEntity> {
val userSubs = userEntity.subscriptions
val sub = subInnerId?.let { id ->
userSubs?.firstOrNull { sub -> sub.id == id } as SubscriptionEntity?
}?:SubscriptionEntity()
.apply {
setOwner(userEntity)
setTopic(topic)
setNumber(topic_id)
}
sub.setState(newState)
return db.saveSubscription(sub)
}
fun saveSubscribtions(
userEntity: UserEntity,
apiSubs: List<SubscriptionStatusRaw>
): Observable<List<SubscriptionModel>>? {
val userSubs = userEntity.subscriptions
val newSubList = mutableListOf<SubscriptionEntity>()
apiSubs.forEach { apiSub ->
val cachedSub =
userSubs?.firstOrNull { s ->
s.topic == apiSub.topic && s.number == apiSub.estate_id
}
as SubscriptionEntity?
?: SubscriptionEntity()
.apply {
setOwner(userEntity)
setTopic(apiSub.topic)
setNumber(apiSub.estate_id)
}
cachedSub.setState(apiSub.active)
newSubList.add(cachedSub)
}
userSubs?.filter { !newSubList.map { s ->s.id }.contains(it.id) }?.forEach {
(it as SubscriptionEntity).setState(false)
}
userSubs?.forEach {s ->
if (newSubList.firstOrNull { s.topic == it.topic && s.number == it.number} == null)
newSubList.add(s as SubscriptionEntity)
}
return db.upsert(newSubList)
.map {list -> list.map {
fromEntity(
it as SubscriptionEntity)
}}
.toObservable()
}
override fun subscribeTopicResult(
user:UserEntity,
subInnerId:Int,
deviceToken: String,
topic: String,
topic_id: String?
): Observable<List<SubscriptionModel>> =
api.subscribeTopic(
userToken = user.authToken,
deviceToken = deviceToken
,topicName = topic
,topicId = topic_id)
.flatMap { saveSubscribtions(user,it.subscriptions?: arrayListOf()) }
.doOnNext { db.refreshUser(user).blockingFirst() }
override fun unSubscribeTopicResult(
user:UserEntity,
subInnerId:Int,
deviceToken: String,
topic: String,
topic_id: String?
): Observable<List<SubscriptionModel>> =
api.unSuubscribeTopic(
userToken = user.authToken,
deviceToken = deviceToken
,topicName = topic
,topicId = topic_id)
.flatMap { saveSubscribtions(user,it.subscriptions?: arrayListOf()) }
.doOnNext { db.refreshUser(user) }
override fun subscribeTopic(
user:UserEntity,
subInnerId:Int,
deviceToken: String,
topic: String,
topic_id: String?
): Completable = api.subscribeTopic(
userToken = user.authToken,
deviceToken = deviceToken
,topicName = topic
,topicId = topic_id)
.flatMapCompletable {
if (it.status == SUBSCRIPTION_RESULT_STATUS){
saveSubscribeState(user,subInnerId,topic,topic_id,true)
.ignoreElements()
}
else error("Error subscription state!")
}
override fun unSubscribeTopic(
user:UserEntity,
subInnerId:Int,
deviceToken: String,
topic: String,
topic_id: String?
): Completable =
api.unSuubscribeTopic(
userToken = user.authToken,
deviceToken = deviceToken
,topicName = topic
,topicId = topic_id)
.doOnNext { Timber.d("$it") }
.flatMapCompletable {
if (it.status == SUBSCRIPTION_RESULT_STATUS){
saveSubscribeState(user,subInnerId,topic,topic_id,false)
.ignoreElements()
}
else error("Error subscription state!")
}
}
\ No newline at end of file
package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.domain.contract.TourContract
import com.biganto.visual.roompark.domain.model.AuthInfoModel
import io.reactivex.Completable
import io.reactivex.Observable
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
//@Singleton
class ToursRepository @Inject constructor(
private val files: FileModule,
private val db:IDb
): TourContract {
override fun getMultiTourId(building: Int, number: Int): Observable<AuthInfoModel> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getOffer(offerId: Int): Observable<AuthInfoModel> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun deleteToursDbInfo(): Completable =
Completable.merge(
arrayListOf(
db.dropTourFileJuncTable(),
db.dropFileTable(),
db.dropTourTable(),
db.refreshEstatesWithTours()
)
)
// .concatWith { }
.doOnComplete { Timber.w("Completed --") }
.doOnError { Timber.e(it) }
}
package com.biganto.visual.roompark.data.memcache
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
import com.jakewharton.rxrelay2.Relay
import com.jakewharton.rxrelay2.ReplayRelay
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
/**
* Created by Vladislav Bogdashkin on 19.10.2018.
*/
/**
* @param[maxSize] max entries count before automatic save && flush
* @param[flushInterval] time without any actions with cache in SECONDS before automatic save && flush
* */
abstract class EntityCache<K,V>
constructor(
private val maxSize: Int = 300,
private val flushInterval: Long = TimeUnit.MINUTES.toSeconds(1)
) : Cache<K,V> {
private val disposable = CompositeDisposable()
protected abstract fun saveDelegate (values:List<V>)
protected abstract fun readDelegate (key:K) : V?
protected abstract fun deleteDelegate (value:V)
protected val notifier: Relay<V> = ReplayRelay.create<V>().toSerialized()
init {
disposable.addAll(
Observable.interval(flushInterval, flushInterval, TimeUnit.SECONDS)
.doOnTerminate { clear() }
.doOnDispose { clear() }
.subscribe { clear() }
)
}
override fun contains(key: K): Boolean = keyMap.containsKey(key)
protected val locker = Any()
private var lastFlushTime = System.nanoTime()
private val keyMap = ConcurrentHashMap<K, V>()
override val size: Int
get() = keyMap.size
override val toList
get() = keyMap.toList()
fun deleteEntity(key: K){
synchronized(locker) {
deleteItem(key)
}
}
private fun deleteItem(key:K){
synchronized(locker) {
lastFlushTime = System.nanoTime()
var toDelete = keyMap.remove(key)
if (toDelete == null)
toDelete = readDelegate(key)
if (toDelete == null) return // -> нет в хранилище
deleteDelegate(value = toDelete)
}
}
fun deleteEntitys(keys: List<K>){
synchronized(locker) {
keys.forEach { deleteItem(it)}
}
}
override fun set(key: K, value: V?) {
synchronized(locker) {
lastFlushTime = System.nanoTime()
if (keyMap.size > maxSize)
clear()
value?.let {
keyMap[key]=it
notifier.accept(it)
}
}
}
override fun remove(key: K) = keyMap.remove(key)
override fun get(key: K): V? {
synchronized(locker) {
lastFlushTime = System.nanoTime()
return keyMap[key] ?: readDelegate(key)
}
}
override fun saveAll(){
synchronized(locker)
{
if (keyMap.size > 0) {
Timber.d("Going to save items: ${keyMap.values.size}")
saveDelegate(keyMap.values.toList())
}
}
}
override fun clear() {
if (keyMap.size == 0) return
synchronized(locker)
{
saveAll()
keyMap.clear()
}
}
override fun removeAll(): List<V>? {
val values= keyMap.values.toList()
keyMap.clear()
return values
}
private fun recycle() {
if (keyMap.size<=0) return
val shouldRecycle = System.nanoTime() - lastFlushTime >= TimeUnit.MILLISECONDS.toNanos(flushInterval)
if (!shouldRecycle) return
keyMap.clear()
}
}
interface Cache<K,V> {
val size: Int
val toList:List<Pair<K,V>>
operator fun set(key: K, value: V?)
fun contains(key: K): Boolean
operator fun get(key: K): V?
fun remove(key: K): V?
fun removeAll(): List<V>?
fun clear()
fun saveAll()
}
/**
* Suppress warning Leaking This as we sure to make singletone instance of object in single-thread
* more info and
* @see <a href="https://stackoverflow.com/questions/3921616/leaking-this-in-constructor-warning">discussion</a>
*/
@Suppress("LeakingThis")
abstract class LifeCycleCache<K,V>(size:Int, secondsToFlush:Long)
: EntityCache<K, V>(maxSize=size,flushInterval = secondsToFlush)
, LifecycleObserver
{
init {
ProcessLifecycleOwner.get().lifecycle
.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onAppPaused() {
saveAll()
}
}
package com.biganto.visual.roompark.data.repository.api.biganto
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourFilesDataRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourFilesSimpleDataRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourPreviewRaw
import io.reactivex.*
import io.reactivex.schedulers.Schedulers
import okhttp3.HttpUrl
import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.Retrofit
import timber.log.Timber
import timber.log.Timber.e
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
/**
* Created by Vladislav Bogdashkin on 13.06.2018.
*/
@Singleton
class BigantoRetrofitRepository @Inject constructor(@Named("bigantoApi") retrofit:Retrofit)
: IBigantoApi{
private val api = retrofit.create(IBigantoMobileApi::class.java)
override fun provideHttpUrl(): HttpUrl {
return HttpUrl.parse(IBigantoMobileApi.BASE_URL)!!
}
override fun getAppVersion() = api
.getAppVersion()
.compose(RetrofitResponseValidation())
override fun getToursPreviewById(tourIds: List<String>): Observable<List<TourPreviewRaw>> {
return api.getToursPreviewById(ids = tourIds.joinToString(","))
.compose(RetrofitResponseValidation())
.subscribeOn(Schedulers.io())
}
override fun getTourMetaAsString(tour_id: String): Observable<String> = api
.getTourMetaAsString(ids = tour_id)
.map { it.toString() }
.doOnError { e(it) }
.subscribeOn(Schedulers.io())
override fun getTourFiles(tour_id: String, resolution: String): Observable<List<TourFilesDataRaw>> = api
.getTourFiles(ids = tour_id, resolution = resolution)
.compose(RetrofitResponseValidation())
.doOnError { e(it) }
.subscribeOn(Schedulers.io())
override fun getTourFilesSizes(ids: List<String>, resolution: String)
: Observable<List<TourFilesSimpleDataRaw>> = api
.getTourFilesSize(ids = ids.joinToString(separator = ","), resolution = resolution)
.compose(RetrofitResponseValidation())
.doOnError { e(it) }
.subscribeOn(Schedulers.io())
override fun downloadFile(uri: String, headers: Map<String, String>?): Flowable<ResponseBody> = api
.requestFile(headers ?: HashMap<String, String>(), uri)
.compose(FlowableRetrofitResponseValidation())
.doOnError(::e)
override fun getOfferTours(multiTourIds:List<Int>): Observable<List<TourPreviewRaw>> =
api
.getOfferTours(offerId = multiTourIds.joinToString(separator = ","))
.compose(RetrofitResponseValidation())
.map { it.values.flatten() }
.doOnError { e(it) }
.subscribeOn(Schedulers.io())
override fun getOfferTours(multiTourId:Int): Observable<List<TourPreviewRaw>> =
api
.getOfferTours(offerId = multiTourId.toString())
.compose(RetrofitResponseValidation())
.map { it[multiTourId.toString()]?.toList()?: error("No tours avaliable")}
.doOnError { e(it) }
.subscribeOn(Schedulers.io())
}
internal class RetrofitResponseValidation<T> : ObservableTransformer<Response<T>, T> {
override fun apply(responseObservable: Observable<Response<T>>): ObservableSource<T> {
return responseObservable.switchMap { resp ->
// Timber.d("reutrned code: %s",resp.code())
if (resp.code() == 200 || resp.code() == 206)
if (resp.body() == null)
return@switchMap Completable.complete().toObservable<T>().doOnNext{Timber.d("Completed responseBody")}
else return@switchMap Observable.just(resp.body())
Timber.d("Returning Exception!")
return@switchMap Observable.error<T>(retrofit2.HttpException(resp))
}
}
}
internal class RetrofitResponseValidationString<String> : ObservableTransformer<Response<String>, String> {
override fun apply(responseObservable: Observable<Response<String>>): ObservableSource<String> {
return responseObservable.switchMap<String> { resp ->
// Timber.d("reutrned code: %s",resp.code())
if (resp.code() == 200)
if (resp.body() == null)
return@switchMap Completable.complete().toObservable<String>().doOnNext{Timber.d("Completed responseBody")}
else return@switchMap Observable.just<String>(resp.body())
Timber.d("Returning Exception!")
return@switchMap Observable.error<String>(retrofit2.HttpException(resp))
}
}
}
internal class FlowableRetrofitResponseValidation<T> : FlowableTransformer<Response<T>, T> {
override fun apply(responseObservable: Flowable<Response<T>>): Flowable<T> {
return responseObservable.switchMap { resp ->
if (resp.code() == 200 || resp.code() == 206)
if (resp.body() == null)
return@switchMap Completable.complete().toFlowable<T>().doOnNext{Timber.d("Completed responseBody")}
else return@switchMap Flowable.just(resp.body())
Timber.d("Returning Exception!")
return@switchMap Flowable.error<T>(retrofit2.HttpException(resp))
}
}
}
//Allow to override HttpException with custom exception output, example:
//sealed class CustomApiException(val code:Int,val biganto_message:String):RuntimeException()
//{
// class RedirectException():CustomApiException(code = 300,biganto_message = "Redirect page..")
// class WrongAuthDataException():CustomApiException(code = 401,biganto_message = "Login or password not allowed")
// class BlockedUserException():CustomApiException(code = 403,biganto_message = "User has been banned")
// class ServerException():CustomApiException(code = 500,biganto_message = "Error server: undefined")
//
//}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.biganto
import com.biganto.visual.roompark.data.repository.api.biganto.raw.AppVersionRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourFilesDataRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourFilesSimpleDataRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourPreviewRaw
import io.reactivex.Flowable
import io.reactivex.Observable
import okhttp3.HttpUrl
import okhttp3.ResponseBody
/**
* Created by Vladislav Bogdashkin on 13.06.2018.
*/
interface IBigantoApi {
fun provideHttpUrl():HttpUrl
fun downloadFile(uri: String, headers: Map<String, String>?): Flowable<ResponseBody>
// fun getToursFiles(tour_ids: List<Int>, resolution: String): Flowable<Map<String, List<TourFileRaw>>>?
fun getTourMetaAsString(tour_id: String): Observable<String>
fun getTourFiles(tour_id: String, resolution: String): Observable<List<TourFilesDataRaw>>
fun getAppVersion(): Observable<AppVersionRaw>
fun getToursPreviewById(tourIds: List<String>): Observable<List<TourPreviewRaw>>
fun getOfferTours(multiTourId:Int): Observable<List<TourPreviewRaw>>
fun getOfferTours(multiTourIds: List<Int>): Observable<List<TourPreviewRaw>>
fun getTourFilesSizes(
ids: List<String>,
resolution: String
): Observable<List<TourFilesSimpleDataRaw>>
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.biganto
import com.biganto.visual.roompark.data.repository.api.biganto.raw.AppVersionRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourFilesDataRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourFilesSimpleDataRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourPreviewRaw
import com.google.gson.JsonArray
import io.reactivex.Flowable
import io.reactivex.Observable
import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.http.*
import java.util.*
/**
* Created by Vladislav Bogdashkin on 13.06.2018.
*/
interface IBigantoMobileApi {
companion object{
//const val BASE_URL="http://local.biganto.com"
const val BASE_URL="https://biganto.com"
//const val API_URL="api-novus/"
const val API_URL="api/"
const val DELIMITER="?"
const val CLIENT_TYPE_PARAM="client"
const val CLIENT_VERSION_PARAM="client_version"
const val API_VERSION_PARAM="v"
const val AUTH_TOKEN="auth_token"
const val LANG_PARAM="lang"
const val DEFAULT_LANG_VALUE="en"
const val DEFAULT_CLIENT_TYPE="androidplayer"
//const val DEFAULT_CLIENT_TYPE="iosplayer"
const val OLD_CLIENT_TYPE="mobileplayer"
const val DEFAULT_CLIENT_VERSION="3.0"
const val DEFAULT_API_VERSION="2.0"
//region AppInfo
const val GET_APP_VERSION="misc.appVersion"
//endregion
//region Authentication
const val AUTH_METHOD="users/login/"
const val AUTH_METHOD_2_0="users.authorize"
const val AUTH_LOGIN_PARAM="email"
const val AUTH_PASSWORD_PARAM="password"
//endregion
//region GetEstates
const val GET_ESTATES_METHOD="estates.getList"
//endregion
//region Portfolio
const val GET_PORTFOLIO_ESTATES_METHOD="portfolio.getEstates"
const val GET_PORTFOLIO_TOURS_METHOD="portfolio.getTours"
//endregion
//region GetToursPreview
const val GET_TOURS_METHOD="tours.getBadges"//"tours.getList"
//const val PARENT_ESTATE_PARAM="id"
const val PARENT_ESTATE_PARAM="estate_id"
const val TOURS_BY_ID_PARAM="id"
const val TOURS_TYPES_PARAM="types"
const val DEFAULT_SUPPORTED_TOURS_TYPES = "virtual,real"
//endregion
//region GetToursMeta
const val GET_TOURS_META_METHOD="tours.getMeta"
const val GET_TOURS_META_ID="id"
//endregion
//region GetFiles
const val GET_TOURS_FILES_METHOD="tours.getFiles"
const val GET_TOURS_FILES_ID="id"
const val GET_TOURS_FILES_RESOLUTION="resolution"
//endregion
//region offers.GetTours
const val OFFER_GET_TOURS_METHOD="offers.getTours"
const val OFFER_GET_TOURS_ID="id"
//endregion
}
@GET("$API_URL$GET_TOURS_METHOD$DELIMITER")
fun getToursPreviewById(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
@Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION,
@Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION,
@Query(LANG_PARAM) languageCode: String = Locale.getDefault().language,
@Query(TOURS_TYPES_PARAM) toursTypes: String = DEFAULT_SUPPORTED_TOURS_TYPES,
@Query(TOURS_BY_ID_PARAM) ids: String
): Observable<Response<List<TourPreviewRaw>>>
@GET("$API_URL$GET_TOURS_META_METHOD$DELIMITER")
fun getTourMetaAsString(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
@Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION,
@Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION,
@Query(LANG_PARAM) languageCode: String = Locale.getDefault().language,
@Query(GET_TOURS_META_ID) ids: String
): Observable<JsonArray>
@GET("$API_URL$GET_TOURS_FILES_METHOD$DELIMITER")
fun getTourFiles(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
@Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION,
@Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION,
@Query(LANG_PARAM) languageCode: String = Locale.getDefault().language,
@Query(GET_TOURS_FILES_ID) ids: String,
@Query(GET_TOURS_FILES_RESOLUTION) resolution: String
): Observable<Response<List<TourFilesDataRaw>>>
@GET("$API_URL$GET_TOURS_FILES_METHOD$DELIMITER")
fun getTourFilesSize(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
@Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION,
@Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION,
@Query(LANG_PARAM) languageCode: String = Locale.getDefault().language,
@Query(GET_TOURS_FILES_ID) ids: String,
@Query(GET_TOURS_FILES_RESOLUTION) resolution: String
): Observable<Response<List<TourFilesSimpleDataRaw>>>
@GET("$API_URL$GET_APP_VERSION$DELIMITER")
fun getAppVersion(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
@Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION,
@Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION,
@Query(LANG_PARAM) languageCode: String = Locale.getDefault().language
): Observable<Response<AppVersionRaw>>
@GET("$API_URL$OFFER_GET_TOURS_METHOD$DELIMITER")
fun getOfferTours(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
@Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION,
@Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION,
@Query(LANG_PARAM) languageCode: String = Locale.getDefault().language,
@Query(OFFER_GET_TOURS_ID) offerId: String
): Observable<Response<Map<String,List<TourPreviewRaw>>>>
@Streaming
@GET
fun requestFile(
@HeaderMap headers: Map<String, String>?,
@Url fileUrl: String
): Flowable<Response<ResponseBody>>
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.biganto.raw
import java.util.*
/**
* Created by Vladislav Bogdashkin on 09.06.2018.
*/
data class LiteTourMetaRaw(
val baseurl:String,
val tour_baseurl:String
)
data class ErrorRaw(
val code:Int,
val message:String
)
data class EstatesRaw(
val estates:List<EstateRaw>,
val errors:List<ErrorRaw>?
)
data class EstatesRoomParkRaw(
val estates:Map<String, EstateRoomParkRaw>,
val errors:List<ErrorRaw>?
)
data class EstateRoomParkRaw(
val avaliable:Boolean,
val long: String, //->long name
val short: String //->short name
)
data class TourOverviewsRoomParkRaw(
val tours:List<TourOverviewRoomParkRaw>,
val errors:List<ErrorRaw>?
)
data class TourOverviewRoomParkRaw(
val hidden:Boolean,
val id:String,
val preview: String,
val title: String,
val url:String
)
data class EstateRaw(
val id:Int,
val created:Date,
val title:String,
val preview:String?,
val cnt_tours:Int,
val errors:List<ErrorRaw>?
)
data class ToursPreviewRaw(
val list:List<TourPreviewRaw>,
val errors:List<ErrorRaw>?
)
data class TourPreviewRaw(
val id:Int,
val created:Date,
val updated:Date,
val type:String,
val title:String,
val preview:String,
val screen:String,
val hidden:Boolean,
val resolutions:List<Int>?,
val features:List<String>?,
val errors:List<ErrorRaw>?
)
data class TourFileRaw(
val size:Long,
val url:String,
val errors:List<ErrorRaw>?
)
data class TourFilesSimpleDataRaw(
val id:Int,
val resolution:Int,
val total_size:Long,
val errors:List<ErrorRaw>?
)
data class TourFilesDataRaw(
val files:List<TourFileRaw>,
val id:Int,
val resolution:Int,
val total_size:Long,
val errors:List<ErrorRaw>?
)
data class TourGetFilesRaw(
val data: Map<String,List<TourFileRaw>>,
val errors:List<ErrorRaw>?
)
data class OfferTours(
val data: Map<String,List<TourPreviewRaw>>,
val errors:List<ErrorRaw>?
)
data class AppVersionRaw(
val current_version : String,
val download_url : String?,
val message : String?,
val critical : Boolean,
val errors : List<ErrorRaw>?
)
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.retrofit.di package com.biganto.visual.roompark.data.repository.api.retrofit.di
import android.app.Application import android.app.Application
import com.biganto.visual.roompark.data.repository.api.retrofit.IRoomParkMobileApi import com.biganto.visual.roompark.data.repository.api.biganto.IBigantoMobileApi
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.ErrorRaw
import com.biganto.visual.roompark.data.repository.api.retrofit.util.* import com.biganto.visual.roompark.data.repository.api.retrofit.util.*
import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkMobileApi
import com.biganto.visual.roompark.data.repository.api.room_park.raw.ErrorRaw
import com.biganto.visual.roompark.data.service.network.INetworkMonitor import com.biganto.visual.roompark.data.service.network.INetworkMonitor
import com.biganto.visual.roompark.data.service.network.LiveNetworkMonitor import com.biganto.visual.roompark.data.service.network.LiveNetworkMonitor
import com.biganto.visual.roompark.data.service.network.NoNetworkException import com.biganto.visual.roompark.data.service.network.NoNetworkException
...@@ -22,6 +23,7 @@ import retrofit2.converter.scalars.ScalarsConverterFactory ...@@ -22,6 +23,7 @@ import retrofit2.converter.scalars.ScalarsConverterFactory
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Named
import javax.inject.Singleton import javax.inject.Singleton
/** /**
...@@ -102,7 +104,7 @@ class RetrofitModule{ ...@@ -102,7 +104,7 @@ class RetrofitModule{
@Provides @Provides
@Singleton @Singleton
// @Named("roomParkApi") @Named("roomParkApi")
fun provideRetrofitRoomParkApi(context: Application): Retrofit = fun provideRetrofitRoomParkApi(context: Application): Retrofit =
Retrofit.Builder() Retrofit.Builder()
.baseUrl(IRoomParkMobileApi.BASE_URL) .baseUrl(IRoomParkMobileApi.BASE_URL)
...@@ -113,4 +115,18 @@ class RetrofitModule{ ...@@ -113,4 +115,18 @@ class RetrofitModule{
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build() .build()
@Provides
@Singleton
@Named("bigantoApi")
fun provideRetrofitBigantoApi(context: Application): Retrofit =
Retrofit.Builder()
.baseUrl(IBigantoMobileApi.BASE_URL)
.client(client(AppVersionManager(context),LiveNetworkMonitor(context)))
.addConverterFactory(NullOnEmptyConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(gsonConverterFactory())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
} }
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.retrofit.util package com.biganto.visual.roompark.data.repository.api.retrofit.util
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.ErrorRaw import com.biganto.visual.roompark.data.repository.api.room_park.raw.ErrorRaw
import com.biganto.visual.roompark.domain.custom_exception.parseException import com.biganto.visual.roompark.domain.custom_exception.parseException
import com.google.gson.* import com.google.gson.*
import timber.log.Timber import timber.log.Timber
......
package com.biganto.visual.roompark.data.repository.api.retrofit.util package com.biganto.visual.roompark.data.repository.api.retrofit.util
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.ErrorRaw import com.biganto.visual.roompark.data.repository.api.room_park.raw.ErrorRaw
import com.biganto.visual.roompark.domain.custom_exception.parseException import com.biganto.visual.roompark.domain.custom_exception.parseException
import com.google.gson.JsonDeserializationContext import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer import com.google.gson.JsonDeserializer
......
package com.biganto.visual.roompark.data.repository.api package com.biganto.visual.roompark.data.repository.api.room_park
import com.biganto.visual.roompark.data.repository.api.retrofit.DEFAULT_ARTICLE_PAGE_SIZE import com.biganto.visual.roompark.data.repository.api.room_park.raw.*
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.*
import io.reactivex.Observable import io.reactivex.Observable
/** /**
...@@ -15,13 +14,15 @@ interface IRoomParkApi { ...@@ -15,13 +14,15 @@ interface IRoomParkApi {
fun subscribeTopic( fun subscribeTopic(
userToken: String, userToken: String,
deviceToken: String, deviceToken: String,
topicName: String topicName: String,
topicId:String?
): Observable<StatusResponse> ): Observable<StatusResponse>
fun unSuubscribeTopic( fun unSuubscribeTopic(
userToken: String, userToken: String,
deviceToken: String, deviceToken: String,
topicName: String topicName: String,
topicId:String?
): Observable<StatusResponse> ): Observable<StatusResponse>
fun getDeals(userToken: String): Observable<List<DealRaw>> fun getDeals(userToken: String): Observable<List<DealRaw>>
...@@ -49,6 +50,3 @@ interface IRoomParkApi { ...@@ -49,6 +50,3 @@ interface IRoomParkApi {
fun getWebCamsList(): Observable<List<WebCamRaw>> fun getWebCamsList(): Observable<List<WebCamRaw>>
} }
interface IBigantoApi {
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.retrofit package com.biganto.visual.roompark.data.repository.api.room_park
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.* import com.biganto.visual.roompark.data.repository.api.room_park.raw.*
import io.reactivex.Observable import io.reactivex.Observable
import retrofit2.Response import retrofit2.Response
import retrofit2.http.* import retrofit2.http.*
...@@ -26,8 +26,8 @@ interface IRoomParkMobileApi{ ...@@ -26,8 +26,8 @@ interface IRoomParkMobileApi{
const val LANG_PARAM="lang" const val LANG_PARAM="lang"
const val DEFAULT_LANG_VALUE="en" const val DEFAULT_LANG_VALUE="en"
const val DEFAULT_CLIENT_TYPE="androidplayer" const val DEFAULT_CLIENT_TYPE="android"
//const val DEFAULT_CLIENT_TYPE="iosplayer" //const val DEFAULT_CLIENT_TYPE="ios"
const val OLD_CLIENT_TYPE="mobileplayer" const val OLD_CLIENT_TYPE="mobileplayer"
const val DEFAULT_CLIENT_VERSION="3.0" const val DEFAULT_CLIENT_VERSION="3.0"
...@@ -48,6 +48,7 @@ interface IRoomParkMobileApi{ ...@@ -48,6 +48,7 @@ interface IRoomParkMobileApi{
const val UNSUBSCRIBE_METHOD="users.unsubscribe" const val UNSUBSCRIBE_METHOD="users.unsubscribe"
const val DEVICE_TOKEN_SUBSCRIBTION_PARAM="deviceToken" const val DEVICE_TOKEN_SUBSCRIBTION_PARAM="deviceToken"
const val TOPIC_SUBSCRIBTION_PARAM="topic" const val TOPIC_SUBSCRIBTION_PARAM="topic"
const val TOPIC_SUBSCRIBTION_TOPIC_ID_PARAM="estate_id"
val topicTypes = arrayOf( val topicTypes = arrayOf(
"deals", "deals",
"progress-1", "progress-1",
...@@ -141,24 +142,28 @@ interface IRoomParkMobileApi{ ...@@ -141,24 +142,28 @@ interface IRoomParkMobileApi{
@Field(PASSWORD_AUTH_PARAM) pwd: String @Field(PASSWORD_AUTH_PARAM) pwd: String
): Observable<Response<AuthRaw>> ): Observable<Response<AuthRaw>>
@POST("$API_URL${SUBSCRIBE_METHOD}OD$DELIMITER") @POST("$API_URL$SUBSCRIBE_METHOD$DELIMITER")
@FormUrlEncoded
fun subscribe( fun subscribe(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE, @Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
@Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION, @Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION,
@Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION, @Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION,
@Query(AUTH_TOKEN) token: String, @Query(AUTH_TOKEN) token: String,
@Query(DEVICE_TOKEN_SUBSCRIBTION_PARAM) deviceToken: String, @Field(DEVICE_TOKEN_SUBSCRIBTION_PARAM) deviceToken: String,
@Query(TOPIC_SUBSCRIBTION_PARAM) topic: String @Field(TOPIC_SUBSCRIBTION_PARAM) topic: String,
@Field(TOPIC_SUBSCRIBTION_TOPIC_ID_PARAM) estateId: String?
): Observable<Response<StatusResponse>> ): Observable<Response<StatusResponse>>
@POST("$API_URL${UNSUBSCRIBE_METHOD}OD$DELIMITER") @POST("$API_URL$UNSUBSCRIBE_METHOD$DELIMITER")
@FormUrlEncoded
fun unsubscribe( fun unsubscribe(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE, @Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
@Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION, @Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION,
@Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION, @Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION,
@Query(AUTH_TOKEN) token: String, @Query(AUTH_TOKEN) token: String,
@Query(DEVICE_TOKEN_SUBSCRIBTION_PARAM) deviceToken: String, @Field(DEVICE_TOKEN_SUBSCRIBTION_PARAM) deviceToken: String,
@Query(TOPIC_SUBSCRIBTION_PARAM) topic: String @Field(TOPIC_SUBSCRIBTION_PARAM) topic: String,
@Field(TOPIC_SUBSCRIBTION_TOPIC_ID_PARAM) estateId: String?
): Observable<Response<StatusResponse>> ): Observable<Response<StatusResponse>>
@GET("$API_URL$DEALS_METHOD$DELIMITER") @GET("$API_URL$DEALS_METHOD$DELIMITER")
......
package com.biganto.visual.roompark.data.repository.api.retrofit package com.biganto.visual.roompark.data.repository.api.room_park
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi import com.biganto.visual.roompark.data.repository.api.room_park.raw.*
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.*
import com.biganto.visual.roompark.util.extensions.asInt import com.biganto.visual.roompark.util.extensions.asInt
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Observable import io.reactivex.Observable
...@@ -12,6 +11,7 @@ import retrofit2.Response ...@@ -12,6 +11,7 @@ import retrofit2.Response
import retrofit2.Retrofit import retrofit2.Retrofit
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named
/** /**
* Created by Vladislav Bogdashkin on 29.10.2019. * Created by Vladislav Bogdashkin on 29.10.2019.
...@@ -19,7 +19,8 @@ import javax.inject.Inject ...@@ -19,7 +19,8 @@ import javax.inject.Inject
const val DEFAULT_ARTICLE_PAGE_SIZE = 10 const val DEFAULT_ARTICLE_PAGE_SIZE = 10
class RetrofitRepository @Inject constructor(retrofit: Retrofit) : IRoomParkApi { class RetrofitRepository @Inject constructor(@Named("roomParkApi") retrofit: Retrofit) :
IRoomParkApi {
private val api = retrofit.create(IRoomParkMobileApi::class.java) private val api = retrofit.create(IRoomParkMobileApi::class.java)
...@@ -35,17 +36,20 @@ class RetrofitRepository @Inject constructor(retrofit: Retrofit) : IRoomParkApi ...@@ -35,17 +36,20 @@ class RetrofitRepository @Inject constructor(retrofit: Retrofit) : IRoomParkApi
override fun subscribeTopic( override fun subscribeTopic(
userToken: String, userToken: String,
deviceToken: String, deviceToken: String,
topicName: String topicName: String,
topicId:String?
): Observable<StatusResponse> = ): Observable<StatusResponse> =
api.subscribe(token = userToken, deviceToken = deviceToken, topic = topicName) api.subscribe(token = userToken, deviceToken = deviceToken, topic = topicName,estateId = topicId)
.doOnError { Timber.w(" WTFF ???") }
.compose(RetrofitResponseValidation()) .compose(RetrofitResponseValidation())
override fun unSuubscribeTopic( override fun unSuubscribeTopic(
userToken: String, userToken: String,
deviceToken: String, deviceToken: String,
topicName: String topicName: String,
topicId:String?
): Observable<StatusResponse> = ): Observable<StatusResponse> =
api.unsubscribe(token = userToken, deviceToken = deviceToken, topic = topicName) api.unsubscribe(token = userToken, deviceToken = deviceToken, topic = topicName,estateId = topicId)
.compose(RetrofitResponseValidation()) .compose(RetrofitResponseValidation())
......
package com.biganto.visual.roompark.data.repository.api.retrofit.raw package com.biganto.visual.roompark.data.repository.api.room_park.raw
import com.google.gson.annotations.Expose
import java.util.* import java.util.*
/** /**
...@@ -13,7 +14,17 @@ data class AuthRaw( ...@@ -13,7 +14,17 @@ data class AuthRaw(
val name:String val name:String
) )
data class StatusResponse(val status:String) data class StatusResponse(
val status:String,
val subscriptions:List<SubscriptionStatusRaw>?
)
data class SubscriptionStatusRaw(
val topic:String,
val estate_id: String,
@Expose
val active: Boolean = true
)
data class DealRaw( data class DealRaw(
val id:String, val id:String,
...@@ -30,6 +41,7 @@ data class EstateRaw( ...@@ -30,6 +41,7 @@ data class EstateRaw(
val id:Int, val id:Int,
val type:String, val type:String,
val number:String, val number:String,
val available:Boolean,
val common_info: CommonInfoRaw, val common_info: CommonInfoRaw,
val plan_png:PlanRaw?, val plan_png:PlanRaw?,
val plan_jpg:PlanRaw?, val plan_jpg:PlanRaw?,
......
package com.biganto.visual.roompark.data.repository.db package com.biganto.visual.roompark.data.repository.db
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.model.* import com.biganto.visual.roompark.data.repository.db.requrey.model.*
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import io.requery.Persistable import io.requery.Persistable
import io.requery.reactivex.ReactiveResult import io.requery.reactivex.ReactiveResult
import io.requery.reactivex.ReactiveScalar
/** /**
* Created by Vladislav Bogdashkin on 29.10.2019. * Created by Vladislav Bogdashkin on 29.10.2019.
*/ */
interface IDb { interface IDb {
fun upsertUser(entity: UserEntity): Observable<UserEntity>? fun upsertUser(entity: UserEntity): Observable<UserEntity>
fun <T : Persistable> upsert(entity: T): Single<T> fun <T : Persistable> upsert(entity: T): Single<T>
fun <T : List<Persistable>> upsert(entity: T): Single<Iterable<Persistable>> fun <T : List<Persistable>> upsert(entity: T): Single<Iterable<Persistable>>
fun fetchFeeds(): Observable<FeedEntity> fun fetchFeeds(): Observable<FeedEntity>
...@@ -27,11 +32,55 @@ interface IDb { ...@@ -27,11 +32,55 @@ interface IDb {
fun getPhotos(albumId: Int): Observable<GalleryPhotoEntity> fun getPhotos(albumId: Int): Observable<GalleryPhotoEntity>
fun getPhoto(photoId: Int): Observable<GalleryPhotoEntity> fun getPhoto(photoId: Int): Observable<GalleryPhotoEntity>
fun getAlbum(albumId: Int): Observable<ImageAlbumEntity> fun getAlbum(albumId: Int): Observable<ImageAlbumEntity>
fun getUserFavorites(uuid: Int): Observable<EstateEntity> fun getUserFavorites(uuid: Int): Observable<EstateEntity?>
fun fetchAllUsers(): Observable<List<UserEntity>> fun fetchAllUsers(): Observable<List<UserEntity>>
fun getEstate(estateId: Int): Observable<EstateEntity> fun getEstate(estateId: Int): Observable<EstateEntity>
fun upsertEstate(entity: EstateEntity) fun upsertEstate(entity: EstateEntity)
fun fetchEstateByNumber(building: Int, number: String): ReactiveResult<EstateEntity> fun fetchEstateByNumber(building: Int, number: String): ReactiveResult<EstateEntity>
fun setArticleReadState(id: Int, state: Boolean): Completable fun setArticleReadState(id: Int, state: Boolean): Completable
fun setDealReadState(id: String, state: Boolean): Completable fun setDealReadState(id: String, state: Boolean): Completable
fun saveSubscription(subscription: SubscriptionEntity): Observable<SubscriptionEntity>
fun getSubscription(id: Int): ReactiveResult<SubscriptionEntity>
fun deleteTourPreview(id: String): Int?
fun deleteTourPreview(entity: TourPreviewEntity)
fun deleteFiles(entity: List<FileEntity>)
fun deleteFile(entity: FileEntity)
fun deleteTourFilesJunction(tourId: String): ReactiveScalar<Int>
fun deleteTourFilesJunction(entity: List<TourFileJunctionEntity>)
fun getTourFilesJunctionUniqueFiles(tourId: String): List<TourFileJunctionEntity?>
fun getTourFilesJunction(tourId: String): ReactiveResult<TourFileJunctionEntity>
fun upsertFileEntity(entity: List<FileEntity>): Observable<Iterable<FileEntity>>
fun upsertFileEntity(entity: FileEntity): Observable<FileEntity>
fun flowableFileEntityes(uris: List<RevisionString>): Flowable<FileEntity>
fun getDownloadedSumFileEntityes(uris: List<RevisionString>): Long
fun fetchTouresWithStates(states: List<DownloadState>): MutableList<TourPreviewEntity>
fun fetchFileEntityes(uris: List<RevisionString>): MutableList<FileEntity>
fun pushFileEntities(uris: List<FileEntity>): Observable<Iterable<FileEntity>>
fun getFileEntity(uri: RevisionString): ReactiveResult<FileEntity>
fun getTourFiles(entity: TourPreviewEntity): MutableList<TourFileJunctionEntity>
fun getTourFiles(tourId: String): MutableList<TourFileJunctionEntity>
fun getTourFilesObservable(tourId: String): ReactiveResult<TourFileJunctionEntity>
fun upsertTourPreview(list: List<TourPreviewEntity>): Observable<Iterable<TourPreviewEntity>>
fun upsertTourPreview(entity: TourPreviewEntity): Observable<TourPreviewEntity>
fun upsertTourFileJunction(entity: List<TourFileJunctionEntity>): Observable<Iterable<TourFileJunctionEntity>>
fun upsertTourFileJunction(entity: TourFileJunctionEntity): Observable<TourFileJunctionEntity>
fun observableTourDownloadState(): Flowable<TourPreviewEntity>
fun getFileEntitysOrDefault(uri: RevisionString): FileEntity
fun getTourPreview(tourId: String): ReactiveResult<TourPreviewEntity>
fun getTourPreviewsObservableResult(estateId: Int): Observable<ReactiveResult<TourPreviewEntity>>
fun getEstateTourPreviews(estateId: Int): Observable<List<TourPreviewEntity>>
fun dropFileTable(): Completable
fun dropTourFileJuncTable(): Completable
fun dropTourTable(): Completable
fun refreshUser(userEntity: UserEntity): Observable<UserEntity>
fun refreshEntities(entities: List<Persistable>): Observable<Iterable<Persistable>>
fun refreshEstatesWithTours(): Completable
fun upsertEstates(entity: List<EstateEntity>): Observable<Iterable<EstateEntity>>?
fun upsertDeals(entity: List<DealEntity>): Observable<Iterable<DealEntity>>
fun deleteSubscriptions(entities: List<Subscription>)
fun deleteEstate(entity: List<EstateEntity>)
fun deleteEstate(entity: EstateEntity)
fun getUserDeals(uuid: Int): Observable<DealEntity?>
fun deleteDeal(entity: DealEntity)
fun deleteDeal(entity: List<DealEntity>)
} }
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey package com.biganto.visual.roompark.data.repository.db.requrey
import android.app.Application import android.app.Application
import com.biganto.visual.roompark.Models
import com.biganto.visual.roompark.data.repository.db.IDb import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.* import com.biganto.visual.roompark.data.repository.db.requrey.model.*
import com.biganto.visual.roompark.di.dagger.DATABASE_VERSION import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreview
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import io.reactivex.BackpressureStrategy
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.rxkotlin.toCompletable
import io.reactivex.schedulers.Schedulers
import io.requery.Persistable import io.requery.Persistable
import io.requery.android.sqlite.DatabaseSource import io.requery.android.sqlite.DatabaseSource
import io.requery.kotlin.notNull
import io.requery.reactivex.KotlinReactiveEntityStore import io.requery.reactivex.KotlinReactiveEntityStore
import io.requery.reactivex.ReactiveResult import io.requery.reactivex.ReactiveResult
import io.requery.sql.KotlinEntityDataStore import io.requery.sql.KotlinEntityDataStore
...@@ -23,6 +30,8 @@ import javax.inject.Inject ...@@ -23,6 +30,8 @@ import javax.inject.Inject
*/ */
private const val DATABASE_VERSION = 16
@Module @Module
class DbModule{ class DbModule{
...@@ -45,13 +54,35 @@ class RequeryRepository @Inject constructor( ...@@ -45,13 +54,35 @@ class RequeryRepository @Inject constructor(
) )
: IDb { : IDb {
override fun refreshUser(userEntity: UserEntity): Observable<UserEntity> =
store.refresh(userEntity).toObservable()
override fun refreshEstatesWithTours(): Completable =
store.select(EstateEntity::class)
.where(EstateEntity.MULTITOUR_ID.notNull())
.get()
.observable()
.toList()
.toObservable()
.doOnNext { Timber.d("bgg ${it.size}") }
.flatMapCompletable{ store.refresh(it).ignoreElement()}
override fun refreshEntities(entities:List<Persistable>): Observable<Iterable<Persistable>>
= store.refresh(entities).toObservable()
override fun dropTourTable() = store.delete(TourPreviewEntity::class).get().toCompletable()
override fun dropTourFileJuncTable() = store.delete(TourFileJunctionEntity::class).get().toCompletable()
override fun dropFileTable() = store.delete(FileEntity::class).get().toCompletable()
private inline fun <reified T : Persistable> fetchAll() = private inline fun <reified T : Persistable> fetchAll() =
store.select(T::class) store.select(T::class)
override fun upsertUser(entity: UserEntity): Observable<UserEntity> = override fun upsertUser(entity: UserEntity): Observable<UserEntity> =
store.upsert(entity).toObservable() store.upsert(entity).toObservable()
override fun <T : Persistable> upsert(entity: T) = store.upsert(entity) override fun <T : Persistable> upsert(entity: T): Single<T> = store.upsert(entity)
override fun <T : List<Persistable>> upsert(entity: T): Single<Iterable<Persistable>> = override fun <T : List<Persistable>> upsert(entity: T): Single<Iterable<Persistable>> =
store.upsert(entity) store.upsert(entity)
...@@ -59,6 +90,12 @@ class RequeryRepository @Inject constructor( ...@@ -59,6 +90,12 @@ class RequeryRepository @Inject constructor(
override fun <T : List<Persistable>> blockingUpsert(entity: T) = override fun <T : List<Persistable>> blockingUpsert(entity: T) =
store.upsert(entity).toObservable().blockingSubscribe() store.upsert(entity).toObservable().blockingSubscribe()
override fun upsertDeals(entity:List<DealEntity>): Observable<Iterable<DealEntity>> =
store.upsert(entity).toObservable()
override fun upsertEstates(entity:List<EstateEntity>): Observable<Iterable<EstateEntity>> =
store.upsert(entity).toObservable()
override fun upsertEstate(entity:EstateEntity) = override fun upsertEstate(entity:EstateEntity) =
store.upsert(entity).toObservable().blockingSubscribe() store.upsert(entity).toObservable().blockingSubscribe()
...@@ -147,18 +184,23 @@ class RequeryRepository @Inject constructor( ...@@ -147,18 +184,23 @@ class RequeryRepository @Inject constructor(
fun upsertFeeds(entity: List<FeedEntity>) = fun upsertFeeds(entity: List<FeedEntity>) =
store.upsert(entity) store.upsert(entity)
override fun userObservableResult(uuid: Int) = override fun userObservableResult(uuid: Int): Observable<ReactiveResult<UserEntity>> =
store.select(UserEntity::class) store.select(UserEntity::class)
.where(UserEntity.UUID.eq(uuid)) .where(UserEntity.UUID.eq(uuid))
.get() .get()
.observableResult() .observableResult()
override fun getUserFavorites(uuid: Int): Observable<EstateEntity> = override fun getUserFavorites(uuid: Int): Observable<EstateEntity?> =
store.select(EstateEntity::class) store.select(EstateEntity::class)
.where(EstateEntity.USER_ID.eq(uuid)) .where(EstateEntity.USER_ID.eq(uuid))
.and(EstateEntity.FAVORITE.eq(true)) .and(EstateEntity.FAVORITE.eq(true))
.get().observable() .get().observable()
override fun getUserDeals(uuid: Int): Observable<DealEntity?> =
store.select(DealEntity::class)
.where(DealEntity.USER_ID.eq(uuid))
.get().observable()
override fun setArticleReadState(id:Int,state:Boolean): Completable = override fun setArticleReadState(id:Int,state:Boolean): Completable =
store.update(ArticleEntity::class) store.update(ArticleEntity::class)
.set(ArticleEntity.READ,state) .set(ArticleEntity.READ,state)
...@@ -175,4 +217,227 @@ class RequeryRepository @Inject constructor( ...@@ -175,4 +217,227 @@ class RequeryRepository @Inject constructor(
.get() .get()
.single() .single()
.ignoreElement() .ignoreElement()
override fun saveSubscription(subscription:SubscriptionEntity)
: Observable<SubscriptionEntity> = store.upsert(subscription)
.toObservable()
.doOnNext { store.refresh(subscription.owner).blockingGet() }
override fun getSubscription(id:Int): ReactiveResult<SubscriptionEntity> =
store.select(SubscriptionEntity::class)
.where(SubscriptionEntity.ID.eq(id))
.get()
///region tours files
override fun getTourPreviewsObservableResult(estateId: Int) = store
.select(TourPreviewEntity::class)
// .join(UserEntity::class).on(UserEntity::uuid.eq(estateId))
.where(TourPreviewEntity.ESTATE_ID.eq(estateId))
.get()
.observableResult()
override fun getTourPreview(tourId: String) = store
.select(TourPreviewEntity::class)
.where(TourPreviewEntity.ID.eq(tourId))
.get()
override fun getEstateTourPreviews(estateId: Int): Observable<List<TourPreviewEntity>> = store
.select(TourPreviewEntity::class)
.where(TourPreviewEntity.ESTATE_ID.eq(estateId))
.get()
.observable()
.toList()
.toObservable()
.map { it.toList() }
override fun upsertTourPreview(list: List<TourPreviewEntity>) = store
.upsert(list)
.doOnSuccess { Timber.d("Upsertd succses %s", it.count()) }
.toObservable()
override fun upsertTourPreview(entity: TourPreviewEntity) = store
.upsert(entity)
.toObservable()
override fun upsertTourFileJunction(entity: List<TourFileJunctionEntity>)
: Observable<Iterable<TourFileJunctionEntity>> = store.upsert(entity).toObservable()
override fun upsertTourFileJunction(entity: TourFileJunctionEntity) = store
.upsert(entity)
.toObservable()
override fun observableTourDownloadState() = store
.select(TourPreviewEntity::class)
.where(TourPreviewEntity.DOWNLOADED.notIn(arrayListOf(DownloadState.NotDownloaded, DownloadState.MetaPreparation)))
.get()
// .flowable()
// .
.observableResult()
.flatMap { it.observable() }
.toFlowable(BackpressureStrategy.BUFFER)
.subscribeOn(Schedulers.io())
override fun getFileEntitysOrDefault(uri: RevisionString) = store
.select(FileEntity::class)
.where(FileEntity.URI.eq(uri))
.get()
.firstOr {
val entity = FileEntity()
entity.setUri(uri)
entity.setDownloadedSize(0)
entity.setTotalSize(0)
entity.setDownloaded(false)
entity
}
override fun getFileEntity(uri: RevisionString) = store
.select(FileEntity::class)
.where(FileEntity.URI.eq(uri))
.get()
override fun getTourFiles(entity : TourPreviewEntity ) =store
.select(TourFileJunctionEntity::class)
.where(TourFileJunctionEntity.TOUR.eq(entity.id))
.get()
.toList()
override fun getTourFiles(tourId : String) =store
.select(TourFileJunctionEntity::class)
.where(TourFileJunctionEntity.TOUR.eq(tourId))
.get()
.toList()
override fun getTourFilesObservable(tourId : String) =store
.select(TourFileJunctionEntity::class)
.where(TourFileJunctionEntity.TOUR.eq(tourId))
.get()
override fun pushFileEntities(uris: List<FileEntity>) = store
.select(FileEntity::class)
.where(FileEntity.URI.`in`(uris.map { it.uri }.toList()))
.get()
.observable()
.toList()
.map {existedUris-> uris.filter{!existedUris.map {file -> file.uri.revisionUri() }.contains(it.uri.revisionUri()) }.toList()}
.flatMapObservable{ store.insert(it).toObservable() }
override fun fetchFileEntityes(uris: List<RevisionString>) = store
.select(FileEntity::class)
.where(FileEntity.URI.`in`(uris))
.get()
.toList()
override fun fetchTouresWithStates(states: List<DownloadState>) = store
.select(TourPreviewEntity::class)
.where(TourPreviewEntity.DOWNLOADED.`in`(states))
.get()
.toList()
override fun getDownloadedSumFileEntityes(uris: List<RevisionString>) = store
.select(FileEntity::class)
.where(FileEntity.URI.`in`(uris))
.get()
.map{ it.downloadedSize }
.sum()
// .observable()
override fun flowableFileEntityes(uris: List<RevisionString>) = store
.select(FileEntity::class)
.where(FileEntity.URI.`in`(uris))
.get()
.flowable()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
override fun upsertFileEntity(entity: FileEntity) = store
.upsert(entity)
.toObservable()
override fun upsertFileEntity(entity: List<FileEntity>) = store
.upsert(entity)
.toObservable()
override fun getTourFilesJunction(tourId:String) = store
.select(TourFileJunctionEntity::class)
.where(TourFileJunctionEntity.TOUR.eq(tourId))
.get()
override fun getTourFilesJunctionUniqueFiles(tourId:String):
List<TourFileJunctionEntity?> =
store
.raw(TourFileJunctionEntity::class,
"SELECT tfj.* from TourFileJunction tfj " +
"join TourFileJunction tfj2 on tfj2.file = tfj.File " +
"WHERE tfj2.tour = $tourId " +
"GROUP BY tfj.file " +
"HAVING COUNT(tfj.file) < 2"
)
.toList()
private fun <E:Persistable> deleteBlocking(entity:E) = store
.delete(entity)
.blockingAwait()
private fun <E:List<Persistable>> deleteBlocking(entity:E) = store
.delete(entity)
.blockingAwait()
override fun deleteTourFilesJunction(entity: List<TourFileJunctionEntity>) =
deleteBlocking(entity)
override fun deleteTourFilesJunction(tourId:String) = store
.delete(TourFileJunctionEntity::class)
.where(TourFileJunctionEntity.TOUR.eq(tourId))
.get()
override fun deleteEstate(entity:EstateEntity) = deleteBlocking(entity)
override fun deleteDeal(entity:DealEntity) = deleteBlocking(entity)
override fun deleteEstate(entity:List<EstateEntity>) = deleteBlocking(entity)
override fun deleteDeal(entity:List<DealEntity>) = deleteBlocking(entity)
override fun deleteFile(entity:FileEntity) = deleteBlocking(entity)
override fun deleteFiles(entity:List<FileEntity>) = deleteBlocking(entity)
override fun deleteTourPreview(entity: TourPreviewEntity) = deleteBlocking(entity)
override fun deleteSubscriptions(entities: List<Subscription>) =
deleteBlocking(entities)
override fun deleteTourPreview(id:String) = store
.delete(TourPreview::class)
.where(TourPreviewEntity.ID.eq(id))
.get()
.single()
.blockingGet()
///endregion
} }
package com.biganto.visual.roompark.data.repository.db.requrey.model package com.biganto.visual.roompark.data.repository.db.requrey.model
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreview
import io.requery.* import io.requery.*
/** /**
...@@ -14,6 +15,7 @@ interface Estate : Persistable { ...@@ -14,6 +15,7 @@ interface Estate : Persistable {
val id: Int val id: Int
val type: String val type: String
val number: String val number: String
val available: Boolean
@get:Nullable @get:Nullable
val sectionBegin: Int? val sectionBegin: Int?
@get:Nullable @get:Nullable
...@@ -38,6 +40,11 @@ interface Estate : Persistable { ...@@ -38,6 +40,11 @@ interface Estate : Persistable {
@get:Nullable @get:Nullable
val multitourId: Int? val multitourId: Int?
@get:Nullable
@get:OneToMany(mappedBy = "estate")
val tours: Set<TourPreview>?
@get:Nullable @get:Nullable
val multitourPreview: String? val multitourPreview: String?
...@@ -73,6 +80,6 @@ interface Estate : Persistable { ...@@ -73,6 +80,6 @@ interface Estate : Persistable {
@get:Nullable @get:Nullable
@get:Column(name = "UserContainer") @get:Column(name = "UserContainer")
@get:ForeignKey(references = User::class ) @get:ForeignKey(references = User::class )
@get:OneToOne(mappedBy = "uuid",cascade = [CascadeAction.NONE]) @get:ManyToOne(cascade = [CascadeAction.NONE])
var user:User? var user:User?
} }
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey.model
import com.biganto.visual.roompark.data.repository.api.biganto.IBigantoMobileApi.Companion.BASE_URL
import com.biganto.visual.roompark.data.repository.api.biganto.raw.LiteTourMetaRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourFileRaw
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.utils.RevisionStringConverter
import com.biganto.visual.roompark.data.repository.file.FileModule
import io.requery.*
import java.io.File
import java.math.BigInteger
import java.security.MessageDigest
/**
* Created by Vladislav Bogdashkin on 15.06.2018.
*/
val domainPredicthMatcher = "^(http|https)://".toRegex()
fun toInitialName(fullPath: String) = String.format(
"%032x"
, BigInteger(
1
, MessageDigest.getInstance("MD5").digest(fullPath.toByteArray(Charsets.UTF_8))
)
)
@Entity
interface File :Persistable {
// @get:Key
// var id: String
// @get:Convert(URIConverter::class)
@get:Key
@get:Convert(RevisionStringConverter::class)
val uri: RevisionString
val totalSize: Long
val downloadedSize: Long
@get:Nullable
val isDownloaded: Boolean
@get:Nullable
val destination: String
}
fun fromRaw(raw: TourFileRaw, meta: LiteTourMetaRaw):FileEntity {
val entity = FileEntity()
entity.setDownloaded(false)
entity.setDownloadedSize(0L)
entity.setTotalSize(raw.size)
var fileUri=raw.url
domainPredicthMatcher.containsMatchIn(raw.url)
val bui = raw.url.indexOf(meta.baseurl)
if (bui>=0) fileUri = toInitialName(meta.baseurl)
.plus(File.separator)
.plus(raw.url.removeRange(0,bui+meta.baseurl.length))
val tui = raw.url.indexOf(meta.tour_baseurl)
if (tui>=0) fileUri = toInitialName(meta.tour_baseurl)
.plus(File.separator)
.plus(raw.url.removeRange(0,tui+meta.tour_baseurl.length))
entity.setUri(RevisionString(FileModule.assetsLocalPath(fileUri)))
entity.setDestination(
if (domainPredicthMatcher.containsMatchIn(raw.url)) raw.url
else BASE_URL.plus(raw.url)
)
return entity
}
fun fromRaw(raw: List<TourFileRaw>, meta: LiteTourMetaRaw): List<FileEntity> =
List(raw.size) { index-> fromRaw(raw[index],meta) }
...@@ -15,8 +15,9 @@ interface Subscription : Persistable { ...@@ -15,8 +15,9 @@ interface Subscription : Persistable {
@get:ForeignKey(references = User::class, referencedColumn = "uuid") @get:ForeignKey(references = User::class, referencedColumn = "uuid")
@get:ManyToOne @get:ManyToOne
val owner: User val owner: User
val deviceToken: String
val topic: String val topic: String
val number: String
@get:Nullable
val number: String? //estateId or smth same, depends on subscription case
val state: Boolean val state: Boolean
} }
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey.model
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.utils.RevisionStringConverter
import io.requery.*
/**
* Created by Vladislav Bogdashkin on 15.06.2018.
*/
@Entity
interface TourFileJunction :Persistable {
@get:Key
@get:Generated
val id: Int
// @get:OneToOne( cascade = [CascadeAction.NONE])
// val tour: TourPreview
//+@get:ForeignKey(delete = ReferentialAction.NO_ACTION, update = ReferentialAction.NO_ACTION, references = TourPreviewEntity::class)
val tour: String
// @get:OneToOne( cascade = [CascadeAction.NONE])
// val file: File
//@get:ForeignKey(delete = ReferentialAction.NO_ACTION, update = ReferentialAction.NO_ACTION, references = FileEntity::class)
@get:Convert(RevisionStringConverter::class)
val file: RevisionString
}
package com.biganto.visual.roomparkvr.data.repository.db.requery.model
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourPreviewRaw
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.model.Estate
import com.biganto.visual.roompark.data.repository.db.requrey.model.EstateEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.domainPredicthMatcher
import com.biganto.visual.roompark.data.repository.db.requrey.utils.IntListConverter
import com.biganto.visual.roompark.data.repository.db.requrey.utils.IsoDateConverter
import com.biganto.visual.roompark.data.repository.db.requrey.utils.RevisionStringConverter
import io.requery.*
import java.util.*
import kotlin.collections.ArrayList
import kotlin.math.abs
/**
* Created by Vladislav Bogdashkin on 15.06.2018.
*/
@Entity
interface TourPreview :Persistable {
@get:Key
val id: String
@get:ForeignKey(references = Estate::class)
@get:ManyToOne
var estate: Estate
//Obsolete
// @get:ForeignKey(references = Tour::class)
// @get:OneToOne(mappedBy = "id", cascade = arrayOf(CascadeAction.SAVE))
// var tour: Tour?
@get:Convert(IsoDateConverter::class)
val created: Date
@get:Convert(IsoDateConverter::class)
val updated: Date
val type: String
val screen: String
val title: String
val preview: String?
val hidden: Boolean
var isDownloaded: DownloadState
var overallSize:Long
var tempSize:Long
var downloadedSize:Long
var overallFiles:Int
var downloadedFiles:Int
var targetResolution : Int
@get:Convert(IntListConverter::class)
var resolutions:ArrayList<Int>
// @get:JunctionTable(name= "TourFilesRule")
// @get:ManyToMany( cascade = arrayOf(CascadeAction.NONE))
// @get:Convert(RevisionStringListConverter::class)
// var tourFiles: ArrayList<RevisionString>?
//
@get:Convert(RevisionStringConverter::class)
val metaFileEntityId: RevisionString?
val tourBaseUrl: String?
val footageBaseUrl: String?
}
fun fromRaw(raw: TourPreviewRaw, parentId:Int, baseUrl:String, userResolution:Int):TourPreviewEntity{
val tour=TourPreviewEntity()
tour.setId(raw.id.toString())
tour.setCreated(raw.created)
tour.setUpdated(raw.updated)
tour.setType(raw.type)
tour.setTitle(raw.title)
tour.setPreview(
if (domainPredicthMatcher.containsMatchIn(raw.preview)) raw.preview
else "$baseUrl${raw.preview}"
)
tour.setScreen(
if (domainPredicthMatcher.containsMatchIn(raw.screen)) raw.screen
else "$baseUrl${raw.screen}"
)
tour.setHidden(raw.hidden)
tour.resolutions= if (raw.resolutions != null && !raw.resolutions.isNullOrEmpty()) ArrayList(raw.resolutions)
else ArrayList(1024)
tour.targetResolution=raw.resolutions?.nearestResolution(userResolution)?:1024
val estate= EstateEntity()
estate.setId(parentId)
tour.estate=estate
tour.isDownloaded=DownloadState.NotDownloaded
tour.overallSize=0L
tour.downloadedSize=0L
tour.tempSize=0L
tour.overallFiles=0
tour.downloadedFiles=0
// val tourMeta= TourEntity()
// tourMeta.setId(raw.id.toString())
// tour.tour=tourMeta
return tour
}
fun List<Int>.nearestResolution(targetResolution:Int ):Int{
var lastScore=Int.MAX_VALUE
var lastRes=0
for (res in this){
var d = targetResolution-res
if (d>0) d*=2
d=abs(d)
if (d<lastScore){lastScore=d;lastRes=res}
}
// Timber.d("Resolutions: $this / t=$targetResolution final=$lastRes score=$lastScore")
return lastRes
}
fun fromRaw(raw: List<TourPreviewRaw>, parentId:Int, url:String, userResolution:Int):List<TourPreviewEntity> =List(raw.size) { index-> fromRaw(raw[index],parentId,url,userResolution) }
enum class DownloadState(val i:Int):Comparable<DownloadState>{
NotDownloaded(0),
MetaPreparation(1),
DownloadQueue(2),
Downloading(3),
Suspended(4),
Downloaded(5),
Crushed(6),
Deleting(7),
NotSynced(20);
}
...@@ -24,5 +24,14 @@ interface User : Persistable { ...@@ -24,5 +24,14 @@ interface User : Persistable {
@get:Nullable @get:Nullable
@get:OneToMany(cascade = [CascadeAction.DELETE]) @get:OneToMany(cascade = [CascadeAction.DELETE])
val deals:List<Deal>? val deals:List<Deal>?
@get:Nullable
@get:OneToMany(cascade = [CascadeAction.DELETE])
val estates:List<Estate>?
@get:Nullable
@get:OneToMany(cascade = [CascadeAction.DELETE])
val subscriptions:List<Subscription>?
} }
package com.biganto.visual.roompark.data.repository.db.requrey.utils;
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString;
import io.requery.Converter;
/**
* Created by Vladislav Bogdashkin on 04.07.2018.
*/
public class RevisionStringConverter implements Converter<RevisionString, String> {
@SuppressWarnings("unchecked")
@Override
public Class<RevisionString> getMappedType() {
return RevisionString.class;
}
@Override
public Class<String> getPersistedType() {
return String.class;
}
@Override
public Integer getPersistedSize() {
return null;
}
@Override
public String convertToPersisted(RevisionString value) {
return value == null ? null : value.revisionUri();
}
@Override
public RevisionString convertToMapped(Class<? extends RevisionString> type,
String value) {
return value == null ? null : new RevisionString(value);
}
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey.utils;
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString;
import java.util.ArrayList;
import io.requery.Converter;
public class RevisionStringListConverter implements Converter<ArrayList<RevisionString>, String> {
private static final String stringDelimeter=">RS<";
@SuppressWarnings("unchecked")
@Override
public Class<ArrayList<RevisionString>> getMappedType() {
return (Class)ArrayList.class;
}
@Override
public Class<String> getPersistedType() {
return String.class;
}
@Override
public Integer getPersistedSize() {
return null;
}
@Override
public String convertToPersisted(ArrayList<RevisionString> value) {
if (value == null) {
return "";
}
StringBuilder sb = new StringBuilder();
int index = 0;
for (RevisionString str: value) {
if (index > 0) {
sb.append(stringDelimeter);
}
sb.append(str.revisionUri());
index++;
}
return sb.toString();
}
@Override
public ArrayList<RevisionString> convertToMapped(Class<? extends ArrayList<RevisionString>> type,
String value) {
ArrayList<RevisionString> list = new ArrayList<>();
if (value != null && !value.isEmpty())
for (String s : value.split(stringDelimeter))
list.add(new RevisionString(s));
return list;
}
}
...@@ -8,6 +8,7 @@ import com.google.gson.JsonElement ...@@ -8,6 +8,7 @@ import com.google.gson.JsonElement
import dagger.Module import dagger.Module
import io.reactivex.Observable import io.reactivex.Observable
import kotlinx.io.IOException import kotlinx.io.IOException
import okio.Okio
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
...@@ -33,8 +34,13 @@ class FileModule @Inject constructor(val context: Application) { ...@@ -33,8 +34,13 @@ class FileModule @Inject constructor(val context: Application) {
fun getFile(fileUri: String): File { fun getFile(fileUri: String): File {
try { try {
val fileName =if (fileUri.contains("/")) fileUri.substring(fileUri.lastIndexOf("/")) else fileUri val fileName =
val fileDir = if (fileUri.contains("/")) fileUri.substring(0, fileUri.lastIndexOf("/")) else "" if (fileUri.contains("/")) fileUri.substring(fileUri.lastIndexOf("/"))
else fileUri
val fileDir =
if (fileUri.contains("/"))
fileUri.substring(0, fileUri.lastIndexOf("/"))
else ""
val directory = File(rootFolder, fileDir) val directory = File(rootFolder, fileDir)
directory.mkdirs() directory.mkdirs()
...@@ -53,15 +59,45 @@ class FileModule @Inject constructor(val context: Application) { ...@@ -53,15 +59,45 @@ class FileModule @Inject constructor(val context: Application) {
file.writeText("[$jsonElement]") //to json array because core unity method parse data like TourData[] Estate[] etc.. file.writeText("[$jsonElement]") //to json array because core unity method parse data like TourData[] Estate[] etc..
} }
fun saveFileToDiskObservable(file:File,content: String):Observable<Long> {
file.parentFile.mkdirs()
return Observable.create { emitter ->
val fileStorage = file
val sink = Okio.buffer(Okio.sink(fileStorage))
val buffer = sink.buffer()
var read = 0L
val step = 8192
val source =
content.byteInputStream()
var bytesRead = 0L
Timber.w("start read ")
file.writeText(content)
Timber.w("butes read ")
// sink.flush()
System.gc()
emitter.onNext(bytesRead)
// emitter.onComplete()
}
}
fun saveFileToDisk(file:File,content: String){ fun saveFileToDisk(file:File,content: String){
Timber.d("write to : $file") Timber.d("write to : $file")
Timber.d("write to : ${file.name}")
// file.createNewFile() // file.createNewFile()
file.parentFile.mkdirs() file.parentFile.mkdirs()
file.writeText(content) //to json array because core unity method parse data like TourData[] Estate[] etc.. file.writeText(content) //to json array because core unity method parse data like TourData[] Estate[] etc..
} }
fun deleteFile(uri:String)= getFile(uri).delete() // fun deleteFile(uri:String)= getFile(uri).delete()
fun deleteAssetFile(uri:String) =
getAssetFile(uri).delete()
fun getAssetFile(uri:String) =
File(assetsDirectory(context).plus(uri))
fun deleteAllCacheObservable() = fun deleteAllCacheObservable() =
Observable.create<Pair<Int, Int>> {emitter -> Observable.create<Pair<Int, Int>> {emitter ->
...@@ -80,18 +116,26 @@ class FileModule @Inject constructor(val context: Application) { ...@@ -80,18 +116,26 @@ class FileModule @Inject constructor(val context: Application) {
} }
val getCoreCacheDirectory:String val getCoreCacheDirectory:String
get(){ get(){
return rootFolder.absolutePath return rootFolder.absolutePath
} }
val freeSpace = rootFolder.freeSpace
companion object { companion object {
fun getDirectory(context: Context, dirType: FileDirectory): File = fun getDirectory(context: Context, dirType: FileDirectory): File =
File(context.filesDir.absolutePath.plus(dirType.dir)) File(context.filesDir.absolutePath.plus(dirType.dir))
fun assetsDirectory(context: Context): String = context.filesDir.absolutePath fun assetsDirectory(context: Context): String =
getDirectory(context,FileDirectory.ToursDir(BIGANTO_TOURS_FOLDER)).absolutePath
fun assetsFile(context: Context): File =
getDirectory(context,FileDirectory.ToursDir(BIGANTO_TOURS_FOLDER))
fun assetsLocalPath(uri:String): String =
FileDirectory.ToursDir(BIGANTO_TOURS_FOLDER).dir.plus(File.separator).plus(uri)
fun assetsFile(context: Context): File = context.filesDir.absoluteFile private const val BIGANTO_TOURS_FOLDER = "biganto"
} }
......
package com.biganto.visual.roompark.data.repository.mapper package com.biganto.visual.roompark.data.repository.mapper
import android.content.res.Resources import android.content.res.Resources
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.* import com.biganto.visual.roompark.data.repository.api.room_park.raw.*
import com.biganto.visual.roompark.data.repository.db.requrey.PhotoResolutions import com.biganto.visual.roompark.data.repository.db.requrey.PhotoResolutions
import com.biganto.visual.roompark.data.repository.db.requrey.TitledPhoto import com.biganto.visual.roompark.data.repository.db.requrey.TitledPhoto
import com.biganto.visual.roompark.data.repository.db.requrey.model.* import com.biganto.visual.roompark.data.repository.db.requrey.model.*
...@@ -113,11 +113,11 @@ fun fromRaw(raw:DealRaw):DealEntity { ...@@ -113,11 +113,11 @@ fun fromRaw(raw:DealRaw):DealEntity {
} }
fun fromRaw(raw:DealRaw,user:UserEntity):DealEntity { fun fromRaw(raw:DealRaw,user:UserEntity):DealEntity =
val entity = fromRaw(raw) fromRaw(raw).apply {
entity.user = user this.user = user
return entity this.estate.user = user
} }
...@@ -126,6 +126,7 @@ fun fromRaw(raw:EstateRaw):EstateEntity{ ...@@ -126,6 +126,7 @@ fun fromRaw(raw:EstateRaw):EstateEntity{
entity.setId(raw.id) entity.setId(raw.id)
entity.setType(raw.type) entity.setType(raw.type)
entity.setNumber(raw.number) entity.setNumber(raw.number)
entity.setAvailable(raw.available)
entity.setSectionBegin(raw.common_info.section_begin) entity.setSectionBegin(raw.common_info.section_begin)
entity.setSectionEnd(raw.common_info.section_end) entity.setSectionEnd(raw.common_info.section_end)
entity.setPlanJpgUrl(raw.plan_jpg?.url) entity.setPlanJpgUrl(raw.plan_jpg?.url)
......
package com.biganto.visual.roompark.data.service.download
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.model.FileEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.TourFileJunctionEntity
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
/**
* Created by Vladislav Bogdashkin on 14.04.2020.
*/
data class TourFileData(
val fileUrl: RevisionString,
val tourId: String,
var tempDownloadedSize: Long = 0L,
var tempOverallFileSize: Long = 0L,
var fileDownloadedSize: Long = 0L,
var tempTourTotalDiff: Long = 0L,
var isDownloaded: Boolean = false,
val fatalState: DownloadState? = null,
var destination: String
) {
constructor(entity: FileEntity, junction: TourFileJunctionEntity) : this(
fileUrl = junction.file
, tourId = junction.tour
, tempDownloadedSize = 0L
, tempOverallFileSize = entity.totalSize
, fileDownloadedSize = entity.downloadedSize
, tempTourTotalDiff = 0L
, isDownloaded = entity.isDownloaded
, destination = entity.destination
)
}
package com.biganto.visual.roompark.data.service.download
import android.app.Service
import android.content.Context
import android.content.Intent
import android.media.MediaScannerConnection
import android.os.IBinder
import androidx.core.math.MathUtils.clamp
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.data.repository.api.biganto.IBigantoApi
import com.biganto.visual.roompark.data.repository.api.biganto.raw.LiteTourMetaRaw
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.model.FileEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.TourFileJunctionEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.fromRaw
import com.biganto.visual.roompark.data.repository.db.requrey.model.toInitialName
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.data.service.lifecycle.ForegroundLifecycleObserver
import com.biganto.visual.roompark.data.service.notification.INotificationCenter
import com.biganto.visual.roompark.domain.use_case.TOUR_IDS_TO_DOWNLOAD_KEY
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import com.google.gson.Gson
import com.google.gson.JsonParser
import com.jakewharton.rxrelay2.PublishRelay
import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import okio.Okio
import timber.log.Timber
import java.io.File
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import javax.inject.Singleton
/**
* Created by Vladislav Bogdashkin on 13.04.2020.
*/
private const val DB_ACCESS_CHUNK_SIZE = 256
private const val READ_SYNC_MILLS = 120L
private const val DEQUE_REQUEST_TIMEOUT_MILLS=100L
private const val META_PREDICTION="/tourMeta_"
private const val META_FILE_TYPE=".json"
const val DOWNLOAD_MANAGER_COMMAND_KEY = "TOURS_DOWNLOAD_MANAGER_COMMAND"
const val DOWNLOAD_MANAGER_ADD_IDS_TO_LOAD_COMMAND = "ADD_TOUR_IDS_TO_QUEUE"
const val DOWNLOAD_MANAGER_STOP_COMMAND = "STOP_SERVICE"
@Singleton
class DownloadManagerService @Inject constructor(
): Service() {
//region define dependencies
@Inject
lateinit var db: IDb
@Inject
lateinit var api: IBigantoApi
@Inject
lateinit var fileModule: FileModule
@Inject
lateinit var context: Context
@Inject
lateinit var notificationsCenter: INotificationCenter
@Inject
lateinit var appLifeCycle: ForegroundLifecycleObserver
//endregion
//region downloading flow
private val disposable = CompositeDisposable()
private val downloadQueue = ArrayDeque<String>()
private val deletingQueue = ArrayDeque<String>()
private var activeDownloading: AtomicBoolean = AtomicBoolean(false)
private fun downloadTourFiles(data: TourPreviewEntity) {
if (!downloadQueue.contains(data.id))
downloadQueue.add(data.id)
}
private fun deleteTourFiles(data: TourPreviewEntity) {
if (!deletingQueue.contains(data.id))
deletingQueue.add(data.id)
}
//endregion
init {
db = RoomParkApplication.component.providedb()
api = RoomParkApplication.component.provideBigantoApi()
context = RoomParkApplication.component.provideAppContext()
fileModule = RoomParkApplication.component.provideFileSystem()
appLifeCycle = RoomParkApplication.component.provideLifeCycle()
notificationsCenter = RoomParkApplication.component.provideNotifivations()
}
override fun onCreate() {
super.onCreate()
Timber.d("START SERVICE AND SEND NOTIFICATION")
startForeground(notificationsCenter.foregroundDownloadServiceChannelId
, notificationsCenter.foregroundDownloadServiceNotification)
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStart(intent: Intent?, startId: Int) {
Timber.d("Got intent: $intent")
val v = intent?.extras?.getString(DOWNLOAD_MANAGER_COMMAND_KEY)
Timber.d("Got intent: $v")
if (v == DOWNLOAD_MANAGER_STOP_COMMAND){
Timber.d("Got DOWNLOAD_MANAGER_STOP_COMMAND")
disposable.clear()
stopSelf()
}
super.onStart(intent, startId)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val v = intent?.extras?.getString(DOWNLOAD_MANAGER_COMMAND_KEY)
if (v == DOWNLOAD_MANAGER_STOP_COMMAND){
disposable.clear()
stopSelf()
}
// Timber.d(" GOT INTENT $intent with action ${intent?.action}")
// if (intent?.action == NOTIFICATION_INTENT_STOP_SERVICE_ACTION)
// {
// stopForeground(false)
// stopSelf()
// }
val command = intent?.getStringExtra(DOWNLOAD_MANAGER_COMMAND_KEY)
if (command == DOWNLOAD_MANAGER_ADD_IDS_TO_LOAD_COMMAND){
intent.getStringArrayListExtra(TOUR_IDS_TO_DOWNLOAD_KEY)
?.forEach { toursToDownloadObserver.accept(it) }
}
Timber.d(" ON START COMMAND,${disposable.size()}")
if (disposable.size() == 0)
attachDownloader()
return START_NOT_STICKY
}
override fun onDestroy() {
disposable.clear()
downloadQueue.forEach { id -> setTourStatus(id, DownloadState.NotDownloaded) }
downloadQueue.clear()
deletingQueue.forEach { id ->
val tour = db.getTourPreview(id).observable().blockingSubscribe { tour ->
setTourStatus(id,
if (tour.downloadedSize == tour.overallSize)
DownloadState.Downloaded
else DownloadState.Suspended
)
}
}
deletingQueue.clear()
super.onDestroy()
}
private fun setTourStatus(id:String,state:DownloadState) =
db.getTourPreview(id)
.observable()
.map { it.apply { isDownloaded = state } }
.flatMap { db.upsert(it).toObservable() }
.blockingSubscribe()
private fun writeFile(response: ResponseBody, model: TourFileData): Observable<TourFileData> {
return Observable.create<TourFileData> { sub ->
try {
if (model.fatalState == DownloadState.Crushed) {
sub.onNext(model)
sub.onComplete()
}
val fileStorage = fileModule.getFile(model.fileUrl.uri())
val sink = Okio.buffer(Okio.appendingSink(fileStorage))
val buffer = sink.buffer()
var read = 0L
val step = 8192
val source = response.source()
var timer = System.currentTimeMillis()
var stop: Boolean = false
sink.use {
while (!stop && { read = source.read(buffer, step.toLong());read }() != -1L) {
model.tempDownloadedSize += read
model.fileDownloadedSize += read
if (model.tempOverallFileSize == 0L)
model.tempTourTotalDiff += model.tempDownloadedSize
model.isDownloaded = (source.exhausted()
&& (model.fileDownloadedSize == model.tempOverallFileSize
|| model.tempOverallFileSize == 0L))
}
}
model.isDownloaded = (source.exhausted()
&& (model.fileDownloadedSize == model.tempOverallFileSize
|| model.tempOverallFileSize == 0L))
sub.onNext(model.copy())
model.tempTourTotalDiff = 0
model.tempDownloadedSize = 0
sub.onComplete()
sink.close()
// refreshGallery(file) //-> обновляем представление файлов в файловой системе android (чтобы их можно было просматривать через explorer и видеть размер, корректно открывать и т.п.)
} catch (e: Exception) {
Timber.e(e)
if (!sub.isDisposed)
sub.onError(e)
setTourStatus(model.tourId,DownloadState.Crushed)
}
}
}
private fun mergeFiles(files: List<FileEntity>) {
Timber.d("Merge files")
files.forEach { file ->
val entity: FileEntity? = db.getFileEntity(file.uri).firstOrNull()
entity?.let {
file.setDownloaded(it.isDownloaded)
file.setDownloadedSize(it.downloadedSize)
file.setTotalSize(it.totalSize)
}
}
Timber.d("save files: ${files.size}")
files.chunked(DB_ACCESS_CHUNK_SIZE).forEach {chunk ->
db.upsertFileEntity(chunk).blockingSubscribe { Timber.d("file saved ${chunk.size}") }
}
}
private fun setDownloadInfo(
id: String,
downloadedSize: Long? = null
, downloadedDiffSize: Long? = null
, totalSize: Long? = null
, resolution: Int? = null
, filesCount: Int? = null
, tempLoadedFiles: Int? = null
, totalSizedDiffSize: Long? = null
) =
db.getTourPreview(id).observable().map {entity ->
entity.also {
downloadedSize?.let { entity.downloadedSize = it }
downloadedDiffSize?.let { entity.downloadedSize += it }
totalSize?.let { entity.overallSize = it }
resolution?.let { entity.targetResolution = it }
filesCount?.let { entity.overallFiles = it }
tempLoadedFiles?.let { entity.downloadedFiles += it }
totalSizedDiffSize?.let { entity.overallSize += it }
if (entity.downloadedFiles == entity.overallFiles)
entity.isDownloaded = DownloadState.Downloaded
}
}
.flatMap { db.upsert(it).toObservable() }
private fun flowableFilesDownloading(tour: TourPreviewEntity) =
api.getTourFiles(tour.id, tour.targetResolution.toString())
.map { it.first() }
.flatMap { raw ->
var downloadedSize = 0L
var totalSize = 0L
val meta = getLocalMeta(tour)
val fileEntities = fromRaw(raw.files,meta)
mergeFiles(fileEntities)
val jlist = db.getTourFilesJunction(tour.id).toList()
Timber.d("jlist:: ${jlist?.size} start ${jlist?.firstOrNull()?.id}")
val junctionList = fileEntities
.map {file ->
val entity = jlist.firstOrNull{it.tour == tour.id && it.file.uri() == file.uri.uri()}
?: TourFileJunctionEntity().apply {
setTour(tour.id)
setFile(file.uri)
}
downloadedSize += file.downloadedSize
totalSize += file.totalSize
entity
}
Timber.d("junctionList:: ${junctionList.size} start ${junctionList.first().id}")
setDownloadInfo(
raw.id.toString()
, tempLoadedFiles = 0
, downloadedSize = downloadedSize
, totalSize = totalSize
, resolution = raw.resolution
, filesCount = raw.files.count()
).map { junctionList }
}
.flatMap{ db.upsertTourFileJunction(it) }
.flatMapIterable { it }
.flatMap { junction ->
db.getFileEntity(junction.file)
.observable()
.map { entity -> TourFileData(entity,junction) }
}
.toFlowable(BackpressureStrategy.BUFFER)
// .map(::validateFile)
.parallel(clamp(Runtime.getRuntime().availableProcessors() - 2, 2, 4))
.runOn(Schedulers.io())
.flatMap { model ->
if (model.isDownloaded) return@flatMap Flowable.just(model)
var header: HashMap<String, String>? = null
if (model.fileDownloadedSize > 0)
header = hashMapOf(Pair("Range", "bytes=${model.fileDownloadedSize}-"))
api.downloadFile(model.destination, header)
.doOnError {
Timber.e(it)
setTourStatus(model.tourId, DownloadState.Crushed)
}
.flatMap<TourFileData> {
writeFile(it, model)
.toFlowable(BackpressureStrategy.BUFFER)
.doOnCancel { Timber.d("CANCELLED") }
}
}
.flatMap { downloadInfo ->
db.upsertFileEntity(
FileEntity().apply {
setUri(downloadInfo.fileUrl)
setDownloadedSize(downloadInfo.fileDownloadedSize)
setTotalSize(downloadInfo.tempOverallFileSize)
setDownloaded(downloadInfo.isDownloaded)
}
)
.toFlowable(BackpressureStrategy.BUFFER)
.map { downloadInfo }
}
.sequential()
.toObservable()
.observeOn(Schedulers.computation())
// .doOnNext{Timber.d("7 ${it}")}
.flatMap { model ->
setDownloadInfo(
model.tourId
, totalSizedDiffSize = model.tempTourTotalDiff
, downloadedDiffSize = model.tempDownloadedSize
, tempLoadedFiles = if (model.isDownloaded) 1 else null
)
.map {
model.tempDownloadedSize = 0
model.tourId
}
}
// .delay(12L, TimeUnit.MILLISECONDS)
private val checkService =
Observable.interval(0L, DEQUE_REQUEST_TIMEOUT_MILLS, TimeUnit.MILLISECONDS)
.filter { hasTasks }
.filter { !activeDownloading.get() }
.switchMap<String> {
when {
deletingQueue.isNotEmpty() -> {
notifyDeleting()
deleteTourFromQueue()
}
downloadQueue.isNotEmpty() -> {
notifyDownloading()
downloadTourFromQueue()
}
else -> {
Timber.e("Empty queues!");return@switchMap Observable.empty()
}
}
}
private fun downloadTourFromQueue(): Observable<String> =
Observable.fromCallable { activeDownloading.set(true);downloadQueue.poll() }
.doOnNext { Timber.d("to load tour: ${it}") }
.flatMap { db.getTourPreview(it).observable() }
.filter {
Timber.d("to load tour: ${it.isDownloaded} ${it.id}")
val forward = it.isDownloaded != DownloadState.Downloaded
activeDownloading.set(forward)
Timber.d("to load tour: $forward")
if (!forward)
notifyDownloadProgress()
forward
}
.flatMap { tour ->
flowableFilesDownloading(tour)
.doFinally {
activeDownloading.set(false)
notifyDownloadProgress()
}
}
.map { it }
.subscribeOn(Schedulers.single())
private fun deleteTourFromQueue() =
Observable.fromCallable { activeDownloading.set(true);deletingQueue.poll() }
.flatMap { db.getTourPreview(it).observable() }
.filter {
val forward = it.isDownloaded == DownloadState.Deleting
activeDownloading.set(forward)
forward
}
.map { it.id }
.doOnNext {
deleteTourSync(it)
}
.onErrorResumeNext(Observable.empty())
.doFinally {
activeDownloading.set(false)
notifyDeleteProgress()
}
.subscribeOn(Schedulers.single())
private fun deleteTourSync(tourId:String) {
try {
db.getTourFilesJunctionUniqueFiles(tourId)
.filterNotNull()
.let { list ->
list.asSequence()
.map{ FileEntity().apply { setUri(it.file) } }
.chunked(DB_ACCESS_CHUNK_SIZE)
.forEach { db.deleteFiles( it) }
list.forEach { j -> if (!fileModule.deleteAssetFile(j.file.uri()))
Timber.w("Not success to delete file! \n" +
"short uri: ${j.file.uri()} \n" +
"full uri: ${fileModule.getAssetFile(j.file.uri())}")
}
}
val resultRowsDelete = db.deleteTourFilesJunction(tourId).value()
Timber.d("Deleted form TourFile Junction rows $resultRowsDelete")
db.deleteTourPreview(tourId)
} catch (err: java.lang.Exception) {
Timber.e(err)
error("can't delete tour")
}
}
private val hasTasks: Boolean
get() = downloadQueue.isNotEmpty() || deletingQueue.isNotEmpty()
//region Notifications
private fun notifyDownloadProgress() {
if (downloadQueue.isEmpty()) {
notificationsCenter
.completeProgressMessage(
resources.getString(R.string.on_all_tours_downloaded_notification_message)
)
if (!hasTasks) {
stopForeground(false)
stopSelf()
}
} else notifyDownloading()
}
private fun notifyDeleteProgress() {
if (deletingQueue.isEmpty()) {
notificationsCenter
.completeProgressMessage(
resources.getString(R.string.on_all_tours_deleted_notification_message)
)
if (!hasTasks) {
stopForeground(false)
stopSelf()
}
} else notifyDeleting()
}
private fun notifyDeleting() = notificationsCenter
.indeterminateProgressMessage(
progress = 1
, progressMax = (deletingQueue.size + 1)
, message =
String.format(
context.resources.getString(R.string.noty_tours_delete_left)
, deletingQueue.size)
)
private fun notifyDownloading() = notificationsCenter
.indeterminateProgressMessage(
progress = 1
, progressMax = (downloadQueue.size + 1)
, message = String.format(
context.resources.getString(R.string.noty_tours_download_left)
, downloadQueue.size)
)
//endregion Notifications
private val toursToDownloadObserver = PublishRelay.create<String>()
public fun addTourToQueue(id:String) = toursToDownloadObserver.accept(id)
private fun attachDownloader() {
disposable.add(
checkService
.subscribe {
if (!appLifeCycle.isAppForeground
&& !hasTasks
&& !activeDownloading.get()) {
stopForeground(true)
stopSelf()
}
}
)
// disposable.add(touresCache.observableToursForDeleting().subscribe(::deleteTourFiles))
disposable.add(
toursToDownloadObserver
.flatMap { db.getTourPreview(it).observable() }
.filter { it.isDownloaded != DownloadState.Downloaded }
.doOnNext {
it.isDownloaded = DownloadState.Downloading
it.downloadedFiles = 0
it.downloadedSize = 0L
it.tempSize = it.overallSize // <- overall changes due downloading!!
it.overallSize = 0L
}
.observeOn(Schedulers.computation())
.flatMap(::getMeta)
.subscribeOn(Schedulers.io())
.doOnError(Timber::e)
.subscribe { downloadQueue.add(it) }
)
}
private fun getMeta(tour: TourPreviewEntity): Observable<String> =
api.getTourMetaAsString(tour.id)
.map { meta ->
tour.apply {
try {
val jMeta = JsonParser().parse(meta).asJsonArray.first()
val metaLite = Gson().fromJson(jMeta, LiteTourMetaRaw::class.java)
val metaUri = RevisionString("$META_PREDICTION${tour.id}$META_FILE_TYPE")
setMetaFileEntityId(metaUri)
setFootageBaseUrl(toInitialName(metaLite.baseurl))
setTourBaseUrl(toInitialName(metaLite.tour_baseurl))
fileModule.saveFileToDisk(
fileModule.getAssetFile(
File.separator
.plus(tour.footageBaseUrl)
.plus(metaUri.uri())
)
, meta
)
} catch (e: Exception) {
Timber.e(e)
}
}
tour
}
.flatMap { db.upsertTourPreview(it) }
.map { tour.id }
.onErrorReturn {
setTourStatus(tour.id,DownloadState.Crushed)
tour.id
}
//#endregion oldMethod
private fun getLocalMeta(tour: TourPreviewEntity): LiteTourMetaRaw {
return Gson().fromJson(
JsonParser().parse(
fileModule.getAssetFile(
"/${tour.footageBaseUrl}"
.plus("/${tour.metaFileEntityId?.uri()}"
)
).readText()
).asJsonArray.first()
, LiteTourMetaRaw::class.java
)
}
private fun refreshGallery(file: File) {
MediaScannerConnection.scanFile(context, arrayOf(file.path), null
)
{ path, uri ->
{}//Timber.d("Scanned $path")
}
}
}
package com.biganto.visual.roompark.data.service.lifecycle
/**
* Created by Vladislav Bogdashkin on 14.04.2020.
*/
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AppLifecycleListener @Inject constructor(): ForegroundLifecycleObserver {
private var isForeground=false
override val isAppForeground : Boolean
get() = isForeground
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() {
Timber.d("Returning to foreground…")
isForeground=true
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() {
Timber.d("Moving to background…")
isForeground=false
}
}
interface ForegroundLifecycleObserver : LifecycleObserver{
val isAppForeground : Boolean
}
\ No newline at end of file
package com.biganto.visual.roompark.data.service.network package com.biganto.visual.roompark.data.service.network
import android.app.Application
import android.content.Context import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
...@@ -9,7 +11,7 @@ import javax.inject.Singleton ...@@ -9,7 +11,7 @@ import javax.inject.Singleton
* Created by Vladislav Bogdashkin on 12.10.2018. * Created by Vladislav Bogdashkin on 12.10.2018.
*/ */
@Singleton @Singleton
class LiveNetworkMonitor(val context: Context) : INetworkMonitor { class LiveNetworkMonitor @Inject constructor(val context: Application) : INetworkMonitor {
override val isConnected: Boolean override val isConnected: Boolean
...@@ -23,7 +25,8 @@ class LiveNetworkMonitor(val context: Context) : INetworkMonitor { ...@@ -23,7 +25,8 @@ class LiveNetworkMonitor(val context: Context) : INetworkMonitor {
override val isWifiConnected: Boolean override val isWifiConnected: Boolean
get() { get() {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
return cm.activeNetworkInfo != null && (cm.activeNetworkInfo.type == ConnectivityManager.TYPE_WIFI); return cm.activeNetworkInfo != null
&& (cm.activeNetworkInfo.type == ConnectivityManager.TYPE_WIFI);
} }
} }
......
package com.biganto.visual.roompark.data.service.notification
import android.annotation.TargetApi
import android.app.*
import android.app.PendingIntent.FLAG_ONE_SHOT
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.data.service.download.DOWNLOAD_MANAGER_COMMAND_KEY
import com.biganto.visual.roompark.data.service.download.DOWNLOAD_MANAGER_STOP_COMMAND
import com.biganto.visual.roompark.data.service.download.DownloadManagerService
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import javax.inject.Inject
import javax.inject.Singleton
/**
* Created by Vladislav Bogdashkin on 13.04.2020.
*/
const val ANDROID_CHANNEL_ID = "com.biganto.visual.androidplayer.data.services.downloader.DownloadManagerService.CHANNEL_ID"
const val TOURS_CHANNEL_ID = "com.biganto.visual.androidplayer.data.services.downloader.DownloadManagerService.TOURS_CHANNEL_ID"
const val PUSH_CHANNEL_ID = "com.biganto.visual.androidplayer.data.services.downloader.DownloadManagerService.PUSH_CHANNEL_ID"
const val DOWNLOAD_SERVICE_ID = 7990
const val TOUR_INFO_SERVICE_ID = 7991
const val PUSH_INFO_SERVICE_ID = 7992
const val NOTIFICATION_INTENT="NOTIFICATION_INTENT_KEY"
const val NOTIFICATION_START_SCREEN="NOTIFICATION_START_SCREEN_KEY"
const val NOTIFICATION_INTENT_SCREEN_TYPE="NOTIFICATION_SHOW_SCREEN"
const val NOTIFICATION_INTENT_STOP_SERVICE_ACTION="STOP_DOWNLOADS_SERVICE"
const val PENDING_REQUEST_CODE=0
interface INotificationCenter{
val foregroundDownloadServiceChannelId : Int
val foregroundDownloadServiceNotification: Notification
fun indeterminateProgressMessage(progress:Int=0
,progressMax:Int=0
,message: String)
fun completeProgressMessage(message: String)
fun donwloadServiceProgressNotfication(progress:Int=0
,progressMax:Int=0
,indeterminate:Boolean=true
,message: String)
val IncomingPushServiceNotification: Notification
fun showPushNotifyMessage(message: String)
}
@Singleton
class NotificationCenter @Inject constructor(val context: Application) : INotificationCenter{
private val updateProgressNotificationDelay_Milliseconds= 333
private var lastTimeProgressNotificationUpdated = 0L
private val actualNotifyManager:NotificationManager
get()= context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
createToursNotificationChannel()
createPushNotificationChannel()
}
}
private val builder = NotificationCompat.Builder(context, ANDROID_CHANNEL_ID)
private val toursNotyBuilder = NotificationCompat.Builder(context, TOURS_CHANNEL_ID)
private val pushNotyBuilder = NotificationCompat.Builder(context, PUSH_CHANNEL_ID)
private val notificationSystemColor = ContextCompat.getColor(context, R.color.colorAccent)
private val icon = BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher_round);
//Заготовка под интерфейсы для равзедения каналов нотификаций по разным инстансам
override val foregroundDownloadServiceChannelId = DOWNLOAD_SERVICE_ID
override val foregroundDownloadServiceNotification: Notification =
builder
.setContentTitle(context.getString(R.string.notification_content_title))//getString(R.string.app_name))
.setContentText(context.getString(R.string.notification_content_text))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.mipmap.ic_launcher)
.setColor(notificationSystemColor)
.build()
override val IncomingPushServiceNotification: Notification =
pushNotyBuilder
.setContentTitle(context.getString(R.string.notification_content_title))//getString(R.string.app_name))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.mipmap.ic_launcher)
.setColor(notificationSystemColor)
.build()
//Заготовка под переход в определенный экран приложения по нажатию на нотификацию
private val toDownloadsIntent =
Intent(context, RoomParkMainActivity::class.java)
.putExtra(NOTIFICATION_INTENT,NOTIFICATION_START_SCREEN)
// .putExtra(NOTIFICATION_INTENT_SCREEN_TYPE,R.id.tab_downloads)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
// private val stopServiceIntent=
// Intent(context, DownloadManagerService::class.java)
// .setAction(NOTIFICATION_INTENT_STOP_SERVICE_ACTION)
override fun indeterminateProgressMessage(progress:Int
, progressMax:Int
, message: String) {
if (progress!=0)
if (System.currentTimeMillis()-lastTimeProgressNotificationUpdated
<updateProgressNotificationDelay_Milliseconds)
return
lastTimeProgressNotificationUpdated=System.currentTimeMillis()
donwloadServiceProgressNotfication(progress, progressMax, true, message)
}
override fun completeProgressMessage(message: String){
donwloadServiceProgressNotfication(indeterminate = false,message = message)
}
override fun showPushNotifyMessage(message: String){
val pendingIntent = PendingIntent.getActivity(context
, PENDING_REQUEST_CODE
, toDownloadsIntent
, FLAG_ONE_SHOT
)
val noty = pushNotyBuilder
.setContentTitle(context.getString(R.string.notification_content_title))//getString(R.string.app_name))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.mipmap.ic_launcher)
.setColor(notificationSystemColor)
.setContentTitle(context.getString(R.string.notification_content_title))
.setContentIntent(pendingIntent)
.setContentText(message)
.setStyle(NotificationCompat.BigTextStyle()
.setBigContentTitle(context.getString(R.string.notification_content_title))
.bigText(
message
))
.setAutoCancel(true)
.build()
actualNotifyManager.notify(
PUSH_INFO_SERVICE_ID
, noty)
}
private val cancelServicePendingIntent: NotificationCompat.Action
by lazy {
val stopServiceIntent = Intent(context, DownloadManagerService::class.java)
stopServiceIntent.putExtra(DOWNLOAD_MANAGER_COMMAND_KEY, DOWNLOAD_MANAGER_STOP_COMMAND)
val pendingStopServiceIntent = PendingIntent.getService(context
, 546
, stopServiceIntent
, FLAG_UPDATE_CURRENT
)
NotificationCompat.Action.Builder(
R.drawable.ic_back,
"Отмена",
pendingStopServiceIntent).build()
}
override fun donwloadServiceProgressNotfication(progress:Int
, progressMax:Int
, indeterminate:Boolean
, message: String){
val b = (if (indeterminate) updateProgressNotificationIndeterminate
else updateProgressNotification)
b.setContentText(message)
b.setProgress(progress, progressMax,indeterminate)
actualNotifyManager.notify(
(if (indeterminate) DOWNLOAD_SERVICE_ID else TOUR_INFO_SERVICE_ID)
, b.build()
)
}
private val defPendingIntent by lazy {
PendingIntent.getActivity(context
, PENDING_REQUEST_CODE
, toDownloadsIntent
, FLAG_UPDATE_CURRENT
)
}
private val updateProgressNotificationIndeterminate by lazy {
builder
.setOnlyAlertOnce(true)
.setContentTitle(context.getString(R.string.notification_content_title))
.setSmallIcon(R.mipmap.ic_launcher)
.setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE)
.setColor(notificationSystemColor)
.setLargeIcon(icon)
.setContentIntent(defPendingIntent)
.addAction(cancelServicePendingIntent)
.setAutoCancel(true)
}
private val updateProgressNotification by lazy {
toursNotyBuilder
.setOnlyAlertOnce(true)
.setContentTitle(context.getString(R.string.notification_content_title))
.setSmallIcon(R.mipmap.ic_launcher)
.setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE)
.setColor(notificationSystemColor)
.setLargeIcon(icon)
.setContentIntent(defPendingIntent)
.addAction(cancelServicePendingIntent)
.setAutoCancel(true)
}
@TargetApi(Build.VERSION_CODES.O)
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(): String {
val channelId = ANDROID_CHANNEL_ID
val channelName = "Biganto Visual Download Service"
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_DEFAULT)
chan.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
chan.enableVibration(false)
chan.lightColor = R.color.colorPrimary;
val service = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return channelId
}
@TargetApi(Build.VERSION_CODES.O)
@RequiresApi(Build.VERSION_CODES.O)
private fun createToursNotificationChannel(): String {
val channelId = TOURS_CHANNEL_ID
val channelName = "Biganto Visual Tour info update"
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_DEFAULT)
chan.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
chan.enableVibration(true)
chan.lightColor = R.color.colorPrimary;
val service = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return channelId
}
@TargetApi(Build.VERSION_CODES.O)
@RequiresApi(Build.VERSION_CODES.O)
private fun createPushNotificationChannel(): String {
val channelId = PUSH_CHANNEL_ID
val channelName = "RoomPark push info update"
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_DEFAULT)
chan.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
chan.enableVibration(true)
chan.lightColor = R.color.colorPrimary
val service = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return channelId
}
private fun DownloadState.notyName() =
when(this){
DownloadState.Downloaded ->context.getString(R.string.state_downloaded_notify_message)
DownloadState.Crushed -> context.getString(R.string.state_crushed_notify_message)
DownloadState.Suspended -> context.getString(R.string.state_suspended_notify_message)
DownloadState.Downloading -> context.getString(R.string.state_downloading_notify_message)
else -> context.getString(R.string.state_else_notify_message)
}
}
...@@ -4,7 +4,7 @@ import android.content.Context ...@@ -4,7 +4,7 @@ import android.content.Context
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.biganto.visual.roompark.BuildConfig import com.biganto.visual.roompark.BuildConfig
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.AppVersionRaw import com.biganto.visual.roompark.data.repository.api.room_park.raw.AppVersionRaw
import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.BehaviorRelay
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
......
...@@ -4,11 +4,15 @@ import android.app.Application ...@@ -4,11 +4,15 @@ import android.app.Application
import com.biganto.visual.androidplayer.data.repository.local.ILocalStore import com.biganto.visual.androidplayer.data.repository.local.ILocalStore
import com.biganto.visual.roompark.base.RoomParkApplication import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.data.local.LocalStorage import com.biganto.visual.roompark.data.local.LocalStorage
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi import com.biganto.visual.roompark.data.repository.api.biganto.IBigantoApi
import com.biganto.visual.roompark.data.repository.api.retrofit.di.RetrofitModule import com.biganto.visual.roompark.data.repository.api.retrofit.di.RetrofitModule
import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.db.IDb import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.DbModule import com.biganto.visual.roompark.data.repository.db.requrey.DbModule
import com.biganto.visual.roompark.data.repository.file.FileModule import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.data.service.lifecycle.ForegroundLifecycleObserver
import com.biganto.visual.roompark.data.service.network.INetworkMonitor
import com.biganto.visual.roompark.data.service.notification.INotificationCenter
import com.biganto.visual.roompark.domain.contract.* import com.biganto.visual.roompark.domain.contract.*
import dagger.BindsInstance import dagger.BindsInstance
import dagger.Component import dagger.Component
...@@ -17,10 +21,6 @@ import dagger.android.AndroidInjector ...@@ -17,10 +21,6 @@ import dagger.android.AndroidInjector
import dagger.android.support.AndroidSupportInjectionModule import dagger.android.support.AndroidSupportInjectionModule
import javax.inject.Singleton import javax.inject.Singleton
/** /**
* Created by Vladislav Bogdashkin on 13.06.2018. * Created by Vladislav Bogdashkin on 13.06.2018.
*/ */
...@@ -53,13 +53,29 @@ interface AppComponent : AndroidInjector<RoomParkApplication>{ ...@@ -53,13 +53,29 @@ interface AppComponent : AndroidInjector<RoomParkApplication>{
fun filesRep(): FilesContract fun filesRep(): FilesContract
fun subsRep(): SubscriptionContract
fun provideLocal():ILocalStore fun provideLocal():ILocalStore
fun provideApi():IRoomParkApi fun provideApi(): IRoomParkApi
fun provideBigantoApi(): IBigantoApi
fun providedb():IDb fun providedb():IDb
fun provideUtils():DeviceUtilsContract
fun provideTour():TourContract
fun providePlan():FlatPlanContract
fun provideLifeCycle(): ForegroundLifecycleObserver
fun provideNotifivations(): INotificationCenter
fun provideNetMonitor(): INetworkMonitor
fun provideAppContext():Application fun provideAppContext():Application
fun provideFileSystem(): FileModule fun provideFileSystem(): FileModule
...@@ -72,41 +88,5 @@ interface AppComponent : AndroidInjector<RoomParkApplication>{ ...@@ -72,41 +88,5 @@ interface AppComponent : AndroidInjector<RoomParkApplication>{
@BindsInstance app:RoomParkApplication @BindsInstance app:RoomParkApplication
):AppComponent ):AppComponent
// @BindsInstance
// fun context(application: Application): Factory
// @BindsInstance
// fun retrofitModule(retrofit: RetrofitModule):Builder
// @BindsInstance
// @Named("roomParkApi")
// fun retrofit(
// retorfitModule: RetrofitModule
// ): DaggerDataComponent.Builder
//
// fun build(): AppComponent
} }
// retorfitModule: RetrofitModule
// fun cache(): ICachedStore
// fun context(): Context
// fun db(): IDb
// fun api(): IApi
// fun roomApi(): IRoomParkApi
// fun fileModule(): FilesModule
//// fun appLifeCycle(): AppLifecycleListener
// fun networkMonitor(): INetworkMonitor
// fun versionMonitor(): IAppVersionControl
// fun versionNotifier(): IAppVersionNotifier
//
// fun estateRepo() : IEstateRepository
// fun tourRepo() : ITourRepository
// fun userRepo() : IUserRepository
} }
\ No newline at end of file
...@@ -9,19 +9,6 @@ import dagger.Module ...@@ -9,19 +9,6 @@ import dagger.Module
* Created by Vladislav Bogdashkin on 13.06.2018. * Created by Vladislav Bogdashkin on 13.06.2018.
*/ */
const val USER_CACHE_LIMIT_SIZE = 3
const val USER_CACHE_LIMIT_SECONDS_INACTIVE = 30L
const val TOURS_CACHE_LIMIT_SIZE = 500
const val TOURS_CACHE_LIMIT_SECONDS_INACTIVE = 200L
const val ESTATES_CACHE_LIMIT_SIZE = 100
const val ESTATES_CACHE_LIMIT_SECONDS_INACTIVE = 200L
const val FILES_CACHE_LIMIT_SIZE = 10000
const val FILES_CACHE_LIMIT_SECONDS_INACTIVE = 60L
const val DATABASE_VERSION = 11
@Module @Module
abstract class AppModule{ abstract class AppModule{
......
...@@ -2,12 +2,20 @@ package com.biganto.visual.roompark.di.dagger ...@@ -2,12 +2,20 @@ package com.biganto.visual.roompark.di.dagger
import com.biganto.visual.roompark.data.data_provider.* import com.biganto.visual.roompark.data.data_provider.*
import com.biganto.visual.roompark.data.local.LocalStorage import com.biganto.visual.roompark.data.local.LocalStorage
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi import com.biganto.visual.roompark.data.repository.api.biganto.BigantoRetrofitRepository
import com.biganto.visual.roompark.data.repository.api.retrofit.RetrofitRepository import com.biganto.visual.roompark.data.repository.api.biganto.IBigantoApi
import com.biganto.visual.roompark.data.repository.api.retrofit.di.RetrofitModule import com.biganto.visual.roompark.data.repository.api.retrofit.di.RetrofitModule
import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.api.room_park.RetrofitRepository
import com.biganto.visual.roompark.data.repository.db.IDb import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.DbModule import com.biganto.visual.roompark.data.repository.db.requrey.DbModule
import com.biganto.visual.roompark.data.repository.db.requrey.RequeryRepository import com.biganto.visual.roompark.data.repository.db.requrey.RequeryRepository
import com.biganto.visual.roompark.data.service.lifecycle.AppLifecycleListener
import com.biganto.visual.roompark.data.service.lifecycle.ForegroundLifecycleObserver
import com.biganto.visual.roompark.data.service.network.INetworkMonitor
import com.biganto.visual.roompark.data.service.network.LiveNetworkMonitor
import com.biganto.visual.roompark.data.service.notification.INotificationCenter
import com.biganto.visual.roompark.data.service.notification.NotificationCenter
import com.biganto.visual.roompark.domain.contract.* import com.biganto.visual.roompark.domain.contract.*
import dagger.Binds import dagger.Binds
import dagger.Component import dagger.Component
...@@ -40,101 +48,55 @@ abstract class ContractRepositoryModule { ...@@ -40,101 +48,55 @@ abstract class ContractRepositoryModule {
@Binds @Binds
@Singleton @Singleton
abstract fun provideFilesContract(impl: FilesContractModule): FilesContract abstract fun provideSubscriptionsContract(impl: SubscriptionRepository): SubscriptionContract
}
@Binds
@Singleton
abstract fun provideFilesContract(impl: FilesContractModule): FilesContract
@Binds
@Singleton
abstract fun provideDeviceContract(impl: DeviceUtilsRepository): DeviceUtilsContract
@Module @Binds
abstract class DataModule { @Singleton
abstract fun provideTourContract(impl: ToursRepository): TourContract
@Binds
@Singleton
abstract fun providePlanContract(impl: PlanRepository): FlatPlanContract
}
// @Singleton
// @Binds
// abstract fun provideAuthContract(contract: AuthContractModule) : AuthContract
// @Binds
// abstract fun provideModule(m:RetrofitModule) : RetrofitModule
// @Binds @Module
// abstract fun provideRpRetrofit(retrofit:Retrofit) : Retrofit abstract class DataModule {
//
@Singleton @Singleton
@Binds @Binds
abstract fun provideRoomParkApi(api: RetrofitRepository) : IRoomParkApi abstract fun provideBigantoApi(bigantoApi:BigantoRetrofitRepository): IBigantoApi
// @Binds
// abstract fun provideStore(store: KotlinReactiveEntityStore<Persistable>) : KotlinReactiveEntityStore<Persistable>
@Singleton @Singleton
@Binds @Binds
abstract fun provideDb(db: RequeryRepository) : IDb abstract fun provideRoomParkApi(roomParkApi:RetrofitRepository): IRoomParkApi
// @Singleton
// @Binds
// abstract fun provideLocalStorage(local: UserHolder) : ILocalStore
/*
@Provides
@Singleton @Singleton
fun provieApi(@Named("bigantoApi") retorfit:Retrofit): IApi { @Binds
return RetrofitRepository(retorfit) abstract fun provideNotyCenter(center: NotificationCenter): INotificationCenter
}
@Provides
@Singleton @Singleton
fun provieRoomParkApi(@Named("roomParkApi") retorfit:Retrofit): IRoomParkApi { @Binds
return RoomParkRetrofitRepository(retorfit) abstract fun provideLifecycleObserver(obs:AppLifecycleListener): ForegroundLifecycleObserver
}
@Provides
@Singleton @Singleton
fun provideDb(context:Application): IDb { @Binds
return RequeryRepository(getRequeryDataStore(context)) abstract fun provideDb(db: RequeryRepository) : IDb
}
@Provides
@Singleton
fun provideFileModule(context:Application): FilesModule {
return FilesModule(context)
}
@Provides
@Singleton @Singleton
fun providesNetworkListener(context:Application): INetworkMonitor { @Binds
return LiveNetworkMonitor(context) abstract fun provideNetwork(networkMonitor: LiveNetworkMonitor) : INetworkMonitor
}
@Provides
@Singleton
fun getRequeryDataStore(context:Application): KotlinReactiveEntityStore<Persistable> {
Timber.d("Kotlin store creating..")
val source = DatabaseSource(context, Models.DEFAULT, "BigantoPerfect", DATABASE_VERSION)
source.setLoggingEnabled(false)
// if ( BuildConfig.DEBUG) {
// // use this in development mode to drop and recreate the tables on every upgrade
// source.setTableCreationMode(TableCreationMode.DROP_CREATE)
// }
val store = KotlinEntityDataStore<Persistable>(source.configuration)
Timber.d("Kotlin store %s",source)
return KotlinReactiveEntityStore(store)
// // override onUpgrade to handle migrating to a new version
// val configuration = source.configuration
// return ReactiveSupport.toReactiveStore(
// EntityDataStore<Persistable>(configuration))
}
*/
} }
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract package com.biganto.visual.roompark.domain.contract
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import com.biganto.visual.roompark.domain.model.AuthInfoModel import com.biganto.visual.roompark.domain.model.AuthInfoModel
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Observable import io.reactivex.Observable
...@@ -12,4 +13,5 @@ interface AuthContract { ...@@ -12,4 +13,5 @@ interface AuthContract {
fun signIn(email:String,password:String) : Observable<AuthInfoModel> fun signIn(email:String,password:String) : Observable<AuthInfoModel>
fun signOut() : Completable fun signOut() : Completable
fun validateAuthState(): Observable<Boolean> fun validateAuthState(): Observable<Boolean>
fun currentUser(): Observable<UserEntity>
} }
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract package com.biganto.visual.roompark.domain.contract
import com.biganto.visual.roompark.domain.model.DealModel import com.biganto.visual.roompark.data.repository.db.requrey.model.DealEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.EstateEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import com.biganto.visual.roompark.domain.model.EstateModel import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.PlanPresetModel
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Observable import io.reactivex.Observable
...@@ -13,20 +14,11 @@ import io.reactivex.Observable ...@@ -13,20 +14,11 @@ import io.reactivex.Observable
interface DealContract{ interface DealContract{
fun getFavorites() : Observable<List<EstateModel>> // fun getFavorites() : Observable<List<EstateModel>>
fun getEstate(estateId: Int): Observable<EstateModel> fun getEstate(estateId: Int): Observable<EstateModel>
fun getPlanTypes(estateId: Int): Observable<List<PlanPresetModel>>
fun getEmptyPlan(estateId: Int, planId: Int): Observable<String>
fun getPlan(
estateId: Int
, planId: Int
, furniture:Boolean?
, sizes:Boolean?
, walls:Boolean?
, electric:Boolean?): Observable<String>
fun getDeals(): Observable<List<DealModel>>
fun fetchEstate(building: Int, number: Int): Observable<EstateModel> fun fetchEstate(building: Int, number: Int): Observable<EstateModel>
fun setDealRead(dealId: String): Completable fun setDealRead(dealId: String): Completable
fun fetchDeals(user: UserEntity): Observable<List<DealEntity>>
fun fetchFavorites(user: UserEntity): Observable<List<EstateEntity>>
fun getDealsApi(user: UserEntity): Observable<List<DealEntity>>
} }
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract
import com.biganto.visual.roompark.data.data_provider.PlanFeaturesVariant
import com.biganto.visual.roompark.domain.model.PlanPresetModel
import com.biganto.visual.roompark.domain.use_case.DownloadUseCase
import io.reactivex.Observable
import java.io.File
/**
* Created by Vladislav Bogdashkin on 20.04.2020.
*/
interface FlatPlanContract{
fun getPlanTypes(estateId: Int): Observable<List<PlanPresetModel>>
fun getPlan(
estateId: Int
, planId: Int
, furniture:Boolean?
, sizes:Boolean?
, walls:Boolean?
, electric:Boolean?): Observable<String>
fun getPlanFile(featuresVariant: PlanFeaturesVariant): File
fun getPlan(featuresVariant: PlanFeaturesVariant): Observable<String>
fun getPlansObservable(
list: List<PlanFeaturesVariant>,
cancellationToken: DownloadUseCase.CancellationToken
): Observable<String>
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract package com.biganto.visual.roompark.domain.contract
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.StatusResponse import com.biganto.visual.roompark.data.repository.db.requrey.model.SubscriptionEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import io.reactivex.Completable
import io.reactivex.Observable import io.reactivex.Observable
/** /**
...@@ -8,15 +11,45 @@ import io.reactivex.Observable ...@@ -8,15 +11,45 @@ import io.reactivex.Observable
*/ */
interface SubscriptionContract{ interface SubscriptionContract{
fun subscribeTopic( fun subscribeTopic(
deviceToken:String, user: UserEntity,
topic:String, subInnerId: Int,
topic_id:String? = null deviceToken: String,
) : Observable<StatusResponse> topic: String,
topic_id: String?
): Completable
fun unSubscribeTopic( fun unSubscribeTopic(
deviceToken:String, user: UserEntity,
topic:String, subInnerId: Int,
topic_id:String? = null deviceToken: String,
) : Observable<StatusResponse> topic: String,
topic_id: String?
): Completable
fun saveSubscribeState(
userEntity: UserEntity,
subInnerId: Int?,
topic: String,
topic_id: String?,
newState: Boolean
): Observable<SubscriptionEntity>
fun saveSubscribeState(sub: SubscriptionEntity): Observable<SubscriptionEntity>
fun subscribeTopicResult(
user: UserEntity,
subInnerId: Int,
deviceToken: String,
topic: String,
topic_id: String?
): Observable<List<SubscriptionModel>>
fun unSubscribeTopicResult(
user: UserEntity,
subInnerId: Int,
deviceToken: String,
topic: String,
topic_id: String?
): Observable<List<SubscriptionModel>>
} }
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract package com.biganto.visual.roompark.domain.contract
import com.biganto.visual.roompark.domain.model.AuthInfoModel import com.biganto.visual.roompark.domain.model.AuthInfoModel
import io.reactivex.Completable
import io.reactivex.Observable import io.reactivex.Observable
...@@ -10,4 +11,5 @@ import io.reactivex.Observable ...@@ -10,4 +11,5 @@ import io.reactivex.Observable
interface TourContract { interface TourContract {
fun getMultiTourId(building:Int, number:Int) : Observable<AuthInfoModel> fun getMultiTourId(building:Int, number:Int) : Observable<AuthInfoModel>
fun getOffer(offerId:Int) : Observable<AuthInfoModel> fun getOffer(offerId:Int) : Observable<AuthInfoModel>
fun deleteToursDbInfo(): Completable
} }
\ No newline at end of file
...@@ -2,7 +2,7 @@ package com.biganto.visual.roompark.domain.custom_exception ...@@ -2,7 +2,7 @@ package com.biganto.visual.roompark.domain.custom_exception
import androidx.annotation.StringRes import androidx.annotation.StringRes
import com.biganto.visual.roompark.R import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.ErrorRaw import com.biganto.visual.roompark.data.repository.api.room_park.raw.ErrorRaw
/** /**
......
package com.biganto.visual.roompark.domain.interactor package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.model.AlbumSortedModel import com.biganto.visual.roompark.domain.model.AlbumSortedModel
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.model.SubscriptionTopic
import com.biganto.visual.roompark.domain.use_case.AlbumsUseCase import com.biganto.visual.roompark.domain.use_case.AlbumsUseCase
import com.biganto.visual.roompark.domain.use_case.SubscriptionUseCase
import io.reactivex.Observable import io.reactivex.Observable
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
...@@ -11,9 +14,12 @@ import javax.inject.Inject ...@@ -11,9 +14,12 @@ import javax.inject.Inject
*/ */
class AlbumsInteractor @Inject constructor( class AlbumsInteractor @Inject constructor(
private val useCase: AlbumsUseCase) { private val useCase: AlbumsUseCase,
private val subUc: SubscriptionUseCase
) {
fun fetchHeaderAlbums() = useCase.getProgressAlbums() fun fetchHeaderAlbums() = useCase.getProgressAlbums()
fun fetchAlbumPhotos(parentId: Int): Observable<List<AlbumSortedModel>> = fun fetchAlbumPhotos(parentId: Int): Observable<List<AlbumSortedModel>> =
useCase.getChildAlbums(parentId) useCase.getChildAlbums(parentId)
.flatMap { .flatMap {
...@@ -31,6 +37,15 @@ class AlbumsInteractor @Inject constructor( ...@@ -31,6 +37,15 @@ class AlbumsInteractor @Inject constructor(
} }
.debounce(80L, TimeUnit.MILLISECONDS) // to reduce double list draw effect .debounce(80L, TimeUnit.MILLISECONDS) // to reduce double list draw effect
fun getSubscriptions(topic: SubscriptionTopic) =
subUc.getSubscriptions(topic)
fun switchSubscription(model: SubscriptionModel, newState: Boolean) =
when(newState){
true -> subUc.subscribeTopic(model.id,model.topic)
false -> subUc.unSubscribeTopic(model.id,model.topic)
}
} }
......
...@@ -2,6 +2,7 @@ package com.biganto.visual.roompark.domain.interactor ...@@ -2,6 +2,7 @@ package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.model.ArticleModel import com.biganto.visual.roompark.domain.model.ArticleModel
import com.biganto.visual.roompark.domain.use_case.FeedUseCase import com.biganto.visual.roompark.domain.use_case.FeedUseCase
import com.biganto.visual.roompark.domain.use_case.SubscriptionUseCase
import io.reactivex.Observable import io.reactivex.Observable
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
......
package com.biganto.visual.roompark.domain.interactor package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.model.SubscriptionTopic
import com.biganto.visual.roompark.domain.use_case.FeedUseCase import com.biganto.visual.roompark.domain.use_case.FeedUseCase
import com.biganto.visual.roompark.domain.use_case.SubscriptionUseCase
import javax.inject.Inject import javax.inject.Inject
/** /**
...@@ -8,7 +11,8 @@ import javax.inject.Inject ...@@ -8,7 +11,8 @@ import javax.inject.Inject
*/ */
class ArticlesInteractor @Inject constructor( class ArticlesInteractor @Inject constructor(
private val uc: FeedUseCase private val uc: FeedUseCase,
private val subUc: SubscriptionUseCase
) { ) {
fun fetchArticles(feed:String) = fun fetchArticles(feed:String) =
...@@ -17,6 +21,23 @@ class ArticlesInteractor @Inject constructor( ...@@ -17,6 +21,23 @@ class ArticlesInteractor @Inject constructor(
fun fetchArticlesPage(feed:String,pageSize:Int,current:Int) = fun fetchArticlesPage(feed:String,pageSize:Int,current:Int) =
uc.fetchArticlesPage(feed,pageSize,current) uc.fetchArticlesPage(feed,pageSize,current)
fun getSubscriptions(feed: String) =
subUc.getSubscriptions(feedSubType(feed))
fun switchSubscription(model:SubscriptionModel,newState: Boolean) =
when(newState){
true -> subUc.subscribeTopic(model.id,model.topic)
false -> subUc.unSubscribeTopic(model.id,model.topic)
}
private fun feedSubType(feed:String) =
when(feed){
"news" -> SubscriptionTopic.News()
"blog" -> SubscriptionTopic.Blog()
"construction-blog" -> SubscriptionTopic.Construction()
else -> error("Unknown feed type")
}
} }
......
...@@ -2,7 +2,10 @@ package com.biganto.visual.roompark.domain.interactor ...@@ -2,7 +2,10 @@ package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.model.DealModel import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.StatusModel import com.biganto.visual.roompark.domain.model.StatusModel
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.model.SubscriptionTopic
import com.biganto.visual.roompark.domain.use_case.DealseUseCase import com.biganto.visual.roompark.domain.use_case.DealseUseCase
import com.biganto.visual.roompark.domain.use_case.SubscriptionUseCase
import io.reactivex.Observable import io.reactivex.Observable
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
...@@ -12,12 +15,12 @@ import javax.inject.Inject ...@@ -12,12 +15,12 @@ import javax.inject.Inject
*/ */
class DealInteractor @Inject constructor( class DealInteractor @Inject constructor(
val useCase : DealseUseCase val useCase : DealseUseCase,
val subUc : SubscriptionUseCase
){ ){
fun getDeal(id:String): Observable<DealModel> = fun getDeal(id:String): Observable<DealModel> =
useCase.getDeals() useCase.prefetchDeal()
.doOnNext { Timber.d("$it") }
.map {deals -> deals.first { it.id==id } } .map {deals -> deals.first { it.id==id } }
fun getStatusList() = Observable.just(statusList.sortedBy{ it.orderId }) fun getStatusList() = Observable.just(statusList.sortedBy{ it.orderId })
...@@ -26,6 +29,15 @@ class DealInteractor @Inject constructor( ...@@ -26,6 +29,15 @@ class DealInteractor @Inject constructor(
fun setDealRead(id:String) = useCase.setDealRead(id) fun setDealRead(id:String) = useCase.setDealRead(id)
fun getSubscriptions(dealId: String) =
subUc.getSubscriptions(SubscriptionTopic.Deals(dealId = dealId))
fun switchSubscription(model: SubscriptionModel, newState: Boolean) =
when(newState){
true -> subUc.subscribeTopic(model.id,model.topic)
false -> subUc.unSubscribeTopic(model.id,model.topic)
}
companion object { companion object {
val statusList = arrayListOf<StatusModel>( val statusList = arrayListOf<StatusModel>(
...@@ -87,4 +99,4 @@ class DealInteractor @Inject constructor( ...@@ -87,4 +99,4 @@ class DealInteractor @Inject constructor(
) )
) )
} }
} }
\ No newline at end of file \ No newline at end of file
package com.biganto.visual.roompark.domain.interactor package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.StatusModel import com.biganto.visual.roompark.domain.model.StatusModel
import com.biganto.visual.roompark.domain.use_case.DealseUseCase import com.biganto.visual.roompark.domain.use_case.DealseUseCase
import io.reactivex.Observable import io.reactivex.Observable
...@@ -13,7 +14,9 @@ class DealsInteractor @Inject constructor( ...@@ -13,7 +14,9 @@ class DealsInteractor @Inject constructor(
val useCase:DealseUseCase val useCase:DealseUseCase
){ ){
fun fetchDeals() = useCase.getDeals() fun fetchDeals(): Observable<List<DealModel>> = useCase.prefetchDeal()
fun getDealsApi(): Observable<List<DealModel>> = useCase.getDeals()
// Single.just(arrayListOf(dealFlat, dealParkign)) // Single.just(arrayListOf(dealFlat, dealParkign))
fun getStatusList() = Observable.just(statusList.sortedBy{ it.orderId }) fun getStatusList() = Observable.just(statusList.sortedBy{ it.orderId })
......
...@@ -17,7 +17,6 @@ class EstateInteractor @Inject constructor( ...@@ -17,7 +17,6 @@ class EstateInteractor @Inject constructor(
fun getPlanTypes(estateId: Int) = fun getPlanTypes(estateId: Int) =
useCase.getEstatePlanPresets(estateId) useCase.getEstatePlanPresets(estateId)
fun getPlan(estateId: Int,planId:Int) = useCase.getPlan(estateId,planId)
fun getPlan(plan:PlanPresetModel) = fun getPlan(plan:PlanPresetModel) =
useCase.getPlan( useCase.getPlan(
......
...@@ -15,6 +15,9 @@ class FavoritesInteractor @Inject constructor( ...@@ -15,6 +15,9 @@ class FavoritesInteractor @Inject constructor(
private val estateUseCase: EstateUseCase private val estateUseCase: EstateUseCase
) { ) {
fun cachedFavorites() =
estateUseCase.prefetchFavorites()
fun getFavoritesForCurrentUser() = fun getFavoritesForCurrentUser() =
estateUseCase.fetchFavorites() estateUseCase.fetchFavorites()
// Single.just(parkingEstateSample ) // Single.just(parkingEstateSample )
...@@ -52,6 +55,7 @@ class FavoritesInteractor @Inject constructor( ...@@ -52,6 +55,7 @@ class FavoritesInteractor @Inject constructor(
albumId = 10, albumId = 10,
multitourId = null, multitourId = null,
url = null url = null
,availableStatus = true
), ),
EstateModel( EstateModel(
id = 1905, id = 1905,
...@@ -81,7 +85,7 @@ class FavoritesInteractor @Inject constructor( ...@@ -81,7 +85,7 @@ class FavoritesInteractor @Inject constructor(
albumId = 10, albumId = 10,
multitourId = null, multitourId = null,
url = null url = null
), ,availableStatus = true),
EstateModel( EstateModel(
id = 1774, id = 1774,
type = FlatType.valueOf("flat".toUpperCase()), type = FlatType.valueOf("flat".toUpperCase()),
...@@ -118,6 +122,7 @@ class FavoritesInteractor @Inject constructor( ...@@ -118,6 +122,7 @@ class FavoritesInteractor @Inject constructor(
albumId = 10, albumId = 10,
url = null, url = null,
multitourId = 5790 multitourId = 5790
,availableStatus = true
// ,explications = arrayListOf<ExplicationListModel>( // ,explications = arrayListOf<ExplicationListModel>(
// ExplicationListModel( // ExplicationListModel(
// planId = 0, // planId = 0,
......
package com.biganto.visual.roompark.domain.interactor package com.biganto.visual.roompark.domain.interactor
import android.content.Context import android.content.Intent
import android.os.Build
import com.biganto.visual.roompark.R import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.BaseRoomParkActivity
import com.biganto.visual.roompark.data.service.download.DOWNLOAD_MANAGER_ADD_IDS_TO_LOAD_COMMAND
import com.biganto.visual.roompark.data.service.download.DOWNLOAD_MANAGER_COMMAND_KEY
import com.biganto.visual.roompark.data.service.download.DownloadManagerService
import com.biganto.visual.roompark.domain.model.CachedDataModel import com.biganto.visual.roompark.domain.model.CachedDataModel
import com.biganto.visual.roompark.domain.model.PushSwitchModel import com.biganto.visual.roompark.domain.model.PushSwitchModel
import com.biganto.visual.roompark.domain.model.SettingsModel import com.biganto.visual.roompark.domain.model.SettingsModel
import com.biganto.visual.roompark.domain.use_case.AuthUseCase import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.use_case.SettingsUseCase import com.biganto.visual.roompark.domain.use_case.*
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
/** /**
...@@ -18,12 +25,75 @@ import javax.inject.Inject ...@@ -18,12 +25,75 @@ import javax.inject.Inject
class SettingsInteractor @Inject constructor( class SettingsInteractor @Inject constructor(
private val auth: AuthUseCase, private val auth: AuthUseCase,
private val settingsUseCase: SettingsUseCase, private val settingsUseCase: SettingsUseCase,
private val activity: Context private val activity: BaseRoomParkActivity,
private val subUc: SubscriptionUseCase,
private val toursUc: TourPreviewsUseCase,
private val planTypes: PlanTypesUseCase
){ ){
private val plans
fun downloadTourPlans(cancellationToken:DownloadUseCase.CancellationToken)
= planTypes.downloadAllPlanTypes(cancellationToken)
fun fetchToursSizes() = toursUc.fetchToursSizes()
fun fetchPlanTypesSizes() = planTypes.fetchNotDownloadedPlansSizes()
private fun startDownloadService() {
val i = Intent(activity, DownloadManagerService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.startForegroundService(i)
} else {
activity.startService(i)
}
}
private fun startDownloadService(ids:List<String>){
Timber.d(" gonna startService ++")
try {
val i = Intent(activity, DownloadManagerService::class.java)
i.putExtra(DOWNLOAD_MANAGER_COMMAND_KEY, DOWNLOAD_MANAGER_ADD_IDS_TO_LOAD_COMMAND)
i.putStringArrayListExtra(TOUR_IDS_TO_DOWNLOAD_KEY, ArrayList(ids))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.startForegroundService(i)
} else {
activity.startService(i)
}
}catch (e:Exception){
Timber.d(" gonna startService errror")
Timber.e(e)
}
}
fun startToursDownloading() =
toursUc.downloadAllDeelsAndEstates()
.doOnNext { Timber.d(" gonna startService") }
.doOnNext { this.startDownloadService() }
.delay(100,TimeUnit.MILLISECONDS)
.doOnNext { Timber.d(" gonna startServic222e") }
.doOnNext { tours ->
startDownloadService(tours.map { tour -> tour.id })
}
.doOnNext { Timber.d(" gonna startServsdfsfdsice") }
// .doOnComplete { Timber.w(" azzaza complete") }
fun getSubscriptions() =
subUc.getCurrentUserSubscriptions()
fun switchSubscription(model: SubscriptionModel, newState: Boolean) =
when(newState){
true -> subUc.subscribeTopic(model.id,model.topic)
false -> subUc.unSubscribeTopic(model.id,model.topic)
}
private val plans : Observable<CachedDataModel>
get() = settingsUseCase.planTypesSize.map { get() = settingsUseCase.planTypesSize.map {
CachedDataModel(activity.resources.getString(R.string.plans_cache),it,1) CachedDataModel(activity.resources.getString(R.string.plans_cache), it, 1)
} }
private val tours private val tours
...@@ -49,7 +119,7 @@ class SettingsInteractor @Inject constructor( ...@@ -49,7 +119,7 @@ class SettingsInteractor @Inject constructor(
fun deleteCacheFiles() = settingsUseCase.clearAllCache() fun deleteCacheFiles() = settingsUseCase.clearAllCache()
fun getCacheInfo() = fun getCacheInfo(): Observable<MutableList<CachedDataModel>> =
Observable.concatArray(plans, tours, feeds, albums, overall).toList().toObservable() Observable.concatArray(plans, tours, feeds, albums, overall).toList().toObservable()
fun fetchSettings(): Observable<SettingsModel> = Observable.just(sampleSettings) fun fetchSettings(): Observable<SettingsModel> = Observable.just(sampleSettings)
......
package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.domain.use_case.DownloadUseCase
import com.biganto.visual.roompark.domain.use_case.TourPreviewsUseCase
import io.reactivex.Observable
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 03.04.2020.
*/
class ToursInteractor @Inject constructor(
private val downloadUseCase: DownloadUseCase,
private val tourUseCase:TourPreviewsUseCase
){
fun getEstateTourList(multiTourId:Int?,estateId:Int) =
tourUseCase.fetchTourOffer(multiTourId
?: error("Отсутсвуют виртуальные туры для данного объекта"),estateId)
fun getEstateTourList(estate:EstateModel): Observable<List<TourModel>> =
tourUseCase.fetchTourOffer(estate.multitourId
?: error("Отсутсвуют виртуальные туры для данного объекта"),estate.id)
fun downloadTour(tourId:String,cancellationToken: DownloadUseCase.CancellationToken) =
downloadUseCase.startTourDownloading(tourId,cancellationToken)
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.model
import android.os.Parcel
import android.os.Parcelable
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import java.text.DecimalFormat
import java.util.*
import kotlin.math.max
/**
* Created by Vladislav Bogdashkin on 30.05.2018.
*/
data class TourModel (
val tour_id:String="-1",
val created : Date=Date(),
val updated:Date=Date(),
val type: TourType = TourType.VIRTUAL,
val title:String="",
val previewUrl:String?="",
val screen:String?=null,
val hidden:Boolean=false,
val metaUri:String?,
val parentId:Int=-1,
var downloadState: DownloadState =DownloadState.MetaPreparation,
val downloadedSize: Long=-1L,
val totalTourSize : Long=-1L,
val targetResolution : Int,
val footageUri : String?
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readSerializable() as Date,
parcel.readSerializable() as Date,
TourType.valueOf(parcel.readString()),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readByte() != 0.toByte(),
parcel.readString(),
parcel.readInt(),
DownloadState.valueOf(parcel.readString()),
parcel.readLong(),
parcel.readLong(),
parcel.readInt(),
parcel.readString())
val metaPredict : String
get() = metaUri?.removeSuffix("$tour_id.json") ?: ""
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(tour_id)
parcel.writeSerializable(created)
parcel.writeSerializable(updated)
parcel.writeString(type.name)
parcel.writeString(title)
parcel.writeString(previewUrl)
parcel.writeString(screen)
parcel.writeByte(if (hidden) 1 else 0)
parcel.writeString(metaUri)
parcel.writeInt(parentId)
parcel.writeString(downloadState.name)
parcel.writeLong(downloadedSize)
parcel.writeLong(totalTourSize)
parcel.writeInt(targetResolution)
parcel.writeString(footageUri)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<TourModel> {
override fun createFromParcel(parcel: Parcel): TourModel {
return TourModel(parcel)
}
override fun newArray(size: Int): Array<TourModel?> {
return arrayOfNulls(size)
}
}
}
fun Long.bytesToSize():String{
if (this==-1L) return "~"
var scale=0
var rest:Long=this
var least=0f
// Timber.d("size to gb: %s",this)
while (rest>=1024) {
least=(rest%1024).toFloat()
rest /= 1024
scale++
}
least/=102.4f
// least*=100f
return when(scale)
{
0->"$rest.${least.format(0)}B"
1->"$rest.${least.format(0)}Kb"
2->"$rest.${least.format(0)}Mb"
3->"$rest.${least.format(0)}Gb"
4->"$rest.${least.format(0)}Tb"
else ->"$rest${least.format(0)}Bb" //BigantoByte
}
}
fun bytesToString(bytes:Long)=bytes.bytesToSize()
fun Float.format(fracDigits: Int): String {
val df = DecimalFormat()
df.maximumFractionDigits = fracDigits
return df.format(this)
}
enum class TourType(val i:Int)
{
VIRTUAL(0),
REAL(1)
}
fun fromEntity(entity: TourPreviewEntity) = TourModel(
tour_id = entity.id,
parentId = entity.estate.id,
created= entity.created,
updated = entity.created,
// updated = entity.updated,
type = when(entity.type){
"virtual"-> TourType.VIRTUAL
"real"-> TourType.REAL
else-> TourType.REAL
},
title = entity.title,
previewUrl = entity.preview,
metaUri= entity.metaFileEntityId?.uri(),
screen = entity.screen,
hidden = entity.hidden,
downloadState = entity.isDownloaded,//calcDownloadState(entity.tour as TourEntity?)
downloadedSize = entity.downloadedSize?:-1L,
totalTourSize = max(entity.overallSize?:0L,entity.tempSize?:0L),
targetResolution = entity.targetResolution,
footageUri = entity.footageBaseUrl
)
fun fromEntity(raw: List<TourPreviewEntity>):List<TourModel> =List(raw.size) { index-> fromEntity(raw[index])}
data class TourLoadProgressModel(
val downloadState: DownloadState,
val downloadedSize: Long=-1L,
val totalTourSize : Long=-1L
)
...@@ -51,6 +51,7 @@ data class EstateModel( ...@@ -51,6 +51,7 @@ data class EstateModel(
val id:Int, val id:Int,
val type:FlatType, val type:FlatType,
val number:String, val number:String,
val availableStatus:Boolean,
val sectionBegin:Int?=null, val sectionBegin:Int?=null,
val sectionEnd:Int?=null, val sectionEnd:Int?=null,
val planPNG:PlanModel?, val planPNG:PlanModel?,
...@@ -90,6 +91,7 @@ fun fromEntity(entity:EstateEntity): EstateModel { ...@@ -90,6 +91,7 @@ fun fromEntity(entity:EstateEntity): EstateModel {
id = entity.id, id = entity.id,
type = FlatType.valueOf(entity.type.toUpperCase()), type = FlatType.valueOf(entity.type.toUpperCase()),
number = entity.number, number = entity.number,
availableStatus = entity.available,
sectionBegin = entity.sectionBegin, sectionBegin = entity.sectionBegin,
sectionEnd = entity.sectionEnd, sectionEnd = entity.sectionEnd,
planPNG = null, planPNG = null,
......
package com.biganto.visual.roompark.domain.model
import com.biganto.visual.roompark.data.repository.db.requrey.model.SubscriptionEntity
/**
* Created by Vladislav Bogdashkin on 26.03.2020.
*/
data class SubscriptionModel(
val topic:SubscriptionTopic,
val id: Int,
val state:Boolean
)
data class TitledSubscriptionModel(
var title:String?=null,
val subModel:SubscriptionModel
)
sealed class SubscriptionTopic(val topicName:String,val topicId:String? = null) {
class Deals(topicName: String =TOPIC_API_NAME_DEALS,dealId:String?) : SubscriptionTopic(topicName,dealId)
class Progress_1(topicName: String =TOPIC_API_NAME_PROGRESS_1) : SubscriptionTopic(topicName)
class Progress_2(topicName: String =TOPIC_API_NAME_PROGRESS_2) : SubscriptionTopic(topicName)
class Progress_3(topicName: String =TOPIC_API_NAME_PROGRESS_3) : SubscriptionTopic(topicName)
class Progress_kindergarden(topicName: String =TOPIC_API_NAME_PROGRESS_KINDERGARDEN) : SubscriptionTopic(topicName)
class Progress_school(topicName: String =TOPIC_API_NAME_PROGRESS_SCHOOL) : SubscriptionTopic(topicName)
class Progress_landscaping(topicName: String =TOPIC_API_NAME_PROGRESS_LANDSCAPING) : SubscriptionTopic(topicName)
class News(topicName: String =TOPIC_API_NAME_NEWS) : SubscriptionTopic(topicName)
class Blog(topicName: String =TOPIC_API_NAME_BLOG) : SubscriptionTopic(topicName)
class Construction(topicName: String = TOPIC_API_NAME_CONSCTRUCTION) : SubscriptionTopic(topicName)
companion object {
fun titleByTopic(topic:SubscriptionTopic) =
when(topic){
is Deals -> "Сделка"
is Progress_1 -> "Ход строительства Дом №1"
is Progress_2 -> "Ход строительства Дом №2"
is Progress_3 -> "Ход строительства Дом №3"
is Progress_kindergarden -> "Ход строительства \nДетский сад"
is Progress_school -> "Ход строительства \nШкола"
is Progress_landscaping -> "Ход строительства \nЛандшафт"
is News -> "Новости"
is Blog -> "Блоги"
is Construction -> "Строительный блог"
}
}
}
private const val TOPIC_API_NAME_DEALS = "deals"
private const val TOPIC_API_NAME_PROGRESS_1 = "progress-1"
private const val TOPIC_API_NAME_PROGRESS_2 = "progress-2"
private const val TOPIC_API_NAME_PROGRESS_3 = "progress-3"
private const val TOPIC_API_NAME_PROGRESS_KINDERGARDEN = "progress-kindergarden"
private const val TOPIC_API_NAME_PROGRESS_SCHOOL = "progress-school"
private const val TOPIC_API_NAME_PROGRESS_LANDSCAPING = "progress-landscaping"
private const val TOPIC_API_NAME_NEWS = "news"
private const val TOPIC_API_NAME_BLOG = "blog"
private const val TOPIC_API_NAME_CONSCTRUCTION = "construction-blog"
private fun topicFromEntity(sub: SubscriptionEntity):SubscriptionTopic =
when(sub.topic){
TOPIC_API_NAME_DEALS -> SubscriptionTopic.Deals(dealId = sub.number)
TOPIC_API_NAME_PROGRESS_1 -> SubscriptionTopic.Progress_1()
TOPIC_API_NAME_PROGRESS_2 -> SubscriptionTopic.Progress_2()
TOPIC_API_NAME_PROGRESS_3 -> SubscriptionTopic.Progress_3()
TOPIC_API_NAME_PROGRESS_KINDERGARDEN -> SubscriptionTopic.Progress_kindergarden()
TOPIC_API_NAME_PROGRESS_SCHOOL -> SubscriptionTopic.Progress_school()
TOPIC_API_NAME_PROGRESS_LANDSCAPING -> SubscriptionTopic.Progress_landscaping()
TOPIC_API_NAME_NEWS -> SubscriptionTopic.News()
TOPIC_API_NAME_BLOG -> SubscriptionTopic.Blog()
TOPIC_API_NAME_CONSCTRUCTION -> SubscriptionTopic.Construction()
else -> error("Unknown subscription type!")
}
fun fromEntity(sub: SubscriptionEntity):SubscriptionModel =
SubscriptionModel(topicFromEntity(sub),sub.id,sub.state)
...@@ -5,6 +5,6 @@ package com.biganto.visual.roompark.domain.model ...@@ -5,6 +5,6 @@ package com.biganto.visual.roompark.domain.model
*/ */
data class TourRequestModel(val building:Int, val flat:Int) //data class TourRequestModel(val building:Int, val flat:Int)
data class TourResponse(val response:List<TourModel>) //data class TourResponse(val response:List<TourModel>)
data class TourModel(val tourId:String, val title:Int, val preview:String?) //data class TourModel(val tourId:String, val title:Int, val preview:String?)
package com.biganto.visual.roompark.domain.model package com.biganto.visual.roompark.domain.model
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.StreamRaw import com.biganto.visual.roompark.data.repository.api.room_park.raw.StreamRaw
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.WebCamRaw import com.biganto.visual.roompark.data.repository.api.room_park.raw.WebCamRaw
/** /**
* Created by Vladislav Bogdashkin on 23.09.2019. * Created by Vladislav Bogdashkin on 23.09.2019.
......
package com.biganto.visual.roompark.domain.use_case
import android.app.Application
import android.media.MediaScannerConnection
import com.biganto.visual.roompark.data.repository.api.biganto.IBigantoApi
import com.biganto.visual.roompark.data.repository.api.biganto.raw.LiteTourMetaRaw
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.model.FileEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.TourFileJunctionEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.fromRaw
import com.biganto.visual.roompark.data.repository.db.requrey.model.toInitialName
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.data.service.download.TourFileData
import com.biganto.visual.roompark.domain.contract.AuthContract
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import com.google.gson.Gson
import com.google.gson.JsonParser
import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import io.reactivex.Observable
import io.reactivex.rxkotlin.Observables
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import okio.Okio
import timber.log.Timber
import java.io.File
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 02.08.2019.
*/
private const val META_PREDICTION="/tourMeta_"
private const val META_FILE_TYPE=".json"
class DownloadUseCase @Inject constructor(
private val db: IDb,
private val api: IBigantoApi,
private val fileModule: FileModule,
private val auth: AuthContract,
private val context: Application
) {
private fun writeFile(
response: ResponseBody,
model: TourFileData,
token: CancellationToken
): Observable<TourFileData> {
return Observable.create<TourFileData> { sub ->
try {
if (token.isCancelled) {
sub.onNext(model)
sub.onComplete()
}
val fileStorage = fileModule.getFile(model.fileUrl.uri())
val sink =
if (model.fileDownloadedSize>0)
Okio.buffer(Okio.appendingSink(fileStorage))
else Okio.buffer(Okio.sink(fileStorage))
val buffer = sink.buffer()
var read = 0L
val step = 8192
val source = response.source()
var stop: Boolean = token.isCancelled
sink.use {
while (!stop && { read = source.read(buffer, step.toLong());read }() != -1L) {
model.tempDownloadedSize += read
model.fileDownloadedSize += read
if (model.tempOverallFileSize == 0L)
model.tempTourTotalDiff += model.tempDownloadedSize
if (token.isCancelled) {
model.isDownloaded = false
model.fileDownloadedSize = 0
sub.onComplete()
stop = true
source.buffer.flush()
}
}
}
model.isDownloaded = (source.exhausted()
&& (model.fileDownloadedSize == model.tempOverallFileSize
|| model.tempOverallFileSize == 0L))
sub.onNext(model.copy())
model.tempTourTotalDiff = 0
model.tempDownloadedSize = 0
sub.onComplete()
sink.close()
} catch (e: Throwable) {
Timber.e(e)
if (!sub.isDisposed)
sub.onError(e)
token.isCancelled = true
}
}
}
private fun mergeFiles(files: List<FileEntity>) {
Timber.d("Nerge files")
files.forEach { file ->
val entity: FileEntity? = db.getFileEntity(file.uri).firstOrNull()
entity?.let {
file.setDownloaded(it.isDownloaded)
file.setDownloadedSize(it.downloadedSize)
file.setTotalSize(it.totalSize)
}
}
Timber.d("save files: ${files.size}")
db.upsertFileEntity(files).blockingSubscribe { Timber.d("file saved") }
}
@Volatile
private var tourDbModel: TourPreviewEntity? = null
private fun setDownloadInfo(
id: String,
downloadedSize: Long? = null
, downloadedDiffSize: Long? = null
, totalSize: Long? = null
, resolution: Int? = null
, filesCount: Int? = null
, tempLoadedFiles: Int? = null
, totalSizedDiffSize: Long? = null
) {
if (tourDbModel?.id != id)
throw error("Wrong tour id")
tourDbModel?.let { entity ->
downloadedSize?.let { entity.downloadedSize = it }
downloadedDiffSize?.let { entity.downloadedSize += it }
totalSize?.let { entity.overallSize = it }
resolution?.let { entity.targetResolution = it }
filesCount?.let { entity.overallFiles = it }
tempLoadedFiles?.let { entity.downloadedFiles += it }
totalSizedDiffSize?.let { entity.overallSize += it }
if (entity.downloadedFiles == entity.overallFiles)
entity.isDownloaded = DownloadState.Downloaded
}
Timber.d(" tour: ${tourDbModel?.downloadedFiles} / ${tourDbModel?.overallFiles}")
}
private fun observableTourDownloading(tour: TourPreviewEntity, token: CancellationToken)
: Observable<TourPreviewEntity> =
api.getTourFiles(tour.id, tour.targetResolution.toString())
.map { tourDbModel = tour;it.first() }
.map { raw ->
var downloadedSize = 0L
var totalSize = 0L
val meta = getLocalMeta(tour)
val fileEntities = fromRaw(raw.files,meta)
mergeFiles(fileEntities)
val jlist = db.getTourFilesJunction(tour.id).toList()
val junctionList = fileEntities
.map {file ->
val entity = jlist.firstOrNull{it.tour == tour.id && it.file.uri() == file.uri.uri()}
?: TourFileJunctionEntity()
entity.setTour(tour.id)
entity.setFile(file.uri)
downloadedSize += file.downloadedSize
totalSize += file.totalSize
entity
}
setDownloadInfo(
raw.id.toString()
, downloadedSize = downloadedSize
, totalSize = totalSize
, resolution = raw.resolution
, filesCount = raw.files.count()
)
junctionList
}
.flatMap{ list ->
tourDbModel?.let {
Observables.zip(
db.upsertTourPreview(it), db.upsertTourFileJunction(list)
).map { list }
}
}
.flatMapIterable { it }
.flatMap { junction ->
db.getFileEntity(junction.file)
.observable()
.map { entity -> TourFileData(entity,junction) }
}
.toFlowable(BackpressureStrategy.BUFFER)
.parallel(4)
.runOn(Schedulers.io())
.filter { !token.isCancelled }
.flatMap { model ->
if (model.isDownloaded)
return@flatMap Flowable.just(model)
val header: HashMap<String, String>? =
if (model.fileDownloadedSize > 0)
hashMapOf(Pair("Range", "bytes=${model.fileDownloadedSize}-"))
else null
api.downloadFile(model.destination, header)
.flatMap<TourFileData> {
writeFile(it, model, token)
.toFlowable(BackpressureStrategy.BUFFER)
.doOnCancel { Timber.w("TOUR DOWNLOADING CANCELLED") }
}
.flatMap { downloadInfo ->
db.upsertFileEntity(
FileEntity().apply {
setUri(downloadInfo.fileUrl)
setDownloadedSize(downloadInfo.fileDownloadedSize)
setTotalSize(downloadInfo.tempOverallFileSize)
setDownloaded(downloadInfo.isDownloaded)
}
)
.toFlowable(BackpressureStrategy.BUFFER)
.map { downloadInfo }
}
}
.sequential()
.toObservable()
.map { model ->
setDownloadInfo(
model.tourId
, totalSizedDiffSize = model.tempTourTotalDiff
, downloadedDiffSize = model.tempDownloadedSize
, tempLoadedFiles = if (model.isDownloaded) 1 else null
)
model.tempDownloadedSize = 0
model.tourId
}
.delay(12L, TimeUnit.MILLISECONDS)
.flatMap { db.upsertTourPreview(tourDbModel!!) }
fun startTourDownloading(tourId: String, cancellataionToken: CancellationToken)
: Observable<TourPreviewEntity> = db.getTourPreview(tourId).observable()
.doOnNext {
it.isDownloaded = DownloadState.Downloading
it.downloadedFiles = 0
it.downloadedSize = 0L
it.tempSize = it.overallSize // <- overall changes due downloading!!
it.overallSize = 0L
}
.flatMap { db.upsertTourPreview(it) }
.flatMap(::getMeta)
.doOnError(Timber::e)
.flatMap { observableTourDownloading(it, cancellataionToken) }
private fun getMeta(tour: TourPreviewEntity) =
api.getTourMetaAsString(tour.id)
.map { meta ->
tour.apply {
try {
val jMeta = JsonParser().parse(meta).asJsonArray.first()
val metaLite = Gson().fromJson(jMeta, LiteTourMetaRaw::class.java)
val metaUri = RevisionString("${META_PREDICTION}${tour.id}${META_FILE_TYPE}")
setMetaFileEntityId(metaUri)
setFootageBaseUrl(toInitialName(metaLite.baseurl))
setTourBaseUrl(toInitialName(metaLite.tour_baseurl))
fileModule.saveFileToDisk(
fileModule.getAssetFile(
File.separator
.plus(tour.footageBaseUrl)
.plus(metaUri.uri())
)
, meta
)
} catch (e: Exception) {
Timber.e(e)
}
}
tour
}
.flatMap { db.upsertTourPreview(it) }
.onErrorReturn {
tour.isDownloaded = DownloadState.Crushed
db.upsertTourPreview(tour).blockingSubscribe()
tour
}
private fun getLocalMeta(tour: TourPreviewEntity): LiteTourMetaRaw {
return Gson().fromJson(
JsonParser().parse(
fileModule.getAssetFile(
File.separator
.plus(tour.footageBaseUrl)
.plus(File.separator)
.plus(tour.metaFileEntityId?.uri())
).readText()
).asJsonArray.first()
, LiteTourMetaRaw::class.java
)
}
//#endregion oldMethod
private fun refreshGallery(file: File) {
MediaScannerConnection.scanFile(
context, arrayOf(file.path), null
)
{ path, uri ->
{}//Timber.d("Scanned $path")
}
}
data class CancellationToken(var isCancelled: Boolean)
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.use_case package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.data.repository.db.requrey.model.DealEntity
import com.biganto.visual.roompark.domain.contract.AuthContract
import com.biganto.visual.roompark.domain.contract.DealContract import com.biganto.visual.roompark.domain.contract.DealContract
import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.fromEntity
import io.reactivex.Observable
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
/** /**
...@@ -8,10 +14,23 @@ import javax.inject.Inject ...@@ -8,10 +14,23 @@ import javax.inject.Inject
*/ */
class DealseUseCase @Inject constructor( class DealseUseCase @Inject constructor(
private val contract: DealContract private val contract: DealContract,
private val authContract: AuthContract
) { ) {
fun getDeals() = contract.getDeals() fun prefetchDeal(): Observable<List<DealModel>> =
authContract.currentUser()
.map {user ->
user.deals?.asSequence()
?.map { it as DealEntity }
?.filterNotNull()?.toList()
}
.map { fromEntity(it, ::fromEntity) }
fun getDeals(): Observable<List<DealModel>> =
authContract.currentUser()
.flatMap(contract::getDealsApi)
.map { fromEntity(it, ::fromEntity) }
fun setDealRead(id:String) = contract.setDealRead(id) fun setDealRead(id:String) = contract.setDealRead(id)
......
package com.biganto.visual.roompark.domain.use_case package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.data.repository.db.requrey.model.EstateEntity
import com.biganto.visual.roompark.domain.contract.AuthContract
import com.biganto.visual.roompark.domain.contract.DealContract import com.biganto.visual.roompark.domain.contract.DealContract
import com.biganto.visual.roompark.domain.contract.FlatPlanContract
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.fromEntity
import io.reactivex.Observable
import javax.inject.Inject import javax.inject.Inject
/** /**
...@@ -8,10 +14,24 @@ import javax.inject.Inject ...@@ -8,10 +14,24 @@ import javax.inject.Inject
*/ */
class EstateUseCase @Inject constructor( class EstateUseCase @Inject constructor(
private val contract: DealContract private val contract: DealContract,
private val planContract: FlatPlanContract,
private val authContract: AuthContract
) { ) {
fun fetchFavorites() = contract.getFavorites()
fun prefetchFavorites() =
authContract.currentUser()
.map {user -> user.estates?.asSequence()
?.map { it as EstateEntity }
?.filter{ it.favorite }?.filterNotNull()?.toList()
}
.map { fromEntity(it, ::fromEntity) }
fun fetchFavorites(): Observable<List<EstateModel>> =
authContract.currentUser()
.flatMap (contract::fetchFavorites)
.map { fromEntity(it, ::fromEntity) }
fun getEstate(estateId: Int) = contract.getEstate(estateId) fun getEstate(estateId: Int) = contract.getEstate(estateId)
...@@ -19,11 +39,8 @@ class EstateUseCase @Inject constructor( ...@@ -19,11 +39,8 @@ class EstateUseCase @Inject constructor(
contract.fetchEstate(building, number) contract.fetchEstate(building, number)
fun getEstatePlanPresets(estateId: Int) = fun getEstatePlanPresets(estateId: Int) =
contract.getPlanTypes(estateId) planContract.getPlanTypes(estateId)
fun getPlan(estateId: Int, planId: Int) =
contract.getEmptyPlan(estateId, planId)
fun getPlan( fun getPlan(
estateId: Int estateId: Int
...@@ -32,7 +49,7 @@ class EstateUseCase @Inject constructor( ...@@ -32,7 +49,7 @@ class EstateUseCase @Inject constructor(
, sizes: Boolean? , sizes: Boolean?
, walls: Boolean? , walls: Boolean?
, electric: Boolean? , electric: Boolean?
) = contract.getPlan( ) = planContract.getPlan(
estateId estateId
, planId , planId
, furniture , furniture
......
package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.data.data_provider.featuresVariants
import com.biganto.visual.roompark.data.repository.db.requrey.model.EstateEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import com.biganto.visual.roompark.domain.contract.AuthContract
import com.biganto.visual.roompark.domain.contract.DealContract
import com.biganto.visual.roompark.domain.contract.FlatPlanContract
import io.reactivex.Observable
import io.reactivex.rxkotlin.Observables
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 20.04.2020.
*/
class PlanTypesUseCase @Inject constructor(
private val planContract: FlatPlanContract,
private val auth:AuthContract,
private val dealContract: DealContract
) {
private fun fetchUserEstates(user:UserEntity): Observable<List<EstateEntity>> =
Observables.zip(
dealContract.fetchDeals(user)
.map { it.map {deal -> deal.estate as EstateEntity } }
,dealContract.fetchFavorites(user))
{t1,t2 -> t1+t2}
private val fetchAllPlanTypes =
auth.currentUser()
.flatMap {user ->
fetchUserEstates(user)
.onErrorReturn { arrayListOf() }
.map { apiList ->
val list =
user.deals?.map { it.estate as EstateEntity }?.toMutableList()
?: mutableListOf()
list.addAll(user.estates?.map { it as EstateEntity } ?: arrayListOf())
(list+apiList).distinctBy { it.id }
}
}
fun fetchNotDownloadedPlansSizes(): Observable<Int> =
fetchAllPlanTypes
.flatMapIterable { it }
.flatMap {
planContract.getPlanTypes(it.id)
.map { plantypes -> plantypes.flatMap { plan -> plan.featuresVariants } }
}
.map { list -> list.sumBy { if (planContract.getPlanFile(it).exists()) 0 else 1 } }
.scan { t1: Int, t2: Int -> t1 + t2 }
fun downloadAllPlanTypes(cancellationToken: DownloadUseCase.CancellationToken): Observable<DownloadProgress> {
var listSize = 0
return fetchAllPlanTypes
.flatMapSingle {
Observable.fromIterable(it)
.flatMap { estateEntity ->
planContract.getPlanTypes(estateEntity.id)
.map { plantypes -> plantypes.flatMap { plan -> plan.featuresVariants } }
}
.toList()
}
.map { it.flatten() }
.filter { !cancellationToken.isCancelled }
.flatMap { list ->
planContract.getPlansObservable(list,cancellationToken)
.map { 1 }
.scan(0,
{ t1, t2 -> t1 + t2 })
.map { completed ->
DownloadProgress(completed, list.size)
}
}
}
}
// private fun downloadPlan(
// list: PlanFeaturesVariant
// , cancellationToken: DownloadUseCase.CancellationToken
// )
// : Observable<DownloadProgress> {
//
// val avaliablethread = true
//
// return Observable.fromIterable(list)
//
//
// .delay(100,TimeUnit.MILLISECONDS)
// .doOnNext { Timber.w("emmited") }
// .delay { variant ->
// planContract.getPlan(variant)}
// .filter { !cancellationToken.isCancelled }
//
// }
data class DownloadProgress(
val currentProgress:Int,
val totalProgress:Int
)
\ No newline at end of file
...@@ -2,6 +2,7 @@ package com.biganto.visual.roompark.domain.use_case ...@@ -2,6 +2,7 @@ package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.data.data_provider.AuthContractModule import com.biganto.visual.roompark.data.data_provider.AuthContractModule
import com.biganto.visual.roompark.domain.contract.FilesContract import com.biganto.visual.roompark.domain.contract.FilesContract
import com.biganto.visual.roompark.domain.contract.TourContract
import io.reactivex.Observable import io.reactivex.Observable
import javax.inject.Inject import javax.inject.Inject
...@@ -11,26 +12,28 @@ import javax.inject.Inject ...@@ -11,26 +12,28 @@ import javax.inject.Inject
class SettingsUseCase @Inject constructor( class SettingsUseCase @Inject constructor(
private val authContract: AuthContractModule, private val authContract: AuthContractModule,
private val fileContract: FilesContract private val fileContract: FilesContract,
private val tourContract: TourContract
){ ){
fun signOut() = authContract.signOut() fun signOut() = authContract.signOut()
fun clearAllCache() = fileContract.deleteAllFiles() fun clearAllCache(): Observable<Pair<Int, Int>> =
tourContract.deleteToursDbInfo()
.andThen(fileContract.deleteAllFiles())
val planTypesSize val planTypesSize
get() = Observable.just(fileContract.getPlansSize()) get() = Observable.defer { Observable.just(fileContract.getPlansSize()) }
val albumsSize val albumsSize
get() = Observable.just(fileContract.getAlbumSize()) get() = Observable.defer { Observable.just(fileContract.getAlbumSize()) }
val feedsSize val feedsSize
get() = Observable.just(fileContract.getFeedSize()) get() = Observable.defer { Observable.just(fileContract.getFeedSize()) }
val toursSize val toursSize
get() = Observable.just(fileContract.getToursSize()) get() = Observable.defer { Observable.just(fileContract.getToursSize()) }
val overallSize val overallSize
get() = Observable.just(fileContract.allCacheSize()) get() = Observable.defer { Observable.just(fileContract.allCacheSize()) }
} }
\ No newline at end of file
package com.biganto.visual.roompark.domain.use_case package com.biganto.visual.roompark.domain.use_case
//import io.reactivex.Observable
import com.biganto.visual.roompark.data.repository.db.requrey.model.DealEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.SubscriptionEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import com.biganto.visual.roompark.domain.contract.AuthContract
import com.biganto.visual.roompark.domain.contract.DeviceUtilsContract import com.biganto.visual.roompark.domain.contract.DeviceUtilsContract
import com.biganto.visual.roompark.domain.contract.SubscriptionContract import com.biganto.visual.roompark.domain.contract.SubscriptionContract
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.model.SubscriptionTopic
import com.biganto.visual.roompark.domain.model.TitledSubscriptionModel
import com.biganto.visual.roompark.domain.model.fromEntity
import io.reactivex.Observable
import io.reactivex.rxkotlin.Observables
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
//import io.reactivex.rxkotlin
/** /**
* Created by Vladislav Bogdashkin on 01.11.2019. * Created by Vladislav Bogdashkin on 01.11.2019.
...@@ -10,25 +24,101 @@ import javax.inject.Inject ...@@ -10,25 +24,101 @@ import javax.inject.Inject
class SubscriptionUseCase @Inject constructor( class SubscriptionUseCase @Inject constructor(
private val subscription: SubscriptionContract, private val subscription: SubscriptionContract,
private val utils: DeviceUtilsContract private val utils: DeviceUtilsContract,
){ private val auth: AuthContract
) {
fun subscribeTopic(topic:String, topicId:String?=null) =
utils.getDeviceId()
.flatMap{deviceToken -> subscription.subscribeTopic(
deviceToken,
topic,
topicId
) }
fun unSubscribeTopic(topic:String, topicId:String?=null) =
utils.getDeviceId() fun subscribeTopic(subId: Int, topic: SubscriptionTopic): Observable<List<TitledSubscriptionModel>> =
.flatMap{deviceToken -> subscription.unSubscribeTopic( Observables.zip(auth.currentUser(), utils.getDeviceId()){
deviceToken, user, token -> SubscribeRequestModel(user,token)
topic, }
topicId .flatMap {requestModel ->
) } subscription.subscribeTopicResult(
requestModel.user
, subId
, requestModel.deviceToken
, topic = topic.topicName
, topic_id = topic.topicId
)
.map {subs -> subsToTitle(requestModel.user,subs)}
.subscribeOn(Schedulers.io())
}
fun unSubscribeTopic(subId: Int, topic: SubscriptionTopic): Observable<List<TitledSubscriptionModel>> =
Observables.zip(auth.currentUser(), utils.getDeviceId()){
user, token -> SubscribeRequestModel(user,token)
}
.flatMap {requestModel ->
subscription.unSubscribeTopicResult(
requestModel.user
, subId
, requestModel.deviceToken
, topic = topic.topicName,
topic_id = topic.topicId
)
.map {subs -> subsToTitle(requestModel.user,subs)}
.subscribeOn(Schedulers.io())
}
private fun subsToTitle(user:UserEntity,subs:List<SubscriptionModel>?)
: List<TitledSubscriptionModel> {
val subList =
subs
?: user.subscriptions
?.asSequence()?.map { it as SubscriptionEntity }?.map(::fromEntity)?.toList()
?: arrayListOf()
return List(subList.size){ i ->
subToTitledSub(user.deals?.map { it as DealEntity }?: arrayListOf(),subList[i])
}
}
private fun subToTitledSub(deals:List<DealEntity>,sub:SubscriptionModel)
: TitledSubscriptionModel {
var title = SubscriptionTopic.titleByTopic(sub.topic)
if (sub.topic is SubscriptionTopic.Deals) {
val deal =
deals.firstOrNull { d -> d.estateCrmId == sub.topic.topicId }
if (deal == null)
Timber.e("Deal not recognized: $sub ; $deals")
title = "$title № ${deal?.estate?.number}"
}
return TitledSubscriptionModel(title, sub)
}
fun getCurrentUserSubscriptions(): Observable<List<TitledSubscriptionModel>> =
auth.currentUser()
.map {user -> subsToTitle(user,null)}
fun getSubscriptions(topic: SubscriptionTopic): Observable<SubscriptionModel> =
auth.currentUser()
.map {user ->
var sub = user.subscriptions
?.firstOrNull { it.topic == topic.topicName && it.number == topic.topicId }
if (sub == null) {
sub = SubscriptionEntity()
sub.setOwner(user)
sub.setTopic(topic.topicName)
sub.setNumber(topic.topicId)
sub.setState(false)
}
subscription.saveSubscribeState(sub as SubscriptionEntity)
.blockingFirst()
}
.map(::fromEntity)
}
private data class SubscribeRequestModel(
val user:UserEntity,
val deviceToken:String
)
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.use_case
import android.content.res.Resources
import com.biganto.visual.roompark.data.repository.api.biganto.IBigantoApi
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourPreviewRaw
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import com.biganto.visual.roompark.domain.contract.AuthContract
import com.biganto.visual.roompark.domain.contract.TourContract
import com.biganto.visual.roompark.domain.custom_exception.CustomApiException
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.domain.model.fromEntity
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.fromRaw
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 19.06.2018.
*/
const val EMPTY_PARENT = -778
const val TOUR_IDS_TO_DOWNLOAD_KEY = "DOWNLOAD_MANAGER_IDS_TO LOAD"
class TourPreviewsUseCase @Inject constructor(
private val api:IBigantoApi
,private val db: IDb
,private val auth:AuthContract
,private val tours:TourContract
) {
fun getTourById(tourId: String): Observable<DownloadTourByIdResult> {
val tour = retrieveTour(tourId)
Timber.d("retrieveTour $tourId / $tour")
if (tour != null) {
if (tour.downloadState == DownloadState.Downloaded)
return Observable.just<DownloadTourByIdResult>(
DownloadTourByIdResult.ReadyToPlay(
tour
)
)
return setTourState(tour.tour_id, DownloadState.DownloadQueue)
.map<DownloadTourByIdResult> { DownloadTourByIdResult.DownloadStarted(tour) }
}
return syncTour(tourId, parentId = EMPTY_PARENT)
.map { it }
.map<DownloadTourByIdResult> { DownloadTourByIdResult.DownloadStarted(it) }
}
fun retrieveTour(tourId: String) =
db.getTourPreview(tourId)
.asSequence()
.map(::fromEntity)
.firstOrNull()
private fun setTourState(tourId: String, state: DownloadState): Observable<Boolean> =
db.getTourPreview(tourId)
.observable()
.doOnNext { it.isDownloaded = state }
.flatMap { db.upsertTourPreview(it).map { true } }
private fun syncTour(tourId: String, parentId: Int): Observable<TourModel> =
syncTour(tourId, parentId, calcTargetResolution())
private fun syncTour(tourId: String, estateId: Int, targetResolution: Int)
: Observable<TourModel> {
val request = ToursRemoteByIdRequestModel(
arrayListOf(tourId)
, estateId
, null
, targetResolution
)
return forceTourUpdate(request)
.map {
if (it.isNullOrEmpty())
throw CustomApiException.TourByIdNotFoundException()
it.first()
}
.doOnNext { it.isDownloaded = DownloadState.DownloadQueue }
.flatMap { db.upsertTourPreview(it) }
.map(::fromEntity)
.doOnError(Timber::e)
}
private fun calcTargetResolution(): Int {
val w = Resources.getSystem().displayMetrics.widthPixels
val h = Resources.getSystem().displayMetrics.heightPixels
return w.coerceAtLeast(h)
}
private fun forceTourUpdate(requestModel: ToursRemoteByIdRequestModel): Observable<List<TourPreviewEntity>> =
api.getToursPreviewById(requestModel.ids)
.subscribeOn(Schedulers.io())
.map {
fromRaw(
it
, requestModel.estateId
, api.provideHttpUrl().toString()
, requestModel.targetResolution
)
}
private fun fetchDbTourList(estateId: Int) =
db.getEstateTourPreviews(estateId)
.filter { !it.isNullOrEmpty() }
private fun fetchApiTourList(multitourId: Int, estateId: Int) =
auth.currentUser()
.map { it.targetResolution }
.flatMap { res ->
api.getOfferTours(multitourId)
.doOnError { Timber.e(it) }
.map {
mergeRaw(
it, TourRemoteRequestModel(
estateId,
api.provideHttpUrl().toString(),
res
)
)
}
.map { it }
.doOnNext { db.blockingUpsert(it) }
}
fun fetchTourOffer(multitourId: Int, parent: Int): Observable<List<TourModel>> =
Observable.mergeDelayError(
arrayListOf(fetchDbTourList(parent), fetchApiTourList(multitourId, parent))
)
.take(1)
.doOnError { Timber.e(it) }
.map(::fromEntity)
.subscribeOn(Schedulers.io())
private fun mergeRaw(raw: List<TourPreviewRaw>, requestRequestModel: TourRemoteRequestModel) =
raw.map { mergeRaw(it, requestRequestModel) }
private fun mergeRaw(raw: TourPreviewRaw, requestRequestModel: TourRemoteRequestModel)
: TourPreviewEntity {
Timber.d("got raw: $raw")
val entity = db.getTourPreview(raw.id.toString()).firstOrNull()
?: return fromRaw(
raw
, requestRequestModel.estateId
, api.provideHttpUrl().toString()
, requestRequestModel.targetResolution
)
val fromApi = fromRaw(
raw
, requestRequestModel.estateId
, api.provideHttpUrl().toString()
, requestRequestModel.targetResolution
)
if (fromApi.updated > entity.updated
&& entity.isDownloaded == DownloadState.Downloaded
) {
entity.isDownloaded =
DownloadState.NotSynced//maxState(entity.isDownloaded, fromApi.isDownloaded)
return entity
}
// In case< where we need to update tour parent, when it was downloaded by link
// if (fromApi.estate.id!=requestRequestModel.estateId)
// entity.estate=fromApi.estate
fromApi.setMetaFileEntityId(entity.metaFileEntityId)
fromApi.targetResolution = entity.targetResolution
fromApi.isDownloaded = entity.isDownloaded
fromApi.overallSize = entity.overallSize
fromApi.downloadedSize = entity.downloadedSize
fromApi.tempSize = entity.tempSize
fromApi.overallFiles = entity.overallFiles
fromApi.downloadedFiles = entity.downloadedFiles
return fromApi
}
private fun getUserEstates(user: UserEntity) = user.deals?.map { it.estate }?.plus(
user.estates
?.asSequence()
?.filter { it.favorite }
?.asIterable()
?: arrayListOf()
)
fun fetchToursSizes(): Observable<Long> =
auth.currentUser()
.map(::getUserEstates)
.map { it.filter { estates -> estates.multitourId != null }.toList() }
.map { estates ->
val toursOffersToLoad = mutableListOf<Int>()
estates.forEach {
if (it.tours.isNullOrEmpty())
toursOffersToLoad.add(it.multitourId!!)
}
Pair(estates, toursOffersToLoad)
}
.flatMap { pair ->
if (pair.second.isNotEmpty()) {
val loadedIds = mutableListOf<String>()
return@flatMap Observable.merge(
pair.second.map {
fetchTourOffer(
it, pair.first.first { estete -> estete.multitourId == it }.id
).doOnError { Timber.e(it) }
.doOnNext { tours -> loadedIds.addAll(tours.map { t -> t.tour_id }) }
}
).takeLast(1)
.doOnNext { db.refreshEntities(pair.first) }
.map { Pair(pair.first, loadedIds) }
} else return@flatMap Observable.just(Pair(pair.first, null))
}
.flatMap { pair ->
val estates = pair.first
val r = (estates.first().user as UserEntity).targetResolution
var knownSize =
estates.flatMap {
it.tours?.map { t -> t as TourPreviewEntity }
?.asIterable() ?: arrayListOf()
}
.filter { t -> t.overallSize > 0L }
.sumByLong { it.overallSize - it.downloadedSize }
val toursToLoadSize =
estates.flatMap {
it.tours?.map { t -> t as TourPreviewEntity }
?.asIterable() ?: arrayListOf()
}
.filter { t -> t.overallSize < 1L }
.map { it.id }.toMutableList()
pair.second?.let { toursToLoadSize.addAll(it) }
if (toursToLoadSize.isNullOrEmpty()) return@flatMap Observable.just(knownSize)
else return@flatMap api.getTourFilesSizes(
toursToLoadSize, r.toString()
)
.flatMapIterable { it }
.flatMap { rawSize ->
db.getTourPreview(rawSize.id.toString())
.observable()
.map { entity ->
knownSize += rawSize.total_size
entity.apply {
overallSize = rawSize.total_size
targetResolution = rawSize.resolution
}
entity
}
.doOnNext { db.upsert(it).blockingGet() }
}.map { knownSize }
}
.subscribeOn(Schedulers.io())
private inline fun <T> Iterable<T>.sumByLong(selector: (T) -> Long): Long {
var sum = 0L
for (element in this) {
sum += selector(element)
}
return sum
}
fun downloadAllDeelsAndEstates(): Observable<Iterable<TourPreviewEntity>> =
auth.currentUser()
.map { user ->
val estatesList = getUserEstates(user)
estatesList
?.asSequence()
?.filter { it.multitourId != null }
?.toList()
?.map {
Pair(
it.multitourId!!
, TourRemoteRequestModel(
estateId = it.id
, targetResolution = user.targetResolution
)
)
}
}
.map { list ->
list.map { pair ->
api.getOfferTours(pair.first)
.map {
mergeRaw(it, pair.second)
}
.map { it }
.blockingFirst()
}
.flatten()
}
.flatMap { db.upsertTourPreview(it) }
.subscribeOn(Schedulers.io())
}
data class TourRemoteRequestModel(val estateId: Int, val token: String? = null, val targetResolution: Int)
data class ToursRemoteByIdRequestModel(val ids: List<String>, val estateId: Int,
val token: String? = null, val targetResolution: Int)
sealed class DownloadTourByIdResult{
class ReadyToPlay(val tour:TourModel): DownloadTourByIdResult()
class DownloadStarted(val tour:TourModel): DownloadTourByIdResult()
class TourError(val id:String): DownloadTourByIdResult()
}
package com.biganto.visual.roompark.player
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Handler
import android.view.OrientationEventListener
import android.view.View
import android.view.animation.AccelerateInterpolator
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
import android.view.animation.Animation.AnimationListener
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.player.unity_utils.IBigantoUnityPlugin
import com.biganto.visual.roompark.player.unity_utils.LoadTourConfig
import com.biganto.visual.roompark.player.unity_utils.UnityOrientation
import com.biganto.visual.roompark.player.unity_utils.requests_raw.UPBaseRequest
import com.biganto.visual.roompark.player.unity_utils.requests_raw.UPBuildTourRequest
import com.biganto.visual.roompark.player.unity_utils.requests_raw.UPChangeOrientationRequest
import com.bumptech.glide.Glide
import com.google.gson.GsonBuilder
import com.unity3d.player.UnityPlayer
import kotlinx.android.synthetic.main.activity_biganto_player.*
//import kotlinx.android.synthetic.main.activity_biganto_player.*
import timber.log.Timber
const val PLAY_TOUR_DATA_EXTRAS="BIGANTO_PLAYER_TOUR_DATA"
const val ERROR_TOUR_BUILD_FINISH_CODE=8601
const val UNITY_SCENE_LOADED_FADE_MILLS=600L
private fun UPBaseRequest.requestToJson():String{
return GsonBuilder().create().toJson(this, javaClass)
}
class BigantoPlayerActivity : Activity(), UnityResponseListener {
lateinit var preview: ImageView
lateinit var previewBackground: FrameLayout
lateinit var biganto: ImageView
lateinit var progress: ProgressBar
lateinit var container: FrameLayout
lateinit var unityContainer: FrameLayout
private var mUnityPlayer: UnityPlayer? = null
private var bigantoPluginListener: IBigantoUnityPlugin? = null
private lateinit var orientationListener: OrientationListener
// @Inject
// lateinit var appLifeCycle: AppLifecycleListener
private lateinit var tourObj: LoadTourConfig
override fun onCreate(savedInstanceState: Bundle?) {
Timber.d("CREATING PLAYER ACTIVITY")
super.onCreate(savedInstanceState)
orientationListener = OrientationListener(this)
// appLifeCycle = BigantoApplication.component.appLifeCycle()
//
// ProcessLifecycleOwner.get().lifecycle
// .addObserver(appLifeCycle)
setContentView(R.layout.activity_biganto_player)
container = this.frameContainer
unityContainer = this.unityFrameContainer
preview = this.playerPreview
previewBackground = this.previewBack
biganto = this.bigantoLogo
progress = this.loadingBar
if (mUnityPlayer == null) {
mUnityPlayer = UnityPlayer(this)
}
tourObj = intent.getParcelableExtra(PLAY_TOUR_DATA_EXTRAS)
val animated = AnimatedVectorDrawableCompat.create(this, R.drawable.biganto_splash_anim)
animated?.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable?) {
biganto.post { animated.start() }
}
})
biganto.setImageDrawable(animated)
animated?.start()
Glide.with(this)
.load(tourObj.previewUri)
// .centerCrop()
.into(preview)
.also {
fadeOutAndHideImage(1f, 0f, 1500, biganto)
Handler().postDelayed(
{
fadeOutAndHideImage(0f, 1f, 1500, preview)
}
, 1200
)
}
}
private fun fadeOutAndHideImage(from: Float, to: Float, duration: Long, img: ImageView) {
val fadeOut = AlphaAnimation(from, to)
fadeOut.interpolator = AccelerateInterpolator()
fadeOut.duration = duration
fadeOut.setAnimationListener(object : AnimationListener {
override fun onAnimationEnd(animation: Animation) {
if (from > to) img.visibility = View.GONE
}
override fun onAnimationRepeat(animation: Animation) {}
override fun onAnimationStart(animation: Animation) {
if (from < to) img.visibility = View.VISIBLE
}
})
img.startAnimation(fadeOut)
}
private fun quitPlayer(){
mUnityPlayer?.stop()
mUnityPlayer?.quit()
}
override fun onBackPressed() {
finish()
}
@SuppressLint("SourceLockedOrientationActivity")
override fun onDestroy() {
this.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
quitPlayer()
super.onDestroy()
}
/**
* invokes from unity by AndroidJavaObject mechanics
*/
fun setUnityPlayerListener(listener: IBigantoUnityPlugin) {
this.bigantoPluginListener = listener
// Timber.d(" UNITY SETTING LISTENER THROGH ACTIVITY")
}
override fun unitySceneLoaded() {
runOnUiThread {
preview.visibility = View.VISIBLE
}
mUnityPlayer?.view?.visibility = View.VISIBLE
bigantoPluginListener?.sendUnityTourBuildRequest(UPBuildTourRequest(tourObj).requestToJson())
}
override fun unityTourBuilded() {
runOnUiThread {
//preview.visibility = View.GONE
fadeOutAndHideImage(1f, 0f, UNITY_SCENE_LOADED_FADE_MILLS, preview)
progress.visibility = View.GONE
previewBackground.visibility = View.GONE
biganto.visibility = View.GONE
}
mUnityPlayer?.view?.visibility = View.VISIBLE
}
override fun unityTourBuildFailed() {
Timber.d("Tour build failed! Finishing activity...")
finish()
}
override fun unityTourBuildProgress(progress: Int, total: Int) {
runOnUiThread {
preview.visibility = View.VISIBLE
}
mUnityPlayer?.view?.visibility = View.VISIBLE
}
override fun unityCrushed(message: String) {
runOnUiThread {
preview.visibility = View.VISIBLE
}
mUnityPlayer?.view?.visibility = View.VISIBLE
}
override fun onNewIntent(intent: Intent) {
Timber.d("PLAYER ACTIVITY GOT INTNET")
// To support deep linking, we need to make sure that the client can get access to
// the last sent intent. The clients access this through a JNI api that allows them
// to get the intent set on launch. To update that after launch we have to manually
// replace the intent with the one caught here.
setIntent(intent)
}
// Pause Unity
override fun onPause() {
super.onPause()
mUnityPlayer?.pause()
}
// Resume Unity
override fun onResume() {
Timber.d("PLAYER ACTIVITY RESUME")
super.onResume()
mUnityPlayer?.resume()
}
override fun onStart() {
super.onStart()
orientationListener.enable()
if (mUnityPlayer?.view?.parent != null) return
mUnityPlayer?.start()
unityContainer.addView(mUnityPlayer?.view)
}
override fun onStop() {
orientationListener.disable()
bigantoPluginListener = null
mUnityPlayer?.stop()
super.onStop()
}
// Low Memory Unity
override fun onLowMemory() {
super.onLowMemory()
mUnityPlayer?.lowMemory()
}
// Trim Memory Unity
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
mUnityPlayer?.lowMemory()
}
}
// This ensures the layout will be correct.
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
mUnityPlayer?.configurationChanged(newConfig)
}
// Notify Unity of the focus change.
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
mUnityPlayer?.windowFocusChanged(hasFocus)
}
//Uncomment this, if you want control backstack inside Unity engine
// // For some reason the multiple keyevent type is not supported by the ndk.
// // Force event injection by overriding dispatchKeyEvent().
// override public boolean dispatchKeyEvent(KeyEvent event)
// {
// if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
// return mUnityPlayer.injectEvent(event);
// return super.dispatchKeyEvent(event);
// }
//
// Pass any events not handled by (unfocused) views straight to UnityPlayer
// override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
// return mUnityPlayer?.injectEvent(event) ?: false;
// }
//
// override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
// return mUnityPlayer?.injectEvent(event) ?: false
// }
//
// override fun onTouchEvent(event: MotionEvent): Boolean {
// return mUnityPlayer?.injectEvent(event) ?: false
// }
// /*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }
inner class OrientationListener(context: Context) : OrientationEventListener(context) {
private val ROTATION_O = 1
private val ROTATION_90 = 2
private val ROTATION_180 = 3
private val ROTATION_270 = 4
var rotation = 0
override fun onOrientationChanged(orientation: Int) {
var newRotation = rotation
if ((orientation < 35 || orientation > 325) && rotation != ROTATION_O) { // PORTRAIT
newRotation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
} else if (orientation in 146..214 && rotation != ROTATION_180) { // REVERSE PORTRAIT
newRotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
} else if (orientation in 56..124 && rotation != ROTATION_270) { // REVERSE LANDSCAPE
newRotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
} else if (orientation in 236..304 && rotation != ROTATION_90) { //LANDSCAPE
newRotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
}
if (newRotation != rotation) {
rotation = newRotation
// UnityBridge.runOnUnityThread {
Timber.d("Unity change orientation request")
// UnityBridge.runOnUnityThread { bigantoPluginListener?.sendUnityChangeOrientationRequest(rotation.toUnityOrientationId().requestToJson()) }
// }
}
}
fun Int.toUnityOrientationId(): UPChangeOrientationRequest {
val orId = when (this) {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT -> UnityOrientation.Portrait
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE -> UnityOrientation.LandscapeLeft
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE -> UnityOrientation.LandscapeRight
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT -> UnityOrientation.PortraitUpsideDown
else -> UnityOrientation.Portrait
}
return UPChangeOrientationRequest(orId)
}
}
}
interface UnityResponseListener{
fun unitySceneLoaded()
fun unityTourBuilded()
fun unityTourBuildProgress(progress:Int, total:Int)
fun unityCrushed(message:String)
fun unityTourBuildFailed()
}
\ No newline at end of file
package com.biganto.visual.roompark.player.unity_utils
import android.content.Context
import com.unity3d.player.UnityPlayer
/**
* Created by Vladislav Bogdashkin on 08.08.2018.
*/
class BigantoTourPlayer(p0: Context) : UnityPlayer(p0)
{
// val title = "Bigant Visual Tour Player"
// override fun kill() {
//// }
}
\ No newline at end of file
package com.biganto.visual.roompark.player.unity_utils;
public interface IBigantoUnityPlugin{
public void sendUnitySceneLoadRequest(String request);
public void sendUnityTourBuildRequest(String request);
public void sendUnityFinishRequest(String request);
public void sendUnityPauseRequest(String request);
public void sendUnityErrorRequest(String request);
public void sendUnityChangeOrientationRequest(String request);
}
package com.biganto.visual.roompark.player.unity_utils
import android.os.Parcel
import android.os.Parcelable
/**
* Created by Vladislav Bogdashkin on 08.05.2019.
*/
data class LoadTourConfig(
val tourId:Int,
val targetResolution:Int,
val cacheFolderUri:String,
val metaUri:String,
val previewUri:String?,
val AllowVR:Boolean,
val AllowDollhouse:Boolean) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readInt(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readInt()!= 0,
parcel.readInt() == 1
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(tourId)
parcel.writeInt(targetResolution)
parcel.writeString(cacheFolderUri)
parcel.writeString(metaUri)
parcel.writeString(previewUri)
parcel.writeInt(if (AllowVR)1 else 0 )
parcel.writeInt(if (AllowDollhouse)1 else 0)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<LoadTourConfig> {
override fun createFromParcel(parcel: Parcel): LoadTourConfig {
return LoadTourConfig(parcel)
}
override fun newArray(size: Int): Array<LoadTourConfig?> {
return arrayOfNulls(size)
}
}
}
\ No newline at end of file
package com.biganto.visual.roompark.player.unity_utils;
import android.os.Handler;
import timber.log.Timber;
/**
* Created by Vladislav Bogdashkin on 23.08.2018.
*/
public final class UnityBridge {
// Содержит ссылку на C# реализацию интерфейса
// Перенаправляет вызов в Unity поток
private static Handler unityMainThreadHandler;
// Функция перевода выполнения в Unity поток, потребуется в дальнейшем
public static void runOnUnityThread(Runnable runnable) {
if (unityMainThreadHandler != null && runnable != null) {
unityMainThreadHandler.post(runnable);
}
}
public static void unRegisterHandler() {
if(unityMainThreadHandler != null)
unityMainThreadHandler.getLooper().quit();
unityMainThreadHandler=null;
}
public static void registerHandler() {
Timber.d(" handler: %s",unityMainThreadHandler );
if(unityMainThreadHandler == null) {
// Так как эту функцию вызываем всегда на старте Unity,
// этот вызов идет из нужного нам в дальнейшем потока,
// создадим для него Handler
unityMainThreadHandler = new Handler();
}
Timber.d(" handler: %s",unityMainThreadHandler );
}
}
\ No newline at end of file
package com.biganto.visual.roompark.player.unity_utils;
import com.google.gson.annotations.SerializedName;
public enum UnityLayoutViewState{
@SerializedName("0")
BINDING (0),
@SerializedName("1")
READY (1),
@SerializedName("2")
DETACHED (2),
@SerializedName("3")
DESTROYED (3);
private final int value;
public int getValue() {
return value;
}
private UnityLayoutViewState(int value) {
this.value = value;
}
}
package com.biganto.visual.roompark.player.unity_utils;
/**
* Created by Vladislav Bogdashkin on 28.08.2018.
*/
public enum UnityOrientation {
Unknown (0),
// Portrait orientation.
Portrait (1),
// Portrait orientation, upside down.
PortraitUpsideDown (2),
// Landscape orientation, counter-clockwise from the portrait orientation.
LandscapeLeft (3),
LandscapeRight (4),
// Auto-rotates the screen as necessary toward any of the enabled orientations.
AutoRotation (5);
int id;
private UnityOrientation(int i){id = i;}
public int GetID(){return id;}
public boolean IsEmpty(){return this.equals(UnityOrientation.Unknown);}
public boolean Compare(int i){return id == i;}
public static UnityOrientation GetValue(int _id)
{
UnityOrientation[] As = UnityOrientation.values();
for (UnityOrientation A : As) {
if (A.Compare(_id))
return A;
}
return UnityOrientation.Unknown;
}
}
package com.biganto.visual.roompark.player.unity_utils;
import com.google.gson.annotations.SerializedName;
public enum UnityPlayerViewState{
@SerializedName("0")
NotInitialized(0),
@SerializedName("1")
PreLoading(1),
@SerializedName("2")
Ready(2),
@SerializedName("3")
Error(3);
private final int value;
public int getValue() {
return value;
}
private UnityPlayerViewState(int value) {
this.value = value;
}
}
package com.biganto.visual.roompark.player.unity_utils.requests_raw;
/**
* Created by Vladislav Bogdashkin on 20.08.2018.
*/
//Unity Player base communication request
public class UPBaseRequest {
}
package com.biganto.visual.roompark.player.unity_utils.requests_raw;
import com.biganto.visual.roompark.player.unity_utils.LoadTourConfig;
/**
* Created by Vladislav Bogdashkin on 20.08.2018.
*/
public class UPBuildTourRequest extends UPBaseRequest{
public int TourId;
public int TargetResolution;
public String AssetsDirectory;
public String MetaPredict;
public String tour_preview;
public Boolean AllowVR=true;
public Boolean AllowDollhouse=true;
public UPBuildTourRequest(){}
public UPBuildTourRequest(
int tour_id,
int target_tex_resolution,
String sd_core_folder_uri,
String meta_file_path,
String tour_preview,
boolean allow_vr,
boolean allow_dollhouse
) {
this.TourId = tour_id;
this.TargetResolution = target_tex_resolution;
this.AssetsDirectory = sd_core_folder_uri;
this.MetaPredict = meta_file_path;
this.tour_preview = tour_preview;
this.AllowVR = allow_vr;
this.AllowDollhouse = allow_dollhouse;
}
public UPBuildTourRequest(LoadTourConfig tour) {
this(tour.getTourId(), tour.getTargetResolution(), tour.getCacheFolderUri(),
tour.getMetaUri(), tour.getPreviewUri(),tour.getAllowVR(),tour.getAllowDollhouse());
}
}
package com.biganto.visual.roompark.player.unity_utils.requests_raw;
import com.biganto.visual.roompark.player.unity_utils.UnityOrientation;
/**
* Created by Vladislav Bogdashkin on 20.08.2018.
*/
public class UPChangeOrientationRequest extends UPBaseRequest {
UnityOrientation orientation;
public UPChangeOrientationRequest(){orientation=UnityOrientation.Portrait;}
public UPChangeOrientationRequest(UnityOrientation orientation){this.orientation=orientation;}
}
package com.biganto.visual.roompark.player.unity_utils.requests_raw;
/**
* Created by Vladislav Bogdashkin on 20.08.2018.
*/
public class UPFinishRequest extends UPBaseRequest{
}
package com.biganto.visual.roompark.player.unity_utils.requests_raw;
/**
* Created by Vladislav Bogdashkin on 20.08.2018.
*/
public class UPPauseRequest extends UPBaseRequest {
}
package com.biganto.visual.roompark.player.unity_utils.requests_raw;
/**
* Created by Vladislav Bogdashkin on 20.08.2018.
*/
public class UPSceneLoadingRequest extends UPBaseRequest{
}
...@@ -12,4 +12,5 @@ import io.reactivex.Observable ...@@ -12,4 +12,5 @@ import io.reactivex.Observable
interface AlbumsScreen : BigantoBaseContract<AlbumsScreenViewState> { interface AlbumsScreen : BigantoBaseContract<AlbumsScreenViewState> {
fun onAlbumSelected(): Observable<AlbumPreviewModel> fun onAlbumSelected(): Observable<AlbumPreviewModel>
fun onPhotoSelected(): Observable<PhotoModel> fun onPhotoSelected(): Observable<PhotoModel>
fun onSubscription(): Observable<Boolean>
} }
\ No newline at end of file
...@@ -20,17 +20,22 @@ import com.biganto.visual.roompark.presentation.screen.albums.util.AlbumListAdap ...@@ -20,17 +20,22 @@ import com.biganto.visual.roompark.presentation.screen.albums.util.AlbumListAdap
import com.biganto.visual.roompark.presentation.screen.albums.util.AlbumsHeaderAdapter import com.biganto.visual.roompark.presentation.screen.albums.util.AlbumsHeaderAdapter
import com.biganto.visual.roompark.presentation.screen.albums.util.BubbleSlider import com.biganto.visual.roompark.presentation.screen.albums.util.BubbleSlider
import com.biganto.visual.roompark.presentation.screen.photo.PhotoScreenController import com.biganto.visual.roompark.presentation.screen.photo.PhotoScreenController
import com.biganto.visual.roompark.util.monades.ExceptionString
import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.widget.checkedChanges
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import jp.wasabeef.glide.transformations.BlurTransformation import jp.wasabeef.glide.transformations.BlurTransformation
import jp.wasabeef.glide.transformations.ColorFilterTransformation import jp.wasabeef.glide.transformations.ColorFilterTransformation
import jp.wasabeef.glide.transformations.CropTransformation
import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
...@@ -67,6 +72,9 @@ class AlbumsScreenController : ...@@ -67,6 +72,9 @@ class AlbumsScreenController :
@BindView(R.id.bubble_slider) @BindView(R.id.bubble_slider)
lateinit var bubble: BubbleSlider lateinit var bubble: BubbleSlider
@BindView(R.id.switch1)
lateinit var albumSubscription: SwitchMaterial
// //
// private val photosBackgroundTarget = object : com.squareup.picasso.Target { // private val photosBackgroundTarget = object : com.squareup.picasso.Target {
...@@ -123,20 +131,26 @@ class AlbumsScreenController : ...@@ -123,20 +131,26 @@ class AlbumsScreenController :
@Inject @Inject
override lateinit var injectedPresenter: AlbumsScreenPresenter override lateinit var injectedPresenter: AlbumsScreenPresenter
// @Inject
// lateinit var snacky:ISnackBarProvider
lateinit var rpActivity: RoomParkMainActivity
fun getComponent() = DaggerAlbumsScreenComponent.factory() fun getComponent() = DaggerAlbumsScreenComponent.factory()
.create(RoomParkApplication.component .create(RoomParkApplication.component
,activity as RoomParkMainActivity ,activity as RoomParkMainActivity
,args.getInt(SELECTED_ALBUM_INDEX_KEY)) ,args.getInt(SELECTED_ALBUM_INDEX_KEY))
.inject(this) .inject(this)
// @Inject
// lateinit var ac: RoomParkMainActivity override fun onSubscription(): Observable<Boolean> =
albumSubscription
.checkedChanges()
.skip(1) //skip init switcher check state
.filter {
if (!silentCheck) return@filter !silentCheck
else silentCheck = false
silentCheck
}
.debounce(600L, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.share()
override fun render(viewState: AlbumsScreenViewState) { override fun render(viewState: AlbumsScreenViewState) {
...@@ -148,6 +162,8 @@ class AlbumsScreenController : ...@@ -148,6 +162,8 @@ class AlbumsScreenController :
is AlbumsScreenViewState.PhotoSelected -> render(viewState) is AlbumsScreenViewState.PhotoSelected -> render(viewState)
is AlbumsScreenViewState.SomeError -> render(viewState) is AlbumsScreenViewState.SomeError -> render(viewState)
is AlbumsScreenViewState.RestoreView -> render(viewState) is AlbumsScreenViewState.RestoreView -> render(viewState)
is AlbumsScreenViewState.SubscriptionStatus -> render(viewState)
is AlbumsScreenViewState.SubscriptionError -> render(viewState)
} }
} }
...@@ -163,6 +179,31 @@ class AlbumsScreenController : ...@@ -163,6 +179,31 @@ class AlbumsScreenController :
) )
} }
private var silentCheck = false
private fun render(viewState: AlbumsScreenViewState.SubscriptionStatus) {
val sw = albumSubscription
Timber.d("")
if (sw.isChecked != viewState.subState)
{
silentCheck = true
sw.isChecked = viewState.subState
}
}
private fun render(viewState: AlbumsScreenViewState.SubscriptionError) {
val sw = albumSubscription
if (sw.isChecked != viewState.subState)
{
silentCheck = true
sw.isChecked = viewState.subState
}
if (viewState.subState)
showError(ExceptionString(R.string.unsubscribe_error_message,null))
else showError(ExceptionString(R.string.subscribe_error_message,null))
}
private fun render(viewState: AlbumsScreenViewState.SomeError) = private fun render(viewState: AlbumsScreenViewState.SomeError) =
showError(viewState.exception) showError(viewState.exception)
...@@ -230,6 +271,13 @@ class AlbumsScreenController : ...@@ -230,6 +271,13 @@ class AlbumsScreenController :
loadGlideBlurred(it.previewUrl, ctx, nestedBgTarget) loadGlideBlurred(it.previewUrl, ctx, nestedBgTarget)
} }
} }
val sw = albumSubscription
if (sw.isChecked != viewState.restore.sub?.state)
{
silentCheck = true
sw.isChecked != sw.isChecked
}
} }
private val nestedBgTarget = object : SimpleTarget<Drawable>(){ private val nestedBgTarget = object : SimpleTarget<Drawable>(){
...@@ -241,8 +289,9 @@ class AlbumsScreenController : ...@@ -241,8 +289,9 @@ class AlbumsScreenController :
private fun loadGlideBlurred(url:String, context: Context, drawable: Target<Drawable>) = private fun loadGlideBlurred(url:String, context: Context, drawable: Target<Drawable>) =
Glide.with(context) Glide.with(context)
.load(url) .load(url)
.transform(BlurTransformation(13, 2)) .transform(CropTransformation(nestedScrollView.width,nestedScrollView.height)
.transform(ColorFilterTransformation(0xCC000000.toInt())) ,BlurTransformation(8, 1)
,ColorFilterTransformation(0xCC000000.toInt()))
.into(drawable) .into(drawable)
override fun onAlbumSelected(): Observable<AlbumPreviewModel> override fun onAlbumSelected(): Observable<AlbumPreviewModel>
...@@ -252,6 +301,7 @@ class AlbumsScreenController : ...@@ -252,6 +301,7 @@ class AlbumsScreenController :
.debounce (300L,TimeUnit.MILLISECONDS) .debounce (300L,TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnNext { (albumsRecyclerView.adapter as AlbumListAdapter).setItems(arrayListOf())} .doOnNext { (albumsRecyclerView.adapter as AlbumListAdapter).setItems(arrayListOf())}
.share()
override fun onPhotoSelected(): Observable<PhotoModel> override fun onPhotoSelected(): Observable<PhotoModel>
......
...@@ -4,10 +4,13 @@ import com.biganto.visual.roompark.conductor.BigantoBasePresenter ...@@ -4,10 +4,13 @@ import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.AlbumsInteractor import com.biganto.visual.roompark.domain.interactor.AlbumsInteractor
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.domain.model.AlbumSortedModel import com.biganto.visual.roompark.domain.model.AlbumSortedModel
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.model.SubscriptionTopic
import com.biganto.visual.roompark.util.monades.ExceptionString import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named import javax.inject.Named
...@@ -21,22 +24,45 @@ class AlbumsScreenPresenter @Inject constructor( ...@@ -21,22 +24,45 @@ class AlbumsScreenPresenter @Inject constructor(
) )
: BigantoBasePresenter<AlbumsScreen, AlbumsScreenViewState>() { : BigantoBasePresenter<AlbumsScreen, AlbumsScreenViewState>() {
private var restoreModel = RestoreModel(listOf(), listOf(),selectedIndex) private var restoreModel = RestoreModel(listOf(), listOf(), selectedIndex,null)
override fun defaultErrorViewStateHandler() = override fun defaultErrorViewStateHandler() =
{e: ExceptionString -> AlbumsScreenViewState.SomeError(e) } { e: ExceptionString -> AlbumsScreenViewState.SomeError(e) }
private fun requestAlbum(id:Int):Observable<AlbumsScreenViewState> = private fun requestAlbum(id: Int): Observable<AlbumsScreenViewState> =
interactor.fetchAlbumPhotos(id) interactor.fetchAlbumPhotos(id)
.filter { !it.isNullOrEmpty() } .filter { !it.isNullOrEmpty() }
.doOnNext{ restoreModel.albumList = it } .doOnNext { restoreModel.albumList = it }
.map { AlbumsScreenViewState.AlbumsSelected(it)} .map { AlbumsScreenViewState.AlbumsSelected(it) }
private fun fetchSubscription(albumId: Int): Observable<AlbumsScreenViewState> =
interactor.getSubscriptions(currentAlbumTopic(albumId))
.doAfterNext { restoreModel.sub = it }
.map<AlbumsScreenViewState> { AlbumsScreenViewState.SubscriptionStatus(it.state) }
// .startWith(Observable.just<AlbumsScreenViewState>(AlbumsScreenViewState.Idle()))
override fun bindIntents() { override fun bindIntents() {
val onSubChecked = intent(AlbumsScreen::onSubscription)
.filter { restoreModel.sub != null }
.flatMap { newState ->
interactor.switchSubscription(restoreModel.sub!!, newState)
.map {subs ->
val s = subs.map { it.subModel }.firstOrNull {it.id == restoreModel.sub?.id}
restoreModel.sub = s
if (s!=null)
return@map AlbumsScreenViewState.SubscriptionStatus(s.state)
else
return@map AlbumsScreenViewState.SubscriptionError(!newState)
}
.onErrorReturn { AlbumsScreenViewState.SubscriptionError(!newState) }
}
val fetchParents = interactor.fetchHeaderAlbums() val fetchParents = interactor.fetchHeaderAlbums()
.filter { !it.isNullOrEmpty() } .filter { !it.isNullOrEmpty() }
.doOnNext{ restoreModel.albumsPreview = it } .doOnNext { restoreModel.albumsPreview = it }
.map { AlbumsScreenViewState.AlbumsListLoaded(it, selectedIndex) } .map { AlbumsScreenViewState.AlbumsListLoaded(it, selectedIndex) }
val fetchSelected = requestAlbum(selectedIndex) val fetchSelected = requestAlbum(selectedIndex)
...@@ -44,19 +70,36 @@ class AlbumsScreenPresenter @Inject constructor( ...@@ -44,19 +70,36 @@ class AlbumsScreenPresenter @Inject constructor(
val headerItemSelected = intent(AlbumsScreen::onAlbumSelected) val headerItemSelected = intent(AlbumsScreen::onAlbumSelected)
.doOnNext { selectedIndex = it.albumId } .doOnNext { selectedIndex = it.albumId }
.doOnNext { restoreModel.currentIndex = it.albumId } .doOnNext { restoreModel.currentIndex = it.albumId }
.flatMap<AlbumsScreenViewState> { model -> .switchMap<AlbumsScreenViewState> { model ->
requestAlbum(model.albumId) requestAlbum(model.albumId)
.startWith(Observable.just(AlbumsScreenViewState.HeaderAlbumChoosed(item = model))) .onErrorReturn(::parseError)
.startWith(Observable.just<AlbumsScreenViewState>(AlbumsScreenViewState.HeaderAlbumChoosed(item = model))
)
} }
val fetchHeaderSubscription = intent(AlbumsScreen::onAlbumSelected)
.map { it.albumId }
.doOnNext { selectedIndex = it ; restoreModel.currentIndex = it }
.switchMap { fetchSubscription(it) }
val photoSelected = intent(AlbumsScreen::onPhotoSelected) val photoSelected = intent(AlbumsScreen::onPhotoSelected)
.map { AlbumsScreenViewState.PhotoSelected(it.photoId) } .map { AlbumsScreenViewState.PhotoSelected(it.photoId) }
val state = restoreStateObservable
.mergeWith(fetchParents) val state = Observable.mergeDelayError(
.mergeWith(fetchSelected) arrayListOf(
.mergeWith(headerItemSelected) fetchParents,
.mergeWith(photoSelected) fetchSelected,
headerItemSelected,
photoSelected,
fetchSubscription(selectedIndex),
fetchHeaderSubscription,
onSubChecked
)
)
.doOnError { Timber.e(it) }
.onErrorReturn(::parseError)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(AlbumsScreenViewState::class.java), AlbumsScreen::render) subscribeViewState(state.cast(AlbumsScreenViewState::class.java), AlbumsScreen::render)
...@@ -66,8 +109,20 @@ class AlbumsScreenPresenter @Inject constructor( ...@@ -66,8 +109,20 @@ class AlbumsScreenPresenter @Inject constructor(
super.detachView() super.detachView()
restoreStateObservable.accept(AlbumsScreenViewState.RestoreView(restoreModel)) restoreStateObservable.accept(AlbumsScreenViewState.RestoreView(restoreModel))
} }
private fun currentAlbumTopic(albumId:Int):SubscriptionTopic {
val z = when(albumId){
9 -> SubscriptionTopic.Progress_2()
10 -> SubscriptionTopic.Progress_1()
11 -> SubscriptionTopic.Progress_3()
106 -> SubscriptionTopic.Progress_kindergarden()
else -> error("unknown parent album type!")
}
return z
}
} }
data class RestoreModel(var albumList:List<AlbumSortedModel> data class RestoreModel(var albumList:List<AlbumSortedModel>
,var albumsPreview:List<AlbumPreviewModel> ,var albumsPreview:List<AlbumPreviewModel>
, var currentIndex:Int) , var currentIndex:Int
\ No newline at end of file , var sub: SubscriptionModel?)
\ No newline at end of file
...@@ -18,4 +18,6 @@ sealed class AlbumsScreenViewState : BigantoBaseViewState() { ...@@ -18,4 +18,6 @@ sealed class AlbumsScreenViewState : BigantoBaseViewState() {
class PhotoSelected(val photoId:Int) : AlbumsScreenViewState() class PhotoSelected(val photoId:Int) : AlbumsScreenViewState()
class SomeError(val exception: ExceptionString) : AlbumsScreenViewState() class SomeError(val exception: ExceptionString) : AlbumsScreenViewState()
class RestoreView(val restore:RestoreModel) : AlbumsScreenViewState() class RestoreView(val restore:RestoreModel) : AlbumsScreenViewState()
class SubscriptionStatus(val subState: Boolean) : AlbumsScreenViewState()
class SubscriptionError(val subState: Boolean) : AlbumsScreenViewState()
} }
\ No newline at end of file
...@@ -10,6 +10,8 @@ import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyc ...@@ -10,6 +10,8 @@ import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyc
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import java.text.SimpleDateFormat
import java.util.*
/** /**
...@@ -31,12 +33,17 @@ class AlbumsHeaderAdapter : CommonRecyclerAdapter<AlbumsHeaderViewHolder, AlbumP ...@@ -31,12 +33,17 @@ class AlbumsHeaderAdapter : CommonRecyclerAdapter<AlbumsHeaderViewHolder, AlbumP
class AlbumsHeaderViewHolder(itemView: View) : CommonViewHolder<AlbumPreviewModel>(itemView) { class AlbumsHeaderViewHolder(itemView: View) : CommonViewHolder<AlbumPreviewModel>(itemView) {
private val dateFormatter = SimpleDateFormat("dd MMMM yyyy", Locale("ru"))
@BindView(R.id.preview) lateinit var preview:ImageView @BindView(R.id.preview) lateinit var preview:ImageView
@BindView(R.id.card_title) lateinit var articleTitle:TextView @BindView(R.id.card_title) lateinit var articleTitle:TextView
@BindView(R.id.card_updated) lateinit var articleDate:TextView @BindView(R.id.card_updated) lateinit var articleDate:TextView
override fun onViewBound(model: AlbumPreviewModel) { override fun onViewBound(model: AlbumPreviewModel) {
articleTitle.text = model.title articleTitle.text = model.title
articleDate.text = dateFormatter.format(model.published)
Glide.with(itemView) Glide.with(itemView)
.load(model.previewUrl) .load(model.previewUrl)
.centerCrop() .centerCrop()
......
...@@ -118,6 +118,7 @@ class ArticleScreenController : ...@@ -118,6 +118,7 @@ class ArticleScreenController :
when(viewState){ when(viewState){
is ArticleScreenViewState.Idle -> render(viewState) is ArticleScreenViewState.Idle -> render(viewState)
is ArticleScreenViewState.ArticleLoaded -> render(viewState) is ArticleScreenViewState.ArticleLoaded -> render(viewState)
is ArticleScreenViewState.SomeError -> render(viewState)
} }
} }
...@@ -125,6 +126,10 @@ class ArticleScreenController : ...@@ -125,6 +126,10 @@ class ArticleScreenController :
} }
private fun render(viewState: ArticleScreenViewState.SomeError) {
showError(viewState.error)
}
private fun render(viewState: ArticleScreenViewState.ArticleLoaded) { private fun render(viewState: ArticleScreenViewState.ArticleLoaded) {
title.text = viewState.item.title title.text = viewState.item.title
......
...@@ -20,6 +20,7 @@ class ArticleScreenPresenter @Inject constructor( ...@@ -20,6 +20,7 @@ class ArticleScreenPresenter @Inject constructor(
@Named(ARTICLE_SCREEN_ARTICLEID) private val selectedArticleId:Int @Named(ARTICLE_SCREEN_ARTICLEID) private val selectedArticleId:Int
) )
: BigantoBasePresenter<ArticleScreen, ArticleScreenViewState>() { : BigantoBasePresenter<ArticleScreen, ArticleScreenViewState>() {
override fun defaultErrorViewStateHandler() = override fun defaultErrorViewStateHandler() =
{e:ExceptionString -> ArticleScreenViewState.SomeError(e)} {e:ExceptionString -> ArticleScreenViewState.SomeError(e)}
...@@ -37,6 +38,7 @@ class ArticleScreenPresenter @Inject constructor( ...@@ -37,6 +38,7 @@ class ArticleScreenPresenter @Inject constructor(
.mergeWith(prefetchCards) .mergeWith(prefetchCards)
.mergeWith(setRead) .mergeWith(setRead)
.doOnError{ Timber.e(it)} .doOnError{ Timber.e(it)}
.onErrorReturn(::parseError)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
......
...@@ -2,11 +2,13 @@ package com.biganto.visual.roompark.presentation.screen.auth ...@@ -2,11 +2,13 @@ package com.biganto.visual.roompark.presentation.screen.auth
import android.view.View import android.view.View
import butterknife.BindView import butterknife.BindView
import butterknife.OnClick
import com.biganto.visual.roompark.R import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.presentation.screen.home.home_routing.HomeBottomNavigationController import com.biganto.visual.roompark.presentation.screen.home.home_routing.HomeBottomNavigationController
import com.biganto.visual.roompark.util.extensions.startUrl
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
...@@ -22,6 +24,8 @@ import javax.inject.Inject ...@@ -22,6 +24,8 @@ import javax.inject.Inject
* Created by Vladislav Bogdashkin on 30.09.2019. * Created by Vladislav Bogdashkin on 30.09.2019.
*/ */
private const val RESTORE_PASS_URL = "https://room-park.ru/users/remind/"
class AuthScreenController : class AuthScreenController :
BigantoBaseController<AuthScreenViewState BigantoBaseController<AuthScreenViewState
, AuthScreen , AuthScreen
...@@ -37,6 +41,12 @@ class AuthScreenController : ...@@ -37,6 +41,12 @@ class AuthScreenController :
@BindView(R.id.login_text_input) lateinit var loginInput:TextInputLayout @BindView(R.id.login_text_input) lateinit var loginInput:TextInputLayout
@BindView(R.id.password_text_input) lateinit var pwdInput:TextInputLayout @BindView(R.id.password_text_input) lateinit var pwdInput:TextInputLayout
@BindView(R.id.sign_in_button) lateinit var signInButton:MaterialButton @BindView(R.id.sign_in_button) lateinit var signInButton:MaterialButton
@BindView(R.id.restore_pass_button) lateinit var restoreButton:MaterialButton
@OnClick(R.id.restore_pass_button)
fun onRestorePass(){
activity?.startUrl(RESTORE_PASS_URL)
}
override fun tryAuth(): Observable<AuthInputModel> = override fun tryAuth(): Observable<AuthInputModel> =
signInButton.clicks() signInButton.clicks()
......
package com.biganto.visual.roompark.presentation.screen.deal package com.biganto.visual.roompark.presentation.screen.deal
import com.biganto.visual.roompark.conductor.BigantoBaseContract import com.biganto.visual.roompark.conductor.BigantoBaseContract
import io.reactivex.Observable
/** /**
* Created by Vladislav Bogdashkin on 30.09.2019. * Created by Vladislav Bogdashkin on 30.09.2019.
*/ */
interface DealScreen : BigantoBaseContract<DealScreenViewState> { interface DealScreen : BigantoBaseContract<DealScreenViewState> {
fun onSubscription(): Observable<Boolean>
fun tourCardClicked(): Observable<Int>
} }
...@@ -12,11 +12,15 @@ import com.biganto.visual.roompark.base.HeaderToolbarModel ...@@ -12,11 +12,15 @@ import com.biganto.visual.roompark.base.HeaderToolbarModel
import com.biganto.visual.roompark.base.RoomParkApplication import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.conductor.dialogs.tour_chooser.ChooseTourDialogController
import com.biganto.visual.roompark.domain.model.DealModel import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.typeDoubleString import com.biganto.visual.roompark.domain.model.typeDoubleString
import com.biganto.visual.roompark.domain.model.typeShortString import com.biganto.visual.roompark.domain.model.typeShortString
import com.biganto.visual.roompark.presentation.screen.estate.EstateScreenController import com.biganto.visual.roompark.presentation.screen.estate.EstateScreenController
import com.biganto.visual.roompark.util.extensions.setGone
import com.biganto.visual.roompark.util.extensions.toRubly import com.biganto.visual.roompark.util.extensions.toRubly
import com.biganto.visual.roompark.util.monades.ExceptionString
import com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView import com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
import com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressAnimationState import com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressAnimationState
import com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil import com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
...@@ -24,11 +28,15 @@ import com.bluelinelabs.conductor.RouterTransaction ...@@ -24,11 +28,15 @@ import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.view.clicks import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxbinding3.view.scrollChangeEvents import com.jakewharton.rxbinding3.view.scrollChangeEvents
import com.jakewharton.rxbinding3.widget.checkedChanges
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
...@@ -91,14 +99,21 @@ class DealScreenController : ...@@ -91,14 +99,21 @@ class DealScreenController :
@BindView(R.id.start_tour_image_view) lateinit var tourScreen: RoundedImageView @BindView(R.id.start_tour_image_view) lateinit var tourScreen: RoundedImageView
private var servedDeal : DealModel? = null
override fun tourCardClicked(): Observable<Int> =
tourScreen.clicks()
.map { 1 }
.debounce(320L, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
override fun onAttach(view: View) { override fun onAttach(view: View) {
super.onAttach(view) super.onAttach(view)
detachDisposable.addAll( detachDisposable.addAll(
toFlatView.clicks() toFlatView.clicks()
.map { servedDeal?.estate?.id?: -1000} .map { dealModel?.estate?.id?: -1000}
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
Timber.d("got card clicked $it") Timber.d("got card clicked $it")
...@@ -140,6 +155,19 @@ class DealScreenController : ...@@ -140,6 +155,19 @@ class DealScreenController :
) )
} }
override fun onSubscription(): Observable<Boolean> =
toolBar.headerToolbar.findViewById<SwitchMaterial>(R.id.switch1)
.checkedChanges()
.skip(1) //skip init switcher check state
.filter {
if (!silentCheck) return@filter !silentCheck
else silentCheck = false
silentCheck
}
.debounce(400L, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
private fun setToolbar(){ private fun setToolbar(){
toolBar.setToolbar(HeaderToolbarModel( toolBar.setToolbar(HeaderToolbarModel(
true true
...@@ -167,6 +195,10 @@ class DealScreenController : ...@@ -167,6 +195,10 @@ class DealScreenController :
is DealScreenViewState.Idle -> render(viewState) is DealScreenViewState.Idle -> render(viewState)
is DealScreenViewState.LoadDeal -> render(viewState) is DealScreenViewState.LoadDeal -> render(viewState)
is DealScreenViewState.SomeError -> render(viewState) is DealScreenViewState.SomeError -> render(viewState)
is DealScreenViewState.RestoreView -> render(viewState)
is DealScreenViewState.SubscriptionStatus -> render(viewState)
is DealScreenViewState.SubscriptionError -> render(viewState)
is DealScreenViewState.ToursLoaded -> render(viewState)
} }
} }
...@@ -174,6 +206,54 @@ class DealScreenController : ...@@ -174,6 +206,54 @@ class DealScreenController :
} }
private var silentCheck = false
private fun render(viewState: DealScreenViewState.SubscriptionStatus) {
val sw = toolBar.headerToolbar.findViewById<SwitchMaterial>(R.id.switch1)
if (sw.isChecked != viewState.subState)
{
silentCheck = true
sw.isChecked = viewState.subState
}
toolBar.headerToolbar.invalidate()
}
private fun render(viewState: DealScreenViewState.SubscriptionError) {
val sw = toolBar.headerToolbar.findViewById<SwitchMaterial>(R.id.switch1)
if (sw.isChecked != viewState.subState)
{
silentCheck = true
sw.isChecked = viewState.subState
}
if (viewState.subState)
showError(ExceptionString(R.string.unsubscribe_error_message,null))
else showError(ExceptionString(R.string.subscribe_error_message,null))
}
private fun render(viewState:DealScreenViewState.RestoreView) {
val sw = toolBar.headerToolbar.findViewById<SwitchMaterial>(R.id.switch1)
if (sw.isChecked != viewState.restore.sub?.state)
{
silentCheck = true
sw.isChecked != sw.isChecked
}
viewState.restore.deal?.let { setUpDeal(it) }
}
private fun render(viewState: DealScreenViewState.ToursLoaded) {
router.pushController(RouterTransaction.with(
ChooseTourDialogController(ArrayList(viewState.tours))
)
.popChangeHandler(DialogChangeHandler())
.pushChangeHandler(DialogChangeHandler())
)
}
private fun render(viewState: DealScreenViewState.SomeError) = private fun render(viewState: DealScreenViewState.SomeError) =
showError(viewState.exception) showError(viewState.exception)
...@@ -185,49 +265,56 @@ class DealScreenController : ...@@ -185,49 +265,56 @@ class DealScreenController :
private var dealModel:DealModel? = null private var dealModel:DealModel? = null
private fun render(viewState: DealScreenViewState.LoadDeal) { private fun setUpDeal(deal:DealModel){
servedDeal = viewState.estate dealModel = deal
dealTitle.text = resources?.getString(
viewState.estate.estate.type.typeDoubleString(), dealModel?.let {
viewState.estate.estate.number
) startTourView.setGone(it.estate.multitourId == null)
dealTitle.text = resources?.getString(
it.estate.type.typeDoubleString(),
it.estate.number
)
info1.title.text = resources?.getString(R.string.building)
info1.text.text = it.estate.commonInfo?.building.toString()
info1.title.text = resources?.getString(R.string.building) info2.title.text = resources?.getString(R.string.section_begin)
info1.text.text = viewState.estate.estate.commonInfo?.building.toString() info2.text.text = it.estate.commonInfo?.section_begin.toString()
info2.title.text = resources?.getString(R.string.section_begin) info3.title.text = resources?.getString(R.string.floor)
info2.text.text = viewState.estate.estate.commonInfo?.section_begin.toString() info3.text.text = it.estate.commonInfo?.floor.toString()
info3.title.text = resources?.getString(R.string.floor) info4.title.text = resources?.getString(R.string.area)
info3.text.text = viewState.estate.estate.commonInfo?.floor.toString() info4.text.text =
resources?.getString(R.string.area_value,it.estate.commonInfo?.area)
info4.title.text = resources?.getString(R.string.area)
info4.text.text =
resources?.getString(R.string.area_value,viewState.estate.estate.commonInfo?.area)
dealSum.text = it.opportunitySum.toRubly()
dealPayed.text = it.paymentSum.toRubly()
dealSumToPay.text = it.amount_pay_sum.toRubly()
dealSum.text = viewState.estate.opportunitySum.toRubly() it.estate.multitourPreview?.let {url ->
dealPayed.text = viewState.estate.paymentSum.toRubly() Glide.with(tourScreen)
dealSumToPay.text = viewState.estate.amount_pay_sum.toRubly() .load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(tourScreen)
}
viewState.estate.estate.multitourPreview?.let { toolBar.setToolbar(HeaderToolbarModel(
Glide.with(tourScreen) true
.load(it) ,resources?.getString(R.string.deal_back_chevron_title)
.diskCacheStrategy(DiskCacheStrategy.ALL) ,null
.into(tourScreen) ,true)
)
} }
}
dealModel = viewState.estate private fun render(viewState: DealScreenViewState.LoadDeal) {
toolBar.setToolbar(HeaderToolbarModel( setUpDeal(viewState.estate)
true
,resources?.getString(R.string.deal_back_chevron_title)
,null
,true)
)
progressLayout.removeAllViews() progressLayout.removeAllViews()
viewState.statusList.forEach { viewState.statusList.forEach {
val statusLayout = LayoutInflater.from(activity) val statusLayout = LayoutInflater.from(activity)
...@@ -236,9 +323,7 @@ class DealScreenController : ...@@ -236,9 +323,7 @@ class DealScreenController :
,false) ,false)
as LinearLayout as LinearLayout
Timber.d("layouted: $statusLayout")
val statusCeil = statusLayout.findViewById<StatusProgressCeil>(R.id.status) val statusCeil = statusLayout.findViewById<StatusProgressCeil>(R.id.status)
Timber.d("layouted ceail : $statusCeil")
val position = it.orderId val position = it.orderId
val statusCount = viewState.statusList.size val statusCount = viewState.statusList.size
...@@ -253,11 +338,11 @@ class DealScreenController : ...@@ -253,11 +338,11 @@ class DealScreenController :
statusCeil.invalidate() statusCeil.invalidate()
val statusTitle = statusLayout.findViewById<MaterialTextView>(R.id.title) val statusTitle = statusLayout.findViewById<MaterialTextView>(R.id.title)
Timber.d("layouted statusTitle : $statusTitle")
statusTitle.text = it.shortTitle statusTitle.text = it.shortTitle
progressLayout.addView(statusLayout) progressLayout.addView(statusLayout)
} }
progressLayout.invalidate() progressLayout.invalidate()
} }
private fun getComponent() = DaggerDealScreenComponent.factory() private fun getComponent() = DaggerDealScreenComponent.factory()
......
package com.biganto.visual.roompark.presentation.screen.deal package com.biganto.visual.roompark.presentation.screen.deal
import android.content.Context import android.content.Context
import androidx.annotation.StringRes
import com.biganto.visual.roompark.conductor.BigantoBasePresenter import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.DealInteractor import com.biganto.visual.roompark.domain.interactor.DealInteractor
import com.biganto.visual.roompark.domain.interactor.ToursInteractor
import com.biganto.visual.roompark.domain.model.DealModel import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.PlanPresetModel import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.presentation.screen.estate.InfoShowType
import com.biganto.visual.roompark.util.monades.ExceptionString import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
...@@ -22,46 +21,94 @@ import javax.inject.Named ...@@ -22,46 +21,94 @@ import javax.inject.Named
class DealScreenPresenter @Inject constructor( class DealScreenPresenter @Inject constructor(
private val interactor: DealInteractor, private val interactor: DealInteractor,
private val toursInteractor: ToursInteractor,
private val context: Context, private val context: Context,
@Named(SELECTED_DEAL_ID_KEY) private val dealId:String @Named(SELECTED_DEAL_ID_KEY) private val dealId:String
) )
: BigantoBasePresenter<DealScreen, DealScreenViewState>() { : BigantoBasePresenter<DealScreen, DealScreenViewState>() {
private var planList: List<PlanPresetModel>? = null
private var estate: DealModel? = null
private var showType: InfoShowType = InfoShowType.COMMON_INFO
override fun defaultErrorViewStateHandler() = override fun defaultErrorViewStateHandler() =
{ e: ExceptionString -> DealScreenViewState.SomeError(e) } { e: ExceptionString -> DealScreenViewState.SomeError(e) }
private var restoreModel = RestoreModel(null,null)
override fun detachView() {
super.detachView()
restoreStateObservable.accept(DealScreenViewState.RestoreView(restoreModel))
}
private val fetchDeal = interactor.getDeal(dealId)
.doOnNext { restoreModel.deal = it }
.map<DealScreenViewState>{ deal ->
DealScreenViewState.LoadDeal(deal ,interactor.getStatusListSync())
}
override fun bindIntents() { override fun bindIntents() {
val fetchDeal = interactor.getDeal(dealId)
.map<DealScreenViewState>{ deal ->
DealScreenViewState.LoadDeal(deal ,interactor.getStatusListSync())
}
val setRead = interactor.setDealRead(dealId) val setRead = interactor.setDealRead(dealId)
.andThen(Observable.just(DealScreenViewState.Idle())) .andThen(Observable.just(DealScreenViewState.Idle()))
val state = restoreStateObservable
.mergeWith(fetchDeal)
.mergeWith(setRead) val onSubChecked = intent(DealScreen::onSubscription)
.filter { restoreModel.sub != null }
.flatMap { newState ->
interactor.switchSubscription(restoreModel.sub!!, newState)
.map {subs ->
val s = subs.map { it.subModel }.firstOrNull {it.id == restoreModel.sub?.id}
restoreModel.sub = s
if (s!=null)
return@map DealScreenViewState.SubscriptionStatus(s.state)
else
return@map DealScreenViewState.SubscriptionError(!newState)
}
.onErrorReturn { DealScreenViewState.SubscriptionError(!newState) }
}
val fetchSubscription =
fetchDeal.flatMap { fetchedDealViewState ->
interactor.getSubscriptions(
(fetchedDealViewState as DealScreenViewState.LoadDeal).estate.estate_id
)
.doOnNext { restoreModel.sub = it }
.map<DealScreenViewState> { DealScreenViewState.SubscriptionStatus(it.state) }
.startWith(Observable.just<DealScreenViewState>(DealScreenViewState.Idle()))
}
.onErrorReturn (::parseError)
val onStartTours = intent(DealScreen::tourCardClicked)
.map { restoreModel.deal }
.map{ it.estate }
.flatMap {estate -> toursInteractor.getEstateTourList(estate)
.map<DealScreenViewState> { DealScreenViewState.ToursLoaded(it) }
.onErrorReturn (::parseError)
}
val state = Observable.mergeDelayError(
arrayListOf(
restoreStateObservable,
fetchDeal,
setRead,
onSubChecked,
fetchSubscription,
onStartTours
))
.doOnError { Timber.e(it) } .doOnError { Timber.e(it) }
.onErrorReturn(::parseError)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(DealScreenViewState::class.java), DealScreen::render) subscribeViewState(state.cast(DealScreenViewState::class.java), DealScreen::render)
} }
}
private fun langString(@StringRes id:Int) = context.resources.getString(id)
private fun langString(@StringRes id:Int,vararg args:Any) =
context.resources.getString(id,*args)
}
data class RestoreModel(var sub: SubscriptionModel?,var deal: DealModel?)
\ No newline at end of file
...@@ -3,6 +3,7 @@ package com.biganto.visual.roompark.presentation.screen.deal ...@@ -3,6 +3,7 @@ package com.biganto.visual.roompark.presentation.screen.deal
import com.biganto.visual.roompark.conductor.BigantoBaseViewState import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.DealModel import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.StatusModel import com.biganto.visual.roompark.domain.model.StatusModel
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.util.monades.ExceptionString import com.biganto.visual.roompark.util.monades.ExceptionString
/** /**
...@@ -14,4 +15,8 @@ sealed class DealScreenViewState : BigantoBaseViewState() { ...@@ -14,4 +15,8 @@ sealed class DealScreenViewState : BigantoBaseViewState() {
class Idle : DealScreenViewState() class Idle : DealScreenViewState()
class LoadDeal(val estate:DealModel,val statusList:List<StatusModel>) : DealScreenViewState() class LoadDeal(val estate:DealModel,val statusList:List<StatusModel>) : DealScreenViewState()
class SomeError(val exception: ExceptionString) : DealScreenViewState() class SomeError(val exception: ExceptionString) : DealScreenViewState()
class RestoreView(val restore:RestoreModel) : DealScreenViewState()
class SubscriptionStatus(val subState: Boolean) : DealScreenViewState()
class SubscriptionError(val subState: Boolean) : DealScreenViewState()
class ToursLoaded(val tours:List<TourModel>) : DealScreenViewState()
} }
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.deals package com.biganto.visual.roompark.presentation.screen.deals
import com.biganto.visual.roompark.conductor.BigantoBaseContract import com.biganto.visual.roompark.conductor.BigantoBaseContract
import com.biganto.visual.roompark.domain.model.DealPreviewModel
import io.reactivex.Observable
/** /**
* Created by Vladislav Bogdashkin on 30.09.2019. * Created by Vladislav Bogdashkin on 30.09.2019.
*/ */
interface DealsScreen : BigantoBaseContract<DealsScreenViewState> { interface DealsScreen : BigantoBaseContract<DealsScreenViewState> {
fun tourCardClicked(): Observable<DealPreviewModel>
} }
...@@ -9,12 +9,16 @@ import com.biganto.visual.roompark.base.HeaderToolbarModel ...@@ -9,12 +9,16 @@ import com.biganto.visual.roompark.base.HeaderToolbarModel
import com.biganto.visual.roompark.base.RoomParkApplication import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.conductor.dialogs.tour_chooser.ChooseTourDialogController
import com.biganto.visual.roompark.domain.model.DealPreviewModel
import com.biganto.visual.roompark.presentation.screen.deal.DealScreenController import com.biganto.visual.roompark.presentation.screen.deal.DealScreenController
import com.biganto.visual.roompark.presentation.screen.deals.util.DealsListAdapter import com.biganto.visual.roompark.presentation.screen.deals.util.DealsListAdapter
import com.biganto.visual.roompark.presentation.screen.estate.EstateScreenController import com.biganto.visual.roompark.presentation.screen.estate.EstateScreenController
import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
...@@ -90,14 +94,7 @@ class DealsScreenController : ...@@ -90,14 +94,7 @@ class DealsScreenController :
.popChangeHandler(FadeChangeHandler()) .popChangeHandler(FadeChangeHandler())
.pushChangeHandler(FadeChangeHandler()) .pushChangeHandler(FadeChangeHandler())
) )
}, }
(dealsRecyclerView.adapter as DealsListAdapter)
.startTour
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
Timber.d("got tour clicked $it")
showMessage(R.string.tour_not_allowed)
}
) )
} }
...@@ -107,6 +104,12 @@ class DealsScreenController : ...@@ -107,6 +104,12 @@ class DealsScreenController :
bindRecycler() bindRecycler()
} }
override fun tourCardClicked(): Observable<DealPreviewModel> =
(dealsRecyclerView.adapter as DealsListAdapter)
.startTour
.map { it }
.observeOn(AndroidSchedulers.mainThread())
override fun render(viewState: DealsScreenViewState) { override fun render(viewState: DealsScreenViewState) {
super.render(viewState) super.render(viewState)
Timber.d("Render state $viewState") Timber.d("Render state $viewState")
...@@ -114,6 +117,8 @@ class DealsScreenController : ...@@ -114,6 +117,8 @@ class DealsScreenController :
is DealsScreenViewState.Idle -> render(viewState) is DealsScreenViewState.Idle -> render(viewState)
is DealsScreenViewState.DealsLoaded -> render(viewState) is DealsScreenViewState.DealsLoaded -> render(viewState)
is DealsScreenViewState.SomeError -> render(viewState) is DealsScreenViewState.SomeError -> render(viewState)
is DealsScreenViewState.ToursLoaded -> render(viewState)
is DealsScreenViewState.RestoreView -> render(viewState)
} }
} }
...@@ -121,10 +126,25 @@ class DealsScreenController : ...@@ -121,10 +126,25 @@ class DealsScreenController :
} }
private fun render(viewState: DealsScreenViewState.RestoreView){
(dealsRecyclerView.adapter as DealsListAdapter).addItems(viewState.restore.list)
}
private fun render(viewState: DealsScreenViewState.DealsLoaded){ private fun render(viewState: DealsScreenViewState.DealsLoaded){
(dealsRecyclerView.adapter as DealsListAdapter).addItems(viewState.items) (dealsRecyclerView.adapter as DealsListAdapter).addItems(viewState.items)
} }
private fun render(viewState: DealsScreenViewState.ToursLoaded) {
router.pushController(RouterTransaction.with(
ChooseTourDialogController(ArrayList(viewState.tours))
)
.popChangeHandler(DialogChangeHandler())
.pushChangeHandler(DialogChangeHandler())
)
}
private fun render(viewState: DealsScreenViewState.SomeError) = private fun render(viewState: DealsScreenViewState.SomeError) =
showError(viewState.exception) showError(viewState.exception)
......
...@@ -2,8 +2,10 @@ package com.biganto.visual.roompark.presentation.screen.deals ...@@ -2,8 +2,10 @@ package com.biganto.visual.roompark.presentation.screen.deals
import com.biganto.visual.roompark.conductor.BigantoBasePresenter import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.DealsInteractor import com.biganto.visual.roompark.domain.interactor.DealsInteractor
import com.biganto.visual.roompark.domain.interactor.ToursInteractor
import com.biganto.visual.roompark.domain.model.DealPreviewModel import com.biganto.visual.roompark.domain.model.DealPreviewModel
import com.biganto.visual.roompark.util.monades.ExceptionString import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
...@@ -15,12 +17,21 @@ import javax.inject.Inject ...@@ -15,12 +17,21 @@ import javax.inject.Inject
class DealsScreenPresenter @Inject constructor( class DealsScreenPresenter @Inject constructor(
private val interactor: DealsInteractor private val interactor: DealsInteractor,
private val toursInteractor: ToursInteractor
) )
: BigantoBasePresenter<DealsScreen, DealsScreenViewState>() { : BigantoBasePresenter<DealsScreen, DealsScreenViewState>() {
override fun defaultErrorViewStateHandler() = override fun defaultErrorViewStateHandler() =
{e:ExceptionString -> DealsScreenViewState.SomeError(e)} { e: ExceptionString -> DealsScreenViewState.SomeError(e) }
private val restoreModel = RestoreModel(mutableListOf())
override fun detachView() {
super.detachView()
restoreStateObservable.accept(DealsScreenViewState.RestoreView(restoreModel))
}
override fun bindIntents() { override fun bindIntents() {
...@@ -29,22 +40,60 @@ class DealsScreenPresenter @Inject constructor( ...@@ -29,22 +40,60 @@ class DealsScreenPresenter @Inject constructor(
val fetchDeals = interactor.fetchDeals() val fetchDeals = interactor.fetchDeals()
.flatMap { deals -> .flatMap { deals ->
getStatusList getStatusList
.map { List(deals.size) { index -> .map{
DealPreviewModel( List(deals.size) { index ->
Pair(deals[index], it) DealPreviewModel(
) Pair(deals[index], it)
} } )
}
}
}
.doOnNext { restoreModel.list = it.toMutableList() }
.map<DealsScreenViewState>(DealsScreenViewState::DealsLoaded)
.onErrorReturn(::parseError)
val getDeals = interactor.getDealsApi()
.flatMap { deals ->
getStatusList
.map{
List(deals.size) { index ->
DealPreviewModel(
Pair(deals[index], it)
)
}
}
}
.doOnNext { restoreModel.list = it.toMutableList() }
.map<DealsScreenViewState>(DealsScreenViewState::DealsLoaded)
.onErrorReturn(::parseError)
val onStartTours = intent(DealsScreen::tourCardClicked)
.flatMap {dealPreview ->
toursInteractor.getEstateTourList(dealPreview.tourId,dealPreview.estateId)
.map { DealsScreenViewState.ToursLoaded(it) }
} }
.map(DealsScreenViewState::DealsLoaded)
val state = restoreStateObservable val state = Observable.mergeDelayError(
.mergeWith(fetchDeals) arrayListOf(
.doOnError{ Timber.e(it)} restoreStateObservable,
fetchDeals,
getDeals,
onStartTours
)
)
.doOnError { Timber.e(it) }
.onErrorReturn(::parseError) .onErrorReturn(::parseError)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(DealsScreenViewState::class.java), DealsScreen::render) subscribeViewState(state.cast(DealsScreenViewState::class.java), DealsScreen::render)
} }
}
} data class RestoreModel(
\ No newline at end of file var list:MutableList<DealPreviewModel>
)
\ No newline at end of file
...@@ -2,6 +2,7 @@ package com.biganto.visual.roompark.presentation.screen.deals ...@@ -2,6 +2,7 @@ package com.biganto.visual.roompark.presentation.screen.deals
import com.biganto.visual.roompark.conductor.BigantoBaseViewState import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.DealPreviewModel import com.biganto.visual.roompark.domain.model.DealPreviewModel
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.util.monades.ExceptionString import com.biganto.visual.roompark.util.monades.ExceptionString
/** /**
...@@ -13,5 +14,7 @@ sealed class DealsScreenViewState : BigantoBaseViewState() { ...@@ -13,5 +14,7 @@ sealed class DealsScreenViewState : BigantoBaseViewState() {
class Idle : DealsScreenViewState() class Idle : DealsScreenViewState()
class DealsLoaded(val items:List<DealPreviewModel>) : DealsScreenViewState() class DealsLoaded(val items:List<DealPreviewModel>) : DealsScreenViewState()
class SomeError(val exception: ExceptionString) : DealsScreenViewState() class SomeError(val exception: ExceptionString) : DealsScreenViewState()
class ToursLoaded(val tours:List<TourModel>) : DealsScreenViewState()
class RestoreView(val restore:RestoreModel) : DealsScreenViewState()
} }
...@@ -34,7 +34,7 @@ class DealsListAdapter : CommonRecyclerAdapter<DealViewHolder, DealPreviewModel> ...@@ -34,7 +34,7 @@ class DealsListAdapter : CommonRecyclerAdapter<DealViewHolder, DealPreviewModel>
override fun getVhLayout() = R.layout.deal_card_viewholder override fun getVhLayout() = R.layout.deal_card_viewholder
private val onFlatClicked = PublishSubject.create<Int>() private val onFlatClicked = PublishSubject.create<Int>()
private val onTourClickced = PublishSubject.create<Int>() private val onTourClickced = PublishSubject.create<DealPreviewModel>()
override fun onBindViewHolder(holder: DealViewHolder, position: Int) { override fun onBindViewHolder(holder: DealViewHolder, position: Int) {
super.onBindViewHolder(holder, position) super.onBindViewHolder(holder, position)
...@@ -73,7 +73,7 @@ class DealViewHolder(itemView: View) : CommonViewHolder<DealPreviewModel>(itemVi ...@@ -73,7 +73,7 @@ class DealViewHolder(itemView: View) : CommonViewHolder<DealPreviewModel>(itemVi
@BindView(R.id.deal_read) lateinit var dealReadFlag:View @BindView(R.id.deal_read) lateinit var dealReadFlag:View
val onStartFlatObs: Observable<Int?> get() = startFlat.clicks().map { bindedModel.estateId } val onStartFlatObs: Observable<Int?> get() = startFlat.clicks().map { bindedModel.estateId }
val onStartTourObs: Observable<Int?> get() = startTour.clicks().map { bindedModel.tourId } val onStartTourObs: Observable<DealPreviewModel> get() = startTour.clicks().map { bindedModel }
override fun onViewBound(model: DealPreviewModel) { override fun onViewBound(model: DealPreviewModel) {
......
...@@ -15,5 +15,6 @@ interface EstateScreen : BigantoBaseContract<EstateScreenViewState> { ...@@ -15,5 +15,6 @@ interface EstateScreen : BigantoBaseContract<EstateScreenViewState> {
fun switchElectric(): Observable<Pair<Int,Boolean>> fun switchElectric(): Observable<Pair<Int,Boolean>>
fun showCommonInfo(): Observable<Int> fun showCommonInfo(): Observable<Int>
fun showExplication(): Observable<Int> fun showExplication(): Observable<Int>
fun tourCardClicked(): Observable<Int>
} }
package com.biganto.visual.roompark.presentation.screen.estate package com.biganto.visual.roompark.presentation.screen.estate
import android.annotation.SuppressLint
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
...@@ -18,11 +19,14 @@ import com.biganto.visual.roompark.base.RoomParkMainActivity ...@@ -18,11 +19,14 @@ import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.base.StatusState import com.biganto.visual.roompark.base.StatusState
import com.biganto.visual.roompark.base.StatusToolbarModel import com.biganto.visual.roompark.base.StatusToolbarModel
import com.biganto.visual.roompark.conductor.BigantoBaseController import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.conductor.dialogs.tour_chooser.ChooseTourDialogController
import com.biganto.visual.roompark.domain.model.* import com.biganto.visual.roompark.domain.model.*
import com.biganto.visual.roompark.presentation.screen.estate.util.FlatInfoAdapter import com.biganto.visual.roompark.presentation.screen.estate.util.FlatInfoAdapter
import com.biganto.visual.roompark.util.extensions.setGone import com.biganto.visual.roompark.util.extensions.setGone
import com.biganto.visual.roompark.util.extensions.startUrl import com.biganto.visual.roompark.util.extensions.startUrl
import com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView import com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
import com.bluelinelabs.conductor.RouterTransaction
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.google.android.material.switchmaterial.SwitchMaterial import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
...@@ -70,6 +74,12 @@ class EstateScreenController : ...@@ -70,6 +74,12 @@ class EstateScreenController :
.map { planTypesTabLayout.selectedTabPosition } .map { planTypesTabLayout.selectedTabPosition }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
override fun tourCardClicked(): Observable<Int> =
tourScreen.clicks()
.map { 1 }
.debounce(320L, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
private fun ViewGroup.switchMatch() = private fun ViewGroup.switchMatch() =
this.findViewById<SwitchMaterial>(R.id.switch1) this.findViewById<SwitchMaterial>(R.id.switch1)
.checkedChanges() .checkedChanges()
...@@ -109,6 +119,7 @@ class EstateScreenController : ...@@ -109,6 +119,7 @@ class EstateScreenController :
@Inject @Inject
override lateinit var injectedPresenter: EstateScreenPresenter override lateinit var injectedPresenter: EstateScreenPresenter
@BindView(R.id.flatTypesCustomView) @BindView(R.id.flatTypesCustomView)
lateinit var flatTypeView: ViewGroup lateinit var flatTypeView: ViewGroup
...@@ -188,13 +199,9 @@ class EstateScreenController : ...@@ -188,13 +199,9 @@ class EstateScreenController :
private fun setToolbar(){ private fun setToolbar(){
toolBar.setToolbar(null, StatusToolbarModel()) toolBar.setToolbar(null, StatusToolbarModel())
toolBar.statusToolbar.findViewById<View>(R.id.back_cross).let { toolBar.statusToolbar.findViewById<View>(R.id.back_cross)
detachDisposable.add( .setOnClickListener { handleBack() }
it.clicks()
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe { handleBack() }
)
}
detachDisposable.add( detachDisposable.add(
flatScroll.scrollChangeEvents() flatScroll.scrollChangeEvents()
// .debounce (25,TimeUnit.MILLISECONDS) // .debounce (25,TimeUnit.MILLISECONDS)
...@@ -203,7 +210,8 @@ class EstateScreenController : ...@@ -203,7 +210,8 @@ class EstateScreenController :
if (it.scrollY > flatTitle.measuredHeight) { if (it.scrollY > flatTitle.measuredHeight) {
val status = estateModel?.to( val status = estateModel?.to(
StatusToolbarModel( StatusToolbarModel(
StatusState.AVAILABLE if (estateModel?.availableStatus == true) StatusState.AVAILABLE
else StatusState.SOLD_OUT
, null , null
, resources?.getString( , resources?.getString(
estateModel?.type?.typeShortString() ?: -1 estateModel?.type?.typeShortString() ?: -1
...@@ -217,7 +225,8 @@ class EstateScreenController : ...@@ -217,7 +225,8 @@ class EstateScreenController :
} else toolBar.setToolbar( } else toolBar.setToolbar(
null, null,
StatusToolbarModel( StatusToolbarModel(
StatusState.AVAILABLE, null, null if (estateModel?.availableStatus == true) StatusState.AVAILABLE
else StatusState.SOLD_OUT, null, null
) )
) )
} }
...@@ -225,7 +234,8 @@ class EstateScreenController : ...@@ -225,7 +234,8 @@ class EstateScreenController :
} }
private fun bindRecycler() { private fun bindRecycler() {
flatInfoRecyclerView.isNestedScrollingEnabled = true flatScroll.isNestedScrollingEnabled = false
flatInfoRecyclerView.isNestedScrollingEnabled = false
flatInfoRecyclerView.layoutManager = flatInfoRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false) LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
flatInfoRecyclerView.adapter = FlatInfoAdapter() flatInfoRecyclerView.adapter = FlatInfoAdapter()
...@@ -255,6 +265,9 @@ class EstateScreenController : ...@@ -255,6 +265,9 @@ class EstateScreenController :
is EstateScreenViewState.PlanTypeSelected -> render(viewState) is EstateScreenViewState.PlanTypeSelected -> render(viewState)
is EstateScreenViewState.SomeError -> render(viewState) is EstateScreenViewState.SomeError -> render(viewState)
is EstateScreenViewState.ShowEstateInfo -> render(viewState) is EstateScreenViewState.ShowEstateInfo -> render(viewState)
is EstateScreenViewState.ToursLoaded -> render(viewState)
is EstateScreenViewState.RestoreView -> render(viewState)
} }
} }
...@@ -262,56 +275,33 @@ class EstateScreenController : ...@@ -262,56 +275,33 @@ class EstateScreenController :
} }
private fun render(viewState: EstateScreenViewState.SomeError) = private fun render(viewState: EstateScreenViewState.SomeError) =
showError(viewState.exception) showError(viewState.exception)
private var estateModel : EstateModel? = null private var estateModel : EstateModel? = null
private fun render(viewState: EstateScreenViewState.LoadEstate) { private fun render(viewState: EstateScreenViewState.LoadEstate) {
estateModel = viewState.estate setEstateInfo(viewState.estate)
toolBar.setToolbar( }
null, StatusToolbarModel(StatusState.AVAILABLE,null, null)
)
flatTitle.text = resources?.getString(viewState.estate.type.typeShortString()
,viewState.estate.number)
siteLink.setGone(viewState.estate.url == null)
viewState.estate.url?.let {url ->
siteLink.setOnClickListener{
activity?.startUrl(url)
}
}
startTour.setGone(viewState.estate.multitourId == null)
startTourDivider.setGone(viewState.estate.multitourId == null)
viewState.estate.multitourPreview?.let { private fun render(viewState: EstateScreenViewState.RestoreView) {
Glide.with(tourScreen) setEstateInfo(viewState.restore.estate)
.load(it)
.into(tourScreen)
}
when(viewState.estate.type){
FlatType.FLAT -> {
flatTypeView.setGone(false)
infoTabDivicder.setGone(false)
explicationTab.setGone(false)
}
else -> {
flatTypeView.setGone(true)
infoTabDivicder.setGone(true)
explicationTab.setGone(true)
}
}
} }
private fun render(viewState: EstateScreenViewState.ShowEstateInfo) { private fun render(viewState: EstateScreenViewState.ShowEstateInfo) {
(flatInfoRecyclerView.adapter as FlatInfoAdapter).setItems(viewState.info) (flatInfoRecyclerView.adapter as FlatInfoAdapter).setItems(viewState.info)
} }
private fun render(viewState: EstateScreenViewState.ToursLoaded) {
router.pushController(RouterTransaction.with(
ChooseTourDialogController(ArrayList(viewState.tours)))
.popChangeHandler(DialogChangeHandler())
.pushChangeHandler(DialogChangeHandler())
)
}
private fun render(viewState: EstateScreenViewState.LoadPlanTypes) { private fun render(viewState: EstateScreenViewState.LoadPlanTypes) {
planTypesTabLayout.removeAllTabs() planTypesTabLayout.removeAllTabs()
...@@ -390,6 +380,7 @@ class EstateScreenController : ...@@ -390,6 +380,7 @@ class EstateScreenController :
} }
@SuppressLint("SetJavaScriptEnabled")
private fun render(viewState: EstateScreenViewState.LoadPlan) { private fun render(viewState: EstateScreenViewState.LoadPlan) {
planWebView.settings.javaScriptEnabled = true planWebView.settings.javaScriptEnabled = true
planWebView.clearCache(true) planWebView.clearCache(true)
...@@ -397,11 +388,59 @@ class EstateScreenController : ...@@ -397,11 +388,59 @@ class EstateScreenController :
planWebView.loadUrl(uri) planWebView.loadUrl(uri)
} }
private fun setEstateInfo(estateModel: EstateModel?){
this.estateModel = estateModel
toolBar.setToolbar(
null,
StatusToolbarModel(
if (estateModel?.availableStatus == true) StatusState.AVAILABLE
else StatusState.SOLD_OUT
, null
, null
)
)
estateModel?.let {estate ->
flatTitle.text = resources?.getString(estate.type.typeShortString()
,estate.number)
}
siteLink.setGone(estateModel?.url == null)
estateModel?.url?.let {url ->
siteLink.setOnClickListener{
activity?.startUrl(url)
}
}
startTour.setGone(estateModel?.multitourId == null)
startTourDivider.setGone(estateModel?.multitourId == null)
estateModel?.multitourPreview?.let {
Glide.with(tourScreen)
.load(it)
.into(tourScreen)
}
when(estateModel?.type){
FlatType.FLAT -> {
flatTypeView.setGone(false)
infoTabDivicder.setGone(false)
explicationTab.setGone(false)
}
else -> {
flatTypeView.setGone(true)
infoTabDivicder.setGone(true)
explicationTab.setGone(true)
}
}
}
private fun getComponent() = DaggerEstateScreenComponent.factory() private fun getComponent() = DaggerEstateScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity .create(RoomParkApplication.component,activity as RoomParkMainActivity
,args.getInt(SELECTED_ESTATE_ID_KEY)) ,args.getInt(SELECTED_ESTATE_ID_KEY))
.inject(this) .inject(this)
override fun getLayoutId(): Int = R.layout.flat_full_card_screen override fun getLayoutId(): Int = R.layout.flat_full_card_screen
......
...@@ -5,6 +5,7 @@ import androidx.annotation.StringRes ...@@ -5,6 +5,7 @@ import androidx.annotation.StringRes
import com.biganto.visual.roompark.R import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.conductor.BigantoBasePresenter import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.EstateInteractor import com.biganto.visual.roompark.domain.interactor.EstateInteractor
import com.biganto.visual.roompark.domain.interactor.ToursInteractor
import com.biganto.visual.roompark.domain.model.* import com.biganto.visual.roompark.domain.model.*
import com.biganto.visual.roompark.presentation.screen.estate.util.DisplayInfoModel import com.biganto.visual.roompark.presentation.screen.estate.util.DisplayInfoModel
import com.biganto.visual.roompark.util.extensions.toRubly import com.biganto.visual.roompark.util.extensions.toRubly
...@@ -23,16 +24,12 @@ import javax.inject.Named ...@@ -23,16 +24,12 @@ import javax.inject.Named
class EstateScreenPresenter @Inject constructor( class EstateScreenPresenter @Inject constructor(
private val interactor: EstateInteractor, private val interactor: EstateInteractor,
private val toursInteractor: ToursInteractor,
private val context: Context, private val context: Context,
@Named(SELECTED_ESTATE_ID_KEY) private val estateId:Int @Named(SELECTED_ESTATE_ID_KEY) private val estateId:Int
) )
: BigantoBasePresenter<EstateScreen, EstateScreenViewState>() { : BigantoBasePresenter<EstateScreen, EstateScreenViewState>() {
private var planList: List<PlanPresetModel>? = null
private var estate: EstateModel? = null
private var showType: InfoShowType = InfoShowType.COMMON_INFO
override fun defaultErrorViewStateHandler() = override fun defaultErrorViewStateHandler() =
{ e: ExceptionString -> EstateScreenViewState.SomeError(e) } { e: ExceptionString -> EstateScreenViewState.SomeError(e) }
...@@ -45,19 +42,30 @@ class EstateScreenPresenter @Inject constructor( ...@@ -45,19 +42,30 @@ class EstateScreenPresenter @Inject constructor(
EstateScreenViewState.PlanTypeSelected(plan) EstateScreenViewState.PlanTypeSelected(plan)
) )
) )
.onErrorReturn (::parseError)
private var restoreModel = RestoreModel(null,null)
override fun detachView() {
super.detachView()
restoreStateObservable.accept(EstateScreenViewState.RestoreView(restoreModel))
}
override fun bindIntents() { override fun bindIntents() {
val prefetchCards = interactor.getEstate(estateId) val prefetchCards = interactor.getEstate(estateId)
.doOnNext { estate = it.copy() } .doOnNext { restoreModel.estate = it.copy() }
.map { EstateScreenViewState.LoadEstate(it) } .map<EstateScreenViewState> { EstateScreenViewState.LoadEstate(it) }
.onErrorReturn (::parseError)
val fetchPlans = interactor.getPlanTypes(estateId) val fetchPlans = interactor.getPlanTypes(estateId)
.doOnNext { planList = it.toList() } .doOnNext {restoreModel.planList = it.toList() }
.map { EstateScreenViewState.LoadPlanTypes(it) } .map<EstateScreenViewState> { EstateScreenViewState.LoadPlanTypes(it) }
.onErrorReturn (::parseError)
val fetchPlan = intent(EstateScreen::planTypesTabSelected) val fetchPlan = intent(EstateScreen::planTypesTabSelected)
.map { planList?.get(it) } .map { restoreModel.planList?.get(it) }
.flatMap { planPreset -> .flatMap { planPreset ->
interactor.getPlan(planPreset) interactor.getPlan(planPreset)
...@@ -71,67 +79,81 @@ class EstateScreenPresenter @Inject constructor( ...@@ -71,67 +79,81 @@ class EstateScreenPresenter @Inject constructor(
.startWith( .startWith(
Observable.just<EstateScreenViewState>( Observable.just<EstateScreenViewState>(
EstateScreenViewState.ShowEstateInfo( EstateScreenViewState.ShowEstateInfo(
showType restoreModel.showType
,if (showType== InfoShowType.COMMON_INFO) , if (restoreModel.showType == InfoShowType.COMMON_INFO)
mapCommonInfo(estate?.commonInfo) mapCommonInfo(restoreModel.estate?.commonInfo)
else mapCommonInfo(planPreset.explication) else mapCommonInfo(planPreset.explication)
) )
) )
) )
.onErrorReturn (::parseError)
} }
val switchSizes = intent(EstateScreen::switchSizes) val switchSizes = intent(EstateScreen::switchSizes)
.map { pair -> .map { pair ->
val plan = planList?.first { it.planId == pair.first } val plan = restoreModel.planList?.first { it.planId == pair.first }
plan?.switchFeature<FeatureModel.Sizes>(pair.second) plan?.switchFeature<FeatureModel.Sizes>(pair.second)
plan plan
}.flatMap(::getPlan) }.flatMap(::getPlan)
val switchFurn = intent(EstateScreen::switchFurniture) val switchFurn = intent(EstateScreen::switchFurniture)
.map { pair -> .map { pair ->
val plan = planList?.first { it.planId == pair.first } val plan = restoreModel.planList?.first { it.planId == pair.first }
plan?.switchFeature<FeatureModel.Furniture>(pair.second) plan?.switchFeature<FeatureModel.Furniture>(pair.second)
plan plan
}.flatMap(::getPlan) }.flatMap(::getPlan)
val switchWalls = intent(EstateScreen::switchWalls) val switchWalls = intent(EstateScreen::switchWalls)
.map { pair -> .map { pair ->
val plan = planList?.first { it.planId == pair.first } val plan = restoreModel.planList?.first { it.planId == pair.first }
plan?.switchFeature<FeatureModel.Walls>(pair.second) plan?.switchFeature<FeatureModel.Walls>(pair.second)
plan plan
}.flatMap(::getPlan) }.flatMap(::getPlan)
val switchElectric = intent(EstateScreen::switchElectric) val switchElectric = intent(EstateScreen::switchElectric)
.map { pair -> .map { pair ->
val plan = planList?.first { it.planId == pair.first } val plan = restoreModel.planList?.first { it.planId == pair.first }
plan?.switchFeature<FeatureModel.Electric>(pair.second) plan?.switchFeature<FeatureModel.Electric>(pair.second)
plan plan
}.flatMap(::getPlan) }.flatMap(::getPlan)
val showInfo = intent(EstateScreen::showCommonInfo) val showInfo = intent(EstateScreen::showCommonInfo)
.doOnNext { showType = InfoShowType.COMMON_INFO } .doOnNext { restoreModel.showType = InfoShowType.COMMON_INFO }
.map { estate?.commonInfo} .map { restoreModel.estate?.commonInfo }
.map (::mapCommonInfo) .map(::mapCommonInfo)
.map { EstateScreenViewState.ShowEstateInfo(showType,it) } .map { EstateScreenViewState.ShowEstateInfo(restoreModel.showType, it) }
val showExplication = intent(EstateScreen::showExplication) val showExplication = intent(EstateScreen::showExplication)
.doOnNext { showType = InfoShowType.EXPLICATIONS } .doOnNext { restoreModel.showType = InfoShowType.EXPLICATIONS }
.map { planList?.get(it)?.explication} .map { restoreModel.planList?.get(it)?.explication }
.map (::mapCommonInfo) .map(::mapCommonInfo)
.map { EstateScreenViewState.ShowEstateInfo(showType,it) } .map { EstateScreenViewState.ShowEstateInfo(restoreModel.showType, it) }
val state = restoreStateObservable
.mergeWith(prefetchCards) val onStartTours = intent(EstateScreen::tourCardClicked)
.mergeWith(fetchPlans) .map { restoreModel.estate }
.mergeWith(fetchPlan) .flatMap {estate -> toursInteractor.getEstateTourList(estate)
.mergeWith(switchElectric) .map { EstateScreenViewState.ToursLoaded(it) }
.mergeWith(switchFurn) }
.mergeWith(switchSizes)
.mergeWith(switchWalls)
.mergeWith(showInfo) val state = Observable.mergeDelayError(
.mergeWith(showExplication) arrayListOf(
restoreStateObservable,
prefetchCards,
fetchPlans,
fetchPlan,
switchElectric,
switchFurn,
switchSizes,
switchWalls,
showInfo,
onStartTours,
showExplication
)
)
.doOnError { Timber.e(it) } .doOnError { Timber.e(it) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
...@@ -194,3 +216,8 @@ enum class InfoShowType{ ...@@ -194,3 +216,8 @@ enum class InfoShowType{
COMMON_INFO, COMMON_INFO,
EXPLICATIONS EXPLICATIONS
} }
data class RestoreModel(var planList: List<PlanPresetModel>? = null,
var estate: EstateModel? = null,
var showType: InfoShowType = InfoShowType.COMMON_INFO)
\ No newline at end of file
...@@ -3,6 +3,7 @@ package com.biganto.visual.roompark.presentation.screen.estate ...@@ -3,6 +3,7 @@ package com.biganto.visual.roompark.presentation.screen.estate
import com.biganto.visual.roompark.conductor.BigantoBaseViewState import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.EstateModel import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.PlanPresetModel import com.biganto.visual.roompark.domain.model.PlanPresetModel
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.presentation.screen.estate.util.DisplayInfoModel import com.biganto.visual.roompark.presentation.screen.estate.util.DisplayInfoModel
import com.biganto.visual.roompark.util.monades.ExceptionString import com.biganto.visual.roompark.util.monades.ExceptionString
...@@ -19,4 +20,6 @@ sealed class EstateScreenViewState : BigantoBaseViewState() { ...@@ -19,4 +20,6 @@ sealed class EstateScreenViewState : BigantoBaseViewState() {
class LoadPlan(val planBody:String) : EstateScreenViewState() class LoadPlan(val planBody:String) : EstateScreenViewState()
class SomeError(val exception: ExceptionString) : EstateScreenViewState() class SomeError(val exception: ExceptionString) : EstateScreenViewState()
class ShowEstateInfo(val showType: InfoShowType, val info:List<DisplayInfoModel>) : EstateScreenViewState() class ShowEstateInfo(val showType: InfoShowType, val info:List<DisplayInfoModel>) : EstateScreenViewState()
class ToursLoaded(val tours:List<TourModel>) : EstateScreenViewState()
class RestoreView(val restore:RestoreModel) : EstateScreenViewState()
} }
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.favorites package com.biganto.visual.roompark.presentation.screen.favorites
import com.biganto.visual.roompark.conductor.BigantoBaseContract import com.biganto.visual.roompark.conductor.BigantoBaseContract
import com.biganto.visual.roompark.domain.model.EstateModel
import io.reactivex.Observable
/** /**
* Created by Vladislav Bogdashkin on 30.09.2019. * Created by Vladislav Bogdashkin on 30.09.2019.
*/ */
interface FavoritesScreen : BigantoBaseContract<FavoritesScreenViewState> { interface FavoritesScreen : BigantoBaseContract<FavoritesScreenViewState> {
fun tourCardClicked(): Observable<EstateModel>
} }
...@@ -9,13 +9,18 @@ import com.biganto.visual.roompark.base.HeaderToolbarModel ...@@ -9,13 +9,18 @@ import com.biganto.visual.roompark.base.HeaderToolbarModel
import com.biganto.visual.roompark.base.RoomParkApplication import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.conductor.dialogs.tour_chooser.ChooseTourDialogController
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.presentation.screen.estate.EstateScreenController import com.biganto.visual.roompark.presentation.screen.estate.EstateScreenController
import com.biganto.visual.roompark.presentation.screen.favorites.util.FavoritesListAdapter import com.biganto.visual.roompark.presentation.screen.favorites.util.FavoritesListAdapter
import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
/** /**
...@@ -38,6 +43,11 @@ class FavoritesScreenController : ...@@ -38,6 +43,11 @@ class FavoritesScreenController :
@BindView(R.id.favorites_cards_recycler_view) @BindView(R.id.favorites_cards_recycler_view)
lateinit var favoritesRecyclerView: RecyclerView lateinit var favoritesRecyclerView: RecyclerView
override fun tourCardClicked(): Observable<EstateModel> =
(favoritesRecyclerView.adapter as FavoritesListAdapter)
.startTour
.debounce(220L, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
override fun onAttach(view: View) { override fun onAttach(view: View) {
super.onAttach(view) super.onAttach(view)
...@@ -56,6 +66,7 @@ class FavoritesScreenController : ...@@ -56,6 +66,7 @@ class FavoritesScreenController :
} }
private fun setToolbar(){ private fun setToolbar(){
R.string.area_living
favoritesRecyclerView.isNestedScrollingEnabled = false favoritesRecyclerView.isNestedScrollingEnabled = false
toolBar.setToolbar( toolBar.setToolbar(
...@@ -66,7 +77,7 @@ class FavoritesScreenController : ...@@ -66,7 +77,7 @@ class FavoritesScreenController :
} }
private fun bindRecycler() { private fun bindRecycler() {
favoritesRecyclerView.isNestedScrollingEnabled = true favoritesRecyclerView.isNestedScrollingEnabled = false
favoritesRecyclerView.layoutManager = favoritesRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false) LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
favoritesRecyclerView.adapter = FavoritesListAdapter() favoritesRecyclerView.adapter = FavoritesListAdapter()
...@@ -93,6 +104,8 @@ class FavoritesScreenController : ...@@ -93,6 +104,8 @@ class FavoritesScreenController :
is FavoritesScreenViewState.Idle -> render(viewState) is FavoritesScreenViewState.Idle -> render(viewState)
is FavoritesScreenViewState.FavoriteEstatesLoaded -> render(viewState) is FavoritesScreenViewState.FavoriteEstatesLoaded -> render(viewState)
is FavoritesScreenViewState.SomeError -> render(viewState) is FavoritesScreenViewState.SomeError -> render(viewState)
is FavoritesScreenViewState.ToursLoaded -> render(viewState)
is FavoritesScreenViewState.RestoreView -> render(viewState)
} }
} }
...@@ -100,6 +113,20 @@ class FavoritesScreenController : ...@@ -100,6 +113,20 @@ class FavoritesScreenController :
} }
private fun render(viewState: FavoritesScreenViewState.RestoreView){
(favoritesRecyclerView.adapter as FavoritesListAdapter).addItems(viewState.restore.list)
}
private fun render(viewState: FavoritesScreenViewState.ToursLoaded) {
router.pushController(RouterTransaction.with(
ChooseTourDialogController(ArrayList(viewState.tours))
)
.popChangeHandler(DialogChangeHandler())
.pushChangeHandler(DialogChangeHandler())
)
}
private fun render(viewState: FavoritesScreenViewState.SomeError) = private fun render(viewState: FavoritesScreenViewState.SomeError) =
showError(viewState.exception) showError(viewState.exception)
......
...@@ -2,7 +2,10 @@ package com.biganto.visual.roompark.presentation.screen.favorites ...@@ -2,7 +2,10 @@ package com.biganto.visual.roompark.presentation.screen.favorites
import com.biganto.visual.roompark.conductor.BigantoBasePresenter import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.FavoritesInteractor import com.biganto.visual.roompark.domain.interactor.FavoritesInteractor
import com.biganto.visual.roompark.domain.interactor.ToursInteractor
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.util.monades.ExceptionString import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
...@@ -14,26 +17,62 @@ import javax.inject.Inject ...@@ -14,26 +17,62 @@ import javax.inject.Inject
class FavoritesScreenPresenter @Inject constructor( class FavoritesScreenPresenter @Inject constructor(
private val interactor: FavoritesInteractor private val interactor: FavoritesInteractor,
private val toursInteractor: ToursInteractor
) )
: BigantoBasePresenter<FavoritesScreen, FavoritesScreenViewState>() { : BigantoBasePresenter<FavoritesScreen, FavoritesScreenViewState>() {
override fun defaultErrorViewStateHandler() = override fun defaultErrorViewStateHandler() =
{e: ExceptionString -> FavoritesScreenViewState.SomeError(e)} { e: ExceptionString -> FavoritesScreenViewState.SomeError(e) }
private val restoreModel = RestoreModel(mutableListOf())
override fun detachView() {
super.detachView()
restoreStateObservable.accept(FavoritesScreenViewState.RestoreView(restoreModel))
}
override fun bindIntents() { override fun bindIntents() {
val prefetchCards = interactor.getFavoritesForCurrentUser() val prefetchCards = interactor.cachedFavorites()
.map { FavoritesScreenViewState.FavoriteEstatesLoaded(it) } .doOnNext { restoreModel.list = it.toMutableList() }
.map<FavoritesScreenViewState> { FavoritesScreenViewState.FavoriteEstatesLoaded(it) }
.onErrorReturn (::parseError)
val invalidateData = interactor.getFavoritesForCurrentUser()
.doOnNext { restoreModel.list = it.toMutableList() }
.map<FavoritesScreenViewState> { FavoritesScreenViewState.FavoriteEstatesLoaded(it) }
.onErrorReturn (::parseError)
val state = restoreStateObservable
.mergeWith(prefetchCards) val onStartTours = intent(FavoritesScreen::tourCardClicked)
.doOnError{ Timber.e(it)} .flatMap {estate -> toursInteractor.getEstateTourList(estate)
.map<FavoritesScreenViewState> { FavoritesScreenViewState.ToursLoaded(it) }
.onErrorReturn (::parseError)
}
val state = Observable.mergeDelayError(
arrayListOf(
restoreStateObservable,
prefetchCards,
invalidateData,
onStartTours
)
)
.doOnError { Timber.e(it) }
.onErrorReturn(::parseError) .onErrorReturn(::parseError)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(FavoritesScreenViewState::class.java), FavoritesScreen::render) subscribeViewState(
state.cast(FavoritesScreenViewState::class.java),
FavoritesScreen::render
)
} }
} }
\ No newline at end of file
data class RestoreModel(
var list:MutableList<EstateModel>
)
\ No newline at end of file
...@@ -2,6 +2,7 @@ package com.biganto.visual.roompark.presentation.screen.favorites ...@@ -2,6 +2,7 @@ package com.biganto.visual.roompark.presentation.screen.favorites
import com.biganto.visual.roompark.conductor.BigantoBaseViewState import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.EstateModel import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.util.monades.ExceptionString import com.biganto.visual.roompark.util.monades.ExceptionString
/** /**
...@@ -13,4 +14,6 @@ sealed class FavoritesScreenViewState : BigantoBaseViewState() { ...@@ -13,4 +14,6 @@ sealed class FavoritesScreenViewState : BigantoBaseViewState() {
class Idle : FavoritesScreenViewState() class Idle : FavoritesScreenViewState()
class FavoriteEstatesLoaded(val items: List<EstateModel>) : FavoritesScreenViewState() class FavoriteEstatesLoaded(val items: List<EstateModel>) : FavoritesScreenViewState()
class SomeError(val exception: ExceptionString) : FavoritesScreenViewState() class SomeError(val exception: ExceptionString) : FavoritesScreenViewState()
class ToursLoaded(val tours:List<TourModel>) : FavoritesScreenViewState()
class RestoreView(val restore:RestoreModel) : FavoritesScreenViewState()
} }
\ No newline at end of file
...@@ -14,6 +14,10 @@ import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyc ...@@ -14,6 +14,10 @@ import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyc
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder
import com.biganto.visual.roompark.util.extensions.setGone import com.biganto.visual.roompark.util.extensions.setGone
import com.biganto.visual.roompark.util.extensions.startUrl import com.biganto.visual.roompark.util.extensions.startUrl
import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.view.clicks
import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject
/** /**
* Created by Vladislav Bogdashkin on 16.10.2019. * Created by Vladislav Bogdashkin on 16.10.2019.
...@@ -22,11 +26,23 @@ import com.biganto.visual.roompark.util.extensions.startUrl ...@@ -22,11 +26,23 @@ import com.biganto.visual.roompark.util.extensions.startUrl
class FavoritesListAdapter : CommonRecyclerAdapter<FavoriteViewHolder,EstateModel>() { class FavoritesListAdapter : CommonRecyclerAdapter<FavoriteViewHolder,EstateModel>() {
override val vhKlazz = FavoriteViewHolder::class override val vhKlazz = FavoriteViewHolder::class
override fun getVhLayout(): Int = R.layout.favorite_card_viewholder override fun getVhLayout(): Int = R.layout.favorite_card_viewholder
private val onTourClickced = PublishSubject.create<EstateModel>()
override fun onBindViewHolder(holder: FavoriteViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
holder.onStartTourObs.subscribe(onTourClickced)
}
val startTour get() = onTourClickced
} }
class FavoriteViewHolder(itemView: View) : CommonViewHolder<EstateModel>(itemView) { class FavoriteViewHolder(itemView: View) : CommonViewHolder<EstateModel>(itemView) {
@BindView(R.id.avaliable_text) lateinit var availableText: MaterialTextView
@BindView(R.id.avaliable_status) lateinit var availableStatus:View
@BindView(R.id.object_card_title) lateinit var estateTitle: TextView @BindView(R.id.object_card_title) lateinit var estateTitle: TextView
@BindView(R.id.common_info_block) lateinit var commonInfo:View @BindView(R.id.common_info_block) lateinit var commonInfo:View
@BindView(R.id.start_tour_button) lateinit var startTour:View @BindView(R.id.start_tour_button) lateinit var startTour:View
...@@ -45,10 +61,16 @@ class FavoriteViewHolder(itemView: View) : CommonViewHolder<EstateModel>(itemVie ...@@ -45,10 +61,16 @@ class FavoriteViewHolder(itemView: View) : CommonViewHolder<EstateModel>(itemVie
init { init {
ButterKnife.bind(this, itemView) ButterKnife.bind(this, itemView)
} }
val onStartTourObs: Observable<EstateModel> get() =
startTour.clicks()
// .filter { bindedModel.availableStatus }
.map { bindedModel }
override fun onViewBound(model: EstateModel) { override fun onViewBound(model: EstateModel) {
estateTitle.text = estateTitle.text =
itemView.context.resources?.getString(model.type.typeDoubleString(),model.number) itemView.context.resources?.getString(model.type.typeDoubleString(),model.number)
...@@ -57,6 +79,15 @@ class FavoriteViewHolder(itemView: View) : CommonViewHolder<EstateModel>(itemVie ...@@ -57,6 +79,15 @@ class FavoriteViewHolder(itemView: View) : CommonViewHolder<EstateModel>(itemVie
startTour.setGone(model.type != FlatType.FLAT) startTour.setGone(model.type != FlatType.FLAT)
startTour.setGone(model.multitourId == null)
availableStatus.isEnabled = model.availableStatus
availableText.text = itemView.resources.getString(
if (model.availableStatus) R.string.estate_avalibale
else R.string.estate_sold_out
)
siteLink.setGone(model.url == null) siteLink.setGone(model.url == null)
siteLinkDivider.setGone(model.url == null) siteLinkDivider.setGone(model.url == null)
model.url?.let {url -> model.url?.let {url ->
...@@ -80,11 +111,11 @@ class FavoriteViewHolder(itemView: View) : CommonViewHolder<EstateModel>(itemVie ...@@ -80,11 +111,11 @@ class FavoriteViewHolder(itemView: View) : CommonViewHolder<EstateModel>(itemVie
if (info.area == null) info1.visibility = View.GONE if (info.area == null) info1.visibility = View.GONE
else { info4.title().text = "Общая, м²"; info4.text().text = info.area.toString()} else { info4.title().text = "Общая, м²"; info4.text().text = info.area.toString()}
if (info.price_meter == null) info1.visibility = View.GONE if (info.price_meter == null && !bindedModel.availableStatus) info1.visibility = View.GONE
else { info5.title().text = "Цена за м²"; info5.text().text = info.price_meter.toRubles()} else { info5.title().text = "Цена за м²"; info5.text().text = info.price_meter?.toRubles()}
if (info.price == null) info1.visibility = View.GONE if (info.price == null && !bindedModel.availableStatus) info1.visibility = View.GONE
else { info6.title().text = "Стоимость"; info6.text().text = info.price.toRubles()} else { info6.title().text = "Стоимость"; info6.text().text = info.price?.toRubles()}
if (true) info7.visibility = View.GONE if (true) info7.visibility = View.GONE
else { info7.title().text = "вщщ"; info7.text().text = info.building.toString()} else { info7.title().text = "вщщ"; info7.text().text = info.building.toString()}
......
...@@ -9,5 +9,6 @@ import io.reactivex.Observable ...@@ -9,5 +9,6 @@ import io.reactivex.Observable
interface ArticlesScreen : BigantoBaseContract<ArticlesScreenViewState> { interface ArticlesScreen : BigantoBaseContract<ArticlesScreenViewState> {
fun requsetsNewArticles(): Observable<Int> fun requsetsNewArticles(): Observable<Int>
fun feedSubscription(): Observable<Boolean>
} }
...@@ -15,10 +15,13 @@ import com.biganto.visual.roompark.conductor.BigantoBaseController ...@@ -15,10 +15,13 @@ import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.presentation.screen.article.ArticleScreenController import com.biganto.visual.roompark.presentation.screen.article.ArticleScreenController
import com.biganto.visual.roompark.presentation.screen.feed_list.util.ArticlesAdapter import com.biganto.visual.roompark.presentation.screen.feed_list.util.ArticlesAdapter
import com.biganto.visual.roompark.util.extensions.setGone import com.biganto.visual.roompark.util.extensions.setGone
import com.biganto.visual.roompark.util.monades.ExceptionString
import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.recyclerview.scrollStateChanges import com.jakewharton.rxbinding3.recyclerview.scrollStateChanges
import com.jakewharton.rxbinding3.widget.checkedChanges
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber import timber.log.Timber
...@@ -53,6 +56,22 @@ class ArticlesScreenController : ...@@ -53,6 +56,22 @@ class ArticlesScreenController :
.debounce(120L, TimeUnit.MILLISECONDS) .debounce(120L, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
override fun feedSubscription(): Observable<Boolean> =
toolBar.headerToolbar.findViewById<SwitchMaterial>(R.id.switch1)
.checkedChanges()
.skip(1) //skip init switcher check state
.filter {
if (!silentCheck) return@filter !silentCheck
else silentCheck = false
silentCheck
}
.doOnNext { }
.debounce(600L, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
private var silentCheck = false
override fun injectDependencies() { override fun injectDependencies() {
getComponent() getComponent()
} }
...@@ -125,6 +144,8 @@ class ArticlesScreenController : ...@@ -125,6 +144,8 @@ class ArticlesScreenController :
is ArticlesScreenViewState.ArticlesScrollPage -> render(viewState) is ArticlesScreenViewState.ArticlesScrollPage -> render(viewState)
is ArticlesScreenViewState.RestoreView -> render(viewState) is ArticlesScreenViewState.RestoreView -> render(viewState)
is ArticlesScreenViewState.SomeError -> render(viewState) is ArticlesScreenViewState.SomeError -> render(viewState)
is ArticlesScreenViewState.SubscriptionStatus -> render(viewState)
is ArticlesScreenViewState.SubscriptionError -> render(viewState)
} }
} }
...@@ -139,6 +160,29 @@ class ArticlesScreenController : ...@@ -139,6 +160,29 @@ class ArticlesScreenController :
progressBar.setGone(true) progressBar.setGone(true)
} }
private fun render(viewState: ArticlesScreenViewState.SubscriptionStatus) {
val sw = toolBar.headerToolbar.findViewById<SwitchMaterial>(R.id.switch1)
Timber.d("substatus: ${sw.isChecked} $silentCheck ${viewState.subState}")
if (sw.isChecked != viewState.subState)
{
silentCheck = true
sw.isChecked = viewState.subState
}
}
private fun render(viewState: ArticlesScreenViewState.SubscriptionError) {
val sw = toolBar.headerToolbar.findViewById<SwitchMaterial>(R.id.switch1)
if (sw.isChecked != viewState.subState)
{
silentCheck = true
sw.isChecked = viewState.subState
}
if (viewState.subState)
showError(ExceptionString(R.string.unsubscribe_error_message,null))
else showError(ExceptionString(R.string.subscribe_error_message,null))
}
private fun render(viewState: ArticlesScreenViewState.ArticlesScrollPage){ private fun render(viewState: ArticlesScreenViewState.ArticlesScrollPage){
Timber.d("got items: ${viewState.items.size}") Timber.d("got items: ${viewState.items.size}")
......
...@@ -2,6 +2,8 @@ package com.biganto.visual.roompark.presentation.screen.feed_list ...@@ -2,6 +2,8 @@ package com.biganto.visual.roompark.presentation.screen.feed_list
import com.biganto.visual.roompark.conductor.BigantoBasePresenter import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.ArticlesInteractor import com.biganto.visual.roompark.domain.interactor.ArticlesInteractor
import com.biganto.visual.roompark.domain.model.ArticlePreviewModel
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.util.monades.ExceptionString import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
...@@ -23,7 +25,7 @@ class ArticlesScreenPresenter @Inject constructor( ...@@ -23,7 +25,7 @@ class ArticlesScreenPresenter @Inject constructor(
) )
: BigantoBasePresenter<ArticlesScreen, ArticlesScreenViewState>() { : BigantoBasePresenter<ArticlesScreen, ArticlesScreenViewState>() {
private val restoreModel = RestoreModel(mutableListOf()) private val restoreModel = RestoreModel(mutableListOf(), null)
override fun detachView() { override fun detachView() {
super.detachView() super.detachView()
...@@ -32,36 +34,71 @@ class ArticlesScreenPresenter @Inject constructor( ...@@ -32,36 +34,71 @@ class ArticlesScreenPresenter @Inject constructor(
override fun defaultErrorViewStateHandler() = override fun defaultErrorViewStateHandler() =
{e: ExceptionString -> ArticlesScreenViewState.SomeError(e)} { e: ExceptionString -> ArticlesScreenViewState.SomeError(e) }
override fun bindIntents() { override fun bindIntents() {
Timber.d("feedId : $feedId") Timber.d("feedId : $feedId")
val onSubChecked = intent(ArticlesScreen::feedSubscription)
.filter { restoreModel.sub != null }
.flatMap { newState ->
interactor.switchSubscription(restoreModel.sub!!, newState)
.map {subs ->
val s = subs.map { it.subModel }.firstOrNull {it.id == restoreModel.sub?.id}
restoreModel.sub = s
if (s!=null)
return@map ArticlesScreenViewState.SubscriptionStatus(s.state)
else
return@map ArticlesScreenViewState.SubscriptionError(!newState)
}
.onErrorReturn { ArticlesScreenViewState.SubscriptionError(!newState) }
}
val fetchSubscription = interactor.getSubscriptions(feedId)
.doAfterNext { restoreModel.sub = it }
.map<ArticlesScreenViewState> { ArticlesScreenViewState.SubscriptionStatus(it.state) }
.startWith(Observable.just<ArticlesScreenViewState>(ArticlesScreenViewState.Idle()))
val prefetchCards = interactor.fetchArticles(feedId) val prefetchCards = interactor.fetchArticles(feedId)
.map<ArticlesScreenViewState>{ ArticlesScreenViewState.ArticlesLoaded(it.articles) } .doOnNext { restoreModel.articles.addAll(it.articles) }
.map<ArticlesScreenViewState> { ArticlesScreenViewState.ArticlesLoaded(it.articles) }
.startWith(Observable.just<ArticlesScreenViewState>(ArticlesScreenViewState.Idle())) .startWith(Observable.just<ArticlesScreenViewState>(ArticlesScreenViewState.Idle()))
val getNewArticlesPage = intent(ArticlesScreen::requsetsNewArticles) val getNewArticlesPage = intent(ArticlesScreen::requsetsNewArticles)
.flatMap { interactor.fetchArticlesPage(feedId, FEED_PAGE_SIZE,it) } .flatMap {currentItem ->
.map<ArticlesScreenViewState> {articlePage -> interactor.fetchArticlesPage(feedId, FEED_PAGE_SIZE, currentItem)
articlePage.articles .map<ArticlesScreenViewState> { articlePage ->
.asSequence() articlePage.articles
.filter { !restoreModel.articles.contains(it) } .asSequence()
.toList() .filter { !restoreModel.articles.contains(it) }
.let {uniqueItems->restoreModel.articles.addAll(uniqueItems)} .toList()
ArticlesScreenViewState.ArticlesScrollPage(articlePage.articles.toList()) .let { uniqueItems -> restoreModel.articles.addAll(uniqueItems) }
ArticlesScreenViewState.ArticlesScrollPage(articlePage.articles.toList())
}
.onErrorReturn(::parseError)
} }
.onErrorReturn (::parseError)
val state = restoreStateObservable val state = Observable.mergeDelayError(
.mergeWith(prefetchCards) arrayListOf(
.mergeWith(getNewArticlesPage) restoreStateObservable,
.doOnError{ Timber.e(it)} prefetchCards,
getNewArticlesPage,
fetchSubscription,
onSubChecked
)
)
.doOnError { Timber.e(it) }
.onErrorReturn(::parseError) .onErrorReturn(::parseError)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(ArticlesScreenViewState::class.java), ArticlesScreen::render) subscribeViewState(state.cast(ArticlesScreenViewState::class.java), ArticlesScreen::render)
} }
}
} data class RestoreModel(
\ No newline at end of file var articles:MutableList<ArticlePreviewModel>,
var sub: SubscriptionModel?
)
\ No newline at end of file
...@@ -15,9 +15,7 @@ sealed class ArticlesScreenViewState : BigantoBaseViewState() { ...@@ -15,9 +15,7 @@ sealed class ArticlesScreenViewState : BigantoBaseViewState() {
class SomeError(val exception: ExceptionString) : ArticlesScreenViewState() class SomeError(val exception: ExceptionString) : ArticlesScreenViewState()
class ArticlesScrollPage(val items:List<ArticlePreviewModel>) : ArticlesScreenViewState() class ArticlesScrollPage(val items:List<ArticlePreviewModel>) : ArticlesScreenViewState()
class RestoreView(val restore:RestoreModel) : ArticlesScreenViewState() class RestoreView(val restore:RestoreModel) : ArticlesScreenViewState()
class SubscriptionStatus(val subState: Boolean) : ArticlesScreenViewState()
class SubscriptionError(val subState: Boolean) : ArticlesScreenViewState()
} }
data class RestoreModel(
var articles:MutableList<ArticlePreviewModel>
)
\ No newline at end of file
...@@ -40,7 +40,6 @@ class ArticleViewHolder(itemView: View) : CommonViewHolder<ArticlePreviewModel>( ...@@ -40,7 +40,6 @@ class ArticleViewHolder(itemView: View) : CommonViewHolder<ArticlePreviewModel>(
override fun onViewBound(model: ArticlePreviewModel) { override fun onViewBound(model: ArticlePreviewModel) {
Timber.d("readeed?: ${model.isRead} of ${model.title}")
articleDate.text = dateFormatter.format(model.published) articleDate.text = dateFormatter.format(model.published)
articleTitle.text = model.title articleTitle.text = model.title
articleIsRead.setGone(model.isRead) articleIsRead.setGone(model.isRead)
......
...@@ -41,6 +41,9 @@ class AlbumCardViewHolder(itemView: View) : CommonViewHolder<AlbumPreviewModel>( ...@@ -41,6 +41,9 @@ class AlbumCardViewHolder(itemView: View) : CommonViewHolder<AlbumPreviewModel>(
articleTitle.text = model.title articleTitle.text = model.title
articleTitle.text = model.title articleTitle.text = model.title
articleDate.text = dateFormatter.format(model.published)
Glide.with(preview) Glide.with(preview)
.load(model.previewUrl) .load(model.previewUrl)
.centerCrop() .centerCrop()
......
...@@ -7,18 +7,14 @@ import android.view.Menu ...@@ -7,18 +7,14 @@ import android.view.Menu
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.NonNull import androidx.annotation.NonNull
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.contains import androidx.core.view.contains
import androidx.core.view.get import androidx.core.view.get
import androidx.viewpager.widget.PagerAdapter import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.biganto.visual.roompark.base.ICollapsingToolBar
import com.biganto.visual.roompark.base.IConductorActivity
import com.bluelinelabs.conductor.Controller import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import timber.log.Timber
/** /**
* Created by Vladislav Bogdashkin on 11.10.2019. * Created by Vladislav Bogdashkin on 11.10.2019.
...@@ -119,53 +115,8 @@ class BNVRouterPagerAdapter( ...@@ -119,53 +115,8 @@ class BNVRouterPagerAdapter(
router.backstack.forEach { it.controller().setOptionsMenuHidden(false) } router.backstack.forEach { it.controller().setOptionsMenuHidden(false) }
currentPrimaryRouter = router currentPrimaryRouter = router
} }
drawToolbar(position)
} }
fun drawToolbar(position: Int){
return
val toolBar = currentPrimaryRouter?.activity as ICollapsingToolBar
toolBar.showAll()
toolBar.appBar.setExpanded(false,true)
toolBar.appBar.setLiftable(true)
toolBar.appBar.setLifted(true)
toolBar.appBarScrollable(false)
toolBar.topAppBar.title = when(position){
0 -> "dsfsdf"
1 -> "ИЗБРАННОЕ"
2 -> "МОИ СДЕЛКИ"
3 -> "СМОТРЕТЬ\nКВАРТИРУ"
4 -> "НАСТРОЙКИ"
else -> ""
}
// toolBar.appBar.visibility=View.VISIBLE
val cc = (currentPrimaryRouter?.activity as IConductorActivity)
Timber.d(" posicione $position")
when(position){
0 -> {
Timber.d(" IN POSOIITION 0 ")
// toolBar.appBar.visibility = Toolbar.GONE;
// toolBar.topAppBar.visibility = View.GONE;
val params: CoordinatorLayout.LayoutParams = cc.conductorContainer.layoutParams as CoordinatorLayout.LayoutParams
params.behavior = null
}
1 ->{}
2 ->{}
3 -> {
toolBar.appBar.setLifted(false)
toolBar.appBar.setLiftable(false)
}
4 ->{}
else -> {}
}
}
override fun isViewFromObject(view: View, router: Any): Boolean { override fun isViewFromObject(view: View, router: Any): Boolean {
require(router is Router) { "Non-router object in router stack!" } require(router is Router) { "Non-router object in router stack!" }
......
package com.biganto.visual.roompark.presentation.screen.photo package com.biganto.visual.roompark.presentation.screen.photo
import android.content.Intent
import android.graphics.Bitmap
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
...@@ -14,11 +16,13 @@ import com.biganto.visual.roompark.base.RoomParkMainActivity ...@@ -14,11 +16,13 @@ import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.conductor.dialogs.ChooseResolutionDialogController import com.biganto.visual.roompark.conductor.dialogs.ChooseResolutionDialogController
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.presentation.screen.photo.util.PhotoPreviewSlider import com.biganto.visual.roompark.presentation.screen.photo.util.PhotoPreviewSlider
import com.biganto.visual.roompark.presentation.screen.photo.util.PhotosAdapter import com.biganto.visual.roompark.presentation.screen.photo.util.PhotosAdapter
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
import timber.log.Timber import timber.log.Timber
import java.io.FileOutputStream
import javax.inject.Inject import javax.inject.Inject
...@@ -68,6 +72,33 @@ class PhotoScreenController : ...@@ -68,6 +72,33 @@ class PhotoScreenController :
handleBack() handleBack()
} }
@OnClick(R.id.share_image_button)
fun onShareImage(){
val imgUrl =
(photoViewPager.adapter as PhotosAdapter)
.getCurrentItem(photoViewPager.currentItem)
.resolutionList
.maxBy { it.resHeight*it.resWidth }
?.url?:""
val shareIntent = Intent()
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
shareIntent.action = Intent.ACTION_SEND
shareIntent.type = "text/plain"
shareIntent.putExtra(Intent.EXTRA_TEXT, imgUrl)
startActivity(shareIntent)
}
private fun writeFile(fileName: String?, bitmap: Bitmap): String {
val f = FileModule.getDirectory(activity!!,FileModule.FileDirectory.Albums(fileName))
val outputStream = FileOutputStream(f)
outputStream.use {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)
it.flush()
}
return f.absolutePath
}
override fun onAttach(view: View) { override fun onAttach(view: View) {
// tempSystemUiFlag = activity?.window?.decorView?.systemUiVisibility // tempSystemUiFlag = activity?.window?.decorView?.systemUiVisibility
super.onAttach(view) super.onAttach(view)
......
...@@ -34,6 +34,8 @@ class PhotosAdapter : CommonRecyclerAdapter<PhotosViewHolder, PhotoModel>() { ...@@ -34,6 +34,8 @@ class PhotosAdapter : CommonRecyclerAdapter<PhotosViewHolder, PhotoModel>() {
fun indexById(id:Int):Int = list.indexOfFirst { it.photoId == id } fun indexById(id:Int):Int = list.indexOfFirst { it.photoId == id }
fun getCurrentItem(pos:Int) = list[pos]
override val vhKlazz = PhotosViewHolder::class override val vhKlazz = PhotosViewHolder::class
......
package com.biganto.visual.roompark.presentation.screen.settings package com.biganto.visual.roompark.presentation.screen.settings
import com.biganto.visual.roompark.conductor.BigantoBaseContract import com.biganto.visual.roompark.conductor.BigantoBaseContract
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import io.reactivex.Observable import io.reactivex.Observable
/** /**
...@@ -8,8 +9,10 @@ import io.reactivex.Observable ...@@ -8,8 +9,10 @@ import io.reactivex.Observable
*/ */
interface SettingsScreen : BigantoBaseContract<SettingsScreenViewState> { interface SettingsScreen : BigantoBaseContract<SettingsScreenViewState> {
// fun downloadAllTours(): Observable<Int>
fun signOut(): Observable<Int> fun signOut(): Observable<Int>
fun clearCache(): Observable<Int> fun clearCache(): Observable<Int>
fun refreshCacheInfo(): Observable<Int> fun refreshCacheInfo(): Observable<Int>
fun onSubscription(): Observable<SubscriptionModel>
} }
...@@ -7,16 +7,22 @@ import androidx.core.widget.NestedScrollView ...@@ -7,16 +7,22 @@ import androidx.core.widget.NestedScrollView
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView import butterknife.BindView
import butterknife.OnClick
import com.biganto.visual.roompark.R import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.HeaderToolbarModel import com.biganto.visual.roompark.base.HeaderToolbarModel
import com.biganto.visual.roompark.base.RoomParkApplication import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.conductor.dialogs.StartPlansDownloadingDialogController
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.conductor.dialogs.tour_chooser.StartToursDownloadingDialogController
import com.biganto.visual.roompark.data.service.network.INetworkMonitor
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.presentation.screen.settings.util.CahcedListAdapter import com.biganto.visual.roompark.presentation.screen.settings.util.CahcedListAdapter
import com.biganto.visual.roompark.presentation.screen.settings.util.PushListAdapter import com.biganto.visual.roompark.presentation.screen.settings.util.PushListAdapter
import com.biganto.visual.roompark.presentation.screen.splash.SplashScreenController import com.biganto.visual.roompark.presentation.screen.splash.SplashScreenController
import com.biganto.visual.roompark.util.extensions.bytesToSize
import com.biganto.visual.roompark.util.extensions.setGone import com.biganto.visual.roompark.util.extensions.setGone
import com.biganto.visual.roompark.util.monades.ExceptionString
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
...@@ -45,13 +51,27 @@ class SettingsScreenController : ...@@ -45,13 +51,27 @@ class SettingsScreenController :
override fun clearCache(): Observable<Int> = override fun clearCache(): Observable<Int> =
clearCacheButton.clicks() clearCacheButton.clicks()
.debounce ( 500, TimeUnit.MICROSECONDS ) .debounce ( 120, TimeUnit.MICROSECONDS )
.map { Timber.d("Clicked clear cache button"); 1 } .map { Timber.d("Clicked clear cache button"); 1 }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
// override fun downloadAllTours(): Observable<Int> =
// toursDownloaderButton.clicks().filter { false }
// .map { 1 }.observeOn(AndroidSchedulers.mainThread())
private val refreshEmitter = BehaviorRelay.create<Int>() private val refreshEmitter = BehaviorRelay.create<Int>()
override fun refreshCacheInfo(): Observable<Int> = refreshEmitter override fun refreshCacheInfo(): Observable<Int> = refreshEmitter
override fun onSubscription(): Observable<SubscriptionModel> =
(pushRecycler.adapter as PushListAdapter)
.onItemClicked
.map { it.subModel }
.debounce ( 600, TimeUnit.MILLISECONDS )
.observeOn(AndroidSchedulers.mainThread())
override fun injectDependencies() { override fun injectDependencies() {
getComponent() getComponent()
} }
...@@ -90,6 +110,21 @@ class SettingsScreenController : ...@@ -90,6 +110,21 @@ class SettingsScreenController :
@BindView(R.id.progress_lock_background) @BindView(R.id.progress_lock_background)
lateinit var progressShame:View lateinit var progressShame:View
@Inject
lateinit var networkMonitor: INetworkMonitor
@OnClick(R.id.downloadFlatCardsIcon)
fun onStartTourPlans(){
if (!networkMonitor.isConnected){
snackbar.showSnackBar(R.string.no_network_error)
return
}
router.pushController(RouterTransaction.with(StartPlansDownloadingDialogController())
.pushChangeHandler(DialogChangeHandler())
.popChangeHandler(DialogChangeHandler())
)
}
private fun setToolbar(){ private fun setToolbar(){
toolBar.appBar.liftOnScrollTargetViewId = R.id.nestedScrollContainer toolBar.appBar.liftOnScrollTargetViewId = R.id.nestedScrollContainer
pushRecycler.isNestedScrollingEnabled = false pushRecycler.isNestedScrollingEnabled = false
...@@ -102,6 +137,19 @@ class SettingsScreenController : ...@@ -102,6 +137,19 @@ class SettingsScreenController :
) )
} }
@OnClick(R.id.downloadToursIcon)
fun startDownloadingDialog(){
if (!networkMonitor.isConnected){
snackbar.showSnackBar(R.string.no_network_error)
return
}
router.pushController(RouterTransaction.with(StartToursDownloadingDialogController())
.pushChangeHandler(DialogChangeHandler())
.popChangeHandler(DialogChangeHandler())
)
}
private fun bindRecycler(){ private fun bindRecycler(){
pushRecycler.isNestedScrollingEnabled = true pushRecycler.isNestedScrollingEnabled = true
pushRecycler.layoutManager = pushRecycler.layoutManager =
...@@ -118,6 +166,7 @@ class SettingsScreenController : ...@@ -118,6 +166,7 @@ class SettingsScreenController :
override fun onViewBound(v: View) { override fun onViewBound(v: View) {
bottomNavigationController.show() bottomNavigationController.show()
setToolbar() setToolbar()
bindRecycler() bindRecycler()
} }
...@@ -131,6 +180,12 @@ class SettingsScreenController : ...@@ -131,6 +180,12 @@ class SettingsScreenController :
is SettingsScreenViewState.SomeError -> render(viewState) is SettingsScreenViewState.SomeError -> render(viewState)
is SettingsScreenViewState.SignOut -> render(viewState) is SettingsScreenViewState.SignOut -> render(viewState)
is SettingsScreenViewState.OnCacheDeleting -> render(viewState) is SettingsScreenViewState.OnCacheDeleting -> render(viewState)
is SettingsScreenViewState.OnPlanTypesPrefetch -> render(viewState)
is SettingsScreenViewState.LoadCachInfo -> render(viewState)
is SettingsScreenViewState.LoadSubscriptions -> render(viewState)
is SettingsScreenViewState.SubscriptionStatus -> render(viewState)
is SettingsScreenViewState.SubscriptionError -> render(viewState)
is SettingsScreenViewState.OnSizePrefetch -> render(viewState)
} }
} }
...@@ -142,6 +197,25 @@ class SettingsScreenController : ...@@ -142,6 +197,25 @@ class SettingsScreenController :
clearCacheButton.setGone(false) clearCacheButton.setGone(false)
} }
private fun render(viewState: SettingsScreenViewState.LoadSubscriptions){
(pushRecycler.adapter as PushListAdapter).setItems(viewState.list)
}
private fun render(viewState: SettingsScreenViewState.LoadCachInfo){
(cachedRecycler.adapter as CahcedListAdapter).setItems(viewState.list)
}
private fun render(viewState: SettingsScreenViewState.SubscriptionStatus){
(pushRecycler.adapter as PushListAdapter).setSubState(viewState.subId,viewState.subState)
}
private fun render(viewState: SettingsScreenViewState.SubscriptionError){
(pushRecycler.adapter as PushListAdapter).setSubState(viewState.subId,viewState.subState)
if (viewState.subState)
showError(ExceptionString(R.string.unsubscribe_error_message,null))
else showError(ExceptionString(R.string.subscribe_error_message,null))
}
private fun render(viewState: SettingsScreenViewState.OnCacheDeleting){ private fun render(viewState: SettingsScreenViewState.OnCacheDeleting){
val isProgressed = viewState.progress>=1f val isProgressed = viewState.progress>=1f
...@@ -160,12 +234,7 @@ class SettingsScreenController : ...@@ -160,12 +234,7 @@ class SettingsScreenController :
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun render(viewState: SettingsScreenViewState.LoadSettingsList){ private fun render(viewState: SettingsScreenViewState.LoadSettingsList){
(pushRecycler.adapter as PushListAdapter).setItems(viewState.settings.pushItems)
toursDownloaderTitle.text = viewState.settings.offlineStoreData[0].title +
"(${viewState.settings.offlineStoreData[0].amountBytes.bytesToSize()})"
flatDownloaderTitle.text = viewState.settings.offlineStoreData[1].title +
"(${viewState.settings.offlineStoreData[1].amountBytes.bytesToSize()})"
(cachedRecycler.adapter as CahcedListAdapter).setItems(viewState.settings.cachedData)
} }
private fun getComponent() = DaggerSettingsScreenComponent.factory() private fun getComponent() = DaggerSettingsScreenComponent.factory()
......
package com.biganto.visual.roompark.presentation.screen.settings package com.biganto.visual.roompark.presentation.screen.settings
import android.content.Context import com.biganto.visual.roompark.base.BaseRoomParkActivity
import com.biganto.visual.roompark.base.RoomParkMainActivity import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen import com.biganto.visual.roompark.di.dagger.PerScreen
...@@ -33,7 +33,7 @@ abstract class SettingsScreenModule{ ...@@ -33,7 +33,7 @@ abstract class SettingsScreenModule{
@PerScreen @PerScreen
@Binds @Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context abstract fun provideActivity(activity: RoomParkMainActivity): BaseRoomParkActivity
// @PerScreen // @PerScreen
// @Binds // @Binds
......
package com.biganto.visual.roompark.presentation.screen.settings package com.biganto.visual.roompark.presentation.screen.settings
import android.content.Context import com.biganto.visual.roompark.base.BaseRoomParkActivity
import com.biganto.visual.roompark.conductor.BigantoBasePresenter import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.SettingsInteractor import com.biganto.visual.roompark.domain.interactor.SettingsInteractor
import com.biganto.visual.roompark.domain.model.CachedDataModel
import com.biganto.visual.roompark.domain.model.TitledSubscriptionModel
import com.biganto.visual.roompark.util.monades.ExceptionString import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
/** /**
* Created by Vladislav Bogdashkin on 30.09.2019. * Created by Vladislav Bogdashkin on 30.09.2019.
*/ */
class SettingsScreenPresenter @Inject constructor( class SettingsScreenPresenter @Inject constructor(
private val interactor: SettingsInteractor, private val interactor: SettingsInteractor,
private val activity: Context private val activity: BaseRoomParkActivity
) )
: BigantoBasePresenter<SettingsScreen, SettingsScreenViewState>() { : BigantoBasePresenter<SettingsScreen, SettingsScreenViewState>() {
private val restoreModel = RestoreModel(mutableListOf(), arrayListOf(), arrayListOf())
override fun detachView() {
super.detachView()
restoreStateObservable.accept(SettingsScreenViewState.RestoreView(restoreModel))
}
override fun defaultErrorViewStateHandler() = override fun defaultErrorViewStateHandler() =
{e: ExceptionString -> SettingsScreenViewState.SomeError(e)} { e: ExceptionString -> SettingsScreenViewState.SomeError(e) }
// private val cacheSizeRefresher = PublishRelay.create<Int>()
// private val cahcObs =
// cacheSizeRefresher.flatMap {
// Observable.merge(
// arrayListOf(
// fetchToursSize, fetchPlansSize
// )
// )
// }
// private val fetchToursSize: Observable<SettingsScreenViewState> = interactor.fetchToursSizes()
// .map { SettingsScreenViewState.OnSizePrefetch(it) }
//
// private val fetchPlansSize: Observable<SettingsScreenViewState> = interactor.fetchPlanTypesSizes()
// .map { it* TYPICAL_PLAN_SIZE }
// .map { SettingsScreenViewState.OnPlanTypesPrefetch(it) }
override fun bindIntents() { override fun bindIntents() {
val fetchSettings = interactor.fetchSettings()
.flatMap {settings ->
interactor.getCacheInfo()
.doOnNext { cached -> cached.sortBy { it.id } }
.doOnNext{ settings.cachedData = it }
.map { settings }}
.map { SettingsScreenViewState.LoadSettingsList(it)}
// val onDownloadTours = intent(SettingsScreen::downloadAllTours)
// .flatMap { interactor.startToursDownloading()
// .andThen(Observable.just(SettingsScreenViewState.Idle()))
// }
val onSubChecked = intent(SettingsScreen::onSubscription)
.flatMap { sub ->
interactor.switchSubscription(sub, !sub.state)
.map<SettingsScreenViewState> {list ->
restoreModel.subs = list.sortedBy { it.subModel.id }.toMutableList()
SettingsScreenViewState.LoadSubscriptions(restoreModel.subs)
}
.doOnError { Timber.e(it) }
.onErrorReturn { SettingsScreenViewState.SubscriptionError(sub.id, sub.state) }
}
val fetchSettings = interactor.fetchSettings()
.doOnNext { restoreModel.offlineStoreData = it.offlineStoreData.toMutableList() }
.map { SettingsScreenViewState.LoadSettingsList(it) }
val fetchCache = interactor.getCacheInfo()
.doOnNext { cached -> cached.sortBy { it.id } }
.doOnNext { restoreModel.cacheInfo = it }
.map { SettingsScreenViewState.LoadCachInfo(it) }
// .doOnNext {cacheSizeRefresher.accept(1) }
val fetchSubscriptions = interactor.getSubscriptions()
.map {list ->
restoreModel.subs = list.sortedBy { it.subModel.id }.toMutableList()
SettingsScreenViewState.LoadSubscriptions(restoreModel.subs)
}
val onSignOut = intent(SettingsScreen::signOut) val onSignOut = intent(SettingsScreen::signOut)
.flatMap { .flatMap {
...@@ -44,28 +96,45 @@ class SettingsScreenPresenter @Inject constructor( ...@@ -44,28 +96,45 @@ class SettingsScreenPresenter @Inject constructor(
val refreshInfo = intent(SettingsScreen::refreshCacheInfo) val refreshInfo = intent(SettingsScreen::refreshCacheInfo)
.flatMap {fetchSettings} .flatMap { fetchCache }
val onClearCache = intent(SettingsScreen::clearCache) val onClearCache = intent(SettingsScreen::clearCache)
.flatMap { .flatMap {
interactor.deleteCacheFiles() interactor.deleteCacheFiles()
.map<SettingsScreenViewState>{ .map<SettingsScreenViewState> {
Timber.d(" got progress: ${it.first} / ${it.second.toFloat()}")
// if (it.first == it.second)
// cacheSizeRefresher.accept(1)
SettingsScreenViewState.OnCacheDeleting( SettingsScreenViewState.OnCacheDeleting(
it.first/it.second.toFloat() it.first / it.second.toFloat()
) )
} }
.delay(600,TimeUnit.MILLISECONDS)
.startWith(SettingsScreenViewState.OnCacheDeleting(0f)) .startWith(SettingsScreenViewState.OnCacheDeleting(0f))
.onErrorReturn(::parseError) .doOnError { Timber.e(it) }
.subscribeOn(Schedulers.io())
} }
val state = Observable.mergeDelayError(
val state = restoreStateObservable arrayListOf(
.mergeWith(fetchSettings) restoreStateObservable,
.mergeWith(onSignOut) // cahcObs,
.mergeWith(onClearCache) // fetchPlansSize,
.mergeWith(refreshInfo) fetchSettings,
.doOnError{ Timber.e(it)} onSignOut,
onClearCache,
refreshInfo,
fetchSubscriptions,
fetchCache,
onSubChecked
// onDownloadTours,
// fetchToursSize
)
)
.doOnError { Timber.e(it) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.onErrorReturn(::parseError) .onErrorReturn(::parseError)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
...@@ -73,4 +142,12 @@ class SettingsScreenPresenter @Inject constructor( ...@@ -73,4 +142,12 @@ class SettingsScreenPresenter @Inject constructor(
subscribeViewState(state.cast(SettingsScreenViewState::class.java), SettingsScreen::render) subscribeViewState(state.cast(SettingsScreenViewState::class.java), SettingsScreen::render)
} }
} }
\ No newline at end of file
data class RestoreModel(
var subs: MutableList<TitledSubscriptionModel>,
var cacheInfo:MutableList<CachedDataModel>,
var offlineStoreData:MutableList<CachedDataModel>
)
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.settings package com.biganto.visual.roompark.presentation.screen.settings
import com.biganto.visual.roompark.conductor.BigantoBaseViewState import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.CachedDataModel
import com.biganto.visual.roompark.domain.model.SettingsModel import com.biganto.visual.roompark.domain.model.SettingsModel
import com.biganto.visual.roompark.domain.model.TitledSubscriptionModel
import com.biganto.visual.roompark.util.monades.ExceptionString import com.biganto.visual.roompark.util.monades.ExceptionString
/** /**
...@@ -16,4 +18,11 @@ sealed class SettingsScreenViewState : BigantoBaseViewState() { ...@@ -16,4 +18,11 @@ sealed class SettingsScreenViewState : BigantoBaseViewState() {
class SomeError(val exception: ExceptionString) : SettingsScreenViewState() class SomeError(val exception: ExceptionString) : SettingsScreenViewState()
class SignOut() : SettingsScreenViewState() class SignOut() : SettingsScreenViewState()
class OnCacheDeleting(val progress:Float) : SettingsScreenViewState() class OnCacheDeleting(val progress:Float) : SettingsScreenViewState()
} class OnSizePrefetch(val size:Long) : SettingsScreenViewState()
\ No newline at end of file class OnPlanTypesPrefetch(val size:Long) : SettingsScreenViewState()
class LoadSubscriptions(val list:List<TitledSubscriptionModel>) : SettingsScreenViewState()
class LoadCachInfo(val list: List<CachedDataModel>) : SettingsScreenViewState()
class SubscriptionStatus(val subId:Int,val subState: Boolean) : SettingsScreenViewState()
class SubscriptionError(val subId:Int,val subState: Boolean) : SettingsScreenViewState()
class RestoreView(val restore:RestoreModel) : SettingsScreenViewState()
}
...@@ -9,7 +9,8 @@ import butterknife.BindView ...@@ -9,7 +9,8 @@ import butterknife.BindView
import butterknife.ButterKnife import butterknife.ButterKnife
import com.biganto.visual.roompark.R import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.domain.model.CachedDataModel import com.biganto.visual.roompark.domain.model.CachedDataModel
import com.biganto.visual.roompark.domain.model.PushSwitchModel import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.model.TitledSubscriptionModel
import com.biganto.visual.roompark.util.extensions.bytesToSize import com.biganto.visual.roompark.util.extensions.bytesToSize
import com.google.android.material.switchmaterial.SwitchMaterial import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
...@@ -92,15 +93,26 @@ abstract class CommonViewHolder<M:Any>(itemView: View): RecyclerView.ViewHolder( ...@@ -92,15 +93,26 @@ abstract class CommonViewHolder<M:Any>(itemView: View): RecyclerView.ViewHolder(
class PushListAdapter : CommonRecyclerAdapter<PushViewHolder,PushSwitchModel>() { class PushListAdapter : CommonRecyclerAdapter<PushViewHolder,TitledSubscriptionModel>() {
override val vhKlazz: KClass<PushViewHolder> override val vhKlazz: KClass<PushViewHolder>
get() = PushViewHolder::class get() = PushViewHolder::class
override fun getVhLayout(): Int = R.layout.bell_switcher_with_text_viewholder override fun getVhLayout(): Int = R.layout.bell_switcher_with_text_viewholder
fun setSubState(subId:Int,state:Boolean){
val ind = list.indexOfFirst{ it.subModel.id == subId}
list[ind] = TitledSubscriptionModel(list[ind].title,SubscriptionModel(
topic = list[ind].subModel.topic,
id = list[ind].subModel.id,
state = state
))
notifyItemChanged(ind)
}
} }
class PushViewHolder(itemView: View) : CommonViewHolder<PushSwitchModel>(itemView) { class PushViewHolder(itemView: View) : CommonViewHolder<TitledSubscriptionModel>(itemView) {
@BindView(R.id.bellSwitcherTitle) @BindView(R.id.bellSwitcherTitle)
lateinit var bellTitle:MaterialTextView lateinit var bellTitle:MaterialTextView
...@@ -108,10 +120,12 @@ class PushViewHolder(itemView: View) : CommonViewHolder<PushSwitchModel>(itemVie ...@@ -108,10 +120,12 @@ class PushViewHolder(itemView: View) : CommonViewHolder<PushSwitchModel>(itemVie
@BindView(R.id.bellSwitch) @BindView(R.id.bellSwitch)
lateinit var switcher:ViewGroup lateinit var switcher:ViewGroup
override fun onViewBound(model: PushSwitchModel) { override fun onViewBound(model: TitledSubscriptionModel) {
Timber.d("model is : $model") Timber.d("model is : $model")
bellTitle.text = model.title bellTitle.text = model.title
switcher.findViewById<SwitchMaterial>(R.id.switch1).isChecked = model.switchState switcher.findViewById<SwitchMaterial>(R.id.switch1).isFocusable = false
switcher.findViewById<SwitchMaterial>(R.id.switch1).isClickable = false
switcher.findViewById<SwitchMaterial>(R.id.switch1).isChecked = model.subModel.state
} }
} }
......
...@@ -9,7 +9,7 @@ import io.reactivex.Observable ...@@ -9,7 +9,7 @@ import io.reactivex.Observable
interface FindFlatScreen : BigantoBaseContract<FindFlatScreenViewState> { interface FindFlatScreen : BigantoBaseContract<FindFlatScreenViewState> {
fun getFlat() : Observable<FlatRequestModel> fun getFlat() : Observable<FlatRequestModel>
fun openFlat() : Observable<Int> fun openFlat() : Observable<FlatRequestModel>
} }
data class FlatRequestModel(val building:Int,val number:Int) data class FlatRequestModel(val building:Int,val number:Int)
\ No newline at end of file
...@@ -17,6 +17,7 @@ import com.bluelinelabs.conductor.changehandler.FadeChangeHandler ...@@ -17,6 +17,7 @@ import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.jakewharton.rxbinding3.material.selections
import com.jakewharton.rxbinding3.view.clicks import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxbinding3.view.keys import com.jakewharton.rxbinding3.view.keys
import com.jakewharton.rxbinding3.widget.afterTextChangeEvents import com.jakewharton.rxbinding3.widget.afterTextChangeEvents
...@@ -36,24 +37,32 @@ class FindFlatScreenController : ...@@ -36,24 +37,32 @@ class FindFlatScreenController :
, FindFlatScreenPresenter>() , FindFlatScreenPresenter>()
, FindFlatScreen { , FindFlatScreen {
private val flatModel:FlatRequestModel get(){
return FlatRequestModel(
estateTabs[flatTabs.selectedTabPosition].building
, flatNumberInput.editText?.text.toString().toInt()
)
}
override fun openFlat(): Observable<Int> = override fun openFlat(): Observable<FlatRequestModel> =
findFlatButton.clicks() findFlatButton.clicks()
.map { 1 } .map { 1 }
.mergeWith(flatNumberEditor.keys{ it.keyCode == KeyEvent.KEYCODE_ENTER }.map { 1 }) .mergeWith(flatNumberEditor.keys{ it.keyCode == KeyEvent.KEYCODE_ENTER }.map { 1 })
.filter { flatNumberInput.editText?.text?.isNotEmpty()?:false }
.map {flatModel}
.doOnNext{ flatNumberEditor.hideKeyboard() } .doOnNext{ flatNumberEditor.hideKeyboard() }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
override fun getFlat(): Observable<FlatRequestModel> = override fun getFlat(): Observable<FlatRequestModel> =
flatNumberEditor.afterTextChangeEvents()// keys{ it.keyCode == KeyEvent.KEYCODE_ENTER } flatNumberEditor.afterTextChangeEvents()// keys{ it.keyCode == KeyEvent.KEYCODE_ENTER }
.map { 1 }
.mergeWith(
flatTabs.selections().map { 1 }
)
.filter { flatNumberInput.editText?.text?.isNotEmpty()?:false } .filter { flatNumberInput.editText?.text?.isNotEmpty()?:false }
.map { .mergeWith(flatTabs.clicks().map { 1 })
FlatRequestModel( .map {flatModel}
estateTabs[flatTabs.selectedTabPosition].building .debounce (120,TimeUnit.MILLISECONDS)
, flatNumberInput.editText?.text.toString().toInt()
)
}
.debounce (300,TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
override fun injectDependencies() { override fun injectDependencies() {
...@@ -117,7 +126,9 @@ class FindFlatScreenController : ...@@ -117,7 +126,9 @@ class FindFlatScreenController :
is FindFlatScreenViewState.SomeError -> render(viewState) is FindFlatScreenViewState.SomeError -> render(viewState)
is FindFlatScreenViewState.FlatFounded -> render(viewState) is FindFlatScreenViewState.FlatFounded -> render(viewState)
is FindFlatScreenViewState.FlatNotFound -> render(viewState) is FindFlatScreenViewState.FlatNotFound -> render(viewState)
is FindFlatScreenViewState.FlatSold -> render(viewState)
is FindFlatScreenViewState.StartFlat -> render(viewState) is FindFlatScreenViewState.StartFlat -> render(viewState)
is FindFlatScreenViewState.RestoreView -> render(viewState)
} }
} }
...@@ -127,6 +138,14 @@ class FindFlatScreenController : ...@@ -127,6 +138,14 @@ class FindFlatScreenController :
private fun render(viewState: FindFlatScreenViewState.Idle){ private fun render(viewState: FindFlatScreenViewState.Idle){
} }
private fun render(viewState: FindFlatScreenViewState.RestoreView){
val tabIndex = viewState.restore.building-1
Timber.d(":: ${viewState.restore.building} : $tabIndex")
flatTabs.getTabAt(tabIndex)?.select() //zero-based
flatNumberInput.editText?.setText(viewState.restore.number.toString())
}
private fun render(viewState: FindFlatScreenViewState.FlatFounded){ private fun render(viewState: FindFlatScreenViewState.FlatFounded){
findFlatButton.isEnabled = true findFlatButton.isEnabled = true
findFlatButton.text = resources?.getString(R.string.flat_ready_to_watch) findFlatButton.text = resources?.getString(R.string.flat_ready_to_watch)
...@@ -138,8 +157,14 @@ class FindFlatScreenController : ...@@ -138,8 +157,14 @@ class FindFlatScreenController :
{findFlatButton.text = resources?.getString(it)}, {findFlatButton.text = resources?.getString(it)},
{findFlatButton.text = it} {findFlatButton.text = it}
) )
}
private fun render(viewState: FindFlatScreenViewState.FlatSold){
findFlatButton.isEnabled = false
viewState.exceptionString.selectHandler(
{findFlatButton.text = resources?.getString(it)},
{findFlatButton.text = it}
)
} }
private fun render(viewState: FindFlatScreenViewState.StartFlat){ private fun render(viewState: FindFlatScreenViewState.StartFlat){
......
...@@ -27,15 +27,24 @@ class FindFlatScreenPresenter @Inject constructor( ...@@ -27,15 +27,24 @@ class FindFlatScreenPresenter @Inject constructor(
//estateId //estateId
private var recentFlat:Int? =null private var recentFlat:Int? =null
private var restoreModel = RestoreModel(-1,-1)
override fun detachView() {
super.detachView()
restoreStateObservable.accept(FindFlatScreenViewState.RestoreView(restoreModel))
}
override fun vsByCode(code: Int): (ExceptionString) -> FindFlatScreenViewState = override fun vsByCode(code: Int): (ExceptionString) -> FindFlatScreenViewState =
when (code) { when (code) {
304 -> {message:ExceptionString ->FindFlatScreenViewState.FlatNotFound(message)} 304 -> {message:ExceptionString ->FindFlatScreenViewState.FlatNotFound(message)}
307 -> {message:ExceptionString ->FindFlatScreenViewState.FlatSold(message)}
else -> super.vsByCode(code) else -> super.vsByCode(code)
} }
override fun bindIntents() { override fun bindIntents() {
val getFlatIntent = intent(FindFlatScreen::getFlat) val getFlatIntent = intent(FindFlatScreen::getFlat)
.doOnNext { restoreModel.building = it.building;restoreModel.number = it.number }
.doOnNext{recentFlat=null} .doOnNext{recentFlat=null}
.doOnNext{Timber.d(" flat is $it")} .doOnNext{Timber.d(" flat is $it")}
.flatMap { request -> .flatMap { request ->
...@@ -47,6 +56,7 @@ class FindFlatScreenPresenter @Inject constructor( ...@@ -47,6 +56,7 @@ class FindFlatScreenPresenter @Inject constructor(
.startWith(Observable.just(FindFlatScreenViewState.RequstFlatProgress())) .startWith(Observable.just(FindFlatScreenViewState.RequstFlatProgress()))
val startFlatIntent = intent(FindFlatScreen::openFlat) val startFlatIntent = intent(FindFlatScreen::openFlat)
.doOnNext { restoreModel.building = it.building;restoreModel.number = it.number }
.map { .map {
if (recentFlat != null) {FindFlatScreenViewState.StartFlat(recentFlat as Int)} if (recentFlat != null) {FindFlatScreenViewState.StartFlat(recentFlat as Int)}
else FindFlatScreenViewState.FlatNotFound( else FindFlatScreenViewState.FlatNotFound(
...@@ -55,6 +65,7 @@ class FindFlatScreenPresenter @Inject constructor( ...@@ -55,6 +65,7 @@ class FindFlatScreenPresenter @Inject constructor(
} }
val state = restoreStateObservable val state = restoreStateObservable
.mergeWith(getFlatIntent) .mergeWith(getFlatIntent)
.mergeWith(startFlatIntent) .mergeWith(startFlatIntent)
...@@ -64,4 +75,7 @@ class FindFlatScreenPresenter @Inject constructor( ...@@ -64,4 +75,7 @@ class FindFlatScreenPresenter @Inject constructor(
subscribeViewState(state.cast(FindFlatScreenViewState::class.java), FindFlatScreen::render) subscribeViewState(state.cast(FindFlatScreenViewState::class.java), FindFlatScreen::render)
} }
} }
\ No newline at end of file
data class RestoreModel(
var building:Int,var number:Int)
...@@ -13,6 +13,8 @@ sealed class FindFlatScreenViewState : BigantoBaseViewState() { ...@@ -13,6 +13,8 @@ sealed class FindFlatScreenViewState : BigantoBaseViewState() {
class RequstFlatProgress : FindFlatScreenViewState() class RequstFlatProgress : FindFlatScreenViewState()
class FlatFounded : FindFlatScreenViewState() class FlatFounded : FindFlatScreenViewState()
class FlatNotFound(val exceptionString:ExceptionString) : FindFlatScreenViewState() class FlatNotFound(val exceptionString:ExceptionString) : FindFlatScreenViewState()
class FlatSold(val exceptionString:ExceptionString) : FindFlatScreenViewState()
class StartFlat(val esateId:Int) : FindFlatScreenViewState() class StartFlat(val esateId:Int) : FindFlatScreenViewState()
class SomeError(val exception: ExceptionString) : FindFlatScreenViewState() class SomeError(val exception: ExceptionString) : FindFlatScreenViewState()
class RestoreView(val restore:RestoreModel) : FindFlatScreenViewState()
} }
\ No newline at end of file
...@@ -5,8 +5,10 @@ import android.view.View ...@@ -5,8 +5,10 @@ import android.view.View
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.biganto.visual.roompark.R import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.IBottomNavigation
import com.biganto.visual.roompark.util.view_utils.snackbar.SnackBarMessageType.* import com.biganto.visual.roompark.util.view_utils.snackbar.SnackBarMessageType.*
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
/** /**
...@@ -68,9 +70,27 @@ class SnackBarProvider @Inject constructor(val activity: Activity) : ISnackBarPr ...@@ -68,9 +70,27 @@ class SnackBarProvider @Inject constructor(val activity: Activity) : ISnackBarPr
override fun showSnackBar(message: String, type: SnackBarMessageType, length: Int) { override fun showSnackBar(message: String, type: SnackBarMessageType, length: Int) {
snack?.dismiss() snack?.dismiss()
snack = Snackbar snack = Snackbar
.make(parentView, message, length) .make(parentView, message, length)
.setAction(actionText(type)) {} // .setAction(actionText(type)) {}
.setActionTextColor(color(type)) // .setActionTextColor(color(type))
Timber.w(" act - ${activity} ")
Timber.w(" act - ${(activity as? IBottomNavigation)} ")
// (activity as? IBottomNavigation)?.let {bNAv ->
// Timber.w(" act $bNAv" )
// snack?.apply {
// Timber.w(" sna")
// view.layoutParams = (view.layoutParams as CoordinatorLayout.LayoutParams).apply {
// Timber.w(" fusc $this")
// setMargins(
// leftMargin,
// topMargin,
// rightMargin,
// 300
// )
// }
// }?.show()
// }
snack?.show() snack?.show()
} }
......
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="128dp"
android:height="148dp"
android:viewportWidth="128"
android:viewportHeight="148">
<path
android:name="path"
android:pathData="M 51.2 125.075 L 51.2 81.005 L 12.8 58.925 L 12.8 102.995 L 51.2 125.075 Z M 64 73.6 L 64 147.2 L 0 110.4 L 0 0 L 12.8 7.36 L 12.8 44.152 L 64 73.6 Z"
android:fillColor="#b4272d"
android:fillAlpha="0"
android:strokeWidth="1"/>
<path
android:name="path_1"
android:pathData="M 85.28 61.28 L 85.28 134.88 L 72.48 127.52 L 72.48 68.692 L 21.28 39.492 L 21.28 24.164 Z"
android:fillColor="#b4272d"
android:fillAlpha="0"
android:strokeWidth="1"/>
<path
android:name="path_2"
android:pathData="M 106.404 48.557 L 106.404 122.157 L 93.604 114.797 L 93.604 55.969 L 42.404 26.77 L 42.404 11.441"
android:fillColor="#b4272d"
android:fillAlpha="0"
android:strokeWidth="1"/>
<path
android:name="path_3"
android:pathData="M 127.37 36.24 L 127.37 109.84 L 114.57 102.48 L 114.57 43.652 L 63.37 14.452 L 63.37 -0.876 Z"
android:fillColor="#b4272d"
android:fillAlpha="0"
android:strokeWidth="1"/>
</vector>
</aapt:attr>
<target android:name="path">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="fillAlpha"
android:duration="250"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/accelerate_interpolator"/>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="1000"
android:duration="250"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:anim/decelerate_interpolator"/>
</set>
</aapt:attr>
</target>
<target android:name="path_1">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="178"
android:duration="343"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/linear_interpolator"/>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="1250"
android:duration="250"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:anim/decelerate_interpolator"/>
</set>
</aapt:attr>
</target>
<target android:name="path_2">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="453"
android:duration="378"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/linear_interpolator"/>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="1500"
android:duration="250"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:anim/decelerate_interpolator"/>
</set>
</aapt:attr>
</target>
<target android:name="path_3">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="646"
android:duration="438"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/linear_interpolator"/>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="1750"
android:duration="250"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:anim/decelerate_interpolator"/>
</set>
</aapt:attr>
</target>
</animated-vector>
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
android:top="5dp" android:top="5dp"
android:bottom="5dp" android:bottom="5dp"
android:src="@drawable/ic_bell_on" android:src="@drawable/ic_bell_on"
android:mipMap="true" android:mipMap="false"
android:gravity="start" android:gravity="start"
android:tintMode="screen" android:tintMode="screen"
android:tint="@color/colorPrimaryDark" /> android:tint="@color/colorPrimaryDark" />
......
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:width="http://schemas.android.com/apk/res-auto">
<item android:state_checked="false">
<layer-list>
<item>
<shape android:shape="rectangle"
android:dither="true"
android:useLevel="false">
<size android:width="48dp" android:height="24dp"/>
<corners android:radius="12dp"/>
</shape>
</item>
<item
android:right="8dp">
<shape
android:shape="ring"
android:width="14dp"
android:height="15dp"
android:gravity="end"
android:tint="@color/colorPrimaryDark" />
</item>
</layer-list>
</item>
<item android:state_checked="true">
<layer-list>
<item>
<shape android:shape="rectangle"
android:dither="true"
android:useLevel="false">
<size android:width="48dp" android:height="24dp"/>
<solid android:color="#FFFFFFFF"/>
<corners android:radius="12dp" />
</shape>
</item>
<item
android:left="8dp">
<shape
android:shape="ring"
android:right="27dp"
android:top="5dp"
android:bottom="5dp"
android:gravity="start"
android:tintMode="screen"
android:tint="@color/colorPrimaryDark"/>
</item>
</layer-list>
</item>
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="2dip" />
<solid android:color="#33000000" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<corners android:radius="2dip" />
<solid android:color="#ccFFFFFF" />
</shape>
</clip>
</item>
<item
android:id="@android:id/progress"
>
<clip>
<shape>
<corners
android:radius="2dip" />
<solid android:color="#FFFFFFFF" />
</shape>
</clip>
</item>
</layer-list>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1536dp"
android:height="1536dp"
android:viewportWidth="1536"
android:viewportHeight="1536">
<path
android:pathData="M0,0h1536v1536h-1536z"
android:fillColor="#FFFFFF"
android:strokeColor="#00000000"
android:fillAlpha="1"/>
<group>
<clip-path
android:pathData="M418,287L1118,287L1118,357L418,357ZM629.07,496.38L911.21,496.38L911.21,565L629.07,565ZM842,565L911.21,565L911.21,981L842,981ZM629.07,565L701,565L701,981L629.07,981ZM629.07,981L911.21,981L911.21,1050.63L629.07,1050.63ZM418,357L488,357L488,1050.63L418,1050.63ZM418,1050.63L701.25,1050.63L701.25,1120.75L418,1120.75ZM1049,357L1118,357L1118,1190L1049,1190ZM628.33,1120.75L701.25,1120.75L701.25,1190L628.33,1190ZM628.33,1190L1118,1190L1118,1257L628.33,1257ZM628.33,1257L697.34,1257L697.34,1536L628.33,1536ZM628.33,1257"/>
<path
android:pathData="M413,282L1123,282L1123,1541L413,1541ZM413,282"
android:fillColor="#227F79"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</group>
</vector>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector
android:width="108dp"
android:height="108dp" android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108" android:viewportWidth="108"
android:viewportHeight="108"> xmlns:android="http://schemas.android.com/apk/res/android">
<path <path android:fillColor="#3DDC84"
android:fillColor="#008577" android:pathData="M0,0h108v108h-108z"/>
android:pathData="M0,0h108v108h-108z" /> <path android:fillColor="#00000000" android:pathData="M9,0L9,108"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:pathData="M9,0L9,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M19,0L19,108" <path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M59,0L59,108"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:pathData="M29,0L29,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M39,0L39,108" <path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,9L108,9"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:pathData="M49,0L49,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M59,0L59,108" <path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,59L108,59"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:pathData="M69,0L69,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M79,0L79,108" <path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,29L89,29"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:pathData="M89,0L89,108" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M99,0L99,108" <path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,79L89,79"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:pathData="M0,9L108,9" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8" <path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" /> android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path <path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:fillColor="#00000000" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:pathData="M0,19L108,19" <path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M69,19L69,89"
<path android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:fillColor="#00000000" <path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:pathData="M0,29L108,29" android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector> </vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1536dp"
android:height="1536dp"
android:viewportWidth="1536"
android:viewportHeight="1536">
<path
android:pathData="M0,0h1536v1536h-1536z"
android:fillColor="#FFFFFF"
android:strokeColor="#00000000"
android:fillAlpha="1"/>
<group>
<clip-path
android:pathData="M418,287L1118,287L1118,357L418,357ZM629.07,496.38L911.21,496.38L911.21,565L629.07,565ZM842,565L911.21,565L911.21,981L842,981ZM629.07,565L701,565L701,981L629.07,981ZM629.07,981L911.21,981L911.21,1050.63L629.07,1050.63ZM418,357L488,357L488,1050.63L418,1050.63ZM418,1050.63L701.25,1050.63L701.25,1120.75L418,1120.75ZM1049,357L1118,357L1118,1190L1049,1190ZM628.33,1120.75L701.25,1120.75L701.25,1190L628.33,1190ZM628.33,1190L1118,1190L1118,1257L628.33,1257ZM628.33,1257L697.34,1257L697.34,1536L628.33,1536ZM628.33,1257"/>
<path
android:pathData="M413,282L1123,282L1123,1541L413,1541ZM413,282"
android:fillColor="#227F79"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="178dp"
android:height="91dp"
android:viewportWidth="178"
android:viewportHeight="91">
<group>
<clip-path android:pathData="M73,0L105,0L105,3.156L73,3.156ZM82.617,9.531L95.535,9.531L95.535,12.734L82.617,12.734ZM92.375,12.734L95.535,12.734L95.535,31.781L92.375,31.781ZM82.617,12.734L85.953,12.734L85.953,31.781L82.617,31.781ZM82.617,31.781L95.535,31.781L95.535,34.879L82.617,34.879ZM73,3.156L76.156,3.156L76.156,34.879L73,34.879ZM73,34.879L85.922,34.879L85.922,38.145L73,38.145ZM101.844,3.156L105,3.156L105,41.25L101.844,41.25ZM82.613,38.145L85.922,38.145L85.922,41.25L82.613,41.25ZM82.613,41.25L105,41.25L105,44.379L82.613,44.379ZM82.613,44.379L85.77,44.379L85.77,57.145L82.613,57.145ZM82.613,44.379 M 0,0"/>
<path
android:pathData="M68,-5L110,-5L110,62.145L68,62.145ZM68,-5"
android:fillColor="#40A19B"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</group>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/download_container"
android:orientation="vertical">
<ImageView
android:id="@+id/backgroundDownloader"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#A62B2727"
android:scaleType="centerCrop"
android:clickable="true"
android:focusableInTouchMode="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/downloadingTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="32dp" android:layout_marginStart="32dp" android:gravity="center_horizontal"
android:textAlignment="center"
style="@style/NoticeTextDownloader"
app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="32dp" android:layout_marginBottom="32dp"
app:layout_constraintBottom_toBottomOf="@+id/backgroundDownloader"
app:layout_constraintVertical_bias="0.32999998"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tourToDownloadTitle"
app:layout_constraintTop_toBottomOf="@+id/downloadingTitle" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="32dp" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="32dp" style="@style/TitleTextDownloader"/>
<ProgressBar
android:id="@+id/downloadProgress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="4dp"
android:layout_marginStart="32dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="32dp"
android:progressDrawable="@drawable/horizontal_progress_downloader"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tourToDownloadTitle" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/cancelDownloadButton"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
app:layout_constraintTop_toBottomOf="@+id/downloadProgress" android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="32dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="32dp"
android:layout_marginBottom="32dp" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.24000001" style="@style/CancelTextDownloader"
android:padding="8dp"
android:paddingStart="8dp" android:paddingEnd="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
...@@ -4,7 +4,11 @@ ...@@ -4,7 +4,11 @@
android:layout_width="284dp" android:layout_width="284dp"
android:layout_height="189dp" android:layout_height="189dp"
android:layout_margin="16dp" android:layout_margin="16dp"
app:cardElevation="0dp" app:cardElevation="8dp"
app:cardMaxElevation="12dp"
android:padding="16dp"
app:cardCornerRadius="4dp"
app:cardForegroundColor="#00000000" app:cardForegroundColor="#00000000"
app:cardPreventCornerOverlap="false" app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="false"> app:cardUseCompatPadding="false">
......
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frameContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".player.BigantoPlayerActivity">
<FrameLayout
android:id="@+id/unityFrameContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
</FrameLayout>
<FrameLayout
android:id="@+id/previewBack"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
</FrameLayout>
<ImageView
android:id="@+id/playerPreview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@color/colorPrimary"
android:visibility="invisible" />
<ImageView
android:id="@+id/bigantoLogo"
android:layout_width="148dp"
android:layout_height="128dp"
android:scaleType="fitCenter"
android:layout_gravity="center"
/>
<ProgressBar
android:id="@+id/loadingBar"
style="@style/BigantoProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_margin="16dp" />
</FrameLayout>
\ No newline at end of file
...@@ -120,13 +120,14 @@ ...@@ -120,13 +120,14 @@
<com.google.android.material.bottomnavigation.BottomNavigationView <com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation_view" android:id="@+id/bottom_navigation_view"
android:background="@color/colorOpacityCardBackground"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="@dimen/bottom_navigation_height"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
android:visibility="gone" android:visibility="gone"
app:elevation="0dp" app:elevation="0dp"
app:itemBackground="@color/colorCommonBackground" app:itemBackground="@color/colorPrimary"
app:itemHorizontalTranslationEnabled="false" app:itemHorizontalTranslationEnabled="false"
app:itemIconTint="@drawable/bottom_navigation_icon_selector" app:itemIconTint="@drawable/bottom_navigation_icon_selector"
app:labelVisibilityMode="unlabeled" app:labelVisibilityMode="unlabeled"
......
...@@ -35,7 +35,6 @@ ...@@ -35,7 +35,6 @@
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:includeFontPadding="false" android:includeFontPadding="false"
android:text="Дом №1"
android:textAlignment="center" /> android:textAlignment="center" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
...@@ -49,7 +48,6 @@ ...@@ -49,7 +48,6 @@
android:gravity="center" android:gravity="center"
android:includeFontPadding="false" android:includeFontPadding="false"
android:maxLines="1" android:maxLines="1"
android:text="14 декабря 2019г"
android:textAlignment="center" /> android:textAlignment="center" />
<FrameLayout <FrameLayout
......
...@@ -4,9 +4,11 @@ ...@@ -4,9 +4,11 @@
android:layout_width="142dp" android:layout_width="142dp"
android:layout_height="94dp" android:layout_height="94dp"
android:layout_margin="16dp" android:layout_margin="16dp"
android:padding="8dp"
app:cardCornerRadius="4dp"
app:cardElevation="4dp" app:cardElevation="4dp"
app:cardMaxElevation="8dp"
app:cardForegroundColor="#00000000" app:cardForegroundColor="#00000000"
app:cardMaxElevation="6dp"
app:cardPreventCornerOverlap="false" app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="false" app:cardUseCompatPadding="false"
app:contentPadding="8dp"> app:contentPadding="8dp">
...@@ -34,7 +36,6 @@ ...@@ -34,7 +36,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:text="Дом №1"
android:textAlignment="center" /> android:textAlignment="center" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
...@@ -47,7 +48,6 @@ ...@@ -47,7 +48,6 @@
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:gravity="center" android:gravity="center"
android:maxLines="1" android:maxLines="1"
android:text="14 декабря 2019г"
android:textAlignment="center" /> android:textAlignment="center" />
<FrameLayout <FrameLayout
......
...@@ -37,7 +37,8 @@ ...@@ -37,7 +37,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:background="@color/colorOpacityBackground"> android:background="@color/colorOpacityBackground"
>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -69,6 +70,7 @@ ...@@ -69,6 +70,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:baselineAligned="false" android:baselineAligned="false"
android:gravity="center_vertical|end" /> android:gravity="center_vertical|end" />
......
...@@ -99,6 +99,7 @@ ...@@ -99,6 +99,7 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/restore_pass_button"
style="@style/AuthButton.Restore" style="@style/AuthButton.Restore"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="40dp" android:layout_height="40dp"
......
...@@ -10,9 +10,11 @@ ...@@ -10,9 +10,11 @@
android:id="@+id/bellSwitcherTitle" android:id="@+id/bellSwitcherTitle"
style="@style/Common_Text.Default" style="@style/Common_Text.Default"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1" android:layout_weight="1"
android:gravity="start|center_vertical" android:gravity="start|center_vertical"
android:includeFontPadding="false"
android:text="блабла" /> android:text="блабла" />
<include <include
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:minHeight="80dp"
android:orientation="vertical" android:orientation="vertical"
tools:itemCount="1" tools:itemCount="1"
tools:listitem="@layout/photo_preview_viewholder" /> tools:listitem="@layout/photo_preview_viewholder" />
......
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:text="КВАРТИРА\n№452" /> android:text="\n" />
<include <include
layout="@layout/horizontal_divider" layout="@layout/horizontal_divider"
...@@ -139,8 +139,7 @@ ...@@ -139,8 +139,7 @@
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_weight="1" android:layout_weight="1"
android:gravity="bottom|end" android:gravity="bottom|end"
android:includeFontPadding="false" android:includeFontPadding="false" />
android:text="5 165 301 ₽" />
</LinearLayout> </LinearLayout>
...@@ -173,8 +172,7 @@ ...@@ -173,8 +172,7 @@
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_weight="1" android:layout_weight="1"
android:gravity="bottom|end" android:gravity="bottom|end"
android:includeFontPadding="false" android:includeFontPadding="false" />
android:text="1 332 543 ₽" />
</LinearLayout> </LinearLayout>
...@@ -207,8 +205,7 @@ ...@@ -207,8 +205,7 @@
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_weight="1" android:layout_weight="1"
android:gravity="bottom|end" android:gravity="bottom|end"
android:includeFontPadding="false" android:includeFontPadding="false" />
android:text="32 543 ₽" />
</LinearLayout> </LinearLayout>
<include <include
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:text="КВАРТИРА\n№452" /> android:text="\n" />
<include <include
layout="@layout/horizontal_divider" layout="@layout/horizontal_divider"
...@@ -138,8 +138,7 @@ ...@@ -138,8 +138,7 @@
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_weight="1" android:layout_weight="1"
android:gravity="bottom|end" android:gravity="bottom|end"
android:includeFontPadding="false" android:includeFontPadding="false" />
android:text="5 165 301 ₽" />
</LinearLayout> </LinearLayout>
<include <include
...@@ -178,8 +177,7 @@ ...@@ -178,8 +177,7 @@
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_weight="1" android:layout_weight="1"
android:gravity="bottom|end" android:gravity="bottom|end"
android:includeFontPadding="false" android:includeFontPadding="false" />
android:text="1 332 543 ₽" />
</LinearLayout> </LinearLayout>
...@@ -219,8 +217,7 @@ ...@@ -219,8 +217,7 @@
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_weight="1" android:layout_weight="1"
android:gravity="bottom|end" android:gravity="bottom|end"
android:includeFontPadding="false" android:includeFontPadding="false" />
android:text="32 543 ₽" />
</LinearLayout> </LinearLayout>
......
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="284dp"
android:layout_height="189dp"
android:layout_margin="16dp"
app:cardElevation="4dp"
app:cardMaxElevation="8dp"
app:cardForegroundColor="#00000000"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="false">
<com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
android:id="@+id/preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:background="@drawable/default_image_placeholder"
android:cropToPadding="true"
android:foreground="@color/colorOpacityBackground"
android:scaleType="centerCrop"
app:image_corner_radius="4dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/card_title"
style="@style/Header_TextView.Inverted_Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="64dp"
android:layout_marginBottom="4dp"
android:text="Дом №1"
android:textAlignment="center" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView6"
style="@style/Common_Text.Inverted"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Последнее обновление"
android:textAlignment="center" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/card_updated"
style="@style/Accent_Minor_TextView.DatePlaceHolder"
android:layout_width="match_parent"
android:layout_height="24dp"
android:layout_marginStart="64dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="64dp"
android:gravity="center"
android:maxLines="1"
android:text="14 декабря 2019г"
android:textAlignment="center" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
\ No newline at end of file
...@@ -18,17 +18,16 @@ ...@@ -18,17 +18,16 @@
android:padding="16dp"> android:padding="16dp">
<FrameLayout <FrameLayout
android:id="@+id/feed_read" android:id="@+id/avaliable_status"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:background="@drawable/new_feed_icon" android:background="@drawable/available_status"
android:backgroundTint="@color/colorAccent"
android:visibility="visible" android:visibility="visible"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/feed_date_text_view3" android:id="@+id/avaliable_text"
style="@style/LiteText.Accent" style="@style/LiteText.Accent"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
...@@ -38,7 +37,7 @@ ...@@ -38,7 +37,7 @@
android:visibility="visible" android:visibility="visible"
app:layout_constraintBottom_toBottomOf="@+id/feed_read" app:layout_constraintBottom_toBottomOf="@+id/feed_read"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/feed_read" app:layout_constraintStart_toEndOf="@+id/avaliable_status"
app:layout_constraintTop_toTopOf="@+id/feed_read" /> app:layout_constraintTop_toTopOf="@+id/feed_read" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
...@@ -47,10 +46,10 @@ ...@@ -47,10 +46,10 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="КВАРТИРА\n№ 452" android:text="\n"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/feed_date_text_view3" /> app:layout_constraintTop_toBottomOf="@+id/avaliable_text" />
<include <include
android:id="@+id/header_divider" android:id="@+id/header_divider"
......
...@@ -74,7 +74,6 @@ ...@@ -74,7 +74,6 @@
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:includeFontPadding="false" android:includeFontPadding="false"
android:text="В «РУМЯНЦЕВО-ПАРК» ПРИСТУПИЛИ К МОНТАЖУ ОКОННЫХ БЛОКОВ"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline" app:layout_constraintStart_toStartOf="@+id/guideline"
......
...@@ -44,7 +44,6 @@ ...@@ -44,7 +44,6 @@
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:includeFontPadding="false" android:includeFontPadding="false"
android:text="22 / 02 / 2019"
android:visibility="visible" android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/feed_read" app:layout_constraintStart_toEndOf="@+id/feed_read"
...@@ -58,7 +57,6 @@ ...@@ -58,7 +57,6 @@
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:includeFontPadding="false" android:includeFontPadding="false"
android:text="В «РУМЯНЦЕВО-ПАРК» ПРИСТУПИЛИ К МОНТАЖУ ОКОННЫХ БЛОКОВ"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline" app:layout_constraintStart_toStartOf="@+id/guideline"
......
...@@ -29,8 +29,7 @@ ...@@ -29,8 +29,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingEnd="16dp" android:paddingEnd="16dp" />
android:text="В ЖК «РУМЯНЦЕВО-ПАРК» ИПОТЕЧНАЯ СТАВКА - 6,5%" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/articleBodyRecyclerView" android:id="@+id/articleBodyRecyclerView"
......
...@@ -45,7 +45,6 @@ ...@@ -45,7 +45,6 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:includeFontPadding="false" android:includeFontPadding="false"
android:text="22 / 02 / 2019"
android:visibility="visible" android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
...@@ -61,7 +60,6 @@ ...@@ -61,7 +60,6 @@
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:includeFontPadding="false" android:includeFontPadding="false"
android:maxLines="3" android:maxLines="3"
android:text="В «РУМЯНЦЕВО-ПАРК» ПРИСТУПИЛИ К МОН ТАЖУ dadasdasd a 22ЫХ БЛОКОВ"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/guideline" app:layout_constraintStart_toEndOf="@+id/guideline"
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
android:id="@+id/info_ceil_header" android:id="@+id/info_ceil_header"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Цена за м²" /> />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/Common_Text.Default" style="@style/Common_Text.Default"
...@@ -17,5 +17,5 @@ ...@@ -17,5 +17,5 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="165 301 ₽" /> />
</LinearLayout> </LinearLayout>
\ No newline at end of file
...@@ -52,28 +52,26 @@ ...@@ -52,28 +52,26 @@
app:layout_constraintGuide_end="80dp" /> app:layout_constraintGuide_end="80dp" />
<ImageView <ImageView
android:id="@+id/show_full_button" android:id="@+id/share_image_button"
android:layout_width="24dp" android:layout_width="40dp"
android:layout_height="24dp" android:layout_height="0dp"
android:layout_marginStart="16dp" android:layout_marginStart="8dp"
android:layout_marginTop="16dp" android:layout_marginTop="12dp"
app:layout_constraintBottom_toBottomOf="parent" android:padding="8dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline2" app:layout_constraintTop_toTopOf="@+id/guideline2"
app:layout_constraintVertical_bias="0.0" app:srcCompat="@drawable/ic_share" />
app:srcCompat="@drawable/iic_full_view" />
<ImageView <ImageView
android:id="@+id/choose_size_button" android:id="@+id/choose_size_button"
android:layout_width="24dp" android:layout_width="40dp"
android:layout_height="24dp" android:layout_height="0dp"
android:layout_marginTop="16dp" android:layout_marginTop="12dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent" android:padding="8dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline2" app:layout_constraintTop_toTopOf="@+id/guideline2"
app:layout_constraintVertical_bias="0.0" app:srcCompat="@drawable/ic_ratio" />
app:srcCompat="@drawable/iic_full_view"/>
<com.biganto.visual.roompark.presentation.screen.photo.util.PhotoPreviewSlider <com.biganto.visual.roompark.presentation.screen.photo.util.PhotoPreviewSlider
android:id="@+id/photosPreviewSlider" android:id="@+id/photosPreviewSlider"
...@@ -87,7 +85,7 @@ ...@@ -87,7 +85,7 @@
android:visibility="invisible" android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/choose_size_button" app:layout_constraintEnd_toStartOf="@+id/choose_size_button"
app:layout_constraintStart_toEndOf="@+id/show_full_button" app:layout_constraintStart_toEndOf="@+id/share_image_button"
app:layout_constraintTop_toTopOf="@+id/guideline2" app:layout_constraintTop_toTopOf="@+id/guideline2"
app:layout_constraintVertical_bias="0.0" /> app:layout_constraintVertical_bias="0.0" />
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#000000"> android:background="#FF000000">
<com.github.chrisbanes.photoview.PhotoView <com.github.chrisbanes.photoview.PhotoView
...@@ -22,16 +22,18 @@ ...@@ -22,16 +22,18 @@
<ImageView <ImageView
android:id="@+id/close_current_button" android:id="@+id/close_current_button"
android:layout_width="32dp" android:layout_width="56dp"
android:layout_height="32dp" android:layout_height="64dp"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_marginTop="16dp" android:layout_marginTop="0dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="0dp"
android:clickable="true" android:clickable="true"
android:contentDescription="@string/content_description_close" android:contentDescription="@string/content_description_close"
android:focusable="true" android:focusable="true"
android:focusableInTouchMode="true" android:focusableInTouchMode="false"
android:padding="16dp"
android:paddingEnd="8dp"
android:scaleType="fitXY" android:scaleType="fitXY"
android:src="@drawable/ic_close_circled" /> android:src="@drawable/ic_close_circled" />
......
...@@ -22,8 +22,7 @@ ...@@ -22,8 +22,7 @@
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:orientation="vertical" android:orientation="vertical">
android:text="Договор готовится для подачи на гос. регистрацию b,kf,kf,fk">
</com.google.android.material.textview.MaterialTextView> </com.google.android.material.textview.MaterialTextView>
</LinearLayout> </LinearLayout>
\ No newline at end of file
...@@ -56,9 +56,7 @@ ...@@ -56,9 +56,7 @@
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:layout_weight="1" android:layout_weight="1"
android:gravity="start|center_vertical" android:gravity="start|center_vertical"
android:text="Скачать карточки моих android:text="@string/download_all_plan_types_settings_with_sizes" />
квартир из избранного
и сделок (4 MB)" />
<ImageView <ImageView
android:id="@+id/downloadFlatCardsIcon" android:id="@+id/downloadFlatCardsIcon"
...@@ -89,9 +87,7 @@ ...@@ -89,9 +87,7 @@
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:layout_weight="1" android:layout_weight="1"
android:gravity="start|center_vertical" android:gravity="start|center_vertical"
android:text="Скачать виртуальные туры android:text="@string/download_all_tours_settings_with_size" />
моих квартир из избранного
и сделок (477 MB)" />
<ImageView <ImageView
android:id="@+id/downloadToursIcon" android:id="@+id/downloadToursIcon"
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:background="@drawable/available_status" android:background="@drawable/available_status"
android:fitsSystemWindows="true"
android:visibility="visible" android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
...@@ -27,9 +26,8 @@ ...@@ -27,9 +26,8 @@
style="@style/Accent_Minor_TextView" style="@style/Accent_Minor_TextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:fitsSystemWindows="true"
android:text="СВОБОДНА"
app:layout_constraintBottom_toBottomOf="@+id/status_icon" app:layout_constraintBottom_toBottomOf="@+id/status_icon"
app:layout_constraintStart_toEndOf="@+id/status_icon" app:layout_constraintStart_toEndOf="@+id/status_icon"
app:layout_constraintTop_toTopOf="@+id/status_icon" /> app:layout_constraintTop_toTopOf="@+id/status_icon" />
...@@ -44,7 +42,6 @@ ...@@ -44,7 +42,6 @@
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:ellipsize="end" android:ellipsize="end"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
android:text="СВОБОДНА"
app:layout_constraintBottom_toBottomOf="@+id/status_icon" app:layout_constraintBottom_toBottomOf="@+id/status_icon"
app:layout_constraintEnd_toStartOf="@+id/back_cross" app:layout_constraintEnd_toStartOf="@+id/back_cross"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
...@@ -53,13 +50,11 @@ ...@@ -53,13 +50,11 @@
<ImageView <ImageView
android:id="@+id/back_cross" android:id="@+id/back_cross"
android:layout_width="24dp" android:layout_width="48dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_marginTop="4dp" android:layout_marginEnd="0dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
android:scaleType="centerCrop" android:scaleType="fitCenter"
android:tint="@color/colorGray" android:tint="@color/colorGray"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
......
...@@ -35,13 +35,12 @@ ...@@ -35,13 +35,12 @@
android:includeFontPadding="true" android:includeFontPadding="true"
android:textAlignment="gravity" android:textAlignment="gravity"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/include13" app:layout_constraintEnd_toStartOf="@+id/subsciption_switcher"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/back_button_chevron" app:layout_constraintStart_toEndOf="@id/back_button_chevron"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<include <include
android:id="@+id/include13"
layout="@layout/bell_switch_view" layout="@layout/bell_switch_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
......
...@@ -12,8 +12,7 @@ ...@@ -12,8 +12,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:gravity="start|center_vertical" android:gravity="start|center_vertical" />
android:text="блабла" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/descriptionText" android:id="@+id/descriptionText"
......
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/tour_preview_imageView"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_bell_on" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tour_name"
style="@style/Common_Text.Notice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:ellipsize="end"
android:text="Тур"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tour_status_imageView"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/tour_preview_imageView"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/tour_status_imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="@+id/tour_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/tour_name"
app:srcCompat="@drawable/ic_back" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/playTourCardOpacityLight"
android:backgroundTintMode="src_atop"
android:clickable="true"
android:focusable="true">
<com.google.android.material.card.MaterialCardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
app:cardElevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.32999998">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/toursList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_marginBottom="8dp"
android:orientation="vertical"
tools:itemCount="3"
tools:listitem="@layout/tour_chooser_viewholder">
</androidx.recyclerview.widget.RecyclerView>
</com.google.android.material.card.MaterialCardView>
<ImageView
android:id="@+id/close_current_button"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:clickable="false"
android:contentDescription="@string/content_description_close"
android:focusable="false"
android:scaleType="fitXY"
android:src="@drawable/ic_close_circled"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/playTourCardOpacityLight"
android:backgroundTintMode="src_atop"
android:clickable="true"
android:focusable="true">
<com.google.android.material.card.MaterialCardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
app:cardElevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.32999998">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/download_dialog_title"
style="@style/Default_TextView.Header_Text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/download_all_tours_notice_text"
android:textAlignment="center" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/all_tours_size_textView"
style="@style/Default_TextView.Notice_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:text="@string/download_all_tours_size_title" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/disk_size_textView"
style="@style/Default_TextView.Notice_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:text="@string/download_all_tours_disk_size_title" />
<LinearLayout
android:id="@+id/buttons_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2">
<Button
android:id="@+id/alert_ok_button"
style="@style/DefaultButton.DismissButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/download_all_tours_yes_action" />
<Button
android:id="@+id/alert_dismiss_button"
style="@style/DefaultButton.DismissButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/download_all_tours_cancel_action"
android:visibility="visible" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<ImageView
android:id="@+id/close_current_button"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:clickable="false"
android:contentDescription="@string/content_description_close"
android:focusable="false"
android:scaleType="fitXY"
android:src="@drawable/ic_close_circled"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" /> <background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" /> <background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>
\ No newline at end of file
...@@ -16,5 +16,6 @@ ...@@ -16,5 +16,6 @@
<item name="colorControlNormal">@color/colorAccent</item> <item name="colorControlNormal">@color/colorAccent</item>
<item name="android:statusBarColor">@color/colorPrimary</item> <item name="android:statusBarColor">@color/colorPrimary</item>
<item name="android:windowLightStatusBar">true</item> <item name="android:windowLightStatusBar">true</item>
<item name="snackbarStyle">@style/Widget.RoomPark.Snackbar</item>
</style> </style>
</resources> </resources>
\ No newline at end of file
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
<string name="api_error_2001">Тур не найден!</string> <string name="api_error_2001">Тур не найден!</string>
<string name="api_error_default">Неизвестная ошибка сервера!</string> <string name="api_error_default">Неизвестная ошибка сервера!</string>
<string name="no_network_error">No network!</string> <string name="no_network_error">Нет сети!</string>
<string name="unknown_error">Unexpected error!</string> <string name="unknown_error">Unexpected error!</string>
<string name="cant_load_item_error">Can\'t load item!</string> <string name="cant_load_item_error">Can\'t load item!</string>
<string name="object_not_found_error">Object not found!</string> <string name="object_not_found_error">Object not found!</string>
...@@ -91,6 +91,56 @@ ...@@ -91,6 +91,56 @@
<string name="estate_type_other_short">ОБ.%s</string> <string name="estate_type_other_short">ОБ.%s</string>
<string name="tour_not_allowed">Запуск туров пока недоступен!</string> <string name="tour_not_allowed">Запуск туров пока недоступен!</string>
<string name="content_description_close">Close</string> <string name="content_description_close">Close</string>
<string name="share_house_photo">Поделиться изображением</string>
<string name="unsubscribe_error_message">При попытке отписаться произошла ошибка!</string>
<string name="subscribe_error_message">Ошибка! Подписаться не удалось!</string>
<!--endregion--> <!--endregion-->
<string name="download_tour_title_text">Скачивается тур</string>
<string name="download_tour_cancel_text">Отмена</string>
<!-- region notifications-->
<string name="notification_content_title">Румянцево Парк</string>
<string name="notification_content_text">Загрузчик</string>
<string name="state_downloaded_notify_message">загружен</string>
<string name="state_crushed_notify_message">произошла ошибка</string>
<string name="state_suspended_notify_message">загрузка приостановлена</string>
<string name="state_downloading_notify_message">загружается</string>
<string name="state_else_notify_message" />
<!--endregion-->
<!-- region notifications-->
<string name="push_notification_content_title">Уведомление!</string>
<!--endregion-->
<string name="on_all_tours_downloaded_notification_message">Загрузка туров завершена</string>
<string name="on_all_tours_deleted_notification_message">Удаление туров завершено</string>
<string name="noty_tours_delete_left">Осталось удалить: %d%n</string>
<string name="noty_tours_download_left">Осталось загрузить: %d%n</string>
<string name="game_view_content_description" />
<string name="download_all_tours_settings_with_size">Скачать виртуальные туры моих квартир из избранного и сделок</string>
<string name="download_all_plan_types_settings_with_sizes">Скачать карточки моих квартир из избранного и сделок</string>
<string name="plan_types_download_completed">Загрузка планов завершена.</string>
<string name="download_plan_types_screen_title">Cкачиваются планировки</string>
<string name="download_all_tours_notice_text">Загрузить все туры?</string>
<string name="download_all_plans_notice_text">Загрузить все \n планировки?</string>
<string name="download_all_tours_size_title">Размер всех туров: %s</string>
<string name="download_all_plans_size_title">Размер всех планировок: %s</string>
<string name="download_all_tours_disk_size_title">Размер свободного места на диске: %s</string>
<string name="download_all_tours_yes_action">Скачать</string>
<string name="download_all_tours_cancel_action">Отмена</string>
<string name="download_all_tours_errors_snackbar_message">Ошибка при попытке загрузить туры!</string>
<string name="download_all_plans_errors_snackbar_message">Ошибка при попытке загрузить планировки!</string>
<string name="download_all_tours_start_snackbar_message">Загрузка туров началась!</string>
</resources> </resources>
<resources> <resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> <style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
...@@ -15,6 +15,17 @@ ...@@ -15,6 +15,17 @@
<item name="colorControlHighlight">@color/colorAccent</item> <item name="colorControlHighlight">@color/colorAccent</item>
<item name="colorControlNormal">@color/colorAccent</item> <item name="colorControlNormal">@color/colorAccent</item>
<item name="android:statusBarColor">@color/colorPrimary</item> <item name="android:statusBarColor">@color/colorPrimary</item>
<item name="snackbarStyle">@style/Widget.RoomPark.Snackbar</item>
</style>
<style name="Widget.RoomPark.Snackbar" parent="Widget.MaterialComponents.Snackbar" tools:override="true">
<item name="android:layout_margin">@null</item>
<!-- Use default Snackbar margins for top/left/right -->
<item name="android:layout_marginTop" tools:ignore="PrivateResource">@dimen/mtrl_snackbar_margin</item>
<item name="android:layout_marginLeft" tools:ignore="PrivateResource">@dimen/mtrl_snackbar_margin</item>
<item name="android:layout_marginRight" tools:ignore="PrivateResource">@dimen/mtrl_snackbar_margin</item>
<!-- Custom bottom margin, this could work for top/left/right too -->
<item name="android:layout_marginBottom">@dimen/bottom_navigation_height</item>
</style> </style>
<style name="Widget.MainTextInputStyle" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox"> <style name="Widget.MainTextInputStyle" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
...@@ -431,4 +442,28 @@ ...@@ -431,4 +442,28 @@
//endregion //endregion
<style name="NoticeTextDownloader" parent="Default_TextView">
<item name="android:textColor">@color/colorNoticeText</item>
<item name="android:text">@string/download_tour_title_text</item>
</style>
<style name="TitleTextDownloader" parent="Default_TextView">
<item name="android:textColor">@color/colorInvertedText</item>
</style>
<style name="BubbleTitleText" parent="Header_TextView.Main_Header">
</style>
<style name="CancelTextDownloader" parent="Default_TextView">
<item name="android:textColor">@color/colorError</item>
<item name="android:text">@string/download_tour_cancel_text</item>
</style>
<style name="BigantoProgressBar">
<item name="android:layout_width">@dimen/default_icon_width</item>
<item name="android:layout_height">@dimen/default_icon_height</item>
<item name="android:indeterminateTint">@color/colorPrimary</item>
</style>
</resources> </resources>
...@@ -14,6 +14,9 @@ buildscript { ...@@ -14,6 +14,9 @@ buildscript {
classpath "com.android.tools.build:gradle:3.6.1" classpath "com.android.tools.build:gradle:3.6.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
} }
......
...@@ -5,8 +5,8 @@ ext { ...@@ -5,8 +5,8 @@ ext {
minSdkVersion_RoomPark = 23 minSdkVersion_RoomPark = 23
compileSdkVersion_RoomPark = 28 compileSdkVersion_RoomPark = 28
VERSION_CODE = 1 VERSION_CODE = 9
VERSION_NAME = "0.0.1" VERSION_NAME = "0.9.4"
// supportLibraryVersion = '1.1.0-alpha05' // supportLibraryVersion = '1.1.0-alpha05'
constrainLayoutVersion = '1.1.3' constrainLayoutVersion = '1.1.3'
...@@ -27,4 +27,7 @@ ext { ...@@ -27,4 +27,7 @@ ext {
photoViewVersion = "2.0.0" photoViewVersion = "2.0.0"
viewPager2Version = "1.0.0" viewPager2Version = "1.0.0"
glideVersion = "4.11.0" glideVersion = "4.11.0"
fireBaseVersion = "17.2.3"
firebaseCrashlyticsVersion = "17.0.0-beta02"
rxKotlinVersion = "2.4.0"
} }
\ No newline at end of file
...@@ -6,16 +6,21 @@ ...@@ -6,16 +6,21 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html # http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true # org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK kotlin.code.style=official
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.enableD8=true
# org.gradle.jvmargs=-Xmx1536m
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.caching=true
android.enableBuildScriptClasspathCheck=false
android.useAndroidX=true android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
Room Park official Android application Room Park official Android application
- - v.0.0.1 - - - - v.0.9.4 - -
# 0.9.4 --> 0.9.2
* global urls support
* update to untiy-mobile master (1.1.3)
* proguard config
# 0.9.2 --> 0.9.1
* universal untiy-mobile building support
* fix resotre url button
Pre-release version
cheers by Vladislav Bogdashkin cheers by Vladislav Bogdashkin
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment