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

Merge branch 'release/0.8.0'

parents 6f48b8a2 638c75f4
*.iml
.gradle
/local.properties
/.idea
/.idea/caches
/.idea/libraries
/.idea/modules.xml
......
Room Park
\ No newline at end of file
......@@ -2,6 +2,8 @@
<dictionary name="bogdashkin">
<words>
<w>Biganto</w>
<w>snackbar</w>
<w>upsert</w>
</words>
</dictionary>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="delegatedBuild" value="false" />
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
......@@ -12,7 +15,6 @@
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="testRunner" value="PLATFORM" />
</GradleProjectSettings>
</option>
</component>
......
......@@ -148,10 +148,25 @@
</profile-state>
</entry>
</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" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</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>
\ 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"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>
\ No newline at end of file
......@@ -8,6 +8,12 @@ apply plugin: 'kotlin-android-extensions'
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'
android {
......@@ -15,7 +21,7 @@ android {
defaultConfig {
applicationId $APPLICATION_ID
ndk {
abiFilters 'armeabi-v7a', 'x86'
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86'
}
minSdkVersion minSdkVersion_RoomPark
targetSdkVersion targetSdkVersion_RoomPark
......@@ -40,6 +46,7 @@ android {
androidExtensions {
experimental = true
}
configurations.all {
resolutionStrategy.force "com.bluelinelabs:conductor:$conductorVersion"
}
......@@ -64,14 +71,9 @@ dependencies {
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation 'androidx.core:core-ktx:1.0.2'
//Koin
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"
implementation 'androidx.core:core-ktx:1.1.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.13.0" // JVM dependency
//Material
implementation "com.google.android.material:material:$materialVersion"
......@@ -79,16 +81,95 @@ dependencies {
//Constraint Layout
implementation "androidx.constraintlayout:constraintlayout:$constrainLayoutVersion"
//image loading store and cashe by url: Picasso
implementation "com.squareup.picasso:picasso:$picassoVersion"
//Logger: Timber
implementation "com.jakewharton.timber:timber:$timberVersion"
//Crashlytics
implementation('com.crashlytics.sdk.android:crashlytics:2.10.0@aar') {
transitive = true;
}
//RxJava
implementation "io.reactivex.rxjava3:rxjava:$rxJavaVersion"
//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
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 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.biganto.visual.roompark">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name=".base.RoomParkApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:screenOrientation="userPortrait"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".base.RoomParkMainActivity">
android:theme="@style/AppTheme.Launch">
<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>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</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>
</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
import android.app.Application
import android.util.Log
import com.biganto.visual.roompark.BuildConfig
import com.biganto.visual.roompark.di.koin.initDI
import com.crashlytics.android.Crashlytics
import com.squareup.picasso.Picasso
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.DaggerAppComponent
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 java.net.SocketException
/**
* 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() {
super.onCreate()
initDI()
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
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
val picasso = picassoBuilder.build()
try {
Picasso.setSingletonInstance(picasso)
} catch (ignored: IllegalStateException) {
Timber.e(ignored, "PICASSO")
// Picasso instance was already set
// cannot set it after Picasso.with(Context) was already in use
RxJavaPlugins.setErrorHandler { e ->
when (e) {
is UndeliverableException -> {
}
// fine, irrelevant network problem or API that throws on cancellation
is IOException -> return@setErrorHandler
is SocketException -> return@setErrorHandler
// fine, some blocking code was interrupted by a dispose call
is InterruptedException ->
return@setErrorHandler
// that's likely a bug in the application
is NullPointerException -> {
Thread.currentThread().uncaughtExceptionHandler
.uncaughtException(Thread.currentThread(), e)
return@setErrorHandler
}
is IllegalArgumentException -> {
Thread.currentThread().uncaughtExceptionHandler
.uncaughtException(Thread.currentThread(), e)
return@setErrorHandler
}
// that's a bug in RxJava or in a custom operator
is IllegalStateException -> {
Thread.currentThread().uncaughtExceptionHandler
.uncaughtException(Thread.currentThread(), e)
return@setErrorHandler
}
}
Timber.w(e, "Undeliverable exception received, not sure what to do")
}
}
}
......@@ -46,7 +79,7 @@ class RoomParkApplication : Application() {
override fun log(priority: Int, tag: String?, message: String, throwable: Throwable?) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) return
Crashlytics.log(priority, tag, message)
throwable?.let { Crashlytics.logException(it) }
// Crashlytics.log(priority, tag, message)
// throwable?.let { Crashlytics.logException(it) }
}
}
\ No newline at end of file
package com.biganto.visual.roompark.base
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import android.view.View
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.di.koin.StartUpData
import org.koin.android.scope.currentScope
import com.biganto.visual.roompark.presentation.screen.splash.SplashScreenController
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
private val entryDate : StartUpData by currentScope.inject()
class RoomParkMainActivity(
) : 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?) {
setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState)
// if (!BuildConfig.DEBUG) Fabric.with(this, Crashlytics())
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.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.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 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
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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