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

Merge branch 'release/0.8.0'

parents 6f48b8a2 638c75f4
*.iml *.iml
.gradle .gradle
/local.properties /local.properties
/.idea
/.idea/caches /.idea/caches
/.idea/libraries /.idea/libraries
/.idea/modules.xml /.idea/modules.xml
......
Room Park
\ No newline at end of file
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
<dictionary name="bogdashkin"> <dictionary name="bogdashkin">
<words> <words>
<w>Biganto</w> <w>Biganto</w>
<w>snackbar</w>
<w>upsert</w>
</words> </words>
</dictionary> </dictionary>
</component> </component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="delegatedBuild" value="false" />
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules"> <option name="modules">
...@@ -12,7 +15,6 @@ ...@@ -12,7 +15,6 @@
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" /> <option name="resolveModulePerSourceSet" value="false" />
<option name="testRunner" value="PLATFORM" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>
......
...@@ -148,10 +148,25 @@ ...@@ -148,10 +148,25 @@
</profile-state> </profile-state>
</entry> </entry>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">
<option name="id" value="Android" /> <option name="id" value="Android" />
</component> </component>
<component name="masterDetails">
<states>
<state key="ScopeChooserConfigurable.UI">
<settings>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.19305019" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project> </project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RenderSettings">
<option name="showDecorations" value="true" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="" vcs="Git" />
</component> </component>
</project> </project>
\ No newline at end of file
...@@ -8,6 +8,12 @@ apply plugin: 'kotlin-android-extensions' ...@@ -8,6 +8,12 @@ apply plugin: 'kotlin-android-extensions'
apply from: '../dependencies.gradle' apply from: '../dependencies.gradle'
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'
android { android {
...@@ -15,7 +21,7 @@ android { ...@@ -15,7 +21,7 @@ android {
defaultConfig { defaultConfig {
applicationId $APPLICATION_ID applicationId $APPLICATION_ID
ndk { ndk {
abiFilters 'armeabi-v7a', 'x86' abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86'
} }
minSdkVersion minSdkVersion_RoomPark minSdkVersion minSdkVersion_RoomPark
targetSdkVersion targetSdkVersion_RoomPark targetSdkVersion targetSdkVersion_RoomPark
...@@ -40,6 +46,7 @@ android { ...@@ -40,6 +46,7 @@ android {
androidExtensions { androidExtensions {
experimental = true experimental = true
} }
configurations.all { configurations.all {
resolutionStrategy.force "com.bluelinelabs:conductor:$conductorVersion" resolutionStrategy.force "com.bluelinelabs:conductor:$conductorVersion"
} }
...@@ -64,14 +71,9 @@ dependencies { ...@@ -64,14 +71,9 @@ dependencies {
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs') implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation 'androidx.core:core-ktx:1.0.2' implementation 'androidx.core:core-ktx:1.1.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
//Koin implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.13.0" // JVM dependency
implementation "org.koin:koin-core:$koinVersion"
implementation "org.koin:koin-core-ext:$koinVersion"
implementation "org.koin:koin-android:$koinVersion"
implementation "org.koin:koin-android-scope:$koinVersion"
//Material //Material
implementation "com.google.android.material:material:$materialVersion" implementation "com.google.android.material:material:$materialVersion"
...@@ -79,16 +81,95 @@ dependencies { ...@@ -79,16 +81,95 @@ dependencies {
//Constraint Layout //Constraint Layout
implementation "androidx.constraintlayout:constraintlayout:$constrainLayoutVersion" implementation "androidx.constraintlayout:constraintlayout:$constrainLayoutVersion"
//image loading store and cashe by url: Picasso
implementation "com.squareup.picasso:picasso:$picassoVersion"
//Logger: Timber //Logger: Timber
implementation "com.jakewharton.timber:timber:$timberVersion" implementation "com.jakewharton.timber:timber:$timberVersion"
//Crashlytics //RxJava
implementation('com.crashlytics.sdk.android:crashlytics:2.10.0@aar') { implementation "io.reactivex.rxjava3:rxjava:$rxJavaVersion"
transitive = true;
} //Mosby MVI
implementation "com.hannesdorfmann.mosby3:mvi-conductor:$mosbyMviConductorVersion"
//conductor support
implementation "com.bluelinelabs:conductor-support:$conductorVersion"
//Butterknife
implementation "com.jakewharton:butterknife:$butterKnifeVersion"
kapt "com.jakewharton:butterknife-compiler:$butterKnifeVersion"
//Dagger2
implementation "com.google.dagger:dagger:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
implementation "com.google.dagger:dagger-android:$daggerVersion"
implementation "com.google.dagger:dagger-android-support:$daggerVersion"
kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
//Rx Relay
implementation "com.jakewharton.rxrelay2:rxrelay:$rxRelayVersion"
implementation "com.jakewharton.rxrelay2:rxrelay:$rxRelayVersion"
//Assist Injected
compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.5.0'
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.0'
//RxBinding
implementation "com.jakewharton.rxbinding3:rxbinding:$rxBindingVersion"
implementation "com.jakewharton.rxbinding3:rxbinding-recyclerview:$rxBindingVersion"
implementation "com.jakewharton.rxbinding3:rxbinding-material:$rxBindingVersion"
//Retrofit2
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
implementation 'com.squareup.okhttp3:okhttp:3.14.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.2'
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.0'
//Sql - Requery
implementation "io.requery:requery:$requeryVersion"
implementation "io.requery:requery-android:$requeryVersion"
implementation "io.requery:requery-kotlin:$requeryVersion"
kapt "io.requery:requery-processor:$requeryVersion"
//RxSharedPreferences
implementation "com.afollestad:rxkprefs:1.2.5"
implementation 'com.android.support:support-annotations:28.0.0'
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
//Exo Player
implementation "com.google.android.exoplayer:exoplayer:$exoPlayerVersion"
implementation "com.google.android.exoplayer:exoplayer-core:$exoPlayerVersion"
implementation "com.google.android.exoplayer:exoplayer-hls:$exoPlayerVersion"
implementation "com.google.android.exoplayer:extension-rtmp:$exoPlayerVersion"
//ViewPager2
implementation "androidx.viewpager2:viewpager2:$viewPager2Version"
//Photo view
implementation "com.github.chrisbanes:PhotoView:$photoViewVersion"
//Glide
implementation "com.github.bumptech.glide:glide:$glideVersion"
implementation 'jp.wasabeef:glide-transformations:4.0.0'
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")
//Tests //Tests
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
......
{
"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
...@@ -2,21 +2,56 @@ ...@@ -2,21 +2,56 @@
<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.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<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"> 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:windowSoftInputMode="stateHidden|adjustPan">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<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>
</application> </application>
</manifest> </manifest>
\ No newline at end of file
package com.biganto.visual.roompark.base
import android.os.Bundle
import android.os.PersistableBundle
import dagger.android.support.DaggerAppCompatActivity
import io.reactivex.disposables.CompositeDisposable
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
abstract class BaseRoomParkActivity : DaggerAppCompatActivity(){
protected val disposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
}
override fun onDestroy() {
super.onDestroy()
disposable.clear()
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
}
}
\ No newline at end of file
package com.biganto.visual.roompark.base;
import android.util.Log;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
/**
* README: Must be called on UI-Thread (main thread), so post to a handler if needed
*
* NOTES:
* - To enable easier debugging, tag your nodes with android:tag="Name-of-my-node"
* - Avoid using wrap_content on ScrollView!
* - If you have center or right/bottom gravity, you should re-layout all nodes, not only the wrap_content: just call the method with the boolean set to true
*
* @author Eric, April 2014
*/
public class LayoutWrapContentUpdater
{
public static final String TAG = LayoutWrapContentUpdater.class.getName();
/**
* Does what a proper requestLayout() should do about layout_width or layout_height = "wrap_content"
*
* Warning: if the subTreeRoot itself has a "wrap_content" layout param, the size will be computed without boundaries maximum size.
* If you do have limits, consider either passing the parent, or calling the method with the size parameters (View.MeasureSpec)
*
* @param subTreeRoot root of the sub tree you want to recompute
*/
public static final void wrapContentAgain( ViewGroup subTreeRoot )
{
wrapContentAgain( subTreeRoot, false, MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED );
}
/** Same but allows re-layout of all views, not only those with "wrap_content". Necessary for "center", "right", "bottom",... */
public static final void wrapContentAgain( ViewGroup subTreeRoot, boolean relayoutAllNodes )
{
wrapContentAgain( subTreeRoot, relayoutAllNodes, MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED );
}
/**
* Same as previous, but with given size in case subTreeRoot itself has layout_width or layout_height = "wrap_content"
*/
public static void wrapContentAgain( ViewGroup subTreeRoot, boolean relayoutAllNodes,
int subTreeRootWidthMeasureSpec, int subTreeRootHeightMeasureSpec )
{
Log.d(TAG, "+++ LayoutWrapContentUpdater wrapContentAgain on subTreeRoot=["+ subTreeRoot +"], with w="
+ subTreeRootWidthMeasureSpec +" and h="+ subTreeRootHeightMeasureSpec );
assert( "main".equals( Thread.currentThread().getName() ) );
if (subTreeRoot == null)
return;
LayoutParams layoutParams = subTreeRoot.getLayoutParams();
// --- First, we force measure on the subTree
int widthMeasureSpec = subTreeRootWidthMeasureSpec;
// When LayoutParams.MATCH_PARENT and Width > 0, we apply measured width to avoid getting dimensions too big
if ( layoutParams.width != LayoutParams.WRAP_CONTENT && subTreeRoot.getWidth() > 0 )
widthMeasureSpec = MeasureSpec.makeMeasureSpec( subTreeRoot.getWidth(), MeasureSpec.EXACTLY );
int heightMeasureSpec = subTreeRootHeightMeasureSpec;
// When LayoutParams.MATCH_PARENT and Height > 0, we apply measured height to avoid getting dimensions too big
if ( layoutParams.height != LayoutParams.WRAP_CONTENT && subTreeRoot.getHeight() > 0 )
heightMeasureSpec = MeasureSpec.makeMeasureSpec( subTreeRoot.getHeight(), MeasureSpec.EXACTLY );
// This measure recursively the whole sub-tree
subTreeRoot.measure( widthMeasureSpec, heightMeasureSpec );
// --- Then recurse on all children to correct the sizes
recurseWrapContent( subTreeRoot, relayoutAllNodes );
// --- RequestLayout to finish properly
subTreeRoot.requestLayout();
return;
}
/**
* Internal method to recurse on view tree. Tag you View nodes in XML layouts to read the logs more easily
*/
private static void recurseWrapContent( View nodeView, boolean relayoutAllNodes )
{
// Does not recurse when visibility GONE
if ( nodeView.getVisibility() == View.GONE ) {
// nodeView.layout( nodeView.getLeft(), nodeView.getTop(), 0, 0 ); // No need
return;
}
LayoutParams layoutParams = nodeView.getLayoutParams();
boolean isWrapWidth = ( layoutParams.width == LayoutParams.WRAP_CONTENT ) || relayoutAllNodes;
boolean isWrapHeight = ( layoutParams.height == LayoutParams.WRAP_CONTENT ) || relayoutAllNodes;
if ( isWrapWidth || isWrapHeight ) {
boolean changed = false;
int right = nodeView.getRight();
int bottom = nodeView.getBottom();
if ( isWrapWidth && nodeView.getMeasuredWidth() > 0 ) {
right = nodeView.getLeft() + nodeView.getMeasuredWidth();
changed = true;
Log.v(TAG, "+++ LayoutWrapContentUpdater recurseWrapContent set Width to "+ nodeView.getMeasuredWidth() +" of node Tag="+ nodeView.getTag() +" ["+ nodeView +"]");
}
if ( isWrapHeight && nodeView.getMeasuredHeight() > 0 ) {
bottom = nodeView.getTop() + nodeView.getMeasuredHeight();
changed = true;
Log.v(TAG, "+++ LayoutWrapContentUpdater recurseWrapContent set Height to "+ nodeView.getMeasuredHeight() +" of node Tag="+ nodeView.getTag() +" ["+ nodeView +"]");
}
if (changed) {
nodeView.layout( nodeView.getLeft(), nodeView.getTop(), right, bottom );
// FIXME: Adjust left & top position when gravity = "center" / "bottom" / "right"
}
}
// --- Recurse
if ( nodeView instanceof ViewGroup ) {
ViewGroup nodeGroup = (ViewGroup)nodeView;
for (int i = 0; i < nodeGroup.getChildCount(); i++) {
recurseWrapContent( nodeGroup.getChildAt(i), relayoutAllNodes );
}
}
return;
}
// End of class
}
\ No newline at end of file
package com.biganto.visual.roompark.base package com.biganto.visual.roompark.base
import android.app.Application
import android.util.Log import android.util.Log
import com.biganto.visual.roompark.BuildConfig import com.biganto.visual.roompark.BuildConfig
import com.biganto.visual.roompark.di.koin.initDI import com.biganto.visual.roompark.di.dagger.AppComponent
import com.crashlytics.android.Crashlytics import com.biganto.visual.roompark.di.dagger.DaggerAppComponent
import com.squareup.picasso.Picasso import dagger.android.AndroidInjector
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.
*/ */
class RoomParkApplication : Application() { class RoomParkApplication : DaggerApplication() {
companion object {
lateinit var component: AppComponent
private set
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
component = DaggerAppComponent.factory()
.create(this) as AppComponent
// .context(this)
// .build()
return component
}
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
initDI()
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree()) if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
else Timber.plant(CrashlyticsTree()) else Timber.plant(CrashlyticsTree())
val picassoBuilder = Picasso.Builder(this)
picassoBuilder.listener { _, uri, exception ->
Timber.e(exception)
Timber.e(exception, "PICASSO url = %s", uri.toString())
}
// Picasso.Builder creates the Picasso object to do the actual requests RxJavaPlugins.setErrorHandler { e ->
val picasso = picassoBuilder.build() when (e) {
is UndeliverableException -> {
try { }
Picasso.setSingletonInstance(picasso) // fine, irrelevant network problem or API that throws on cancellation
} catch (ignored: IllegalStateException) { is IOException -> return@setErrorHandler
Timber.e(ignored, "PICASSO") is SocketException -> return@setErrorHandler
// Picasso instance was already set // fine, some blocking code was interrupted by a dispose call
// cannot set it after Picasso.with(Context) was already in use 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")
}
}
} }
...@@ -46,7 +79,7 @@ class RoomParkApplication : Application() { ...@@ -46,7 +79,7 @@ class RoomParkApplication : Application() {
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) // Crashlytics.log(priority, tag, message)
throwable?.let { Crashlytics.logException(it) } // throwable?.let { Crashlytics.logException(it) }
} }
} }
\ No newline at end of file
package com.biganto.visual.roompark.base package com.biganto.visual.roompark.base
import android.os.Bundle import android.os.Bundle
import android.widget.TextView import android.view.View
import androidx.appcompat.app.AppCompatActivity import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isGone
import butterknife.BindView
import butterknife.ButterKnife
import com.biganto.visual.roompark.R import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.di.koin.StartUpData import com.biganto.visual.roompark.presentation.screen.splash.SplashScreenController
import org.koin.android.scope.currentScope import com.biganto.visual.roompark.util.extensions.setGone
import com.biganto.visual.roompark.util.view_utils.app_bar.DragControlAppBarLayoutBehaviour
import com.bluelinelabs.conductor.Conductor
import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.textview.MaterialTextView
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.switch_toolbar.view.*
import timber.log.Timber
class RoomParkMainActivity : AppCompatActivity() {
lateinit var entryText:TextView class RoomParkMainActivity(
private val entryDate : StartUpData by currentScope.inject() ) : BaseRoomParkActivity()
,ICollapsingToolBar
,IConductorActivity
,IBottomNavigation{
// @Inject
// lateinit var snackbarProvider: ISnackBarProvider
private lateinit var router: Router
@BindView(R.id.top_toolbar) override lateinit var topAppBar: Toolbar
@BindView(R.id.app_bar) override lateinit var appBar: AppBarLayout
@BindView(R.id.topToolbarHolder) override lateinit var coordinatorLayout: CoordinatorLayout
@BindView(R.id.conductor_container) override lateinit var conductorContainer: ViewGroup
@BindView(R.id.bottom_navigation_view) override lateinit var bottomNavigation: BottomNavigationView
@BindView(R.id.custom_toolbar_container) lateinit var tbContainer: LinearLayout
@BindView(R.id.bottom_view_divider) lateinit var bottomShadow: View
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// if (!BuildConfig.DEBUG) Fabric.with(this, Crashlytics())
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
entryText = findViewById(R.id.entryTextView)
entryText.text = entryDate.helloText ButterKnife.bind(this)
setSupportActionBar(topAppBar)
hideAll()
router = Conductor.attachRouter(this, conductorContainer, savedInstanceState)
router.setRoot(RouterTransaction.with(SplashScreenController()))
headerToolbarBack.setOnClickListener {onBackPressed()}
}
override fun displayBackButton(show: Boolean) {
supportActionBar?.setDisplayHomeAsUpEnabled(show)
}
override fun hideAll() {
// appBar.visibility=Toolbar.INVISIBLE
Timber.e(" lay params : ${appBar.layoutParams.javaClass}")
val params:CoordinatorLayout.LayoutParams = conductor_container.layoutParams as CoordinatorLayout.LayoutParams
params.behavior = null
conductor_container.requestLayout()
}
override val statusToolbar: ViewGroup
get() = tbContainer.findViewById(R.id.status_toolbar_container)
override val headerToolbar: ViewGroup
get() = tbContainer.findViewById(R.id.switch_toolbar_container)
override val headerToolbarBack
get() = headerToolbar.findViewById<MaterialTextView>(R.id.back_button_chevron)
override fun setToolbar(header:HeaderToolbarModel?,status:StatusToolbarModel?){
appBar.setExpanded(false,false)
if (header == null && status == null){
hideAll()
appBar.setGone(true)
return
} else appBar.setGone(false)
showAll()
appBarScrollable(false)
headerToolbar.setGone(header==null)
statusToolbar.setGone(status==null)
header?.let {
headerToolbarBack.setGone(!it.backButton)
headerToolbarBack.text = it.backTitle?:""
headerToolbar.toolbar_title.text = it.title?:""
headerToolbar.bell_container.setGone(!(it.switcher?:false))
}
status?.let {
statusToolbar.status_icon.setGone(it.state == null)
statusToolbar.status_title.setGone(it.state == null)
it.state?.let { state ->
statusToolbar.status_icon.isEnabled = when(state){
StatusState.AVAILABLE -> true
else -> false
}
statusToolbar.status_title.text = when(state){
StatusState.AVAILABLE -> resources.getString(R.string.estate_avalibale)
StatusState.SOLD_OUT -> resources.getString(R.string.estate_sold_out)
}
}
statusToolbar.mean_title.setGone(it.meanTitle == null)
it.meanTitle?.let { title -> statusToolbar.mean_title.text = title}
}
val prms = appBar.layoutParams as CoordinatorLayout.LayoutParams
prms.height = (if (!headerToolbar.isGone) headerToolbar.height else 0) +
(if (!statusToolbar.isGone) statusToolbar.height else 0)
appBar.layoutParams = prms
appBar.requestLayout()
}
override fun hide() {
bottomNavigation.setGone(true)
bottomNavigation.setGone(true)
val params = conductorContainer.layoutParams as CoordinatorLayout.LayoutParams
params.bottomMargin = 0
conductorContainer.requestLayout()
}
override fun show() {
bottomNavigation.setGone(false)
bottomNavigation.setGone(false)
val params = conductorContainer.layoutParams as CoordinatorLayout.LayoutParams
params.bottomMargin = resources.getDimensionPixelSize(R.dimen.bottom_navigation_height)
conductorContainer.requestLayout()
}
override fun showAll() {
topAppBar.visibility = View.VISIBLE
val params = conductorContainer.layoutParams as CoordinatorLayout.LayoutParams
params.behavior = AppBarLayout.ScrollingViewBehavior()
conductorContainer.requestLayout()
}
override fun appBarScrollable(allow: Boolean) {
val params = appBar.layoutParams as CoordinatorLayout.LayoutParams
val behavior = params.behavior as DragControlAppBarLayoutBehaviour
behavior.allowDrag=allow
}
override fun onBackPressed() {
if (router.handleBack()) {
Timber.d("In stack : " + router.backstackSize)
} else {
super.onBackPressed()
}
} }
} }
package com.biganto.visual.roompark.base
import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.textview.MaterialTextView
/**
* Created by Vladislav Bogdashkin on 26.09.2019.
*/
interface ISupportActionBar{
val topAppBar : Toolbar
fun displayBackButton(show:Boolean)
fun hideAll()
fun showAll()
}
interface ICollapsingToolBar : ISupportActionBar{
val appBar : AppBarLayout
val coordinatorLayout: CoordinatorLayout
fun appBarScrollable(allow:Boolean)
val statusToolbar:ViewGroup
val headerToolbar:ViewGroup
val headerToolbarBack: MaterialTextView?
fun setToolbar(header: HeaderToolbarModel? = null,status:StatusToolbarModel? = null)
}
interface IConductorActivity{
val conductorContainer: ViewGroup
}
interface IBottomNavigation{
val bottomNavigation : BottomNavigationView
fun hide()
fun show()
}
data class HeaderToolbarModel(
val backButton:Boolean
,val backTitle:String?
,val title:String?
,val switcher:Boolean? = null)
data class StatusToolbarModel(val state:StatusState? = null
,val stateTitle:String? = null
,val meanTitle:String? = null)
enum class StatusState{AVAILABLE,SOLD_OUT}
\ No newline at end of file
package com.biganto.visual.roompark.conductor
import com.hannesdorfmann.mosby3.mvp.MvpView
import timber.log.Timber
/**
* Created by Vladislav Bogdashkin on 28.05.2018.
*/
interface BigantoBaseContract<in VS : BigantoBaseViewState> : MvpView {
fun render(viewState: VS) =
Timber.d("Render state ${viewState.javaClass}")
}
abstract class BigantoBaseViewState
\ No newline at end of file
package com.biganto.visual.roompark.conductor
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.annotation.LayoutRes
import androidx.annotation.StringRes
import butterknife.ButterKnife
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.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.mosby.mvi.BigantoMviController
import com.biganto.visual.roompark.di.dagger.ActivityModule
import com.biganto.visual.roompark.util.monades.ExceptionString
import com.biganto.visual.roompark.util.view_utils.snackbar.ISnackBarProvider
import com.hannesdorfmann.mosby3.mvi.MviBasePresenter
import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber
/**
* Created by Vladislav Bogdashkin on 28.05.2018.
*/
abstract class BigantoBaseController<VS : BigantoBaseViewState,V: BigantoBaseContract<VS>,P : MviBasePresenter<V,VS>>
: BigantoMviController<V, P> {
constructor():super()
constructor(args: Bundle):super(args)
val inject by lazy { injectDependencies() }
protected abstract fun injectDependencies()
protected val detachDisposable = CompositeDisposable()
abstract var injectedPresenter:P
override fun createPresenter(): P = injectedPresenter
@LayoutRes
protected abstract fun getLayoutId(): Int
lateinit var toolBar: ICollapsingToolBar
lateinit var bottomNavigationController: IBottomNavigation
lateinit var snackbar: ISnackBarProvider
override fun onAttach(view: View) {
Timber.d("On Attach $view")
super.onAttach(view)
}
override fun onDetach(view: View) {
Timber.d("On Detach $view" )
detachDisposable.clear()
super.onDetach(view)
}
private var isInjected = false
override fun onContextAvailable(context: Context) {
super.onContextAvailable(context)
inject
}
abstract fun onViewBound(v:View)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
val view = inflater.inflate(getLayoutId(), container, false)
// instantiate the view
retainViewMode = RetainViewMode.RELEASE_DETACH
ButterKnife.bind(this, view)
(activity as RoomParkMainActivity).let{
snackbar = ActivityModule.provideSnackBar(it)
toolBar = it
bottomNavigationController = it
snackbar.bindRootView(toolBar.coordinatorLayout)
}
onViewBound(view)
return view
}
protected fun View.hideKeyboard() {
val inputMethodManager = applicationContext
?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(this.windowToken, 0)
}
protected fun View.showKeyboard() {
val inputMethodManager = applicationContext?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.showSoftInput(this, 0)
}
protected fun showMessage(@StringRes msgResId: Int) {
Toast.makeText(this.applicationContext, msgResId, Toast.LENGTH_SHORT).show()
}
override fun handleBack(): Boolean {
router.popController(this)
return true
// return super.handleBack()
}
fun showError(e: ExceptionString) =
e.selectHandler(snackbar::showSnackBar,snackbar::showSnackBar)
protected val isTablet
get() = resources?.getBoolean(R.bool.isTablet)?:false
protected val catalogSpansCount
get() = resources?.getInteger(R.integer.catalogSpans)?:1
protected val toursSpansCount
get() = resources?.getInteger(R.integer.catalogSpans)?:1
}
\ No newline at end of file
package com.biganto.visual.roompark.conductor
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.data.service.network.NoNetworkException
import com.biganto.visual.roompark.domain.custom_exception.CustomApiException
import com.biganto.visual.roompark.util.monades.ExceptionString
import com.hannesdorfmann.mosby3.mvi.MviBasePresenter
import com.hannesdorfmann.mosby3.mvp.MvpView
import com.jakewharton.rxrelay2.PublishRelay
import io.reactivex.Observable
import timber.log.Timber
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
abstract class BigantoBasePresenter<V : MvpView, VS>
: MviBasePresenter<V, VS>() {
protected val restoreStateObservable =
PublishRelay.create<VS>()
fun observableParseError(t: Throwable): Observable<VS> =
Observable.defer { Observable.just(parseError(t)) }
abstract fun defaultErrorViewStateHandler(): (ExceptionString) -> VS
open fun vsByCode(code: Int) = defaultErrorViewStateHandler()
open fun vsByThrowable(t: Throwable) = defaultErrorViewStateHandler()
open fun parseError(t: Throwable) : VS =
when (t) {
is CustomApiException ->{
Timber.e("CustomApiException ${t.messageStringId} / ${t.customMessage}")
parse(t)
}
is NoNetworkException -> parse(t)
else -> {Timber.e(t);parse(t)}
}
private fun parse(e: CustomApiException) = onCodeReturn(e)
private fun parse(e: NoNetworkException) = onNoNetwork(e)
private fun parse(e: Throwable) = onRandomError(e)
open fun onRandomError(t: Throwable): VS =
vsByThrowable(t).invoke(
ExceptionString(R.string.unknown_error, null)
)
open fun onNoNetwork(e: NoNetworkException): VS =
vsByThrowable(e).invoke(
ExceptionString(R.string.no_network_error, null)
)
private fun onCodeReturn(e: CustomApiException): VS =
vsByCode(e.code).invoke(ExceptionString(e.messageStringId,e.customMessage))
}
\ No newline at end of file
package com.biganto.visual.roompark.conductor.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.LayoutRes
import com.biganto.visual.roompark.R
import com.bluelinelabs.conductor.Controller
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
internal const val ALERT_DIALOG_MESSAGE_KEY = "BIGANTO_ALERT_MESSAGE"
internal const val ALERT_DIALOG_BUTTON_TEXT_KEY = "BIGANTO_ALERT_OK_BUTTON_TEXT"
internal const val ALERT_DIALOG_BUTTON_CANCEL_TEXT_KEY = "BIGANTO_ALERT_CANCEL_BUTTON_TEXT"
fun formBundle(message:String): Bundle {
val b = Bundle()
b.putString(ALERT_DIALOG_MESSAGE_KEY,message)
return b
}
fun formBundle(message:String,buttonText:String): Bundle {
val b = Bundle()
b.putString(ALERT_DIALOG_MESSAGE_KEY,message)
b.putString(ALERT_DIALOG_BUTTON_TEXT_KEY,buttonText)
return b
}
fun formBundle(message:String,buttonText:String,cancelButtonText:String): Bundle {
val b = Bundle()
b.putString(ALERT_DIALOG_MESSAGE_KEY,message)
b.putString(ALERT_DIALOG_BUTTON_TEXT_KEY,buttonText)
b.putString(ALERT_DIALOG_BUTTON_CANCEL_TEXT_KEY,cancelButtonText)
return b
}
open class AlertDialogController : Controller {
constructor():super()
constructor(args: Bundle):super(args)
public constructor(alertMessage:String):super(formBundle(alertMessage))
public constructor(alertMessage:String,buttonText: String):super(formBundle(alertMessage, buttonText))
public constructor(alertMessage:String,buttonText: String,cancelText: String)
:super(formBundle(alertMessage, buttonText,cancelText))
public open var dismissActionDelegate = {router.popController(this);Unit}
@LayoutRes
fun getLayoutId() = R.layout.alert_dialog_screen_view
private lateinit var messageTextView : TextView
private lateinit var okButton : Button
private lateinit var dismissButton : Button
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
val view = inflater.inflate(getLayoutId(), container, false)
messageTextView=view.findViewById(R.id.alert_message_text_view)
val alertMessage = args.getString(ALERT_DIALOG_MESSAGE_KEY)
messageTextView.text=alertMessage
okButton=view.findViewById(R.id.alert_ok_button)
if (args.containsKey(ALERT_DIALOG_BUTTON_TEXT_KEY))
okButton.text=args.getString(ALERT_DIALOG_BUTTON_TEXT_KEY)
dismissButton=view.findViewById(R.id.alert_dismiss_button)
val bContainer=view.findViewById<LinearLayout>(R.id.buttons_container)
if (args.containsKey(ALERT_DIALOG_BUTTON_CANCEL_TEXT_KEY)){
bContainer.weightSum=2f
dismissButton.visibility=View.VISIBLE
dismissButton.text=args.getString(ALERT_DIALOG_BUTTON_CANCEL_TEXT_KEY)
dismissButton.setOnClickListener { router.popController(this) }
}else{
bContainer.weightSum=1f
dismissButton.visibility=View.GONE
}
okButton.setOnClickListener {
router.popController(this)
dismissActionDelegate.invoke()
}
return view
}
}
\ No newline at end of file
package com.biganto.visual.roompark.conductor.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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.PhotoResolutionModel
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyclerAdapter
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.textview.MaterialTextView
import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
internal const val PHOTOS_KEY = "CHHOSE_PHOTO_LIST_KEY"
private fun formBundle(photos: ArrayList<PhotoResolutionModel>): Bundle {
val b = Bundle()
b.putParcelableArrayList(PHOTOS_KEY,photos)
return b
}
class ChooseResolutionDialogController : Controller {
constructor(args: Bundle) : super(args)
constructor(photos: ArrayList<PhotoResolutionModel>) : super(formBundle(photos))
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.photoSizesRecyclerView)
recyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
recyclerView.adapter = PhotoSizeAdapter()
recyclerView.itemAnimator = null
recyclerView.addItemDecoration(
DividerItemDecoration(activity,DividerItemDecoration.VERTICAL)
)
args.getParcelableArrayList<PhotoResolutionModel>(PHOTOS_KEY)?.let {
(recyclerView.adapter as PhotoSizeAdapter).setItems(it)
}
detachDisposable.add(
(recyclerView.adapter as PhotoSizeAdapter).onItemClicked.subscribe {
Timber.d("gonna shit : $it")
router.replaceTopController(RouterTransaction.with(
PhotoDialogController(
it.url
)
)
.popChangeHandler(DialogChangeHandler())
.pushChangeHandler(DialogChangeHandler())
)
})
view.findViewById<View>(R.id.cancel_button).setOnClickListener { handleBack() }
return view
}
@LayoutRes
fun getLayoutId() = R.layout.choose_size_modal_screen
override fun handleBack(): Boolean {
return router.popCurrentController()
}
}
internal class PhotoSizeAdapter:CommonRecyclerAdapter<PhotoSizeViewHolder,PhotoResolutionModel>(){
override val vhKlazz = PhotoSizeViewHolder::class
override fun getVhLayout(): Int = R.layout.choose_size_viewholder
}
internal class PhotoSizeViewHolder(itemView: View) :CommonViewHolder<PhotoResolutionModel>(itemView){
@BindView(R.id.size_text)
lateinit var textView : MaterialTextView
override fun onViewBound(model: PhotoResolutionModel) {
Timber.d("resname: ${model.resName}")
textView.text = "${model.resWidth}x${model.resHeight}"
}
}
package com.biganto.visual.roompark.conductor.dialogs
import android.os.Bundle
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
class ExceptionAlertDialogController: AlertDialogController {
constructor(args: Bundle) :super(args)
constructor(message: String, buttonText: String):super(message, buttonText)
override var dismissActionDelegate = {activity?.finish();Unit}
}
\ No newline at end of file
package com.biganto.visual.roompark.conductor.dialogs
import android.content.pm.ActivityInfo
import android.graphics.Bitmap
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import androidx.annotation.LayoutRes
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.RecyclerView
import com.biganto.visual.roompark.R
import com.bluelinelabs.conductor.Controller
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.github.chrisbanes.photoview.PhotoView
import com.google.android.material.snackbar.Snackbar
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
internal const val PHOTO_URL_KEY = "CHHOSE_PHOTO_LIST_KEY"
class PhotoDialogController : Controller {
constructor(args: Bundle) : super(args)
constructor(photoUrl: String) : super(bundleOf(PHOTO_URL_KEY to photoUrl))
lateinit var recyclerView : RecyclerView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
val view = inflater.inflate(getLayoutId(), container, false)
val progress = view.findViewById<ProgressBar>(R.id.photo_load_progress_bar)
progress.visibility = View.VISIBLE
view.findViewById<View>(R.id.close_current_button).setOnClickListener { handleBack() }
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
args.getString(PHOTO_URL_KEY)?.let {
val photoView = view.findViewById<PhotoView>(R.id.photo_frame)
Glide.with(photoView)
.asBitmap()
.load(it)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.addListener(object :RequestListener<Bitmap>{
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
progress.visibility = View.INVISIBLE
Snackbar.make(view,"Ошибка при загрузке!",Snackbar.LENGTH_SHORT)
return false
}
override fun onResourceReady(
resource: Bitmap?,
model: Any?,
target: Target<Bitmap>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
progress.visibility = View.INVISIBLE
return false
}
})
.into(photoView)
}
return view
}
@LayoutRes
fun getLayoutId() = R.layout.photo_viewer
override fun handleBack(): Boolean {
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
return router.popCurrentController()
}
}
package com.biganto.visual.roompark.conductor.dialogs
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import timber.log.Timber
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
class UpdateAppAlertDialogController: AlertDialogController {
constructor(args: Bundle) : super(args)
constructor(message: String, buttonText: String) : super(message, buttonText)
override var dismissActionDelegate = {
val appPackageName = activity?.packageName
var uri = Uri.parse("https://cdn.fishki.net/upload/post/201502/17/1432283/659d2f29de589d5d41252f18e9beb292.jpg")
// uri = Uri.parse("market://details?id=$appPackageName");
try {
startActivity(Intent(Intent.ACTION_VIEW, uri))
} catch (e: Exception) {
when (e) {
is android.content.ActivityNotFoundException -> {
uri = Uri.parse("https://play.google.com/store/apps/details?id=$appPackageName")
startActivity(Intent(Intent.ACTION_VIEW, uri))
}
else -> {
Timber.e("Fatal exception occured, while try to update app version!: $e")
activity?.finish()
}
}
}
Unit
}
}
\ No newline at end of file
package com.biganto.visual.roompark.conductor.dialogs.change_handler;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bluelinelabs.conductor.changehandler.AnimatorChangeHandler;
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
public class DialogChangeHandler extends AnimatorChangeHandler {
private static final int FadeInDuration = 240;
public DialogChangeHandler() {
super(false);
}
@NonNull
@Override
protected Animator getAnimator(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, boolean isPush, boolean toAddedToContainer) {
AnimatorSet animator = new AnimatorSet();
if (to != null && toAddedToContainer) {
ObjectAnimator anim = ObjectAnimator.ofFloat(to, View.ALPHA, 0, 1f);
anim.setDuration(FadeInDuration);
animator.play(anim);
}
return animator;
}
@Override
protected void resetFromView(@NonNull View from) {
// empty - Nothing to do with parent view
}
}
package com.biganto.visual.roompark.conductor.mosby.mvi;
import android.os.Bundle;
import com.hannesdorfmann.mosby3.MviConductorDelegateCallback;
import com.hannesdorfmann.mosby3.MviController;
import com.hannesdorfmann.mosby3.mvi.MviPresenter;
import com.hannesdorfmann.mosby3.mvp.MvpView;
public abstract class BigantoMviController<V extends MvpView, P extends MviPresenter<V, ?>>
extends MviController<V,P> implements MvpView, MviConductorDelegateCallback<V, P> {
public BigantoMviController() {super();}
public BigantoMviController(Bundle args) {
super(args);
}
/**
* Переопределяем метод, чтобы подставить кастомную реализацию LifeCycleListener'a, с учетом проверки связанного
* презентера на null - значение
*/
protected LifecycleListener getMosbyLifecycleListener() {
return new BigantoMviConductorLifecycleListener<>(this);
}
}
package com.biganto.visual.roompark.conductor.mosby.mvi
import android.util.Log
import android.view.View
import androidx.annotation.NonNull
import com.bluelinelabs.conductor.Controller
import com.hannesdorfmann.mosby3.MviConductorDelegateCallback
import com.hannesdorfmann.mosby3.mvi.MviPresenter
import com.hannesdorfmann.mosby3.mvp.MvpView
/**
* В связи с тем, что в исходной реализации MviConductorLifeCycleListener презентер приватное поле,
* есть необходимость рконструировать дефолтный класс библиотеки с внесением изменений - проверкой поля presenter на
* null значение при вызове методов attachView и destroy.
*
* Суть проблемы кроется в том, что при реализации некоторых кейсов (например, когда мы желаем сохранять стек
* навигации среди нескольких роутеров одновроеменно), мы сохраняем ссылку на контроллер, даже в тех случаях когда
* презентер уничтожен (вьюха в mosbyMVI у нас и так отвязана от жизненного цикла контроллера) и при каскадном
* удалении бэкстека, у нас может получиться таким образом, что презентер уже был уничтожен ранее. Т.о. необходима
* проверка на наличие инстанса привязанного к контроллеру презентера.
*
* * **Детально по ссылке:** [mosby issue](https://github.com/sockeqwe/mosby-conductor/issues/38)
*/
class BigantoMviConductorLifecycleListener<V : MvpView, P : MviPresenter<V, *>>
/**
* Instantiate a new Mosby MVP Listener
*
* @param callback [MviConductorDelegateCallback] to set presenter. Typically the
* controller
* himself.
*/
@JvmOverloads constructor(private var callback: MviConductorDelegateCallback<V, P>?,
private val keepPresenterInstance: Boolean = true) : Controller.LifecycleListener() {
private var presenter: P? = null
override fun postCreateView(@NonNull controller: Controller, @NonNull view: View) {
var viewStateWillBeRestored = false
if (presenter == null) {
// Process death,
// hence no presenter with the given viewState id stored, although we have a viewState id
presenter = callback!!.createPresenter()
if (DEBUG) {
Log.d(
DEBUG_TAG,
"New Presenter instance created for $controller. Presenter: $presenter")
}
} else {
viewStateWillBeRestored = true
if (DEBUG) {
Log.d(
DEBUG_TAG, "Reusing Presenter instance for controller "
+ controller
+ ". Presenter: "
+ presenter)
}
}
val viewMpv = callback?.mvpView ?: throw NullPointerException(
"MvpView returned from getMvpView() is null. Returned by " + controller.activity!!)
if (viewStateWillBeRestored) {
callback!!.setRestoringViewState(true)
}
presenter?.attachView(viewMpv)
if (viewStateWillBeRestored) {
callback!!.setRestoringViewState(false)
}
if (DEBUG) {
Log.d(
DEBUG_TAG,
"MvpView attached to Presenter. MvpView: $view Presenter: $presenter")
}
}
override fun preDestroyView(@NonNull controller: Controller, @NonNull view: View) {
presenter?.detachView()
if (DEBUG) {
Log.d(
DEBUG_TAG, "detached MvpView from Presenter. MvpView "
+ callback!!.mvpView
+ " Presenter: "
+ presenter)
}
}
override fun postDestroy(@NonNull controller: Controller) {
if (DEBUG) {
Log.d(
DEBUG_TAG, "Presenter destroyed because View destroyed. MvpView "
+ callback!!.mvpView
+ " Presenter: "
+ presenter)
}
presenter?.destroy()
presenter = null
callback = null
}
companion object {
private var DEBUG = false
private const val DEBUG_TAG = "MviLifecycleListener"
/**
* Determines whether or not a Presenter Instance should be kept
*
* @param keepPresenterInstance true, if the delegate has enabled keep
*/
internal fun retainPresenterInstance(keepPresenterInstance: Boolean, controller: Controller): Boolean {
return keepPresenterInstance && (controller.activity!!.isChangingConfigurations
|| !controller.activity!!.isFinishing) && !controller.isBeingDestroyed
}
}
}
\ No newline at end of file
package com.biganto.visual.roompark.data
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import timber.log.Timber
class RoomParkMessageService : FirebaseMessagingService() {
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)
}
// 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
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi
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.mapper.fromRaw
import com.biganto.visual.roompark.data.repository.mapper.fromRawList
import com.biganto.visual.roompark.domain.contract.DevProgressContract
import com.biganto.visual.roompark.domain.model.*
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
//@Singleton
class AlbumsContractModule @Inject constructor(
private val api: IRoomParkApi,
private val db: IDb
): DevProgressContract {
override fun getAlbumPreviews(albumId: Int): Observable<List<AlbumPhotoPreviewModel>> {
TODO("zzz")
}
override fun getAlbumPreview(albumId: Int): Observable<AlbumPreviewModel> =
db.getAlbum(albumId)
.map (::fromEntity)
override fun getProgressCards(): Observable<List<AlbumPreviewModel>> = fetchTopLevelAlbums()
override fun getProgressAlbumList(albumId: Int): Observable<List<AlbumPreviewModel>> =
fetchAlbums(albumId)
override fun getAlbumPhoto(photoId: Int): Observable<PhotoModel> =
db.getPhoto(photoId).map(::fromEntity)
override fun getWebCamsList(): Observable<WebCamListModel> =
fetchWebCams()
override fun getWebCamStream(camId: Int): Observable<WebCamModel> {
TODO("at current sight indeed function")
}
override fun getAlbumPhotoList(albumId: Int): Observable<List<PhotoModel>> =
fetchAlbumPhotos(albumId)
init {
Timber.d("Devs Repository Created")
}
//region allAlbums
private val fetchTopLevelAlbumsApi =
api.getAlbums()
.doOnNext { Timber.d("raw0 $it") }
.map{ fromRawList(it,::fromRaw) }
.doOnNext(db::blockingUpsert)
private val fetchTopLevelAlbumsDb =
db.getTopLevelAlbums()
.doOnNext { Timber.d("top level albums count : ${it.size}") }
private fun fetchTopLevelAlbums(): Observable<List<AlbumPreviewModel>> =
Observable.mergeDelayError(
arrayListOf(fetchTopLevelAlbumsDb,fetchTopLevelAlbumsApi)
)
.doOnNext { Timber.d("got entity $it") }
.map { fromEntity(it,::fromEntity) }
//endregion allAlbums
//region concrete Albums
private fun fetchAlbumsApi(parentAlbumId:Int) =
api.getAlbums(parentAlbumId)
.doOnNext { Timber.d("raw0 $it") }
.map{ fromRawList(it,::fromRaw) }
.doOnNext(db::blockingUpsert)
.doOnNext {
it.asSequence().map { album ->
if (db.checkIfExistsAlbumJunction(album.id, parentAlbumId) != null) {
return@map null
}
val entity = ImageAlbumJunctionEntity()
entity.setAlbumId(album.id)
entity.setParentId(parentAlbumId)
entity
}.filterNotNull().toList().also { junctions -> db.blockingUpsert(junctions) }
}
.doOnNext { Timber.d("ablums ${it.size}") }
.subscribeOn(Schedulers.io())
private fun fetchAlbumsDb(parentAlbumId:Int) =
db.getChildAlbums(parentAlbumId)
.subscribeOn(Schedulers.io())
private fun fetchAlbums(parentId:Int): Observable<List<AlbumPreviewModel>> =
Observable.mergeDelayError(
arrayListOf(fetchAlbumsDb(parentId),fetchAlbumsApi(parentId))
).map { fromEntity(it,::fromEntity) }
//endregion concrete Albums
//region cams
private val fetchWebCamsApi =
api.getWebCamsList()
.doOnNext { Timber.d("raw0 $it") }
.map{fromRawList(it)}
.subscribeOn(Schedulers.io())
private fun fetchWebCams(): Observable<WebCamListModel> = fetchWebCamsApi
//endregion
//region concrete Albums
private fun fetchAlbumPhotosApi(albumId:Int) =
api.getPhotos(albumId)
.doOnNext { Timber.d("raw0 $it") }
.map{ fromRawList(it,::fromRaw) }
// .map { it }
.doOnNext(db::blockingUpsert)
.subscribeOn(Schedulers.io())
private fun fetchAlbumsPhotosDb(albumId:Int) =
db.getPhotos(albumId)
.toList()
.toObservable()
.filter{it.isNotEmpty()}
.subscribeOn(Schedulers.io())
private fun fetchAlbumPhotos(albumId: Int): Observable<List<PhotoModel>> =
Observable.mergeDelayError(
arrayListOf(fetchAlbumsPhotosDb(albumId),fetchAlbumPhotosApi(albumId))
).map { fromEntity(it,::fromEntity) }
//endregion concrete Albums
}
package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.androidplayer.data.repository.local.ILocalStore
import com.biganto.visual.roompark.data.local.UserState
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi
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.data.repository.mapper.fromRaw
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.fromEntity
import io.reactivex.Completable
import io.reactivex.Observable
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
//
class AuthContractModule @Inject constructor(
private val local: ILocalStore,
private val api: IRoomParkApi,
private val db: IDb
): AuthContract {
init {
Timber.d("Auth Repository Created")
}
override fun signOut(): Completable = local.setRecentUser(null)
override fun signIn(email: String, password: String): Observable<AuthInfoModel> =
api.authenticate(email,password)
.map ( ::fromRaw )
.flatMap{ db.upsertUser(it) }
.doOnNext{ Timber.d("user id: ${it.uuid}")}
.doOnNext { local.setRecentUser(it.uuid.toString()).blockingAwait() }
.map(::fromEntity)
override fun validateAuthState(): Observable<Boolean> = local.recentUser()
.map { when(it){
is UserState.NotAuthenticated -> false
is UserState.Authenticated -> true
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()
} }
}
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
package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.androidplayer.data.repository.local.ILocalStore
import com.biganto.visual.roompark.data.local.UserState
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi
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.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.DealContract
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.PlanPresetModel
import com.biganto.visual.roompark.domain.model.fromEntity
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
//
const val TEST_DEAL_TOKEN =
"183|iauhqAKpzrex9g1JYFYcp4443_PU5tm8ioce8G-QhtQbjNP-_a8CnW0WBw8O7dj4Rm2xdRGTuqvkfAF2zyMUSg=="
class EstateRepository @Inject constructor(
private val local: ILocalStore,
private val api: IRoomParkApi,
private val db: IDb,
private val file: FileModule
): DealContract {
init {
Timber.d("Estate Repository Created $this")
}
private val getFavoritesApi: Observable<List<EstateEntity>> =
local.recentUser()
.flatMap {
when (it) {
is UserState.Authenticated -> db.fetchUser(it.uuid.toInt())
else -> throw CustomApiException.NotAuthorizedException()
}
}
.flatMap { user ->
api.getFavorites(user.authToken)
.doOnError(Timber::e)
.map { fromRawList(it, ::fromRaw) }
.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()
}
override fun getFavorites(): Observable<List<EstateModel>> {
return Observable.mergeDelayError(
arrayListOf(
getFavoritesApi, getFavoritesDb
)
).map { fromEntity(it, ::fromEntity) }
.doOnError(Timber::e)
}
private fun fetchEstateDb(id: Int) = db.getEstate(id)
override fun getEstate(estateId: Int): Observable<EstateModel> {
return fetchEstateDb(estateId).map(::fromEntity)
}
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
)
}
// fun getPlanRequestString(estateId: Int
// , planId:Int
// , furniture:Boolean
// , sizes:Boolean
// , walls:Boolean
// , electric:Boolean
// ) = api.getDirectPlan(estateId,planId,furniture,sizes,electric).
private val getDealsApi: Observable<List<DealEntity>> =
local.recentUser()
.doOnError (Timber::e)
.flatMap {
when (it) {
is UserState.Authenticated -> db.fetchUser(it.uuid.toInt())
else -> throw CustomApiException.NotAuthorizedException()
}
}
.doOnError (Timber::e)
.flatMap { user ->
api.getDeals(TEST_DEAL_TOKEN)//api.getDeals(user.authToken)
.doOnError(Timber::e)
.map { fromRawList(it, ::fromRaw) }
.doOnNext {
it.forEach { deal ->
deal.user = user
}
}
.doOnNext(db::blockingUpsert)
}
private val getDealsDb: Observable<List<DealEntity>> =
local.recentUser()
.flatMap {
when (it) {
is UserState.Authenticated -> db.fetchUser(it.uuid.toInt()).take(1)
else -> throw CustomApiException.NotAuthorizedException()
}
}
.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 =
db.setDealReadState(dealId,true)
private fun fetchEstateApi(building: Int,number:Int)=
api.getEstate(building,number)
.doOnNext { Timber.d("raw0 $it") }
.map (::fromRaw)
.doOnNext(db::upsertEstate)
.subscribeOn(Schedulers.io())
private fun fetchEstateDb(building: Int,number:Int) =
db.fetchEstateByNumber(building,number.toString())
.observable()
.subscribeOn(Schedulers.io())
override fun fetchEstate(building: Int,number:Int): Observable<EstateModel> =
Observable.mergeDelayError(
arrayListOf(fetchEstateApi(building,number),
fetchEstateDb(building,number)
)
).map (::fromEntity)
}
package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi
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.mapper.fromRaw
import com.biganto.visual.roompark.data.repository.mapper.fromRawList
import com.biganto.visual.roompark.domain.contract.FeedsContract
import com.biganto.visual.roompark.domain.model.ArticleModel
import com.biganto.visual.roompark.domain.model.ArticlesPreviewModel
import com.biganto.visual.roompark.domain.model.FeedModel
import com.biganto.visual.roompark.domain.model.fromEntity
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import timber.log.Timber.e
import javax.inject.Inject
import kotlin.math.floor
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
//@Singleton
class FeedsContractModule @Inject constructor(
private val api: IRoomParkApi,
private val db: IDb
): FeedsContract {
override fun fetchFeeds(): Observable<List<FeedModel>> =
fetchAllFeeds().doOnError (::e)
override fun fetchFeedObservable(feedAlias: String): Observable<ArticlesPreviewModel> =
fetchArticles(feedAlias)
override fun fetchFeedObservable(feedAlias: String,pageSize:Int, startIndex:Int)
: Observable<ArticlesPreviewModel> = fetchArticles(feedAlias,pageSize,startIndex)
override fun getArticle(id: Int): Observable<ArticleModel> =
fetchArticle(id)
init {
Timber.d("Feeds Repository Created")
}
private fun fetchArticlessApi(feedAlias:String,pageSize:Int, startIndex:Int)
: Observable<List<ArticleEntity>>? =api.getArticlesPage(
feedAlias
,pageSize
,floor(startIndex.toDouble()/pageSize.toDouble()).toInt()+1
)
.doOnNext { Timber.d("raw0 $it") }
.map { it.items }
.map { fromRaw(it, feedAlias) }
.doOnNext(db::blockingUpsert)
.subscribeOn(Schedulers.io())
private fun fetchArticlesDb(feedAlias: String,pageSize:Int, startIndex:Int)
: Observable<MutableList<ArticleEntity>>? =
db.fetchArticles(feedAlias, pageSize, startIndex)
.toList()
.toObservable()
.subscribeOn(Schedulers.io())
private fun fetchArticles(feedAlias: String,pageSize:Int = 10, startIndex:Int = 0)
: Observable<ArticlesPreviewModel> =
Observable.mergeDelayError(
arrayListOf(fetchArticlesDb(feedAlias,pageSize,startIndex),
fetchArticlessApi(feedAlias,pageSize,startIndex)
)
).map { fromEntity(feedAlias,it) }
private val fetchFeedsApi =
api.getFeeds()
.doOnNext { Timber.d("raw0 $it") }
.map{fromRawList(it,::fromRaw)}
.doOnNext(db::blockingUpsert)
.subscribeOn(Schedulers.io())
private val fetchFeedsDb =
db.fetchFeeds()
.toList()
.toObservable()
.subscribeOn(Schedulers.io())
private fun fetchAllFeeds(): Observable<List<FeedModel>> =
Observable.mergeDelayError(
arrayListOf(fetchFeedsDb,fetchFeedsApi)
).map { fromEntity(it,::fromEntity) }
private fun fetchArticleApi(id:Int) =
api.getArticle(id)
.doOnNext { Timber.d("raw0 $it") }
.map ( ::fromRaw )
.flatMap { article ->
db.getArticle(article.id).observable()
.map { articleEntity ->
articleEntity.setBody(article.body)
articleEntity.setPublished(article.published)
articleEntity.photo = article.photo
articleEntity.setTitle(article.title)
articleEntity
}
}
.map { arrayListOf(it)}
.doOnNext(db::blockingUpsert)
.map { it.first() }
.subscribeOn(Schedulers.io())
private fun fetchArticleDb(id:Int) =
db.getArticle(id).observable()
.filter{it.body != null}
.subscribeOn(Schedulers.io())
private fun fetchArticle(articleId:Int): Observable<ArticleModel> =
Observable.mergeDelayError(
arrayListOf(fetchArticleDb(articleId),fetchArticleApi(articleId))
).map { fromEntity(it) }
override fun articleRead(articleId: Int): Completable =
db.setArticleReadState(articleId,true)
}
package com.biganto.visual.roompark.data.data_provider
import android.app.Application
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi
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.FilesContract
import com.biganto.visual.roompark.util.extensions.folderSize
import java.io.File
import javax.inject.Inject
import kotlin.reflect.full.primaryConstructor
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
//@Singleton
class FilesContractModule @Inject constructor(
private val files: FileModule,
private val api: IRoomParkApi,
private val db: IDb,
private val context: Application
): FilesContract {
private inline fun <reified T:FileModule.FileDirectory> getDirectory(): T? =
T::class.primaryConstructor?.call()
private inline fun <reified T:FileModule.FileDirectory> getFile(): File {
val d = getDirectory<T>() ?: error("Unresolved class type")
return FileModule.getDirectory(context,d)
}
override fun deleteAllFiles() = files.deleteAllCacheObservable()
override fun getPlansSize() = getFile<FileModule.FileDirectory.PlanTypeDir>().folderSize
override fun getToursSize() = getFile<FileModule.FileDirectory.ToursDir>().folderSize
override fun getFeedSize() = getFile<FileModule.FileDirectory.FeedsDir>().folderSize
override fun getAlbumSize() = getFile<FileModule.FileDirectory.Albums>().folderSize
override fun allCacheSize() = FileModule.assetsFile(context).folderSize
}
package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi
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 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?,
nuewState:Boolean
): Observable<SubscriptionEntity> {
var sub = subInnerId?.let {id ->
userEntity.subscriptions?.firstOrNull { sub -> sub.id == id }
}
if (sub == null) {
sub = SubscriptionEntity()
sub.setOwner(userEntity)
sub.setTopic(topic)
sub.setNumber(topic_id)
}
(sub as SubscriptionEntity).setState(nuewState)
return db.saveSubscription(sub).toObservable()
}
override fun subscribeTopic(
user:UserEntity,
subInnerId:Int,
deviceToken: String,
topic: String,
topic_id: String?
): Completable = api.subscribeTopic(
userToken = if (topic_id!=null) TEST_DEAL_TOKEN else user.authToken,
deviceToken = deviceToken
,topicName = topic
,topicId = topic_id)
.doOnNext { Timber.d("UUUUUUUU $it") }
.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 = if (topic_id!=null) TEST_DEAL_TOKEN else 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.androidplayer.data.repository.local
import com.biganto.visual.roompark.data.local.UserState
import io.reactivex.Completable
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 14.06.2018.
*/
interface ILocalStore {
fun setRecentUser(uuid: String?): Completable
fun recentUser(): Observable<in UserState>
fun setDownloadListDestinationTourId(tourId: String): Observable<String>?
fun getDownloadListDestinationTourId(): Observable<String>?
fun recentUserObs(): Observable<String>
}
\ No newline at end of file
package com.biganto.visual.roompark.data.local
import android.app.Application
import com.afollestad.rxkprefs.Pref
import com.afollestad.rxkprefs.rxkPrefs
import com.biganto.visual.androidplayer.data.repository.local.ILocalStore
import com.jakewharton.rxrelay2.BehaviorRelay
import dagger.Module
import dagger.Provides
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
/**
* Created by Vladislav Bogdashkin on 14.06.2018.
*/
@Module
class LocalStorage(){
// @Binds
// @Singleton
// abstract fun provideStore(store:UserHolder):ILocalStore
@Provides
@Singleton
fun provideStore(app : Application):ILocalStore = UserHolder(app)
}
class UserHolder @Inject constructor(val context : Application) : ILocalStore
{
private val prefs = rxkPrefs(context)
private val userSession : Pref<String> = prefs.string(RECENT_UUID_KEY, EMPTY_PREF_VALUE_KEY)
private val sessionSubject = BehaviorRelay.create<UserState>()
private val disposable = CompositeDisposable()
init {
Timber.d(" inited")
disposable.add(userSession.observe().subscribe {
sessionSubject.accept(
if (it == EMPTY_PREF_VALUE_KEY) UserState.NotAuthenticated()
else UserState.Authenticated(it)
)
})
val s = userSession.get()
sessionSubject.accept(
if ( s== EMPTY_PREF_VALUE_KEY) UserState.NotAuthenticated()
else UserState.Authenticated(s)
)
}
companion object {
const val RECENT_UUID_KEY = "com.biganto.visual.androidplayer.LAST_USER_UUD"
const val EMPTY_PREF_VALUE_KEY = "NO_ACTIVE_SESSION"
const val SCROLL_LIST_TO_TOUR_KEY="SCROLL_LIST_TO_TOUR_ID"
}
override fun setDownloadListDestinationTourId(tourId:String) =
prefs.string(SCROLL_LIST_TO_TOUR_KEY,tourId)
.observe()
override fun getDownloadListDestinationTourId() =
prefs.string(SCROLL_LIST_TO_TOUR_KEY,"")
.observe()
override fun recentUserObs() = userSession.observe()
override fun recentUser(): Observable<UserState> = sessionSubject
override fun setRecentUser(uuid: String?) =
Completable.defer {
Completable.fromCallable {
userSession.set(uuid ?: EMPTY_PREF_VALUE_KEY) }
.doOnComplete { Timber.d("complete save null value") }
}
}
sealed class UserState{
class NotAuthenticated:UserState()
data class Authenticated(val uuid: String):UserState()
}
data class UserPrefModel(
val uuid:String
,val name:String
,val email:String
,val timeZone:String)
package com.biganto.visual.roompark.data.repository.api
import com.biganto.visual.roompark.data.repository.api.retrofit.DEFAULT_ARTICLE_PAGE_SIZE
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.*
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
interface IRoomParkApi {
fun authenticate(login: String, pwd: String): Observable<AuthRaw>
fun subscribeTopic(
userToken: String,
deviceToken: String,
topicName: String,
topicId:String?
): Observable<StatusResponse>
fun unSuubscribeTopic(
userToken: String,
deviceToken: String,
topicName: String,
topicId:String?
): Observable<StatusResponse>
fun getDeals(userToken: String): Observable<List<DealRaw>>
fun getFavorites(userToken: String): Observable<List<EstateRaw>>
fun getFeeds(): Observable<List<FeedRaw>>
fun getArticle(id: Int): Observable<ArticleRaw>
fun getPhotos(parentId: Int): Observable<List<GalleryImageRaw>>
fun getEstatePlanTypes(estateId: Int): Observable<List<PlanTypeRaw>>
fun getDirectPlan(
estateId: Int,
planType: Int,
showFurniture: Boolean,
showSizes: Boolean,
showWalls: Boolean,
showElectric: Boolean
): Observable<String>
fun getMultiTour(building: Int, flat: Int): Observable<Int>
fun getArticlesPage(feedName:String,
pageSize:Int = DEFAULT_ARTICLE_PAGE_SIZE,
page:Int): Observable<ArticlesListPaginationRaw>
fun getAlbums(parentId: Int? = null): Observable<List<ImageAlbumRaw>>
fun getEstate(building: Int, flat: Int): Observable<EstateRaw>
fun getWebCamsList(): Observable<List<WebCamRaw>>
}
interface IBigantoApi {
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.retrofit
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.*
import io.reactivex.Observable
import retrofit2.Response
import retrofit2.http.*
/**
* Created by Vladislav Bogdashkin on 13.06.2018.
*/
interface IRoomParkMobileApi{
companion object{
// const val BASE_URL="https://stage.room-park.ru:38386"
const val BASE_URL="https://room-park.ru"
//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="android"
//const val DEFAULT_CLIENT_TYPE="ios"
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 Authorization
const val AUTH_METHOD="users.authorize"
const val EMAIL_AUTH_PARAM="email"
const val PASSWORD_AUTH_PARAM="password"
//endregion
//region Subscription
const val SUBSCRIBE_METHOD="users.subscribe"
const val UNSUBSCRIBE_METHOD="users.unsubscribe"
const val DEVICE_TOKEN_SUBSCRIBTION_PARAM="deviceToken"
const val TOPIC_SUBSCRIBTION_PARAM="topic"
const val TOPIC_SUBSCRIBTION_TOPIC_ID_PARAM="estate_id"
val topicTypes = arrayOf(
"deals",
"progress-1",
"progress-2",
"progress-3",
"progress-kindergarden",
"progress-school",
"progress-landscaping",
"news",
"blog",
"constructing-blog"
)
//endregion
//region GetDeals
const val DEALS_METHOD="users.getDeals"
//endregion
//region GetFavorites
const val FAVORITES_METHOD="users.getFavorites"
//endregion
//region GetFavorites
const val FEEDS_METHOD="news.getFeeds"
//endregion
//region GetFavorites
const val GET_ARTICLES_PAGE_METHOD="news.getArticlesList"
const val ARTICLES_PAGINATION_ALIAS_PARAM="feed_alias"
const val ARTICLES_PAGINATION_PAGE_SIZE_PARAM="pagesize"
const val ARTICLES_PAGINATION_PAGE_PARAM="page"
//endregion
//region GetFavorites
const val GET_ARTICLE_METHOD="news.getArticle"
const val ARTICLE_ID_PARAM="article_id"
//endregion
//region GetAlbums
const val ALBUMS_METHOD="progress.getAlbums"
const val PARENT_ALBUMD_ID_PARAM="parent_id"
//endregion
//region GetAlbums
const val PHOTOS_METHOD="progress.getPhotos"
const val PHOTOS_ALBUMD_ID_PARAM="album_id"
//endregion
//region Get Plan Types
const val GET_PLAN_TYPES_METHOD="estates.getPlanTypes"
const val PLAN_TYPES_ESTATE_ID_PARAM="estate_id"
//endregion
//region Get Plan Types
const val GET_DIRECT_PLAN_METHOD="estates.getPlan"
const val DIRECT_PLAN_ESTATE_ID_PARAM="estate_id"
const val DIRECT_PLAN_TYPE_PARAM="plan_id"
const val DIRECT_PLAN_FURNITURE_PARAM="furniture"
const val DIRECT_PLAN_SIZES_PARAM="sizes"
const val DIRECT_PLAN_WALLS_PARAM="walls"
const val DIRECT_PLAN_ELECTRIC_PARAM="electric"
//endregion
//region Get MultitourId
const val GET_MULTITOUR_ID="estates.getMultiTour"
const val MULTITOUR_BUILDING_NUMBER_PARAM="building"
const val MULTITOUR_FLAT_NUMBER_PARAM="number"
//endregion
//region Get Estate
const val GET_ESTATE_ID="estates.getEstate"
const val ESTATE_BUILDING_NUMBER_PARAM="building"
const val ESTATE_FLAT_NUMBER_PARAM="number"
//endregion
//region Get Web Cams
const val GET_WEB_CAMS_METHOD="webcams.getCameras"
//endregion
}
@POST("$API_URL$AUTH_METHOD$DELIMITER")
@FormUrlEncoded
fun authoriz(
@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,
@Field(EMAIL_AUTH_PARAM) email: String,
@Field(PASSWORD_AUTH_PARAM) pwd: String
): Observable<Response<AuthRaw>>
@POST("$API_URL${SUBSCRIBE_METHOD}$DELIMITER")
@FormUrlEncoded
fun subscribe(
@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(AUTH_TOKEN) token: String,
@Field(DEVICE_TOKEN_SUBSCRIBTION_PARAM) deviceToken: String,
@Field(TOPIC_SUBSCRIBTION_PARAM) topic: String,
@Field(TOPIC_SUBSCRIBTION_TOPIC_ID_PARAM) estateId: String?
): Observable<Response<StatusResponse>>
@POST("$API_URL${UNSUBSCRIBE_METHOD}$DELIMITER")
@FormUrlEncoded
fun unsubscribe(
@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(AUTH_TOKEN) token: String,
@Field(DEVICE_TOKEN_SUBSCRIBTION_PARAM) deviceToken: String,
@Field(TOPIC_SUBSCRIBTION_PARAM) topic: String,
@Field(TOPIC_SUBSCRIBTION_TOPIC_ID_PARAM) estateId: String?
): Observable<Response<StatusResponse>>
@GET("$API_URL$DEALS_METHOD$DELIMITER")
fun getDeals(
@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(AUTH_TOKEN) token: String
): Observable<Response<List<DealRaw>>>
@GET("$API_URL$FAVORITES_METHOD$DELIMITER")
fun getFavorites(
@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(AUTH_TOKEN) token: String
): Observable<Response<List<EstateRaw>>>
@GET("$API_URL$FEEDS_METHOD$DELIMITER")
fun getFeeds(
@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
): Observable<Response<List<FeedRaw>>>
@GET("$API_URL$GET_WEB_CAMS_METHOD$DELIMITER")
fun getWebCams(
@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
): Observable<Response<List<WebCamRaw>>>
@GET("$API_URL$GET_ARTICLES_PAGE_METHOD$DELIMITER")
fun getArticlesPage(
@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(ARTICLES_PAGINATION_ALIAS_PARAM) alias: String,
@Query(ARTICLES_PAGINATION_PAGE_SIZE_PARAM) pageSize: Int,
@Query(ARTICLES_PAGINATION_PAGE_PARAM) page: Int
): Observable<Response<ArticlesListPaginationRaw>>
@GET("$API_URL$GET_ARTICLE_METHOD$DELIMITER")
fun getArticle(
@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(ARTICLE_ID_PARAM) id: Int
): Observable<Response<ArticleRaw>>
@GET("$API_URL$ALBUMS_METHOD$DELIMITER")
fun getAlbums(
@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(PARENT_ALBUMD_ID_PARAM) id: Int?
): Observable<Response<List<ImageAlbumRaw>>>
@GET("$API_URL$PHOTOS_METHOD$DELIMITER")
fun getPhotos(
@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(PHOTOS_ALBUMD_ID_PARAM) id: Int
): Observable<Response<List<GalleryImageRaw>>>
@GET("$API_URL$GET_PLAN_TYPES_METHOD$DELIMITER")
fun getPlanTypes(
@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(PLAN_TYPES_ESTATE_ID_PARAM) estateId: Int
): Observable<Response<List<PlanTypeRaw>>>
@GET("$API_URL$GET_DIRECT_PLAN_METHOD$DELIMITER")
fun getPlan(
@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(DIRECT_PLAN_ESTATE_ID_PARAM) estate_id:Int,
@Query(DIRECT_PLAN_TYPE_PARAM) planType:Int,
@Query(DIRECT_PLAN_FURNITURE_PARAM) furniture:Int,
@Query(DIRECT_PLAN_SIZES_PARAM) sizes:Int,
@Query(DIRECT_PLAN_WALLS_PARAM) walls:Int,
@Query(DIRECT_PLAN_ELECTRIC_PARAM) electric:Int
): Observable<Response<String>>
@Deprecated("change to estates.getEstate method as more common")
@GET("$API_URL$GET_MULTITOUR_ID$DELIMITER")
fun getMultiTourId(
@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(MULTITOUR_BUILDING_NUMBER_PARAM) building:Int,
@Query(MULTITOUR_FLAT_NUMBER_PARAM) flatNumber:Int
): Observable<Response<Int>>
@GET("$API_URL$GET_ESTATE_ID$DELIMITER")
fun getEstate(
@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(ESTATE_BUILDING_NUMBER_PARAM) building:Int,
@Query(ESTATE_FLAT_NUMBER_PARAM) flatNumber:Int
): Observable<Response<EstateRaw>>
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.retrofit
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.*
import com.biganto.visual.roompark.util.extensions.asInt
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.ObservableSource
import io.reactivex.ObservableTransformer
import io.reactivex.schedulers.Schedulers
import retrofit2.Response
import retrofit2.Retrofit
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
const val DEFAULT_ARTICLE_PAGE_SIZE = 10
class RetrofitRepository @Inject constructor(retrofit: Retrofit) : IRoomParkApi {
private val api = retrofit.create(IRoomParkMobileApi::class.java)
init {
Timber.d("Module inited")
}
override fun authenticate(login: String, pwd: String): Observable<AuthRaw> =
api.authoriz(email = login, pwd = pwd)
.subscribeOn(Schedulers.io())
.compose(RetrofitResponseValidation())
override fun subscribeTopic(
userToken: String,
deviceToken: String,
topicName: String,
topicId:String?
): Observable<StatusResponse> =
api.subscribe(token = userToken, deviceToken = deviceToken, topic = topicName,estateId = topicId)
.doOnError { Timber.w(" WTFF ???") }
.compose(RetrofitResponseValidation())
override fun unSuubscribeTopic(
userToken: String,
deviceToken: String,
topicName: String,
topicId:String?
): Observable<StatusResponse> =
api.unsubscribe(token = userToken, deviceToken = deviceToken, topic = topicName,estateId = topicId)
.compose(RetrofitResponseValidation())
override fun getDeals(userToken: String): Observable<List<DealRaw>> =
api.getDeals(token = userToken)
.compose(RetrofitResponseValidation())
override fun getFavorites(userToken: String): Observable<List<EstateRaw>> =
api.getFavorites(token = userToken)
.compose(RetrofitResponseValidation())
override fun getFeeds(): Observable<List<FeedRaw>> =
api.getFeeds()
.subscribeOn(Schedulers.io())
.compose(RetrofitResponseValidation())
override fun getArticlesPage(
feedName: String,
pageSize: Int,
page: Int
): Observable<ArticlesListPaginationRaw> =
api.getArticlesPage(alias = feedName, pageSize = pageSize, page = page)
.compose(RetrofitResponseValidation())
override fun getArticle(id: Int): Observable<ArticleRaw> =
api.getArticle(id = id)
.subscribeOn(Schedulers.io())
.compose(RetrofitResponseValidation())
override fun getAlbums(parentId: Int?): Observable<List<ImageAlbumRaw>> =
api.getAlbums(id = parentId)
.subscribeOn(Schedulers.io())
.compose(RetrofitResponseValidation())
override fun getWebCamsList(): Observable<List<WebCamRaw>> =
api.getWebCams()
.subscribeOn(Schedulers.io())
.compose(RetrofitResponseValidation())
override fun getPhotos(parentId: Int): Observable<List<GalleryImageRaw>> =
api.getPhotos(id = parentId)
.subscribeOn(Schedulers.io())
.compose(RetrofitResponseValidation())
override fun getEstatePlanTypes(estateId: Int): Observable<List<PlanTypeRaw>> =
api.getPlanTypes(estateId = estateId)
.compose(RetrofitResponseValidation())
override fun getDirectPlan(
estateId: Int,
planType: Int,
showFurniture: Boolean,
showSizes: Boolean,
showWalls: Boolean,
showElectric: Boolean
): Observable<String> =
api.getPlan(
estate_id = estateId,
planType = planType,
furniture = showFurniture.asInt,
sizes = showSizes.asInt,
walls = showWalls.asInt,
electric = showElectric.asInt
)
.compose(RetrofitResponseValidation())
// fun getPlanString(
// estateId: Int,
// planType: Int,
// showFurniture: Boolean,
// showSizes: Boolean,
// showWalls: Boolean,
// showElectric: Boolean) = api.getPlan(
// estate_id = estateId,
// planType = planType,
// furniture = showFurniture.asInt,
// sizes = showSizes.asInt,
// walls = showWalls.asInt,
// electric = showElectric.asInt
// ).req
@Deprecated("hidden by states.getEstate method")
override fun getMultiTour(building: Int, flat: Int): Observable<Int> =
api.getMultiTourId(
building = building,
flatNumber = flat
)
.compose(RetrofitResponseValidation())
override fun getEstate(building: Int, flat: Int): Observable<EstateRaw> =
api.getEstate(
building = building,
flatNumber = flat
)
.compose(RetrofitResponseValidation())
}
enum class PlanTypeCatalog(val planId:Int, val planName:String ){
PROJECT(0,"проектная планировка"),
PROJECT_PLAN_1(1,"перепланировка №1"),
PROJECT_PLAN_2(2,"перепланировка №2"),
}
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))
}
}
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.retrofit.di
import android.app.Application
import com.biganto.visual.roompark.data.repository.api.retrofit.IRoomParkMobileApi
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.service.network.INetworkMonitor
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.version_control.AppVersionManager
import com.biganto.visual.roompark.data.service.version_control.IAppVersionControl
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import dagger.Module
import dagger.Provides
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
import timber.log.Timber
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
private const val TIMEOUT_SECONDS=120L
private const val WRITE_SECONDS=120L
private const val READ_SECONDS=120L
val INTERCEPT_LOG_LEVEL = HttpLoggingInterceptor.Level.BODY
@Module
class RetrofitModule{
// var networkMonitor = LiveNetworkMonitor(context)
//
// var versionControl = AppVersionManager(context)
init {
Timber.d("Module inited")
}
private inline fun <reified T> genericType() = object : TypeToken<T>() {}.type
private fun gsonConverterFactory() = GsonConverterFactory.create(
GsonBuilder()
.registerTypeHierarchyAdapter(Any::class.java,CustomExceptionDeserializer<Any>())
.registerTypeAdapter(genericType<MutableList<ErrorRaw>>(), ErrorsListDeserializer())
.registerTypeAdapter(Date::class.java, DateSerializer())
.setLenient()
.create())
private fun client(appVersionManager: IAppVersionControl, networkMonitor: INetworkMonitor) =
OkHttpClient.Builder()
.hostnameVerifier { hostname, session -> true}
.connectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
.writeTimeout(WRITE_SECONDS, TimeUnit.SECONDS)
.readTimeout(READ_SECONDS, TimeUnit.SECONDS)
.addInterceptor {
if (networkMonitor.isConnected) {
it.proceed(it.request())
} else {
throw NoNetworkException("No Network")
}
}
.addInterceptor(VersionValidationInterceptor(appVersionManager))
.addInterceptor(
HttpLoggingInterceptor { Timber.tag("OkHttp").d("RetroLogger: $it") }
.setLevel(INTERCEPT_LOG_LEVEL)
)
.addInterceptor {
it.proceed(
it.request()
.newBuilder()
.header("Content-Type", "application/json")
.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(gsonConverterFactory())
// .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
// .build()
//@Inject
//lateinit var context: RoomParkApplication
@Provides
@Singleton
// @Named("roomParkApi")
fun provideRetrofitRoomParkApi(context: Application): Retrofit =
Retrofit.Builder()
.baseUrl(IRoomParkMobileApi.BASE_URL)
.client(client(AppVersionManager(context), LiveNetworkMonitor(context)))
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(NullOnEmptyConverterFactory.create())
.addConverterFactory(gsonConverterFactory())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.retrofit.raw
import java.util.*
/**
* Created by Vladislav Bogdashkin on 28.10.2019.
*/
data class AuthRaw(
val token:String,
val id: Int,
val email:String,
val name:String
)
data class StatusResponse(val status:String)
data class DealRaw(
val id:String,
val estate_id:String,
val opportunity_sum:Int,
val payment_sum:Int,
val amount_pay_sum:Int,
val status_id:Int,
val manager:String,
val estate:EstateRaw
)
data class EstateRaw(
val id:Int,
val type:String,
val number:String,
val common_info: CommonInfoRaw,
val plan_png:PlanRaw?,
val plan_jpg:PlanRaw?,
val url:String,
val album_id:Int,
val multitour_id:Int?,
val multitour_preview:String?
)
data class PlanRaw(
val url:String,
val width:Int,
val height:Int
)
data class CommonInfoRaw(
val building: Int,
val section_begin:Int,
val section_end:Int?,
val floor:Int,
val floor_max:Int,
val area: Float?,
val area_living: Float?,
val kind: String?,
val decoration: String?,
val ceiling:Float?,
val windows_face:String?,
val direction: String?,
val price_meter:Int?,
val price:Int?,
val discount: Float?,
val doscount_amount: Int?,
val dependent: Boolean?,
val rooms:Int?
)
data class EstateRoomRaw(
val title:String,
val living:Boolean,
val area:Float
)
data class FeedRaw(
val alias:String,
val title:String
)
data class ArticlesListPaginationRaw(
val page : Int,
val pagesize : Int,
val total : Int,
val pages : Int,
val items : List<NewsArticleRaw>
)
data class NewsArticleRaw(
val id : Int,
val published : Date,
val title : String,
val announce : String,
val preview : String?
)
data class ArticleRaw(
val id : Int,
val published : Date,
val title : String,
val announce: String,
val preview: String?,
val body : String,
val feed_alias : String,
val photo : List<NewsPhotoRaw>?
)
data class NewsPhotoRaw(
val title : String,
val url : String
)
data class ImageAlbumRaw(
val id:Int,
val title:String,
val sort:Int,
val date:Date,
val parent_id:Int?,
val preview: String?
)
data class GalleryImageRaw(
val id:Int,
val title:String,
val description:String?,
val sort:Int,
val album_id:Int,
val resolutions:List<ResolutionRaw>
)
data class ResolutionRaw(
val res_name:String,
val url:String,
val width:Int,
val height:Int
)
data class PlanTypeRaw(
val plan_id:Int,
val title:String,
val features: List<String>,
val explication: List<EstateRoomRaw>?
)
data class MultiTourRaw(
val multiTourId:Int
)
data class ErrorRaw(
val code:Int,
val message:String
)
data class AppVersionRaw(
val current_version : String,
val download_url : String?,
val message : String?,
val critical : Boolean,
val errors : List<ErrorRaw>?
)
data class WebCamRaw(
val id:Int,
val title:String,
val streams:List<StreamRaw>
)
data class StreamRaw(
val res_name:String,
val hls:String,
val rtmp:String
)
\ No newline at end of file
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.domain.custom_exception.parseException
import com.google.gson.*
import timber.log.Timber
import java.lang.reflect.Type
/**
* Created by Vladislav Bogdashkin on 12.11.2019.
*/
class CustomExceptionDeserializer<T> : JsonDeserializer<T> {
override fun deserialize(json: JsonElement, typeOfT: Type, ctx: JsonDeserializationContext): T {
val vals = ArrayList<ErrorRaw>()
//region valid case
when(json){
is JsonObject -> if (!json.asJsonObject.keySet().contains("errors"))
return Gson().newBuilder().create().fromJson(json,typeOfT)
is JsonArray -> return Gson().newBuilder().create().fromJson(json,typeOfT)
}
//endregion valid case
//region parse errorList
val errorList = json.asJsonObject.get("errors")
Timber.w("api errorlist: $errorList")
when(json) {
is JsonArray -> errorList.asJsonArray.forEach{
vals.add(ctx.deserialize<T>(it, ErrorRaw::class.java) as ErrorRaw)
}
is JsonObject -> json.asJsonObject.get("errors").asJsonArray.forEach {
vals.add(ctx.deserialize<T>(it, ErrorRaw::class.java) as ErrorRaw)
}
else -> throw RuntimeException("Unexpected JSON type: " + json.javaClass)
}
if (vals.size==0)
error("unhandled api result : $json")
throw parseException(vals.first()) //-> for now throws only first!
//endregion
}
}
\ No newline at end of file
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.domain.custom_exception.parseException
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import java.lang.reflect.Type
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
class ErrorsListDeserializer: JsonDeserializer<List<ErrorRaw>> {
override fun deserialize(json: JsonElement, typeOfT: Type, ctx: JsonDeserializationContext): List<ErrorRaw> {
val vals = ArrayList<ErrorRaw>()
when {
json.isJsonArray -> for (e in json.asJsonArray) {
vals.add(ctx.deserialize<Any>(e, ErrorRaw::class.java) as ErrorRaw)
}
json.isJsonObject -> vals.add(ctx.deserialize<Any>(json, ErrorRaw::class.java) as ErrorRaw)
else -> throw RuntimeException("Unexpected JSON type: " + json.javaClass)
}
if (vals.isEmpty())
return vals
throw parseException(vals.first()) //-> for now throws only first!
}
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.retrofit.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
/**
* Created by Vladislav Bogdashkin on 15.10.2018.
*/
public final class NullOnEmptyConverterFactory extends Converter.Factory {
private NullOnEmptyConverterFactory() {
}
public static Converter.Factory create() {
return new NullOnEmptyConverterFactory();
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
return (Converter<ResponseBody, Object>) body -> {
if (body.contentLength() == 0) {
return null;
}
if (body.contentLength() == 2/*equals OK*/)
// if (body.string().equals("OK")) -> can't read value because we can read stream only once
return null;
return delegate.convert(body);
};
}
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.retrofit.util
import android.annotation.SuppressLint
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import java.lang.reflect.Type
import java.text.SimpleDateFormat
import java.util.*
/**
* Created by Vladislav Bogdashkin on 09.06.2018.
*/
class DateSerializer : JsonSerializer<Date> {
@SuppressLint("SimpleDateFormat")
override fun serialize(srcDate: Date?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement? {
if (srcDate == null)
return null
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS+hh:mm")
val formatted = dateFormat.format(srcDate)
return JsonPrimitive(formatted)
}
}
package com.biganto.visual.roompark.data.repository.api.retrofit.util
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonParseException
import java.lang.reflect.Type
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
class SingleElementToListDeserializer<T>(private val clazz: Class<T>) : JsonDeserializer<List<T>> {
@Throws(JsonParseException::class)
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): List<T> {
val resultList = arrayListOf<T>()
if (json.isJsonArray) {
for (e in json.asJsonArray) {
resultList.add(context.deserialize<T>(e, clazz))
}
} else if (json.isJsonObject) {
resultList.add(context.deserialize<T>(json, clazz))
} else {
throw RuntimeException("Unexpected JSON type: " + json.javaClass)
}
return resultList
}
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.retrofit.util
import com.biganto.visual.roompark.data.service.version_control.IAppVersionControl
import okhttp3.Interceptor
import okhttp3.Response
/**
* Created by Vladislav Bogdashkin on 26.04.2019.
*/
class VersionValidationInterceptor(private val versionControl: IAppVersionControl)
: Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
// if (!versionControl.appVersionValid)
// if (!chain.request().url().encodedPath().contains(GET_APP_VERSION))
// versionControl.validateVersion()
return chain.proceed(chain.request())
}
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db
import com.biganto.visual.roompark.data.repository.db.requrey.model.*
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.Single
import io.requery.Persistable
import io.requery.reactivex.ReactiveResult
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
interface IDb {
fun upsertUser(entity: UserEntity): Observable<UserEntity>?
fun <T : Persistable> upsert(entity: T): Single<T>
fun <T : List<Persistable>> upsert(entity: T): Single<Iterable<Persistable>>
fun fetchFeeds(): Observable<FeedEntity>
fun fetchUser(uuid: Int): Observable<UserEntity>
fun <T : List<Persistable>> blockingUpsert(entity: T)
fun userObservableResult(uuid: Int): Observable<ReactiveResult<UserEntity>>
fun fetchArticles(feedAlias: String, pageSize: Int, startIndex: Int): Observable<ArticleEntity>
fun getTopLevelAlbums(): Observable<List<ImageAlbumEntity>>
fun getChildAlbums(parentId: Int): Observable<List<ImageAlbumEntity>>
fun checkIfExistsAlbumJunction(albumId: Int, parentAlbumId: Int): ImageAlbumJunctionEntity?
fun getFeed(feedAlias: String): ReactiveResult<FeedEntity>
fun getArticle(id: Int): ReactiveResult<ArticleEntity>
fun getPhotos(albumId: Int): Observable<GalleryPhotoEntity>
fun getPhoto(photoId: Int): Observable<GalleryPhotoEntity>
fun getAlbum(albumId: Int): Observable<ImageAlbumEntity>
fun getUserFavorites(uuid: Int): Observable<EstateEntity>
fun fetchAllUsers(): Observable<List<UserEntity>>
fun getEstate(estateId: Int): Observable<EstateEntity>
fun upsertEstate(entity: EstateEntity)
fun fetchEstateByNumber(building: Int, number: String): ReactiveResult<EstateEntity>
fun setArticleReadState(id: Int, state: Boolean): Completable
fun setDealReadState(id: String, state: Boolean): Completable
fun saveSubscription(subscription: SubscriptionEntity): Single<SubscriptionEntity>
fun getSubscription(id: Int): ReactiveResult<SubscriptionEntity>
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey
import android.app.Application
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.*
import com.biganto.visual.roompark.di.dagger.DATABASE_VERSION
import dagger.Module
import dagger.Provides
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.Single
import io.requery.Persistable
import io.requery.android.sqlite.DatabaseSource
import io.requery.reactivex.KotlinReactiveEntityStore
import io.requery.reactivex.ReactiveResult
import io.requery.sql.KotlinEntityDataStore
import io.requery.sql.TableCreationMode
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 13.06.2018.
*/
@Module
class DbModule{
@Provides
fun provideStore(context: Application): KotlinReactiveEntityStore<Persistable> {
Timber.d("Kotlin store creating..")
val source = DatabaseSource(context, Models.DEFAULT, "BigantoPerfect", DATABASE_VERSION)
// source.setLoggingEnabled(true)
// source.setWriteAheadLoggingEnabled(true)
source.setTableCreationMode(TableCreationMode.DROP_CREATE)
val store = KotlinEntityDataStore<Persistable>(source.configuration)
Timber.d("Kotlin store %s",source)
return KotlinReactiveEntityStore(store)
}
}
class RequeryRepository @Inject constructor(
private val store: KotlinReactiveEntityStore<Persistable>
)
: IDb {
private inline fun <reified T : Persistable> fetchAll() =
store.select(T::class)
override fun upsertUser(entity: UserEntity): Observable<UserEntity> =
store.upsert(entity).toObservable()
override fun <T : Persistable> upsert(entity: T): Single<T> = store.upsert(entity)
override fun <T : List<Persistable>> upsert(entity: T): Single<Iterable<Persistable>> =
store.upsert(entity)
override fun <T : List<Persistable>> blockingUpsert(entity: T) =
store.upsert(entity).toObservable().blockingSubscribe()
override fun upsertEstate(entity:EstateEntity) =
store.upsert(entity).toObservable().blockingSubscribe()
override fun fetchFeeds(): Observable<FeedEntity> =
fetchAll<FeedEntity>().get().observable()
override fun fetchEstateByNumber(building:Int,number:String): ReactiveResult<EstateEntity> =
store.select(EstateEntity::class)
.where(EstateEntity.NUMBER.eq(number))
.and(EstateEntity.INFO_BUILDING.eq(building))
.get()
override fun getFeed(feedAlias: String): ReactiveResult<FeedEntity> =
fetchAll<FeedEntity>().where(FeedEntity.ALIAS.eq(feedAlias))
.get()
override fun getArticle(id: Int): ReactiveResult<ArticleEntity> =
fetchAll<ArticleEntity>().where(ArticleEntity.ID.eq(id))
.get()
override fun getTopLevelAlbums(): Observable<List<ImageAlbumEntity>> =
Observable.fromArray(
store.raw(
ImageAlbumEntity::class,
"SELECT alb.* from ImageAlbum alb " +
"left join ImageAlbumJunction albJun on albJun.albumId = alb.id " +
"WHERE albJun.albumId is null "
).toList()
)
override fun getChildAlbums(parentId: Int): Observable<List<ImageAlbumEntity>> =
Observable.fromArray(
store.raw(
ImageAlbumEntity::class,
"SELECT alb.* " +
"from ImageAlbum alb " +
" left join ImageAlbumJunction albJun on albJun.albumId = alb.id " +
"WHERE albJun.parentId is $parentId"
).toList()
)
override fun getAlbum(albumId: Int): Observable<ImageAlbumEntity> =
store.select(ImageAlbumEntity::class)
.where(ImageAlbumEntity.ID.eq(albumId))
.get().observable()
override fun getEstate(estateId: Int): Observable<EstateEntity> =
store.select(EstateEntity::class)
.where(EstateEntity.ID.eq(estateId))
.get().observable()
override fun getPhotos(albumId: Int): Observable<GalleryPhotoEntity> =
store.select(GalleryPhotoEntity::class)
.where(GalleryPhotoEntity.ALBUM_ID.eq(albumId))
.get().observable()
override fun getPhoto(photoId: Int): Observable<GalleryPhotoEntity> =
store.select(GalleryPhotoEntity::class)
.where(GalleryPhotoEntity.ID.eq(photoId))
.get().observable()
override fun fetchArticles(feedAlias: String, pageSize: Int, startIndex: Int)
: Observable<ArticleEntity> =
fetchAll<ArticleEntity>()
.where(ArticleEntity.FEED_ID.eq(feedAlias))
.orderBy(ArticleEntity.PUBLISHED.desc())
.limit(pageSize)
.offset(startIndex)
.get().observable()
override fun checkIfExistsAlbumJunction(albumId: Int, parentAlbumId: Int) =
fetchAll<ImageAlbumJunctionEntity>()
.where(ImageAlbumJunctionEntity.ALBUM_ID.eq(albumId))
.and(ImageAlbumJunctionEntity.PARENT_ID.eq(parentAlbumId))
.get()
.firstOrNull()
override fun fetchUser(uuid: Int): Observable<UserEntity> =
store.select(UserEntity::class).where(UserEntity.UUID.eq(uuid)).get().observable()
// fetchAll<UserEntity>().where(UserEntity.UUID.eq(uuid)).get().observable()
override fun fetchAllUsers(): Observable<List<UserEntity>> =
store.select(UserEntity::class).get().observable().toList().toObservable()
fun upsertFeeds(entity: List<FeedEntity>) =
store.upsert(entity)
override fun userObservableResult(uuid: Int): Observable<ReactiveResult<UserEntity>> =
store.select(UserEntity::class)
.where(UserEntity.UUID.eq(uuid))
.get()
.observableResult()
override fun getUserFavorites(uuid: Int): Observable<EstateEntity> =
store.select(EstateEntity::class)
.where(EstateEntity.USER_ID.eq(uuid))
.and(EstateEntity.FAVORITE.eq(true))
.get().observable()
override fun setArticleReadState(id:Int,state:Boolean): Completable =
store.update(ArticleEntity::class)
.set(ArticleEntity.READ,state)
.where(ArticleEntity.ID.eq(id))
.get()
.single()
.ignoreElement()
override fun setDealReadState(id:String,state:Boolean): Completable =
store.update(DealEntity::class)
.set(DealEntity.READ,state)
.where(DealEntity.ID.eq(id))
.get()
.single()
.ignoreElement()
override fun saveSubscription(subscription:SubscriptionEntity) =
store.upsert(subscription)
override fun getSubscription(id:Int): ReactiveResult<SubscriptionEntity> =
store.select(SubscriptionEntity::class)
.where(SubscriptionEntity.ID.eq(id))
.get()
}
package com.biganto.visual.roompark.data.repository.db.requrey
import kotlinx.serialization.Serializable
/**
* Created by Vladislav Bogdashkin on 15.06.2018.
*/
private fun revisionStringToString(revisionString:String):String
{val ind = revisionString.indexOf('?'); return if (ind>0) (revisionString.substring(0,ind)) else revisionString } //remove revision flag
private fun revisionStringToRevision(revisionString:String):String?
{val ind = revisionString.indexOf('?'); return if (ind>0) (revisionString.substring(ind)) else null} //remove revision flag
class RevisionString(private val value:String, private val revision:String?){
constructor(revisionString:String) : this(
value= revisionStringToString(revisionString),revision= revisionStringToRevision(revisionString)
)
fun uri()=value
/**
* @return full-string with revision
*/
fun revisionUri()="$value${revision.orEmpty()}"
}
@Serializable
data class TitledPhoto(val title:String, val url:String){
companion object{
private const val delimiter = 'ø'
}
constructor(str:String) : this(
title = str.substringBefore(delimiter),
url = str.substringAfter(delimiter)
)
fun delimiterString() = "$title$delimiter$url"
}
@Serializable
data class PhotoResolutions(
val res_name:String,
val url:String,
val width:Int,
val height:Int
) {
companion object {
private const val delimiter1 = 'ø'
private const val delimiter2 = 'Õ'
private const val delimiter3 = 'è'
}
constructor(str: String) : this(
res_name = str.substringBefore(delimiter1),
url = str.substringAfter(delimiter1).substringBefore(delimiter2),
width = str.substringAfter(delimiter2).substringBefore(delimiter3).toInt(),
height = str.substringAfter(delimiter3).toInt()
)
fun delimiterString() = "$res_name$delimiter1$url$delimiter2$width$delimiter3$height"
}
\ 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.TitledPhoto
import com.biganto.visual.roompark.data.repository.db.requrey.utils.TitledPhotoListConverter
import io.requery.*
import java.util.*
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
@Entity
interface Article : Persistable {
@get:Key
val id:Int
val published : Date
val title : String
@get:Nullable
val announce : String?
@get:Nullable
val preview : String?
@get:Nullable
val body : String?
@get:ForeignKey(references = Feed::class, referencedColumn = "alias")
@get:ManyToOne(cascade = [CascadeAction.NONE])
val feed : Feed?
@get:Convert(TitledPhotoListConverter::class)
var photo : List<TitledPhoto>?
val read : Boolean
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey.model
import io.requery.*
/**
* Created by Vladislav Bogdashkin on 05.11.2019.
*/
@Entity
interface Deal : Persistable {
@get:Key
val id:String
val estateCrmId:String
val opportunitySum:Int
val paymentSum:Int
val amountPaySum:Int
val statusId:Int
val managerName: String
@get:ForeignKey(references = Estate::class )
@get:OneToOne(cascade = [CascadeAction.SAVE])
var estate:Estate
@get:Nullable
@get:Column(name = "UserContainer")
@get:ForeignKey(references = User::class )
@get:ManyToOne(cascade = [CascadeAction.NONE])
var user:User?
val read : Boolean
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey.model
import io.requery.*
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
@Entity
interface Estate : Persistable {
@get:Key
val id: Int
val type: String
val number: String
@get:Nullable
val sectionBegin: Int?
@get:Nullable
val sectionEnd: Int?
@get:Nullable
val planJpgUrl: String?
@get:Nullable
val planJpgWidth: Int?
@get:Nullable
val planJpgHeight: Int?
@get:Nullable
val planPngUrl: String?
@get:Nullable
val planPngWidth: Int?
@get:Nullable
val planPngHeight: Int?
@get:Nullable
val rooms: Int?
@get:Nullable
val albumId: Int?
@get:Nullable
val multitourId: Int?
@get:Nullable
val multitourPreview: String?
val info_building: Int
val info_section_begin: Int
val info_floor: Int
val info_floor_max: Int
@get:Nullable
val url: String?
@get:Nullable
val info_area: Float?
@get:Nullable
val info_area_living: Float?
@get:Nullable
val info_kind: String?
@get:Nullable
val info_dependent: Boolean?
@get:Nullable
val info_decoration: String?
@get:Nullable
val info_window_face: String?
@get:Nullable
val info_price: Int?
@get:Nullable
val info_price_meter: Int?
@get:Nullable
val info_ceiling: Float?
@get:Nullable
val info_direction: String?
val favorite : Boolean
@get:Nullable
@get:Column(name = "UserContainer")
@get:ForeignKey(references = User::class )
@get:OneToOne(mappedBy = "uuid",cascade = [CascadeAction.NONE])
var user:User?
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey.model
import io.requery.*
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
@Entity
interface Explication : Persistable {
@get:Key
@get:Generated
val id: Int
val living: Boolean
val area: Float
val title: String
@get:ManyToOne(cascade = [CascadeAction.NONE])
val owner: PlanPreset
}
package com.biganto.visual.roompark.data.repository.db.requrey.model
import io.requery.*
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
@Entity
interface Feed : Persistable {
@get:Key
val alias: String
val title:String
@get:JunctionTable(name= "FeedArticlesRule")
@get:OneToMany( cascade = [CascadeAction.DELETE])
val articles:Set<Article>?
}
\ 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.PhotoResolutions
import com.biganto.visual.roompark.data.repository.db.requrey.utils.PhotoResolutionsConverter
import io.requery.*
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
@Entity
interface GalleryPhoto : Persistable {
@get:Key
val id:Int
val title:String
@get:Nullable
val description:String
val sort:Int
val album_id:Int
@get:Convert(PhotoResolutionsConverter::class)
var resolutions:List<PhotoResolutions>
}
\ 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.utils.IsoDateConverter
import io.requery.Convert
import io.requery.Entity
import io.requery.Key
import io.requery.Persistable
import java.util.*
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
@Entity
interface ImageAlbum : Persistable {
@get:Key
val id:Int
val title:String
val sort:Int
@get:Convert(IsoDateConverter::class)
val published: Date
val preview:String
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey.model
import io.requery.*
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
@Entity
interface ImageAlbumJunction : Persistable {
@get:Key
@get:Generated
val id:Int
@get:Nullable
val albumId:Int?
@get:Nullable
val parentId:Int?
}
\ 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.utils.StringListConverter
import io.requery.*
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
@Entity
interface PlanPreset : Persistable {
@get:Key
@get:Generated
val id: Int
@get:ForeignKey(references = Estate::class)
@get:ManyToOne(cascade = [CascadeAction.NONE])
var estateId:Estate
val planId: Int
val title:String
@get:Convert(StringListConverter::class)
val features:List<String>
@get:OneToMany( mappedBy = "owner", cascade = [CascadeAction.SAVE, CascadeAction.DELETE])
val explication:MutableList<Explication>
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey.model
import io.requery.Entity
import io.requery.Generated
import io.requery.Key
import io.requery.Persistable
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
@Entity
interface Settings : Persistable {
@get:Key
@get:Generated
val id: Int
val deviceToken: String
val feedsSubscription: Boolean
val authToken: String
val targetResolution: Int
//??/?////????/? ???WERR EW WF W
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey.model
import io.requery.*
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
@Entity
interface Subscription : Persistable {
@get:Key
@get:Generated
val id: Int
@get:ForeignKey(references = User::class, referencedColumn = "uuid")
@get:ManyToOne
val owner: User
val topic: String
@get:Nullable
val number: String? //estateId or smth same, depends on subscription case
val state: Boolean
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey.model
import io.requery.*
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
@Entity
@Table(name = "UserContainer")
interface User : Persistable {
@get:Key
val uuid: Int
val name: String
val email: String
val authToken: String
val targetResolution: Int
// @get:ForeignKey(references = Deal::class )
@get:Nullable
@get:OneToMany(cascade = [CascadeAction.DELETE])
val deals:List<Deal>?
@get:Nullable
@get:OneToMany(cascade = [CascadeAction.DELETE])
val subscriptions:List<Subscription>?
}
package com.biganto.visual.roompark.data.repository.db.requrey.utils;
import java.util.ArrayList;
import io.requery.Converter;
/**
* Created by Vladislav Bogdashkin on 04.07.2018.
*/
public class IntListConverter implements Converter<ArrayList<Integer>, String> {
private final String stringDelimiter = ",";
@SuppressWarnings("unchecked")
@Override
public Class<ArrayList<Integer>> getMappedType() {
return (Class)ArrayList.class;
}
@Override
public Class<String> getPersistedType() {
return String.class;
}
@Override
public Integer getPersistedSize() {
return null;
}
@Override
public String convertToPersisted(ArrayList<Integer> value) {
if (value == null) {
return "";
}
StringBuilder sb = new StringBuilder();
int index = 0;
for (Integer str: value) {
if (index > 0) {
sb.append(stringDelimiter);
}
sb.append(str);
index++;
}
return sb.toString();
}
@Override
public ArrayList<Integer> convertToMapped(Class<? extends ArrayList<Integer>> type,
String value) {
ArrayList<Integer> list = new ArrayList<>();
if (!value.isEmpty())
for (String _val : value.split(stringDelimiter)) {
list.add(Integer.parseInt(_val));
}
return list;
}
}
package com.biganto.visual.roompark.data.repository.db.requrey.utils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import io.requery.Converter;
import timber.log.Timber;
/**
* Created by Vladislav Bogdashkin on 04.07.2018.
*/
public class IsoDateConverter implements Converter<Date, String> {
private SimpleDateFormat apiFormat =new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS+hh:mm", Locale.ROOT);
//SimpleDateFormat dbFormat =new SimpleDateFormat("YYYYMMDDThhmmss", Locale.ROOT);
@SuppressWarnings("unchecked")
@Override
public Class<Date> getMappedType() {
return Date.class;
}
@Override
public Class<String> getPersistedType() {
return String.class;
}
@Override
public Integer getPersistedSize() {
return null;
}
@Override
public String convertToPersisted(Date value) {
return value == null ? null : apiFormat.format(value);
}
@Override
public Date convertToMapped(Class<? extends Date> type,
String value) {
try {
return value == null ? null : apiFormat.parse(value);
}
catch (Exception e){
Timber.e("Wrong stored data format! {%s}",value);
return new Date();
}
}
}
\ 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.PhotoResolutions;
import java.util.ArrayList;
import java.util.List;
import io.requery.Converter;
import timber.log.Timber;
/**
* Created by Vladislav Bogdashkin on 04.07.2018.
*/
public class PhotoResolutionsConverter implements Converter<List<PhotoResolutions>, String> {
private static final String stringDelimeter="♀°♀";
@SuppressWarnings("unchecked")
@Override
public Class<List<PhotoResolutions>> getMappedType() {
return (Class) List.class;
}
@Override
public Class<String> getPersistedType() {
return String.class;
}
@Override
public Integer getPersistedSize() {
return null;
}
@Override
public String convertToPersisted(List<PhotoResolutions> value) {
if (value == null) {
return "";
}
StringBuilder sb = new StringBuilder();
int index = 0;
for (PhotoResolutions str: value) {
if (index > 0) {
sb.append(stringDelimeter);
}
sb.append(str.delimiterString());
index++;
}
return sb.toString();
}
@Override
public List<PhotoResolutions> convertToMapped(Class<? extends List<PhotoResolutions>> type,
String value) {
try {
ArrayList<PhotoResolutions> list = new ArrayList<>();
if (value != null && !value.isEmpty())
for (String s : value.split(stringDelimeter))
list.add(new PhotoResolutions(s));
return list;
}
catch (Exception e){
Timber.e("Wrong stored data format! {%s}",value);
return new ArrayList<PhotoResolutions>(0);
}
}
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey.utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import io.requery.Converter;
/**
* Created by Vladislav Bogdashkin on 04.07.2018.
*/
public class StringListConverter implements Converter<List<String>, String> {
private static final String stringDelimiter ="¹/|¹";
@SuppressWarnings("unchecked")
@Override
public Class<List<String>> getMappedType() {
return (Class)List.class;
}
@Override
public Class<String> getPersistedType() {
return String.class;
}
@Override
public Integer getPersistedSize() {
return null;
}
@Override
public String convertToPersisted(List<String> value) {
if (value == null) {
return "";
}
StringBuilder sb = new StringBuilder();
int index = 0;
for (String str: value) {
if (index > 0) {
sb.append(stringDelimiter);
}
sb.append(str);
index++;
}
return sb.toString();
}
@Override
public List<String> convertToMapped(Class<? extends List<String>> type,
String value) {
ArrayList<String> list = new ArrayList<>();
if (value != null) {
list.addAll(Arrays.asList(value.split(stringDelimiter)));
}
return list;
}
}
package com.biganto.visual.roompark.data.repository.db.requrey.utils;
import com.biganto.visual.roompark.data.repository.db.requrey.TitledPhoto;
import java.util.ArrayList;
import java.util.List;
import io.requery.Converter;
import timber.log.Timber;
/**
* Created by Vladislav Bogdashkin on 04.07.2018.
*/
public class TitledPhotoListConverter implements Converter<List<TitledPhoto>, String> {
private static final String stringDelimeter="♀°♀";
@SuppressWarnings("unchecked")
@Override
public Class<List<TitledPhoto>> getMappedType() {
return (Class) List.class;
}
@Override
public Class<String> getPersistedType() {
return String.class;
}
@Override
public Integer getPersistedSize() {
return null;
}
@Override
public String convertToPersisted(List<TitledPhoto> value) {
if (value == null) {
return "";
}
StringBuilder sb = new StringBuilder();
int index = 0;
for (TitledPhoto str: value) {
if (index > 0) {
sb.append(stringDelimeter);
}
sb.append(str.delimiterString());
index++;
}
return sb.toString();
}
@Override
public List<TitledPhoto> convertToMapped(Class<? extends List<TitledPhoto>> type,
String value) {
try {
ArrayList<TitledPhoto> list = new ArrayList<>();
if (value != null && !value.isEmpty())
for (String s : value.split(stringDelimeter))
list.add(new TitledPhoto(s));
return list;
}
catch (Exception e){
Timber.e("Wrong stored data format! {%s}",value);
return new ArrayList<TitledPhoto>(0);
}
}
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.file
import android.app.Application
import android.content.Context
import android.os.Environment
import com.biganto.visual.roompark.util.extensions.asInt
import com.google.gson.JsonElement
import dagger.Module
import io.reactivex.Observable
import kotlinx.io.IOException
import timber.log.Timber
import java.io.File
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 29.06.2018.
*/
@Module
class FileModule @Inject constructor(val context: Application) {
private val rootFolder: File
get() {
val rootFile = context.filesDir
Environment.getDataDirectory()
if (!rootFile.exists())
rootFile.mkdirs()
return rootFile
}
fun getFile(fileUri: String): File {
try {
val fileName =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)
directory.mkdirs()
val file = File(directory, fileName)
return file
}
catch (e:Exception)
{
Timber.e("Caused file: %s",fileUri)
throw error("unconditional file")
}
}
fun saveFileToDisk(file:File,jsonElement: JsonElement){
file.writeText("[$jsonElement]") //to json array because core unity method parse data like TourData[] Estate[] etc..
}
fun saveFileToDisk(file:File,content: String){
Timber.d("write to : $file")
Timber.d("write to : ${file.name}")
// file.createNewFile()
file.parentFile.mkdirs()
file.writeText(content) //to json array because core unity method parse data like TourData[] Estate[] etc..
}
fun deleteFile(uri:String)= getFile(uri).delete()
fun deleteAllCacheObservable() =
Observable.create<Pair<Int, Int>> {emitter ->
val foldersToDelete = listOf(
FileDirectory.Albums(),
FileDirectory.FeedsDir(),
FileDirectory.PlanTypeDir(),
FileDirectory.ToursDir()
)
foldersToDelete.forEachIndexed { index, dir ->
if (getDirectory(context,dir).deleteRecursively())
emitter.onNext(Pair(index, foldersToDelete.size-1))
else emitter.onError(IOException("Error occurred on files deleting!"))
}
emitter.onComplete()
}
val getCoreCacheDirectory:String
get(){
return rootFolder.absolutePath
}
companion object {
fun getDirectory(context: Context, dirType: FileDirectory): File =
File(context.filesDir.absolutePath.plus(dirType.dir))
fun assetsDirectory(context: Context): String = context.filesDir.absolutePath
fun assetsFile(context: Context): File = context.filesDir.absoluteFile
}
sealed class FileDirectory(protected var path:String) {
val dir:String get() = path
class PlanTypeDir(): FileDirectory(dirName){
private companion object {const val dirName = "/estates"}
constructor (childName:String? = null)
: this(){path = dirName.plus(childName.slashed())}
constructor (catalog:String = dirName
,estateId: Int
, planId:Int
, furniture:Boolean? = null
, sizes:Boolean? = null
, walls:Boolean? = null
, electric:Boolean? = null
) : this(){
path = (catalog.plus("/plan").plus("/$estateId").plus("/$planId")
.plus("/plan${planId}" +
"${sizes?.asInt?.toString()?.map { "_s$it" }?:""}" +
"${furniture?.asInt?.toString()?.map { "_f$it" }?:""}" +
"${walls?.asInt?.toString()?.map { "_w$it" }?:""}" +
"${electric?.asInt?.toString()?.map { "_e$it" }?:""}")
.plus(".html"))
}
}
class ToursDir() : FileDirectory(dirName){
private companion object {const val dirName = "/tours"}
constructor (childName:String? = null)
: this(){path = dirName.plus(childName.slashed())}
}
class FeedsDir(): FileDirectory(dirName) {
private companion object {const val dirName = "/feeds"}
constructor (childName:String? = null)
: this(){path = dirName.plus(childName.slashed())}
}
class Albums(): FileDirectory(dirName) {
private companion object {const val dirName = "/photos"}
constructor (childName:String? = null)
: this(){path = dirName.plus(childName.slashed())}
}
}
}
private fun String?.slashed(): String {
return this?.padStart(this.length+1,'/')?:""
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.mapper
import android.content.res.Resources
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.*
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.model.*
import kotlin.math.max
/**
* Created by Vladislav Bogdashkin on 06.11.2019.
*/
private val display = Resources.getSystem().displayMetrics
fun fromRaw(raw: AuthRaw) : UserEntity {
val user = UserEntity()
user.setEmail(raw.email)
user.setUuid(raw.id)
user.setAuthToken(raw.token)
user.setName(raw.name)
user.setTargetResolution(calcTargetResolution)
return user
}
fun fromRaw(raw:FeedRaw) : FeedEntity {
val entity = FeedEntity()
entity.setTitle(raw.title)
entity.setAlias(raw.alias)
return entity
}
fun fromRaw(raw:ArticleRaw) : ArticleEntity {
val entity = ArticleEntity()
entity.setId(raw.id)
entity.setPublished(raw.published)
entity.setTitle(raw.title)
entity.setBody(raw.body)
entity.setAnnounce(raw.announce)
entity.setPreview(raw.preview)
raw.photo?.let {entity.photo = fromRawList(it, ::fromRaw)}
val feed = FeedEntity()
feed.setAlias(raw.feed_alias)
entity.setFeed(feed)
return entity
}
fun fromRaw(raw:NewsArticleRaw,feedAlias:String):ArticleEntity{
val entity = ArticleEntity()
entity.setId(raw.id)
entity.setPreview(raw.preview)
entity.setAnnounce(raw.announce)
entity.setPublished(raw.published)
entity.setTitle(raw.title)
val feed = FeedEntity()
feed.setAlias(feedAlias)
entity.setFeed(feed)
return entity
}
fun fromRaw(raw:NewsPhotoRaw):TitledPhoto{
return TitledPhoto(raw.title,raw.url)
}
fun fromRaw(raw:ImageAlbumRaw) : ImageAlbumEntity {
val entity = ImageAlbumEntity()
entity.setId(raw.id)
entity.setTitle(raw.title)
entity.setPublished(raw.date)
entity.setSort(raw.sort)
entity.setPreview(raw.preview?:"https://room-park.ru/assets/gallery_images/image_2600/00/00/03/800-1b7546.png")
return entity
}
fun fromRaw(raw:GalleryImageRaw) : GalleryPhotoEntity {
val entity = GalleryPhotoEntity()
entity.setId(raw.id)
entity.setTitle(raw.title)
entity.setDescription(raw.description)
entity.setSort(raw.sort)
entity.setAlbum_id(raw.album_id)
entity.resolutions = fromRawList(raw.resolutions,::fromRaw)
return entity
}
fun fromRaw(raw:ResolutionRaw) =
PhotoResolutions(raw.res_name,raw.url,raw.width,raw.height)
fun fromRaw(raw:DealRaw,uuid:Int):DealEntity {
val user = UserEntity()
user.setUuid(uuid)
return fromRaw(raw,user)
}
fun fromRaw(raw:DealRaw):DealEntity {
val entity = DealEntity()
entity.setId(raw.id)
entity.setEstateCrmId(raw.estate_id)
entity.setOpportunitySum(raw.opportunity_sum)
entity.setPaymentSum(raw.payment_sum)
entity.setAmountPaySum(raw.amount_pay_sum)
entity.setStatusId(raw.status_id)
entity.setManagerName(raw.manager)
entity.estate = fromRaw(raw.estate)
return entity
}
fun fromRaw(raw:DealRaw,user:UserEntity):DealEntity {
val entity = fromRaw(raw)
entity.user = user
return entity
}
fun fromRaw(raw:EstateRaw):EstateEntity{
val entity = EstateEntity()
entity.setId(raw.id)
entity.setType(raw.type)
entity.setNumber(raw.number)
entity.setSectionBegin(raw.common_info.section_begin)
entity.setSectionEnd(raw.common_info.section_end)
entity.setPlanJpgUrl(raw.plan_jpg?.url)
entity.setPlanJpgWidth(raw.plan_jpg?.width)
entity.setPlanJpgHeight(raw.plan_jpg?.height)
entity.setPlanPngUrl(raw.plan_png?.url)
entity.setPlanPngWidth(raw.plan_png?.width)
entity.setPlanPngHeight(raw.plan_png?.height)
entity.setRooms(raw.common_info.rooms)
entity.setAlbumId(raw.album_id)
entity.setMultitourId(raw.multitour_id)
entity.setMultitourPreview(raw.multitour_preview)
entity.setUrl(raw.url)
entity.setInfo_floor_max(raw.common_info.floor_max)
entity.setInfo_area(raw.common_info.area)
entity.setInfo_area_living(raw.common_info.area_living)
entity.setInfo_kind(raw.common_info.kind)
entity.setInfo_dependent(raw.common_info.dependent)
entity.setInfo_decoration(raw.common_info.decoration)
entity.setInfo_building(raw.common_info.building)
entity.setInfo_section_begin(raw.common_info.section_begin)
entity.setInfo_floor(raw.common_info.floor)
entity.setInfo_window_face(raw.common_info.windows_face)
entity.setInfo_price(raw.common_info.price)
entity.setInfo_price_meter(raw.common_info.price_meter)
entity.setInfo_ceiling(raw.common_info.ceiling)
entity.setInfo_direction(raw.common_info.direction)
entity.setFavorite(false)
return entity
}
fun fromRaw(raw:PlanTypeRaw):PlanPresetEntity{
val entity = PlanPresetEntity()
entity.setFeatures(raw.features)
entity.setPlanId(raw.plan_id)
entity.setTitle(raw.title)
entity.explication.clear()
entity.explication.addAll(fromRawList(raw.explication!!,::fromRaw))
return entity
}
fun fromRaw(raw:EstateRoomRaw):ExplicationEntity{
val entity = ExplicationEntity()
entity.setArea(raw.area)
entity.setLiving(raw.living)
entity.setTitle(raw.title)
return entity
}
//fun fromRaw(raw: List<FeedRaw>):List<FeedEntity> = List(raw.displaySize) { index-> fromRaw(raw[index]) }
fun <E,M> fromRawList(raw: List<E>,block:(E)->M):List<M> = List(raw.size) { index-> block(raw[index]) }
fun fromRaw(raw: List<NewsArticleRaw>,feedAlias:String):List<ArticleEntity> =
List(raw.size) { index-> fromRaw(raw[index],feedAlias) }
val calcTargetResolution = max(display.widthPixels,display.heightPixels)
package com.biganto.visual.roompark.data.service.network
/**
* Created by Vladislav Bogdashkin on 12.10.2018.
*/
public interface INetworkMonitor{
val isConnected:Boolean
val isWifiConnected:Boolean
}
package com.biganto.visual.roompark.data.service.network
import android.content.Context
import android.net.ConnectivityManager
import javax.inject.Singleton
/**
* Created by Vladislav Bogdashkin on 12.10.2018.
*/
@Singleton
class LiveNetworkMonitor(val context: Context) : INetworkMonitor {
override val isConnected: Boolean
get() {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetwork = cm.activeNetworkInfo
return activeNetwork != null && activeNetwork.isConnectedOrConnecting
}
override val isWifiConnected: Boolean
get() {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
return cm.activeNetworkInfo != null && (cm.activeNetworkInfo.type == ConnectivityManager.TYPE_WIFI);
}
}
class NoNetworkException(override var message:String): Exception(message)
package com.biganto.visual.roompark.data.service.user_session
import com.biganto.visual.androidplayer.data.repository.local.ILocalStore
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.data.local.UserState
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.domain.custom_exception.CustomApiException
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 19.11.2019.
*/
//@Singleton
class UserSessionService @Inject constructor(
private val local:ILocalStore,
private val db:IDb
):IUserSession{
private var _token:String? = null
override val token = _token?: ""
val disposable =
local.recentUserObs()
.flatMap { db.fetchUser(it.toInt()) }
.subscribe { _token = it.authToken }
val disposable2 =
local.recentUserObs()
.doOnNext { Timber.w("got new user $it") }
.flatMap { db.userObservableResult(it.toInt())
.doOnNext { Timber.w("obs db user $it") }
.flatMap{ reactiveResult -> reactiveResult.observable()
.doOnNext { Timber.w("flatmapUser db user $it") } }
}
.doOnNext { Timber.w("subscribe user $it") }
.subscribe { _token = it.authToken }
fun currentUser() =
local.recentUser().map {
when(it){
is UserState.Authenticated ->it.uuid
is UserState.NotAuthenticated ->
CustomApiException
.UnknownCustomApiException(-1, R.string.unauthorized_user_request,null)
else -> CustomApiException
.UnknownCustomApiException(-1, R.string.unauthorized_user_request,null)
}
}
fun getUser(uuid:Int) = db.fetchUser(uuid).map { it.authToken }
}
class UnauthorizedUser(override var message:String,val userId:String): Exception(message)
interface IUserSession{
val token :String
}
sealed class UserSessionState(){
class Active(uuid:String):UserSessionState()
class Inactive():UserSessionState()
}
\ No newline at end of file
package com.biganto.visual.roompark.data.service.version_control
import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import com.biganto.visual.roompark.BuildConfig
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.AppVersionRaw
import com.jakewharton.rxrelay2.BehaviorRelay
import dagger.Module
import dagger.Provides
import io.reactivex.Observable
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
/**
* Created by Vladislav Bogdashkin on 12.10.2018.
*/
@Module
class AppVersionModule @Inject constructor(val context: Context){
private val manager = AppVersionManager(context)
@Provides
@Singleton
fun provideVersionListener(): IAppVersionControl {
return manager
}
@Provides
@Singleton
fun provideAppVersionNotifier(): IAppVersionNotifier {
return manager
}
}
@Singleton
class AppVersionManager @Inject constructor(val context: Context)
: IAppVersionControl, IAppVersionNotifier {
override fun processAppVersionResponse(): Observable<UpdateAppModel> {
return notifier
}
// val api : IApi by lazy {
// RoomParkApplication.component.api()
// }
// lateinit var notifier : IAppVersionNotifier
private val notifier = BehaviorRelay.create<UpdateAppModel>()
private var appVersionStatus = AppVersionStatus.UNCHECKED
override val appVersionValid: Boolean
get() = appVersionStatus == AppVersionStatus.VALID
private var buildVersion: String = BuildConfig.VERSION_NAME
override fun validateVersion() {
// api.getAppVersion().subscribe(::mergeVersion, Timber::e).dispose()
}
private fun mergeVersion(apiAppVersion: AppVersionRaw){
Timber.d("Got new version info (current: $buildVersion, incoming: $apiAppVersion")
appVersionStatus = AppVersionStatus.VALID
// region Code in spoiler not used in current version
// if (apiAppVersion.current_version!=buildVersion)
// appVersionStatus = AppVersionStatus.OUTDATED
// if (apiAppVersion.critical && appVersionStatus == AppVersionStatus.OUTDATED)
// appVersionStatus = AppVersionStatus.CRITICAL
//endregion
if (apiAppVersion.critical && appVersionStatus == AppVersionStatus.OUTDATED)
appVersionStatus = AppVersionStatus.CRITICAL
if (appVersionStatus == AppVersionStatus.CRITICAL)
sendIntentToUpdate(apiAppVersion)
}
private fun sendIntentToUpdate(apiAppVersion: AppVersionRaw){
val updInfo = UpdateAppModel(
currentVersion = buildVersion,
newVersion = apiAppVersion.current_version,
isCritical = apiAppVersion.critical,
downloadUrl = apiAppVersion.download_url,
message = apiAppVersion.message
)
notifier.accept(updInfo)
}
}
interface IAppVersionControl{
val appVersionValid: Boolean
fun validateVersion()
}
interface IAppVersionNotifier{
fun processAppVersionResponse():Observable<UpdateAppModel>
}
enum class AppVersionStatus{
OUTDATED,
CRITICAL,
VALID,
UNCHECKED
}
data class UpdateAppModel(
val currentVersion : String,
val newVersion : String,
val isCritical : Boolean,
val message : String?,
val downloadUrl : String?
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
parcel.readByte() != 0.toByte(),
parcel.readString(),
parcel.readString()) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(currentVersion)
parcel.writeString(newVersion)
parcel.writeByte(if (isCritical) 1 else 0)
parcel.writeString(message)
parcel.writeString(downloadUrl)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<UpdateAppModel> {
override fun createFromParcel(parcel: Parcel): UpdateAppModel {
return UpdateAppModel(parcel)
}
override fun newArray(size: Int): Array<UpdateAppModel?> {
return arrayOfNulls(size)
}
}
}
class AppVersionNotCheckedException(message:String): Exception(message)
\ No newline at end of file
package com.biganto.visual.roompark.di.dagger
import android.app.Activity
import android.content.Context
import android.content.res.Resources
import com.biganto.visual.roompark.base.BaseRoomParkActivity
import com.biganto.visual.roompark.base.ICollapsingToolBar
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.util.view_utils.snackbar.ISnackBarProvider
import com.biganto.visual.roompark.util.view_utils.snackbar.SnackBarProvider
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.android.ContributesAndroidInjector
/**
* Created by Vladislav Bogdashkin on 31.07.2019.
*/
@Module
abstract class ActivityModule {
@Binds
abstract fun providesContext(activity: Activity):Context
@Module
companion object{
@JvmStatic
@Provides
fun provideResources(activity: Activity) : Resources = activity.resources
@JvmStatic
@Provides
fun provideSnackBar(activity: Activity): ISnackBarProvider {
return SnackBarProvider(activity)
}
}
}
@Module
abstract class AppActivityModule {
@Module
internal interface MainActivityModule {
@Binds
fun bindMainActivity(activity: RoomParkMainActivity): BaseRoomParkActivity
@Binds
fun bindMainActivityToolbar(activity: RoomParkMainActivity): ICollapsingToolBar
}
@Module
internal interface BaseActivityModule {
@Binds
fun bindBaseActivity(activity: BaseRoomParkActivity): BaseRoomParkActivity
}
@ContributesAndroidInjector(
modules = [ActivityModule::class, MainActivityModule::class])
abstract fun contributeMainActivityInjector(): RoomParkMainActivity
@ContributesAndroidInjector(
modules = [ActivityModule::class, BaseActivityModule::class])
abstract fun contributeBaseActivityInjector(): BaseRoomParkActivity
}
package com.biganto.visual.roompark.di.dagger
import android.app.Application
import com.biganto.visual.androidplayer.data.repository.local.ILocalStore
import com.biganto.visual.roompark.base.RoomParkApplication
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.retrofit.di.RetrofitModule
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.file.FileModule
import com.biganto.visual.roompark.domain.contract.*
import dagger.BindsInstance
import dagger.Component
import dagger.android.AndroidInjectionModule
import dagger.android.AndroidInjector
import dagger.android.support.AndroidSupportInjectionModule
import javax.inject.Singleton
/**
* Created by Vladislav Bogdashkin on 13.06.2018.
*/
@Singleton
@Component(modules =
[ AppModule::class,
DataModule::class,
RetrofitModule::class,
DbModule::class,
LocalStorage::class,
// LocalStorage::class,
// AppVersionModule::class,
AndroidInjectionModule::class,
AndroidSupportInjectionModule::class,
// MappingProvider::class,
ActivityModule::class,
ContractRepositoryModule::class,
AppActivityModule ::class,
FileModule::class])
interface AppComponent : AndroidInjector<RoomParkApplication>{
fun authC(): AuthContract
fun feedsC(): FeedsContract
fun feedsAlb(): DevProgressContract
fun estateRep(): DealContract
fun filesRep(): FilesContract
fun subsRep(): SubscriptionContract
fun provideLocal():ILocalStore
fun provideApi():IRoomParkApi
fun providedb():IDb
fun provideUtils():DeviceUtilsContract
fun provideAppContext():Application
fun provideFileSystem(): FileModule
@Component.Factory
interface Factory{
fun create(
@BindsInstance app:RoomParkApplication
):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
package com.biganto.visual.roompark.di.dagger
import android.app.Application
import com.biganto.visual.roompark.base.RoomParkApplication
import dagger.Binds
import dagger.Module
/**
* 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 = 12
@Module
abstract class AppModule{
@Binds
abstract fun provideAppContext(app: RoomParkApplication): Application
}
package com.biganto.visual.roompark.di.dagger
import com.biganto.visual.roompark.data.data_provider.*
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.retrofit.RetrofitRepository
import com.biganto.visual.roompark.data.repository.api.retrofit.di.RetrofitModule
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.RequeryRepository
import com.biganto.visual.roompark.domain.contract.*
import dagger.Binds
import dagger.Component
import dagger.Module
import javax.inject.Singleton
@Component
interface DataComponent{
}
@Module(includes = [DataModule::class, RetrofitModule::class, DbModule::class,LocalStorage::class])
abstract class ContractRepositoryModule {
@Singleton
@Binds
abstract fun provideAuth(contract:AuthContractModule):AuthContract
@Singleton
@Binds
abstract fun provideFeedsContract(impl: FeedsContractModule): FeedsContract
@Binds
@Singleton
abstract fun provideDevProgressContract(impl: AlbumsContractModule): DevProgressContract
@Binds
@Singleton
abstract fun provideEstateContract(impl: EstateRepository): DealContract
@Binds
@Singleton
abstract fun provideSubscriptionsContract(impl: SubscriptionRepository): SubscriptionContract
@Binds
@Singleton
abstract fun provideFilesContract(impl: FilesContractModule): FilesContract
@Binds
@Singleton
abstract fun provideDeviceContract(impl: DeviceUtilsRepository): DeviceUtilsContract
}
@Module
abstract class DataModule {
// @Singleton
// @Binds
// abstract fun provideAuthContract(contract: AuthContractModule) : AuthContract
// @Binds
// abstract fun provideModule(m:RetrofitModule) : RetrofitModule
// @Binds
// abstract fun provideRpRetrofit(retrofit:Retrofit) : Retrofit
//
@Singleton
@Binds
abstract fun provideRoomParkApi(api: RetrofitRepository) : IRoomParkApi
// @Binds
// abstract fun provideStore(store: KotlinReactiveEntityStore<Persistable>) : KotlinReactiveEntityStore<Persistable>
@Singleton
@Binds
abstract fun provideDb(db: RequeryRepository) : IDb
// @Singleton
// @Binds
// abstract fun provideLocalStorage(local: UserHolder) : ILocalStore
/*
@Provides
@Singleton
fun provieApi(@Named("bigantoApi") retorfit:Retrofit): IApi {
return RetrofitRepository(retorfit)
}
@Provides
@Singleton
fun provieRoomParkApi(@Named("roomParkApi") retorfit:Retrofit): IRoomParkApi {
return RoomParkRetrofitRepository(retorfit)
}
@Provides
@Singleton
fun provideDb(context:Application): IDb {
return RequeryRepository(getRequeryDataStore(context))
}
@Provides
@Singleton
fun provideFileModule(context:Application): FilesModule {
return FilesModule(context)
}
@Provides
@Singleton
fun providesNetworkListener(context:Application): INetworkMonitor {
return LiveNetworkMonitor(context)
}
@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.di.dagger
import javax.inject.Scope
/**
* Created by Vladislav Bogdashkin on 14.06.2018.
*/
/**
* Custom Scope for each Screen
*/
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class PerScreen
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope
\ No newline at end of file
package com.biganto.visual.roompark.di.dagger
import android.content.Context
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
import com.bumptech.glide.module.AppGlideModule
/**
* Created by Vladislav Bogdashkin on 19.03.2020.
*/
@GlideModule
class RoomParkGlideModule : AppGlideModule() {
// fun applyOptions(@NonNull context: Context?, @NonNull builder: GlideBuilder) {
// super.applyOptions(context, builder)
// builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_ARGB_8888))
// }
override fun applyOptions(context: Context, builder: GlideBuilder) {
val filderName = "../files/photos"
builder.setDiskCache(
InternalCacheDiskCacheFactory(
context,filderName,1024L*1024L*256L)
)
}
//
// fun registerComponents(
// @NonNull context: Context?, @NonNull glide: Glide?, @NonNull registry: Registry
// ) {
// registry.append(Photo::class.java, InputStream::class.java, Factory())
// }
// Disable manifest parsing to avoid adding similar modules twice.
override fun isManifestParsingEnabled(): Boolean {
return false
}
}
\ No newline at end of file
package com.biganto.visual.roompark.di.koin
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
import org.koin.core.qualifier.named
import org.koin.dsl.module
/**
* Created by Vladislav Bogdashkin on 03.09.2019.
*/
fun RoomParkApplication.initDI(){
startKoin {
androidLogger()
androidContext(this@initDI)
modules(listOf(appModule, scopesModule))
}
}
val appModule = module{
}
val scopesModule = module {
scope(named<RoomParkMainActivity>()){
scoped { StartUpData("ja pesik!") }
}
}
data class StartUpData(val helloText:String)
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 io.reactivex.Completable
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
interface AuthContract {
fun signIn(email:String,password:String) : Observable<AuthInfoModel>
fun signOut() : Completable
fun validateAuthState(): Observable<Boolean>
fun currentUser(): Observable<UserEntity>
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract
import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.PlanPresetModel
import io.reactivex.Completable
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
interface DealContract{
fun getFavorites() : Observable<List<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 setDealRead(dealId: String): Completable
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract
import com.biganto.visual.roompark.domain.model.*
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
interface DevProgressContract{
fun getProgressCards(): io.reactivex.Observable<List<AlbumPreviewModel>>
fun getProgressAlbumList(albumId:Int): io.reactivex.Observable<List<AlbumPreviewModel>>
fun getAlbumPreviews(albumId:Int): io.reactivex.Observable<List<AlbumPhotoPreviewModel>>
fun getAlbumPreview(albumId:Int): Observable<AlbumPreviewModel>
fun getAlbumPhoto(photoId:Int): io.reactivex.Observable<PhotoModel>
fun getWebCamsList(): io.reactivex.Observable<WebCamListModel>
fun getWebCamStream(camId:Int): io.reactivex.Observable<WebCamModel>
fun getAlbumPhotoList(albumId: Int): Observable<List<PhotoModel>>
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
interface DeviceUtilsContract {
fun getDeviceId() : Observable<String>
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract
import com.biganto.visual.roompark.domain.model.ArticleModel
import com.biganto.visual.roompark.domain.model.ArticlesPreviewModel
import com.biganto.visual.roompark.domain.model.FeedModel
import io.reactivex.Completable
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
interface FeedsContract{
fun fetchFeeds(): Observable<List<FeedModel>>
fun getArticle(id:Int): Observable<ArticleModel>
fun fetchFeedObservable(
feedAlias: String,
pageSize: Int,
startIndex: Int
): Observable<ArticlesPreviewModel>
fun fetchFeedObservable(feedAlias: String): Observable<ArticlesPreviewModel>
fun articleRead(articleId:Int):Completable
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
interface FilesContract{
fun allCacheSize(): Long
fun getAlbumSize(): Long
fun getFeedSize(): Long
fun getToursSize(): Long
fun getPlansSize(): Long
fun deleteAllFiles(): Observable<Pair<Int, Int>>
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract
import com.biganto.visual.roompark.data.repository.db.requrey.model.SubscriptionEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import io.reactivex.Completable
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
interface SubscriptionContract{
fun subscribeTopic(
user: UserEntity,
subInnerId: Int,
deviceToken: String,
topic: String,
topic_id: String?
): Completable
fun unSubscribeTopic(
user: UserEntity,
subInnerId: Int,
deviceToken: String,
topic: String,
topic_id: String?
): Completable
fun saveSubscribeState(
userEntity: UserEntity,
subInnerId: Int?,
topic: String,
topic_id: String?,
nuewState: Boolean
): Observable<SubscriptionEntity>
fun saveSubscribeState(sub: SubscriptionEntity): Observable<SubscriptionEntity>
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract
import com.biganto.visual.roompark.domain.model.AuthInfoModel
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
interface TourContract {
fun getMultiTourId(building:Int, number:Int) : Observable<AuthInfoModel>
fun getOffer(offerId:Int) : Observable<AuthInfoModel>
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.custom_exception
import androidx.annotation.StringRes
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.ErrorRaw
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
const val CUSTOM_API_ERROR_RESPONSE_CODE_100=100
const val CUSTOM_API_ERROR_RESPONSE_CODE_101=101
const val CUSTOM_API_ERROR_RESPONSE_CODE_102=102
const val CUSTOM_API_ERROR_RESPONSE_CODE_103=103
const val CUSTOM_API_ERROR_RESPONSE_CODE_104=104
const val CUSTOM_API_ERROR_RESPONSE_CODE_111=111
const val CUSTOM_API_ERROR_RESPONSE_CODE_112=112
const val CUSTOM_API_ERROR_RESPONSE_CODE_201=201
const val CUSTOM_API_ERROR_RESPONSE_CODE_202=202
const val CUSTOM_API_ERROR_RESPONSE_CODE_203=203
const val CUSTOM_API_ERROR_RESPONSE_CODE_204=204
const val CUSTOM_API_ERROR_RESPONSE_CODE_205=205
const val CUSTOM_API_ERROR_RESPONSE_CODE_206=206
const val CUSTOM_API_ERROR_RESPONSE_CODE_211=211
const val CUSTOM_API_ERROR_RESPONSE_CODE_212=212
const val CUSTOM_API_ERROR_RESPONSE_CODE_213=213
const val CUSTOM_API_ERROR_RESPONSE_CODE_221=221
const val CUSTOM_API_ERROR_RESPONSE_CODE_231=231
const val CUSTOM_API_ERROR_RESPONSE_CODE_TOUR_NOT_FOUND=2001
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_100_MESSAGE_ID= R.string.api_error_100
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_101_MESSAGE_ID=R.string.api_error_101
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_102_MESSAGE_ID=R.string.api_error_102
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_103_MESSAGE_ID=R.string.api_error_103
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_104_MESSAGE_ID=R.string.api_error_104
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_111_MESSAGE_ID=R.string.api_error_111
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_112_MESSAGE_ID=R.string.api_error_112
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_201_MESSAGE_ID= R.string.api_error_201
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_202_MESSAGE_ID=R.string.api_error_202
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_203_MESSAGE_ID=R.string.api_error_203
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_204_MESSAGE_ID=R.string.api_error_204
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_205_MESSAGE_ID=R.string.api_error_205
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_206_MESSAGE_ID=R.string.api_error_206
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_211_MESSAGE_ID=R.string.api_error_211
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_212_MESSAGE_ID=R.string.api_error_212
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_213_MESSAGE_ID=R.string.api_error_213
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_221_MESSAGE_ID=R.string.api_error_221
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_231_MESSAGE_ID=R.string.api_error_231
@StringRes
const val CUSTOM_API_ERROR_RESPONSE_CODE_TOUR_NOT_FOUND_MESSAGE_ID=R.string.api_error_2001
@StringRes
const val CUSTOM_API_ERROR_DEFAULT_MESSAGE_ID=R.string.api_error_default
sealed class CustomApiException(val code:Int,@StringRes val messageStringId: Int?, val customMessage:String? = null):Exception() {
class NotAuthorizedException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_100
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_100_MESSAGE_ID)
class TokenSyntaxException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_101
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_101_MESSAGE_ID)
class UserWithTokenNotFoundException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_102
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_102_MESSAGE_ID)
class TokenEstimatedException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_103
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_103_MESSAGE_ID)
class UserBannedException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_104
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_104_MESSAGE_ID)
class WrongLoginException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_111
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_111_MESSAGE_ID)
class WrongPasswordException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_112
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_112_MESSAGE_ID)
class UnexpectedSubscriptionTopicId() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_201
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_201_MESSAGE_ID)
class SubcriptionAldreadyExists() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_202
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_202_MESSAGE_ID)
class DealCrmTimeoutException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_203
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_203_MESSAGE_ID)
class DealCrmWrongAnswearFormatException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_204
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_204_MESSAGE_ID)
class DealCrmError() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_205
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_205_MESSAGE_ID)
class SubscriptionDbException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_206
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_206_MESSAGE_ID)
class WrongFeedException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_211
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_211_MESSAGE_ID)
class ArticleNotFoundException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_212
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_212_MESSAGE_ID)
class ArticleIdExpectedException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_213
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_213_MESSAGE_ID)
class PhotoIdExpectedException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_221
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_221_MESSAGE_ID)
class WrongZoneIdException() : CustomApiException(CUSTOM_API_ERROR_RESPONSE_CODE_231
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_231_MESSAGE_ID)
class TourByIdNotFoundException() : CustomApiException(
CUSTOM_API_ERROR_RESPONSE_CODE_TOUR_NOT_FOUND
,messageStringId= CUSTOM_API_ERROR_RESPONSE_CODE_TOUR_NOT_FOUND_MESSAGE_ID)
class UnknownCustomApiException(code: Int, @StringRes messageStringId: Int?, apiMessage:String?)
: CustomApiException(code,messageStringId,apiMessage)
}
//as an agreement error message should be correct for user (and localized, if needed) on server-side
fun parseException(err: ErrorRaw) =
CustomApiException.UnknownCustomApiException(err.code, null, err.message)
package com.biganto.visual.roompark.domain.interactor
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.SubscriptionUseCase
import io.reactivex.Completable
import io.reactivex.Observable
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.10.2019.
*/
class AlbumsInteractor @Inject constructor(
private val useCase: AlbumsUseCase,
private val subUc: SubscriptionUseCase
) {
fun fetchHeaderAlbums() = useCase.getProgressAlbums()
fun fetchAlbumPhotos(parentId: Int): Observable<List<AlbumSortedModel>> =
useCase.getChildAlbums(parentId)
.flatMap {
Observable.fromIterable(it).flatMap { alb ->
useCase.getPhotos(alb.albumId).take(1)
.map { photos ->
AlbumSortedModel(
alb.title,
alb.published,
alb.albumId,
photos
)
}
}.toList().toObservable()
}
.debounce(80L, TimeUnit.MILLISECONDS) // to reduce double list draw effect
fun getSubscriptions(topic: SubscriptionTopic) =
subUc.getSubscriptions(topic)
fun switchSubscription(model: SubscriptionModel, newState: Boolean): Completable =
when(newState){
true -> subUc.subscribeTopic(model.id,model.topic)
false -> subUc.unSubscribeTopic(model.id,model.topic)
}
}
package com.biganto.visual.roompark.domain.interactor
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.SubscriptionUseCase
import io.reactivex.Observable
import java.util.*
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.10.2019.
*/
class ArticleInteractor @Inject constructor(
private val uc:FeedUseCase
) {
fun fetchArticle(articleId: Int): Observable<ArticleModel> =
uc.getArticle(articleId)
fun setArticleRead(articleId: Int) = uc.setRead(articleId)
// Single.just(
// when (articleId) {
// 2 -> article2
// else -> error("unknown feedId")
// }
// )
companion object{
val article2 = ArticleModel(
2,
Date(12315536L),
"В ЖК «РУМЯНЦЕВО-ПАРК» ИПОТЕЧНАЯ СТАВКА - 6,5%",
"С марта 2019 года для всех покупателей квартир в жилом комплексе бизнес-класса «Румянцево-Парк» от компании Lexion Development, доступна минимальная ипотечная ставка от банка-партнера ПАО Банк ВТБ в размере 6,5% по программе субсидирования ипотеки от застройщика. \n" +
"\n" +
"Минимальная процентная ставка действительна при покупке любого типа квартир в жилом комплексе – от студий 23,8 кв. м до четырехкомнатных квартир площадью 102,7 кв. м. Минимальный первоначальный взнос составляет от 20%.\n" +
"\n" +
"Более подробная информация доступна в офисе продаж по телефону: +7 (495) 127-86-86",
"https://room-park.ru/assets/news_articles/preview/00/00/00/96-a972cd.jpeg",
null,
false
)
}
}
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.SubscriptionUseCase
import io.reactivex.Completable
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.10.2019.
*/
class ArticlesInteractor @Inject constructor(
private val uc: FeedUseCase,
private val subUc: SubscriptionUseCase
) {
fun fetchArticles(feed:String) =
uc.getArticles(feed)
fun fetchArticlesPage(feed:String,pageSize:Int,current:Int) =
uc.fetchArticlesPage(feed,pageSize,current)
fun getSubscriptions(feed: String) =
subUc.getSubscriptions(feedSubType(feed))
fun switchSubscription(model:SubscriptionModel,newState: Boolean): Completable =
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")
}
}
package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.use_case.AuthUseCase
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.10.2019.
*/
class AuthInteractor @Inject constructor(
private val uc:AuthUseCase
){
fun getAuth() = uc.validateAuth()
fun signIn(login:String, password:String) =
uc.signIn(login,password)
}
\ No newline at end of file
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.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.SubscriptionUseCase
import io.reactivex.Completable
import io.reactivex.Observable
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.10.2019.
*/
class DealInteractor @Inject constructor(
val useCase : DealseUseCase,
val subUc : SubscriptionUseCase
){
fun getDeal(id:String): Observable<DealModel> =
useCase.getDeals()
.doOnNext { Timber.d("$it") }
.map {deals -> deals.first { it.id==id } }
fun getStatusList() = Observable.just(statusList.sortedBy{ it.orderId })
fun getStatusListSync() = statusList.asSequence().sortedBy{ it.orderId }.toList()
fun setDealRead(id:String) = useCase.setDealRead(id)
fun getSubscriptions(dealId: String) =
subUc.getSubscriptions(SubscriptionTopic.Deals(dealId = dealId))
fun switchSubscription(model: SubscriptionModel, newState: Boolean): Completable =
when(newState){
true -> subUc.subscribeTopic(model.id,model.topic)
false -> subUc.unSubscribeTopic(model.id,model.topic)
}
companion object {
val statusList = arrayListOf<StatusModel>(
StatusModel(
1,
"Квартира забронирована за Вами. Скоро начнется подготовка договора",
"Бронь"
),
StatusModel(
2,
"Договор находится в процессе подготовки",
"Подготовка договора"
),
StatusModel(
3,
"Договор подготовлен и отправлен в офис продаж",
"Договор подготовлен"
),
StatusModel(
4,
"Договор находится в офисе продаж. Просим Вас приехать на подписание в назначенное время",
"Договор готов к подписанию"
),
StatusModel(
5,
"Договор передан на подписание Застройщику",
"ДДУ подписан с покупателем"
),
StatusModel(
6,
"Договор подписан Застройщиком",
"Договор подписан"
),
StatusModel(
7,
"Договор скоро будет отправлен в Росреестр",
"Договор готовится для подачи на гос. регистрацию"
),
StatusModel(
8,
"Договор передан в Росреестр для гос. регистрации",
"Договор на регистрации"
),
StatusModel(
9,
"Ваш договор успешно зарегистрирован в Росреестре",
"Зарегистрирован"
),
StatusModel(
10,
"Договор находится в офисе продаж. Вы можете забрать его в любое время",
"Договор готов к выдаче"
)
,
StatusModel(
11,
"Договор выдан",
"Договор выдан"
)
)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.model.StatusModel
import com.biganto.visual.roompark.domain.use_case.DealseUseCase
import io.reactivex.Observable
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.10.2019.
*/
class DealsInteractor @Inject constructor(
val useCase:DealseUseCase
){
fun fetchDeals() = useCase.getDeals()
// Single.just(arrayListOf(dealFlat, dealParkign))
fun getStatusList() = Observable.just(statusList.sortedBy{ it.orderId })
companion object{
val statusList = arrayListOf<StatusModel>(
StatusModel(
1,
"Квартира забронирована за Вами. Скоро начнется подготовка договора",
"Бронь"
),
StatusModel(
2,
"Договор находится в процессе подготовки",
"Подготовка договора"
),
StatusModel(
3,
"Договор подготовлен и отправлен в офис продаж",
"Договор подготовлен"
),
StatusModel(
4,
"Договор находится в офисе продаж. Просим Вас приехать на подписание в назначенное время",
"Договор готов к подписанию"
),
StatusModel(
5,
"Договор передан на подписание Застройщику",
"ДДУ подписан с покупателем"
),
StatusModel(
6,
"Договор подписан Застройщиком",
"Договор подписан"
),
StatusModel(
7,
"Договор скоро будет отправлен в Росреестр",
"Договор готовится для подачи на гос. регистрацию"
),
StatusModel(
8,
"Договор передан в Росреестр для гос. регистрации",
"Договор на регистрации"
),
StatusModel(
9,
"Ваш договор успешно зарегистрирован в Росреестре",
"Зарегистрирован"
),
StatusModel(
10,
"Договор находится в офисе продаж. Вы можете забрать его в любое время",
"Договор готов к выдаче"
),
StatusModel(
11,
"Договор выдан",
"Договор выдан"
)
)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.model.PlanPresetModel
import com.biganto.visual.roompark.domain.use_case.EstateUseCase
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.10.2019.
*/
class EstateInteractor @Inject constructor(
private val useCase: EstateUseCase
){
fun getEstate(estateId:Int) = useCase.getEstate(estateId)
fun getPlanTypes(estateId: Int) =
useCase.getEstatePlanPresets(estateId)
fun getPlan(estateId: Int,planId:Int) = useCase.getPlan(estateId,planId)
fun getPlan(plan:PlanPresetModel) =
useCase.getPlan(
plan.estateId
,plan.planId
,furniture = plan.features.firstOrNull { it.featureName == "furniture" }?.switchedOn
,sizes = plan.features.firstOrNull { it.featureName == "sizes" }?.switchedOn
,walls = plan.features.firstOrNull { it.featureName == "walls" }?.switchedOn
,electric = plan.features.firstOrNull { it.featureName == "electric" }?.switchedOn
)
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.model.CommonInfoModel
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.FlatType
import com.biganto.visual.roompark.domain.model.PlanModel
import com.biganto.visual.roompark.domain.use_case.EstateUseCase
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.10.2019.
*/
class FavoritesInteractor @Inject constructor(
private val estateUseCase: EstateUseCase
) {
fun getFavoritesForCurrentUser() =
estateUseCase.fetchFavorites()
// Single.just(parkingEstateSample )
companion object {
val parkingEstateSample = arrayListOf(
EstateModel(
id = 2273,
type = FlatType.valueOf("parking".toUpperCase()),
number = "П1.021",
commonInfo = CommonInfoModel(
building = 1,
section_begin = 1,
floor = -1,
floor_max = 13,
area = 13.25f,
area_living = 0.00f,
kind = null,
decoration = "Без отделки, свободная планировка",
ceiling = null,
windows_face = null,
direction = null,
price_meter = null,
price = 1170025,
discount = null,
discount_amount = 970025,
rooms = null,
dependent = false
),
planPNG = null,
planJPG = null,
albumId = 10,
multitourId = null,
url = null
),
EstateModel(
id = 1905,
type = FlatType.valueOf("storage".toUpperCase()),
number = "104.23",
commonInfo = CommonInfoModel(
building = 1,
section_begin = 4,
floor = -1,
floor_max = 13,
area = 2.78f,
area_living = 0.00f,
kind = null,
decoration = "beez otdelki",
ceiling = null,
windows_face = null,
direction = null,
price_meter = 69676,
price = 193700,
discount = null,
discount_amount = null,
dependent = false,
rooms = null
),
planPNG = null,
planJPG = null,
albumId = 10,
multitourId = null,
url = null
),
EstateModel(
id = 1774,
type = FlatType.valueOf("flat".toUpperCase()),
number = "417",
commonInfo = CommonInfoModel(
building = 1,
section_begin = 6,
floor = 5,
floor_max = 20,
area = 40.00f,
area_living = 16.30f,
kind = "1к-2",
decoration = "Без отделки, свободная планировкаотделки",
ceiling = 2.95f,
windows_face = "Двор",
direction = "восток",
price_meter = 142808,
price = 5712300,
discount = 5.00f,
discount_amount = 5426685,
dependent = null,
rooms = 1
),
planPNG = PlanModel(
url = "https://room-park.ru/assets/estates/plan_png/00/00/06/1774-a0e8d6.png",
width = 281,
height = 292
),
planJPG = PlanModel(
url = "https://room-park.ru/assets/estates/plan_jpg/00/00/06/1774-6b330b.jpeg",
width = 693,
height = 720
),
albumId = 10,
url = null,
multitourId = 5790
// ,explications = arrayListOf<ExplicationListModel>(
// ExplicationListModel(
// planId = 0,
// items = arrayListOf(
// ExplicationModel(
// living = true,
// area = 16.30f,
// stateTitle = "Спальня"
// ),
// ExplicationModel(
// living = false,
// area = 11.40f,
// stateTitle = "Кухня"
// ),
// ExplicationModel(
// living = false,
// area = 1.90f,
// stateTitle = "Прихожая"
// ),
// ExplicationModel(
// living = false,
// area = 5.20f,
// stateTitle = "Коридор"
// ),
// ExplicationModel(
// living = false,
// area = 1.70f,
// stateTitle = "Лоджия"
// ),
// ExplicationModel(
// living = false,
// area = 3.50f,
// stateTitle = "Санузел"
// )
// )
// ), ExplicationListModel(
// planId = 1,
// items = arrayListOf(
// ExplicationModel(
// living = true,
// area = 16.30f,
// stateTitle = "Спальня"
// ),
// ExplicationModel(
// living = false,
// area = 11.40f,
// stateTitle = "Кухня"
// ),
// ExplicationModel(
// living = false,
// area = 5.20f,
// stateTitle = "Коридор"
// ),
// ExplicationModel(
// living = false,
// area = 1.90f,
// stateTitle = "Прихожая"
// ),
// ExplicationModel(
// living = false,
// area = 3.50f,
// stateTitle = "Санузел"
// ),
// ExplicationModel(
// living = false,
// area = 1.70f,
// stateTitle = "Лоджия"
// )
// )
// ), ExplicationListModel(
// planId = 2,
// items = arrayListOf(
// ExplicationModel(
// living = false,
// area = 1.00f,
// stateTitle = "Кухня-гостиная"
// ),
// ExplicationModel(
// living = false,
// area = 2.00f,
// stateTitle = "Коридор"
// ),
// ExplicationModel(
// living = false,
// area = 3.00f,
// stateTitle = "Прихожая"
// ),
// ExplicationModel(
// living = false,
// area = 4.00f,
// stateTitle = "Санузел"
// ),
// ExplicationModel(
// living = false,
// area = 5.00f,
// stateTitle = "Лоджия"
// )
// )
// )
// )
)
)
}
}
package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.model.*
import com.biganto.visual.roompark.domain.use_case.AlbumsUseCase
import com.biganto.visual.roompark.domain.use_case.CamsUseCase
import com.biganto.visual.roompark.domain.use_case.FeedUseCase
import io.reactivex.Observable
import java.util.*
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.10.2019.
*/
class FeedsInteractor @Inject constructor(
private val feedsUseCase: FeedUseCase,
private val albumsUseCase: AlbumsUseCase,
private val camsUseCase: CamsUseCase
) {
fun fetchTopFeeds(): Observable<FeedsHeaderModel> =
feedsUseCase.getFeeds().map {FeedsHeaderModel(it)}
//Single.just(testFeeds)
fun fetchArticles(feedAlias:String): Observable<ArticlesPreviewModel> =
feedsUseCase.getArticles(feedAlias)
// Single.just(
// when (feedId) {
// 1 -> testNewsArticles
// 2 -> testBlogsArticles
// 3 -> testDevArticles
// else -> error("unknown feedId")
// }
// )
fun fetchArticles(feedAlias:String,pageSize:Int,startIndex:Int): Observable<ArticlesPreviewModel> =
feedsUseCase.fetchArticlesPage(feedAlias,pageSize,startIndex)
fun fetchAlbums(): Observable<List<AlbumPreviewModel>> = albumsUseCase.getProgressAlbums()
fun fetchCams(): Observable<WebCamListModel> = camsUseCase.getWebCams()
//Observable.just(camsList)
companion object {
private const val sampleUrl: String =
"https://room-park.ru/assets/news_articles/preview/00/00/00/109-8c9b72.jpeg"
private const val sampleUrlB =
"https://room-park.ru/assets/news_articles/preview/00/00/00/104-2b4eff.jpeg"
private const val sampleUrlDev =
"https://room-park.ru/assets/news_articles/preview/00/00/00/96-a972cd.jpeg"
val testFeeds = FeedsHeaderModel(
listOf(
FeedModel(
"news", "НОВОСТИ"),
FeedModel("news", "БЛОГИ"),
FeedModel("news", "БЛОГ СТРОИТЕЛЬСТВА")
)
)
val testNewsArticles = ArticlesPreviewModel(
"news",
listOf(
ArticlePreviewModel(
1
, Date()
, "Первая новость"
, "Самый очаровательной анонс в мире"
, sampleUrl
, false
),
ArticlePreviewModel(
1
, Date()
, "Первая новость"
, "Самый очаровательной анонс в мире"
, sampleUrl
, false
),
ArticlePreviewModel(
1
, Date()
, "Первая новость"
, "Самый очаровательной анонс в мире"
, sampleUrl
, false
)
)
)
val testBlogsArticles = ArticlesPreviewModel(
"news",
listOf(
ArticlePreviewModel(
1
, Date()
, "Первая новость"
, "Самый очаровательной анонс в мире"
, sampleUrlB
, false
),
ArticlePreviewModel(
1
, Date()
, "НОВЫЙ ОФИС ПРОДАЖ ЖК «РУМЯНЦЕВО-ПАРК»"
, "Самый очаровательной анонс в мире"
, sampleUrlB
, false
),
ArticlePreviewModel(
1
, Date()
, "В «РУМЯНЦЕВО-ПАРК» ПРИСТУПИЛИ К МОНТАЖУ ОКОННЫХ БЛОКОВ"
, "Самый очаровательной анонс в мире"
, sampleUrlB
, false
)
)
)
val testDevArticles = ArticlesPreviewModel(
"news",
listOf(
ArticlePreviewModel(
1
, Date()
, "Ход1"
, "Самый очаровательной анонс в мире"
, sampleUrlDev
, false
),
ArticlePreviewModel(
1
, Date()
, "Ход2"
, "Самый очаровательной анонс в мире"
, sampleUrlDev
, false
),
ArticlePreviewModel(
1
, Date()
, "Ход3"
, "Самый очаровательной анонс в мире"
, sampleUrlDev
, false
)
)
)
val albumsPreviews = listOf<AlbumPhotoPreviewModel>(
AlbumPhotoPreviewModel(
1,
2,
Date(3131231L),
"Dom 1",
"https://room-park.ru/assets/gallery_images/image_2600/00/00/02/764-1d9795.png",
false
),
AlbumPhotoPreviewModel(
1,
2,
Date(31312323421L),
"Dom 1",
"https://room-park.ru/assets/gallery_images/image_2600/00/00/02/626-8afd4a.jpeg",
false
),
AlbumPhotoPreviewModel(
1,
2,
Date(3131232131L),
"Dom 1",
"https://room-park.ru/assets/gallery_images/image_2600/00/00/02/573-1fa95c.jpeg",
false
)
)
val camsList = WebCamListModel(arrayListOf(
WebCamModel(
"Камера Дом №1",
1,
arrayListOf()
// "blob:https://room-park.ru/fd579683-4bda-4f87-8663-dfe6c02af2f1"
),
WebCamModel(
"Камера Дом №2",
2,
arrayListOf()
// "blob:https://room-park.ru/58956693-f36d-4f8a-bc89-9bded955dfdd"
),
WebCamModel(
"Камера Дом №3",
3,
arrayListOf()
// "blob:https://room-park.ru/19d41d5a-565b-4fc9-a72f-06fbe87e8fb6"
)
))
}
}
package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.use_case.EstateUseCase
import io.reactivex.Observable
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.10.2019.
*/
class FindFlatInteractor @Inject constructor(
val useCase:EstateUseCase
){
fun getFlat(building:Int,flat:Int): Observable<Int>
= useCase.fetchEstate(building,flat)
.map { it.id }
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.model.AlbumSortedModel
import com.biganto.visual.roompark.domain.use_case.AlbumsUseCase
import io.reactivex.Observable
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.10.2019.
*/
class PhotoInteractor @Inject constructor(
private val useCase: AlbumsUseCase) {
fun fetchHeaderAlbums() = useCase.getProgressAlbums()
fun fetchAlbumPhotos(parentId: Int): Observable<List<AlbumSortedModel>> =
useCase.getChildAlbums(parentId)
.flatMap {
Observable.fromIterable(it).flatMap { alb ->
useCase.getPhotos(alb.albumId).take(1)
.map { photos ->
AlbumSortedModel(
alb.title,
alb.published,
alb.albumId,
photos
)
}
}.toList().toObservable()
}
.debounce(80L, TimeUnit.MILLISECONDS) // to reduce double list draw effect
fun fetchPhotos(albumId: Int) =
useCase.getPhotos(albumId).take(1)
fun fetchPhoto(photoId:Int) = useCase.getPhotoByid(photoId)
fun getAlbum(albumId:Int) = useCase.getDirectAlbum(albumId)
}
package com.biganto.visual.roompark.domain.interactor
import android.content.Context
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.domain.model.CachedDataModel
import com.biganto.visual.roompark.domain.model.PushSwitchModel
import com.biganto.visual.roompark.domain.model.SettingsModel
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.use_case.AuthUseCase
import com.biganto.visual.roompark.domain.use_case.SettingsUseCase
import com.biganto.visual.roompark.domain.use_case.SubscriptionUseCase
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.10.2019.
*/
class SettingsInteractor @Inject constructor(
private val auth: AuthUseCase,
private val settingsUseCase: SettingsUseCase,
private val activity: Context,
private val subUc: SubscriptionUseCase
){
fun getSubscriptions() =
subUc.getCurrentUserSubscriptions()
fun switchSubscription(model: SubscriptionModel, newState: Boolean): Completable =
when(newState){
true -> subUc.subscribeTopic(model.id,model.topic)
false -> subUc.unSubscribeTopic(model.id,model.topic)
}
private val plans
get() = settingsUseCase.planTypesSize.map {
CachedDataModel(activity.resources.getString(R.string.plans_cache),it,1)
}
private val tours
get() = settingsUseCase.toursSize.map {
CachedDataModel(activity.resources.getString(R.string.tours_cache),it,2)
}
private val feeds
get() = settingsUseCase.feedsSize.map {
CachedDataModel(activity.resources.getString(R.string.feeds_cache),it,3)
}
private val albums
get() = settingsUseCase.albumsSize.map {
CachedDataModel(activity.resources.getString(R.string.albums_cache),it,4)
}
private val overall
get() = settingsUseCase.overallSize.map {
CachedDataModel(activity.resources.getString(R.string.overall_cache),it,0)
}
fun deleteCacheFiles() = settingsUseCase.clearAllCache()
fun getCacheInfo() =
Observable.concatArray(plans, tours, feeds, albums, overall).toList().toObservable()
fun fetchSettings(): Observable<SettingsModel> = Observable.just(sampleSettings)
fun signOut() = auth.signOut().subscribeOn(Schedulers.io())
companion object{
val sampleSettings = SettingsModel(
arrayListOf(
PushSwitchModel("Новости",1,"zsldfj",true),
PushSwitchModel("Блог",2,"zsldfj",false),
PushSwitchModel("Ход Строительства Дом №1",3,"zsldfj",true),
PushSwitchModel("Сделка кв №451",4,"zsldfj",true),
PushSwitchModel("Сделка кв №452",5,"zsldfj",true)
),
arrayListOf(
CachedDataModel("Скачать карточки моих\n" +
"квартир из избранного\n" +
"и сделок",8415456588L,900),
CachedDataModel("Скачать виртуальные туры \n" +
"моих квартир из избранного\n" +
"и сделок ",656853321588L,999)
),
arrayListOf(
CachedDataModel("Всего скачано",2834264238L,1),
CachedDataModel("Карточки квартир",6434268L,2),
CachedDataModel("Виртуальные туры",1782234268L,3),
CachedDataModel("Новости и заметки",4323438L,4),
CachedDataModel("Фотографии",164444L,5)
)
)
}
}
package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.use_case.AuthUseCase
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.10.2019.
*/
class SplashInteractor @Inject constructor(
private val uc:AuthUseCase
){
fun getAuth() = uc.validateAuth()
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.model.WebCamListModel
import com.biganto.visual.roompark.domain.use_case.CamsUseCase
import io.reactivex.Observable
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.10.2019.
*/
class WebCamsInteractor @Inject constructor(
private val camsUseCase: CamsUseCase
) {
fun fetchCams(): Observable<WebCamListModel> = camsUseCase.getWebCams()
}
package com.biganto.visual.roompark.domain.model
data class DealPreviewModel(
val id:String,
val estateId:Int,
val tourId: Int?,
val type:FlatType,
val name:String,
val building:Int?,
val section:Int?,
val floor:Int?,
val area: Float?,
val statusNo : Int,
val dealSum : Int,
val dealPayout : Int,
val dealToPay : Int,
val dealTourIds : List<Int?>?,
val isViewed: Boolean = true,
val tourPreview: String?,
val isRead: Boolean,
val statusList : List<StatusModel>
){
constructor(data:Pair<DealModel,List<StatusModel>>) : this(
data.first.id,
data.first.estate.id,
data.first.estate.multitourId,
data.first.estate.type,
data.first.estate.number,
data.first.estate.commonInfo?.building,
data.first.estate.commonInfo?.section_begin,
data.first.estate.commonInfo?.floor,
data.first.estate.commonInfo?.area,
data.first.statusId,
data.first.opportunitySum,
data.first.paymentSum,
data.first.amount_pay_sum,
arrayListOf<Int?>(data.first.estate.multitourId),
false,
data.first.estate.multitourPreview,
data.first.isRead,
data.second
)
}
package com.biganto.visual.roompark.domain.model
import android.os.Parcel
import android.os.Parcelable
import com.biganto.visual.roompark.data.repository.db.requrey.model.GalleryPhotoEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.ImageAlbumEntity
import timber.log.Timber
import java.util.*
/**
* Created by Vladislav Bogdashkin on 23.09.2019.
*/
data class AlbumPreviewModel(
val albumId:Int,
val parentId:Int,
val title:String,
val published: Date,
val previewUrl:String,
val isRead:Boolean
)
data class AlbumPhotoPreviewModel(
val albumId:Int,
val parentId:Int,
val published: Date,
val title:String,
val previewUrl:String,
val isRead:Boolean
)
data class AlbumSortedModel(
val title:String,
val published: Date,
val albumId: Int,
val items:List<PhotoModel>
)
data class PhotoListModel(val items:List<PhotoModel>)
data class PhotoModel(
val photoId:Int,
val albumId:Int,
val description:String?,
val sort:Int,
val resolutionList:List<PhotoResolutionModel>
){
fun optimalResolution(width:Int, height:Int) =
resolutionList
.asSequence()
.filter { it.resWidth >= width || it.resWidth >= height }
.filter { it.resHeight >= height || it.resHeight >= width }
.onEach { Timber.d("res filtered: ${it.resWidth} / ${it.resHeight}") }
.minBy { it.resHeight * it.resWidth }
?: resolutionList.maxBy { it.resHeight * it.resWidth }!!
}
data class PhotoResolutionModel(
val resName:String,
val url:String,
val resWidth:Int,
val resHeight:Int
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
parcel.readInt(),
parcel.readInt()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(resName)
parcel.writeString(url)
parcel.writeInt(resWidth)
parcel.writeInt(resHeight)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<PhotoResolutionModel> {
override fun createFromParcel(parcel: Parcel): PhotoResolutionModel {
return PhotoResolutionModel(parcel)
}
override fun newArray(size: Int): Array<PhotoResolutionModel?> {
return arrayOfNulls(size)
}
}
}
fun fromEntity(entity: ImageAlbumEntity):AlbumPreviewModel =
AlbumPreviewModel(
albumId = entity.id,
parentId = -1,
title = entity.title,
published = entity.published,
previewUrl = entity.preview,
isRead = false
)
fun fromEntity(entity: GalleryPhotoEntity) =
PhotoModel(
photoId = entity.id,
albumId = entity.album_id,
description = entity.description,
sort = entity.sort,
resolutionList = List(entity.resolutions.size){
PhotoResolutionModel(
resName = entity.resolutions[it].res_name,
url = entity.resolutions[it].url,
resWidth = entity.resolutions[it].width,
resHeight = entity.resolutions[it].height
)}
)
\ No newline at end of file
package com.biganto.visual.roompark.domain.model
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
data class AuthInfoModel(
val token:String,
val id:Int,
val name:String,
val email:String
)
fun fromEntity(entity:UserEntity) = AuthInfoModel(
entity.authToken,
entity.uuid,
entity.name,
entity.email
)
\ No newline at end of file
package com.biganto.visual.roompark.domain.model
import androidx.annotation.StringRes
import com.biganto.visual.roompark.R
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.ExplicationEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.PlanPreset
/**
* Created by Vladislav Bogdashkin on 23.09.2019.
*/
const val DEFAULT_WALLS_FEATURE_NAME = "walls"
data class DealListModel(val deals:List<DealListModel>)
data class StatusModel(
val orderId:Int,
val title:String,
val shortTitle:String
)
data class DealModel(
val id:String,
val estate_id:String,
val opportunitySum:Int,
val paymentSum:Int,
val amount_pay_sum:Int,
val statusId:Int,
val managerName: String,
val isRead: Boolean,
val estate:EstateModel
)
fun fromEntity(entity:DealEntity) = DealModel(
id = entity.id,
estate_id = entity.estateCrmId,
opportunitySum = entity.opportunitySum,
paymentSum = entity.paymentSum,
amount_pay_sum = entity.amountPaySum,
statusId = entity.statusId,
managerName = entity.managerName,
isRead = entity.read,
estate = fromEntity(entity.estate as EstateEntity)
)
data class EstateModel(
val id:Int,
val type:FlatType,
val number:String,
val sectionBegin:Int?=null,
val sectionEnd:Int?=null,
val planPNG:PlanModel?,
val planJPG:PlanModel?,
val rooms:Int?=null,
val albumId:Int?=null,
val multitourId:Int?=null,
val multitourPreview:String?=null,
val commonInfo:CommonInfoModel? = null,
val url:String?
)
enum class FlatType{
FLAT,
PARKING,
STORAGE
}
@StringRes fun FlatType.typeDoubleString() =
when(this){
FlatType.FLAT -> R.string.estate_type_flat_long
FlatType.PARKING -> R.string.estate_type_parking_long
FlatType.STORAGE -> R.string.estate_type_store_long
else -> R.string.estate_type_other_long
}
@StringRes fun FlatType.typeShortString() =
when(this){
FlatType.FLAT -> R.string.estate_type_flat_short
FlatType.PARKING -> R.string.estate_type_parking_short
FlatType.STORAGE -> R.string.estate_type_store_short
else -> R.string.estate_type_other_short
}
fun fromEntity(entity:EstateEntity): EstateModel {
val model = EstateModel(
id = entity.id,
type = FlatType.valueOf(entity.type.toUpperCase()),
number = entity.number,
sectionBegin = entity.sectionBegin,
sectionEnd = entity.sectionEnd,
planPNG = null,
planJPG = null,
rooms = entity.rooms,
albumId = entity.albumId,
multitourId = entity.multitourId,
multitourPreview = entity.multitourPreview,
commonInfo = CommonInfoModel(
building = entity.info_building,
section_begin = entity.sectionBegin,
floor = entity.info_floor,
floor_max = entity.info_floor_max,
area = entity.info_area,
area_living = entity.info_area_living,
kind = entity.info_kind,
dependent = entity.info_dependent,
decoration = entity.info_decoration,
ceiling = entity.info_ceiling,
windows_face = entity.info_window_face,
direction = entity.info_direction,
price_meter = entity.info_price_meter,
price = entity.info_price,
// discount = entity.di
// discount_amount = entity
rooms = entity.rooms
),
url = entity.url
)
return model
}
data class PlanModel(
val url:String,
val width:Int,
val height:Int
)
data class CommonInfoModel(
val building: Int,
val section_begin: Int?,
val floor: Int,
val floor_max: Int,
val area: Float?,
val area_living: Float?,
val kind: String?,
val dependent:Boolean?=null,
val decoration: String?,
val ceiling: Float?,
val windows_face: String?,
val direction: String?,
val price_meter: Int?,
val price: Int?,
val discount: Float? = null,
val discount_amount: Int? = null,
val rooms: Int?
)
fun fromEntity(entity : PlanPreset) = PlanPresetModel(
planId = entity.planId,
estateId = entity.estateId.id,
title = entity.title,
features = entity.features.asSequence()
.map { FeatureModel.fromFeature(it) }.toList(),
//default true value for walls (otherwise plan may be empty)
explication = List(entity.explication.size)
{ ExplicationModel((entity.explication[it] as ExplicationEntity))}
)
data class PlanPresetModel(
val planId:Int,
val explication:List<ExplicationModel>,
var estateId:Int,
val title:String,
val features:List<FeatureModel>
){
inline fun <reified T : FeatureModel> switchFeature(value:Boolean){
features.first { it is T}.switchedOn = value
}
}
data class ExplicationModel(
val living: Boolean,
val area: Float,
val title: String
){
constructor(entity:ExplicationEntity):this(entity.living,entity.area,entity.title)
}
/*
data class CommonEstateInfoModel(
val corpus:Int,
val section:Int,
val floor:Int,
val totalFloors:Int,
val totalArea:Float,
val livingArea:Float,
val rooms:Int,
val flatType:String,
val furnishType:String,
val ceilingHieght:Float,
val windowViewTGype:String,
val sideOfTheWorld:String,
val costPerM2:Float,
val totalSum:Float
)
*/
package com.biganto.visual.roompark.domain.model
import com.biganto.visual.roompark.data.repository.db.requrey.TitledPhoto
import com.biganto.visual.roompark.data.repository.db.requrey.model.ArticleEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.FeedEntity
import java.util.*
/**
* Created by Vladislav Bogdashkin on 23.09.2019.
*/
const val DEFAULT_ROOM_PARK_PREVIEW = "https://room-park.ru/assets/news_articles/preview/00/00/00/15-fe1886.jpeg"
data class FeedModel(val title:String, val alias:String)
data class FeedsHeaderModel(val feeds:List<FeedModel>)
data class ArticlePreviewModel(
val articleId:Int,
val published: Date,
val title:String,
val announce:String,
val previewUrl:String,
val isRead:Boolean
){
override fun equals(other: Any?): Boolean {
if (other is ArticlePreviewModel)
return this.articleId == other.articleId
return super.equals(other)
}
}
data class ArticlesPreviewModel(val parentFeedAlias:String,val articles:List<ArticlePreviewModel>)
data class ArticleModel(
val articleId:Int,
val published: Date,
val title:String,
val htmlBody:String,
val previewUrl:String,
val bottomPhotoList:List<TitledPhoto>?,
val isRead:Boolean
)
fun fromEntity(entity: FeedEntity):FeedModel = FeedModel(
title = entity.title,
alias = entity.alias
)
fun fromEntityPreview(entity: ArticleEntity):ArticlePreviewModel = ArticlePreviewModel(
articleId = entity.id,
published = entity.published,
title = entity.title,
announce = entity.announce?:"",
previewUrl = entity.preview?:"",
isRead = entity.read
)
fun fromEntity(parentId:String,entity: List<ArticleEntity>):ArticlesPreviewModel =
ArticlesPreviewModel(
parentFeedAlias = parentId,
articles = fromEntity(entity,::fromEntityPreview)
)
fun fromEntity(entity: ArticleEntity) : ArticleModel =
ArticleModel(
articleId = entity.id,
published = entity.published,
title = entity.title,
htmlBody = entity.body!!,
previewUrl = entity.preview?:DEFAULT_ROOM_PARK_PREVIEW,
bottomPhotoList = entity.photo,
isRead = entity.read
)
fun <E,M> fromEntity(raw: List<E>,block:(E)->M):List<M> = List(raw.size) { index-> block(raw[index])}
package com.biganto.visual.roompark.domain.model
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
data class FlatNum(
val corpusId:Int,
val flatNum:Int
)
\ No newline at end of file
package com.biganto.visual.roompark.domain.model
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
data class PlanTypeModel(
val id:Int,
val planTypeId:Int,
val estateId:Int,
val features:List<FeatureModel>,
val uri:String
)
sealed class FeatureModel(
val featureName:String,
val featureTitle:String,
var switchedOn: Boolean
){
companion object{
fun fromFeature(featureAlias:String):FeatureModel =
when(featureAlias){
"furniture" -> Furniture()
"sizes" -> Sizes()
"electric" -> Electric()
"walls" -> Walls()
else -> error("Unsupported feature type")
}
}
class Furniture:FeatureModel("furniture","Мебель",false)
class Sizes:FeatureModel("sizes","Размеры",false)
class Electric:FeatureModel("electric","Электрика",false)
class Walls:FeatureModel("walls","Перегородки",true)
}
package com.biganto.visual.roompark.domain.model
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
data class PushSwitchModel(
val title:String,
val innerId:Int,
val pushId:String,
val switchState: Boolean
)
data class CachedDataModel(
val title:String,
val amountBytes:Long,
val id:Int //>?
)
data class SettingsModel(
val pushItems:List<PushSwitchModel>,
var offlineStoreData:List<CachedDataModel>,
var cachedData:List<CachedDataModel>
)
\ No newline at end of file
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)
package com.biganto.visual.roompark.domain.model
/**
* Created by Vladislav Bogdashkin on 22.10.2019.
*/
data class TourRequestModel(val building:Int, val flat:Int)
data class TourResponse(val response:List<TourModel>)
data class TourModel(val tourId:String, val title:Int, val preview:String?)
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.retrofit.raw.WebCamRaw
/**
* Created by Vladislav Bogdashkin on 23.09.2019.
*/
data class WebCamModel(
val title:String,
val index:Int,
val streams:List<StreamModel>
)
data class StreamModel(
val res_name:String,
val hls:String,
val rtmp:String
)
data class WebCamListModel(val items:List<WebCamModel>)
fun fromRaw(raw:StreamRaw) = StreamModel(
raw.res_name,
raw.hls,
raw.rtmp
)
fun fromRaw(raw:WebCamRaw) = WebCamModel(
raw.title,
raw.id,
List(raw.streams.size){i -> fromRaw(raw.streams[i]) }
)
fun fromRawList(raw:List<WebCamRaw>) =
WebCamListModel(List(raw.size){ i -> fromRaw(raw[i]) })
package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.domain.contract.DevProgressContract
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
class AlbumsUseCase @Inject constructor(
private val contract: DevProgressContract
){
fun getProgressAlbums() = contract.getProgressCards()
fun getChildAlbums(parentAlbumId:Int) =
contract.getProgressAlbumList(parentAlbumId)
fun getPhotos(parentAlbumId:Int) =
contract.getAlbumPhotoList(parentAlbumId)
fun getPhotoByid(photoId:Int) = contract.getAlbumPhoto(photoId)
fun getDirectAlbum(albumId:Int) =
contract.getAlbumPreview(albumId)
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.data.data_provider.AuthContractModule
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
class AuthUseCase @Inject constructor(
private val authContract: AuthContractModule
){
fun validateAuth() = authContract.validateAuthState()
fun signIn(login:String,pwd:String) = authContract.signIn(login,pwd)
fun signOut() = authContract.signOut()
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.domain.contract.DevProgressContract
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
class CamsUseCase @Inject constructor(
private val contract: DevProgressContract
){
fun getWebCams() = contract.getWebCamsList()
fun getCam(camId:Int) =
contract.getWebCamStream(camId)
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.domain.contract.DealContract
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
class DealseUseCase @Inject constructor(
private val contract: DealContract
) {
fun getDeals() = contract.getDeals()
fun setDealRead(id:String) = contract.setDealRead(id)
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.domain.contract.DealContract
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
class EstateUseCase @Inject constructor(
private val contract: DealContract
) {
fun fetchFavorites() = contract.getFavorites()
fun getEstate(estateId: Int) = contract.getEstate(estateId)
fun fetchEstate(building:Int, number:Int) =
contract.fetchEstate(building, number)
fun getEstatePlanPresets(estateId: Int) =
contract.getPlanTypes(estateId)
fun getPlan(estateId: Int, planId: Int) =
contract.getEmptyPlan(estateId, planId)
fun getPlan(
estateId: Int
, planId: Int
, furniture: Boolean?
, sizes: Boolean?
, walls: Boolean?
, electric: Boolean?
) = contract.getPlan(
estateId
, planId
, furniture
, sizes
, walls
, electric
)
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.domain.contract.FeedsContract
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
class FeedUseCase @Inject constructor(
private val contract: FeedsContract
){
fun getFeeds() = contract.fetchFeeds()
fun getArticles(feedAlias:String) =
contract.fetchFeedObservable(feedAlias)
fun fetchArticlesPage(feedAlias:String,pageSize:Int,startIndex:Int) =
contract.fetchFeedObservable(feedAlias,pageSize,startIndex)
fun getArticle(articleId:Int) =
contract.getArticle(articleId)
fun setRead(articleId: Int) = contract.articleRead(articleId)
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.data.data_provider.AuthContractModule
import com.biganto.visual.roompark.domain.contract.FilesContract
import io.reactivex.Observable
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 24.09.2019.
*/
class SettingsUseCase @Inject constructor(
private val authContract: AuthContractModule,
private val fileContract: FilesContract
){
fun signOut() = authContract.signOut()
fun clearAllCache() = fileContract.deleteAllFiles()
val planTypesSize
get() = Observable.just(fileContract.getPlansSize())
val albumsSize
get() = Observable.just(fileContract.getAlbumSize())
val feedsSize
get() = Observable.just(fileContract.getFeedSize())
val toursSize
get() = Observable.just(fileContract.getToursSize())
val overallSize
get() = Observable.just(fileContract.allCacheSize())
}
\ No newline at end of file
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.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.Completable
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
//import io.reactivex.rxkotlin
/**
* Created by Vladislav Bogdashkin on 01.11.2019.
*/
class SubscriptionUseCase @Inject constructor(
private val subscription: SubscriptionContract,
private val utils: DeviceUtilsContract,
private val auth: AuthContract
) {
fun subscribeTopic(subId: Int, topic: SubscriptionTopic): Completable =
Observable.zip(auth.currentUser(), utils.getDeviceId()
, BiFunction<UserEntity,String,SubscribeRequestModel> {
user, token -> SubscribeRequestModel(user,token)
})
.flatMapCompletable {
subscription.subscribeTopic(
it.user
, subId
, it.deviceToken
, topic = topic.topicName,
topic_id = topic.topicId
).subscribeOn(Schedulers.io())
}
fun unSubscribeTopic(subId: Int, topic: SubscriptionTopic): Completable =
Observable.zip(auth.currentUser(), utils.getDeviceId()
, BiFunction<UserEntity,String,SubscribeRequestModel> {
user, token -> SubscribeRequestModel(user,token)
})
.flatMapCompletable {
subscription.unSubscribeTopic(
it.user
, subId
, it.deviceToken
, topic = topic.topicName,
topic_id = topic.topicId
).subscribeOn(Schedulers.io())
}
fun getCurrentUserSubscriptions(): Observable<List<TitledSubscriptionModel>> =
auth.currentUser()
.map {user ->
val subList = user.subscriptions?: arrayListOf()
val list = List<TitledSubscriptionModel>(subList.size){i ->
val sub:SubscriptionModel = fromEntity(subList[i] as SubscriptionEntity)
var title = SubscriptionTopic.titleByTopic(sub.topic)
if (sub.topic is SubscriptionTopic.Deals){
val deal =
user.deals?.firstOrNull { d->d.id == sub.topic.topicId } as DealEntity
title = "$title № ${deal.estate.number}"
}
TitledSubscriptionModel(title,sub)
}
list
}
fun getSubscriptions(topic: SubscriptionTopic): Observable<SubscriptionModel> =
auth.currentUser()
.map {user ->
var sub = user.subscriptions
?.firstOrNull { it.topic == topic.topicName && it.number == topic.topicId }
Timber.d("fetched topic: $sub")
if (sub == null) {
sub = SubscriptionEntity()
sub.setOwner(user)
sub.setTopic(topic.topicName)
sub.setNumber(topic.topicId)
sub.setState(false)
}
Timber.w("sub is : $sub")
subscription.saveSubscribeState(sub as SubscriptionEntity).blockingFirst()
}
.map(::fromEntity)
}
private data class SubscribeRequestModel(
val user:UserEntity,
val deviceToken:String
)
package com.biganto.visual.roompark.presentation.screen.albums
import com.biganto.visual.roompark.conductor.BigantoBaseContract
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.domain.model.PhotoModel
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface AlbumsScreen : BigantoBaseContract<AlbumsScreenViewState> {
fun onAlbumSelected(): Observable<AlbumPreviewModel>
fun onPhotoSelected(): Observable<PhotoModel>
fun onSubscription(): Observable<Boolean>
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.albums
import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.View
import androidx.core.os.bundleOf
import androidx.core.widget.NestedScrollView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.domain.model.PhotoModel
import com.biganto.visual.roompark.presentation.screen.albums.util.AlbumListAdapter
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.photo.PhotoScreenController
import com.biganto.visual.roompark.util.monades.ExceptionString
import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration
import com.bluelinelabs.conductor.RouterTransaction
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.widget.checkedChanges
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import jp.wasabeef.glide.transformations.BlurTransformation
import jp.wasabeef.glide.transformations.ColorFilterTransformation
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
const val SELECTED_ALBUM_INDEX_KEY = "SELECTED_ALBUM_INDEX"
class AlbumsScreenController :
BigantoBaseController<AlbumsScreenViewState
, AlbumsScreen
, AlbumsScreenPresenter>
, AlbumsScreen {
constructor(args: Bundle):super(args)
constructor(id: Int) : super(bundleOf(SELECTED_ALBUM_INDEX_KEY to id))
@BindView(R.id.headers_recycler_view)
lateinit var headersRecyclerView: RecyclerView
@BindView(R.id.albums_recycler_view)
lateinit var albumsRecyclerView: RecyclerView
@BindView(R.id.photo_albums_container)
lateinit var nestedScrollView: NestedScrollView
@BindView(R.id.header_album_title)
lateinit var currentAlbomTitle: MaterialTextView
@BindView(R.id.bubble_slider)
lateinit var bubble: BubbleSlider
@BindView(R.id.switch1)
lateinit var albumSubscription: SwitchMaterial
//
// private val photosBackgroundTarget = object : com.squareup.picasso.Target {
// override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
//// nestedScrollView.background = placeHolderDrawable
// }
//
// override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
// Timber.e(e)
// }
//
// override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
// bitmap?.scaleCenterCrop(nestedScrollView)?.let {
// nestedScrollView.background = BitmapDrawable(activity?.resources, it)
// }
// }
// }
private fun bindRecycler() {
headersRecyclerView.isNestedScrollingEnabled = false
headersRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false)
headersRecyclerView.adapter = AlbumsHeaderAdapter()
headersRecyclerView.itemAnimator = null
if (headersRecyclerView.itemDecorationCount == 0)
headersRecyclerView.addItemDecoration(
CeilsDecoration(1
, resources?.getDimensionPixelSize(R.dimen.ceil_grid_padding))
)
bubble.setUpView(headersRecyclerView)
albumsRecyclerView.isNestedScrollingEnabled = false
albumsRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
albumsRecyclerView.adapter = AlbumListAdapter()
albumsRecyclerView.itemAnimator = null
}
override fun onViewBound(v: View) {
toolBar.hideAll()
bottomNavigationController.hide()
bindRecycler()
}
override fun injectDependencies() {
getComponent()
}
@Inject
override lateinit var injectedPresenter: AlbumsScreenPresenter
fun getComponent() = DaggerAlbumsScreenComponent.factory()
.create(RoomParkApplication.component
,activity as RoomParkMainActivity
,args.getInt(SELECTED_ALBUM_INDEX_KEY))
.inject(this)
override fun onSubscription(): Observable<Boolean> =
albumSubscription
.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())
.share()
override fun render(viewState: AlbumsScreenViewState) {
when(viewState){
is AlbumsScreenViewState.Idle -> render(viewState)
is AlbumsScreenViewState.AlbumsSelected -> render(viewState)
is AlbumsScreenViewState.AlbumsListLoaded-> render(viewState)
is AlbumsScreenViewState.HeaderAlbumChoosed-> render(viewState)
is AlbumsScreenViewState.PhotoSelected -> render(viewState)
is AlbumsScreenViewState.SomeError -> render(viewState)
is AlbumsScreenViewState.RestoreView -> render(viewState)
is AlbumsScreenViewState.SubscriptionStatus -> render(viewState)
is AlbumsScreenViewState.SubscriptionError -> render(viewState)
}
}
private fun render(viewState: AlbumsScreenViewState.Idle){
}
private fun render(viewState: AlbumsScreenViewState.PhotoSelected){
router.pushController(RouterTransaction.with(PhotoScreenController(viewState.photoId))
.pushChangeHandler(DialogChangeHandler())
.popChangeHandler(DialogChangeHandler())
)
}
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) =
showError(viewState.exception)
private fun render(viewState: AlbumsScreenViewState.AlbumsListLoaded) {
(headersRecyclerView.adapter as AlbumsHeaderAdapter).setItems(
viewState.list.asSequence().sortedByDescending { it.published }.toList()
)
(headersRecyclerView.adapter as AlbumsHeaderAdapter)
.getItemPosition(viewState.selectedAlbumId)
.let {
headersRecyclerView.smoothScrollToPosition(it)
bubble.onCurrentItemChanged(it)
}
viewState.list.first { it.albumId == viewState.selectedAlbumId }.let {
currentAlbomTitle.text = it.title
activity?.let { ctx ->
loadGlideBlurred(it.previewUrl, ctx, nestedBgTarget)
}
}
}
private fun render(viewState: AlbumsScreenViewState.HeaderAlbumChoosed) {
(headersRecyclerView.adapter as AlbumsHeaderAdapter)
.getItemPosition(viewState.item.albumId)
.let {
headersRecyclerView.smoothScrollToPosition(it)
bubble.onCurrentItemChanged(it)
}
currentAlbomTitle.text = viewState.item.title
activity?.let { ctx ->
loadGlideBlurred(viewState.item.previewUrl, ctx, nestedBgTarget)
}
}
private fun render(viewState: AlbumsScreenViewState.AlbumsSelected){
(albumsRecyclerView.adapter as AlbumListAdapter).setItems(
viewState.list.asSequence().sortedByDescending { it.published }.toList()
)
}
private fun render(viewState: AlbumsScreenViewState.RestoreView) {
(albumsRecyclerView.adapter as AlbumListAdapter).setItems(
viewState.restore.albumList.asSequence().sortedByDescending { it.published }.toList()
)
(headersRecyclerView.adapter as AlbumsHeaderAdapter).setItems(
viewState.restore.albumsPreview.asSequence().sortedByDescending { it.published }.toList()
)
(headersRecyclerView.adapter as AlbumsHeaderAdapter)
.getItemPosition(viewState.restore.currentIndex)
.let {
headersRecyclerView.smoothScrollToPosition(it)
bubble.onCurrentItemChanged(it)
}
viewState.restore.albumsPreview.first { it.albumId == viewState.restore.currentIndex }
.let {
currentAlbomTitle.text = it.title
activity?.let { ctx ->
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>(){
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
nestedScrollView.background = resource
}
}
private fun loadGlideBlurred(url:String, context: Context, drawable: Target<Drawable>) =
Glide.with(context)
.load(url)
.transform(BlurTransformation(13, 2))
.transform(ColorFilterTransformation(0xCC000000.toInt()))
.into(drawable)
override fun onAlbumSelected(): Observable<AlbumPreviewModel>
= (headersRecyclerView.adapter as AlbumsHeaderAdapter)
.onItemClicked
.map { it }
.debounce (300L,TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { (albumsRecyclerView.adapter as AlbumListAdapter).setItems(arrayListOf())}
override fun onPhotoSelected(): Observable<PhotoModel>
= (albumsRecyclerView.adapter as AlbumListAdapter)
.photoObs
.map { it }
.debounce (300L,TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
override fun getLayoutId(): Int = R.layout.albums_screen
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.albums
import android.content.Context
import com.biganto.visual.roompark.base.IBottomNavigation
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import javax.inject.Named
///**
// * Created by Vladislav Bogdashkin on 30.09.2019.
// */
@PerScreen
@Component(
modules = [AlbumsScreenModule::class]
,dependencies = [AppComponent::class]
)
interface AlbumsScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
,@BindsInstance @Named(SELECTED_ALBUM_INDEX_KEY) camIndex:Int
): AlbumsScreenComponent
}
fun inject(controller: AlbumsScreenController)
}
@Module
abstract class AlbumsScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
@PerScreen
@Binds
abstract fun provideBottomNavigation(activitiy: RoomParkMainActivity): IBottomNavigation
}
package com.biganto.visual.roompark.presentation.screen.albums
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.AlbumsInteractor
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
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 io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class AlbumsScreenPresenter @Inject constructor(
private val interactor: AlbumsInteractor
,@Named(SELECTED_ALBUM_INDEX_KEY) private var selectedIndex:Int
)
: BigantoBasePresenter<AlbumsScreen, AlbumsScreenViewState>() {
private var restoreModel = RestoreModel(listOf(), listOf(), selectedIndex,null)
override fun defaultErrorViewStateHandler() =
{ e: ExceptionString -> AlbumsScreenViewState.SomeError(e) }
private fun requestAlbum(id: Int): Observable<AlbumsScreenViewState> =
interactor.fetchAlbumPhotos(id)
.filter { !it.isNullOrEmpty() }
.doOnNext { restoreModel.albumList = 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() {
val onSubChecked = intent(AlbumsScreen::onSubscription)
.filter { restoreModel.sub != null }
.flatMap { newState ->
interactor.switchSubscription(restoreModel.sub!!, newState)
.andThen(
Observable.just<AlbumsScreenViewState>(
AlbumsScreenViewState.SubscriptionStatus(
newState
)
)
)
.onErrorReturn { AlbumsScreenViewState.SubscriptionError(!newState) }
}
val fetchParents = interactor.fetchHeaderAlbums()
.filter { !it.isNullOrEmpty() }
.doOnNext { restoreModel.albumsPreview = it }
.map { AlbumsScreenViewState.AlbumsListLoaded(it, selectedIndex) }
val fetchSelected = requestAlbum(selectedIndex)
val headerItemSelected = intent(AlbumsScreen::onAlbumSelected)
.doOnNext { selectedIndex = it.albumId }
.doOnNext { restoreModel.currentIndex = it.albumId }
.flatMap<AlbumsScreenViewState> { model ->
requestAlbum(model.albumId)
.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)
.map { AlbumsScreenViewState.PhotoSelected(it.photoId) }
val state = Observable.mergeDelayError(
arrayListOf(
fetchParents,
fetchSelected,
headerItemSelected,
photoSelected,
fetchSubscription(selectedIndex),
fetchHeaderSubscription,
onSubChecked
)
)
.doOnError { Timber.e(it) }
.onErrorReturn(::parseError)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(AlbumsScreenViewState::class.java), AlbumsScreen::render)
}
override fun detachView() {
super.detachView()
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>
,var albumsPreview:List<AlbumPreviewModel>
, var currentIndex:Int
, var sub: SubscriptionModel?)
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.albums
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.domain.model.AlbumSortedModel
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class AlbumsScreenViewState : BigantoBaseViewState() {
class Idle : AlbumsScreenViewState()
class AlbumsListLoaded(val list:List<AlbumPreviewModel>, val selectedAlbumId:Int) : AlbumsScreenViewState()
class AlbumsSelected(val list:List<AlbumSortedModel>) : AlbumsScreenViewState()
class HeaderAlbumChoosed(val item:AlbumPreviewModel) : AlbumsScreenViewState()
class PhotoSelected(val photoId:Int) : AlbumsScreenViewState()
class SomeError(val exception: ExceptionString) : 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
package com.biganto.visual.roompark.presentation.screen.albums.util
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyclerAdapter
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
/**
* Created by Vladislav Bogdashkin on 15.10.2019.
*/
class AlbumsHeaderAdapter : CommonRecyclerAdapter<AlbumsHeaderViewHolder, AlbumPreviewModel>() {
override val vhKlazz = AlbumsHeaderViewHolder::class
override fun getVhLayout(): Int = R.layout.album_header_preview_viewholder
fun getItemPosition(model: AlbumPreviewModel) = getItemPosition(model.albumId)
fun getItemPosition(modelId: Int) = list.indexOfFirst {
it.albumId == modelId
}
}
class AlbumsHeaderViewHolder(itemView: View) : CommonViewHolder<AlbumPreviewModel>(itemView) {
@BindView(R.id.preview) lateinit var preview:ImageView
@BindView(R.id.card_title) lateinit var articleTitle:TextView
@BindView(R.id.card_updated) lateinit var articleDate:TextView
override fun onViewBound(model: AlbumPreviewModel) {
articleTitle.text = model.title
Glide.with(itemView)
.load(model.previewUrl)
.centerCrop()
.fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(preview)
}
}
package com.biganto.visual.roompark.presentation.screen.albums.util
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import androidx.recyclerview.widget.StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.domain.model.AlbumSortedModel
import com.biganto.visual.roompark.domain.model.PhotoModel
import com.biganto.visual.roompark.domain.model.PhotoResolutionModel
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.view_utils.image_view.RoundedImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.view.detaches
import com.jakewharton.rxrelay2.BehaviorRelay
import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber
/**
* Created by Vladislav Bogdashkin on 16.10.2019.
*/
class AlbumListAdapter : CommonRecyclerAdapter<AlbumViewHolder, AlbumSortedModel>() {
override val vhKlazz = AlbumViewHolder::class
override fun getVhLayout(): Int = R.layout.date_album_viewholder
private val disp = CompositeDisposable()
val photoObs = BehaviorRelay.create<PhotoModel>()
override fun onBindViewHolder(holder: AlbumViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
holder.setIsRecyclable(false)
disp.addAll(
holder.photoClicksObservable()
?.doOnNext { Timber.d("photo clicked $it") }
?.takeUntil( holder.itemView.detaches() )
?.subscribe(photoObs)
)
}
}
class AlbumViewHolder(itemView: View) : CommonViewHolder<AlbumSortedModel>(itemView) {
@BindView(R.id.date_title_textview)
lateinit var albumTitle: MaterialTextView
@BindView(R.id.photos_recyclerview)
lateinit var photosRecyclerView: RecyclerView
// @BindView(R.id.camStatusIcon) lateinit var camStatusIcon:ImageView
private var adapter:PhotosAdapter? = null
fun photoClicksObservable() =
adapter?.onItemClicked
override fun onViewBound(model: AlbumSortedModel) {
albumTitle.text = model.title
photosRecyclerView.isNestedScrollingEnabled = false
photosRecyclerView.layoutManager =
StaggeredGridLayoutManager(4, RecyclerView.VERTICAL)
(photosRecyclerView.layoutManager as StaggeredGridLayoutManager)
.gapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
adapter = PhotosAdapter()
photosRecyclerView.adapter = adapter
photosRecyclerView.itemAnimator = null
adapter?.setItems(model.items)
}
}
class PhotosAdapter : CommonRecyclerAdapter<PhotosViewHolder, PhotoModel>() {
override val vhKlazz = PhotosViewHolder::class
override fun getVhLayout(): Int = R.layout.photo_preview_viewholder
}
class PhotosViewHolder(itemView: View) : CommonViewHolder<PhotoModel>(itemView) {
@BindView(R.id.photo_preview_imageview) lateinit var photoPreview: RoundedImageView
override fun onViewBound(model: PhotoModel) {
model.resolutionList.lowlest()?.let {
Glide.with(itemView)
.asBitmap()
.load(it.url)
.centerCrop()
.fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(photoPreview)
}}
}
fun List<PhotoResolutionModel>.lowlest() =
this.minBy { it.resWidth * it.resHeight }
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.albums.util
import android.content.Context
import android.graphics.SurfaceTexture
import android.util.AttributeSet
import android.view.TextureView
import androidx.recyclerview.widget.RecyclerView
import timber.log.Timber
/**
* Created by Vladislav Bogdashkin on 22.12.2019.
*/
class BubbleSlider @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : TextureView(context, attrs, defStyleAttr)
,TextureView.SurfaceTextureListener{
private var texThread: RendererThread?=null
private var recyclerView:RecyclerView? = null
private var actualPosition = -1
private fun invalidatePosition(){
val child = recyclerView?.findViewHolderForAdapterPosition(actualPosition)
if (child == null) texThread?.setCenter(-9999)
else texThread?.setCenter(child.itemView.x.toInt()+child.itemView.width/ 2)
}
fun setUpView(view:RecyclerView) {
recyclerView = view
recyclerView?.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
invalidatePosition()
}
}
fun onCurrentItemChanged(newPosition:Int){
actualPosition = newPosition
invalidatePosition()
texThread?.setIndex(actualPosition)
}
fun processSliding(_position:Float){
texThread?.setSlidePosition(_position)
}
init {
surfaceTextureListener=this
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
Timber.d("On Size Chagned")
super.onSizeChanged(w, h, oldw, oldh)
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) {
print(" onSurfaceTextureSizeChanged")
texThread?.setSize(width,height)
//Ignored
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
print(" onSurfaceTextureUpdated")
//Ignored
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean {
texThread?.isStopped=true
return true
}
override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {
Timber.d("avaliable surf: $surface")
surface?.let {
if (texThread?.isAlive==true)
return
texThread = RendererThread(context,it)
texThread?.setSize(width,height)
texThread?.start()
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
texThread?.isStopped=true
}
}
package com.biganto.visual.roompark.presentation.screen.albums.util
import android.content.Context
import android.graphics.*
import android.view.Surface
import com.biganto.visual.roompark.R
import timber.log.Timber
import kotlin.math.absoluteValue
import kotlin.math.tan
/**
* Created by Vladislav Bogdashkin on 18.07.2019.
*/
private const val CONST_SLEEP_THRESHOLD = 6L
private const val CONST_HEIGHT_MARGIN_BORDER_PX = 8
private const val CONST_DECAY_TIMER = 500L
class RendererThread(val context: Context, val surface: SurfaceTexture)
: Thread(){
private val dens:Float by lazy { context.resources.displayMetrics.density }
private val SLEEP_THRESHOLD get() = CONST_SLEEP_THRESHOLD
private val HEIGHT_MARGIN_BORDER_PX get() =
(CONST_HEIGHT_MARGIN_BORDER_PX *dens).int
private val DECAY_TIMER get() = CONST_DECAY_TIMER
private var isDirty = true
var isStopped = false
private var width = 0
private var height = 0
private var centerX = 0
private var destCenterX = 0
private var holdIndex = 0
get() = if (isDragging) field
else currentIndex
private var currentIndex:Int = 0
set(value) {Timber.w("currentIndex to: $value");field = value}
fun setIndex(index:Int){
if (!isDragging) holdIndex = index
currentIndex = index
isDirty=true
}
private val Float.int:Int get() {return this.toInt()}
fun setCenter(centerX:Int){
this.centerX = centerX
Timber.w("CENTER IS: $centerX")
isDirty = true
}
private var isDragging = false
private var slidePosition = 0f
fun setSlidePosition(newPosition:Float){
slidePosition = newPosition
isDirty = true
}
private var decayFraction = 0f
private fun timerDecay(mills:Long){
isDirty=true
decayFraction+=mills
if (decayFraction> DECAY_TIMER) isDirty=false
val dirty = centerSmooth()
if (!isDirty) isDirty = dirty
}
private fun centerSmooth():Boolean{
centerX = centerX.smoothLerp(destCenterX-centerX,decayFraction/DECAY_TIMER)
Timber.w("CENTER visa verse: $centerX -> $destCenterX")
return (centerX - destCenterX).absoluteValue>1
}
private fun Float.smoothLerp( delta: Float, fraction:Float =.4f): Float {
return this + fraction * (delta)
}
private fun Int.smoothLerp( delta: Int, fraction:Float =.4f): Int {
return (this + fraction * (delta)).int
}
fun setSize(w: Int, h: Int){
width=w
height=h
}
private val pinColor:Int by lazy {
context.resources.getColor(R.color.colorOpacityBackground,context.theme)
}
private val trianglePath:Path
get() {
val p = Path()
p.fillType = Path.FillType.EVEN_ODD
p.moveTo(centerX.toFloat(),0f)
p.lineTo(centerX.toFloat() + height*tan(Math.toRadians(45.0)).toFloat(),height.toFloat())
p.lineTo(centerX.toFloat() - height*tan(Math.toRadians(45.0)).toFloat(),height.toFloat())
p.lineTo(centerX.toFloat(),0f)
p.close()
return p
}
private val trianglePaint:Paint by lazy {
val p = Paint()
p.color = pinColor
p.style = Paint.Style.FILL_AND_STROKE
p.isAntiAlias = true
p
}
override fun run() {
super.run()
Timber.w("isStopped: $isStopped")
Timber.w("isDirty: $isDirty")
isDirty = true
while (!isStopped) {
while (!isDirty)
sleep(SLEEP_THRESHOLD) // in real life this sleep is more complicated
isDirty = false
val surf = Surface(surface)
val dRect = Rect(
0
, 0
, width
, height
)
val c = surf.lockCanvas(dRect)
c.drawColor(Color.WHITE)
c.drawPath(trianglePath,trianglePaint)
// c.drawRect(Rect(centerX - 30,height,centerX+30,0),trianglePaint)
// timerDecay(SLEEP_THRESHOLD)
surf.unlockCanvasAndPost(c)
surf.release()
// isDirty = false
}
Timber.w("STOPPED; $isStopped")
Timber.w("STOPPED; $isDirty")
isDirty=false
}
}
package com.biganto.visual.roompark.presentation.screen.article
import com.biganto.visual.roompark.conductor.BigantoBaseContract
import com.biganto.visual.roompark.domain.model.FeedModel
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface ArticleScreen : BigantoBaseContract<ArticleScreenViewState> {
}
package com.biganto.visual.roompark.presentation.screen.article
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import butterknife.OnClick
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.conductor.dialogs.PhotoDialogController
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.presentation.screen.article.util.ArticleBottomPhotoAdapter
import com.biganto.visual.roompark.presentation.screen.article.util.HtmlPageAdapter
import com.biganto.visual.roompark.presentation.screen.article.util.HtmlTag
import com.biganto.visual.roompark.util.extensions.formatToSimple
import com.biganto.visual.roompark.util.extensions.setGone
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 jp.wasabeef.glide.transformations.BlurTransformation
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
private const val ARTICLE_ID = "DIRECT_ARTICLE_ID_KEY"
class ArticleScreenController :
BigantoBaseController<ArticleScreenViewState
, ArticleScreen
, ArticleScreenPresenter>
, ArticleScreen {
constructor(args:Bundle):super(args)
constructor(id: Int) : super(bundleOf(ARTICLE_ID to id))
override fun injectDependencies() {
getComponent()
}
@Inject
override lateinit var injectedPresenter: ArticleScreenPresenter
@BindView(R.id.articleTitle)
lateinit var title:MaterialTextView
@BindView(R.id.articleHeaderBlock)
lateinit var headerBlock: ViewGroup
@BindView(R.id.articleBodyRecyclerView)
lateinit var articleRecyclerView: RecyclerView
@BindView(R.id.article_photos_recylcerView)
lateinit var bottomPhotosRecyclerView: RecyclerView
private val blurPreview:ImageView by lazy {
headerBlock.findViewById<ImageView>(R.id.articlePreviewBlurred)
}
private val articlePreview:ImageView by lazy {
headerBlock.findViewById<ImageView>(R.id.articlePreview)
}
val articleDate:MaterialTextView by lazy {
headerBlock.findViewById<MaterialTextView>(R.id.articleDate)
}
@OnClick(R.id.articleCloseButton)
fun onCloseArticle(){
handleBack()
}
private fun bindRecycler() {
articleRecyclerView.isNestedScrollingEnabled = true
articleRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
articleRecyclerView.adapter = HtmlPageAdapter()
articleRecyclerView.itemAnimator = null
bottomPhotosRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false)
bottomPhotosRecyclerView.adapter = ArticleBottomPhotoAdapter()
bottomPhotosRecyclerView.itemAnimator = null
}
override fun onViewBound(v: View) {
toolBar.setToolbar()
bottomNavigationController.hide()
bindRecycler()
detachDisposable.add(
(bottomPhotosRecyclerView.adapter as ArticleBottomPhotoAdapter)
.onItemClicked
.subscribe { router.pushController(
RouterTransaction.with(PhotoDialogController(it.url))
.pushChangeHandler(DialogChangeHandler())
.popChangeHandler(DialogChangeHandler())
)
}
)
}
override fun render(viewState: ArticleScreenViewState) {
super.render(viewState)
Timber.d("Render state $viewState")
when(viewState){
is ArticleScreenViewState.Idle -> render(viewState)
is ArticleScreenViewState.ArticleLoaded -> render(viewState)
is ArticleScreenViewState.SomeError -> render(viewState)
}
}
private fun render(viewState: ArticleScreenViewState.Idle){
}
private fun render(viewState: ArticleScreenViewState.SomeError) {
showError(viewState.error)
}
private fun render(viewState: ArticleScreenViewState.ArticleLoaded) {
title.text = viewState.item.title
val z = viewState.item.htmlBody
.replace("<\\br>","\n")
.replace("<br>","\n")
.replace("</br>","\n")
.replace("<p>","")
.replace("</p>","")
val tags = z.lines().map {
if (it.startsWith("<img"))
HtmlTag.ImageSource(it.substringAfter("src=\"").substringBefore("\""))
else HtmlTag.Text(it)
}.toList()
(articleRecyclerView.adapter as HtmlPageAdapter).setItems(tags)
articleDate.text = viewState.item.published.formatToSimple
bottomPhotosRecyclerView.setGone(viewState.item.bottomPhotoList.isNullOrEmpty())
viewState.item.bottomPhotoList?.let {
(bottomPhotosRecyclerView.adapter as ArticleBottomPhotoAdapter).setItems(it)
}
Glide.with(blurPreview)
.load(viewState.item.previewUrl)
.transform(BlurTransformation(40,4))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(blurPreview)
Glide.with(articlePreview)
.load(viewState.item.previewUrl)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(articlePreview)
}
private fun getComponent() = DaggerArticleScreenComponent.factory()
.create(RoomParkApplication.component
,activity as RoomParkMainActivity
,args.getInt(ARTICLE_ID))
.inject(this)
override fun getLayoutId(): Int = R.layout.feed_read_screen
}
package com.biganto.visual.roompark.presentation.screen.article
import android.content.Context
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import javax.inject.Named
const val ARTICLE_SCREEN_ARTICLEID="ARTICLE_SCREEN_ARTICLEID_KEY"
@PerScreen
@Component(
modules = [ArticleScreenModule::class],
dependencies = [AppComponent::class])
interface ArticleScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
,@BindsInstance @Named(ARTICLE_SCREEN_ARTICLEID) articleId:Int
): ArticleScreenComponent
}
val presenter: ArticleScreenPresenter
fun inject(controller: ArticleScreenController)
}
@Module
abstract class ArticleScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
// @PerScreen
// @Binds
// abstract fun provideContract(impl: FeedsContractModule): FeedsContract
}
package com.biganto.visual.roompark.presentation.screen.article
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.ArticleInteractor
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class ArticleScreenPresenter @Inject constructor(
private val interactor: ArticleInteractor,
@Named(ARTICLE_SCREEN_ARTICLEID) private val selectedArticleId:Int
)
: BigantoBasePresenter<ArticleScreen, ArticleScreenViewState>() {
override fun defaultErrorViewStateHandler() =
{e:ExceptionString -> ArticleScreenViewState.SomeError(e)}
override fun bindIntents() {
val prefetchCards = interactor.fetchArticle(selectedArticleId)
.map { ArticleScreenViewState.ArticleLoaded(it) }
val setRead = interactor.setArticleRead(selectedArticleId)
.andThen(Observable.just(ArticleScreenViewState.Idle()))
val state = restoreStateObservable
.mergeWith(prefetchCards)
.mergeWith(setRead)
.doOnError{ Timber.e(it)}
.onErrorReturn(::parseError)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(ArticleScreenViewState::class.java), ArticleScreen::render)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.article
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.ArticleModel
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class ArticleScreenViewState : BigantoBaseViewState() {
class Idle : ArticleScreenViewState()
class ArticleLoaded(val item: ArticleModel) : ArticleScreenViewState()
class SomeError(val error:ExceptionString ) : ArticleScreenViewState()
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.article.util
import android.view.View
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.data.repository.db.requrey.TitledPhoto
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.view_utils.image_view.RoundedImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import kotlin.reflect.KClass
/**
* Created by Vladislav Bogdashkin on 29.01.2020.
*/
class ArticleBottomPhotoAdapter : CommonRecyclerAdapter<ArticleBottomPhotoViewHolder,TitledPhoto>() {
override val vhKlazz: KClass<ArticleBottomPhotoViewHolder>
get() = ArticleBottomPhotoViewHolder::class
override fun getVhLayout(): Int = R.layout.photo_article_slider_viewholder
}
class ArticleBottomPhotoViewHolder(itemView: View) : CommonViewHolder<TitledPhoto>(itemView){
override fun onViewBound(model: TitledPhoto) {
// articleTitle.text = model.title
Glide.with(itemView)
.load(model.url)
.centerCrop()
.fitCenter()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into((itemView as RoundedImageView))
}
}
package com.biganto.visual.roompark.presentation.screen.article.util
import android.text.Html
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import butterknife.ButterKnife
import com.biganto.visual.roompark.R
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.textview.MaterialTextView
/**
* Created by Vladislav Bogdashkin on 29.01.2020.
*/
sealed class HtmlTag {
class Text(val text:String):HtmlTag()
class ImageSource(val src:String):HtmlTag()
}
class HtmlPageAdapter : RecyclerView.Adapter<HtmlTagViewHolder<HtmlTag>>() {
private val list = mutableListOf<HtmlTag>()
override fun getItemViewType(position: Int): Int {
return when (list[position]){
is HtmlTag.Text -> HtmlViewType.TEXT.viewType
is HtmlTag.ImageSource -> HtmlViewType.IMG.viewType
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HtmlTagViewHolder<HtmlTag> {
return when (HtmlViewType.fromInt(viewType)) {
HtmlViewType.TEXT ->
HtmlTextViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.htlm_text_viewholder, parent, false)
) as HtmlTagViewHolder<HtmlTag>
HtmlViewType.IMG ->
HtmlImageViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.htlm_image_viewholder, parent, false)
) as HtmlTagViewHolder<HtmlTag>
}
}
override fun onBindViewHolder(holder: HtmlTagViewHolder<HtmlTag>, position: Int) {
holder.bindModel(list[position])
}
override fun getItemCount(): Int = list.size
fun setItems(items:List<HtmlTag>){
this.list.clear()
this.list.addAll(items)
notifyDataSetChanged()
}
}
abstract class HtmlTagViewHolder<M:HtmlTag>(itemView: View) : RecyclerView.ViewHolder(itemView){
abstract fun onViewBound(model: M)
fun bindModel(model: M){
ButterKnife.bind(this, itemView)
bindedModel = model
onViewBound(model)
}
protected lateinit var bindedModel: M
}
class HtmlTextViewHolder(itemView: View) : HtmlTagViewHolder<HtmlTag.Text>(itemView) {
override fun onViewBound(model: HtmlTag.Text) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
(itemView as MaterialTextView).text = Html.fromHtml(model.text,Html.FROM_HTML_MODE_COMPACT)
} else {
(itemView as MaterialTextView).text = Html.fromHtml(model.text)
}
itemView.movementMethod = LinkMovementMethod.getInstance()
}
}
class HtmlImageViewHolder(itemView: View) :HtmlTagViewHolder<HtmlTag.ImageSource>(itemView) {
override fun onViewBound(model: HtmlTag.ImageSource) {
val url = "https://room-park.ru${model.src}"
Glide.with(itemView)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(itemView as ImageView)
}
}
private enum class HtmlViewType(val viewType:Int){
TEXT(0),
IMG(1);
companion object {
fun fromInt(type: Int) = HtmlViewType.values().first { it.viewType == type }
}
}
package com.biganto.visual.roompark.presentation.screen.auth
import com.biganto.visual.roompark.conductor.BigantoBaseContract
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface AuthScreen : BigantoBaseContract<AuthScreenViewState> {
fun tryAuth(): Observable<AuthInputModel>
}
data class AuthInputModel(val login:String, val pwd:String)
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.auth
import android.view.View
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.presentation.screen.home.home_routing.HomeBottomNavigationController
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputLayout
import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxbinding3.widget.textChanges
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class AuthScreenController :
BigantoBaseController<AuthScreenViewState
, AuthScreen
, AuthScreenPresenter>()
, AuthScreen {
override fun onViewBound(v: View) {
toolBar.setToolbar()
bottomNavigationController.hide()
}
@BindView(R.id.login_text_input) lateinit var loginInput:TextInputLayout
@BindView(R.id.password_text_input) lateinit var pwdInput:TextInputLayout
@BindView(R.id.sign_in_button) lateinit var signInButton:MaterialButton
override fun tryAuth(): Observable<AuthInputModel> =
signInButton.clicks()
.debounce (200L,TimeUnit.MILLISECONDS)
.doOnNext { signInButton.hideKeyboard() }
.map<AuthInputModel> {
AuthInputModel(
loginInput.editText?.text?.toString() ?: ""
, pwdInput.editText?.text?.toString() ?: ""
)
}
.observeOn(AndroidSchedulers.mainThread())
override fun injectDependencies() {
getComponent()
}
override fun onAttach(view: View) {
super.onAttach(view)
detachDisposable.addAll(
loginInput.editText?.textChanges()
?.filter { loginInput.isErrorEnabled }
?.subscribe {
loginInput.isErrorEnabled = false
loginInput.error = null
signInButton.isEnabled=true
},
pwdInput.editText?.textChanges()
?.filter { pwdInput.isErrorEnabled }
?.subscribe {
pwdInput.isErrorEnabled = false
pwdInput.error = null
signInButton.isEnabled=true
}
)
}
@Inject
override lateinit var injectedPresenter: AuthScreenPresenter
// @Inject
// lateinit var snacky:ISnackBarProvider
lateinit var rpActivity: RoomParkMainActivity
fun getComponent() = DaggerAuthScreenComponent
.factory()
.create(
RoomParkApplication.component
,activity as RoomParkMainActivity)
.inject(this)
override fun render(viewState: AuthScreenViewState) {
super.render(viewState)
Timber.d("Render state $viewState")
when(viewState){
is AuthScreenViewState.Idle -> render(viewState)
is AuthScreenViewState.Authorization -> render(viewState)
is AuthScreenViewState.SignedIn -> render(viewState)
is AuthScreenViewState.SignInError -> render(viewState)
is AuthScreenViewState.SomeError -> render(viewState)
is AuthScreenViewState.WrongLogin -> render(viewState)
is AuthScreenViewState.WrongPassword -> render(viewState)
}
}
private fun render(viewState: AuthScreenViewState.Idle){
loginInput.isErrorEnabled = false
loginInput.error = null
pwdInput.isErrorEnabled = false
pwdInput.error = null
signInButton.isEnabled=true
}
private fun render(viewState: AuthScreenViewState.Authorization){
// toolBar.hideAll()
signInButton.isEnabled = false
// snacky.showSnackBar("lul")
}
private fun render(viewState: AuthScreenViewState.SignedIn){
router.setRoot(RouterTransaction.with(HomeBottomNavigationController()))
// snacky.showSnackBar("lul")
}
private fun render(viewState: AuthScreenViewState.SignInError){
showError(viewState.exception)
signInButton.isEnabled=true
}
private fun render(viewState: AuthScreenViewState.WrongLogin){
// showError(viewState.exception)
loginInput.isErrorEnabled = true
loginInput.errorIconDrawable = null
viewState.exception.selectHandler(
{strId -> loginInput.error = resources?.getString(strId)},
{message -> loginInput.error = message}
)
}
private fun render(viewState: AuthScreenViewState.WrongPassword){
pwdInput.isErrorEnabled = true
pwdInput.errorIconDrawable = null
viewState.exception.selectHandler(
{strId -> pwdInput.error = resources?.getString(strId)},
{message -> pwdInput.error = message}
)
}
private fun render(viewState: AuthScreenViewState.SomeError) {
showError(viewState.exception)
signInButton.isEnabled=true
}
override fun getLayoutId(): Int = R.layout.authentication_screen
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.auth
import android.content.Context
import com.biganto.visual.roompark.base.IBottomNavigation
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
@PerScreen
@Component(
modules = [
AuthScreenModule::class
]
,dependencies = [AppComponent::class]
)
interface AuthScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent,
@BindsInstance activity: RoomParkMainActivity
): AuthScreenComponent
}
// val presenter: AuthScreenPresenter
fun inject(controller: AuthScreenController)
}
@Module(
// includes = [DataModule::class,DbModule::class,RetrofitModule::class]
)
abstract class AuthScreenModule{
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
// @PerScreen
// @Binds
// abstract fun provideAuth(contract: AuthContractModule): AuthContract
@Binds
abstract fun provideBottomNavigation(activitiy: RoomParkMainActivity): IBottomNavigation
}
package com.biganto.visual.roompark.presentation.screen.auth
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.AuthInteractor
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class AuthScreenPresenter @Inject constructor(
private val interactor: AuthInteractor
)
: BigantoBasePresenter<AuthScreen, AuthScreenViewState>() {
override fun defaultErrorViewStateHandler() =
{e:ExceptionString -> AuthScreenViewState.SomeError(e)}
override fun vsByCode(code: Int): (ExceptionString) -> AuthScreenViewState =
when (code) {
111 -> {e:ExceptionString -> AuthScreenViewState.WrongLogin(e)}
112 -> {e:ExceptionString -> AuthScreenViewState.WrongPassword(e)}
else -> {e:ExceptionString -> AuthScreenViewState.SomeError(e)}
}
override fun bindIntents() {
val onAuth = intent(AuthScreen::tryAuth)
.flatMap <AuthScreenViewState> { model ->
interactor.signIn(model.login, model.pwd)
.doOnNext { Timber.d("auth returned $it") }
.map { it }
.map<AuthScreenViewState> { AuthScreenViewState.SignedIn() }
.onErrorReturn{parseError(it)}
.startWith(Observable.just(AuthScreenViewState.Authorization()))
}
val state = restoreStateObservable
.mergeWith(onAuth)
.doOnError { Timber.e(it) }
.onErrorReturn(::parseError)
.subscribeOn(Schedulers.single())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(AuthScreenViewState::class.java), AuthScreen::render)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.auth
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class AuthScreenViewState : BigantoBaseViewState() {
class Idle : AuthScreenViewState()
class Authorization : AuthScreenViewState()
class SignedIn : AuthScreenViewState()
class SignInError(val exception: ExceptionString) : AuthScreenViewState()
class SomeError(val exception: ExceptionString) : AuthScreenViewState()
class WrongLogin(val exception: ExceptionString) : AuthScreenViewState()
class WrongPassword(val exception: ExceptionString) : AuthScreenViewState()
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.deal
import com.biganto.visual.roompark.conductor.BigantoBaseContract
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface DealScreen : BigantoBaseContract<DealScreenViewState> {
fun onSubscription(): Observable<Boolean>
}
package com.biganto.visual.roompark.presentation.screen.deal
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import androidx.core.os.bundleOf
import androidx.core.widget.NestedScrollView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.HeaderToolbarModel
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.typeDoubleString
import com.biganto.visual.roompark.domain.model.typeShortString
import com.biganto.visual.roompark.presentation.screen.estate.EstateScreenController
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.status_progress_view.StatusProgressAnimationState
import com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxbinding3.view.scrollChangeEvents
import com.jakewharton.rxbinding3.widget.checkedChanges
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
const val SELECTED_DEAL_ID_KEY = "SELECTED_DEAL_GUID"
class DealScreenController :
BigantoBaseController<DealScreenViewState
, DealScreen
, DealScreenPresenter>
, DealScreen {
constructor(args: Bundle):super(args)
constructor(id: String) : super(bundleOf(SELECTED_DEAL_ID_KEY to id))
override fun injectDependencies() {
getComponent()
}
@Inject
override lateinit var injectedPresenter: DealScreenPresenter
@BindView(R.id.deal_card_header)
lateinit var dealTitle: MaterialTextView
@BindView(R.id.deal_nestedScrollView)
lateinit var dealContainer: NestedScrollView
@BindView(R.id.statusContainer)
lateinit var statusContainer: LinearLayout
@BindView(R.id.progress_holder)
lateinit var progressLayout: LinearLayout
@BindView(R.id.info_ceil_1) lateinit var info1:View
@BindView(R.id.info_ceil_2) lateinit var info2:View
@BindView(R.id.info_ceil_3) lateinit var info3:View
@BindView(R.id.info_ceil_4) lateinit var info4:View
@BindView(R.id.deal_sum_value_text_view) lateinit var dealSum: MaterialTextView
@BindView(R.id.deal_payed_value_text_view) lateinit var dealPayed: MaterialTextView
@BindView(R.id.deal_to_pay_value_text_view) lateinit var dealSumToPay: MaterialTextView
@BindView(R.id.flat_card_card_view)
lateinit var toFlatView: View
@BindView(R.id.start_tour_card)
lateinit var startTourView: View
@BindView(R.id.start_tour_image_view) lateinit var tourScreen: RoundedImageView
private var servedDeal : DealModel? = null
override fun onAttach(view: View) {
super.onAttach(view)
detachDisposable.addAll(
toFlatView.clicks()
.map { servedDeal?.estate?.id?: -1000}
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
Timber.d("got card clicked $it")
router.pushController(
RouterTransaction.with(EstateScreenController(it))
.popChangeHandler(FadeChangeHandler())
.pushChangeHandler(FadeChangeHandler())
)
}
)
detachDisposable.add(
dealContainer.scrollChangeEvents()
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe {
if (it.scrollY > dealTitle.measuredHeight)
dealModel?.let { deal ->
toolBar.setToolbar(
HeaderToolbarModel(
true
, resources?.getString(R.string.deal_back_chevron_title)
, resources?.getString(deal.estate.type.typeShortString()
,deal.estate.number)
, true
)
)
}
else
toolBar.setToolbar(
HeaderToolbarModel(
true
, resources?.getString(R.string.deal_back_chevron_title)
, null
, true
)
)
}
)
}
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
}
.doOnNext { }
.debounce(600L, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
private fun setToolbar(){
toolBar.setToolbar(HeaderToolbarModel(
true
,resources?.getString(R.string.deal_back_chevron_title)
,null
,null)
)
}
private fun bindRecycler() {
}
override fun onViewBound(v: View) {
bottomNavigationController.hide()
setToolbar()
bindRecycler()
}
override fun render(viewState: DealScreenViewState) {
super.render(viewState)
Timber.d("Render state $viewState")
when(viewState){
is DealScreenViewState.Idle -> render(viewState)
is DealScreenViewState.LoadDeal -> render(viewState)
is DealScreenViewState.SomeError -> render(viewState)
is DealScreenViewState.RestoreView -> render(viewState)
is DealScreenViewState.SubscriptionStatus -> render(viewState)
is DealScreenViewState.SubscriptionError -> render(viewState)
}
}
private fun render(viewState: DealScreenViewState.Idle){
}
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
}
}
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
}
}
private fun render(viewState: DealScreenViewState.SomeError) =
showError(viewState.exception)
private val View.title
get() = this.findViewById<MaterialTextView>(R.id.info_ceil_header)
private val View.text
get() = this.findViewById<MaterialTextView>(R.id.info_ceil_content)
private var dealModel:DealModel? = null
private fun render(viewState: DealScreenViewState.LoadDeal) {
servedDeal = viewState.estate
dealTitle.text = resources?.getString(
viewState.estate.estate.type.typeDoubleString(),
viewState.estate.estate.number
)
info1.title.text = resources?.getString(R.string.building)
info1.text.text = viewState.estate.estate.commonInfo?.building.toString()
info2.title.text = resources?.getString(R.string.section_begin)
info2.text.text = viewState.estate.estate.commonInfo?.section_begin.toString()
info3.title.text = resources?.getString(R.string.floor)
info3.text.text = viewState.estate.estate.commonInfo?.floor.toString()
info4.title.text = resources?.getString(R.string.area)
info4.text.text =
resources?.getString(R.string.area_value,viewState.estate.estate.commonInfo?.area)
dealSum.text = viewState.estate.opportunitySum.toRubly()
dealPayed.text = viewState.estate.paymentSum.toRubly()
dealSumToPay.text = viewState.estate.amount_pay_sum.toRubly()
viewState.estate.estate.multitourPreview?.let {
Glide.with(tourScreen)
.load(it)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(tourScreen)
}
dealModel = viewState.estate
toolBar.setToolbar(HeaderToolbarModel(
true
,resources?.getString(R.string.deal_back_chevron_title)
,null
,true)
)
progressLayout.removeAllViews()
viewState.statusList.forEach {
val statusLayout = LayoutInflater.from(activity)
.inflate(R.layout.progress_status_title_vertical
,progressLayout
,false)
as LinearLayout
Timber.d("layouted: $statusLayout")
val statusCeil = statusLayout.findViewById<StatusProgressCeil>(R.id.status)
Timber.d("layouted ceail : $statusCeil")
val position = it.orderId
val statusCount = viewState.statusList.size
val lastStatusPassed = viewState.estate.statusId
statusCeil.setHasEnd(position != statusCount)
statusCeil.setHasStart(position > 1)
statusCeil.setIsEnabled(position <= lastStatusPassed)
statusCeil.setNext(position < lastStatusPassed)
if (position > lastStatusPassed)
statusCeil.setAnimState(StatusProgressAnimationState.DISABLE)
statusCeil.invalidate()
val statusTitle = statusLayout.findViewById<MaterialTextView>(R.id.title)
Timber.d("layouted statusTitle : $statusTitle")
statusTitle.text = it.shortTitle
progressLayout.addView(statusLayout)
}
progressLayout.invalidate()
}
private fun getComponent() = DaggerDealScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity
, args.getString(SELECTED_DEAL_ID_KEY)?: error("Deal id not found!")
)
.inject(this)
override fun getLayoutId(): Int = R.layout.deal_screen
}
package com.biganto.visual.roompark.presentation.screen.deal
import android.content.Context
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import javax.inject.Named
@PerScreen
@Component(
modules = [DealScreenModule::class],
dependencies = [AppComponent::class])
interface DealScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
,@BindsInstance @Named(SELECTED_DEAL_ID_KEY) selectedDealId:String
): DealScreenComponent
}
val presenter: DealScreenPresenter
fun inject(controller: DealScreenController)
}
@Module
abstract class DealScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
}
package com.biganto.visual.roompark.presentation.screen.deal
import android.content.Context
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.DealInteractor
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class DealScreenPresenter @Inject constructor(
private val interactor: DealInteractor,
private val context: Context,
@Named(SELECTED_DEAL_ID_KEY) private val dealId:String
)
: BigantoBasePresenter<DealScreen, DealScreenViewState>() {
override fun defaultErrorViewStateHandler() =
{ e: ExceptionString -> DealScreenViewState.SomeError(e) }
private var restoreModel = RestoreModel(null)
override fun detachView() {
super.detachView()
restoreStateObservable.accept(DealScreenViewState.RestoreView(restoreModel))
}
override fun bindIntents() {
val fetchDeal = interactor.getDeal(dealId)
.map<DealScreenViewState>{ deal ->
DealScreenViewState.LoadDeal(deal ,interactor.getStatusListSync())
}
val setRead = interactor.setDealRead(dealId)
.andThen(Observable.just(DealScreenViewState.Idle()))
val onSubChecked = intent(DealScreen::onSubscription)
.filter { restoreModel.sub != null }
.flatMap { newState ->
interactor.switchSubscription(restoreModel.sub!!, newState)
.andThen(
Observable.just<DealScreenViewState>(
DealScreenViewState.SubscriptionStatus(newState)
)
)
.onErrorReturn { DealScreenViewState.SubscriptionError(!newState) }
}
val fetchSubscription = interactor.getSubscriptions(dealId)
.doAfterNext { restoreModel.sub = it }
.map<DealScreenViewState> { DealScreenViewState.SubscriptionStatus(it.state) }
.startWith(Observable.just<DealScreenViewState>(DealScreenViewState.Idle()))
val state = Observable.mergeDelayError(
arrayListOf(
restoreStateObservable,
fetchDeal,
setRead,
onSubChecked,
fetchSubscription
))
.doOnError { Timber.e(it) }
.onErrorReturn(::parseError)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(DealScreenViewState::class.java), DealScreen::render)
}
}
data class RestoreModel(var sub: SubscriptionModel?)
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.deal
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.StatusModel
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class DealScreenViewState : BigantoBaseViewState() {
class Idle : DealScreenViewState()
class LoadDeal(val estate:DealModel,val statusList:List<StatusModel>) : 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()
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.deals
import com.biganto.visual.roompark.conductor.BigantoBaseContract
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface DealsScreen : BigantoBaseContract<DealsScreenViewState> {
}
package com.biganto.visual.roompark.presentation.screen.deals
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.HeaderToolbarModel
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
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.estate.EstateScreenController
import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class DealsScreenController :
BigantoBaseController<DealsScreenViewState
, DealsScreen
, DealsScreenPresenter>()
, DealsScreen {
override fun injectDependencies() {
getComponent()
}
@BindView(R.id.favorites_cards_recycler_view)
lateinit var dealsRecyclerView: RecyclerView
private fun bindRecycler() {
dealsRecyclerView.isNestedScrollingEnabled = true
dealsRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
dealsRecyclerView.adapter = DealsListAdapter()
dealsRecyclerView.itemAnimator = null
if (dealsRecyclerView.itemDecorationCount == 0)
dealsRecyclerView.addItemDecoration(
CeilsDecoration(1
, resources?.getDimensionPixelSize(R.dimen.ceil_grid_padding))
)
}
private fun setToolbar(){
dealsRecyclerView.isNestedScrollingEnabled = false
toolBar.setToolbar(
HeaderToolbarModel(
false,null
,resources?.getString(R.string.my_deals),null)
)
}
@Inject
override lateinit var injectedPresenter: DealsScreenPresenter
override fun onAttach(view: View) {
super.onAttach(view)
Timber.d("On attach View")
detachDisposable.addAll(
(dealsRecyclerView.adapter as DealsListAdapter)
.onItemClicked
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
Timber.d("got card clicked $it")
router.pushController(
RouterTransaction.with(DealScreenController(it.id))
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
)
},
(dealsRecyclerView.adapter as DealsListAdapter)
.startFlat
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
Timber.d("got estate clicked $it")
router.pushController(
RouterTransaction.with(EstateScreenController(it!!))
.popChangeHandler(FadeChangeHandler())
.pushChangeHandler(FadeChangeHandler())
)
},
(dealsRecyclerView.adapter as DealsListAdapter)
.startTour
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
Timber.d("got tour clicked $it")
showMessage(R.string.tour_not_allowed)
}
)
}
override fun onViewBound(v: View) {
bottomNavigationController.show()
setToolbar()
bindRecycler()
}
override fun render(viewState: DealsScreenViewState) {
super.render(viewState)
Timber.d("Render state $viewState")
when(viewState){
is DealsScreenViewState.Idle -> render(viewState)
is DealsScreenViewState.DealsLoaded -> render(viewState)
is DealsScreenViewState.SomeError -> render(viewState)
}
}
private fun render(viewState: DealsScreenViewState.Idle){
}
private fun render(viewState: DealsScreenViewState.DealsLoaded){
(dealsRecyclerView.adapter as DealsListAdapter).addItems(viewState.items)
}
private fun render(viewState: DealsScreenViewState.SomeError) =
showError(viewState.exception)
private fun getComponent() = DaggerDealsScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
override fun getLayoutId(): Int = R.layout.favorites_screen
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.deals
import android.content.Context
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
@PerScreen
@Component(
modules = [DealsScreenModule::class],
dependencies = [AppComponent::class])
interface DealsScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): DealsScreenComponent
}
val presenter: DealsScreenPresenter
fun inject(controller: DealsScreenController)
}
@Module
abstract class DealsScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
}
package com.biganto.visual.roompark.presentation.screen.deals
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.DealsInteractor
import com.biganto.visual.roompark.domain.model.DealPreviewModel
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class DealsScreenPresenter @Inject constructor(
private val interactor: DealsInteractor
)
: BigantoBasePresenter<DealsScreen, DealsScreenViewState>() {
override fun defaultErrorViewStateHandler() =
{e:ExceptionString -> DealsScreenViewState.SomeError(e)}
override fun bindIntents() {
val getStatusList = interactor.getStatusList()
val fetchDeals = interactor.fetchDeals()
.flatMap { deals ->
getStatusList
.map { List(deals.size) { index ->
DealPreviewModel(
Pair(deals[index], it)
)
} }
}
.map(DealsScreenViewState::DealsLoaded)
val state = restoreStateObservable
.mergeWith(fetchDeals)
.doOnError{ Timber.e(it)}
.onErrorReturn(::parseError)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(DealsScreenViewState::class.java), DealsScreen::render)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.deals
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.DealPreviewModel
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class DealsScreenViewState : BigantoBaseViewState() {
class Idle : DealsScreenViewState()
class DealsLoaded(val items:List<DealPreviewModel>) : DealsScreenViewState()
class SomeError(val exception: ExceptionString) : DealsScreenViewState()
}
package com.biganto.visual.roompark.presentation.screen.deals.util
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.domain.model.DealPreviewModel
import com.biganto.visual.roompark.domain.model.StatusModel
import com.biganto.visual.roompark.domain.model.typeDoubleString
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.roompark.util.extensions.toRubly
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.StatusProgressCeil
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
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.
*/
class DealsListAdapter : CommonRecyclerAdapter<DealViewHolder, DealPreviewModel>() {
override val vhKlazz = DealViewHolder::class
override fun getVhLayout() = R.layout.deal_card_viewholder
private val onFlatClicked = PublishSubject.create<Int>()
private val onTourClickced = PublishSubject.create<Int>()
override fun onBindViewHolder(holder: DealViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
holder.onStartFlatObs.subscribe(onFlatClicked)
holder.onStartTourObs.subscribe(onTourClickced)
}
val startFlat get() = onFlatClicked
val startTour get() = onTourClickced
}
class DealViewHolder(itemView: View) : CommonViewHolder<DealPreviewModel>(itemView) {
@BindView(R.id.deal_card_header) lateinit var estateTitle: TextView
@BindView(R.id.common_info_block) lateinit var commonInfo:View
@BindView(R.id.start_tour_button) lateinit var startTour:View
@BindView(R.id.flat_card_card_view) lateinit var startFlat:View
@BindView(R.id.info_ceil_1) lateinit var info1:View
@BindView(R.id.info_ceil_2) lateinit var info2:View
@BindView(R.id.info_ceil_3) lateinit var info3:View
@BindView(R.id.info_ceil_4) lateinit var info4:View
@BindView(R.id.deal_sum_value_text_view) lateinit var dealSum:TextView
@BindView(R.id.deal_payed_value_text_view) lateinit var dealPayed:TextView
@BindView(R.id.deal_to_pay_value_text_view) lateinit var dealSumToPay:TextView
@BindView(R.id.progress_holder) lateinit var statusContainer:LinearLayout
@BindView(R.id.statusContainer) lateinit var statusLayout:LinearLayout
@BindView(R.id.start_tour_image_view) lateinit var tourScreen:RoundedImageView
@BindView(R.id.deal_read) lateinit var dealReadFlag:View
val onStartFlatObs: Observable<Int?> get() = startFlat.clicks().map { bindedModel.estateId }
val onStartTourObs: Observable<Int?> get() = startTour.clicks().map { bindedModel.tourId }
override fun onViewBound(model: DealPreviewModel) {
dealReadFlag.setGone(model.isRead)
estateTitle.text =
itemView.context.resources?.getString(model.type.typeDoubleString(),model.name)
renderCommonInfo(model)
if (model.dealTourIds.isNullOrEmpty()) startTour.visibility = View.GONE
else startTour.visibility = View.VISIBLE
dealSum.text = model.dealSum.toRubly()
dealPayed.text = model.dealPayout.toRubly()
dealSumToPay.text = model.dealToPay.toRubly()
statusContainer.deployProgressView(model.statusList,model.statusNo,false)
val currentStatusText = LayoutInflater.from(itemView.context)
.inflate(R.layout.status_title_textview,statusContainer,false)
as MaterialTextView
currentStatusText.text = model.statusList[model.statusNo-1].title
statusLayout.addView(currentStatusText,0)
statusLayout.invalidate()
model.tourPreview?.let {
Glide.with(tourScreen)
.load(it)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(tourScreen)
}
}
private fun renderCommonInfo(info: DealPreviewModel){
if (info.building == null) info1.visibility = View.GONE
else { info1.title().text = "Корпус"; info1.text().text = info.building.toString()}
if (info.section == null) info1.visibility = View.GONE
else { info2.title().text = "Секция"; info2.text().text = info.section.toString()}
if (info.floor == null) info1.visibility = View.GONE
else { info3.title().text = "Этаж"; info3.text().text = info.floor.toString()}
if (info.area == null) info1.visibility = View.GONE
else { info4.title().text = "Общая, м²"; info4.text().text = info.area.toString()}
}
private fun View.title() = this.findViewById<TextView>(R.id.info_ceil_header)
private fun View.text() = this.findViewById<TextView>(R.id.info_ceil_content)
}
private fun LinearLayout.deployProgressView(
list: List<StatusModel>,
currentStatus: Int,
isVertical: Boolean
){
this.removeAllViews()
this.weightSum = list.size.toFloat()
list.sortedBy { it.orderId }.forEach{
this.addView(createStatusView(this,isVertical,it.orderId,list.size,currentStatus))
}
this.invalidate()
}
fun createStatusView(container: ViewGroup
, isVertical: Boolean
,position: Int
,statusCount: Int
,lastStatusPassed:Int): StatusProgressCeil {
val scale = container.resources.displayMetrics.density.toDouble()
val param24dp = kotlin.math.ceil(24 * scale).toInt()
val wrapContent = ViewGroup.LayoutParams.WRAP_CONTENT
val ceil = LayoutInflater.from(container.context)
.inflate(R.layout.status_view,container,false) as StatusProgressCeil
ceil.layoutParams.height = if (isVertical) wrapContent else param24dp
ceil.layoutParams.width = if (isVertical) param24dp else wrapContent
(ceil.layoutParams as LinearLayout.LayoutParams).weight = 1f
(ceil.layoutParams as LinearLayout.LayoutParams).gravity = Gravity.BOTTOM
ceil.setHasEnd(position != statusCount)
ceil.setHasStart(position > 1)
ceil.setIsEnabled(position <= lastStatusPassed)
ceil.setNext(position < lastStatusPassed)
if (position > lastStatusPassed)
ceil.setAnimState(StatusProgressAnimationState.DISABLE)
ceil.invalidate()
return ceil
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.estate
import com.biganto.visual.roompark.conductor.BigantoBaseContract
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface EstateScreen : BigantoBaseContract<EstateScreenViewState> {
fun planTypesTabSelected(): Observable<Int>
fun switchSizes(): Observable<Pair<Int,Boolean>>
fun switchWalls(): Observable<Pair<Int,Boolean>>
fun switchFurniture(): Observable<Pair<Int,Boolean>>
fun switchElectric(): Observable<Pair<Int,Boolean>>
fun showCommonInfo(): Observable<Int>
fun showExplication(): Observable<Int>
}
package com.biganto.visual.roompark.presentation.screen.estate
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.webkit.WebView
import android.widget.LinearLayout
import androidx.core.os.bundleOf
import androidx.core.view.isNotEmpty
import androidx.core.widget.NestedScrollView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.base.StatusState
import com.biganto.visual.roompark.base.StatusToolbarModel
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.domain.model.*
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.startUrl
import com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
import com.bumptech.glide.Glide
import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.tabs.TabLayout
import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.material.selections
import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxbinding3.view.scrollChangeEvents
import com.jakewharton.rxbinding3.widget.checkedChanges
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
const val SELECTED_ESTATE_ID_KEY = "SELECTED_ESTATE_INDEX"
class EstateScreenController :
BigantoBaseController<EstateScreenViewState
, EstateScreen
, EstateScreenPresenter>
, EstateScreen {
override fun showCommonInfo(): Observable<Int> =
commonInfoTab.clicks()
.doOnNext {
commonInfoTab.background = resources?.getDrawable(R.drawable.bottom_line_text_view)
explicationTab.background = null
}
.map { 1 }
.observeOn(AndroidSchedulers.mainThread())
override fun showExplication(): Observable<Int> =
explicationTab.clicks()
.doOnNext {
explicationTab.background = resources?.getDrawable(R.drawable.bottom_line_text_view)
commonInfoTab.background = null
}
.map { planTypesTabLayout.selectedTabPosition }
.observeOn(AndroidSchedulers.mainThread())
private fun ViewGroup.switchMatch() =
this.findViewById<SwitchMaterial>(R.id.switch1)
.checkedChanges()
.filter {planTypesTabLayout.isNotEmpty()}
.filter{planTypesTabLayout.selectedTabPosition>=0}
.map {
Pair(planTypesTabLayout.selectedTabPosition,it)
}
override fun switchSizes(): Observable<Pair<Int, Boolean>> =
sizesSwitcher.switchMatch()
override fun switchWalls(): Observable<Pair<Int, Boolean>> =
wallsSwitcher.switchMatch()
override fun switchFurniture(): Observable<Pair<Int, Boolean>> =
furnSwitcher.switchMatch()
override fun switchElectric(): Observable<Pair<Int, Boolean>> =
electricSwitcher.switchMatch()
override fun planTypesTabSelected(): Observable<Int> =
planTypesTabLayout.selections()
.debounce (200L, TimeUnit.MILLISECONDS)
.map { it.position }
.observeOn(AndroidSchedulers.mainThread())
constructor(args: Bundle):super(args)
constructor(id: Int) : super(bundleOf(SELECTED_ESTATE_ID_KEY to id))
override fun injectDependencies() {
getComponent()
}
@Inject
override lateinit var injectedPresenter: EstateScreenPresenter
@BindView(R.id.flatTypesCustomView)
lateinit var flatTypeView: ViewGroup
@BindView(R.id.planTypesTabs)
lateinit var planTypesTabLayout: TabLayout
@BindView(R.id.flat_plan_webview)
lateinit var planWebView: WebView
@BindView(R.id.flat_nested_scroll)
lateinit var flatScroll: NestedScrollView
@BindView(R.id.flat_content_recycler_view)
lateinit var flatInfoRecyclerView: RecyclerView
@BindView(R.id.flat_title)
lateinit var flatTitle: MaterialTextView
@BindView(R.id.sizes_switch_container)
lateinit var sizesContainer: LinearLayout
@BindView(R.id.sizes_switch_header)
lateinit var sizesTitle: MaterialTextView
@BindView(R.id.sizes_switcher)
lateinit var sizesSwitcher: ViewGroup
@BindView(R.id.electricity_switch_container)
lateinit var electricContainer: LinearLayout
@BindView(R.id.electricity_switch_header)
lateinit var electricTitle: MaterialTextView
@BindView(R.id.electricity_switcher)
lateinit var electricSwitcher: ViewGroup
@BindView(R.id.furniture_switch_container)
lateinit var furnContainer: LinearLayout
@BindView(R.id.furniture_switch_header)
lateinit var furnTitle: MaterialTextView
@BindView(R.id.furniture_switcher)
lateinit var furnSwitcher: ViewGroup
@BindView(R.id.walls_switch_container)
lateinit var wallsContainer: LinearLayout
@BindView(R.id.walls_switch_header)
lateinit var wallsTitle: MaterialTextView
@BindView(R.id.walls_switcher)
lateinit var wallsSwitcher: ViewGroup
@BindView(R.id.common_info_tab)
lateinit var commonInfoTab: MaterialTextView
@BindView(R.id.explication_tab)
lateinit var explicationTab: MaterialTextView
@BindView(R.id.site_link_viewholder)
lateinit var siteLink: View
@BindView(R.id.start_tour_card)
lateinit var startTour: View
@BindView(R.id.info_tab_divider)
lateinit var infoTabDivicder: View
@BindView(R.id.start_tour_card_divider)
lateinit var startTourDivider: View
@BindView(R.id.sizes_divider)
lateinit var sizesDivider: View
@BindView(R.id.electricity_divider)
lateinit var electricityDivider: View
@BindView(R.id.walls_divider)
lateinit var wallsDivider: View
@BindView(R.id.furniture_divider)
lateinit var furnitureDivider: View
@BindView(R.id.start_tour_image_view) lateinit var tourScreen: RoundedImageView
private fun setToolbar(){
toolBar.setToolbar(null, StatusToolbarModel())
toolBar.statusToolbar.findViewById<View>(R.id.back_cross).let {
detachDisposable.add(
it.clicks()
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe { handleBack() }
)
}
detachDisposable.add(
flatScroll.scrollChangeEvents()
// .debounce (25,TimeUnit.MILLISECONDS)
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe {
if (it.scrollY > flatTitle.measuredHeight) {
val status = estateModel?.to(
StatusToolbarModel(
StatusState.AVAILABLE
, null
, resources?.getString(
estateModel?.type?.typeShortString() ?: -1
, estateModel?.number
)
)
)
toolBar.setToolbar(
null, status = status?.second
)
} else toolBar.setToolbar(
null,
StatusToolbarModel(
StatusState.AVAILABLE, null, null
)
)
}
)
}
private fun bindRecycler() {
flatInfoRecyclerView.isNestedScrollingEnabled = true
flatInfoRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
flatInfoRecyclerView.adapter = FlatInfoAdapter()
flatInfoRecyclerView.itemAnimator = null
}
override fun onViewBound(v: View) {
bottomNavigationController.hide()
setToolbar()
bindRecycler()
}
override fun render(viewState: EstateScreenViewState) {
super.render(viewState)
Timber.d("Render state $viewState")
when(viewState){
is EstateScreenViewState.Idle -> render(viewState)
is EstateScreenViewState.LoadEstate -> render(viewState)
is EstateScreenViewState.LoadPlanTypes -> render(viewState)
is EstateScreenViewState.LoadPlan -> render(viewState)
is EstateScreenViewState.PlanTypeSelected -> render(viewState)
is EstateScreenViewState.SomeError -> render(viewState)
is EstateScreenViewState.ShowEstateInfo -> render(viewState)
}
}
private fun render(viewState: EstateScreenViewState.Idle){
}
private fun render(viewState: EstateScreenViewState.SomeError) =
showError(viewState.exception)
private var estateModel : EstateModel? = null
private fun render(viewState: EstateScreenViewState.LoadEstate) {
estateModel = 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 {
Glide.with(tourScreen)
.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) {
(flatInfoRecyclerView.adapter as FlatInfoAdapter).setItems(viewState.info)
}
private fun render(viewState: EstateScreenViewState.LoadPlanTypes) {
planTypesTabLayout.removeAllTabs()
var i = 1
viewState.types.forEach {
try {
val tab = planTypesTabLayout.newTab()
.setCustomView(R.layout.select_text_tab).setTag(it.planId)
(tab.customView as MaterialTextView).text = "Вариант $i"
planTypesTabLayout.addTab(tab)
i++
} catch (e: Exception) {
Timber.e(e)
}
}
planTypeSelected(viewState.types.first())
}
private fun planTypeSelected(plan:PlanPresetModel){
wallsContainer.setGone(true)
sizesContainer.setGone(true)
electricContainer.setGone(true)
furnContainer.setGone(true)
furnitureDivider.setGone(true)
wallsDivider.setGone(true)
sizesDivider.setGone(true)
electricityDivider.setGone(true)
plan.features.forEach {
when(it){
is FeatureModel.Furniture -> {
furnContainer.setGone(false)
furnTitle.text = it.featureTitle
furnSwitcher.findViewById<SwitchMaterial>(R.id.switch1).isChecked =
it.switchedOn
furnitureDivider.setGone(false)
}
is FeatureModel.Walls ->{
wallsContainer.setGone(false)
wallsTitle.text = it.featureTitle
wallsSwitcher.findViewById<SwitchMaterial>(R.id.switch1).isChecked =
it.switchedOn
wallsDivider.setGone(false)
}
is FeatureModel.Sizes ->{
sizesContainer.setGone(false)
sizesTitle.text = it.featureTitle
sizesSwitcher.findViewById<SwitchMaterial>(R.id.switch1).isChecked =
it.switchedOn
sizesDivider.setGone(false)
}
is FeatureModel.Electric ->{
electricContainer.setGone(false)
electricTitle.text = it.featureTitle
electricSwitcher.findViewById<SwitchMaterial>(R.id.switch1).isChecked =
it.switchedOn
electricityDivider.setGone(false)
}
}
}
}
private fun render(viewState: EstateScreenViewState.PlanTypeSelected) {
planTypeSelected(viewState.item)
}
private fun render(viewState: EstateScreenViewState.LoadPlan) {
planWebView.settings.javaScriptEnabled = true
planWebView.clearCache(true)
val uri = "file:///${Uri.parse(viewState.planBody).path}"
planWebView.loadUrl(uri)
}
private fun getComponent() = DaggerEstateScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity
,args.getInt(SELECTED_ESTATE_ID_KEY))
.inject(this)
override fun getLayoutId(): Int = R.layout.flat_full_card_screen
}
package com.biganto.visual.roompark.presentation.screen.estate
import android.content.Context
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import javax.inject.Named
@PerScreen
@Component(
modules = [EstateScreenModule::class],
dependencies = [AppComponent::class])
interface EstateScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
,@BindsInstance @Named(SELECTED_ESTATE_ID_KEY) selectedEstateId:Int
): EstateScreenComponent
}
val presenter: EstateScreenPresenter
fun inject(controller: EstateScreenController)
}
@Module
abstract class EstateScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
}
package com.biganto.visual.roompark.presentation.screen.estate
import android.content.Context
import androidx.annotation.StringRes
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.EstateInteractor
import com.biganto.visual.roompark.domain.model.*
import com.biganto.visual.roompark.presentation.screen.estate.util.DisplayInfoModel
import com.biganto.visual.roompark.util.extensions.toRubly
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class EstateScreenPresenter @Inject constructor(
private val interactor: EstateInteractor,
private val context: Context,
@Named(SELECTED_ESTATE_ID_KEY) private val estateId:Int
)
: BigantoBasePresenter<EstateScreen, EstateScreenViewState>() {
private var planList: List<PlanPresetModel>? = null
private var estate: EstateModel? = null
private var showType: InfoShowType = InfoShowType.COMMON_INFO
override fun defaultErrorViewStateHandler() =
{ e: ExceptionString -> EstateScreenViewState.SomeError(e) }
private fun getPlan(plan: PlanPresetModel): Observable<EstateScreenViewState> =
interactor.getPlan(plan)
.map<EstateScreenViewState> { EstateScreenViewState.LoadPlan(it) }
override fun bindIntents() {
val prefetchCards = interactor.getEstate(estateId)
.doOnNext { estate = it.copy() }
.map { EstateScreenViewState.LoadEstate(it) }
val fetchPlans = interactor.getPlanTypes(estateId)
.doOnNext { planList = it.toList() }
.map { EstateScreenViewState.LoadPlanTypes(it) }
val fetchPlan = intent(EstateScreen::planTypesTabSelected)
.map { planList?.get(it) }
.flatMap {planPreset ->
interactor.getPlan(planPreset)
.map<EstateScreenViewState> { plan -> EstateScreenViewState.LoadPlan(plan) }
.startWith(
Observable.just<EstateScreenViewState>(
EstateScreenViewState.PlanTypeSelected(planPreset)
)
)
.startWith(
Observable.just<EstateScreenViewState>(
EstateScreenViewState.ShowEstateInfo(
showType
,if (showType== InfoShowType.COMMON_INFO)
mapCommonInfo(estate?.commonInfo)
else mapCommonInfo(planPreset.explication)
)
)
)
}
val switchSizes = intent(EstateScreen::switchSizes)
.map { pair ->
val plan = planList?.first { it.planId == pair.first }
plan?.switchFeature<FeatureModel.Sizes>(pair.second)
plan
}.flatMap(::getPlan)
val switchFurn = intent(EstateScreen::switchFurniture)
.map { pair ->
val plan = planList?.first { it.planId == pair.first }
plan?.switchFeature<FeatureModel.Furniture>(pair.second)
plan
}.flatMap(::getPlan)
val switchWalls = intent(EstateScreen::switchWalls)
.map { pair ->
val plan = planList?.first { it.planId == pair.first }
plan?.switchFeature<FeatureModel.Walls>(pair.second)
plan
}.flatMap(::getPlan)
val switchElectric = intent(EstateScreen::switchElectric)
.map { pair ->
val plan = planList?.first { it.planId == pair.first }
plan?.switchFeature<FeatureModel.Electric>(pair.second)
plan
}.flatMap(::getPlan)
val showInfo = intent(EstateScreen::showCommonInfo)
.doOnNext { showType = InfoShowType.COMMON_INFO }
.map { estate?.commonInfo}
.map (::mapCommonInfo)
.map { EstateScreenViewState.ShowEstateInfo(showType,it) }
val showExplication = intent(EstateScreen::showExplication)
.doOnNext { showType = InfoShowType.EXPLICATIONS }
.map { planList?.get(it)?.explication}
.map (::mapCommonInfo)
.map { EstateScreenViewState.ShowEstateInfo(showType,it) }
val state = restoreStateObservable
.mergeWith(prefetchCards)
.mergeWith(fetchPlans)
.mergeWith(fetchPlan)
.mergeWith(switchElectric)
.mergeWith(switchFurn)
.mergeWith(switchSizes)
.mergeWith(switchWalls)
.mergeWith(showInfo)
.mergeWith(showExplication)
.doOnError { Timber.e(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(EstateScreenViewState::class.java), EstateScreen::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)
private fun mapCommonInfo(list: List<ExplicationModel>): ArrayList<DisplayInfoModel> =
ArrayList(
List(list.size){Timber.d("area value: ${list[it].area}");DisplayInfoModel(list[it].title
,langString(R.string.area_value,list[it].area))
}
)
private fun mapCommonInfo(info: CommonInfoModel?): ArrayList<DisplayInfoModel> {
val r = arrayListOf<DisplayInfoModel>()
if (info == null) return r
info.building.let {
r.add(DisplayInfoModel(langString(R.string.building),"$it"))
}
info.section_begin.let {
r.add(DisplayInfoModel(langString(R.string.section_begin),"$it"))
}
r.add(DisplayInfoModel(langString(R.string.floor),"${info.floor}/${info.floor_max}"))
info.area?.let {
r.add(DisplayInfoModel(langString(R.string.area),langString(R.string.area_value,it)))
}
info.area_living?.let {
r.add(DisplayInfoModel(
langString(R.string.area_living),langString(R.string.area_value,it))
)
}
info.rooms?.let {r.add(DisplayInfoModel(langString(R.string.rooms),"$it"))}
info.kind?.let {r.add(DisplayInfoModel(langString(R.string.flat_kind), it))}
info.decoration?.let {r.add(DisplayInfoModel(langString(R.string.flat_decoration), it))}
info.ceiling?.let {r.add(DisplayInfoModel(langString(R.string.ceiling)
, langString(R.string.meters_value,it)))
}
info.windows_face?.let {r.add(DisplayInfoModel(langString(R.string.window_face), it))}
info.direction?.let {r.add(DisplayInfoModel(langString(R.string.direction), it))}
info.price_meter?.let {r.add(DisplayInfoModel(langString(R.string.price_meter), it
.toRubly()))}
info.price?.let {r.add(DisplayInfoModel(langString(R.string.price), it.toRubly()))}
info.discount?.let {r.add(DisplayInfoModel(langString(R.string.max_discount)
, langString(R.string.discount_value,it)))}
info.discount_amount?.let {r.add(DisplayInfoModel(langString(R.string.discounted_price)
, it.toRubly()))}
return r
}
}
enum class InfoShowType{
COMMON_INFO,
EXPLICATIONS
}
package com.biganto.visual.roompark.presentation.screen.estate
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.PlanPresetModel
import com.biganto.visual.roompark.presentation.screen.estate.util.DisplayInfoModel
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class EstateScreenViewState : BigantoBaseViewState() {
class Idle : EstateScreenViewState()
class LoadEstate(val estate:EstateModel) : EstateScreenViewState()
class LoadPlanTypes(val types:List<PlanPresetModel>) : EstateScreenViewState()
class PlanTypeSelected(val item:PlanPresetModel) : EstateScreenViewState()
class LoadPlan(val planBody:String) : EstateScreenViewState()
class SomeError(val exception: ExceptionString) : EstateScreenViewState()
class ShowEstateInfo(val showType: InfoShowType, val info:List<DisplayInfoModel>) : EstateScreenViewState()
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.estate.util
import android.view.View
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyclerAdapter
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder
import com.google.android.material.textview.MaterialTextView
/**
* Created by Vladislav Bogdashkin on 16.10.2019.
*/
class FlatInfoAdapter : CommonRecyclerAdapter<FlatInfoViewHolder, DisplayInfoModel>() {
override val vhKlazz = FlatInfoViewHolder::class
override fun getVhLayout(): Int = R.layout.flate_measure_info_viewholder
override fun onBindViewHolder(holder: FlatInfoViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
if (position%2 == 0)
holder.itemView.background = holder.itemView.context.getDrawable(R.color.colorDividerLightGray)
else
holder.itemView.background = holder.itemView.context.getDrawable(R.color.colorPrimary)
}
}
class FlatInfoViewHolder(itemView: View) : CommonViewHolder<DisplayInfoModel>(itemView) {
@BindView(R.id.flat_measure_description)
lateinit var infoTitle:MaterialTextView
@BindView(R.id.flat_measure_value)
lateinit var infoValue:MaterialTextView
override fun onViewBound(model: DisplayInfoModel) {
infoTitle.text = model.langTitle
infoValue.text = model.langValue
}
}
data class DisplayInfoModel(val langTitle:String, val langValue:String)
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.favorites
import com.biganto.visual.roompark.conductor.BigantoBaseContract
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface FavoritesScreen : BigantoBaseContract<FavoritesScreenViewState> {
}
package com.biganto.visual.roompark.presentation.screen.favorites
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.HeaderToolbarModel
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.presentation.screen.estate.EstateScreenController
import com.biganto.visual.roompark.presentation.screen.favorites.util.FavoritesListAdapter
import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class FavoritesScreenController :
BigantoBaseController<FavoritesScreenViewState
, FavoritesScreen
, FavoritesScreenPresenter>()
, FavoritesScreen {
override fun injectDependencies() {
getComponent()
}
@Inject
override lateinit var injectedPresenter: FavoritesScreenPresenter
@BindView(R.id.favorites_cards_recycler_view)
lateinit var favoritesRecyclerView: RecyclerView
override fun onAttach(view: View) {
super.onAttach(view)
detachDisposable.addAll(
(favoritesRecyclerView.adapter as FavoritesListAdapter)
.onItemClicked
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
Timber.d("got card clicked $it")
router.pushController(RouterTransaction.with(EstateScreenController(it.id))
.popChangeHandler(FadeChangeHandler())
.pushChangeHandler(FadeChangeHandler())
)
}
)
}
private fun setToolbar(){
favoritesRecyclerView.isNestedScrollingEnabled = false
toolBar.setToolbar(
HeaderToolbarModel(
false,null
,resources?.getString(R.string.favorites),null)
)
}
private fun bindRecycler() {
favoritesRecyclerView.isNestedScrollingEnabled = true
favoritesRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
favoritesRecyclerView.adapter = FavoritesListAdapter()
favoritesRecyclerView.itemAnimator = null
if (favoritesRecyclerView.itemDecorationCount == 0)
favoritesRecyclerView.addItemDecoration(
CeilsDecoration(1
, resources?.getDimensionPixelSize(R.dimen.ceil_grid_padding))
)
}
override fun onViewBound(v: View) {
bottomNavigationController.show()
setToolbar()
bindRecycler()
}
override fun render(viewState: FavoritesScreenViewState) {
super.render(viewState)
Timber.d("Render state $viewState")
when(viewState){
is FavoritesScreenViewState.Idle -> render(viewState)
is FavoritesScreenViewState.FavoriteEstatesLoaded -> render(viewState)
is FavoritesScreenViewState.SomeError -> render(viewState)
}
}
private fun render(viewState: FavoritesScreenViewState.Idle){
}
private fun render(viewState: FavoritesScreenViewState.SomeError) =
showError(viewState.exception)
private fun render(viewState: FavoritesScreenViewState.FavoriteEstatesLoaded) {
(favoritesRecyclerView.adapter as FavoritesListAdapter).addItems(viewState.items)
}
private fun getComponent() = DaggerFavoritesScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
override fun getLayoutId(): Int = R.layout.favorites_screen
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.favorites
import android.content.Context
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
@PerScreen
@Component(
modules = [FavoritesScreenModule::class],
dependencies = [AppComponent::class])
interface FavoritesScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): FavoritesScreenComponent
}
val presenter: FavoritesScreenPresenter
fun inject(controller: FavoritesScreenController)
}
@Module
abstract class FavoritesScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
}
package com.biganto.visual.roompark.presentation.screen.favorites
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.FavoritesInteractor
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class FavoritesScreenPresenter @Inject constructor(
private val interactor: FavoritesInteractor
)
: BigantoBasePresenter<FavoritesScreen, FavoritesScreenViewState>() {
override fun defaultErrorViewStateHandler() =
{e: ExceptionString -> FavoritesScreenViewState.SomeError(e)}
override fun bindIntents() {
val prefetchCards = interactor.getFavoritesForCurrentUser()
.map { FavoritesScreenViewState.FavoriteEstatesLoaded(it) }
val state = restoreStateObservable
.mergeWith(prefetchCards)
.doOnError{ Timber.e(it)}
.onErrorReturn(::parseError)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(FavoritesScreenViewState::class.java), FavoritesScreen::render)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.favorites
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class FavoritesScreenViewState : BigantoBaseViewState() {
class Idle : FavoritesScreenViewState()
class FavoriteEstatesLoaded(val items: List<EstateModel>) : FavoritesScreenViewState()
class SomeError(val exception: ExceptionString) : FavoritesScreenViewState()
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.favorites.util
import android.app.Activity
import android.view.View
import android.widget.TextView
import butterknife.BindView
import butterknife.ButterKnife
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.domain.model.CommonInfoModel
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.FlatType
import com.biganto.visual.roompark.domain.model.typeDoubleString
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.roompark.util.extensions.startUrl
/**
* Created by Vladislav Bogdashkin on 16.10.2019.
*/
class FavoritesListAdapter : CommonRecyclerAdapter<FavoriteViewHolder,EstateModel>() {
override val vhKlazz = FavoriteViewHolder::class
override fun getVhLayout(): Int = R.layout.favorite_card_viewholder
}
class FavoriteViewHolder(itemView: View) : CommonViewHolder<EstateModel>(itemView) {
@BindView(R.id.object_card_title) lateinit var estateTitle: TextView
@BindView(R.id.common_info_block) lateinit var commonInfo:View
@BindView(R.id.start_tour_button) lateinit var startTour:View
@BindView(R.id.info_ceil_1) lateinit var info1:View
@BindView(R.id.info_ceil_2) lateinit var info2:View
@BindView(R.id.info_ceil_3) lateinit var info3:View
@BindView(R.id.info_ceil_4) lateinit var info4:View
@BindView(R.id.info_ceil_5) lateinit var info5:View
@BindView(R.id.info_ceil_6) lateinit var info6:View
@BindView(R.id.info_ceil_7) lateinit var info7:View
@BindView(R.id.info_ceil_8) lateinit var info8:View
@BindView(R.id.site_link) lateinit var siteLink:View
@BindView(R.id.link_divider) lateinit var siteLinkDivider:View
init {
ButterKnife.bind(this, itemView)
}
override fun onViewBound(model: EstateModel) {
estateTitle.text =
itemView.context.resources?.getString(model.type.typeDoubleString(),model.number)
commonInfo.setGone(model.commonInfo == null)
model.commonInfo?.let {renderCommonInfo(it)}
startTour.setGone(model.type != FlatType.FLAT)
siteLink.setGone(model.url == null)
siteLinkDivider.setGone(model.url == null)
model.url?.let {url ->
siteLink.setOnClickListener{
(itemView.context as? Activity)?.startUrl(url)
}
}
}
private fun renderCommonInfo(info:CommonInfoModel){
if (info.building == null) info1.visibility = View.GONE
else { info1.title().text = "Корпус"; info1.text().text = info.building.toString()}
if (info.section_begin == null) info1.visibility = View.GONE
else { info2.title().text = "Секция"; info2.text().text = info.section_begin.toString()}
if (info.floor == null) info1.visibility = View.GONE
else { info3.title().text = "Этаж"; info3.text().text = info.floor.toString()}
if (info.area == null) info1.visibility = View.GONE
else { info4.title().text = "Общая, м²"; info4.text().text = info.area.toString()}
if (info.price_meter == null) info1.visibility = View.GONE
else { info5.title().text = "Цена за м²"; info5.text().text = info.price_meter.toRubles()}
if (info.price == null) info1.visibility = View.GONE
else { info6.title().text = "Стоимость"; info6.text().text = info.price.toRubles()}
if (true) info7.visibility = View.GONE
else { info7.title().text = "вщщ"; info7.text().text = info.building.toString()}
if (true) info8.visibility = View.GONE
else { info8.title().text = "щщщщщз"; info8.text().text = info.building.toString()}
}
private fun View.title() = this.findViewById<TextView>(R.id.info_ceil_header)
private fun View.text() = this.findViewById<TextView>(R.id.info_ceil_content)
private fun Int.toRubles(): String {
return String.format("%,d \u20BD",this).replace(',',' ')
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.feed_list
import com.biganto.visual.roompark.conductor.BigantoBaseContract
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface ArticlesScreen : BigantoBaseContract<ArticlesScreenViewState> {
fun requsetsNewArticles(): Observable<Int>
fun feedSubscription(): Observable<Boolean>
}
package com.biganto.visual.roompark.presentation.screen.feed_list
import android.view.View
import android.widget.ProgressBar
import androidx.core.os.bundleOf
import androidx.core.view.isNotEmpty
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.HeaderToolbarModel
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
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.util.extensions.setGone
import com.biganto.visual.roompark.util.monades.ExceptionString
import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.recyclerview.scrollStateChanges
import com.jakewharton.rxbinding3.widget.checkedChanges
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
const val FEED_ALIAS = "DIRECT_FEED_ID_KEY"
class ArticlesScreenController :
BigantoBaseController<ArticlesScreenViewState
, ArticlesScreen
, ArticlesScreenPresenter>
, ArticlesScreen {
constructor()
constructor(feedAlias:String):super(bundleOf(FEED_ALIAS to feedAlias))
override fun requsetsNewArticles(): Observable<Int> =
articlesRecyclerView.scrollStateChanges()
.filter { it == RecyclerView.SCROLL_STATE_IDLE}
.map { articlesRecyclerView.layoutManager as LinearLayoutManager }
.map { it.findLastCompletelyVisibleItemPosition() }
.filter{ articlesRecyclerView.isNotEmpty() }
.filter { it>(articlesRecyclerView.adapter?.itemCount?:0)-4 }
.map { articlesRecyclerView.adapter?.itemCount?:0 }
.debounce(120L, TimeUnit.MILLISECONDS)
.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() {
getComponent()
}
@Inject
override lateinit var injectedPresenter: ArticlesScreenPresenter
@BindView(R.id.articles_recycler_view)
lateinit var articlesRecyclerView: RecyclerView
@BindView(R.id.progress_bar_view)
lateinit var progressBar: ProgressBar
@BindView(R.id.empty_list_text_view)
lateinit var emptyListNotice: MaterialTextView
private fun setToolbar() {
articlesRecyclerView.isNestedScrollingEnabled = false
toolBar.setToolbar(
HeaderToolbarModel(
true, null
, resources?.getString(
when (args.getString(FEED_ALIAS)) {
"news" -> R.string.news_header
else -> R.string.blog_header
}
), true
)
, null
)
}
private fun bindRecycler() {
articlesRecyclerView.isNestedScrollingEnabled = true
articlesRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
articlesRecyclerView.adapter = ArticlesAdapter()
articlesRecyclerView.itemAnimator = null
if (articlesRecyclerView.itemDecorationCount == 0)
articlesRecyclerView.addItemDecoration(
CeilsDecoration(
1
, resources?.getDimensionPixelSize(R.dimen.ceil_grid_padding)
)
)
detachDisposable.add(
(articlesRecyclerView.adapter as ArticlesAdapter)
.onItemClicked.subscribe {
router.pushController(RouterTransaction.with(ArticleScreenController(it.articleId)))
}
)
}
override fun onViewBound(v: View) {
bottomNavigationController.hide()
setToolbar()
bindRecycler()
}
override fun render(viewState: ArticlesScreenViewState) {
super.render(viewState)
Timber.d("Render state $viewState")
when(viewState){
is ArticlesScreenViewState.Idle -> render(viewState)
is ArticlesScreenViewState.ArticlesLoaded -> render(viewState)
is ArticlesScreenViewState.ArticlesScrollPage -> render(viewState)
is ArticlesScreenViewState.RestoreView -> render(viewState)
is ArticlesScreenViewState.SomeError -> render(viewState)
is ArticlesScreenViewState.SubscriptionStatus -> render(viewState)
is ArticlesScreenViewState.SubscriptionError -> render(viewState)
}
}
private fun render(viewState: ArticlesScreenViewState.Idle){
progressBar.setGone(false)
emptyListNotice.setGone(true)
articlesRecyclerView.setGone(true)
}
private fun render(viewState: ArticlesScreenViewState.SomeError) {
showError(viewState.exception)
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){
Timber.d("got items: ${viewState.items.size}")
(articlesRecyclerView.adapter as ArticlesAdapter).addItems(viewState.items)
}
private fun render(viewState: ArticlesScreenViewState.RestoreView){
progressBar.setGone(true)
emptyListNotice.setGone(true)
articlesRecyclerView.setGone(true)
if (viewState.restore.articles.isNullOrEmpty())
{
emptyListNotice.setGone(false)
articlesRecyclerView.setGone(true)
}
else{
emptyListNotice.setGone(true)
articlesRecyclerView.setGone(false)
(articlesRecyclerView.adapter as ArticlesAdapter).setItems(viewState.restore.articles)
}
}
private fun render(viewState: ArticlesScreenViewState.ArticlesLoaded) {
progressBar.setGone(true)
emptyListNotice.setGone(true)
articlesRecyclerView.setGone(true)
if (viewState.items.isNullOrEmpty())
{
emptyListNotice.setGone(false)
articlesRecyclerView.setGone(true)
}
else{
emptyListNotice.setGone(true)
articlesRecyclerView.setGone(false)
(articlesRecyclerView.adapter as ArticlesAdapter).setItems(viewState.items)
}
}
private fun getComponent() = DaggerArticlesScreenComponent.factory()
.create(args.getString(FEED_ALIAS)!!
,RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
override fun getLayoutId(): Int = R.layout.feed_direct_screen
override fun handleBack(): Boolean {
Timber.d("handle back in class ")
router.popController(this)
return true
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.feed_list
import android.content.Context
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import javax.inject.Named
@PerScreen
@Component(
modules = [ArticlesScreenModule::class],
dependencies = [AppComponent::class])
interface ArticlesScreenComponent {
@Component.Factory
interface Factory{
fun create(
@BindsInstance @Named(FEED_ALIAS) feed:String
, appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): ArticlesScreenComponent
}
val presenter: ArticlesScreenPresenter
fun inject(controller: ArticlesScreenController)
}
@Module
abstract class ArticlesScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
@PerScreen
@Binds
abstract fun provideFeedId(@Named("FEED_ID_INT") feedId:Int) : Int
}
package com.biganto.visual.roompark.presentation.screen.feed_list
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
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 io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
private const val FEED_PAGE_SIZE = 10
class ArticlesScreenPresenter @Inject constructor(
@Named(FEED_ALIAS) private val feedId:String,
private val interactor: ArticlesInteractor
)
: BigantoBasePresenter<ArticlesScreen, ArticlesScreenViewState>() {
private val restoreModel = RestoreModel(mutableListOf(), null)
override fun detachView() {
super.detachView()
restoreStateObservable.accept(ArticlesScreenViewState.RestoreView(restoreModel))
}
override fun defaultErrorViewStateHandler() =
{ e: ExceptionString -> ArticlesScreenViewState.SomeError(e) }
override fun bindIntents() {
Timber.d("feedId : $feedId")
val onSubChecked = intent(ArticlesScreen::feedSubscription)
.filter { restoreModel.sub != null }
.flatMap { newState ->
interactor.switchSubscription(restoreModel.sub!!, newState)
.andThen(
Observable.just<ArticlesScreenViewState>(
ArticlesScreenViewState.SubscriptionStatus(
newState
)
)
)
.onErrorReturn { ArticlesScreenViewState.SubscriptionError(!newState) }
}
val fetchSubscription = interactor.getSubscriptions(feedId)
.doOnNext { Timber.w("got smthng: $it") }
.doAfterNext { restoreModel.sub = it }
.map<ArticlesScreenViewState> { ArticlesScreenViewState.SubscriptionStatus(it.state) }
.startWith(Observable.just<ArticlesScreenViewState>(ArticlesScreenViewState.Idle()))
val prefetchCards = interactor.fetchArticles(feedId)
.map<ArticlesScreenViewState> { ArticlesScreenViewState.ArticlesLoaded(it.articles) }
.startWith(Observable.just<ArticlesScreenViewState>(ArticlesScreenViewState.Idle()))
val getNewArticlesPage = intent(ArticlesScreen::requsetsNewArticles)
.flatMap {
interactor.fetchArticlesPage(feedId, FEED_PAGE_SIZE, it)
.map<ArticlesScreenViewState> { articlePage ->
articlePage.articles
.asSequence()
.filter { !restoreModel.articles.contains(it) }
.toList()
.let { uniqueItems -> restoreModel.articles.addAll(uniqueItems) }
ArticlesScreenViewState.ArticlesScrollPage(articlePage.articles.toList())
}
.onErrorReturn(::parseError)
}
val state = Observable.mergeDelayError(
arrayListOf(
restoreStateObservable,
prefetchCards,
getNewArticlesPage,
fetchSubscription,
onSubChecked
)
)
.doOnError { Timber.e(it) }
.onErrorReturn(::parseError)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(ArticlesScreenViewState::class.java), ArticlesScreen::render)
}
}
data class RestoreModel(
var articles:MutableList<ArticlePreviewModel>,
var sub: SubscriptionModel?
)
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.feed_list
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.ArticlePreviewModel
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class ArticlesScreenViewState : BigantoBaseViewState() {
class Idle : ArticlesScreenViewState()
class ArticlesLoaded(val items: List<ArticlePreviewModel>) : ArticlesScreenViewState()
class SomeError(val exception: ExceptionString) : ArticlesScreenViewState()
class ArticlesScrollPage(val items:List<ArticlePreviewModel>) : ArticlesScreenViewState()
class RestoreView(val restore:RestoreModel) : ArticlesScreenViewState()
class SubscriptionStatus(val subState: Boolean) : ArticlesScreenViewState()
class SubscriptionError(val subState: Boolean) : ArticlesScreenViewState()
}
package com.biganto.visual.roompark.presentation.screen.feed_list.util
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.domain.model.ArticlePreviewModel
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.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import timber.log.Timber
import java.text.SimpleDateFormat
import java.util.*
/**
* Created by Vladislav Bogdashkin on 16.10.2019.
*/
class ArticlesAdapter : CommonRecyclerAdapter<ArticleViewHolder, ArticlePreviewModel>() {
override val vhKlazz = ArticleViewHolder::class
override fun getVhLayout(): Int = R.layout.feed_direct_viewholder
}
class ArticleViewHolder(itemView: View) : CommonViewHolder<ArticlePreviewModel>(itemView) {
private val dateFormatter = SimpleDateFormat("dd / MM / yyyy", Locale.getDefault())
@BindView(R.id.imageHolder) lateinit var preview: ImageView
@BindView(R.id.feed_date_text_view) lateinit var articleDate: TextView
@BindView(R.id.feed_title_info_text_view) lateinit var articleTitle: TextView
@BindView(R.id.feed_read) lateinit var articleIsRead:View
override fun onViewBound(model: ArticlePreviewModel) {
articleDate.text = dateFormatter.format(model.published)
articleTitle.text = model.title
articleIsRead.setGone(model.isRead)
Glide.with(preview)
.load(model.previewUrl)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(preview)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.feeds
import com.biganto.visual.roompark.conductor.BigantoBaseContract
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.domain.model.ArticlePreviewModel
import com.biganto.visual.roompark.domain.model.FeedModel
import com.biganto.visual.roompark.domain.model.WebCamModel
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface FeedsScreen : BigantoBaseContract<FeedsScreenViewState> {
fun feedsTabSelected(): Observable<String>
fun requsetsNewArticles(): Observable<Pair<FeedModel, Int>>
fun onAllFeedArticles(): Observable<FeedModel>
fun onCameraSelected(): Observable<WebCamModel>
fun onArticleSelected(): Observable<ArticlePreviewModel>
fun onAlbumSelected(): Observable<AlbumPreviewModel>
}
data class EstateTabModel(val title:String, val building:Int)
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.feeds
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.domain.model.ArticlePreviewModel
import com.biganto.visual.roompark.domain.model.FeedModel
import com.biganto.visual.roompark.domain.model.WebCamModel
import com.biganto.visual.roompark.presentation.screen.albums.AlbumsScreenController
import com.biganto.visual.roompark.presentation.screen.article.ArticleScreenController
import com.biganto.visual.roompark.presentation.screen.feed_list.ArticlesScreenController
import com.biganto.visual.roompark.presentation.screen.feeds.utils.AlbumsPreviewAdapter
import com.biganto.visual.roompark.presentation.screen.feeds.utils.ArticlesPreviewAdapter
import com.biganto.visual.roompark.presentation.screen.feeds.utils.CamsListAdapter
import com.biganto.visual.roompark.presentation.screen.web_cam.WebCamScreenController
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.google.android.material.button.MaterialButton
import com.google.android.material.tabs.TabLayout
import com.jakewharton.rxbinding3.material.selections
import com.jakewharton.rxbinding3.recyclerview.scrollStateChanges
import com.jakewharton.rxbinding3.view.clicks
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class FeedsScreenController :
BigantoBaseController<FeedsScreenViewState
, FeedsScreen
, FeedsScreenPresenter>()
, FeedsScreen {
override fun onCameraSelected(): Observable<WebCamModel> =
(camsRecyclerView.adapter as CamsListAdapter).onItemClicked
.debounce (120L, TimeUnit.MILLISECONDS)
override fun onArticleSelected(): Observable<ArticlePreviewModel> =
(feedsRecyclerView.adapter as ArticlesPreviewAdapter).onItemClicked
.debounce (120L, TimeUnit.MILLISECONDS)
override fun onAlbumSelected(): Observable<AlbumPreviewModel> =
(devProgressRecyclerView.adapter as AlbumsPreviewAdapter).onItemClicked
.debounce (120L, TimeUnit.MILLISECONDS)
override fun feedsTabSelected(): Observable<String>
= feedsTabs.selections()
.debounce (200L, TimeUnit.MILLISECONDS)
.doOnNext { Timber.d("it.position ${it.position}") }
.doOnNext { Timber.d("storedFeedsList.size ${storedFeedsList.size }") }
.map { it.position }
.map { storedFeedsList[it].alias }
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { allFeedArticles.text = "ВСЕ ${allFeedName(it)} →" }
override fun injectDependencies() {
getComponent()
}
@BindView(R.id.feedsTabs)
lateinit var feedsTabs:TabLayout
@BindView(R.id.feedsRecyclerView)
lateinit var feedsRecyclerView:RecyclerView
@BindView(R.id.dev_progress_recycler_view)
lateinit var devProgressRecyclerView:RecyclerView
@BindView(R.id.cams_recycler_view)
lateinit var camsRecyclerView:RecyclerView
@BindView(R.id.feedsBlock)
lateinit var feedsBlockView:ViewGroup
@BindView(R.id.to_feed_articles)
lateinit var allFeedArticles:MaterialButton
override fun requsetsNewArticles(): Observable<Pair<FeedModel, Int>> =
feedsRecyclerView.scrollStateChanges()
.filter { it == RecyclerView.SCROLL_STATE_IDLE}
.map { feedsRecyclerView.layoutManager as LinearLayoutManager }
.map { it.findLastCompletelyVisibleItemPosition() }
.filter{ storedFeedsList.isNotEmpty() }
.filter { it>(feedsRecyclerView.adapter?.itemCount?:0)-4 }
.map { feedsRecyclerView.adapter?.itemCount?:0 }
.map { Pair(storedFeedsList[feedsTabs.selectedTabPosition],it)}
.debounce(120L,TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
private fun bindRecycler(){
feedsRecyclerView.isNestedScrollingEnabled = true
feedsRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
feedsRecyclerView.adapter = ArticlesPreviewAdapter()
feedsRecyclerView.itemAnimator = null
devProgressRecyclerView.isNestedScrollingEnabled = true
devProgressRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false)
devProgressRecyclerView.adapter = AlbumsPreviewAdapter()
devProgressRecyclerView.itemAnimator = null
camsRecyclerView.isNestedScrollingEnabled = true
camsRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
camsRecyclerView.adapter = CamsListAdapter()
camsRecyclerView.itemAnimator = null
}
private var storedFeedsList:List<FeedModel> = arrayListOf()
@Inject
override lateinit var injectedPresenter: FeedsScreenPresenter
override fun onAllFeedArticles(): Observable<FeedModel> =
allFeedArticles.clicks()
.map{ feedsTabs.getTabAt(feedsTabs.selectedTabPosition)}
.map { storedFeedsList[it.position] }
override fun onViewBound(v: View) {
bottomNavigationController.show()
toolBar.setToolbar()
bindRecycler()
}
override fun render(viewState: FeedsScreenViewState) {
// super.render(viewState)
Timber.d("Render state $viewState")
when(viewState){
is FeedsScreenViewState.Idle -> render(viewState)
is FeedsScreenViewState.FeedsPages -> render(viewState)
is FeedsScreenViewState.AlbumsPages -> render(viewState)
is FeedsScreenViewState.ArticlesScrollPage -> render(viewState)
is FeedsScreenViewState.CamsList -> render(viewState)
is FeedsScreenViewState.GetFeedArticlesPreview -> render(viewState)
is FeedsScreenViewState.RestoreView -> render(viewState)
is FeedsScreenViewState.ToArticlesScreen -> render(viewState)
is FeedsScreenViewState.ToWebCam -> render(viewState)
is FeedsScreenViewState.ToArticle -> render(viewState)
is FeedsScreenViewState.ToAlbum -> render(viewState)
is FeedsScreenViewState.SomeError -> render(viewState)
}
}
private fun render(viewState: FeedsScreenViewState.Idle){
}
private fun render(viewState: FeedsScreenViewState.ToArticlesScreen) {
router.pushController(
RouterTransaction.with(ArticlesScreenController(viewState.feedAlias))
.popChangeHandler(FadeChangeHandler())
.pushChangeHandler(FadeChangeHandler())
)
}
private fun render(viewState: FeedsScreenViewState.ToWebCam) {
router.pushController(
RouterTransaction.with(WebCamScreenController(viewState.camId))
.popChangeHandler(FadeChangeHandler())
.pushChangeHandler(FadeChangeHandler())
)
}
private fun render(viewState: FeedsScreenViewState.ToArticle) {
router.pushController(
RouterTransaction.with(ArticleScreenController(viewState.articleId))
.popChangeHandler(FadeChangeHandler())
.pushChangeHandler(FadeChangeHandler())
)
}
private fun render(viewState: FeedsScreenViewState.ToAlbum) {
router.pushController(
RouterTransaction.with(AlbumsScreenController(viewState.albumId))
.popChangeHandler(FadeChangeHandler())
.pushChangeHandler(FadeChangeHandler())
)
}
private fun render(viewState: FeedsScreenViewState.SomeError) =
showError(viewState.exception)
private fun allFeedName(feedAlias:String) = if (feedAlias=="news") "НОВОСТИ" else "БЛОГИ"
private fun render(viewState: FeedsScreenViewState.FeedsPages){
feedsTabs.removeAllTabs()
viewState.items.forEach {feed ->
val tab = feedsTabs.newTab()
.setCustomView(R.layout.feeds_tab_view)
.setTag(feed.alias)
tab.customView
?.let {
it.findViewById<TextView>(R.id.tab_title)?.text = feed.title
it.findViewById<TextView>(R.id.tab_divider)?.visibility =
if (viewState.items.indexOf(feed) == viewState.items.size - 1) View.GONE
else View.VISIBLE
}
feedsTabs.addTab(tab)
}
storedFeedsList = viewState.items
feedsTabs.selectTab(feedsTabs.getTabAt(0))
}
private fun render(viewState: FeedsScreenViewState.AlbumsPages){
Timber.d("got albums pages: ${viewState.items.size}")
(devProgressRecyclerView.adapter as AlbumsPreviewAdapter).setItems(viewState.items)
}
private fun render(viewState: FeedsScreenViewState.ArticlesScrollPage){
Timber.d("got items: ${viewState.items.size}")
(feedsRecyclerView.adapter as ArticlesPreviewAdapter).addItems(viewState.items)
}
private fun render(viewState: FeedsScreenViewState.GetFeedArticlesPreview){
(feedsRecyclerView.adapter as ArticlesPreviewAdapter).setItems(viewState.items)
}
private fun render(viewState: FeedsScreenViewState.CamsList){
(camsRecyclerView.adapter as CamsListAdapter).setItems(viewState.items)
}
private fun render(viewState: FeedsScreenViewState.RestoreView){
(camsRecyclerView.adapter as CamsListAdapter).setItems(viewState.restore.cams)
(feedsRecyclerView.adapter as ArticlesPreviewAdapter).setItems(viewState.restore.articles)
(devProgressRecyclerView.adapter as AlbumsPreviewAdapter).setItems(viewState.restore.albums)
feedsTabs.removeAllTabs()
viewState.restore.feeds.forEach {feed ->
val tab = feedsTabs.newTab()
.setCustomView(R.layout.feeds_tab_view)
.setTag(feed.alias)
tab.customView
?.let {
it.findViewById<TextView>(R.id.tab_title)?.text = feed.title
it.findViewById<TextView>(R.id.tab_divider)?.visibility =
if (viewState.restore.feeds.indexOf(feed) == viewState.restore.feeds.size - 1) View.GONE
else View.VISIBLE
}
feedsTabs.addTab(tab)
}
storedFeedsList = viewState.restore.feeds
feedsTabs.selectTab(feedsTabs.getTabAt(0))
}
private fun getComponent() = DaggerFeedsScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
override fun getLayoutId(): Int = R.layout.feeds_screen
override fun handleBack(): Boolean {
Timber.d("handle back in class ")
router.popController(this)
return super.handleBack()
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.feeds
import android.content.Context
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.data.service.user_session.IUserSession
import com.biganto.visual.roompark.data.service.user_session.UserSessionService
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
@PerScreen
@Component(
modules = [FeedsScreenModule::class]
,dependencies = [AppComponent::class]
)
interface FeedsScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): FeedsScreenComponent
}
fun inject(controller: FeedsScreenController)
}
@Module
abstract class FeedsScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
// @PerScreen
// @Binds
// abstract fun provideContract(impl: FeedsContractModule): FeedsContract
//
// @PerScreen
// @Binds
// abstract fun provideDevContract(impl: AlbumsContractModule): DevProgressContract
@PerScreen
@Binds
abstract fun provideSession(impl: UserSessionService): IUserSession
}
package com.biganto.visual.roompark.presentation.screen.feeds
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.FeedsInteractor
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
private const val FEED_PREVIEW_PAGE_SIZE = 10
class FeedsScreenPresenter @Inject constructor(
private val interactor: FeedsInteractor
)
: BigantoBasePresenter<FeedsScreen, FeedsScreenViewState>() {
override fun defaultErrorViewStateHandler() =
{e: ExceptionString -> FeedsScreenViewState.SomeError(e)}
private val restoreModel = RestoreModel(
arrayListOf(),
arrayListOf(),
arrayListOf(),
mutableListOf()
)
override fun attachView(view: FeedsScreen) {
Timber.d("AttachView ")
super.attachView(view)
}
override fun detachView() {
super.detachView()
restoreStateObservable.accept(FeedsScreenViewState.RestoreView(restoreModel))
}
override fun bindIntents() {
val fetchCams = interactor.fetchCams()
.map <FeedsScreenViewState>{
restoreModel.cams = it.items
FeedsScreenViewState.CamsList(it.items)
}
.onErrorReturn (::parseError)
val fetchAlbums = interactor.fetchAlbums()
.map <FeedsScreenViewState>{
Timber.d("got albums: $it")
restoreModel.albums = it
FeedsScreenViewState.AlbumsPages(it)
}
.onErrorReturn (::parseError)
val fetchFeeds = interactor.fetchTopFeeds()
.map <FeedsScreenViewState>{
restoreModel.feeds = it.feeds
FeedsScreenViewState.FeedsPages(it.feeds.toList())
}
.onErrorReturn (::parseError)
val getFeedArticlesPreview = intent(FeedsScreen::feedsTabSelected)
.flatMap { interactor.fetchArticles(it) }
.map<FeedsScreenViewState> {articlePage ->
articlePage.articles
.asSequence()
.filter { !restoreModel.articles.contains(it) }
.toList()
.let {uniqueItems->restoreModel.articles.addAll(uniqueItems)}
FeedsScreenViewState.GetFeedArticlesPreview(articlePage.articles.toList())
}
.onErrorReturn (::parseError)
val switchToAllArtiles = intent(FeedsScreen::onAllFeedArticles)
.flatMap{ Observable.defer{Observable.just<FeedsScreenViewState>(
FeedsScreenViewState.ToArticlesScreen(it.alias)) }}
val selectCamera = intent(FeedsScreen::onCameraSelected)
.map<FeedsScreenViewState> {
FeedsScreenViewState.ToWebCam(it.index)
}
val selectArticle = intent(FeedsScreen::onArticleSelected)
.map<FeedsScreenViewState> {
FeedsScreenViewState.ToArticle(it.articleId)
}
val selectAlbum = intent(FeedsScreen::onAlbumSelected)
.map<FeedsScreenViewState> {
FeedsScreenViewState.ToAlbum(it.albumId)
}
val getNewArticlesPage = intent(FeedsScreen::requsetsNewArticles)
.flatMap { interactor.fetchArticles(it.first.alias, FEED_PREVIEW_PAGE_SIZE,it.second) }
.map<FeedsScreenViewState> {articlePage ->
articlePage.articles
.asSequence()
.filter { !restoreModel.articles.contains(it) }
.toList()
.let {uniqueItems->restoreModel.articles.addAll(uniqueItems)}
FeedsScreenViewState.ArticlesScrollPage(articlePage.articles.toList())
}
.onErrorReturn (::parseError)
val state =
fetchFeeds
.mergeWith(getFeedArticlesPreview)
.mergeWith(fetchAlbums)
.mergeWith(fetchCams)
.mergeWith(getNewArticlesPage)
.mergeWith(switchToAllArtiles)
.mergeWith(selectCamera)
.mergeWith(selectAlbum)
.mergeWith(selectArticle)
.mergeWith(restoreStateObservable)
.doOnError{ Timber.e(it)}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(FeedsScreenViewState::class.java), FeedsScreen::render)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.feeds
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.domain.model.ArticlePreviewModel
import com.biganto.visual.roompark.domain.model.FeedModel
import com.biganto.visual.roompark.domain.model.WebCamModel
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class FeedsScreenViewState : BigantoBaseViewState() {
class Idle : FeedsScreenViewState()
class FeedsPages(val items:List<FeedModel>) : FeedsScreenViewState()
class AlbumsPages(val items:List<AlbumPreviewModel>) : FeedsScreenViewState()
class CamsList(val items:List<WebCamModel>) : FeedsScreenViewState()
class GetFeedArticlesPreview(val items:List<ArticlePreviewModel>) : FeedsScreenViewState()
class ArticlesScrollPage(val items:List<ArticlePreviewModel>) : FeedsScreenViewState()
class RestoreView(val restore:RestoreModel) : FeedsScreenViewState()
class SomeError(val exception: ExceptionString) : FeedsScreenViewState()
class ToArticlesScreen(val feedAlias:String) : FeedsScreenViewState()
class ToArticle(val articleId:Int) : FeedsScreenViewState()
class ToAlbum(val albumId:Int) : FeedsScreenViewState()
class ToWebCam(val camId:Int) : FeedsScreenViewState()
}
data class RestoreModel(
var feeds:List<FeedModel>,
var albums:List<AlbumPreviewModel>,
var cams:List<WebCamModel>,
var articles:MutableList <ArticlePreviewModel>
)
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.feeds.utils
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.domain.model.ArticlePreviewModel
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.formatToSimple
import com.biganto.visual.roompark.util.extensions.setGone
import com.bumptech.glide.Glide
/**
* Created by Vladislav Bogdashkin on 15.10.2019.
*/
class ArticlesPreviewAdapter : CommonRecyclerAdapter<ArticlePreviewViewHolder,ArticlePreviewModel>() {
override val vhKlazz = ArticlePreviewViewHolder::class
override fun getVhLayout(): Int = R.layout.feed_preview_viewholder
}
class ArticlePreviewViewHolder(itemView: View) : CommonViewHolder<ArticlePreviewModel>(itemView) {
@BindView(R.id.imageHolder) lateinit var preview:ImageView
@BindView(R.id.feed_date_text_view) lateinit var articleDate:TextView
@BindView(R.id.feed_title_info_text_view) lateinit var articleTitle:TextView
@BindView(R.id.feed_read) lateinit var articleIsRead:View
override fun onViewBound(model: ArticlePreviewModel) {
articleDate.text = model.published.formatToSimple
articleTitle.text = model.title
articleIsRead.setGone(model.isRead)
Glide.with(preview)
.load(model.previewUrl)
.into(preview)
}
}
package com.biganto.visual.roompark.presentation.screen.feeds.utils
import android.view.View
import android.widget.ImageView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.domain.model.WebCamModel
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyclerAdapter
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder
import com.google.android.material.textview.MaterialTextView
/**
* Created by Vladislav Bogdashkin on 15.10.2019.
*/
class CamsListAdapter : CommonRecyclerAdapter<CamListViewHolder,WebCamModel>() {
override val vhKlazz = CamListViewHolder::class
override fun getVhLayout(): Int = R.layout.cam_button_viewholder
}
class CamListViewHolder(itemView: View) : CommonViewHolder<WebCamModel>(itemView) {
@BindView(R.id.camTitle) lateinit var camTitle:MaterialTextView
@BindView(R.id.camStatus) lateinit var camStatus: MaterialTextView
@BindView(R.id.camStatusIcon) lateinit var camStatusIcon:ImageView
override fun onViewBound(model: WebCamModel) {
camTitle.text = model.title
camStatus.text = if (model.streams.isNullOrEmpty()) "ОФФЛАЙН" else "ОНЛАЙН"
itemView.isEnabled = !model.streams.isNullOrEmpty()
camStatusIcon.visibility = if (model.streams.isNullOrEmpty()) View.GONE else View.VISIBLE
}
}
package com.biganto.visual.roompark.presentation.screen.feeds.utils
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyclerAdapter
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder
import com.bumptech.glide.Glide
import java.text.SimpleDateFormat
import java.util.*
/**
* Created by Vladislav Bogdashkin on 15.10.2019.
*/
class AlbumsPreviewAdapter : CommonRecyclerAdapter<AlbumCardViewHolder,AlbumPreviewModel>() {
override val vhKlazz = AlbumCardViewHolder::class
override fun getVhLayout(): Int = R.layout.estate_card_viewholder
fun getItemPosition(model:AlbumPreviewModel) = getItemPosition(model.albumId)
fun getItemPosition(modelId:Int)= list.indexOfFirst { it.albumId == modelId }
}
class AlbumCardViewHolder(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.card_title) lateinit var articleTitle:TextView
@BindView(R.id.card_updated) lateinit var articleDate:TextView
override fun onViewBound(model: AlbumPreviewModel) {
// articleDate.text = dateFormatter.format(model.published)
articleTitle.text = model.title
articleTitle.text = model.title
Glide.with(preview)
.load(model.previewUrl)
.centerCrop()
.fitCenter()
.into(preview)
}
}
package com.biganto.visual.roompark.presentation.screen.feeds.utils
import android.view.View
import androidx.viewpager.widget.PagerAdapter
import com.biganto.visual.roompark.domain.model.FeedModel
import timber.log.Timber
/**
* Created by Vladislav Bogdashkin on 14.10.2019.
*/
class FeedsPagerAdapter: PagerAdapter() {
public fun updateData(items:List<FeedModel>){
if (list.size>0) Timber.w("View pager list is not empty!")
list.clear()
list.addAll(items)
notifyDataSetChanged()
}
private val list:MutableList<FeedModel> = mutableListOf()
override fun isViewFromObject(view: View, `object`: Any): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getCount(): Int = list.size
override fun getPageTitle(position: Int): CharSequence? = list[position].title
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.home
import android.os.Bundle
import android.os.Parcelable
import android.util.SparseArray
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import androidx.annotation.NonNull
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.contains
import androidx.core.view.get
import androidx.viewpager.widget.PagerAdapter
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.Router
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.bottomnavigation.BottomNavigationView
import timber.log.Timber
/**
* Created by Vladislav Bogdashkin on 11.10.2019.
*/
class BNVRouterPagerAdapter(
val host: Controller,
val bnv: BottomNavigationView,
val viewPager: ViewPager,
val pages: Map<Int, () -> Controller>
): PagerAdapter() {
companion object {
private const val KEY_SAVED_PAGES = "BottomBarRouterPagerAdapter.savedStates"
private const val KEY_MAX_PAGES_TO_STATE_SAVE = "BottomBarRouterPagerAdapter.maxPagesToStateSave"
private const val KEY_SAVE_PAGE_HISTORY = "BottomBarRouterPagerAdapter.savedPageHistory"
}
private var maxPagesToStateSave = Integer.MAX_VALUE
private val visibleRouters = SparseArray<Router>()
var currentPrimaryRouter: Router? = null
private set
var savedPageHistory = ArrayList<Int>()
private set
var savedPages: SparseArray<Bundle> = SparseArray()
private set
init {
require(bnv.menu.all { pages.containsKey(it.itemId) }) { "All menu items must have a matching page setup!" }
viewPager.adapter = this
bnv.setOnNavigationItemSelectedListener {
viewPager.currentItem = bnv.menu.indexOf(it)
true
}
}
/**
* Called when a router is instantiated. Here the router's root should be set if needed.
*
* @param router The router used for the page
* @param position The page position to be instantiated.
*/
fun configureRouter(@NonNull router: Router, position: Int) {
if(!router.hasRootController()) {
val page = pages[bnv.menu[position].itemId] ?: throw Exception("Page not found in initializers!")
router.setRoot(RouterTransaction.with(page.invoke()))
}
}
override fun getCount(): Int = bnv.menu.size()
override fun getPageTitle(position: Int): CharSequence? = bnv.menu[position].title
override fun instantiateItem(container: ViewGroup, position: Int): Any {
return host.getChildRouter(container, makeRouterName(container.id, getItemId(position))).apply {
if(!hasRootController()) {
val savedState = savedPages.get(position)
if(savedState != null) {
restoreInstanceState(savedState)
savedPages.remove(position)
savedPageHistory.remove(position)
}
rebindIfNeeded()
configureRouter(this, position)
if(this !== currentPrimaryRouter) {
backstack.forEach { it.controller().setOptionsMenuHidden(true) }
}
visibleRouters.put(position, this)
}
}
}
override fun destroyItem(container: ViewGroup, position: Int, router: Any) {
require(router is Router) { "Non-router object in router stack!" }
val savedState = Bundle()
router.saveInstanceState(savedState)
savedPages.put(position, savedState)
savedPageHistory.remove(position)
savedPageHistory.add(position)
ensurePagesSaved()
host.removeChildRouter(router)
visibleRouters.remove(position)
}
override fun setPrimaryItem(container: ViewGroup, position: Int, router: Any) {
require(router is Router) { "Non-router object in router stack!" }
if(router !== currentPrimaryRouter) {
currentPrimaryRouter?.backstack?.forEach { it.controller().setOptionsMenuHidden(true) }
router.backstack.forEach { it.controller().setOptionsMenuHidden(false) }
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 {
require(router is Router) { "Non-router object in router stack!" }
for(transaction in router.backstack) if(transaction.controller().view == view) return true
return false
}
fun setMaxPagesToStateSave(maxPagesToStateSave: Int) {
require(maxPagesToStateSave >= 0) { "Only positive integers may be passed for maxPagesToStateSave." }
this.maxPagesToStateSave = maxPagesToStateSave
ensurePagesSaved()
}
override fun saveState(): Parcelable? {
return (super.saveState() as? Bundle? ?: Bundle()).apply {
putSparseParcelableArray(KEY_SAVED_PAGES, savedPages)
putInt(KEY_MAX_PAGES_TO_STATE_SAVE, maxPagesToStateSave)
putIntegerArrayList(KEY_SAVE_PAGE_HISTORY, savedPageHistory)
}
}
override fun restoreState(state: Parcelable?, loader: ClassLoader?) {
super.restoreState(state, loader)
(state as? Bundle?)?.apply {
savedPages = getSparseParcelableArray(KEY_SAVED_PAGES) ?: SparseArray()
maxPagesToStateSave = getInt(KEY_MAX_PAGES_TO_STATE_SAVE)
savedPageHistory = getIntegerArrayList(KEY_SAVE_PAGE_HISTORY) as ArrayList<Int>
}
}
fun getRouter(position: Int): Router = visibleRouters[position, null]
fun getItemId(position: Int): Long {
return bnv.menu.getItem(position).itemId.toLong()
}
private fun ensurePagesSaved() {
while (savedPages.size() > maxPagesToStateSave) {
val positionToRemove = savedPageHistory.removeAt(0)
savedPages.remove(positionToRemove)
}
}
private fun makeRouterName(viewId: Int, id: Long): String {
return "$viewId:$id"
}
}
fun Menu.all(predicate: (android.view.MenuItem) -> Boolean): Boolean {
if(size() == 0) return false
for(index in (0 until size())) if(!predicate(get(index))) return false
return true
}
fun Menu.indexOf(item: android.view.MenuItem): Int {
require(size() > 0) { "Menu is empty!" }
require(contains(item)) { "Item is not part of menu!" }
for(index in (0 until size())) if(get(index) == item) return index
throw Exception("Item is not part of menu!")
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.home
import android.view.View
import androidx.viewpager.widget.ViewPager
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.presentation.screen.deals.DealsScreenController
import com.biganto.visual.roompark.presentation.screen.favorites.FavoritesScreenController
import com.biganto.visual.roompark.presentation.screen.feeds.FeedsScreenController
import com.biganto.visual.roompark.presentation.screen.settings.SettingsScreenController
import com.biganto.visual.roompark.presentation.screen.to_flat.FindFlatScreenController
import com.google.android.material.bottomnavigation.BottomNavigationView
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 11.10.2019.
*/
@Deprecated("Реализация через ViewPager сулит некоторые дополнительны проблемы и оверхед")
class HomeController :
BigantoBaseController<HomeScreenViewState
, HomeScreen
, HomeScreenPresenter>()
, HomeScreen {
override fun onViewBound(v: View) {
bottomNavigationController.bottomNavigation.visibility = BottomNavigationView.VISIBLE
viewpager.offscreenPageLimit = 0
pageAdapter = BNVRouterPagerAdapter(this,bottomNavigationController.bottomNavigation,viewpager,
mapOf(
Pair(R.id.tab_feeds,{FeedsScreenController()})
,Pair(R.id.tab_favorites,{ FavoritesScreenController() })
,Pair(R.id.tab_deals,{ DealsScreenController() })
,Pair(R.id.tab_look_flat,{ FindFlatScreenController() })
,Pair(R.id.tab_settings,{SettingsScreenController()})
)
)
}
override fun injectDependencies() {
getComponent()
}
@BindView(R.id.home_router_host)
lateinit var viewpager: ViewPager
@Inject
override lateinit var injectedPresenter: HomeScreenPresenter
private lateinit var pageAdapter : BNVRouterPagerAdapter
fun getComponent() = DaggerHomeScreenComponent
.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
override fun render(viewState: HomeScreenViewState) {
when(viewState){
is HomeScreenViewState.Idle -> render(viewState)
is HomeScreenViewState.ToScreen -> render(viewState)
}
}
private fun render(viewState: HomeScreenViewState.Idle){
}
private fun render(viewState: HomeScreenViewState.ToScreen){
}
override fun getLayoutId(): Int = R.layout.home_screen_viewpager
override fun handleBack(): Boolean {
Timber.d("handle back in class ")
val childRouter = pageAdapter.currentPrimaryRouter
if (childRouter != null) {
val backStackSizeCondition = childRouter.backstackSize < 2
val backStackResult = childRouter.handleBack()
if (backStackResult
&& backStackSizeCondition
)
return backStackResult
} else {
Timber.e(IllegalStateException(
"handleBack called with getChildRouter(currentlySelectedItemId) == null.")
,"handleBack called with getChildRouter(currentlySelectedItemId) == null.")
}
return false
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.home
import com.biganto.visual.roompark.conductor.BigantoBaseContract
import com.biganto.visual.roompark.presentation.screen.home.HomeScreenViewState
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface HomeScreen : BigantoBaseContract<HomeScreenViewState> {
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.home
import android.content.Context
import com.biganto.visual.roompark.base.IBottomNavigation
import com.biganto.visual.roompark.base.ICollapsingToolBar
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.data.data_provider.AuthContractModule
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import com.biganto.visual.roompark.domain.contract.AuthContract
import com.biganto.visual.roompark.presentation.screen.home.home_routing.HomeBottomNavigationController
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
///**
// * Created by Vladislav Bogdashkin on 30.09.2019.
// */
@PerScreen
@Component(
modules = [HomeScreenModule::class],
dependencies = [AppComponent::class])
interface HomeScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): HomeScreenComponent
}
val presenter: HomeScreenPresenter
fun inject(controller: HomeController)
fun inject(controller: HomeBottomNavigationController)
}
@Module
abstract class HomeScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
@PerScreen
@Binds
abstract fun provideAuth(contract: AuthContractModule): AuthContract
@PerScreen
@Binds
abstract fun provideToolbar(activitiy: RoomParkMainActivity): ICollapsingToolBar
@PerScreen
@Binds
abstract fun provideBottomNavigation(activitiy: RoomParkMainActivity): IBottomNavigation
}
package com.biganto.visual.roompark.presentation.screen.home
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.util.monades.ExceptionString
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class HomeScreenPresenter @Inject constructor(
// ,var context:Context
)
: BigantoBasePresenter<HomeScreen, HomeScreenViewState>() {
override fun defaultErrorViewStateHandler() =
{e: ExceptionString -> HomeScreenViewState.SomeError(e)}
override fun bindIntents() {
val state = restoreStateObservable
subscribeViewState(state.cast(HomeScreenViewState::class.java), HomeScreen::render)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.home
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class HomeScreenViewState : BigantoBaseViewState() {
class Idle : HomeScreenViewState()
class ToScreen(val message:String) : HomeScreenViewState()
class SomeError(val exception: ExceptionString) : HomeScreenViewState()
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.home.home_routing
import android.os.Bundle
import android.util.SparseArray
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.annotation.IdRes
import androidx.annotation.NonNull
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.presentation.screen.home.HomeScreen
import com.biganto.visual.roompark.presentation.screen.home.HomeScreenPresenter
import com.biganto.visual.roompark.presentation.screen.home.HomeScreenViewState
import com.bluelinelabs.conductor.ChangeHandlerFrameLayout
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.google.android.material.bottomnavigation.BottomNavigationView
import timber.log.Timber
/**
* The [Controller] for the Bottom Navigation View. Populates a [BottomNavigationView]
* with the supplied [Menu] resource. The first item set as checked will be shown by default.
* The backstack of each [MenuItem] is switched out, in order to maintain a separate backstack
* for each [MenuItem] - even though that is against the Google Design Guidelines.
*
*
*
* Internally works similarly to [com.bluelinelabs.conductor.support.RouterPagerAdapter],
* in the sense that it keeps track of the currently active [MenuItem] and the paired Child
* [Router]. Everytime we navigate from one to another, or [ ][Controller.onSaveInstanceState] is called, we save the entire instance state of the Child
* [Router], and cache it, so we have it available when we navigate to another [ ] and can then restore the correct Child [Router] (and thus the entire backstack)
*
* @author chris6647@gmail.com
* @see [Material
* Design Guidelines](https://material.io/guidelines/components/bottom-navigation.html.bottom-navigation-behavior)
*/
abstract class BottomNavigationController :
BigantoBaseController<HomeScreenViewState
, HomeScreen
, HomeScreenPresenter>
, HomeScreen {
lateinit var controllerContainer: ChangeHandlerFrameLayout
protected var currentlySelectedItemId = INVALID_INT
protected var routerSavedStateBundles: SparseArray<Bundle>? = null
/**
* Used to popToTag (this) [BottomNavigationController] instance if needed
*
* @return The tag set on the transaction contains this [BottomNavigationController]
* instance
*/
protected abstract val tag: String
constructor():super()
constructor(args: Bundle) : super(args)
override fun onViewBound(v: View) {
retainViewMode=RetainViewMode.RELEASE_DETACH
}
@IdRes
protected abstract fun getStartupMenuId(): Int
protected override fun onAttach(@NonNull view: View) {
super.onAttach(view)
Timber.d(" OnAttachView: ${this::class}")
if (routerSavedStateBundles == null) {
/*
* Fresh start, setup everything.
* Must be done in onAttach to avoid artifacts if using multiple Activities,
* and in case of resuming the app (i.e. when the view is not created again)
*/
val menu = bottomNavigationController.bottomNavigation.menu
val menuSize = menu.size()
routerSavedStateBundles = SparseArray(menuSize)
val startUpMenuItem = menu.findItem(getStartupMenuId())
for (i in 0 until menuSize) {
val menuItem = menu.getItem(i)
/* We want start from direct menu item */
if (menuItem==startUpMenuItem) {
/*
* Seems like the BottomNavigationView always initializes index 0 as isChecked / Selected,
* regardless of what was set in the menu xml originally.
* So basically all we're doing here is always setting up menuItem index 0.
*/
val itemId = menuItem.itemId
bottomNavigationController.bottomNavigation.selectedItemId = itemId
navigateTo(getControllerFor(itemId), false)
break
}
}
} else {
/*
* Since we are restoring our state,
* and onRestoreInstanceState is called before onViewBound,
* all we need to do is rebind.
*/
getChildRouter(currentlySelectedItemId)!!.rebindIfNeeded()
}
}
/**
* Get the Child [Router] matching the supplied ItemId.
*
* @param menuItemId [MenuItem] ID
* @return
*/
protected fun getChildRouter(@IdRes menuItemId: Int): Router? {
return getChildRouter(controllerContainer, "itemId:$menuItemId")
}
/**
* Remove the supplied [Router] as a child of this Controller.
*
* @param childRouter
*/
protected fun destroyChildRouter(childRouter: Router?) {
if (childRouter!=null) removeChildRouter(childRouter)
}
/**
* Navigate to the supplied [Controller], while setting the menuItemId as selected on the
* [BottomNavigationView]. Supplied Controller must match the nextMenuItemId as defined in
* [BottomNavigationMenuItem] or an [IllegalArgumentException] will be thrown.
*
*
*
* If this method is to be called when Requires [BottomNavigationController] is not
* attached, it requires [BottomNavigationController.getTag] to return a valid tag, that
* will return this [BottomNavigationController] instance in this [Router]'s
* backstack.
*
* @param controller to navigate to
* @param shouldOverrideBackstack whether or not to navigate to whatever existing backstack, or to
* set a new one
*/
fun navigateTo(@NonNull controller: Controller, shouldOverrideBackstack: Boolean) {
if (isSupportedController(controller)) {
if (isAttached) {
@IdRes val menuItemId = getIdForController(controller)
if (currentlySelectedItemId != menuItemId) {
/* Navigating to new menuItemId */
if (currentlySelectedItemId != INVALID_INT) {
/* Save state of current router before destroying it */
val oldChildRouter = getChildRouter(currentlySelectedItemId)
save(oldChildRouter!!, currentlySelectedItemId)
destroyChildRouter(oldChildRouter)
}
/* Configure the new childRouter */
val newChildRouter = getChildRouter(menuItemId)
val routerSavedState = routerSavedStateBundles!!.get(menuItemId)
if (!shouldOverrideBackstack
&& routerSavedState != null
&& !routerSavedState.isEmpty) {
newChildRouter!!.restoreInstanceState(routerSavedState)
routerSavedStateBundles!!.remove(menuItemId)
newChildRouter.rebindIfNeeded()
} else {
newChildRouter!!.setRoot(RouterTransaction.with(controller))
}
ensureMenuSelectionState(menuItemId)
} else if (currentlySelectedItemId != INVALID_INT) {
/* Navigating to same menuItemId */
@IdRes val newMenuItem = if (shouldOverrideBackstack) menuItemId else currentlySelectedItemId
/* Don't want to save state, because we are resetting it */
destroyChildRouter(getChildRouter(newMenuItem))
routerSavedStateBundles!!.remove(newMenuItem)
/* Must get reference to newly recreated childRouter to avoid old view not being removed */
getChildRouter(newMenuItem)!!
.setRoot(
RouterTransaction.with(controller)
// .popChangeHandler(HorizontalChangeHandler(false))
// .pushChangeHandler(FadeChangeHandler(false))
)
if (newMenuItem != currentlySelectedItemId) {
ensureMenuSelectionState(newMenuItem)
}
} else {
Timber.e("Attempted to reset backstack on BottomNavigationController with currentlySelectedItemId=$currentlySelectedItemId")
}
} else {
/*
* Navigate to ourselves, and once the view is ready (so we can get the childRouter),
* navigate as instructed.
*/
this@BottomNavigationController.addLifecycleListener(
object : Controller.LifecycleListener() {
override fun postAttach(@NonNull attachedController: Controller, @NonNull view: View) {
super.postAttach(attachedController, view)
this@BottomNavigationController.removeLifecycleListener(this)
this@BottomNavigationController.navigateTo(controller, shouldOverrideBackstack)
}
override fun preDestroy(@NonNull controller: Controller) {
super.preDestroy(controller)
/* Clean ourselves up in case we're destroyed without having been attached */
this@BottomNavigationController.removeLifecycleListener(this)
}
})
router.popToTag(tag)
}
} else {
Timber.e("Attempted to navigate to unsupported controller=$controller")
}
}
/**
* Resets the backstack for the menuItemId matching the [Controller] found at backstack
* index 0.
*
* @param backstack
*/
fun navigateTo(@NonNull backstack: List<RouterTransaction>) {
val rootController = backstack[0].controller()
if (isSupportedController(rootController)) {
val menuItemId = getIdForController(rootController)
if (isAttached()) {
destroyChildRouter(getChildRouter(currentlySelectedItemId))
routerSavedStateBundles!!.remove(currentlySelectedItemId)
if (currentlySelectedItemId != menuItemId) {
ensureMenuSelectionState(menuItemId)
}
/* Must get reference to newly recreated childRouter to avoid old view not being removed */
getChildRouter(currentlySelectedItemId)!!.setBackstack(backstack, FadeChangeHandler())
} else {
/*
* Navigate to ourselves, and once the view is ready (so we can get the childRouter),
* navigate as instructed.
*/
this@BottomNavigationController.addLifecycleListener(
object : Controller.LifecycleListener() {
override fun postAttach(@NonNull controller: Controller, @NonNull view: View) {
super.postAttach(controller, view)
this@BottomNavigationController.removeLifecycleListener(this)
this@BottomNavigationController.navigateTo(backstack)
}
override fun preDestroy(@NonNull controller: Controller) {
super.preDestroy(controller)
/* Clean ourselves up in case we're destroyed without having been attached */
this@BottomNavigationController.removeLifecycleListener(this)
}
})
getRouter().popToTag(tag)
}
} else {
Timber.e("Attempted to navigate to backstack with unsupported root controller=$rootController")
}
}
/**
* Ensure correct Checked state based on given menuItemId
*
* @param menuItemId
*/
private fun ensureMenuSelectionState(@IdRes menuItemId: Int) {
val menu = bottomNavigationController.bottomNavigation.menu
for (i in 0 until menu.size()) {
val menuItem = menu.getItem(i)
if (menuItem.isChecked && menuItem.itemId != menuItemId) {
menuItem.isChecked = false
} else if (menuItem.itemId == menuItemId) {
menuItem.isChecked = true
currentlySelectedItemId = menuItemId
}
}
if (currentlySelectedItemId != menuItemId) {
Timber.e("Unexpected BottomNavigation selected Menu Item.")
}
}
/**
* Saves the Child [Router] into a [Bundle] and caches that [Bundle].
*
*
*
* Be cautious as this call causes the controller flag it needs reattach, so it should only be
* called just prior to destroying the router
*
* @param menuItemId [MenuItem] ID
*/
private fun save(childRouter: Router, @IdRes menuItemId: Int) {
val routerBundle = Bundle()
childRouter.saveInstanceState(routerBundle)
routerSavedStateBundles!!.put(menuItemId, routerBundle)
}
override fun onRestoreInstanceState(@NonNull savedInstanceState: Bundle) {
Timber.d(" onRestoreInstanceState: ${this::class}")
routerSavedStateBundles = savedInstanceState.getSparseParcelableArray(
KEY_STATE_ROUTER_BUNDLES
)
currentlySelectedItemId = savedInstanceState.getInt(KEY_STATE_CURRENTLY_SELECTED_ID, INVALID_INT)
}
override fun onSaveInstanceState(@NonNull outState: Bundle) {
Timber.d(" onSaveInstanceState: ${this::class}")
outState.putSparseParcelableArray(KEY_STATE_ROUTER_BUNDLES, routerSavedStateBundles)
/*
* For some reason the BottomNavigationView does not seem to correctly restore its
* selectedId, even though the view appears with the correct state.
* So we keep track of it manually
*/
outState.putInt(KEY_STATE_CURRENTLY_SELECTED_ID, currentlySelectedItemId)
}
/**
* Return a target instance of [Controller] for given menu item ID
*
* @param menuItemId the ID tapped by the user
* @return the [Controller] instance to navigate to
*/
@NonNull
protected abstract fun getControllerFor(@IdRes menuItemId: Int): Controller
/**
* @param controller
* @return the MenuItemId for matching the given [Controller]
*/
@IdRes
protected abstract fun getIdForController(@NonNull controller: Controller): Int
/**
* @param controller [Controller] to test
* @return whether or not the supplied controller is of a class supported in this [ ]
*/
protected abstract fun isSupportedController(@NonNull controller: Controller): Boolean
companion object {
val TAG = "BottomNavigationContr"
private val KEY_MENU_RESOURCE = "key_menu_resource"
private val KEY_STATE_ROUTER_BUNDLES = "key_state_router_bundles"
private val KEY_STATE_CURRENTLY_SELECTED_ID = "key_state_currently_selected_id"
val INVALID_INT = -1
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.home.home_routing
import android.os.Bundle
import android.view.View
import androidx.annotation.IdRes
import androidx.annotation.NonNull
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.presentation.screen.deals.DealsScreenController
import com.biganto.visual.roompark.presentation.screen.favorites.FavoritesScreenController
import com.biganto.visual.roompark.presentation.screen.feeds.FeedsScreenController
import com.biganto.visual.roompark.presentation.screen.home.DaggerHomeScreenComponent
import com.biganto.visual.roompark.presentation.screen.home.HomeScreenPresenter
import com.biganto.visual.roompark.presentation.screen.home.HomeScreenViewState
import com.biganto.visual.roompark.presentation.screen.settings.SettingsScreenController
import com.biganto.visual.roompark.presentation.screen.to_flat.FindFlatScreenController
import com.bluelinelabs.conductor.Controller
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 14.02.2019.
*/
const val OPEN_CHILD_CONTROLLER_KEY="OPEN_HOME_SCREEN_CHILD_CONTROLLER_ID"
fun toPageBundle(@IdRes menuItemId:Int): Bundle {
val b = Bundle()
b.putInt(OPEN_CHILD_CONTROLLER_KEY, menuItemId)
return b
}
class HomeBottomNavigationController(@IdRes toPage: Int = R.id.tab_feeds)
: BottomNavigationController(toPageBundle(toPage)) {
override fun injectDependencies() {
getComponent()
}
override fun getStartupMenuId(): Int =
args.getInt(OPEN_CHILD_CONTROLLER_KEY, R.id.tab_feeds)
@Inject
override lateinit var injectedPresenter: HomeScreenPresenter
fun getComponent() = DaggerHomeScreenComponent
.factory()
.create(RoomParkApplication.component, activity as RoomParkMainActivity)
.inject(this)
override fun render(viewState: HomeScreenViewState) {
when (viewState) {
is HomeScreenViewState.Idle -> render(viewState)
is HomeScreenViewState.ToScreen -> render(viewState)
is HomeScreenViewState.SomeError -> render(viewState)
}
}
private fun render(viewState: HomeScreenViewState.Idle) {
}
private fun render(viewState: HomeScreenViewState.SomeError) =
showError(viewState.exception)
private fun render(viewState: HomeScreenViewState.ToScreen) {
// snacky.showSnackBar("lul")
}
override fun getLayoutId() = R.layout.tab_routing_screen_view
override val tag: String
get() = "BottomNavigationImpl"
override fun onViewBound(@NonNull v: View) {
super.onViewBound(v)
toolBar.setToolbar()
bottomNavigationController.show()
Timber.d(" onViewBound: ${this::class}")
controllerContainer = v.findViewById(R.id.tabContainer)
bottomNavigationController.bottomNavigation.setOnNavigationItemSelectedListener { item ->
navigateTo(getControllerFor(item.itemId), false)
true
}
router.backstack.clear()
// val toScreen = args.getInt(OPEN_CHILD_CONTROLLER_KEY, R.id.tab_feeds)
}
override fun getControllerFor(menuItemId: Int): Controller = when (menuItemId) {
R.id.tab_feeds -> FeedsScreenController()
R.id.tab_favorites -> FavoritesScreenController()
R.id.tab_deals -> DealsScreenController()
R.id.tab_look_flat -> FindFlatScreenController()
R.id.tab_settings -> SettingsScreenController()
else -> FeedsScreenController()
}
override fun isSupportedController(controller: Controller): Boolean {
return true
}
override fun getIdForController(controller: Controller): Int {
return when (controller) {
is FeedsScreenController -> R.id.tab_feeds
is FavoritesScreenController -> R.id.tab_favorites
is DealsScreenController -> R.id.tab_deals
is FindFlatScreenController -> R.id.tab_look_flat
is SettingsScreenController -> R.id.tab_settings
else -> 0
}
}
override fun handleBack(): Boolean {
/*
* The childRouter should handleBack,
* as this BottomNavigationController doesn't have a back step sensible to the user.
*/
val childRouter = getChildRouter(currentlySelectedItemId)
if (childRouter != null) {
if (childRouter.backstackSize<2) return false
val handleBackResult = childRouter.handleBack()
// if ()
// navigateTo(getControllerFor(R.id.tab_feeds), false)
Timber.d("backstack result $handleBackResult")
return true
} else {
Timber.e(
IllegalStateException(
"handleBack called with getChildRouter(currentlySelectedItemId) == null."
),
"handleBack called with getChildRouter(currentlySelectedItemId) == null."
)
}
return false
}
}
package com.biganto.visual.roompark.presentation.screen.photo
import com.biganto.visual.roompark.conductor.BigantoBaseContract
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface PhotoScreen : BigantoBaseContract<PhotoScreenViewState> {
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.photo
import android.content.Intent
import android.graphics.Bitmap
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import android.widget.ImageView
import androidx.core.os.bundleOf
import androidx.viewpager2.widget.ViewPager2
import butterknife.BindView
import butterknife.OnClick
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.conductor.dialogs.ChooseResolutionDialogController
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.PhotosAdapter
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.textview.MaterialTextView
import timber.log.Timber
import java.io.FileOutputStream
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
const val SELECTED_PHOTO_KEY = "SELECTED_PHOTO_ID"
class PhotoScreenController :
BigantoBaseController<PhotoScreenViewState
, PhotoScreen
, PhotoScreenPresenter>
, PhotoScreen {
constructor(args: Bundle):super(args)
constructor(id: Int) : super(bundleOf(SELECTED_PHOTO_KEY to id))
@BindView(R.id.photoTitle)
lateinit var albumTitle: MaterialTextView
@BindView(R.id.photo_frame)
lateinit var photoViewPager: ViewPager2
@BindView(R.id.photosPreviewSlider)
lateinit var slider: PhotoPreviewSlider
@BindView(R.id.choose_size_button)
lateinit var chooseSizeButton : ImageView
@OnClick(R.id.choose_size_button)
fun onSwitchSize(){
router.pushController(RouterTransaction.with(ChooseResolutionDialogController(ArrayList(
(photoViewPager.adapter as PhotosAdapter)
.getItem(photoViewPager.currentItem).resolutionList
)
)).pushChangeHandler(DialogChangeHandler())
.popChangeHandler(DialogChangeHandler())
)
}
@OnClick(R.id.close_current_button)
fun onCloseArticle(){
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) {
// tempSystemUiFlag = activity?.window?.decorView?.systemUiVisibility
super.onAttach(view)
}
private var tempSystemUiFlag : Int? = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
override fun onDetach(view: View) {
// activity?.window?.decorView?.systemUiVisibility = tempSystemUiFlag?:View.SYSTEM_UI_FLAG_LAYOUT_STABLE
super.onDetach(view)
}
private fun bindRecycler() {
photoViewPager.isNestedScrollingEnabled = false
photoViewPager.offscreenPageLimit = 1
photoViewPager.adapter = PhotosAdapter()
setVewPager()
}
private fun setVewPager(){
val ap = PhotosAdapter()
photoViewPager.adapter = ap
photoViewPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
photoViewPager.offscreenPageLimit = 2
ap.notifyDataSetChanged()
photoViewPager.invalidate()
slider.setUpViewPager(photoViewPager)
}
override fun onViewBound(v: View) {
toolBar.setToolbar()
bottomNavigationController.hide()
bindRecycler()
// activity?.window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
}
override fun injectDependencies() {
getComponent()
}
@Inject
override lateinit var injectedPresenter: PhotoScreenPresenter
// @Inject
// lateinit var snacky:ISnackBarProvider
lateinit var rpActivity: RoomParkMainActivity
fun getComponent() = DaggerPhotoScreenComponent.factory()
.create(RoomParkApplication.component
,activity as RoomParkMainActivity
,args.getInt(SELECTED_PHOTO_KEY)).inject(this)
override fun render(viewState: PhotoScreenViewState) {
Timber.d("render: $viewState")
when(viewState){
is PhotoScreenViewState.Idle -> render(viewState)
is PhotoScreenViewState.PhotoSelected -> render(viewState)
is PhotoScreenViewState.PhotoFetched -> render(viewState)
is PhotoScreenViewState.PhotoListLoaded -> render(viewState)
is PhotoScreenViewState.AldumFetched-> render(viewState)
is PhotoScreenViewState.SomeError -> render(viewState)
}
}
private fun render(viewState: PhotoScreenViewState.Idle){
}
private fun render(viewState: PhotoScreenViewState.SomeError) =
showError(viewState.exception)
private fun render(viewState: PhotoScreenViewState.PhotoListLoaded) {
(photoViewPager.adapter as PhotosAdapter).setItems(
viewState.list.asSequence().sortedBy{it.sort}.toList()
)
slider.visibility= View.VISIBLE
slider.setUpViewPager(photoViewPager)
photoViewPager.currentItem =
(photoViewPager.adapter as PhotosAdapter).indexById(viewState.selectedId)
}
private fun render(viewState: PhotoScreenViewState.AldumFetched) {
albumTitle.text = viewState.model.title
}
private fun render(viewState: PhotoScreenViewState.PhotoFetched){
(photoViewPager.adapter as PhotosAdapter).setItems(arrayListOf(viewState.model))
}
private fun render(viewState: PhotoScreenViewState.PhotoSelected){
// (photoViewPager.adapter as PhotoViewerAdapter).setItems(arrayListOf(viewState.model))
}
override fun handleBack(): Boolean {
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
// activity?.window?.decorView?.systemUiVisibility = tempSystemUiFlag?:View.SYSTEM_UI_FLAG_LAYOUT_STABLE
router.popController(this)
return true
}
override fun getLayoutId(): Int = R.layout.photo_view_screen
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.photo
import android.content.Context
import com.biganto.visual.roompark.base.IBottomNavigation
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import javax.inject.Named
///**
// * Created by Vladislav Bogdashkin on 30.09.2019.
// */
@PerScreen
@Component(
modules = [PhotoScreenModule::class]
,dependencies = [AppComponent::class]
)
interface PhotoScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
,@BindsInstance @Named(SELECTED_PHOTO_KEY) camIndex:Int
): PhotoScreenComponent
}
fun inject(controller: PhotoScreenController)
}
@Module
abstract class PhotoScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
@PerScreen
@Binds
abstract fun provideBottomNavigation(activitiy: RoomParkMainActivity): IBottomNavigation
}
package com.biganto.visual.roompark.presentation.screen.photo
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.PhotoInteractor
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
import javax.inject.Named
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class PhotoScreenPresenter @Inject constructor(
private val interactor: PhotoInteractor
,@Named(SELECTED_PHOTO_KEY) private var selectedIndex:Int
)
: BigantoBasePresenter<PhotoScreen, PhotoScreenViewState>() {
override fun defaultErrorViewStateHandler() =
{ e: ExceptionString -> PhotoScreenViewState.SomeError(e) }
override fun bindIntents() {
val fetchAlbum = interactor.fetchPhoto(selectedIndex)
.flatMap { photo ->
Observable.merge<PhotoScreenViewState>(
arrayListOf(
interactor.getAlbum(photo.albumId)
.map { PhotoScreenViewState.AldumFetched(it) }
,
interactor.fetchPhotos(photo.albumId)
.map<PhotoScreenViewState> {
PhotoScreenViewState.PhotoListLoaded(it,selectedIndex)
}
.startWith(Observable.just(PhotoScreenViewState.PhotoFetched(photo)))
))
}
val state = restoreStateObservable
.mergeWith(fetchAlbum)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(PhotoScreenViewState::class.java), PhotoScreen::render)
}
override fun detachView() {
super.detachView()
restoreStateObservable.accept(PhotoScreenViewState.Idle())
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.photo
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.domain.model.PhotoModel
import com.biganto.visual.roompark.domain.model.PhotoResolutionModel
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class PhotoScreenViewState : BigantoBaseViewState() {
class Idle : PhotoScreenViewState()
class PhotoListLoaded(val list:List<PhotoModel>,val selectedId:Int) : PhotoScreenViewState()
class PhotoFetched(val model:PhotoModel) : PhotoScreenViewState()
class PhotoSelected(val model:PhotoResolutionModel) : PhotoScreenViewState()
class SomeError(val exception: ExceptionString) : PhotoScreenViewState()
class AldumFetched(val model:AlbumPreviewModel) : PhotoScreenViewState()
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.photo.util
import android.content.Context
import android.graphics.SurfaceTexture
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.TextureView
import android.view.View
import androidx.viewpager2.widget.ViewPager2
import kotlinx.android.synthetic.main.photo_page_viewholder.view.*
import timber.log.Timber
/**
* Created by Vladislav Bogdashkin on 22.12.2019.
*/
class PhotoPreviewSlider @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : TextureView(context, attrs, defStyleAttr)
,TextureView.SurfaceTextureListener{
private var texThread: RendererThread?=null
private var adapter: PhotosAdapter? = null
private var vp:ViewPager2? = null
fun setUpViewPager(viewpager:ViewPager2) {
this.adapter = viewpager.adapter as PhotosAdapter
with(viewpager) {
setPageTransformer { page, position ->
setParallaxTransformation(page, position)
}
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
var actualPosition = 0
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
if (position == actualPosition){
processSliding(positionOffset)
}
else{
processSliding(positionOffset-1)
}
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
}
override fun onPageSelected(position: Int) {
actualPosition = position
texThread?.setIndex(position)
super.onPageSelected(position)
}
})
}
vp = viewpager
}
private fun setParallaxTransformation(page: View, position: Float) {
page.apply {
when {
position < -1 -> // [-Infinity,-1)
// This page is way off-screen to the left.
alpha = 1f
position < -1 -> // [-1,0){
{
}
position <= 1 -> { // [-1,1]
photo_view.translationX = -position * (width / 2)
// slider.processSliding(position)
}
else -> // (1,+Infinity]
{
// This page is way off-screen to the right.
alpha = 1f
}
}
}
}
fun processSliding(_position:Float){
texThread?.setSlidePosition(_position)
}
val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener(){
override fun onDoubleTap(e: MotionEvent?): Boolean {
Timber.d( "double tap")
return true
}
override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
mIsScrolling=true
Timber.d( "scroll")
texThread?.perfomDrag(distanceX)
return true
}
override fun onDown(e: MotionEvent?): Boolean {
Timber.d( "down")
return true
}
})
private var mIsScrolling = false
init {
surfaceTextureListener=this
val gestureListener = object : OnTouchListener {
private var gesture: GestureDetector = gestureDetector
override fun onTouch(v: View, event: MotionEvent): Boolean {
Timber.w(" ON TOUCH EVENT!")
val performTouch = gesture.onTouchEvent(event)
if (performTouch) return true
if (event.action == MotionEvent.ACTION_UP) {
if (mIsScrolling) {
Timber.d("OnTouchListener --> onTouch ACTION_UP")
mIsScrolling = false
texThread?.perfomDrag(null,mIsScrolling)
}
}
if (event.action == MotionEvent.ACTION_BUTTON_PRESS)
performClick()
return false
}
}
setOnTouchListener(gestureListener)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
Timber.d("On Size Chagned")
super.onSizeChanged(w, h, oldw, oldh)
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) {
print(" onSurfaceTextureSizeChanged")
texThread?.setSize(width,height)
//Ignored
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
print(" onSurfaceTextureUpdated")
//Ignored
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean {
texThread?.isStopped=true
return true
}
override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {
Timber.d("avaliable surf: $surface")
surface?.let {
if (texThread?.isAlive==true)
return
texThread = RendererThread(context,it)
texThread?.setSize(width,height)
texThread?.onChangePage = {ind -> vp?.setCurrentItem(ind,false)}
texThread?.start()
adapter?.let {adapter ->
texThread?.setAdapter(adapter.photosPreviewList)
texThread?.setIndex(vp?.currentItem?:0)
// texThread?.setIndex(min(_l.size,2))
}?: error("adapter not ready!")
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
texThread?.isStopped=true
}
}
package com.biganto.visual.roompark.presentation.screen.photo.util
import android.content.Context
import android.graphics.Point
import android.view.View
import android.view.WindowManager
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.domain.model.PhotoModel
import com.biganto.visual.roompark.domain.model.PhotoResolutionModel
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyclerAdapter
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.github.chrisbanes.photoview.PhotoView
import com.google.android.material.textview.MaterialTextView
import timber.log.Timber
import kotlin.math.absoluteValue
/**
* Created by Vladislav Bogdashkin on 16.10.2019.
*/
class PhotosAdapter : CommonRecyclerAdapter<PhotosViewHolder, PhotoModel>() {
val photosPreviewList:List<String> get() {
val ret = list.map { it.resolutionList.lowelest()?.url?: error("No res urls!")}.toList()
Timber.d("$ret")
return ret
}
fun getItem(index:Int) = list[index]
fun indexById(id:Int):Int = list.indexOfFirst { it.photoId == id }
fun getCurrentItem(pos:Int) = list[pos]
override val vhKlazz = PhotosViewHolder::class
override fun getVhLayout(): Int = R.layout.photo_page_viewholder
}
class PhotosViewHolder(itemView: View) : CommonViewHolder<PhotoModel>(itemView) {
@BindView(R.id.photo_view)
lateinit var photoPreview: PhotoView
@BindView(R.id.photo_description)
lateinit var description: MaterialTextView
private val windowSize by lazy {
val pSize: Point = Point()
(itemView.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager)
.defaultDisplay.getSize(pSize)
pSize
}
override fun onViewBound(model: PhotoModel) {
description.visibility = if (model.description != null) View.VISIBLE else View.GONE
model.description?.let { description.text = it }
model.resolutionList
.minBy {
((windowSize.x - it.resWidth + .1f).absoluteValue
+ (windowSize.y - it.resHeight + .1f).absoluteValue
)
}
?.let { photo ->
Timber.d("val: ${photo.resWidth} : ${photo.resHeight}")
Glide.with(photoPreview)
.load(photo.url)
.thumbnail(model.resolutionList.lowelest()?.let { thumb ->
Glide.with(photoPreview).load(thumb.url).diskCacheStrategy(DiskCacheStrategy.ALL)
.centerCrop()
})
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(photoPreview)
}
}
}
private fun List<PhotoResolutionModel>.lowelest() =
this.minBy { it.resWidth * it.resHeight }
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.photo.util
import android.content.Context
import android.graphics.*
import android.graphics.drawable.Drawable
import android.view.Surface
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import timber.log.Timber
import kotlin.math.abs
import kotlin.math.absoluteValue
import kotlin.math.sign
import kotlin.math.sin
/**
* Created by Vladislav Bogdashkin on 18.07.2019.
*/
private const val CONST_SLEEP_THRESHOLD = 8L
private const val CONST_CENTER_PHOTO_BORDER_PX = 8
private const val CONST_CENTER_HEIGHT_DIFF_PX = 0
private const val CONST_HEIGHT_MARGIN_BORDER_PX = 8
private const val CONST_DIFF_BORDER_PX = 1
private const val CONST_DECAY_TIMER = 500L
class RendererThread(val context: Context, val surface: SurfaceTexture)
: Thread(){
private val dens:Float by lazy { context.resources.displayMetrics.density }
private val SLEEP_THRESHOLD get() = CONST_SLEEP_THRESHOLD
private val CENTER_PHOTO_BORDER_PX get() =
(CONST_CENTER_PHOTO_BORDER_PX *dens).int
private val CENTER_HEIGHT_DIFF_PX get() =
(CONST_CENTER_HEIGHT_DIFF_PX *dens).int
private val HEIGHT_MARGIN_BORDER_PX get() =
(CONST_HEIGHT_MARGIN_BORDER_PX *dens).int
private val DIFF_BORDER_PX get() =
(CONST_DIFF_BORDER_PX *dens).int
private val DECAY_TIMER get() = CONST_DECAY_TIMER
private var isDirty = true
var isStopped = false
private var width = 0
private var height = 0
private var bmpCache:MutableList<Bitmap?> = mutableListOf()
private var holdIndex = 0
get() = if (isDragging) field
else currentIndex
private var currentIndex:Int = 0
set(value) {Timber.w("currentIndex to: $value");field = value}
private var bmpCacheUrls:List<String> = arrayListOf()
fun setAdapter(urls:List<String> ) {
bmpCacheUrls = urls
bmpCache = MutableList(bmpCacheUrls.size) { null}
bmpCacheUrls.fetchUrls()
bordersArray = IntArray(bmpCacheUrls.size*4)
cropArray = IntArray(bmpCacheUrls.size*4)
}
private fun List<String>.fetchUrls(){
this.forEachIndexed{index, s ->
if (bmpCache.getOrNull(index) == null){
Timber.d("loading: $s")
Glide.with(context)
.asBitmap()
.load(bmpCacheUrls[index])
.apply(RequestOptions().override(width/bmpCache.size, height))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(object : CustomTarget<Bitmap>(){
override fun onLoadCleared(placeholder: Drawable?) {
Timber.d(" onLoadCleared ")
bmpCache[index] = null
}
override fun onResourceReady(
resource: Bitmap,
transition: Transition<in Bitmap>?
) {
Timber.d(" onResourceReady $resource")
bmpCache[index] = resource
isDirty = true
}
})
}
}
}
fun setIndex(index:Int){
if (!isDragging) holdIndex = index
currentIndex = index
isDirty=true
}
private var bordersArray:IntArray? = null
private var cropArray:IntArray? = null
private fun IntArray.getPosition(dir:Float):Int {
if (dir < 0 && this.getX(currentIndex) > width / 2f)
return (currentIndex - 1).coerceIn(0,this.size/4-1)
if (dir > 0 && this.getRight(currentIndex) < width / 2f)
return (currentIndex + 1).coerceIn(0,this.size/4-1)
return currentIndex
}
private fun IntArray.getX(index: Int) = if (index>=0) this[index*4+0] else 0
private fun IntArray.getY(index: Int) = this[index*4+1]
private fun IntArray.getRight(index: Int) = if (index>=0) this[index*4+2] else 0
private fun IntArray.getBottom(index: Int) = this[index*4+3]
private fun IntArray.getWidth(index: Int) = this[index*4+2] - this[index*4+0]
private fun IntArray.getHeight(index: Int) = this[index*4+3] - this[index*4+1]
private fun IntArray.aspect(index: Int) =
this.getWidth(index).toFloat()/this.getHeight(index).toFloat()
private fun IntArray.setX(index:Int,block:()->Int){
this[index*4+0] = block.invoke()
}
private fun IntArray.setY(index:Int,block:()->Int){
this[index*4+1] = block.invoke()
}
private fun IntArray.setRight(index:Int,block:()->Int){
this[index*4+2] = block.invoke()
}
private fun IntArray.setBottom(index:Int,block:()->Int){
this[index*4+3] = block.invoke()
}
private val Bitmap.aspect:Float get() = this.width.toFloat()/this.height.toFloat()
private fun calcBorders() {
val centerX = width / 2f - scrollOffset
val adjWidth = height * .6f
bmpCache.forEachIndexed { index, bitmap ->
val limit = (2 - (index - currentIndex).absoluteValue).coerceIn(0, 1)
val direction = (index - currentIndex).sign//cos(((index-currentIndex).sign)*1.57f)
val ch2 = limit * (
(slidePosition * direction).coerceIn(0f, 1f)
+ (1f - direction.absoluteValue) * (1f - slidePosition.absoluteValue)
)
bordersArray?.let { arr ->
val wDiff = height * (bitmap?.aspect ?: 0f)
val _width = adjWidth +
dragKoef*((wDiff - adjWidth) / 2f + CENTER_PHOTO_BORDER_PX) * ch2
arr.setX(index) { arr.getRight(index - 1) }
arr.setRight(index) {(arr.getX(index) + _width).int}
arr.setY(index) {(HEIGHT_MARGIN_BORDER_PX + CENTER_HEIGHT_DIFF_PX * (1f - ch2)).int}
arr.setBottom(index) {
height - (HEIGHT_MARGIN_BORDER_PX + CENTER_HEIGHT_DIFF_PX * (1f - ch2)).int
}
}
cropArray?.let { arr ->
val asp = bordersArray?.aspect(index) ?: 0f
bitmap?.let {
if (asp >= it.aspect) {
arr.setX(index) { 0 }
arr.setRight(index) { it.width }
val hKef = it.aspect / asp
val marg = it.height * (1f - hKef) / 2f
arr.setY(index) { marg.int }
arr.setBottom(index) { it.height - marg.int }
} else {
arr.setY(index) { 0 }
arr.setBottom(index) { it.height }
val hKef = asp / it.aspect
val marg = it.width * (1f - hKef) / 2f
arr.setX(index) { marg.int }
arr.setRight(index) { it.width - marg.int }
}
}
}
}
bordersArray?.let { arr ->
val halfW = arr.getWidth(currentIndex) / 2f
val minusW = arr.getX(holdIndex) - centerX + halfW * (1f + 2f * slidePosition)
bmpCache.forEachIndexed { index, bitmap ->
val limit = (2 - (index - currentIndex).absoluteValue).coerceIn(0, 1)
val direction = (index - currentIndex).sign
val cBorder = (CENTER_PHOTO_BORDER_PX)
val borderKoef = (
direction + 1f
- limit * sin((direction + slidePosition.sign).coerceIn(-1f, 1f) * 1.57f)
* slidePosition.absoluteValue
)
val borderSize = dragKoef*cBorder * borderKoef
arr.setX(index) {(arr.getX(index) - minusW.int + borderSize + DIFF_BORDER_PX).int}
arr.setRight(index) {
(arr.getRight(index) - minusW.int + borderSize - DIFF_BORDER_PX).int
}
}
}
}
private val Float.int:Int get() {return this.toInt()}
fun perfomDrag(distanceX:Float?,dragging:Boolean = true){
isDragging = dragging
if (isDragging) distanceX?.let {perfomScroll(it)}
else {
holdIndex = currentIndex
scrollOffset = 0f
touchOffset=0f
}
isDirty = true
}
private fun perfomScroll(distanceX:Float) {
touchOffset += distanceX * scrollScaleFactor
val p = bordersArray?.getPosition(distanceX.sign)?:0
if (p != currentIndex) {
setIndex(p)
onChangePage?.invoke(p)
}
isDirty=true
}
var onChangePage: ((newPage:Int)->Unit)? = null
private var scrollScaleFactor = 1f
private var touchOffset = 0f
private var scrollOffset = 0f
private var isDragging = false
private var slidePosition = 0f
private var dragKoef = 1f
fun setSlidePosition(newPosition:Float){
slidePosition = newPosition
isDirty = true
}
private var decayFraction = 0f
private fun timerDecay(mills:Long){
isDirty=true
decayFraction+=mills
animateBorders()
scrollOffset = scrollOffset.smoothLerp(touchOffset-scrollOffset,.2f)
if (touchOffset> 0 && scrollOffset >= touchOffset) {
scrollOffset = touchOffset
isDirty=false
}
else if (touchOffset< 0 && scrollOffset <= touchOffset) {
scrollOffset = touchOffset
isDirty=false
}
if (decayFraction> DECAY_TIMER) isDirty=false
val dirty = animateBorders()
if (!isDirty) isDirty = dirty
}
private fun animateBorders():Boolean{
if (isDragging && dragKoef>0){
dragKoef = dragKoef.smoothLerp(-dragKoef,.12f)
if (dragKoef<.1f) dragKoef = 0f
return dragKoef>0
}
if (!isDragging && dragKoef<1f) {
dragKoef = dragKoef.smoothLerp(1f-dragKoef,.03f)
if (dragKoef>.99f) dragKoef = 1f
return dragKoef<1f
}
return false
}
private fun Float.smoothLerp( delta: Float, fraction:Float =.4f): Float {
return this + fraction * (delta)
}
fun setSize(w: Int, h: Int){
width=w
height=h
}
override fun run() {
super.run()
Timber.w("isStopped: $isStopped")
Timber.w("isDirty: $isDirty")
isDirty = true
while (!isStopped) {
while (!isDirty)
sleep(SLEEP_THRESHOLD) // in real life this sleep is more complicated
isDirty = false
val surf = Surface(surface)
val dRect = Rect(
width.div(4) + width.div(2)
, HEIGHT_MARGIN_BORDER_PX
, width - width.div(4)
, height.minus(HEIGHT_MARGIN_BORDER_PX)
)
val c = surf.lockCanvas(dRect)
c.drawColor(Color.BLACK)
timerDecay(SLEEP_THRESHOLD)
if (bmpCache.size>0) calcBorders()
bmpCache.forEachIndexed { index, bitmap ->
// Timber.d("bitmap ${bitmap} / $index")
bitmap?.let {
val bmpX = cropArray?.getX(index) ?: 0
val bmpY = cropArray?.getY(index) ?: 0
val bmpRight = cropArray?.getRight(index) ?: 0
val bmpBottom = cropArray?.getBottom(index) ?: 0
val dstX = bordersArray?.getX(index) ?: 0
val dstY = bordersArray?.getY(index) ?: 0
val dstRight = bordersArray?.getRight(index) ?: 0
val dstBottom = bordersArray?.getBottom(index) ?: 0
c.drawBitmap(
it
, Rect(bmpX, bmpY, bmpRight, bmpBottom)
, Rect(dstX, dstY, dstRight, dstBottom)
, null
)
}
}
surf.unlockCanvasAndPost(c)
surf.release()
// isDirty = false
}
Timber.w("STOPPED; $isStopped")
Timber.w("STOPPED; $isDirty")
isDirty=false
}
private fun calcHeightPixel(index:Int, selectedIndex:Int): Int {
return height
}
private fun calcWidthPixel(index:Int, selectedIndex:Int): Int {
val dist = abs(index-selectedIndex)
val bled = index - selectedIndex
// Timber.d("gonna calc: $bled")
try {
var bled_res = 100;
// Timber.d("res is : $bled_res")
if (bled==0) bled_res = calcHeightPixel(index,selectedIndex)
else bled_res = (calcHeightPixel(index,selectedIndex)* (1f - .05f*dist)).int
bled_res = when(bled)
{
0 -> calcHeightPixel(index,selectedIndex)
in -1 downTo Int.MIN_VALUE -> (calcHeightPixel(index,selectedIndex)* (1f - .05f*dist)).int
in 1 .. Int.MAX_VALUE -> (calcHeightPixel(index,selectedIndex)* (1f - .05f*dist)).int
else -> error("Unexeptable value")
}
// Timber.d("res is : $bled_res")
return bled_res
}catch (e:Exception){Timber.e(e)}
return -1
}
private fun Bitmap.flip():Bitmap{
val flip = Matrix()
flip.postScale(1f, -1f)
return Bitmap.createBitmap(
this,
0,
0,
this.width,
this.height,
flip,
true
)
}
}
package com.biganto.visual.roompark.presentation.screen.settings
import com.biganto.visual.roompark.conductor.BigantoBaseContract
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface SettingsScreen : BigantoBaseContract<SettingsScreenViewState> {
fun signOut(): Observable<Int>
fun clearCache(): Observable<Int>
fun refreshCacheInfo(): Observable<Int>
fun onSubscription(): Observable<SubscriptionModel>
}
package com.biganto.visual.roompark.presentation.screen.settings
import android.annotation.SuppressLint
import android.view.View
import android.widget.ImageView
import androidx.core.widget.NestedScrollView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.HeaderToolbarModel
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
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.PushListAdapter
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.monades.ExceptionString
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxrelay2.BehaviorRelay
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class SettingsScreenController :
BigantoBaseController<SettingsScreenViewState
, SettingsScreen
, SettingsScreenPresenter>()
, SettingsScreen {
override fun signOut(): Observable<Int> =
signOutButton.clicks()
.map { Timber.d("Clicked sign out button"); 1 }
.observeOn(AndroidSchedulers.mainThread())
override fun clearCache(): Observable<Int> =
clearCacheButton.clicks()
.debounce ( 500, TimeUnit.MICROSECONDS )
.map { Timber.d("Clicked clear cache button"); 1 }
.observeOn(AndroidSchedulers.mainThread())
private val refreshEmitter = BehaviorRelay.create<Int>()
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() {
getComponent()
}
@Inject
override lateinit var injectedPresenter: SettingsScreenPresenter
@BindView(R.id.nestedScrollContainer)
lateinit var scrollContainer:NestedScrollView
@BindView(R.id.pushRecyclerView)
lateinit var pushRecycler:RecyclerView
@BindView(R.id.cachedRecyclerView)
lateinit var cachedRecycler:RecyclerView
@BindView(R.id.downloadToursText)
lateinit var toursDownloaderTitle:MaterialTextView
@BindView(R.id.downloadFlatCardsText)
lateinit var flatDownloaderTitle:MaterialTextView
@BindView(R.id.downloadToursIcon)
lateinit var toursDownloaderButton:ImageView
@BindView(R.id.downloadFlatCardsIcon)
lateinit var flatDownloaderButton:ImageView
@BindView(R.id.signOutButton)
lateinit var signOutButton:MaterialTextView
@BindView(R.id.clear_cache_button)
lateinit var clearCacheButton:MaterialTextView
@BindView(R.id.progress_lock_background)
lateinit var progressShame:View
private fun setToolbar(){
toolBar.appBar.liftOnScrollTargetViewId = R.id.nestedScrollContainer
pushRecycler.isNestedScrollingEnabled = false
cachedRecycler.isNestedScrollingEnabled = false
toolBar.setToolbar(
HeaderToolbarModel(
false,null
,resources?.getString(R.string.settings),null)
)
}
private fun bindRecycler(){
pushRecycler.isNestedScrollingEnabled = true
pushRecycler.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
pushRecycler.adapter = PushListAdapter()
pushRecycler.itemAnimator = null
cachedRecycler.isNestedScrollingEnabled = true
cachedRecycler.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
cachedRecycler.adapter = CahcedListAdapter()
cachedRecycler.itemAnimator = null
}
override fun onViewBound(v: View) {
bottomNavigationController.show()
setToolbar()
bindRecycler()
}
override fun render(viewState: SettingsScreenViewState) {
super.render(viewState)
Timber.d("Render state $viewState")
when(viewState){
is SettingsScreenViewState.Idle -> render(viewState)
is SettingsScreenViewState.LoadSettingsList -> render(viewState)
is SettingsScreenViewState.SomeError -> render(viewState)
is SettingsScreenViewState.SignOut -> render(viewState)
is SettingsScreenViewState.OnCacheDeleting -> render(viewState)
is SettingsScreenViewState.LoadCachInfo -> render(viewState)
is SettingsScreenViewState.LoadSubscriptions -> render(viewState)
is SettingsScreenViewState.SubscriptionStatus -> render(viewState)
is SettingsScreenViewState.SubscriptionError -> render(viewState)
}
}
private fun render(viewState: SettingsScreenViewState.SomeError) =
showError(viewState.exception)
private fun render(viewState: SettingsScreenViewState.Idle){
progressShame.setGone(true)
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){
val isProgressed = viewState.progress>=1f
progressShame.setGone(isProgressed)
clearCacheButton.setGone(!isProgressed)
refreshEmitter.accept(1)
}
private fun render(viewState: SettingsScreenViewState.SignOut){
router.setRoot(RouterTransaction.with(SplashScreenController())
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
)
}
@SuppressLint("SetTextI18n")
private fun render(viewState: SettingsScreenViewState.LoadSettingsList){
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()})"
}
private fun getComponent() = DaggerSettingsScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
override fun getLayoutId(): Int = R.layout.settings_screen
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.settings
import android.content.Context
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
@PerScreen
@Component(
modules = [SettingsScreenModule::class]
,dependencies = [AppComponent::class]
)
interface SettingsScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): SettingsScreenComponent
}
fun inject(controller: SettingsScreenController)
}
@Module
abstract class SettingsScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
// @PerScreen
// @Binds
// abstract fun provideAuth(contract: AuthContractModule): AuthContract
}
package com.biganto.visual.roompark.presentation.screen.settings
import android.content.Context
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.SettingsInteractor
import com.biganto.visual.roompark.domain.model.CachedDataModel
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.model.TitledSubscriptionModel
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class SettingsScreenPresenter @Inject constructor(
private val interactor: SettingsInteractor,
private val activity: Context
)
: BigantoBasePresenter<SettingsScreen, SettingsScreenViewState>() {
private val restoreModel = RestoreModel(mutableListOf(), arrayListOf(), arrayListOf())
override fun detachView() {
super.detachView()
restoreStateObservable.accept(SettingsScreenViewState.RestoreView(restoreModel))
}
override fun defaultErrorViewStateHandler() =
{ e: ExceptionString -> SettingsScreenViewState.SomeError(e) }
override fun bindIntents() {
val onSubChecked = intent(SettingsScreen::onSubscription)
.flatMap { sub ->
interactor.switchSubscription(sub, !sub.state)
.andThen(
Observable.just<SettingsScreenViewState>(
SettingsScreenViewState.SubscriptionStatus(
sub.id, !sub.state
)
)
.doOnNext {
val ind = restoreModel
.subs
.indexOfFirst { it.subModel.id == sub.id }
restoreModel.subs[ind] =
TitledSubscriptionModel(
title = restoreModel.subs[ind].title,
subModel = SubscriptionModel(
topic = sub.topic,
id = restoreModel.subs[ind].subModel.id,
state = !sub.state
)
)
}
)
.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) }
val fetchSubscriptions = interactor.getSubscriptions()
.doOnNext { restoreModel.subs = it.toMutableList() }
.map { SettingsScreenViewState.LoadSubscriptions(it) }
val onSignOut = intent(SettingsScreen::signOut)
.flatMap {
interactor.signOut()
.andThen(Observable.just<SettingsScreenViewState>(SettingsScreenViewState.SignOut()))
.onErrorReturn(::parseError)
}
val refreshInfo = intent(SettingsScreen::refreshCacheInfo)
.flatMap { fetchCache }
val onClearCache = intent(SettingsScreen::clearCache)
.flatMap {
interactor.deleteCacheFiles()
.map<SettingsScreenViewState> {
SettingsScreenViewState.OnCacheDeleting(
it.first / it.second.toFloat()
)
}
.startWith(SettingsScreenViewState.OnCacheDeleting(0f))
.doOnError { Timber.e(it) }
.onErrorReturn(::parseError)
}
val state = Observable.mergeDelayError(
arrayListOf(
restoreStateObservable,
fetchSettings,
onSignOut,
onClearCache,
refreshInfo,
fetchSubscriptions,
fetchCache,
onSubChecked
)
)
.doOnError { Timber.e(it) }
.subscribeOn(Schedulers.io())
.onErrorReturn(::parseError)
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(SettingsScreenViewState::class.java), SettingsScreen::render)
}
}
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
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.TitledSubscriptionModel
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class SettingsScreenViewState : BigantoBaseViewState() {
class Idle : SettingsScreenViewState()
class LoadSettingsList(val settings:SettingsModel) : SettingsScreenViewState()
// class LoadCachedInfo(val cached:SettingsModel) : SettingsScreenViewState()
class SomeError(val exception: ExceptionString) : SettingsScreenViewState()
class SignOut() : SettingsScreenViewState()
class OnCacheDeleting(val progress:Float) : 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()
}
package com.biganto.visual.roompark.presentation.screen.settings.util
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import butterknife.ButterKnife
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.domain.model.CachedDataModel
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.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.view.clicks
import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject
import timber.log.Timber
import kotlin.reflect.KClass
import kotlin.reflect.full.valueParameters
import kotlin.reflect.jvm.javaType
/**
* Created by Vladislav Bogdashkin on 16.10.2019.
*/
abstract class CommonRecyclerAdapter<VH:CommonViewHolder<M>,M:Any> : RecyclerView.Adapter<VH>() {
private val onModelClicked = PublishSubject.create<M>()
val onItemClicked : Observable<M>
get() = onModelClicked
protected var list: MutableList<M> = mutableListOf()
fun setItems(items:List<M>){
this.list.clear()
this.list.addAll(items)
notifyDataSetChanged()
}
fun addItems(items:List<M>){
items.asSequence().filter { !list.contains(it) }.toList().let {uniqueItems->
this.list.addAll(uniqueItems)
notifyItemRangeInserted(list.size-uniqueItems.size,uniqueItems.size)
}
}
protected abstract val vhKlazz : KClass<VH>
@LayoutRes
protected abstract fun getVhLayout() : Int
private fun inflateViewHolder(parent:ViewGroup): View =
LayoutInflater.from(parent.context)
.inflate(getVhLayout(), parent, false)
private fun getViewHolderInstance(klazz:KClass<VH>, view:View):VH =
klazz.constructors.first{ it.valueParameters[0].type.javaType == View::class.java}.call(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH =
getViewHolderInstance(vhKlazz,inflateViewHolder(parent))
override fun getItemCount(): Int = list.size
override fun onBindViewHolder(holder: VH, position: Int) {
holder.bindModel(list[position])
holder.onClicked()
.subscribe(onModelClicked)
}
}
abstract class CommonViewHolder<M:Any>(itemView: View): RecyclerView.ViewHolder(itemView) {
abstract fun onViewBound(model: M)
fun bindModel(model: M){
ButterKnife.bind(this, itemView)
bindedModel = model
onViewBound(model)
}
fun onClicked(): Observable<M> = itemView.clicks().map { bindedModel }
protected lateinit var bindedModel: M
}
class PushListAdapter : CommonRecyclerAdapter<PushViewHolder,TitledSubscriptionModel>() {
override val vhKlazz: KClass<PushViewHolder>
get() = PushViewHolder::class
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<TitledSubscriptionModel>(itemView) {
@BindView(R.id.bellSwitcherTitle)
lateinit var bellTitle:MaterialTextView
@BindView(R.id.bellSwitch)
lateinit var switcher:ViewGroup
override fun onViewBound(model: TitledSubscriptionModel) {
Timber.d("model is : $model")
bellTitle.text = model.title
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
}
}
class CahcedListAdapter : CommonRecyclerAdapter<CachedViewHolder,CachedDataModel>() {
override val vhKlazz: KClass<CachedViewHolder>
get() = CachedViewHolder::class
override fun getVhLayout(): Int = R.layout.text_description_viewholder
}
class CachedViewHolder(itemView: View) : CommonViewHolder<CachedDataModel>(itemView) {
@BindView(R.id.descriptionTitle)
lateinit var title:MaterialTextView
@BindView(R.id.descriptionText)
lateinit var description:MaterialTextView
override fun onViewBound(model: CachedDataModel) {
Timber.d("model is : $model")
title.text = model.title
description.text = model.amountBytes.bytesToSize()
}
}
package com.biganto.visual.roompark.presentation.screen.splash
import com.biganto.visual.roompark.conductor.BigantoBaseContract
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface SplashScreen : BigantoBaseContract<SplashScreenViewState> {
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.splash
import android.view.View
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.presentation.screen.auth.AuthScreenController
import com.biganto.visual.roompark.presentation.screen.home.home_routing.HomeBottomNavigationController
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class SplashScreenController :
BigantoBaseController<SplashScreenViewState
, SplashScreen
, SplashScreenPresenter>()
, SplashScreen {
override fun onViewBound(v: View) {
// toolBar.setToolbar()
bottomNavigationController.hide()
}
override fun injectDependencies() {
getComponent()
}
@Inject
override lateinit var injectedPresenter: SplashScreenPresenter
// @Inject
// lateinit var snacky:ISnackBarProvider
lateinit var rpActivity: RoomParkMainActivity
fun getComponent() = DaggerSplashScreenComponent
.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
override fun render(viewState: SplashScreenViewState) {
when(viewState){
is SplashScreenViewState.Idle -> render(viewState)
is SplashScreenViewState.ToAuthScreen -> render(viewState)
is SplashScreenViewState.ToHomeScreen -> render(viewState)
is SplashScreenViewState.SomeError -> render(viewState)
}
}
private fun render(viewState: SplashScreenViewState.Idle){
}
private fun render(viewState: SplashScreenViewState.SomeError) =
showError(viewState.exception)
private fun render(viewState: SplashScreenViewState.ToAuthScreen){
router.pushController(RouterTransaction.with(AuthScreenController())
.popChangeHandler(FadeChangeHandler())
.pushChangeHandler(FadeChangeHandler())
)
}
private fun render(viewState: SplashScreenViewState.ToHomeScreen){
router.setRoot(RouterTransaction.with(HomeBottomNavigationController())
.popChangeHandler(FadeChangeHandler(200L))
.pushChangeHandler(FadeChangeHandler(300L))
)
}
override fun getLayoutId(): Int = R.layout.splash_screen
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.splash
import android.content.Context
import com.biganto.visual.roompark.base.IBottomNavigation
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
///**
// * Created by Vladislav Bogdashkin on 30.09.2019.
// */
@PerScreen
@Component(
modules = [SplashScreenModule::class]
,dependencies = [AppComponent::class]
)
interface SplashScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): SplashScreenComponent
}
fun inject(controller: SplashScreenController)
}
@Module
abstract class SplashScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
// @PerScreen
// @Binds
// abstract fun provideAuth(contract: AuthContractModule): AuthContract
// @PerScreen
// @Binds
// abstract fun provideToolbar(activitiy: RoomParkMainActivity): ICollapsingToolBar
@PerScreen
@Binds
abstract fun provideBottomNavigation(activitiy: RoomParkMainActivity): IBottomNavigation
}
package com.biganto.visual.roompark.presentation.screen.splash
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.SplashInteractor
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class SplashScreenPresenter @Inject constructor(
private val acitivityContext: RoomParkMainActivity
, private val interactor: SplashInteractor
// ,var context:Context
)
: BigantoBasePresenter<SplashScreen, SplashScreenViewState>() {
override fun defaultErrorViewStateHandler() =
{e: ExceptionString -> SplashScreenViewState.SomeError(e)}
override fun bindIntents() {
val state = restoreStateObservable
.mergeWith(interactor.getAuth()
.map {
if (it) SplashScreenViewState.ToHomeScreen()
else SplashScreenViewState.ToAuthScreen()
}
.delay (250, TimeUnit.MILLISECONDS)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(SplashScreenViewState::class.java), SplashScreen::render)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.splash
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class SplashScreenViewState : BigantoBaseViewState() {
class Idle : SplashScreenViewState()
class ToHomeScreen() : SplashScreenViewState()
class ToAuthScreen() : SplashScreenViewState()
class SomeError(val exception: ExceptionString) : SplashScreenViewState()
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.to_flat
import com.biganto.visual.roompark.conductor.BigantoBaseContract
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface FindFlatScreen : BigantoBaseContract<FindFlatScreenViewState> {
fun getFlat() : Observable<FlatRequestModel>
fun openFlat() : Observable<Int>
}
data class FlatRequestModel(val building:Int,val number:Int)
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.to_flat
import android.view.KeyEvent
import android.view.View
import android.widget.EditText
import android.widget.TextView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.HeaderToolbarModel
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.presentation.screen.estate.EstateScreenController
import com.biganto.visual.roompark.presentation.screen.feeds.EstateTabModel
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.google.android.material.button.MaterialButton
import com.google.android.material.tabs.TabLayout
import com.google.android.material.textfield.TextInputLayout
import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxbinding3.view.keys
import com.jakewharton.rxbinding3.widget.afterTextChangeEvents
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class FindFlatScreenController :
BigantoBaseController<FindFlatScreenViewState
, FindFlatScreen
, FindFlatScreenPresenter>()
, FindFlatScreen {
override fun openFlat(): Observable<Int> =
findFlatButton.clicks()
.map { 1 }
.mergeWith(flatNumberEditor.keys{ it.keyCode == KeyEvent.KEYCODE_ENTER }.map { 1 })
.doOnNext{ flatNumberEditor.hideKeyboard() }
.observeOn(AndroidSchedulers.mainThread())
override fun getFlat(): Observable<FlatRequestModel> =
flatNumberEditor.afterTextChangeEvents()// keys{ it.keyCode == KeyEvent.KEYCODE_ENTER }
.filter { flatNumberInput.editText?.text?.isNotEmpty()?:false }
.map {
FlatRequestModel(
estateTabs[flatTabs.selectedTabPosition].building
, flatNumberInput.editText?.text.toString().toInt()
)
}
.debounce (300,TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
override fun injectDependencies() {
getComponent()
}
private fun setToolbar(){
toolBar.setToolbar(
HeaderToolbarModel(
false,null
,resources?.getString(R.string.flats),null)
)
}
@BindView(R.id.flat_estate_tabs)
lateinit var flatTabs: TabLayout
@BindView(R.id.flatNumberInputer)
lateinit var flatNumberInput: TextInputLayout
@BindView(R.id.flatNumberEditText)
lateinit var flatNumberEditor: EditText
@BindView(R.id.findFlatButton)
lateinit var findFlatButton: MaterialButton
@Inject
override lateinit var injectedPresenter: FindFlatScreenPresenter
private val estateTabs = arrayListOf(
EstateTabModel("Д1",1),
EstateTabModel("Д2",2),
EstateTabModel("Д3",3)
)
override fun onViewBound(v: View) {
bottomNavigationController.show()
setToolbar()
estateTabs.forEach {estate ->
val tab = flatTabs.newTab()
.setCustomView(R.layout.to_flat_tab_view)
tab.customView
?.let {
it.findViewById<TextView>(R.id.tab_title)?.text = estate.title
it.findViewById<TextView>(R.id.tab_divider)?.visibility =
if (estateTabs.indexOf(estate) == estateTabs.size - 1) View.GONE
else View.VISIBLE
}
flatTabs.addTab(tab)
}
}
override fun render(viewState: FindFlatScreenViewState) {
super.render(viewState)
Timber.d("Render state $viewState")
when(viewState){
is FindFlatScreenViewState.Idle -> render(viewState)
is FindFlatScreenViewState.SomeError -> render(viewState)
is FindFlatScreenViewState.FlatFounded -> render(viewState)
is FindFlatScreenViewState.FlatNotFound -> render(viewState)
is FindFlatScreenViewState.StartFlat -> render(viewState)
}
}
private fun render(viewState: FindFlatScreenViewState.SomeError) =
showError(viewState.exception)
private fun render(viewState: FindFlatScreenViewState.Idle){
}
private fun render(viewState: FindFlatScreenViewState.FlatFounded){
findFlatButton.isEnabled = true
findFlatButton.text = resources?.getString(R.string.flat_ready_to_watch)
}
private fun render(viewState: FindFlatScreenViewState.FlatNotFound){
findFlatButton.isEnabled = false
viewState.exceptionString.selectHandler(
{findFlatButton.text = resources?.getString(it)},
{findFlatButton.text = it}
)
}
private fun render(viewState: FindFlatScreenViewState.StartFlat){
router.pushController(
RouterTransaction.with(EstateScreenController(viewState.esateId))
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
)
}
private fun getComponent() = DaggerFindFlatScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
override fun getLayoutId(): Int = R.layout.find_flat_screen
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.to_flat
import android.content.Context
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
@PerScreen
@Component(
modules = [FindFlatScreenModule::class],
dependencies = [AppComponent::class])
interface FindFlatScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): FindFlatScreenComponent
}
val presenter: FindFlatScreenPresenter
fun inject(controller: FindFlatScreenController)
}
@Module
abstract class FindFlatScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
}
package com.biganto.visual.roompark.presentation.screen.to_flat
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.FindFlatInteractor
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class FindFlatScreenPresenter @Inject constructor(
private val interactor: FindFlatInteractor
)
: BigantoBasePresenter<FindFlatScreen, FindFlatScreenViewState>() {
override fun defaultErrorViewStateHandler() =
{e: ExceptionString -> FindFlatScreenViewState.SomeError(e)}
//estateId
private var recentFlat:Int? =null
override fun vsByCode(code: Int): (ExceptionString) -> FindFlatScreenViewState =
when (code) {
304 -> {message:ExceptionString ->FindFlatScreenViewState.FlatNotFound(message)}
else -> super.vsByCode(code)
}
override fun bindIntents() {
val getFlatIntent = intent(FindFlatScreen::getFlat)
.doOnNext{recentFlat=null}
.doOnNext{Timber.d(" flat is $it")}
.flatMap { request ->
interactor.getFlat(request.building,request.number)
.doOnNext { recentFlat=it }
.map<FindFlatScreenViewState> {FindFlatScreenViewState.FlatFounded()}
.onErrorReturn (::parseError)
}
.startWith(Observable.just(FindFlatScreenViewState.RequstFlatProgress()))
val startFlatIntent = intent(FindFlatScreen::openFlat)
.map {
if (recentFlat != null) {FindFlatScreenViewState.StartFlat(recentFlat as Int)}
else FindFlatScreenViewState.FlatNotFound(
ExceptionString(R.string.flat_not_found,null)
)
}
val state = restoreStateObservable
.mergeWith(getFlatIntent)
.mergeWith(startFlatIntent)
.doOnError { Timber.e(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(FindFlatScreenViewState::class.java), FindFlatScreen::render)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.to_flat
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class FindFlatScreenViewState : BigantoBaseViewState() {
class Idle : FindFlatScreenViewState()
class RequstFlatProgress : FindFlatScreenViewState()
class FlatFounded : FindFlatScreenViewState()
class FlatNotFound(val exceptionString:ExceptionString) : FindFlatScreenViewState()
class StartFlat(val esateId:Int) : FindFlatScreenViewState()
class SomeError(val exception: ExceptionString) : FindFlatScreenViewState()
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.web_cam
import com.biganto.visual.roompark.conductor.BigantoBaseContract
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface WebCamScreen : BigantoBaseContract<WebCamScreenViewState> {
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.web_cam
import android.content.pm.ActivityInfo
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import androidx.core.os.bundleOf
import butterknife.BindView
import butterknife.OnClick
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.google.android.exoplayer2.ExoPlayerFactory
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.ext.rtmp.RtmpDataSourceFactory
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.source.hls.DefaultHlsDataSourceFactory
import com.google.android.exoplayer2.source.hls.DefaultHlsExtractorFactory
import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
const val SELECTED_CAM_INDEX_KEY = "SELECTED_CAM_INDEX"
class WebCamScreenController :
BigantoBaseController<WebCamScreenViewState
, WebCamScreen
, WebCamScreenPresenter>
, WebCamScreen {
constructor(args: Bundle):super(args)
constructor(id: Int) : super(bundleOf(SELECTED_CAM_INDEX_KEY to id))
@OnClick(R.id.close_current_button)
fun onClickExit(){
handleBack()
}
@BindView(R.id.webCamPlayerView)
lateinit var playerView:PlayerView
lateinit var player:SimpleExoPlayer
override fun onViewBound(v: View) {
activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
toolBar.setToolbar()
bottomNavigationController.hide()
}
override fun injectDependencies() {
getComponent()
}
@Inject
override lateinit var injectedPresenter: WebCamScreenPresenter
fun getComponent() = DaggerWebCamScreenComponent
.factory()
.create(RoomParkApplication.component
,activity as RoomParkMainActivity
,args.getInt(SELECTED_CAM_INDEX_KEY))
.inject(this)
override fun render(viewState: WebCamScreenViewState) {
when(viewState){
is WebCamScreenViewState.Idle -> render(viewState)
is WebCamScreenViewState.CamSelected -> render(viewState)
is WebCamScreenViewState.WebCamsLIstLoaded-> render(viewState)
is WebCamScreenViewState.SomeError -> render(viewState)
}
}
override fun handleBack(): Boolean {
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
return super.handleBack()
}
private fun render(viewState: WebCamScreenViewState.Idle){
}
private fun render(viewState: WebCamScreenViewState.SomeError) =
showError(viewState.exception)
private fun render(viewState: WebCamScreenViewState.CamSelected){
}
private fun render(viewState: WebCamScreenViewState.WebCamsLIstLoaded){
val bandwidthMeter = DefaultBandwidthMeter.Builder(activity).build()
val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory()
val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
player = ExoPlayerFactory.newSimpleInstance(activity!!,
trackSelector
)
// player.addListener(object : Player.EventListener {
//
// override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {
// Timber.d("onTimelineChanged: ");
// }
//
// override fun onTracksChanged(
// trackGroups: TrackGroupArray?,
// trackSelections: TrackSelectionArray?
// ) {
// Timber.d("onTracksChanged: ");
// }
//
// override fun onLoadingChanged(isLoading: Boolean) {
// Timber.d("onLoadingChanged: ");
// }
//
// override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
// Timber.d("onPlayerStateChanged: $playWhenReady");
// Timber.d("onPlayerStateChanged: $playbackState")
// Timber.d("onPlayerStateChanged: ${player.isPlaying}")
//
//
// }
//
// override fun onRepeatModeChanged(repeatMode: Int) {
// Timber.d("onRepeatModeChanged: ");
// }
//
// override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
// Timber.d("onShuffleModeEnabledChanged");
// }
//
// override fun onPlayerError(error: ExoPlaybackException?) {
// Timber.d("onPlayerError")
// }
//
// override fun onPositionDiscontinuity(reason: Int) {
// Timber.d("onPositionDiscontinuity")
// }
//
// override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters?) {
// Timber.d("onPlaybackParametersChanged")
// }
//
// override fun onPlaybackSuppressionReasonChanged(playbackSuppressionReason: Int) {
// Timber.d("onPlaybackSuppressionReasonChanged")
// }
//
// override fun onIsPlayingChanged(isPlaying: Boolean) {
// Timber.d("onIsPlayingChanged")
// }
//
// })
playerView.player = player
val sourceUrl = viewState.camList.first{it.index == viewState.selectedCamIndex}
.streams
.last().hls
// var rtmpUrl = viewState.camList.first{it.index == viewState.selectedCamIndex}.streams
// .first().rtmp
// rtmpUrl = "rtmp://room-park.ru:1935/cam2-360/stream"
// Timber.d("source url : ${Uri.parse(rtmpUrl)}")
val mediaSource = buildMediaSource(Uri.parse(sourceUrl))
// val mediaSource = buildRtmps(Uri.parse(rtmpUrl))
player.prepare(mediaSource,true,true)
player.playWhenReady = true
// player.seekToDefaultPosition()
}
override fun getLayoutId(): Int = R.layout.webcam_screen
private fun buildMediaSource(uri: Uri): MediaSource {
val source = DefaultDataSourceFactory(activity,"ua")
val dataSourceFactory = DefaultHlsDataSourceFactory(source)//.createDataSource(DATA_TYPE_DRM)
val extr = DefaultHlsExtractorFactory(DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM,true)
(DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES)
return HlsMediaSource.Factory(dataSourceFactory)
.setExtractorFactory(extr)
.setAllowChunklessPreparation(true)
.createMediaSource(uri)
}
private fun buildRtmps(uri: Uri): MediaSource {
val source = DefaultDataSourceFactory(activity,"ua")
val dataSourceFactory = RtmpDataSourceFactory()
return ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(uri)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.web_cam
import android.content.Context
import com.biganto.visual.roompark.base.IBottomNavigation
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import javax.inject.Named
///**
// * Created by Vladislav Bogdashkin on 30.09.2019.
// */
@PerScreen
@Component(
modules = [WebCamScreenModule::class]
,dependencies = [AppComponent::class]
)
interface WebCamScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
,@BindsInstance @Named(SELECTED_CAM_INDEX_KEY) camIndex:Int
): WebCamScreenComponent
}
fun inject(controller: WebCamScreenController)
}
@Module
abstract class WebCamScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
@PerScreen
@Binds
abstract fun provideBottomNavigation(activitiy: RoomParkMainActivity): IBottomNavigation
}
package com.biganto.visual.roompark.presentation.screen.web_cam
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.WebCamsInteractor
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
import javax.inject.Named
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class WebCamScreenPresenter @Inject constructor(
private val interactor: WebCamsInteractor
,@Named(SELECTED_CAM_INDEX_KEY) private var selectedCamIndex:Int
)
: BigantoBasePresenter<WebCamScreen, WebCamScreenViewState>() {
override fun defaultErrorViewStateHandler() =
{e: ExceptionString -> WebCamScreenViewState.SomeError(e) }
override fun bindIntents() {
val state = restoreStateObservable
.mergeWith(interactor.fetchCams()
.map {WebCamScreenViewState.WebCamsLIstLoaded(it.items.toList(),selectedCamIndex)}
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(WebCamScreenViewState::class.java), WebCamScreen::render)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.web_cam
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.WebCamModel
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
sealed class WebCamScreenViewState : BigantoBaseViewState() {
class Idle : WebCamScreenViewState()
class WebCamsLIstLoaded(val camList:List<WebCamModel>,val selectedCamIndex:Int) : WebCamScreenViewState()
class CamSelected(val model:WebCamModel) : WebCamScreenViewState()
class SomeError(val exception: ExceptionString) : WebCamScreenViewState()
}
\ No newline at end of file
package com.biganto.visual.roompark.util.extensions
import android.app.Activity
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.RectF
import android.net.Uri
import android.view.View
import timber.log.Timber
import java.io.File
import java.text.DecimalFormat
import java.text.SimpleDateFormat
import java.util.*
/**
* Created by Vladislav Bogdashkin on 23.10.2019.
*/
private val dateFormatter = SimpleDateFormat("dd / MM / yyyy", Locale.getDefault())
val Date.formatToSimple: String
get() = dateFormatter.format(this)
val Boolean?.asInt
get() = if (this != null && this) 1 else 0
fun Int.toRubly() = String.format("%,d \u20BD",this).replace(',',' ')
fun Long.bytesToSize():String{
if (this==-1L) return "~"
var scale=0
var rest:Long=this
var least=0f
while (rest>=1024) {
least=(rest%1024).toFloat()
rest /= 1024
scale++
}
least/=102.4f
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 Float.format(fracDigits: Int): String {
val df = DecimalFormat()
df.maximumFractionDigits = fracDigits
return df.format(this)
}
fun View.setGone(isGone:Boolean){
this.visibility = if (isGone) View.GONE else View.VISIBLE
}
fun Bitmap.scaleCenterCrop(viewHolder:View): Bitmap {
Timber.d("go crpo")
val sourceWidth = this.width
val sourceHeight = this.height
// Compute the scaling factors to fit the new height and width, respectively.
// To cover the final image, the final scaling will be the bigger
// of these two.
val xScale = viewHolder.measuredWidth.toFloat() / sourceWidth
val yScale = viewHolder.measuredHeight.toFloat() / sourceHeight
val scale = xScale.coerceAtLeast(yScale)
Timber.d(" left/top $xScale/$yScale")
// Now get the displaySize of the source bitmap when scaled
val scaledWidth = scale * sourceWidth
val scaledHeight = scale * sourceHeight
// Let's find out the upper left coordinates if the scaled bitmap
// should be centered in the new displaySize give by the parameters
val left = (viewHolder.measuredWidth - scaledWidth) / 2
val top = (viewHolder.measuredHeight - scaledHeight) / 2
Timber.d(" left/top $left/$top")
// The target rectangle for the new, scaled version of the source bitmap will now
// be
val targetRect = RectF(left, top, left + scaledWidth, top + scaledHeight)
// Finally, we create a new bitmap of the specified displaySize and draw our new,
// scaled bitmap onto it.
val dest = Bitmap.createBitmap(viewHolder.measuredWidth, viewHolder.measuredHeight, this.config)
val canvas = Canvas(dest)
Timber.d(" targetRect $targetRect")
canvas.drawBitmap(this, null, targetRect, null)
return dest
}
val File.folderSize get() =
this.walkTopDown()
.asSequence()
.map {it.length()}
.sum()
fun Activity.startUrl(url:String){
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
this.startActivity(browserIntent)
}
\ No newline at end of file
package com.biganto.visual.roompark.util.monades
import androidx.annotation.StringRes
import com.biganto.visual.roompark.domain.custom_exception.CustomApiException
/**
* Created by Vladislav Bogdashkin on 12.11.2019.
*/
sealed class Either<out E, out V> {
data class Left<out E>(val left: E) : Either<E, Nothing>()
data class Right<out V>(val right: V) : Either<Nothing, V>()
}
data class ExceptionString(@StringRes private val stringId: Int?, private val message: String?) {
fun selectHandler(a1:(Int)->Unit,a2:(String)->Unit) =
when(errorMessage){
is Either.Left -> a1(errorMessage.left)
is Either.Right -> a2(errorMessage.right)
}
constructor(e:CustomApiException) : this(
if (e.customMessage!=null) null else e.messageStringId,
e.customMessage
)
val errorMessage : Either<Int,String> = if (stringId!=null) Either.Left(stringId) else Either.Right(message!!)
// init {
// Timber.d(" messages: $stringId / $message")
// if (stringId==null && message==null) throw error{ "both values cannot be null!" }
// errorMessage = if (stringId!=null) Either.Left(stringId) else Either.Right(message!!)
// }
}
package com.biganto.visual.roompark.util.view_utils.app_bar
import android.content.Context
import android.util.AttributeSet
import com.google.android.material.appbar.AppBarLayout
/**
* Created by Vladislav Bogdashkin on 23.05.2019.
*/
class DragControlAppBarLayoutBehaviour(context: Context, attrs: AttributeSet)
: AppBarLayout.Behavior(context, attrs) {
var allowDrag = false
init {
setDragCallback(object : DragCallback() {
override fun canDrag(appBarLayout: AppBarLayout): Boolean = allowDrag
})
}
}
\ No newline at end of file
package com.biganto.visual.roompark.util.view_utils.grid
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
import androidx.recyclerview.widget.StaggeredGridLayoutManager
/**
* Created by Vladislav Bogdashkin on 04.06.2019.
*/
class CeilsDecoration(private val spanCount:Int, private val spacing:Int? = 0)
: RecyclerView.ItemDecoration
() {
override fun getItemOffsets(outRect: Rect, view: View,
parent: RecyclerView, state: RecyclerView.State) {
val layoutParams = view.layoutParams as? StaggeredGridLayoutManager.LayoutParams? ?: return
if (layoutParams.isFullSpan) {
val adapterPosition= layoutParams.viewAdapterPosition
var topMargin = 0
if (adapterPosition != NO_POSITION)
if (adapterPosition!=0)
topMargin=(spacing?:0)/2
outRect.set(0, topMargin, 0, 0)
return
}
val spanIndex = layoutParams.spanIndex
val layoutPosition = layoutParams.viewLayoutPosition
val itemCount = parent.adapter?.itemCount?:0
val leftEdge = spanIndex == 0
val rightEdge = spanIndex == spanCount - 1
val topEdge = spanIndex < spanCount
val bottomEdge = layoutPosition >= itemCount - spanCount
spacing?.let {
outRect.set(
if (leftEdge) it else it/2,
if (topEdge) it else it/2,
if (rightEdge) it else it/2,
if (bottomEdge) it else it/2
)
}?:outRect.set(0,0,0,0)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.util.view_utils.html
import android.text.Editable
import android.text.Html
import android.text.Spannable
import android.text.Spanned
import android.text.style.TypefaceSpan
import org.xml.sax.XMLReader
import timber.log.Timber
/**
* Created by Vladislav Bogdashkin on 02.12.2019.
*/
class HtmlTagHandler : Html.TagHandler {
override fun handleTag(opening: Boolean, tag: String, output: Editable, xmlReader: XMLReader) {
Timber.e(tag,"Tag" )
if (tag.startsWith("b")) {
Timber.d( "Code tag encountered1")
if (opening) {
Timber.d("Code tag encountered1")
output.setSpan(
TypefaceSpan("monospace"),
output.length,
output.length,
Spannable.SPAN_MARK_MARK
)
} else {
Timber.d("Code tag encountered2")
val obj = getLast(output, TypefaceSpan::class.java)
val where = output.getSpanStart(obj)
output.removeSpan(obj)
output.setSpan(
TypefaceSpan("monospace"),
where,
output.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
} else {
Timber.d("Code Exited")
}
}
private fun getLast(text: Editable, kind: Class<*>): Any? {
val objs = text.getSpans(0, text.length, kind)
if (objs.isEmpty()) {
return null
} else {
for (i in objs.size downTo 1) {
if (text.getSpanFlags(objs[i - 1]) == Spannable.SPAN_MARK_MARK) {
return objs[i - 1]
}
}
return null
}
}
}
\ No newline at end of file
package com.biganto.visual.roompark.util.view_utils.image_view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Path
import android.graphics.Path.FillType
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
import android.widget.ImageView
import com.biganto.visual.roompark.R
/**
* Created by Vladislav Bogdashkin on 23.05.2019.
*/
class RoundedImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ImageView(context, attrs, defStyleAttr){
private lateinit var mMaskPath: Path
private val array =
getContext().obtainStyledAttributes(attrs, R.styleable.RoundedImageView)
private var mCornerRadius =
array.getDimensionPixelSize(R.styleable.RoundedImageView_image_corner_radius,0)
fun init() {
this.setLayerType(View.LAYER_TYPE_HARDWARE, null)
generateMaskPath(width, height)
invalidate()
}
/**
* Set the corner radius to use for the RoundedRectangle.
*
* @param Primitive int - The corner radius of the rounded rectangle.
*/
fun setCornerRadius(cornerRadius: Int) {
mCornerRadius = cornerRadius
generateMaskPath(width, height)
invalidate()
}
override fun onSizeChanged(w: Int, h: Int, oldW: Int, oldH: Int) {
super.onSizeChanged(w, h, oldW, oldH)
if (w != oldW || h != oldH) {
generateMaskPath(w, h)
}
}
private fun generateMaskPath(w: Int, h: Int) {
mMaskPath = Path()
mMaskPath.addRoundRect(RectF(
0f
, 0f
, w.toFloat()
, h.toFloat())
, mCornerRadius.toFloat()
, mCornerRadius.toFloat()
, Path.Direction.CCW)
mMaskPath.fillType = FillType.WINDING
}
val alphaRect = RectF(RectF(0f, 0f, width.toFloat(), height.toFloat()))
override fun onDraw(canvas: Canvas) {
if (canvas.isOpaque) { // If canvas is opaque, make it transparent
alphaRect.set(0f, 0f, width.toFloat(), height.toFloat())
canvas.saveLayerAlpha(alphaRect,255)
}
canvas.clipPath(mMaskPath)
super.onDraw(canvas)
}
}
\ No newline at end of file
package com.biganto.visual.roompark.util.view_utils.snackbar
import android.app.Activity
import android.view.View
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.util.view_utils.snackbar.SnackBarMessageType.*
import com.google.android.material.snackbar.Snackbar
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 06.05.2019.
*/
class SnackBarProvider @Inject constructor(val activity: Activity) : ISnackBarProvider {
override val isRootBounded: Boolean
get() = rootView != null
override val snackBar: Snackbar?
get() = snack
private var snack: Snackbar? = null
private var rootView: View? = null
private val parentView: View
get() = rootView
?: activity.window.decorView.rootView
private fun color(type: SnackBarMessageType) =
ContextCompat.getColor(activity,
when (type) {
DEFAULT -> R.color.colorAccent
ERROR -> R.color.colorAccent
ATTENTION -> R.color.colorAccent
OK -> R.color.colorAccent
})
private fun actionText(type: SnackBarMessageType) =
activity.getString(
when (type) {
DEFAULT -> R.string.snackbar_dismiss_button_default
ERROR -> R.string.snackbar_dismiss_button_error
ATTENTION -> R.string.snackbar_dismiss_button_attention
OK -> R.string.snackbar_dismiss_button_ok
})
override fun bindRootView(rootView: View?) {
this.rootView = rootView
}
override fun showSnackBar(@StringRes stringId: Int) {
showSnackBar(activity.resources.getString(stringId), Snackbar.LENGTH_SHORT)
}
override fun showSnackBar(message: String) {
showSnackBar(message, Snackbar.LENGTH_SHORT)
}
override fun showSnackBar(message: String, length: Int) {
showSnackBar(message, SnackBarMessageType.DEFAULT, length)
}
override fun showSnackBar(message: String, type: SnackBarMessageType, length: Int) {
snack?.dismiss()
snack = Snackbar
.make(parentView, message, length)
.setAction(actionText(type)) {}
.setActionTextColor(color(type))
snack?.show()
}
}
interface ISnackBarProvider{
fun bindRootView(rootView : View?)
val isRootBounded : Boolean
val snackBar : Snackbar?
fun showSnackBar(@StringRes stringId: Int)
fun showSnackBar(message: String)
fun showSnackBar(message: String, length: Int)
fun showSnackBar(message: String, type: SnackBarMessageType, length: Int)
}
enum class SnackBarMessageType{
DEFAULT,
ERROR,
ATTENTION,
OK
}
\ No newline at end of file
package com.biganto.visual.roompark.util.view_utils.status_progress_view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PointF
import android.util.AttributeSet
import android.view.View
import com.biganto.visual.roompark.R
/**
* Created by Vladislav Bogdashkin on 19.09.2019.
*/
class StatusProgressCeil @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val array =
getContext().obtainStyledAttributes(attrs, R.styleable.StatusProgressCeil)
private var direction:StatusProgressDirection =
StatusProgressDirection.fromInt(array.getInt(R.styleable.StatusProgressCeil_direction,1))
private var animateState:StatusProgressAnimationState =
StatusProgressAnimationState.fromInt(array.getInt(R.styleable.StatusProgressCeil_anim_state,1))
private var hasStart:Boolean =
array.getBoolean(R.styleable.StatusProgressCeil_hasStart,false)
private var hasEnd:Boolean =
array.getBoolean(R.styleable.StatusProgressCeil_hasEnd,false)
private var isEnable:Boolean =
array.getBoolean(R.styleable.StatusProgressCeil_isEnable,false)
private var nextEnable:Boolean =
array.getBoolean(R.styleable.StatusProgressCeil_nextEnable,false)
fun setHasStart(has:Boolean) { hasStart = has }
fun setHasEnd(has:Boolean) { hasEnd = has }
fun setIsEnabled(isEnable:Boolean) { this.isEnable = isEnable}
fun setNext(nextisEnable:Boolean) { nextEnable = nextisEnable }
fun setAnimState(state:StatusProgressAnimationState) { animateState = state }
private val animationTimeMills:Long = 80L
private var lastAnimateStateChanged:Long = 0L
val ifAnimateEnd:Boolean get(){
return (System.currentTimeMillis()-lastAnimateStateChanged) > animationTimeMills
}
private val fillEnableColor:Int = resources.getColor(R.color.colorCommonBackground)
private val fillDisableColor:Int = resources.getColor(R.color.colorOpacityBackground)
private val enablePaint:Paint = Paint()
get(){
field.style = Paint.Style.FILL_AND_STROKE
field.color = fillEnableColor
return field
}
private val disablePaint:Paint = Paint()
get(){
field.style = Paint.Style.FILL_AND_STROKE
field.color = fillDisableColor
return field
}
private val prorgressHalfW:Float
get() { return when(direction){
StatusProgressDirection.HORIZONTAL -> measuredHeight/16f
StatusProgressDirection.VERTICAL -> measuredWidth/16f
}}
private val enableRadius:Float
get() { return when(direction){
StatusProgressDirection.HORIZONTAL -> measuredHeight/4f
StatusProgressDirection.VERTICAL -> measuredWidth/4f
}}
private val crossRadius:Float
get() { return when(direction){
StatusProgressDirection.HORIZONTAL -> measuredHeight/2.4f
StatusProgressDirection.VERTICAL -> measuredWidth/2.4f
}}
private val disableRadius:Float
get() { return when(direction){
StatusProgressDirection.HORIZONTAL -> measuredHeight/4f
StatusProgressDirection.VERTICAL -> measuredWidth/4f
}}
//region startProgressRect
private val progressStartLeft:Float
get() {
return when (direction) {
StatusProgressDirection.HORIZONTAL -> 0f
StatusProgressDirection.VERTICAL -> measuredWidth / 2f - prorgressHalfW
}
}
private val progressStartTop:Float
get() {
return when (direction) {
StatusProgressDirection.HORIZONTAL -> measuredHeight / 2f - prorgressHalfW
StatusProgressDirection.VERTICAL -> 0f
}
}
private val progressStartRight:Float
get() {
return when (direction) {
StatusProgressDirection.HORIZONTAL -> measuredWidth/2f
StatusProgressDirection.VERTICAL -> measuredWidth / 2f + prorgressHalfW
}
}
private val progressStartBottom:Float
get() {
return when (direction) {
StatusProgressDirection.HORIZONTAL -> measuredHeight / 2f + prorgressHalfW
StatusProgressDirection.VERTICAL -> measuredHeight / 2f
}
}
//endregion
//region endProgressRect
private val progressEndLeft:Float
get() {
return when (direction) {
StatusProgressDirection.HORIZONTAL -> width/2f
StatusProgressDirection.VERTICAL -> width / 2f - prorgressHalfW
}
}
private val progressEndTop:Float
get() {
return when (direction) {
StatusProgressDirection.HORIZONTAL -> height / 2f - prorgressHalfW
StatusProgressDirection.VERTICAL -> height / 2f
}
}
private val progressEndRight:Float
get() {
return when (direction) {
StatusProgressDirection.HORIZONTAL -> width.toFloat()
StatusProgressDirection.VERTICAL -> width / 2f + prorgressHalfW
}
}
private val progressEndBottom:Float
get() {
return when (direction) {
StatusProgressDirection.HORIZONTAL -> height / 2f + prorgressHalfW
StatusProgressDirection.VERTICAL -> height.toFloat()
}
}
//endregion
private var enableDisableCrossRadius:Int = 0
private val centerStatus:PointF
get() {
return when(direction){
StatusProgressDirection.HORIZONTAL ->
PointF(width/2f,height/2f)
StatusProgressDirection.VERTICAL ->
PointF(width/2f,height/2f)
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawColor(Color.TRANSPARENT)
when(animateState){
StatusProgressAnimationState.ENABLE -> drawEnable(canvas)
StatusProgressAnimationState.DISABLE -> drawDisable(canvas)
StatusProgressAnimationState.TOENABLE -> drawToEnable(canvas)
StatusProgressAnimationState.TODISABLE -> drawToDisable(canvas)
}
}
fun drawStartProgress(canvas: Canvas?){
if (hasStart)
canvas?.drawRect(
progressStartLeft,
progressStartTop,
progressStartRight,
progressStartBottom,
if (isEnable) enablePaint else disablePaint
)
}
fun drawEndProgress(canvas: Canvas?){
if (hasEnd)
canvas?.drawRect(
progressEndLeft,
progressEndTop,
progressEndRight,
progressEndBottom,
if (nextEnable) enablePaint else disablePaint
)
}
private fun drawEnable(canvas: Canvas?){
drawStartProgress(canvas)
drawEndProgress(canvas)
canvas?.drawCircle(centerStatus.x,centerStatus.y
,if (nextEnable) enableRadius else crossRadius
,enablePaint)
}
private fun drawDisable(canvas: Canvas?){
drawStartProgress(canvas)
drawEndProgress(canvas)
canvas?.drawCircle(centerStatus.x,centerStatus.y,enableRadius,disablePaint)
}
private fun drawToEnable(canvas: Canvas?){
drawStartProgress(canvas)
drawEndProgress(canvas)
canvas?.drawCircle(centerStatus.x,centerStatus.y,enableRadius,enablePaint)
}
private fun drawToDisable(canvas: Canvas?){
drawStartProgress(canvas)
drawEndProgress(canvas)
canvas?.drawCircle(centerStatus.x,centerStatus.y,enableRadius,disablePaint)
}
}
enum class StatusProgressDirection(val dir:Int){
HORIZONTAL(1),
VERTICAL(2);
companion object {
fun fromInt(value: Int) = values().first { it.dir == value }
}
}
enum class StatusProgressAnimationState(val state:Int){
ENABLE(1),
DISABLE(2),
TOENABLE(3),
TODISABLE(4);
companion object {
fun fromInt(value: Int) = StatusProgressAnimationState.values().first { it.state == value }
}
}
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="@color/colorError" />
<item android:color="@color/colorAccent" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:color="@color/colorPrimary" />
<item android:state_focused="true" android:color="@color/colorPrimary" />
<item android:state_pressed="true" android:color="@color/colorPrimary" />
<item android:color="@color/colorAccent" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="@color/colorGray" />
<item android:color="@color/colorAccent" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:color="@color/colorInvertedText" />
<item android:state_focused="true" android:color="@color/colorOpacityBackgroundInv" />
<item android:state_pressed="true" android:color="@color/colorOpacityBackgroundInv" />
<item android:color="@color/colorOpacityBackgroundInv" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/colorAccent" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="true"
android:width="8dp" android:height="8dp">
<shape android:shape="oval">
<size android:width="8dp" android:height="8dp"/>
<solid android:color="@color/colorAccent" />
</shape>
</item>
<item
android:width="8dp" android:height="8dp">
<shape android:shape="oval">
<size android:width="8dp" android:height="8dp"/>
<solid android:color="@color/colorGray" />
</shape>
</item>
</selector>
\ No newline at end of file
<?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">
<bitmap
android:width="14dp"
android:height="15dp"
android:src="@drawable/ic_bell_off"
android:tintMode="multiply"
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">
<bitmap
android:right="27dp"
android:top="5dp"
android:bottom="5dp"
android:src="@drawable/ic_bell_on"
android:mipMap="true"
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"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:color="@color/colorAccent">
</item>
<item android:state_checked="true"
android:color="@color/colorAccent">
</item>
<item android:color="@color/colorOpacityBackgroundInv">
</item>
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:width="24dp" android:height="24dp">
<shape android:shape="oval">
<size android:width="24dp" android:height="24dp"/>
<stroke
android:width="2dp"
android:color="#00000000" />
<padding
android:left="0dp"
android:top="0dp"
android:right="0dp"
android:bottom="0dp" />
</shape>
</item>
<item android:state_checked="true"
android:width="24dp" android:height="24dp">
<shape android:shape="oval">
<size android:width="24dp" android:height="24dp"/>
<stroke
android:width="2dp"
android:color="#00000000" />
</shape>
</item>
<item>
<shape android:shape="oval">
<size android:width="24dp" android:height="24dp"/>
<stroke
android:width="2dp"
android:color="#00000000" />
</shape>
</item>
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<layer-list>
<item>
<shape android:shape="rectangle">
<solid android:color="#00000000"/>
</shape>
</item>
<item android:gravity="bottom">
<shape android:shape="rectangle">
<size android:height="2dp"/>
<solid android:color="@color/colorAccent"/>
</shape>
</item>
</layer-list>
</item>
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/colorAccent" android:state_checked="true" />
<item android:color="@color/colorGray" />
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="4px"/>
<gradient
android:type="linear"
android:angle="270"
android:centerY="@dimen/bottom_gradient_height"
android:startColor="#00000000"
android:endColor="#14000000">
</gradient>
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="4px"/>
<solid android:color="@color/colorOpacityCardBackground"/>
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:type="linear"
android:angle="270"
android:startColor="@color/colorAccentSecondary"
android:endColor="@color/colorAccent">
</gradient>
</shape>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="10dp"
android:height="16dp"
android:viewportWidth="10"
android:viewportHeight="16">
<group>
<clip-path android:pathData="M1.0819,6.8938L8.0999,0.1113C8.2628,-0.0406 8.5308,-0.0363 8.6987,0.1198L9.8548,1.1944C10.0227,1.351 10.0269,1.6002 9.8639,1.7513L3.3468,8.05L9.8796,14.2304C10.0438,14.3835 10.0396,14.6354 9.87,14.7931L8.7023,15.8789C8.5327,16.0367 8.2618,16.0409 8.0976,15.8879L0.1204,8.3413C-0.0438,8.1887 -0.0396,7.9364 0.13,7.7783L1.0819,6.8938Z M 0,0"/>
<group>
<clip-path android:pathData="M-16,248L359,248L359,-27L-16,-27Z M 0,0"/>
<path
android:pathData="M-5,-5L15,-5L15,21L-5,21Z"
android:fillColor="#227f79"/>
</group>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="26.666668dp"
android:viewportWidth="32"
android:viewportHeight="26.666668">
<path
android:pathData="M-446.6666,-989.3333L53.3333,-989.3333L53.3333,93.3333l-500,-0z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
<group>
<clip-path android:pathData="M-446.6666,-989.3333L53.3333,-989.3333L53.3333,93.3333l-500,-0z M 0,0"/>
<group>
<clip-path android:pathData="M-446.6666,93.3333L53.3333,93.3333l0,-1082.6666l-500,-0z M 0,0"/>
<path
android:pathData="M-453.3333,-995.9999L60,-995.9999L60,100l-513.3333,-0z"
android:fillColor="#000000"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
<group>
<clip-path android:pathData="M2.6667,2.6667L2.6667,24L22.6667,24L22.6667,2.6667ZM0,0L25.3333,0L25.3333,26.6667L0,26.6667Z M 0,0"/>
<group>
<clip-path android:pathData="M-446.6666,93.3333L53.3333,93.3333l0,-1082.6666l-500,-0z M 0,0"/>
<path
android:pathData="M-6.6667,-6.6667L32,-6.6667L32,33.3333L-6.6667,33.3333Z"
android:fillColor="#d8d8d8"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
<group>
<clip-path android:pathData="M24,24L24,8L17.3333,8L17.3333,24ZM25.3333,24l4,-0L29.3333,8L25.3333,8ZM16,24L16,8L2.6667,8L2.6667,24ZM0,5.3333L32,5.3333L32,26.6667L0,26.6667Z M 0,0"/>
<group>
<clip-path android:pathData="M-446.6666,93.3333L53.3333,93.3333l0,-1082.6666l-500,-0z M 0,0"/>
<path
android:pathData="M-6.6667,-1.3333L38.6667,-1.3333L38.6667,33.3333L-6.6667,33.3333Z"
android:fillColor="#d8d8d8"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
<group>
<clip-path android:pathData="m17.3333,5.3333l1.3333,-0L18.6667,26.6667l-1.3333,-0z M 0,0"/>
<group>
<clip-path android:pathData="M-446.6666,93.3333L53.3333,93.3333l0,-1082.6666l-500,-0z M 0,0"/>
<path
android:pathData="M10.6667,-1.3333L25.3333,-1.3333L25.3333,33.3333L10.6667,33.3333Z"
android:fillColor="#000000"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
<group>
<clip-path android:pathData="m25.3333,5.3333l1.3333,-0L26.6667,26.6667l-1.3333,-0z M 0,0"/>
<group>
<clip-path android:pathData="M-446.6666,93.3333L53.3333,93.3333l0,-1082.6666l-500,-0z M 0,0"/>
<path
android:pathData="M18.6667,-1.3333L33.3333,-1.3333L33.3333,33.3333L18.6667,33.3333Z"
android:fillColor="#000000"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
<group>
<clip-path android:pathData="M2.6667,2.6667L24,2.6667L24,4L2.6667,4Z M 0,0"/>
<group>
<clip-path android:pathData="M-446.6666,93.3333L53.3333,93.3333l0,-1082.6666l-500,-0z M 0,0"/>
<path
android:pathData="M-4,-4L30.6667,-4L30.6667,10.6667L-4,10.6667Z"
android:fillColor="#000000"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
<group>
<clip-path android:pathData="M0,4L25.3333,4L25.3333,5.3333L0,5.3333Z M 0,0"/>
<group>
<clip-path android:pathData="M-446.6666,93.3333L53.3333,93.3333l0,-1082.6666l-500,-0z M 0,0"/>
<path
android:pathData="M-6.6667,-2.6667L32,-2.6667L32,12L-6.6667,12Z"
android:fillColor="#000000"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/colorAccent"
android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
</vector>
<vector android:height="32dp" android:viewportHeight="85.333336"
android:viewportWidth="85.333336" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
<group>
<clip-path android:pathData="M0,0L85.3333,0L85.3333,85.3333L0,85.3333Z M 0,0"/>
<path android:fillAlpha="0" android:fillColor="#d8d8d8"
android:fillType="nonZero"
android:pathData="M-6.6667,-6.6667L92,-6.6667L92,92L-6.6667,92Z" android:strokeColor="#00000000"/>
</group>
<group>
<clip-path android:pathData="m46.4147,42.6667 l11.9311,11.9311c0.5207,0.5207 0.5207,1.3649 0,1.8856l-1.7678,1.7678c-0.5207,0.5207 -1.3649,0.5207 -1.8856,-0l-11.9311,-11.9311 -11.9311,11.9311c-0.5207,0.5207 -1.3649,0.5207 -1.8856,-0l-1.7678,-1.7678c-0.5207,-0.5207 -0.5207,-1.3649 0,-1.8856L39.1078,42.6667 27.1767,30.7356c-0.5207,-0.5207 -0.5207,-1.3649 0,-1.8856l1.7678,-1.7678c0.5207,-0.5207 1.3649,-0.5207 1.8856,-0l11.9311,11.9311 11.9311,-11.9311c0.5207,-0.5207 1.3649,-0.5207 1.8856,-0l1.7678,1.7678c0.5207,0.5207 0.5207,1.3649 0,1.8856z M 0,0"/>
<path android:fillAlpha="1" android:fillColor="#ffffff"
android:fillType="nonZero"
android:pathData="m20.1195,20.0249l45.2835,-0L65.403,65.3084l-45.2835,-0z" android:strokeColor="#00000000"/>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<group>
<clip-path android:pathData="M-335,259L40,259L40,-16L-335,-16Z M 0,0"/>
<group>
<clip-path android:pathData="M0,32L32,32L32,0L0,0Z M 0,0"/>
<group>
<clip-path android:pathData="M16,32C24.8366,32 32,24.8366 32,16C32,7.1634 24.8366,0 16,0C7.1634,0 0,7.1634 0,16C0,24.8366 7.1634,32 16,32Z M 0,0"/>
<group>
<clip-path android:pathData="M0,32L32,32L32,0L0,0Z M 0,0"/>
<path
android:fillColor="#FF000000"
android:pathData="M-5,-5L37,-5L37,37L-5,37Z"
android:strokeAlpha="0.64"
android:fillAlpha="0.64"/>
</group>
</group>
<group>
<clip-path android:pathData="M17.7778,16L24,22.2222L22.2222,24L16,17.7778L9.7778,24L8,22.2222L14.2222,16L8,9.7778L9.7778,8L16,14.2222L22.2222,8L24,9.7778L17.7778,16Z M 0,0"/>
<group>
<clip-path android:pathData="M0,32L32,32L32,0L0,0Z M 0,0"/>
<path
android:pathData="M3,3L29,3L29,29L3,29Z"
android:strokeAlpha="0.64"
android:fillColor="#ffffff"
android:fillAlpha="0.64"/>
</group>
</group>
</group>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="14dp"
android:viewportWidth="14"
android:viewportHeight="14">
<group>
<clip-path android:pathData="M-308,250L67,250L67,-25L-308,-25Z M 0,0"/>
<group>
<clip-path android:pathData="M0,14L14,14L14,0L0,0Z M 0,0"/>
<group>
<clip-path android:pathData="M8.4815,7L13.6667,12.1852L12.1852,13.6667L7,8.4815L1.8148,13.6667L0.3333,12.1852L5.5185,7L0.3333,1.8148L1.8148,0.3333L7,5.5185L12.1852,0.3333L13.6667,1.8148L8.4815,7Z M 0,0"/>
<group>
<clip-path android:pathData="M0,14L14,14L14,0L0,0Z M 0,0"/>
<path
android:pathData="M-4.6667,-4.6667L18.6667,-4.6667L18.6667,18.6667L-4.6667,18.6667Z"
android:strokeAlpha="0.64"
android:fillColor="#222222"
android:fillAlpha="0.64"/>
</group>
</group>
</group>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<group>
<clip-path android:pathData="m18.7127,8.2729c0.6038,-0 1.092,0.4887 1.0925,1.0907 0,0.602 -0.4882,1.0907 -1.0925,1.0907L10.5215,10.4543c-0.6033,-0 -1.0925,-0.4887 -1.0925,-1.0907 0,-0.602 0.4893,-1.0907 1.0925,-1.0907zM19.8047,13.4543c0,0.6025 -0.4882,1.0912 -1.092,1.0912L10.5215,14.5455c-0.6033,-0 -1.0925,-0.4887 -1.0925,-1.0912 0,-0.602 0.4893,-1.0907 1.0925,-1.0907l8.1907,-0c0.6043,-0 1.0925,0.4881 1.0925,1.0907zM14.6176,18.6361L10.5215,18.6361c-0.6033,-0 -1.0925,-0.4882 -1.0925,-1.0907 0,-0.6025 0.4893,-1.0912 1.0925,-1.0912l4.0961,-0c0.6033,-0 1.092,0.4887 1.092,1.0912 0,0.602 -0.4887,1.0907 -1.092,1.0907zM22.862,22.3468c0.2111,-0.1389 0.3901,-0.3283 0.5174,-0.5484L23.9013,20.8967L23.9013,26.9093C23.9013,27.5113 23.4121,28 22.8083,28L6.4254,28C5.8221,28 5.3333,27.5118 5.3333,26.9093L5.3333,5.0907C5.3333,4.4887 5.8221,4 6.4254,4L22.8083,4c0.6033,-0 1.0925,0.4882 1.0925,1.0907L23.9008,6.7135L21.7163,10.4926l0,-4.3107L7.5179,6.1819L7.5179,25.8176L21.7163,25.8176l0,-2.7155zM29.3206,9.0521c0.0291,0.1338 0.0081,0.2727 -0.0603,0.3911L22.4335,21.2541C22.3911,21.3271 22.3323,21.3899 22.2613,21.4359L18.6943,23.7878C18.5215,23.9007 18.3001,23.9078 18.1201,23.8047 17.9417,23.701 17.8364,23.506 17.8487,23.2997l0.2556,-4.2612c0.0051,-0.0848 0.0302,-0.167 0.0726,-0.241l6.8262,-11.8088c0.0685,-0.1185 0.1784,-0.2063 0.3088,-0.2477 0.0971,-0.0301 0.9765,-0.2711 2.3661,0.53 1.3896,0.8017 1.6201,1.683 1.6426,1.7811zM20.0706,21.9 L21.1401,21.1944c-0.1759,-0.2272 -0.4862,-0.5356 -1.022,-0.8446 -0.5363,-0.3084 -0.9586,-0.4238 -1.2434,-0.4611L18.7986,21.1663C19.0164,21.251 19.2393,21.3588 19.4617,21.4869 19.683,21.6151 19.8891,21.7535 20.0706,21.9ZM12.9479,23.6765C12.933,23.5836 12.9346,23.4917 12.9381,23.4232 12.8855,23.5228 12.8272,23.6178 12.7643,23.7066 12.5158,24.06 11.8323,23.8501 12.0153,23.3921 12.0281,23.361 12.044,23.3283 12.0598,23.2956 12.0588,23.2951 12.0578,23.2946 12.0568,23.2946 12.0772,23.2435 12.0976,23.194 12.1176,23.1434 12.0174,23.2113 11.9259,23.2905 11.8538,23.4003 11.5511,23.82 10.8553,23.437 11.1467,22.9872 11.3507,22.7043 11.5445,22.4107 11.727,22.1114 10.9693,22.8942 10.2653,23.7312 9.6007,24.5931 9.2837,25.0042 8.5716,24.5977 8.8937,24.1795 9.6437,23.2062 10.4187,22.2284 11.2986,21.3669 11.6503,21.0228 12.1196,20.4494 12.6667,20.4698 12.7986,20.4749 12.9627,20.5418 13.0205,20.6725 13.2091,21.1024 13.0235,21.634 12.7357,22.1365 12.8369,22.1584 12.9269,22.211 12.9913,22.3162 13.0143,22.3555 13.0302,22.3943 13.0465,22.4331 13.2924,22.3933 13.5378,22.4868 13.7306,22.8161c0.0644,0.1098 0.0864,0.1905 0.0864,0.2502 1.0317,-0.17 1.8977,0.0929 2.9387,0.0929 0.5281,-0 0.5281,0.818 0,0.818 -0.8926,-0 -2.0215,-0.4759 -2.8548,-0.071C13.7648,23.9722 13.5695,24.1029 13.4059,24.034 13.2367,23.9635 12.9836,23.8956 12.9479,23.6765Z M 0,0"/>
<group>
<clip-path android:pathData="M-234.6667,93.3333L265.3333,93.3333L265.3333,-989.3333L-234.6667,-989.3333Z M 0,0"/>
<path
android:pathData="M-1.3333,-2.6667L36,-2.6667L36,34.6667L-1.3333,34.6667Z"
android:fillColor="#a1a1a1"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="22dp"
android:viewportWidth="24"
android:viewportHeight="22">
<group>
<clip-path android:pathData="M8,13L8,15L5,15C2.2386,15 0,12.7614 0,10C0,7.8733 1.3277,6.0568 3.1995,5.334C3.9469,2.2723 6.7081,0 10,0C12.6381,0 14.9353,1.4593 16.1285,3.6147C16.0756,3.6386 16.0229,3.663 15.9706,3.6882L16.8369,5.4908L18.7903,5.0616C18.7861,5.0427 18.7819,5.0238 18.7776,5.0049C18.8514,5.0016 18.9255,5 19,5C21.7614,5 24,7.2386 24,10C24,12.7614 21.7614,15 19,15L14,15L14,13L19,13C20.6569,13 22,11.6568 22,10C22,8.3432 20.6569,7 19,7C18.5425,7 18.1035,7.1011 17.7032,7.2935L15.4258,8.388L14.8835,5.9201C14.3842,3.6479 12.3592,2 10,2C7.6829,2 5.6839,3.5902 5.1424,5.8083L4.8946,6.8234L3.9199,7.1997C2.7726,7.6428 2,8.749 2,10C2,11.6568 3.3431,13 5,13L8,13Z M 0,0"/>
<group>
<clip-path android:pathData="M-146,252L229,252L229,-23L-146,-23Z M 0,0"/>
<path
android:pathData="M-5,-5L29,-5L29,20L-5,20Z"
android:fillColor="#227f79"/>
</group>
</group>
<group>
<clip-path android:pathData="M11,22L16,18.2294L11.8565,18.2294L11.8565,8L10.1435,8L10.1435,18.2294L6,18.2294L11,22Z M 0,0"/>
<group>
<clip-path android:pathData="M-146,252L229,252L229,-23L-146,-23Z M 0,0"/>
<path
android:pathData="M1,27L21,27L21,3L1,3Z"
android:fillColor="#227f79"/>
</group>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<group>
<clip-path android:pathData="M25.4385,6.2567C24.3589,5.6705 23.1205,5.3333 21.7993,5.3333 19.4683,5.3333 17.3807,6.3803 16.0023,8.022 14.6173,6.3803 12.5317,5.3333 10.1975,5.3333 8.8795,5.3333 7.6434,5.6705 6.5613,6.2567 4.2397,7.5239 2.6667,9.9604 2.6667,12.7603 2.6667,13.5619 2.799,14.3307 3.0387,15.0499 4.3314,20.777 16.0023,28 16.0023,28c0,-0 11.6631,-7.2228 12.9577,-12.9501C29.1996,14.3307 29.3333,13.5609 29.3333,12.7603c0,-2.7989 -1.573,-5.2346 -3.8948,-6.5036z M 0,0"/>
<group>
<clip-path android:pathData="M-134.6667,93.3333L365.3333,93.3333L365.3333,-989.3333L-134.6667,-989.3333Z M 0,0"/>
<path
android:pathData="M-4,-1.3333L36,-1.3333L36,34.6667L-4,34.6667Z"
android:fillColor="#a1a1a1"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<group>
<clip-path android:pathData="M27,7.6923L30.6667,7.6923L30.6667,24.3077C30.6667,27.9428 27.9167,28 27.9167,28L5,28C1.3333,28 1.3333,24.3077 1.3333,24.3077L1.3333,4L27,4ZM5,26.1538L25.7863,26.1538C25.427,25.7468 25.1667,25.1578 25.1667,24.3077L25.1667,5.8462L3.1667,5.8462L3.1667,24.3077c0,-0 0,1.8462 1.8333,1.8462zM5,9.5385L23.3333,9.5385L23.3333,11.3846L5,11.3846ZM15.0833,20.6154L21.5,20.6154L21.5,22.4615l-6.4167,-0zM15.0833,16.9231L23.3333,16.9231L23.3333,18.7692l-8.25,-0zM15.0833,13.2308L23.3333,13.2308l0,1.8461l-8.25,-0zM5,13.2308L13.25,13.2308L13.25,22.4615L5,22.4615Z M 0,0"/>
<group>
<clip-path android:pathData="M-34.6667,93.3333L465.3333,93.3333L465.3333,-989.3333L-34.6667,-989.3333Z M 0,0"/>
<path
android:pathData="M-5.3333,-2.6667L37.3333,-2.6667L37.3333,34.6667L-5.3333,34.6667Z"
android:fillColor="#a1a1a1"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<group>
<clip-path android:pathData="m16,13.2813c-0.401,-0 -0.7273,-0.3154 -0.7273,-0.7031 0,-0.3877 0.3263,-0.7031 0.7273,-0.7031 0.401,-0 0.7273,0.3154 0.7273,0.7031 0,0.3877 -0.3263,0.7031 -0.7273,0.7031zM9.697,28L9.697,8.2187L22.303,8.2187L22.303,28ZM20.1212,17.9219c-0.4017,-0 -0.7273,0.3148 -0.7273,0.7031 0,0.3883 0.3256,0.7031 0.7273,0.7031 0.4017,-0 0.7273,-0.3148 0.7273,-0.7031 0,-0.3883 -0.3256,-0.7031 -0.7273,-0.7031zM16,10.4687c-1.2031,-0 -2.1818,0.9463 -2.1818,2.1094 0,1.1631 0.9788,2.1094 2.1818,2.1094 1.2031,-0 2.1818,-0.9463 2.1818,-2.1094 0,-1.1631 -0.9788,-2.1094 -2.1818,-2.1094zM5.3333,4L26.6667,4L26.6667,28L23.7576,28L23.7576,6.8125L8.2424,6.8125L8.2424,28L5.3333,28Z M 0,0"/>
<group>
<clip-path android:pathData="M-334.6667,93.3333L165.3333,93.3333L165.3333,-989.3333L-334.6667,-989.3333Z M 0,0"/>
<path
android:pathData="M-1.3333,-2.6667L33.3333,-2.6667L33.3333,34.6667L-1.3333,34.6667Z"
android:fillColor="#a1a1a1"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="25dp"
android:height="16dp"
android:viewportWidth="25"
android:viewportHeight="16">
<group>
<clip-path android:pathData="M1.5,0L23.5,0C24.3281,0 25,0.6719 25,1.5L25,14.5C25,15.3281 24.3281,16 23.5,16L1.5,16C0.6719,16 0,15.3281 0,14.5L0,1.5C0,0.6719 0.6719,0 1.5,0ZM1.5,3.5L1.5,12.5L23.5,12.5L23.5,3.5ZM1.5,3.5 M 0,0"/>
<path
android:pathData="M0,0h25v16h-25z"
android:fillColor="#D8D8D8"
android:strokeColor="#00000000"
android:fillAlpha="1"/>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="25dp"
android:height="16dp"
android:viewportWidth="25"
android:viewportHeight="16">
<group>
<clip-path android:pathData="M1.5,1.5L1.5,14.5L23.5,14.5L23.5,1.5ZM1.5,0L23.5,0C24.3281,0 25,0.6719 25,1.5L25,14.5C25,15.3281 24.3281,16 23.5,16L1.5,16C0.6719,16 0,15.3281 0,14.5L0,1.5C0,0.6719 0.6719,0 1.5,0ZM11.75,11.1875L11.75,4.9727L10.7813,5.9414C10.4883,6.2344 10.0117,6.2344 9.7188,5.9414C9.4258,5.6484 9.4258,5.1758 9.7188,4.8828L11.8828,2.7188C12.0391,2.5625 12.25,2.4883 12.457,2.5C12.4688,2.5 12.4844,2.5 12.5,2.5C12.707,2.5 12.8945,2.582 13.0313,2.7188L15.1914,4.8828C15.4844,5.1758 15.4844,5.6484 15.1914,5.9414C14.8984,6.2344 14.4258,6.2344 14.1328,5.9414L13.25,5.0625L13.25,11.1016L14.1328,10.2188C14.4258,9.9258 14.8984,9.9258 15.1914,10.2188C15.4844,10.5117 15.4844,10.9883 15.1914,11.2813L13.0313,13.4414C12.8711,13.6016 12.6641,13.6719 12.457,13.6602C12.25,13.6719 12.0391,13.6016 11.8828,13.4414L9.7188,11.2813C9.4258,10.9883 9.4258,10.5117 9.7188,10.2188C10.0117,9.9258 10.4883,9.9258 10.7813,10.2188ZM11.75,11.1875 M 0,0"/>
<path
android:pathData="M1.5,1.5L1.5,14.5L23.5,14.5L23.5,1.5ZM1.5,0L23.5,0C24.3281,0 25,0.6719 25,1.5L25,14.5C25,15.3281 24.3281,16 23.5,16L1.5,16C0.6719,16 0,15.3281 0,14.5L0,1.5C0,0.6719 0.6719,0 1.5,0ZM11.75,11.1875L11.75,4.9727L10.7813,5.9414C10.4883,6.2344 10.0117,6.2344 9.7188,5.9414C9.4258,5.6484 9.4258,5.1758 9.7188,4.8828L11.8828,2.7188C12.0391,2.5625 12.25,2.4883 12.457,2.5C12.4688,2.5 12.4844,2.5 12.5,2.5C12.707,2.5 12.8945,2.582 13.0313,2.7188L15.1914,4.8828C15.4844,5.1758 15.4844,5.6484 15.1914,5.9414C14.8984,6.2344 14.4258,6.2344 14.1328,5.9414L13.25,5.0625L13.25,11.1016L14.1328,10.2188C14.4258,9.9258 14.8984,9.9258 15.1914,10.2188C15.4844,10.5117 15.4844,10.9883 15.1914,11.2813L13.0313,13.4414C12.8711,13.6016 12.6641,13.6719 12.457,13.6602C12.25,13.6719 12.0391,13.6016 11.8828,13.4414L9.7188,11.2813C9.4258,10.9883 9.4258,10.5117 9.7188,10.2188C10.0117,9.9258 10.4883,9.9258 10.7813,10.2188ZM11.75,11.1875"
android:fillColor="#000000"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</group>
<group>
<clip-path android:pathData="M1.5,1.5L1.5,14.5L23.5,14.5L23.5,1.5ZM1.5,0L23.5,0C24.3281,0 25,0.6719 25,1.5L25,14.5C25,15.3281 24.3281,16 23.5,16L1.5,16C0.6719,16 0,15.3281 0,14.5L0,1.5C0,0.6719 0.6719,0 1.5,0ZM11.75,11.1875L11.75,4.9727L10.7813,5.9414C10.4883,6.2344 10.0117,6.2344 9.7188,5.9414C9.4258,5.6484 9.4258,5.1758 9.7188,4.8828L11.8828,2.7188C12.0391,2.5625 12.25,2.4883 12.457,2.5C12.4688,2.5 12.4844,2.5 12.5,2.5C12.707,2.5 12.8945,2.582 13.0313,2.7188L15.1914,4.8828C15.4844,5.1758 15.4844,5.6484 15.1914,5.9414C14.8984,6.2344 14.4258,6.2344 14.1328,5.9414L13.25,5.0625L13.25,11.1016L14.1328,10.2188C14.4258,9.9258 14.8984,9.9258 15.1914,10.2188C15.4844,10.5117 15.4844,10.9883 15.1914,11.2813L13.0313,13.4414C12.8711,13.6016 12.6641,13.6719 12.457,13.6602C12.25,13.6719 12.0391,13.6016 11.8828,13.4414L9.7188,11.2813C9.4258,10.9883 9.4258,10.5117 9.7188,10.2188C10.0117,9.9258 10.4883,9.9258 10.7813,10.2188ZM11.75,11.1875 M 0,0"/>
<path
android:pathData="M1.5,1.5L1.5,14.5L23.5,14.5L23.5,1.5ZM1.5,0L23.5,0C24.3281,0 25,0.6719 25,1.5L25,14.5C25,15.3281 24.3281,16 23.5,16L1.5,16C0.6719,16 0,15.3281 0,14.5L0,1.5C0,0.6719 0.6719,0 1.5,0ZM11.75,11.1875L11.75,4.9727L10.7813,5.9414C10.4883,6.2344 10.0117,6.2344 9.7188,5.9414C9.4258,5.6484 9.4258,5.1758 9.7188,4.8828L11.8828,2.7188C12.0391,2.5625 12.25,2.4883 12.457,2.5C12.4688,2.5 12.4844,2.5 12.5,2.5C12.707,2.5 12.8945,2.582 13.0313,2.7188L15.1914,4.8828C15.4844,5.1758 15.4844,5.6484 15.1914,5.9414C14.8984,6.2344 14.4258,6.2344 14.1328,5.9414L13.25,5.0625L13.25,11.1016L14.1328,10.2188C14.4258,9.9258 14.8984,9.9258 15.1914,10.2188C15.4844,10.5117 15.4844,10.9883 15.1914,11.2813L13.0313,13.4414C12.8711,13.6016 12.6641,13.6719 12.457,13.6602C12.25,13.6719 12.0391,13.6016 11.8828,13.4414L9.7188,11.2813C9.4258,10.9883 9.4258,10.5117 9.7188,10.2188C10.0117,9.9258 10.4883,9.9258 10.7813,10.2188ZM11.75,11.1875"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#D8D8D8"
android:strokeLineCap="butt"/>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="21.3dp"
android:height="38.5dp"
android:viewportWidth="21.3"
android:viewportHeight="38.5">
<path
android:pathData="M8.6,8.6h4.3V21.4H8.6ZM2.1,2.1H19.2V27.8H8.6V23.5H15V6.4H6.4V23.5H2.1ZM0,0V25.7H6.4V38.5H8.5V29.9H21.3V0Z"
android:fillColor="#217f79"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<clip-path android:pathData="M9.3333,2.6667L9.3333,9.3333L8,9.3333L8,2.6667L2.6667,2.6667L2.6667,13.3333L8,13.3333L8,12L9.3333,12L9.3333,18.6667L8,18.6667L8,14.6667L2.6667,14.6667L2.6667,21.3333L12,21.3333L12,18.6667L13.3333,18.6667L13.3333,21.3333L21.3333,21.3333L21.3333,18.6667L24,18.6667L24,24L0,24L0,0L24,0L24,16L21.3333,16L21.3333,9.3333L13.3333,9.3333L13.3333,16L12,16L12,8L21.3333,8L21.3333,2.6667L9.3333,2.6667Z M 0,0"/>
<group>
<clip-path android:pathData="M-72,251L303,251L303,-24L-72,-24Z M 0,0"/>
<path
android:pathData="M-5,-5L29,-5L29,29L-5,29Z"
android:fillColor="#227f79"/>
</group>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="18dp"
android:viewportWidth="16"
android:viewportHeight="18">
<group>
<clip-path android:pathData="M0,0L16,9L0,18ZM0,0 M 0,0"/>
<path
android:pathData="M0,0h16v18h-16z"
android:fillColor="#FFFFFF"
android:strokeColor="#00000000"
android:fillAlpha="1"/>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="20">
<group>
<clip-path android:pathData="M0,0L19,0L19,2L0,2ZM17,2L19,2L19,4L17,4ZM0,2L2,2L2,4L0,4ZM14,4L19,4L19,6L14,6ZM20,4L24,4L24,6L20,6ZM0,4L13,4L13,6L0,6ZM11,6L13,6L13,18L11,18ZM17,6L19,6L19,18L17,18ZM22,6L24,6L24,18L22,18ZM0,6L2,6L2,18L0,18ZM0,18L13,18L13,20L0,20ZM14,18L19,18L19,20L14,20ZM20,18L24,18L24,20L20,20ZM20,18 M 0,0"/>
<path
android:pathData="M0,0h24v20h-24z"
android:fillColor="#D8D8D8"
android:strokeColor="#00000000"
android:fillAlpha="1"/>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<group>
<clip-path android:pathData="M16,0C7.1634,0 0,7.1634 0,16 0,24.8366 7.1634,32 16,32 24.8366,32 32,24.8366 32,16 32,7.1634 24.8366,0 16,0ZM16,29.7143C8.4252,29.7143 2.2857,23.5749 2.2857,16 2.2857,8.4251 8.4252,2.2857 16,2.2857 23.5748,2.2857 29.7143,8.4251 29.7143,16 29.7143,23.5749 23.5748,29.7143 16,29.7143ZM12.9623,13.9085C12.8891,14.0091 12.8092,14.1006 12.7452,14.2103 12.2904,14.9965 12.1921,15.888 12.3863,16.7085L10,25.8194 16.1738,19.3051c1.0149,-0.1394 1.9566,-0.7154 2.5097,-1.6686 0.1326,-0.2308 0.2286,-0.4709 0.304,-0.7154l0.4411,0.2218 2.5715,-12.1074 -9.4286,8.6789zM14.7245,15.3508c0.0937,-0.1623 0.2263,-0.2811 0.3726,-0.3749l1.7371,0.8686c0.016,0.2217 -0.0113,0.4457 -0.1302,0.6514 -0.3154,0.5463 -1.0149,0.7337 -1.5611,0.4183 -0.5464,-0.3177 -0.7337,-1.0149 -0.4183,-1.5635z M 0,0"/>
<group>
<clip-path android:pathData="M-995.9999,446.6667L86.6667,446.6667L86.6667,-53.3333l-1082.6666,-0z M 0,0"/>
<path
android:pathData="M-6.6667,-6.6667L38.6667,-6.6667L38.6667,38.6667L-6.6667,38.6667Z"
android:fillColor="#40a19b"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<group>
<clip-path android:pathData="m16.003,20.6667c-2.6496,-0 -4.7976,-2.0893 -4.7976,-4.6667 0,-2.5773 2.148,-4.6667 4.7976,-4.6667 2.6496,-0 4.7976,2.0893 4.7976,4.6667 0,2.5773 -2.148,4.6667 -4.7976,4.6667zM26.1876,17.2933c0.0548,-0.4267 0.0959,-0.8533 0.0959,-1.2933 0,-0.44 -0.0411,-0.88 -0.0959,-1.3333l2.8923,-2.1733c0.2604,-0.2 0.329,-0.56 0.1645,-0.8533l-2.7415,-4.6133c-0.1645,-0.2933 -0.5346,-0.4133 -0.8361,-0.2933l-3.4131,1.3333c-0.7128,-0.52 -1.453,-0.9733 -2.3165,-1.3067L19.4299,3.2267C19.375,2.9067 19.0872,2.6667 18.7445,2.6667L13.2616,2.6667C12.9189,2.6667 12.631,2.9067 12.5762,3.2267L12.069,6.76C11.2054,7.0933 10.4652,7.5467 9.7525,8.0667l-3.4131,-1.3333c-0.3016,-0.12 -0.6717,-0 -0.8361,0.2933L2.7617,11.64C2.5835,11.9333 2.6658,12.2933 2.9262,12.4933L5.8184,14.6667C5.7636,15.12 5.7225,15.56 5.7225,16c0,0.44 0.0411,0.8667 0.096,1.2933L2.9262,19.5067C2.6658,19.7067 2.5835,20.0667 2.7617,20.36L5.5032,24.9733C5.6677,25.2667 6.0378,25.3733 6.3393,25.2667L9.7525,23.92C10.4652,24.4533 11.2054,24.9067 12.069,25.24L12.5762,28.7733C12.631,29.0933 12.9189,29.3333 13.2616,29.3333l5.483,-0c0.3427,-0 0.6305,-0.24 0.6854,-0.56l0.5072,-3.5333c0.8636,-0.3467 1.6038,-0.7867 2.3165,-1.32l3.4131,1.3467c0.3016,0.1067 0.6717,-0 0.8361,-0.2933l2.7415,-4.6133c0.1645,-0.2933 0.096,-0.6533 -0.1645,-0.8533z M 0,0"/>
<group>
<clip-path android:pathData="M-434.6666,93.3333L65.3333,93.3333l0,-1082.6666l-500,-0z M 0,0"/>
<path
android:pathData="M-4,-4L36,-4L36,36L-4,36Z"
android:fillColor="#a1a1a1"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="22dp"
android:viewportWidth="16"
android:viewportHeight="22">
<group>
<clip-path android:pathData="M11,7L16,7L16,9L11,9ZM0,7L5,7L5,9L0,9ZM14,9L16,9L16,20L14,20ZM0,9L2,9L2,20L0,20ZM0,20L16,20L16,22L0,22ZM0,20 M 0,0"/>
<path
android:pathData="M-5,2L21,2L21,27L-5,27ZM-5,2"
android:fillColor="#D8D8D8"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</group>
<group>
<clip-path android:pathData="M3,0L13,0L13,14L3,14ZM3,0 M 0,0"/>
<group>
<clip-path android:pathData="M8,0L13,3.7695L8.8555,3.7695L8.8555,14L7.1445,14L7.1445,3.7695L3,3.7695ZM8,0 M 0,0"/>
<path
android:pathData="M-2,-5L18,-5L18,19L-2,19ZM-2,-5"
android:fillColor="#D8D8D8"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</group>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="29.333332dp"
android:height="32dp"
android:viewportWidth="29.333332"
android:viewportHeight="32">
<group>
<clip-path android:pathData="M22.6556,26.1103 L24.8134,29.5128C25.1329,30.0167 25.1462,30.6484 24.8481,31.1644 24.5502,31.6803 23.9874,32 23.3769,32L6.0128,32C5.405,32 4.8444,31.6832 4.5451,31.1708 4.246,30.6584 4.2549,30.0298 4.5684,29.5254L6.6871,26.1162C2.6651,23.5806 0,19.1884 0,14.2041 0,6.3719 6.5794,0 14.6667,0 22.7539,0 29.3333,6.3719 29.3333,14.2041 29.3333,19.1845 26.6723,23.5738 22.6556,26.1103Z M 0,0"/>
<group>
<clip-path android:pathData="M-185.3333,97.3333L314.6667,97.3333L314.6667,-985.3333L-185.3333,-985.3333Z M 0,0"/>
<path
android:pathData="M-6.6667,-6.6667L36,-6.6667L36,38.6667L-6.6667,38.6667Z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="21.333332dp"
android:height="29.333332dp"
android:viewportWidth="21.333332"
android:viewportHeight="29.333332">
<path
android:pathData="M-28,-983.9999L472,-983.9999L472,98.6667L-28,98.6667Z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
<group>
<clip-path android:pathData="M-28,-983.9999L472,-983.9999L472,98.6667L-28,98.6667Z M 0,0"/>
<group>
<clip-path android:pathData="M-28,98.6667L472,98.6667L472,-983.9999L-28,-983.9999Z M 0,0"/>
<path
android:pathData="M-34.6667,-990.6666L478.6666,-990.6666L478.6666,105.3333L-34.6667,105.3333Z"
android:fillColor="#000000"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
<group>
<clip-path android:pathData="M0,9.3333L21.3333,9.3333L21.3333,29.3333L0,29.3333Z M 0,0"/>
<group>
<clip-path android:pathData="M-28,98.6667L472,98.6667L472,-983.9999L-28,-983.9999Z M 0,0"/>
<path
android:pathData="M0,9.3333L21.3333,9.3333L21.3333,29.3333L0,29.3333Z"
android:fillColor="#000000"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
<group>
<clip-path android:pathData="M0,9.3333L21.3333,9.3333L21.3333,29.3333L0,29.3333Z M 0,0"/>
<group>
<clip-path android:pathData="M-28,98.6667L472,98.6667L472,-983.9999L-28,-983.9999Z M 0,0"/>
<path
android:pathData="M0,9.3333L21.3333,9.3333L21.3333,29.3333L0,29.3333Z"
android:strokeAlpha="1"
android:strokeLineJoin="miter"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#d8d8d8"
android:strokeLineCap="butt"/>
</group>
</group>
<group>
<clip-path android:pathData="m6.6667,8l8,-0L14.6667,13.3333L6.6667,13.3333Z M 0,0"/>
<group>
<clip-path android:pathData="M-28,98.6667L472,98.6667L472,-983.9999L-28,-983.9999Z M 0,0"/>
<path
android:pathData="M0,1.3333L21.3333,1.3333L21.3333,20L0,20Z"
android:fillColor="#000000"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
<group>
<clip-path android:pathData="m10.6667,0 l6.6667,5.0274L11.8086,5.0274L11.8086,18.6667L9.5247,18.6667L9.5247,5.0274L4,5.0274Z M 0,0"/>
<group>
<clip-path android:pathData="M-28,98.6667L472,98.6667L472,-983.9999L-28,-983.9999Z M 0,0"/>
<path
android:pathData="M-2.6667,-6.6667L24,-6.6667L24,25.3333L-2.6667,25.3333Z"
android:fillColor="#d8d8d8"
android:strokeColor="#00000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</group>
</group>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
android:width="8dp" android:height="8dp">
<shape android:shape="oval">
<size android:width="8dp" android:height="8dp"/>
<solid android:color="@color/colorAccentSecondary" />
</shape>
</item>
<item android:state_checked="true"
android:width="8dp" android:height="8dp">
<shape android:shape="oval">
<size android:width="8dp" android:height="8dp"/>
<solid android:color="@color/colorAccent" />
</shape>
</item>
<item
android:width="8dp" android:height="8dp">
<shape android:shape="oval">
<size android:width="8dp" android:height="8dp"/>
<solid android:color="@color/colorGray" />
</shape>
</item>
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:drawable="@drawable/accent_selector_rectangle" />
<item android:state_focused="true" android:drawable="@drawable/accent_selector_rectangle" />
<item android:state_pressed="true" android:drawable="@drawable/accent_selector_rectangle" />
<item android:drawable="@drawable/primary_selector_rectangle"/>
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/colorPrimary" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="12dp"/>
<solid android:color="@color/colorCommonBackground"/>
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="24dp"/>
<solid android:color="@color/colorOpacityCardBackground"/>
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<font-family
xmlns:app="http://schemas.android.com/apk/res-auto">
<font
app:fontStyle="normal"
app:fontWeight="400"
app:font="@font/acrom_regular"
/>
<font
app:fontStyle="normal"
app:fontWeight="100"
app:font="@font/acrom_medium"
/>
</font-family>
\ No newline at end of file
<?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="0dp"
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
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/bottom_drawer_menu"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".MainActivity"> tools:context=".base.RoomParkMainActivity">
<TextView
android:id="@+id/entryTextView" <androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="wrap_content" android:id="@+id/topToolbarHolder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="0dp"
android:background="@color/colorPrimary"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="true"
android:contentInsetStart="0dp"
android:contentInsetLeft="0dp"
android:contentInsetEnd="0dp"
android:contentInsetRight="0dp"
android:minHeight="0dp"
android:padding="0dp"
android:theme="@style/ThemeOverlay.AppCompat.Light"
android:visibility="visible"
app:contentInsetEnd="0dp"
app:contentInsetLeft="0dp"
app:contentInsetRight="0dp"
app:contentInsetStart="0dp"
app:expanded="true"
app:layout_behavior=".util.view_utils.app_bar.DragControlAppBarLayoutBehaviour"
app:layout_constrainedHeight="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:liftOnScroll="true"
app:titleEnabled="false">
<androidx.appcompat.widget.Toolbar
android:id="@+id/top_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="true"
android:contentInsetStart="0dp"
android:contentInsetLeft="0dp"
android:contentInsetEnd="0dp"
android:contentInsetRight="0dp"
android:minHeight="0dp"
android:padding="0dp"
android:visibility="visible"
app:contentInsetEnd="0dp"
app:contentInsetLeft="0dp"
app:contentInsetRight="0dp"
app:contentInsetStart="0dp"
app:contentInsetStartWithNavigation="0dp"
app:layout_collapseMode="pin"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:titleTextAppearance="@style/Header_TextView.Main_Header">
<LinearLayout
android:id="@+id/custom_toolbar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include
layout="@layout/status_layout_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible" />
<include
layout="@layout/switch_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible" />
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/conductor_container"
android:background="@color/colorPrimary"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="56dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
<!-- <com.bluelinelabs.conductor.ChangeHandlerFrameLayout-->
<!-- android:id="@+id/conductor_container"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- android:background="@color/colorBackground"-->
<!-- app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />-->
<!--</android.support.constraint.ConstraintLayout>-->
<View
android:id="@+id/bottom_view_divider"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_gradient_height"
android:layout_gravity="bottom"
android:background="@drawable/botttom_bar_shadow"
android:visibility="invisible" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Hello World!" android:layout_gravity="bottom"
app:layout_constraintBottom_toBottomOf="parent" android:fitsSystemWindows="true"
app:layout_constraintLeft_toLeftOf="parent" android:visibility="gone"
app:layout_constraintRight_toRightOf="parent" app:elevation="0dp"
app:layout_constraintTop_toTopOf="parent" /> app:itemBackground="@color/colorCommonBackground"
app:itemHorizontalTranslationEnabled="false"
app:itemIconTint="@drawable/bottom_navigation_icon_selector"
app:labelVisibilityMode="unlabeled"
app:menu="@menu/bottom_navigation_menu"
tools:visibility="visible">
</com.google.android.material.bottomnavigation.BottomNavigationView>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?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="142dp"
android:layout_height="94dp"
android:layout_margin="16dp"
android:padding="8dp"
app:cardCornerRadius="4dp"
app:cardElevation="4dp"
app:cardMaxElevation="8dp"
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/playTourCardOpacityBackground"
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/Default_TextView.Inverted_Text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginBottom="4dp"
android:includeFontPadding="false"
android:text="Дом №1"
android:textAlignment="center" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/card_updated"
style="@style/Accent_Minor_TextView.Inverted"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:gravity="center"
android:includeFontPadding="false"
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
<?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="142dp"
android:layout_height="94dp"
android:layout_margin="16dp"
app:cardElevation="4dp"
app:cardForegroundColor="#00000000"
app:cardMaxElevation="6dp"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="false"
app:contentPadding="8dp">
<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/Default_TextView.Inverted_Text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginBottom="4dp"
android:text="Дом №1"
android:textAlignment="center" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/card_updated"
style="@style/Accent_Minor_TextView.Inverted"
android:layout_width="match_parent"
android:layout_height="16dp"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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/container"
android:background="@color/colorPrimary"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/headers_recycler_view"
android:layout_width="match_parent"
android:layout_height="121dp"
android:background="@color/colorPrimary"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:itemCount="1"
tools:listitem="@layout/album_header_preview_viewholder" />
<com.biganto.visual.roompark.presentation.screen.albums.util.BubbleSlider
android:id="@+id/bubble_slider"
android:layout_width="match_parent"
android:layout_height="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/headers_recycler_view"
/>
<androidx.core.widget.NestedScrollView
android:id="@+id/photo_albums_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@color/colorOpacityBackground">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:orientation="horizontal">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/header_album_title"
style="@style/Header_TextView.Inverted_Header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="Дом №1" />
<include
android:id="@+id/include"
layout="@layout/bell_switch_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:baselineAligned="false"
android:gravity="center_vertical|end" />
</LinearLayout>
<include
android:id="@+id/include12"
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:backgroundTint="@color/colorOpacityBackgroundInv" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/albums_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
tools:itemCount="2"
tools:listitem="@layout/date_album_viewholder" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
\ 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"
android:id="@+id/alert_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorOpacityBackground"
android:clickable="true"
android:focusable="auto"
android:focusableInTouchMode="true"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="64dp"
android:layout_marginTop="64dp"
android:layout_marginEnd="64dp"
android:layout_marginBottom="64dp"
app:cardCornerRadius="6dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias=".4">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/alert_message_text_view"
style="@style/Default_TextView.Error_Text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="16dp" />
<LinearLayout
android:id="@+id/buttons_container"
android:layout_width="wrap_content"
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="asdasdasd" />
<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:visibility="visible" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</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:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@string/enter"
android:textAlignment="textStart"
android:textAppearance="@style/Header_TextView.Main_Header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/into"
android:textAlignment="textStart"
android:textAppearance="@style/Header_TextView.Main_Header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="16dp"
android:text="@string/private_office"
android:textAlignment="textStart"
android:textAppearance="@style/Header_TextView.Accent_Header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/textView2"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/login_text_input"
style="@style/AuthTextInputLayout.Login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:nextFocusDown="@id/password_text_input"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView3">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<FrameLayout
android:id="@+id/password_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/login_text_input"
>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_text_input"
style="@style/AuthTextInputLayout.Password"
android:nextFocusDown="@id/sign_in_button"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:maxLength="64"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|top"
android:orientation="vertical">
<com.google.android.material.button.MaterialButton
style="@style/AuthButton.Restore"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:textAlignment="viewEnd" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</FrameLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/sign_in_button"
style="@style/AuthButton.Enable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:includeFontPadding="false"
android:paddingTop="0dp"
android:paddingBottom="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password_container" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/bell_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switch1"
android:layout_width="48dp"
android:layout_height="24dp"
android:height="24dp"
android:checked="false"
app:switchMinWidth="48dp" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="horizontal"
android:weightSum="1">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/bellSwitcherTitle"
style="@style/Common_Text.Default"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:gravity="start|center_vertical"
android:includeFontPadding="false"
android:text="блабла" />
<include
android:id="@+id/bellSwitch"
layout="@layout/bell_switch_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:weightSum="1" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="46dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/imageView2"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="start|center_vertical"
android:layout_weight="0"
android:tint="@color/colorAccent"
app:srcCompat="@drawable/ic_webcam" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/camTitle"
style="@style/Accent_Minor_TextView.Default"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_weight="1"
android:gravity="center_vertical|fill_vertical"
android:text="TextView"/>
<ImageView
android:id="@+id/camStatusIcon"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_gravity="start|center_vertical"
android:layout_margin="4dp"
android:layout_weight="0"
android:tint="@color/colorAccent"
app:srcCompat="@drawable/new_feed_icon" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/camStatus"
style="@style/Feed.Notice"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:layout_weight="0"
android:gravity="center_vertical|fill_vertical"
android:text="ОНЛАЙН"/>
</LinearLayout>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="144dp"
android:background="@color/colorOpacityBackground"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
style="@style/Feed"
android:textColor="@color/colorInvertedText"
android:id="@+id/textView14"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Ландшафтный парк"
android:textAlignment="center" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginBottom="36dp"
android:layout_marginTop="1dp"
android:layout_gravity="center">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@drawable/webcams_placeholder"
android:orientation="horizontal"
android:paddingStart="24dp"
android:paddingEnd="24dp" />
</FrameLayout>
</LinearLayout>
<?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:id="@+id/linearLayout7"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/frameLayout2"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/colorOpacityBackground"
android:paddingTop="64dp"
app:layout_constraintBottom_toTopOf="@+id/textView13"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0">
</FrameLayout>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView13"
style="@style/Header_TextView.Main_Header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:paddingStart="16dp"
android:text="ОТКРЫТЬ\nВ ДРУГОМ РАЗМЕРЕ"
android:textAlignment="viewStart"
app:layout_constraintBottom_toTopOf="@+id/include4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<include
android:id="@+id/include4"
layout="@layout/horizontal_divider"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toTopOf="@+id/photoSizesRecyclerView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photoSizesRecyclerView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:paddingStart="16dp"
app:layout_constraintBottom_toTopOf="@+id/cancel_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/cancel_button"
style="@style/Default_TextView.Cancel_Text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:clickable="true"
android:gravity="center"
android:paddingTop="32dp"
android:paddingBottom="64dp"
android:text="ОТМЕНА"
android:textAlignment="gravity"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="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:id="@+id/linearLayout8"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/size_text"
style="@style/Default_TextView.Accent_Text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:includeFontPadding="false"
android:text="1080x1920"
android:textAlignment="viewStart"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/imageView6"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:scaleType="center"
android:scaleX="-1"
android:tint="@color/colorGray"
app:layout_constraintBottom_toBottomOf="@+id/size_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/size_text"
app:srcCompat="@drawable/ic_chevron_left" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/date_title_textview"
style="@style/Common_Text.Inverted"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="TextView" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos_recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:orientation="vertical"
tools:itemCount="1"
tools:listitem="@layout/photo_preview_viewholder" />
<include
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="4dp"
android:layout_marginTop="16dp"
android:background="@color/colorGray" />
</LinearLayout>
\ No newline at end of file
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
>
<FrameLayout
android:elevation="10dp"
android:id="@+id/deal_read"
android:layout_width="12dp"
android:layout_height="12dp"
android:background="@drawable/new_feed_icon"
android:backgroundTint="@color/colorAccent"
android:visibility="visible"
android:layout_margin="6dp"/>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:layout_marginBottom="8dp"
android:orientation="vertical"
app:cardElevation="6dp"
app:cardMaxElevation="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/deal_card_header"
style="@style/Currency_TextView.Currency"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="КВАРТИРА\n№452" />
<include
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp" />
<LinearLayout
android:id="@+id/common_info_block"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal">
<include
android:id="@+id/info_ceil_1"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<include
android:id="@+id/info_ceil_2"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<include
android:id="@+id/info_ceil_3"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<include
android:id="@+id/info_ceil_4"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:id="@+id/statusContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:background="@drawable/gradient_background_accent"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:id="@+id/progress_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"></LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="2">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/deal_sum_header_text_view"
style="@style/Accent_Minor_TextView.Inverted"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom"
android:includeFontPadding="true"
android:text="Сумма договора"/>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/deal_sum_value_text_view"
style="@style/Default_TextView.Header_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom|end"
android:includeFontPadding="false"
android:text="5 165 301 ₽" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="2">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/deal_payed_header_text_view"
style="@style/Accent_Minor_TextView.Inverted"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom"
android:includeFontPadding="true"
android:text="Сумма платежей" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/deal_payed_value_text_view"
style="@style/Default_TextView.Header_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom|end"
android:includeFontPadding="false"
android:text="1 332 543 ₽" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="2">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/deal_to_pay_header_text_view"
style="@style/Accent_Minor_TextView.Inverted"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom"
android:includeFontPadding="true"
android:text="Сумма к оплате" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/deal_to_pay_value_text_view"
style="@style/Default_TextView.Accent_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom|end"
android:includeFontPadding="false"
android:text="32 543 ₽" />
</LinearLayout>
<include
android:id="@+id/start_tour_button"
layout="@layout/start_tour_viewholder"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/flat_card_card_view"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_margin="16dp"
app:cardElevation="4dp"
app:cardMaxElevation="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorCommonBackground"
android:weightSum="3">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="end|center_vertical"
android:layout_marginStart="40dp"
android:layout_weight="0"
android:baselineAlignBottom="false"
android:src="@drawable/ic_flat"
android:tint="@android:color/black"
app:srcCompat="@drawable/ic_plan" />
<com.google.android.material.textview.MaterialTextView
style="@style/Default_TextView.Header_Text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center|center_vertical"
android:layout_weight="2"
android:gravity="center|start"
android:text="КАРТОЧКА КВАРТИРЫ"
android:textAlignment="center" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/deal_nestedScrollView"
android:layout_marginBottom="8dp"
android:background="@color/colorPrimary"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/deal_card_header"
style="@style/Currency_TextView.Currency"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="КВАРТИРА\n№452" />
<include
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp" />
<LinearLayout
android:id="@+id/common_info_block"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal">
<include
android:id="@+id/info_ceil_1"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<include
android:id="@+id/info_ceil_2"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<include
android:id="@+id/info_ceil_3"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<include
android:id="@+id/info_ceil_4"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:id="@+id/statusContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:background="@drawable/gradient_background_accent"
android:backgroundTint="#99000000"
android:backgroundTintMode="src_over"
android:orientation="vertical"
android:padding="16dp">
<com.google.android.material.textview.MaterialTextView
style="@style/Default_TextView.Inverted_Text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="СТАТУС СДЕЛКИ" />
<LinearLayout
android:id="@+id/progress_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="vertical" />
</LinearLayout>
<com.google.android.material.textview.MaterialTextView
style="@style/Default_TextView.Header_Text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="16dp"
android:text="ФИНАНСЫ" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="2">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/deal_sum_header_text_view"
style="@style/Common_Text.Notice"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom"
android:includeFontPadding="true"
android:text="Сумма договора" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/deal_sum_value_text_view"
style="@style/Currency_TextView.Currency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom|end"
android:includeFontPadding="false"
android:text="5 165 301 ₽" />
</LinearLayout>
<include
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="2">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/deal_payed_header_text_view"
style="@style/Common_Text.Notice"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom"
android:includeFontPadding="true"
android:text="Сумма платежей" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/deal_payed_value_text_view"
style="@style/Currency_TextView.Currency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom|end"
android:includeFontPadding="false"
android:text="1 332 543 ₽" />
</LinearLayout>
<include
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="2">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/deal_to_pay_header_text_view"
style="@style/Common_Text.Notice"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom"
android:includeFontPadding="true"
android:text="Сумма к оплате" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/deal_to_pay_value_text_view"
style="@style/Currency_TextView.Currency_Accent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom|end"
android:includeFontPadding="false"
android:text="32 543 ₽" />
</LinearLayout>
<include
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="31dp" />
<include
android:id="@+id/start_tour_card"
layout="@layout/start_tour_viewholder_big"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="24dp" />
<LinearLayout
android:id="@+id/flat_card_card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@color/colorCommonBackground"
android:weightSum="3">
<com.google.android.material.textview.MaterialTextView
style="@style/Default_TextView.Header_Text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center|center_vertical"
android:layout_weight="2"
android:gravity="start"
android:text="Открыть карточку квартиры"
android:textAlignment="gravity" />
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="end|center_vertical"
android:layout_weight="1"
android:baselineAlignBottom="false"
android:src="@drawable/ic_flat"
android:tint="@color/colorAccent" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/feeds_list_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp" />
</LinearLayout>
\ No newline at end of file
<?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
<?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:id="@+id/linearLayout5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorOpacityBackground"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/catalog_header"
style="@style/Header_TextView.Inverted_Header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:text="Дом №1"
app:layout_constraintEnd_toStartOf="@+id/include2"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/include2"
layout="@layout/bell_switch_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="@+id/catalog_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/include3"
layout="@layout/horizontal_divider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/catalog_header" />
<androidx.recyclerview.widget.RecyclerView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/include3" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:background="@color/colorCommonBackground">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/linearLayout10"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<FrameLayout
android:id="@+id/feed_read"
android:layout_width="12dp"
android:layout_height="12dp"
android:background="@drawable/new_feed_icon"
android:backgroundTint="@color/colorAccent"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/feed_date_text_view3"
style="@style/LiteText.Accent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:includeFontPadding="false"
android:text="СВОБОДНА"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="@+id/feed_read"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/feed_read"
app:layout_constraintTop_toTopOf="@+id/feed_read" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/object_card_title"
style="@style/Header_TextView.Main_Header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="КВАРТИРА\n№ 452"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/feed_date_text_view3" />
<include
android:id="@+id/header_divider"
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/object_card_title" />
<include
android:id="@+id/common_info_block"
layout="@layout/favorites_common_info_block"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/header_divider" />
<include
android:id="@+id/start_tour_button"
layout="@layout/start_tour_viewholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/common_info_block" />
<include
android:id="@+id/link_divider"
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/start_tour_button" />
<include
android:id="@+id/site_link"
layout="@layout/site_link_viewholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/link_divider" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<include
android:id="@+id/info_ceil_1"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<include
android:id="@+id/info_ceil_2"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<include
android:id="@+id/info_ceil_3"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<include
android:id="@+id/info_ceil_4"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<include
android:id="@+id/info_ceil_5"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<include
android:id="@+id/info_ceil_6"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<include
android:id="@+id/info_ceil_7"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<include
android:id="@+id/info_ceil_8"
layout="@layout/info_ceil_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="visible" />
</LinearLayout>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorNoticeBackground"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/favorites_cards_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/favorite_card_viewholder" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorNoticeBackground"
android:orientation="vertical">
<ProgressBar
android:id="@+id/progress_bar_view"
style="?android:attr/progressBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/empty_list_text_view"
style="@style/Common_Text.Notice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Нет элементов"
android:textAlignment="center" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/articles_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/feed_direct_viewholder" />
</LinearLayout>
\ 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:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
android:id="@+id/imageHolder"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
app:image_corner_radius="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:orientation="vertical"
app:layout_constraintGuide_begin="150dp" />
<FrameLayout
android:id="@+id/feed_read"
android:layout_width="wrap_content"
android:layout_height="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="18dp"
android:background="@drawable/new_feed_icon"
android:visibility="visible"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<TextView
android:id="@+id/feed_date_text_view"
style="@style/Feed.Notice"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:includeFontPadding="false"
android:text="22 / 02 / 2019"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/feed_read"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/feed_title_info_text_view"
style="@style/Feed_Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:includeFontPadding="false"
android:text="В «РУМЯНЦЕВО-ПАРК» ПРИСТУПИЛИ К МОНТАЖУ ОКОННЫХ БЛОКОВ"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/feed_date_text_view" />
<TextView
android:id="@+id/feed_text_info_text_view"
style="@style/Feed.Description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:includeFontPadding="false"
android:text="В «РУМЯНЦЕВО-ПАРК» ПРИСТУПИЛИ К МОНТАЖУ ОКОННЫХ БЛОКОВ"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/feed_title_info_text_view"
app:layout_constraintVertical_bias="0.0" />
<include
layout="@layout/horizontal_divider"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageHolder" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?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:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorFeedViewHolderBackground">
<com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
android:id="@+id/imageHolder"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:image_corner_radius="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="68dp" />
<FrameLayout
android:id="@+id/feed_read"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="10dp"
android:background="@drawable/new_feed_icon"
android:visibility="visible"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/feed_date_text_view"
style="@style/Feed.Notice"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:includeFontPadding="false"
android:text="22 / 02 / 2019"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/feed_read"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/feed_title_info_text_view"
style="@style/Feed_Title"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:includeFontPadding="false"
android:text="В «РУМЯНЦЕВО-ПАРК» ПРИСТУПИЛИ К МОНТАЖУ ОКОННЫХ БЛОКОВ"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/feed_date_text_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?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:id="@+id/linearLayout4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/articlePreviewBlurred"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="32dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
android:id="@+id/articlePreview"
android:layout_width="125dp"
android:layout_height="125dp"
android:layout_marginStart="16dp"
android:layout_marginTop="44dp"
app:image_corner_radius="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/articleDate"
style="@style/Feed.Notice"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:includeFontPadding="false"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/articlePreview" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
xmlns:tools="http://schemas.android.com/tools">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/article_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/articleHeaderBlock"
layout="@layout/feed_read_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/articleTitle"
style="@style/Header_TextView.Main_Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="В ЖК «РУМЯНЦЕВО-ПАРК» ИПОТЕЧНАЯ СТАВКА - 6,5%" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/articleBodyRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/article_photos_recylcerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:background="@color/colorOpacityBackground"
android:orientation="horizontal"
android:paddingBottom="16dp"
tools:listitem="@layout/photo_viewholder" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<ImageView
android:id="@+id/articleCloseButton"
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="true"
android:contentDescription="@string/content_description_close"
android:focusable="true"
android:scaleType="fitXY"
android:src="@drawable/ic_close_circled"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</RelativeLayout>
\ 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:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorFeedViewHolderBackground"
android:orientation="vertical">
<com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
android:id="@+id/imageHolder"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:image_corner_radius="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="76dp" />
<FrameLayout
android:id="@+id/feed_read"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_marginTop="10dp"
android:background="@drawable/new_feed_icon"
android:visibility="visible"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/feed_date_text_view"
style="@style/Feed.Notice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="32dp"
android:includeFontPadding="false"
android:text="22 / 02 / 2019"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/feed_title_info_text_view"
style="@style/Common_Text.Inverted"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="32dp"
android:includeFontPadding="false"
android:maxLines="3"
android:text="В «РУМЯНЦЕВО-ПАРК» ПРИСТУПИЛИ К МОН ТАЖУ dadasdasd a 22ЫХ БЛОКОВ"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/feed_date_text_view"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?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:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="360dp"
android:background="@drawable/gradient_background_accent"
android:orientation="vertical"
android:paddingStart="16dp">
<com.google.android.material.tabs.TabLayout
android:id="@+id/feedsTabs"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="#00000000"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tabMaxWidth="@dimen/tab_max_width"
app:tabMode="scrollable" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/feedsRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/feeds_divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/feedsTabs"
app:layout_constraintVertical_bias="0.0"
tools:itemCount="3"
tools:listitem="@layout/feed_preview_viewholder" />
<include
android:id="@+id/feeds_divider"
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@drawable/botttom_bar_shadow"
app:layout_constraintBottom_toTopOf="@+id/to_feed_articles"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/to_feed_articles"
style="@style/AllFeeds.News"
android:layout_width="match_parent"
android:layout_height="64dp"
android:gravity="center_vertical|end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
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"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/feedsBlock"
layout="@layout/feeds_block_view"
android:layout_width="0dp"
android:layout_height="348dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/dev_progress_header"
style="@style/Header_TextView.Main_Header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="ХОД\nСТРОИТЕЛЬСТВА"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/feedsBlock" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dev_progress_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/dev_progress_header"
tools:itemCount="3"
tools:listitem="@layout/estate_card_viewholder" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/cams_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/dev_progress_recycler_view"
tools:listitem="@layout/cam_button_viewholder" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
\ 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"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tab_title"
style="@style/Header_TextView.TabItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tab_divider"
style="@style/Header_TextView.TabItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="@id/tab_title"
android:layout_marginStart="16dp"
android:text="@string/feeds_tab_divider"
android:textColor="@color/colorInvertedNoticeText"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/tab_title"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.tabs.TabLayout
android:id="@+id/flat_estate_tabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="16dp"
android:background="#00000000"
app:tabIndicatorColor="@color/colorAccent"
app:tabMaxWidth="@dimen/tab_max_width"
app:tabMode="scrollable" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/flatNumberInputer"
style="@style/SelectFlatInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:colorControlActivated="@color/colorAccent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/flatNumberEditText"
style="@style/ToFlat.EditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cursorVisible="true"
android:includeFontPadding="false"
android:inputType="number"
android:maxLength="4"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/findFlatButton"
style="@style/FlatButton.Watch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"/>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/flat_nested_scroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/linearLayout12"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/flat_title"
style="@style/Header_TextView.Main_Header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="..."
android:textAlignment="viewStart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/flatTypesCustomView"
layout="@layout/flat_plan_type_selector_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/flat_title" />
<include
android:id="@+id/switch_flat_content_tab"
layout="@layout/simple_text_tabs"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/flatTypesCustomView" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/flat_content_recycler_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/switch_flat_content_tab"
tools:listitem="@layout/flate_measure_info_viewholder" />
<include
android:id="@+id/start_tour_card_divider"
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="32dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/flat_content_recycler_view" />
<include
android:id="@+id/start_tour_card"
layout="@layout/start_tour_viewholder_big"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/start_tour_card_divider" />
<include
android:id="@+id/include11"
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="32dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/start_tour_card" />
<include
android:id="@+id/site_link_viewholder"
layout="@layout/site_link_viewholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/include11" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:ignore="WebViewLayout">
<com.google.android.material.tabs.TabLayout
android:id="@+id/planTypesTabs"
android:layout_width="match_parent"
android:layout_height="24dp"
android:layout_gravity="bottom"
android:layout_marginStart="16dp"
android:padding="0dp"
app:tabPaddingStart="0dp"
app:tabPaddingEnd="0dp"
app:tabPaddingTop="0dp"
app:tabPaddingBottom="0dp"
app:tabMinWidth="0dp"
app:tabMode="scrollable" />
<include
android:id="@+id/include5"
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/planTypesTabs" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView19"
style="@style/Common_Text.Default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="Перепланировка"
android:textAlignment="viewStart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/include5" />
<WebView
android:id="@+id/flat_plan_webview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:minHeight="200dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView19" />
<include
android:id="@+id/sizes_divider"
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/flat_plan_webview" />
<LinearLayout
android:id="@+id/sizes_switch_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sizes_divider">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/sizes_switch_header"
style="@style/Default_TextView.Header_Text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:text="Размеры" />
<include
android:id="@+id/sizes_switcher"
layout="@layout/bell_switch_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_weight="0" />
</LinearLayout>
<include
android:id="@+id/furniture_divider"
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sizes_switch_container" />
<LinearLayout
android:id="@+id/furniture_switch_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/furniture_divider">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/furniture_switch_header"
style="@style/Default_TextView.Header_Text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:text="Мебель" />
<include
android:id="@+id/furniture_switcher"
layout="@layout/bell_switch_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_weight="0" />
</LinearLayout>
<include
android:id="@+id/electricity_divider"
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/furniture_switch_container" />
<LinearLayout
android:id="@+id/electricity_switch_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/electricity_divider">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/electricity_switch_header"
style="@style/Default_TextView.Header_Text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:text="Электрика" />
<include
android:id="@+id/electricity_switcher"
layout="@layout/bell_switch_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_weight="0" />
</LinearLayout>
<include
android:id="@+id/walls_divider"
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/electricity_switch_container" />
<LinearLayout
android:id="@+id/walls_switch_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/walls_divider">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/walls_switch_header"
style="@style/Default_TextView.Header_Text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:text="Стены" />
<include
android:id="@+id/walls_switcher"
layout="@layout/bell_switch_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_weight="0" />
</LinearLayout>
<include
android:id="@+id/include9"
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="24dp"
android:layout_marginTop="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/walls_switch_container" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/flat_measure_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:minHeight="32dp"
android:weightSum="2">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/flat_measure_description"
style="@style/Accent_Minor_TextView.Inverted"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="Стоимость сукиблядь ааааа блпядповд" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/flat_measure_value"
style="@style/Accent_Minor_TextView.Default"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_marginStart="16dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:text=" бля бля ебаны йрот какого хуя \n ебаный рот" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager.widget.ViewPager
android:id="@+id/home_router_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:id="@+id/frameDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorOpacityBackgroundInv"
xmlns:android="http://schemas.android.com/apk/res/android">
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView 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="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitStart"
app:image_corner_radius="4dp" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Default_HtmlView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:linksClickable="true"
android:minHeight="16dp" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
style="@style/LiteText.Notice"
android:id="@+id/info_ceil_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Цена за м²" />
<com.google.android.material.textview.MaterialTextView
style="@style/Common_Text.Default"
android:id="@+id/info_ceil_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="165 301 ₽" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="256dp"
android:layout_height="228dp"
android:layout_margin="16dp"
android:scaleType="centerCrop"
app:image_corner_radius="4dp" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorOpacityBackground"
android:orientation="vertical"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView9"
style="@style/Common_Text.Inverted"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="14 марта 2019" />
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
\ No newline at end of file
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorOpacityBackground"
android:orientation="vertical">
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/photo_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/photo_description"
style="@style/Accent_Minor_TextView.Inverted"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@color/colorOpacityBackground"
android:paddingStart="32dp"
android:paddingTop="16dp"
android:paddingEnd="32dp"
android:paddingBottom="16dp"
android:textAlignment="center" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/photo_preview_imageview"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="4dp"
android:orientation="vertical"
app:image_corner_radius="4dp">
</com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView>
\ 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:id="@+id/linearLayout6"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
<com.google.android.material.textview.MaterialTextView
style="@style/Common_Text.Inverted"
android:id="@+id/photoTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="56dp"
android:text="TextView"
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/close_current_button"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:clickable="true"
android:contentDescription="@string/content_description_close"
android:focusable="true"
android:scaleType="fitXY"
android:src="@drawable/ic_close_circled"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/photo_frame"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="24dp"
app:layout_constraintBottom_toTopOf="@+id/guideline2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/photoTitle"
app:srcCompat="@drawable/default_image_placeholder" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_end="80dp" />
<ImageView
android:id="@+id/share_image_button"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:padding="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline2"
app:srcCompat="@drawable/ic_share" />
<ImageView
android:id="@+id/choose_size_button"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:padding="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline2"
app:srcCompat="@drawable/ic_ratio" />
<com.biganto.visual.roompark.presentation.screen.photo.util.PhotoPreviewSlider
android:id="@+id/photosPreviewSlider"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:orientation="horizontal"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/choose_size_button"
app:layout_constraintStart_toEndOf="@+id/share_image_button"
app:layout_constraintTop_toTopOf="@+id/guideline2"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#000000">
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/photo_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ProgressBar
android:id="@+id/photo_load_progress_bar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:indeterminate="true" />
<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="true"
android:contentDescription="@string/content_description_close"
android:focusable="true"
android:focusableInTouchMode="true"
android:scaleType="fitXY"
android:src="@drawable/ic_close_circled" />
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="80dp"
android:layout_height="80dp"
app:image_corner_radius="4dp"
xmlns:android="http://schemas.android.com/apk/res/android" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/sum"
android:layout_width="16dp"
android:layout_height="wrap_content"
android:background="@color/colorCheckListGradientEnd"
android:orientation="vertical"
android:weightSum="3">
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:layout_width="wrap_content"
android:layout_height="24dp"
app:direction="vertical"
app:hasEnd="true"
app:hasStart="false"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/statusProgressCeil2"
android:layout_width="match_parent"
android:layout_height="24dp"
android:scaleType="fitEnd"
app:direction="vertical"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/statusProgressCeil4"
android:layout_width="match_parent"
android:layout_height="24dp"
android:scaleType="fitEnd"
app:direction="vertical"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/statusProgressCeil5"
android:layout_width="match_parent"
android:layout_height="24dp"
android:scaleType="fitEnd"
app:direction="vertical"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/statusProgressCeil6"
android:layout_width="match_parent"
android:layout_height="24dp"
android:scaleType="fitEnd"
app:direction="vertical"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/statusProgressCeil3"
android:layout_width="match_parent"
android:layout_height="24dp"
android:scaleType="fitEnd"
app:direction="vertical"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:layout_width="wrap_content"
android:layout_height="24dp"
android:scaleType="fitEnd"
app:direction="vertical"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:layout_width="wrap_content"
android:layout_height="24dp"
android:scaleType="fitEnd"
app:direction="vertical"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_weight="1"
android:scaleType="fitEnd"
app:direction="vertical"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="false" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_weight="1"
android:scaleType="fitEnd"
app:anim_state="disable"
app:direction="vertical"
app:hasEnd="false"
app:hasStart="true"
app:isEnable="false"
app:nextEnable="false" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/sum"
android:layout_width="match_parent"
android:layout_height="24dp"
android:background="@color/colorCheckListGradientEnd"
android:orientation="horizontal"
android:weightSum="10">
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
app:direction="horizontal"
app:hasEnd="true"
app:hasStart="false"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/statusProgressCeil2"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="fitEnd"
app:direction="horizontal"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/statusProgressCeil4"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="fitEnd"
app:direction="horizontal"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/statusProgressCeil5"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="fitEnd"
app:direction="horizontal"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/statusProgressCeil6"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="fitEnd"
app:direction="horizontal"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/statusProgressCeil3"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="fitEnd"
app:direction="horizontal"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="fitEnd"
app:direction="horizontal"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="fitEnd"
app:direction="horizontal"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="true" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="fitEnd"
app:direction="horizontal"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="false" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="fitEnd"
app:anim_state="disable"
app:direction="horizontal"
app:hasEnd="false"
app:hasStart="true"
app:isEnable="false"
app:nextEnable="false" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/status"
android:layout_width="24dp"
android:layout_height="match_parent"
app:direction="vertical"
app:hasEnd="false"
app:hasStart="false" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
style="@style/Common_Text.Inverted"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:orientation="vertical"
android:text="Договор готовится для подачи на гос. регистрацию b,kf,kf,fk">
</com.google.android.material.textview.MaterialTextView>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/textTitle"
style="@style/Accent_Minor_TextView.Tab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp"
android:text="Вариант 1">
</com.google.android.material.textview.MaterialTextView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView 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/nestedScrollContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
style="@style/LiteText.Divider"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="bottom"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:text="PUSH УВЕДОМЛЕНИЯ" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/pushRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:itemCount="1"
tools:listitem="@layout/bell_switcher_with_text_viewholder" />
<com.google.android.material.textview.MaterialTextView
style="@style/LiteText.Divider"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="bottom"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:text="ОФФЛАЙН ДОСТУП" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="horizontal"
android:weightSum="1">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/downloadFlatCardsText"
style="@style/Common_Text.Default"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:layout_weight="1"
android:gravity="start|center_vertical"
android:text="Скачать карточки моих
квартир из избранного
и сделок (4 MB)" />
<ImageView
android:id="@+id/downloadFlatCardsIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_download"
android:weightSum="1"/>
</LinearLayout>
<include
layout="@layout/horizontal_divider"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="horizontal"
android:weightSum="1">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/downloadToursText"
style="@style/Common_Text.Default"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:layout_weight="1"
android:gravity="start|center_vertical"
android:text="Скачать виртуальные туры
моих квартир из избранного
и сделок (477 MB)" />
<ImageView
android:id="@+id/downloadToursIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_download"
android:weightSum="1" />
</LinearLayout>
<com.google.android.material.textview.MaterialTextView
style="@style/LiteText.Divider"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="bottom"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:text="КЕШИРОВАННЫЕ ДАННЫX" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/cachedRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:itemCount="1"
tools:listitem="@layout/text_description_viewholder" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/clear_cache_button"
style="@style/Default_TextView.Clear_Cache_Text"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_margin="16dp"
android:textAlignment="center" />
<FrameLayout
android:id="@+id/progress_lock_background"
android:background="@color/colorOpacityBackgroundInv"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_margin="16dp"
android:backgroundTint="@color/colorPrimary"
android:visibility="gone"
>
<ProgressBar
android:id="@+id/settings_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:layout_gravity="center"/>
</FrameLayout>
<com.google.android.material.textview.MaterialTextView
style="@style/LiteText.Divider"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="bottom"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:text="ВЫ АВТОРИЗОВАНЫ КАК USERNAME@DOMAIN.COM" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/signOutButton"
style="@style/Default_TextView.Sign_Out_Text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="32dp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/common_info_tab"
style="@style/Default_TextView.Accent_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bottom_line_text_view"
android:text="Общая информация" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/info_tab_divider"
style="@style/Default_TextView.Accent_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:text="/" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/explication_tab"
style="@style/Default_TextView.Accent_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Экспликация" />
</LinearLayout>
\ 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"
android:id="@+id/linearLayout11"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView18"
style="@style/Default_TextView.Header_Text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Посмотреть на плане на сайте"
app:layout_constraintBottom_toBottomOf="@+id/room_park_link_icon"
app:layout_constraintEnd_toStartOf="@+id/room_park_link_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/room_park_link_icon" />
<ImageView
android:id="@+id/room_park_link_icon"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_room_park" />
</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:id="@+id/linearLayout">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/splashBackground"
tools:layout_editor_absoluteX="174dp" tools:layout_editor_absoluteY="540dp" android:scaleType="centerCrop"/>
<ImageView
android:layout_width="32dp"
android:layout_height="wrap_content" app:srcCompat="@drawable/ic_onlyr"
android:id="@+id/camStatusIcon"
app:layout_constraintEnd_toEndOf="@+id/splashBackground"
app:layout_constraintStart_toStartOf="@+id/splashBackground" app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="8dp" app:layout_constraintTop_toTopOf="@+id/splashBackground"
android:layout_marginBottom="8dp" android:layout_marginEnd="8dp"/>
<TextView
style="@style/Header_TextView.Main_Header"
android:text="РУМЯНЦЕВО-ПАРК"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/logo_header" android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@+id/camStatusIcon"
app:layout_constraintStart_toStartOf="@+id/camStatusIcon" app:layout_constraintEnd_toEndOf="@+id/camStatusIcon"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?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="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:cardElevation="4dp"
app:cardMaxElevation="8dp"
android:padding="16dp"
app:cardCornerRadius="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="64dp"
android:orientation="vertical">
<com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
android:id="@+id/start_tour_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="@color/playTourCardOpacityBackground"
android:foregroundTintMode="src_in"
android:scaleType="centerCrop"
android:src="@drawable/default_image_placeholder" />
<ImageView
android:id="@+id/roundedImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/start_tour_image_view"
app:layout_constraintHorizontal_bias="0.22"
app:layout_constraintStart_toStartOf="@+id/start_tour_image_view"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_play" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView17"
style="@style/Default_TextView.Inverted_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="ВИРТУАЛЬНЫЙ ТУР"
app:layout_constraintBottom_toBottomOf="@+id/roundedImageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/roundedImageView"
app:layout_constraintTop_toTopOf="@+id/roundedImageView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android" >
<com.google.android.material.textview.MaterialTextView
android:id="@+id/start_tour_title_view"
style="@style/Default_TextView.Header_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="16dp"
android:includeFontPadding="false"
android:text="ВИРТУАЛЬНЫЙ ТУР" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:cardElevation="8dp"
app:cardMaxElevation="12dp"
android:padding="16dp"
app:cardCornerRadius="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="160dp"
android:orientation="vertical">
<com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
android:id="@+id/start_tour_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="@color/playTourCardOpacityLight"
android:foregroundTintMode="src_in"
android:scaleType="centerCrop"
android:src="@drawable/default_image_placeholder"
app:image_corner_radius="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
<ImageView
android:id="@+id/roundedImageView"
android:layout_width="32dp"
android:layout_height="0dp"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="16dp"
android:tint="@color/colorCommonBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/start_tour_image_view"
app:layout_constraintStart_toStartOf="@+id/start_tour_image_view"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_play" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
\ 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:id="@+id/status_toolbar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:orientation="vertical">
<FrameLayout
android:id="@+id/status_icon"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="@drawable/available_status"
android:fitsSystemWindows="true"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/status_title"
style="@style/Accent_Minor_TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:fitsSystemWindows="true"
android:text="СВОБОДНА"
app:layout_constraintBottom_toBottomOf="@+id/status_icon"
app:layout_constraintStart_toEndOf="@+id/status_icon"
app:layout_constraintTop_toTopOf="@+id/status_icon" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/mean_title"
style="@style/Accent_Minor_TextView.DatePlaceHolder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:fitsSystemWindows="true"
android:text="СВОБОДНА"
app:layout_constraintBottom_toBottomOf="@+id/status_icon"
app:layout_constraintEnd_toStartOf="@+id/back_cross"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/status_title"
app:layout_constraintTop_toTopOf="@+id/status_icon" />
<ImageView
android:id="@+id/back_cross"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:tint="@color/colorGray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_close" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Default_TextView.Inverted_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_marginBottom="24dp"
android:gravity="center"
android:orientation="vertical"
android:text="Договор готовится для подачи на гос. регистрацию">
</com.google.android.material.textview.MaterialTextView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil>
\ 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:id="@+id/switch_toolbar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/back_button_chevron"
style="@style/Accent_Minor_TextView.Default"
android:layout_width="wrap_content"
android:layout_height="32dp"
android:layout_marginStart="8dp"
android:drawableStart="@drawable/ic_chevron_left"
android:fitsSystemWindows="true"
android:gravity="start|center_vertical"
android:text="МОИ \nСДЕЛКИ"
android:textAlignment="gravity"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/toolbar_title"
style="@style/Header_TextView.Main_Header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="none"
android:fitsSystemWindows="true"
android:gravity="start|center_vertical"
android:includeFontPadding="true"
android:textAlignment="gravity"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/subsciption_switcher"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/back_button_chevron"
app:layout_constraintTop_toTopOf="parent" />
<include
layout="@layout/bell_switch_view"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:fitsSystemWindows="true"
android:gravity="right|center_vertical"
app:layout_constraintBottom_toBottomOf="parent"
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"?>
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
xmlns:android="http://schemas.android.com/apk/res/android">
<!--<com.biganto.visual.androidplayer.presentation.screen.home_routing.NonSwipeableViewPager-->
<!--android:id="@+id/tabsContainerViewPager"-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="match_parent" />-->
<com.bluelinelabs.conductor.ChangeHandlerFrameLayout
android:id="@+id/tabContainer"
android:background="@color/colorPrimary"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
\ 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="@drawable/gradient_background_accent">
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/statusProgressCeil"
android:layout_width="16dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="@+id/textView20"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:direction="vertical"
app:hasEnd="true"
app:hasStart="false"
app:isEnable="true"
app:nextEnable="true" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView20"
style="@style/Common_Text.Inverted"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/statusProgressCeil"
app:layout_constraintTop_toTopOf="parent" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/statusProgressCeil8"
android:layout_width="16dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
app:direction="vertical"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:layout_constraintBottom_toBottomOf="@+id/textView22"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusProgressCeil"
app:nextEnable="true" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView22"
style="@style/Common_Text.Inverted"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/statusProgressCeil"
app:layout_constraintTop_toBottomOf="@+id/textView20" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/statusProgressCeil81"
android:layout_width="16dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
app:direction="vertical"
app:hasEnd="true"
app:hasStart="true"
app:isEnable="true"
app:nextEnable="false"
app:layout_constraintBottom_toBottomOf="@+id/textView212"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusProgressCeil8"
/>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView212"
style="@style/Common_Text.Inverted"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:text="Договор готовится для подачи на гос. регистрацию"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/statusProgressCeil8"
app:layout_constraintTop_toBottomOf="@+id/textView22" />
<com.biganto.visual.roompark.util.view_utils.status_progress_view.StatusProgressCeil
android:id="@+id/statusProgressCeil813"
android:layout_width="16dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
app:direction="vertical"
app:hasEnd="false"
app:hasStart="true"
app:isEnable="false"
app:nextEnable="true"
app:anim_state="disable"
app:layout_constraintBottom_toBottomOf="@+id/textView2125"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusProgressCeil81"
/>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView2125"
style="@style/Common_Text.Inverted"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/statusProgressCeil813"
app:layout_constraintTop_toBottomOf="@+id/textView212" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="horizontal"
android:weightSum="1">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/descriptionTitle"
style="@style/Default_TextView.Header_Text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start|center_vertical"
android:text="блабла" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/descriptionText"
style="@style/Default_TextView.Notice_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:text="50 mb"
android:weightSum="1" />
</LinearLayout>
\ 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"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tab_title"
style="@style/Default_TextView.Accent_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tab_divider"
style="@style/Header_TextView.Accent_Header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="@id/tab_title"
android:layout_marginStart="16dp"
android:text="@string/feeds_tab_divider"
android:textColor="@color/colorAccentSecondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/tab_title"
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"
android:id="@+id/linearLayout9"
android:background="#FF000000"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/webCamPlayerView"
android:background="#FF000000"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.google.android.exoplayer2.ui.PlayerView>
<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="true"
android:contentDescription="@string/content_description_close"
android:focusable="true"
android:focusableInTouchMode="true"
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"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:tint="@color/colorGray"
app:srcCompat="@drawable/ic_webcam">
</ImageView>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView15"
style="@style/Default_TextView"
android:layout_width="24dp"
android:layout_height="24dp"
android:gravity="center"
android:includeFontPadding="false"
android:paddingBottom="2dp"
android:text="1"
android:textAlignment="center" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<group
android:checkableBehavior="single"
android:enabled="true"
android:visible="true">
<item
android:id="@+id/tab_feeds"
android:enabled="true"
android:icon="@drawable/ic_feeds"
android:title="@string/feeds"
app:showAsAction="ifRoom" />
<item
android:id="@+id/tab_favorites"
android:enabled="true"
android:icon="@drawable/ic_favorites"
android:title="@string/favorites"
app:showAsAction="ifRoom" />
<item
android:id="@+id/tab_deals"
android:enabled="true"
android:icon="@drawable/ic_deals"
android:title="@string/my_deals"
app:showAsAction="always" />
<item
android:id="@+id/tab_look_flat"
android:enabled="true"
android:icon="@drawable/ic_flat"
android:title="@string/flats"
app:showAsAction="always" />
<item
android:id="@+id/tab_settings"
android:enabled="true"
android:icon="@drawable/ic_settings"
android:title="@string/settings"
app:showAsAction="always" />
</group>
</menu>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="isTablet">true</bool>
<integer name="catalogSpans">3</integer>
<integer name="toursCeilSpans">4</integer>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="isTablet">true</bool>
<integer name="catalogSpans">2</integer>
<integer name="toursCeilSpans">2</integer>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="isTablet">true</bool>
<integer name="catalogSpans">6</integer>
<integer name="toursCeilSpans">5</integer>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="isTablet">true</bool>
<integer name="catalogSpans">3</integer>
<integer name="toursCeilSpans">4</integer>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorSecondary">@color/colorAccent</item>
<item name="colorSecondaryVariant">@color/colorAccentSecondary</item>
<item name="colorPrimaryDark">@color/colorAccentSecondary</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="materialCardViewStyle">@style/Widget.Biganto.MaterialCardView</item>
<item name="switchStyle">
@style/Widget.MaterialComponents.CompoundButton.Switch.BellSwitchStyle
</item>
<item name="colorControlActivated">@color/colorAccent</item>
<item name="colorControlHighlight">@color/colorAccent</item>
<item name="colorControlNormal">@color/colorAccent</item>
<item name="android:statusBarColor">@color/colorPrimary</item>
<item name="android:windowLightStatusBar">true</item>
</style>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="isTablet">false</bool>
<integer name="catalogSpans">1</integer>
<integer name="toursCeilSpans">2</integer>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RoundedImageView">
<attr name="image_corner_radius" format="dimension"/>
</declare-styleable>
<declare-styleable name="StatusProgressCeil">
<attr name="hasStart" format="boolean"/>
<attr name="hasEnd" format="boolean"/>
<attr name="isEnable" format="boolean"/>
<attr name="nextEnable" format="boolean"/>
<attr name="direction">
<enum name="horizontal" value="1" />
<enum name="vertical" value="2" />
</attr>
<attr name="anim_state">
<enum name="enable" value="1" />
<enum name="disable" value="2" />
<enum name="toEnable" value="3" />
<enum name="toDisable" value="4" />
</attr>
</declare-styleable>
</resources>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="colorPrimary">#008577</color> <color name="colorPrimary">#FFFFFF</color>
<color name="colorPrimaryDark">#00574B</color> <color name="colorPrimaryDark">#FFFFFF</color>
<color name="colorAccent">#D81B60</color> <color name="colorAccent">#227F79</color>
<color name="colorAccentSecondary">#093835</color>
<color name="colorGray">#A1A1A1</color>
<color name="colorOpacityBackgroundInv">#E3E3E3E3</color>
//region background
<color name="colorCommonBackground">#FFFFFF</color>
<color name="colorNoticeBackground">#EEEEEE</color>
<color name="colorOpacityBackground">#CC000000</color>
<color name="colorOpacityCardBackground">#5C000000</color>
<color name="colorDividerLightGray">#F2F2F2</color>
<color name="playTourCardOpacityBackground">#B2000000</color>
<color name="playTourCardOpacityLight">#20000000</color>
<color name="colorCheckListGradientStart">@color/colorAccent</color>
<color name="colorCheckListGradientEnd">@color/colorAccentSecondary</color>
//endregion
//region commons
<color name="colorAttention">#F5A623</color>
<color name="colorError">#B4272D</color>
//endregion
//region text
<color name="colorHeaderText">#222222</color>
<color name="colorCommonText">#000000</color>
<color name="colorNoticeText">#A1A1A1</color>
<color name="colorInvertedText">#FFFFFF</color>
<color name="colorInvertedNoticeText">#A1FFFFFF</color>
//endregion
<color name="colorFeedViewHolderBackground">#00000000</color>
</resources> </resources>
<resources> <resources>
<string name="app_name">Room Park</string> <string name="app_name">Room Park</string>
<string name="feeds">Новости</string>
<string name="favorites">ИЗБРАННОЕ</string>
<string name="my_deals">МОИ СДЕЛКИ</string>
<string name="flats">ПОИСК КВАРТИРЫ</string>
<string name="settings">НАСТРОЙКИ</string>
<string name="snackbar_dismiss_button_default">ПОНЯТНО</string>
<string name="snackbar_dismiss_button_error">ОШИБКА</string>
<string name="snackbar_dismiss_button_attention">ВНИМАНИЕ</string>
<string name="snackbar_dismiss_button_ok">ОК</string>
<string name="enter">ВХОД</string>
<string name="into">В</string>
<string name="private_office">ЛИЧНЫЙ КАБИНЕТ</string>
<!--region Custom Exception-->
<string name="api_error_100">Метод требует авторизации.</string>
<string name="api_error_101">Ошибка синтаксиса токена.</string>
<string name="api_error_102">Пользователь, указанный в авторизационном токене, не найден.</string>
<string name="api_error_103">Пользователь найден, но токен не подходит. Нужно переавторизоваться.</string>
<string name="api_error_104">Пользователь не подтвердил почту.</string>
<string name="api_error_111">Неверный логин</string>
<string name="api_error_112">Неверный пароль</string>
<string name="api_error_201">Неверный топик для подписки / отписки</string>
<string name="api_error_202">Попытка добавить уже существующую подписку</string>
<string name="api_error_203">Timeout соединения с CRM (метод получения сделок)</string>
<string name="api_error_204">Неверный формат ответа от CRM</string>
<string name="api_error_205">CRM вернул ошибку (указывается её код в ответе)</string>
<string name="api_error_206">Ошибка БД, невозможно осуществить подписку</string>
<string name="api_error_211">Неверное обозначение фида</string>
<string name="api_error_212">Статья не найдена</string>
<string name="api_error_213">Не был передан article_id</string>
<string name="api_error_221">Не передан или неверный album_id</string>
<string name="api_error_231">Неверный id зоны</string>
<string name="api_error_2001">Тур не найден!</string>
<string name="api_error_default">Неизвестная ошибка сервера!</string>
<string name="no_network_error">No network!</string>
<string name="unknown_error">Unexpected error!</string>
<string name="cant_load_item_error">Can\'t load item!</string>
<string name="object_not_found_error">Object not found!</string>
<string name="can_not_delete_tour_error">Can\'t Delete Tour!</string>
<string name="can_not_sync_tour_error">Can\'t sync tour!</string>
<string name="can_not_download_all_tours_error">Can\'t download tours!</string>
<string name="auth_data_requirments_failed">Login at least %d symbols
\nPassword at least %d symbols</string>
<string name="feeds_tab_divider">/</string>
<string name="unauthorized_user_request">Пользователь не авторизован</string>
<string name="building">Корпус</string>
<string name="section_begin">Секция</string>
<string name="floor">Этаж</string>
<string name="area">Общая площадь</string>
<string name="area_value">%,.1f м\u00B2</string>
<string name="area_living">Жилая площадь</string>
<string name="rooms">Кол-во комнат</string>
<string name="flat_kind">Тип квартиры</string>
<string name="flat_decoration">Наличие отделки</string>
<string name="ceiling">Высота потолков</string>
<string name="meters_value">%,.1f м</string>
<string name="window_face">Вид из окон</string>
<string name="direction">Сторона света</string>
<string name="price_meter">Цена за м\u00B2</string>
<string name="price">Стоимость</string>
<string name="max_discount">Максимальная скидка с учетом всех акций</string>
<string name="discount_value">%,.1f \%</string>
<string name="discounted_price">Стоимость со скидкой</string>
<string name="flat_not_found">В ЭТОМ ДОМЕ НЕТ КВАРТИРЫ С ТАКИМ НОМЕРОМ</string>
<string name="flat_ready_to_watch">СМОТРЕТЬ</string>
<string name="plans_cache">Карточки квартир</string>
<string name="tours_cache">Виртуальные туры</string>
<string name="feeds_cache">Новости и заметки</string>
<string name="albums_cache">Фотографии</string>
<string name="overall_cache">Всего скачано</string>
<string name="estate_avalibale">СВОБОДНА</string>
<string name="estate_sold_out">ПРОДАНА</string>
<string name="deal_back_chevron_title">МОИ\nСДЕЛКИ</string>
<string name="news_header">НОВОСТИ</string>
<string name="blog_header">БЛОГ</string>
<string name="estate_type_flat_long">КВАРТИРА\n№%s</string>
<string name="estate_type_parking_long">МАШИНОМЕСТО\n%s</string>
<string name="estate_type_store_long">КЛАДОВКА\n%s</string>
<string name="estate_type_other_long">ОБЪЕКТ\n%s</string>
<string name="estate_type_flat_short">КВ.№%s</string>
<string name="estate_type_parking_short">М\\М %s</string>
<string name="estate_type_store_short">КЛ.%s</string>
<string name="estate_type_other_short">ОБ.%s</string>
<string name="tour_not_allowed">Запуск туров пока недоступен!</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-->
</resources> </resources>
<resources> <resources>
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorSecondary">@color/colorAccent</item>
<item name="colorSecondaryVariant">@color/colorAccentSecondary</item>
<item name="colorPrimaryDark">@color/colorAccentSecondary</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
<item name="materialCardViewStyle">@style/Widget.Biganto.MaterialCardView</item>
<item name="switchStyle">@style/Widget.MaterialComponents.CompoundButton.Switch.BellSwitchStyle</item>
<item name="colorControlActivated">@color/colorAccent</item>
<item name="colorControlHighlight">@color/colorAccent</item>
<item name="colorControlNormal">@color/colorAccent</item>
<item name="android:statusBarColor">@color/colorPrimary</item>
</style>
<style name="Widget.MainTextInputStyle" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<item name="android:theme">@style/AppTheme.Theme2</item>
</style>
<style name="AppTheme.Launch">
<item name="android:windowBackground">@color/colorAccent</item>
</style>
<style name="Widget.MaterialComponents.CompoundButton.Switch.BellSwitchStyle" >
<item name="track" >@drawable/bell_switch_track</item>
<item name="android:thumb" >@drawable/bell_switcher</item>
<item name="thumbTint">@color/colorPrimaryDark</item>
<item name="trackTint">@drawable/bell_switch_track_tint</item>
</style>
<style name="Widget.Biganto.MaterialCardView" parent="Widget.MaterialComponents.CardView">
<item name="cardUseCompatPadding">false</item>
<item name="strokeWidth">0dp</item>
<item name="cornerFamily">cut</item>
<item name="cornerSize">4dp</item>
</style>
<style name="Auth.ErrorText" parent="TextAppearance.Design.Error">
<item name="android:textColor">@color/colorError</item>
<item name="android:textSize">@dimen/lite_notice</item>
</style>
<style name="Auth.EditText" parent="Widget.AppCompat.EditText">
<!-- Inactive underline color-->
<item name="colorControlNormal">@color/colorAccent</item>
<!-- Cursor and Active underline color, uses colorAccent by default if not defined-->
<item name="colorControlActivated">@color/colorAccentSecondary</item>
</style>
<style name="AuthTextInputLayout" parent="Widget.MainTextInputStyle">
<item name="colorControlNormal">@color/colorCommonBackground</item>
<item name="colorControlActivated">@color/colorCommonBackground</item>
<item name="boxStrokeColor">@color/colorAccent</item>
<item name="boxBackgroundColor">@color/colorCommonBackground</item>
<item name="android:textSize">@dimen/common_text</item>
<item name="android:labelTextSize">@dimen/minor_text</item>
<item name="android:textColorHint">@color/colorNoticeText</item>
<item name="android:textColor">@color/colorCommonText</item>
<item name="errorTextAppearance">@style/Auth.EditText</item>
<item name="hintTextColor">@color/colorNoticeText</item>
</style>
<style name="AuthTextInputLayout.Login">
<item name="android:hint">"EMAIL"</item>
</style>
<style name="AuthTextInputLayout.Password">
<item name="android:hint">"ПАРОЛЬ"</item>
</style>
<style name="AppTheme.Theme2">
<item name="colorPrimary">@color/colorAttention</item>
</style>
<style name="SelectFlatInputLayout" parent="Widget.MainTextInputStyle">
<item name="colorControlNormal">@color/colorAccent</item>
<item name="colorControlActivated">@color/colorAccentSecondary</item>
<item name="colorControlHighlight">@color/colorAccentSecondary</item>
<item name="android:textColorHint">@color/colorNoticeText</item>
<item name="android:textColor">@color/colorCommonText</item>
<item name="errorTextAppearance">@style/Auth.EditText</item>
<item name="hintTextColor">@color/colorNoticeText</item>
<item name="android:includeFontPadding">false</item>
<item name="android:baselineAlignBottom">true</item>
<item name="android:textSize">@dimen/super_size</item>
</style>
<style name="ToFlat.EditText" parent="SelectFlatInputLayout">
<!-- <item name="android:theme">@style/AppTheme</item>-->
<!-- Inactive underline color-->
<item name="android:textSize">@dimen/super_size</item>
<item name="android:hint">"Кв.№"</item>
<item name="android:baselineAlignBottom">true</item>
<item name="android:includeFontPadding">false</item>
<!-- Cursor and Active underline color, uses colorAccent by default if not defined-->
</style>
<style name="ToggleTextButton" parent="Widget.MaterialComponents.Button.TextButton">
<item name="strokeColor">@color/colorAccent</item>
<item name="rippleColor">@color/colorAccentSecondary</item>
<item name="boxBackgroundColor">@color/colorCommonBackground</item>
<item name="android:textSize">@dimen/accent_header</item>
<item name="android:textColor">@color/colorAccent</item>
<item name="errorTextAppearance">@style/Auth.EditText</item>
<item name="hintTextColor">@color/colorNoticeText</item>
</style>
<style name="DefaultButton" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="strokeColor">@color/colorAccent</item>
<item name="strokeWidth">0dp</item>
<item name="cornerRadius">0dp</item>
<item name="rippleColor">@color/colorAccentSecondary</item>
<item name="boxBackgroundColor">@color/colorCommonBackground</item>
<item name="android:textSize">@dimen/common_text</item>
<item name="android:textColor">@color/colorAccent</item>
<item name="errorTextAppearance">@style/Auth.EditText</item>
<item name="hintTextColor">@color/colorNoticeText</item>
</style>
<style name="DefaultButton.DismissButton">
<item name="android:text">"СКРЫТЬ"</item>
</style>
<style name="AuthButton" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="strokeColor">@color/colorAccent</item>
<item name="strokeWidth">4dp</item>
<item name="android:height">64dp</item>
<item name="cornerRadius">0dp</item>
<item name="rippleColor">@color/colorAccentSecondary</item>
<item name="boxBackgroundColor">@color/colorCommonBackground</item>
<item name="android:textSize">@dimen/common_text</item>
<item name="android:textColor">@color/colorAccent</item>
<item name="errorTextAppearance">@style/Auth.EditText</item>
<item name="hintTextColor">@color/colorNoticeText</item>
</style>
<style name="FlatButton.Watch" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="android:text">"СМОТРЕТЬ"</item>
<item name="android:textColor">@color/flat_watch_button_selector</item>
<item name="strokeColor">@color/flat_watch_button_selector</item>
<item name="strokeWidth">4dp</item>
<item name="android:height">64dp</item>
<item name="cornerRadius">0dp</item>
<item name="rippleColor">@color/colorAccentSecondary</item>
<item name="android:textSize">@dimen/accent_header</item>
</style>
<style name="AuthButton.Enable">
<item name="android:text">"ВОЙТИ"</item>
<item name="android:textColor">@color/sign_in_button_selector</item>
<item name="strokeColor">@color/sign_in_button_selector</item>
</style>
<style name="AuthButton.Restore">
<item name="android:text">"Я ЗАБЫЛ"</item>
<item name="android:textSize">@dimen/minor_text</item>
<item name="android:fontFamily">@font/acrom_regular</item>
<item name="strokeWidth">0dp</item>
</style>
<style name="AllFeeds" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="strokeWidth">0dp</item>
<item name="android:height">64dp</item>
<item name="cornerRadius">0dp</item>
<item name="rippleColor">@color/colorOpacityBackground</item>
<item name="android:textSize">@dimen/common_text</item>
<item name="android:textColor">@color/colorInvertedText</item>
<item name="hintTextColor">@color/colorInvertedText</item>
<item name="android:text">"Я ЗАБЫЛ"</item>
<item name="android:fontFamily">@font/acrom_medium</item>
<item name="android:gravity">end</item>
</style>
<style name="AllFeeds.News">
<item name="android:text">"ВСЕ НОВОСТИ →"</item>
</style>>
<style name="AllFeeds.Blogs">
<item name="android:text">"ВСЕ БЛОГИ →"</item>
</style>>
<style name="AllFeeds.Blogs_Development">
<item name="android:text">"ПОДРОБНЕЕ →"</item>
</style>>
<style name="DevProgressButton" parent="Widget.MaterialComponents.Button.UnelevatedButton">
<item name="strokeWidth">1dp</item>
<item name="android:colorBackground">@color/colorCommonBackground</item>
<item name="android:colorControlNormal">@color/colorCommonBackground</item>
<item name="android:colorControlActivated">@color/colorCommonBackground</item>
<item name="boxBackgroundColor">@color/colorCommonBackground</item>
<item name="android:layout_height">24dp</item>
<item name="android:layout_width">128dp</item>
<item name="cornerRadius">12dp</item>
<item name="rippleColor">@color/colorGray</item>
<item name="android:textSize">@dimen/minor_text</item>
<item name="android:textColor">@color/colorHeaderText</item>
<item name="android:fontFamily">@font/acrom_medium</item>
<item name="android:gravity">center</item>
</style>
//region TextView
<style name="Base.Widget.AppCompat.TextView.AcromMediumTextView">
<item name="android:fontFamily">@font/acrom_medium</item>
</style>
<style name="Base.Widget.AppCompat.TextView.AcromRegularTextView">
<item name="android:fontFamily">@font/acrom_regular</item>
</style>
<style name="Base.Widget.AppCompat.Button.AcromMediumButton">
<item name="android:fontFamily">@font/acrom_medium</item>
</style>
<style name="Base.Widget.AppCompat.Button.AcromRegularButton">
<item name="android:fontFamily">@font/acrom_regular</item>
</style>
<style name="Header_TextView" parent="Base.Widget.AppCompat.TextView.AcromMediumTextView">
<item name="android:textSize">@dimen/main_header</item>
</style>
<style name="Header_TextView.Main_Header">
<item name="android:textColor">@color/colorHeaderText</item>
</style>
<style name="Header_TextView.Accent_Header">
<item name="android:textColor">@color/colorAccent</item>
</style>
<style name="Header_TextView.Inverted_Header">
<item name="android:textColor">@color/colorInvertedText</item>
</style>
<style name="Header_TextView.TabItem">
<item name="android:textColor">@color/tab_item_selector</item>
</style> </style>
<style name="Flat_Input_TextView" parent="Base.Widget.AppCompat.TextView.AcromMediumTextView">
<item name="android:textSize">@dimen/super_size</item>
</style>
<style name="Flat_Input_TextView.Flat_Text">
<item name="android:textColor">@color/colorHeaderText</item>
</style>
<style name="Flat_Input_TextView.Flat_Error">
<item name="android:textColor">@color/colorError</item>
</style>
<style name="Flat_Input_TextView.Flat_Notice">
<item name="android:textColor">@color/colorNoticeText</item>
</style>
<style name="Currency_TextView" parent="Base.Widget.AppCompat.TextView.AcromMediumTextView">
<item name="android:textSize">@dimen/currency</item>
</style>
<style name="Currency_TextView.Currency">
<item name="android:textColor">@color/colorHeaderText</item>
</style>
<style name="Currency_TextView.Currency_Accent">
<item name="android:textColor">@color/colorAccent</item>
</style>
<style name="Default_HtmlView" parent="Base.Widget.AppCompat.TextView.AcromRegularTextView">
<item name="android:textSize">@dimen/accent_header</item>
</style>
<style name="Default_TextView" parent="Base.Widget.AppCompat.TextView.AcromMediumTextView">
<item name="android:textSize">@dimen/accent_header</item>
<item name="android:textColor">@color/colorHeaderText</item>
</style>
<style name="Default_TextView.Accent_Text">
<item name="android:textColor">@color/colorAccent</item>
</style>
<style name="Default_TextView.Header_Text">
<item name="android:textColor">@color/colorHeaderText</item>
</style>
<style name="Default_TextView.Error_Text">
<item name="android:textColor">@color/colorError</item>
</style>
<style name="Default_TextView.Inverted_Text">
<item name="android:textColor">@color/colorInvertedText</item>
</style>
<style name="Default_TextView.Cancel_Text">
<item name="android:text">"ОТМЕНА"</item>
<item name="android:gravity">center</item>
<item name="android:textColor">@color/colorError</item>
</style>
<style name="Default_TextView.Clear_Cache_Text">
<item name="android:text">"ОЧИСТИТЬ КЭШ"</item>
<item name="android:gravity">center</item>
<item name="android:textColor">@color/colorError</item>
</style>
<style name="Default_TextView.Sign_Out_Text">
<item name="android:text">"СМЕНИТЬ АККАУНТ"</item>
<item name="android:gravity">center</item>
<item name="android:textColor">@color/colorError</item>
</style>
<style name="Default_TextView.Notice_Text">
<item name="android:textColor">@color/colorNoticeText</item>
</style>
<style name="Feed_Title" parent="Base.Widget.AppCompat.TextView.AcromMediumTextView">
<item name="android:textSize">@dimen/common_text</item>
<item name="android:textColor">@color/colorAccent</item>
</style>
<style name="Feed_Title.Disclaimer">
<item name="android:textColor">@color/colorInvertedText</item>
</style>
<style name="Common_Text" parent="Base.Widget.AppCompat.TextView.AcromRegularTextView">
<item name="android:textSize">@dimen/common_text</item>
</style>
<style name="Common_Text.Default">
<item name="android:textColor">@color/colorCommonText</item>
</style>
<style name="Common_Text.Notice">
<item name="android:textColor">@color/colorNoticeText</item>
</style>
<style name="Common_Text.Inverted">
<item name="android:textColor">@color/colorInvertedText</item>
</style>
<style name="Common_Text.Inverted_Notice">
<item name="android:textColor">@color/colorInvertedNoticeText</item>
</style>
<style name="Accent_Minor_TextView" parent="Base.Widget.AppCompat.TextView.AcromMediumTextView">
<item name="android:textSize">@dimen/minor_text</item>
</style>
<style name="Accent_Minor_TextView.Default">
<item name="android:textColor">@color/colorAccent</item>
</style>
<style name="Accent_Minor_TextView.Inverted">
<item name="android:textColor">@color/colorNoticeText</item>
</style>
<style name="Accent_Minor_TextView.Tab">
<item name="android:textColor">@color/plantype_tab_text_selector</item>
<item name="android:background">@drawable/plantype_tab_background_selector</item>
</style>
<style name="Accent_Minor_TextView.DatePlaceHolder">
<item name="android:textColor">@color/colorCommonText</item>
<item name="android:background">@drawable/rounded_text</item>
<item name="android:paddingStart">12dp</item>
<item name="android:paddingEnd">12dp</item>
</style>
<style name="Feed" parent="Base.Widget.AppCompat.TextView.AcromRegularTextView">
<item name="android:textSize">@dimen/lite_notice</item>
</style>
<style name="Feed.Notice">
<item name="android:textColor">@color/colorNoticeText</item>
</style>
<style name="Feed.Description">
<item name="android:textColor">@color/colorCommonText</item>
</style>
<style name="LiteText" parent="Base.Widget.AppCompat.TextView.AcromRegularTextView">
<item name="android:textSize">@dimen/lite_notice</item>
</style>
<style name="LiteText.Accent">
<item name="android:textColor">@color/colorAccent</item>
</style>
<style name="LiteText.Notice">
<item name="android:textColor">@color/colorNoticeText</item>
</style>
<style name="LiteText.Divider">
<item name="android:background">@color/colorNoticeBackground</item>
<item name="android:textColor">@color/colorGray</item>
</style>
//endregion
</resources> </resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="main_header">32sp</dimen>
<dimen name="super_size">128sp</dimen>
<dimen name="accent_header">16sp</dimen>
<dimen name="minor_text">12sp</dimen>
<dimen name="common_text">14sp</dimen>
<dimen name="currency">24sp</dimen>
<dimen name="lite_notice">11sp</dimen>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="default_icon_width">24dp</dimen>
<dimen name="default_icon_height">24dp</dimen>
<dimen name="sub_icon_width">12dp</dimen>
<dimen name="sub_icon_height">12dp</dimen>
<dimen name="bottom_gradient_height">3dp</dimen>
<dimen name="ceil_grid_padding">8dp</dimen>
<dimen name="tab_max_width">999dp</dimen>
<dimen name="bottom_navigation_height">56dp</dimen>
</resources>
\ No newline at end of file
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.3.41' ext.kotlin_version = '1.3.61'
repositories { repositories {
flatDir { flatDir {
dirs 'libs' dirs 'libs'
...@@ -11,8 +11,12 @@ buildscript { ...@@ -11,8 +11,12 @@ buildscript {
} }
dependencies { dependencies {
classpath "com.android.tools.build:gradle:3.5.0" 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 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0-beta03'
// 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
} }
...@@ -22,6 +26,7 @@ allprojects { ...@@ -22,6 +26,7 @@ allprojects {
repositories { repositories {
google() google()
jcenter() jcenter()
maven { url "https://jitpack.io" }
} }
} }
......
...@@ -2,21 +2,32 @@ ext { ...@@ -2,21 +2,32 @@ ext {
$APPLICATION_ID = "com.biganto.visual.roompark" $APPLICATION_ID = "com.biganto.visual.roompark"
targetSdkVersion_RoomPark = 28 targetSdkVersion_RoomPark = 28
minSdkVersion_RoomPark = 21 minSdkVersion_RoomPark = 23
compileSdkVersion_RoomPark = 28 compileSdkVersion_RoomPark = 28
VERSION_CODE = 1 VERSION_CODE = 2
VERSION_NAME = "0.0.1" VERSION_NAME = "0.8.0"
// supportLibraryVersion = '1.1.0-alpha05' // supportLibraryVersion = '1.1.0-alpha05'
constrainLayoutVersion = '1.1.3' constrainLayoutVersion = '1.1.3'
// requeryVersion = '1.5.1' requeryVersion = '1.6.1'
// rxBindingVersion = '2.1.1' rxBindingVersion = '3.0.0'
conductorVersion = '3.0.0-rc1' conductorVersion = '3.0.0-rc1'
materialVersion = '1.1.0-alpha09' materialVersion = '1.1.0-alpha10'
gradleVersion = '3.5.0' gradleVersion = '3.5.0'
koinVersion = '2.0.1' koinVersion = '2.0.1'
timberVersion = '4.7.1' timberVersion = '4.7.1'
picassoVersion = '2.71828' picassoVersion = '2.71828'
rxJavaVersion = '3.0.0-RC3'
mosbyMviConductorVersion = '3.1.0'
butterKnifeVersion = '10.2.0'
daggerVersion = '2.25.2'
rxRelayVersion = '2.1.1'
exoPlayerVersion = "2.10.8"
photoViewVersion = "2.0.0"
viewPager2Version = "1.0.0"
glideVersion = "4.11.0"
fireBaseVersion = "17.2.3"
firebaseCrashlyticsVersion = "17.0.0-beta02"
rxKotlinVersion = "2.4.0"
} }
\ No newline at end of file
...@@ -3,4 +3,5 @@ distributionBase=GRADLE_USER_HOME ...@@ -3,4 +3,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
Room Park official Android application Room Park official Android application
- - v.0.0.1 - - - - v.0.8.0 - -
All common functionality, except tour launcher
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