Commit 3c478553 authored by Vladislav Bogdashkin's avatar Vladislav Bogdashkin 🎣

Merge branch 'release/0.9.0'

parents f1bc28e2 a0ebda88
......@@ -13,3 +13,10 @@
/captures
.externalNativeBuild
.cxx
/app/libs
/app/src/main/assets
/app/src/main/jniLibs
/app/release
......@@ -21,7 +21,7 @@ android {
defaultConfig {
applicationId $APPLICATION_ID
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86'
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
minSdkVersion minSdkVersion_RoomPark
targetSdkVersion targetSdkVersion_RoomPark
......@@ -60,6 +60,10 @@ android {
targetCompatibility 1.8
sourceCompatibility 1.8
}
lintOptions {
disable 'BinaryOperationInTimber'
}
}
kapt {
......@@ -171,6 +175,10 @@ dependencies {
//RxKotlin
implementation("io.reactivex.rxjava2:rxkotlin:$rxKotlinVersion")
//Arch Lifecycle
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
//Tests
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
......
......@@ -2,8 +2,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.biganto.visual.roompark">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name=".base.RoomParkApplication"
......@@ -26,6 +31,14 @@
</activity>
<activity android:name=".player.BigantoPlayerActivity" android:screenOrientation="fullSensor"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density"
android:largeHeap="true"
android:hardwareAccelerated="true"
android:process=":UnityKillsMe">
</activity>
<service
android:name=".data.RoomParkMessageService"
android:enabled="true"
......@@ -52,6 +65,11 @@
android:resource="@color/colorAccent" />
</service>
<service
android:name=".data.service.download.DownloadManagerService"
android:enabled="true"
android:exported="false" />
</application>
</manifest>
\ No newline at end of file
......@@ -102,6 +102,7 @@ abstract class BigantoBaseController<VS : BigantoBaseViewState,V: BigantoBaseCon
}
override fun handleBack(): Boolean {
detachDisposable.clear()
router.popController(this)
return true
// return super.handleBack()
......
......@@ -30,12 +30,11 @@ abstract class BigantoBasePresenter<V : MvpView, VS>
open fun parseError(t: Throwable) : VS =
when (t) {
is CustomApiException ->{
is CustomApiException -> {
Timber.e("CustomApiException ${t.messageStringId} / ${t.customMessage}")
parse(t)
}
is NoNetworkException -> parse(t)
is NoNetworkException -> {parse(t)}
else -> {Timber.e(t);parse(t)}
}
......
......@@ -25,7 +25,7 @@ import timber.log.Timber
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
internal const val PHOTOS_KEY = "CHHOSE_PHOTO_LIST_KEY"
internal const val PHOTOS_KEY = "CHOOSE_PHOTO_LIST_KEY"
private fun formBundle(photos: ArrayList<PhotoResolutionModel>): Bundle {
val b = Bundle()
......@@ -66,7 +66,6 @@ class ChooseResolutionDialogController : Controller {
detachDisposable.add(
(recyclerView.adapter as PhotoSizeAdapter).onItemClicked.subscribe {
Timber.d("gonna shit : $it")
router.replaceTopController(RouterTransaction.with(
PhotoDialogController(
it.url
......
package com.biganto.visual.roompark.conductor.dialogs
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.annotation.LayoutRes
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.BaseRoomParkActivity
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.ActivityModule
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import com.biganto.visual.roompark.domain.interactor.SettingsInteractor
import com.biganto.visual.roompark.domain.use_case.DownloadUseCase
import com.biganto.visual.roompark.util.view_utils.snackbar.ISnackBarProvider
import com.bluelinelabs.conductor.Controller
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.FitCenter
import com.google.android.material.textview.MaterialTextView
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import jp.wasabeef.glide.transformations.BlurTransformation
import jp.wasabeef.glide.transformations.ColorFilterTransformation
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
class DownloadPlansDialogController : Controller {
constructor():super()
constructor(args: Bundle) : super(args)
lateinit var progressBarDownload: ProgressBar
lateinit var downloaderBg: ImageView
lateinit var downloadTourTitleText: MaterialTextView
lateinit var cancelDownloadText: MaterialTextView
lateinit var downloadingContentText: MaterialTextView
override fun onContextAvailable(context: Context) {
super.onContextAvailable(context)
DaggerPlansDownloaderScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
}
private var downloadToken = DownloadUseCase.CancellationToken(false)
@Inject
lateinit var downloader: SettingsInteractor
@Inject
lateinit var rpActivity: RoomParkMainActivity
lateinit var snackbar: ISnackBarProvider
private val disposables = CompositeDisposable()
override fun onDetach(view: View) {
disposables.clear()
super.onDetach(view)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
val view = inflater.inflate(getLayoutId(), container, false)
snackbar = ActivityModule.provideSnackBar(rpActivity)
progressBarDownload = view.findViewById(R.id.downloadProgress)
downloaderBg = view.findViewById(R.id.backgroundDownloader)
cancelDownloadText = view.findViewById(R.id.cancelDownloadButton)
downloadTourTitleText = view.findViewById(R.id.tourToDownloadTitle)
downloadingContentText = view.findViewById(R.id.downloadingTitle)
downloadingContentText.text = resources?.getString(R.string.download_plan_types_screen_title)
// progress.visibility = View.VISIBLE
downloadToken = DownloadUseCase.CancellationToken(false)
downloadTourTitleText.text = ""
Glide.with(view)
.load(R.drawable.flat_placeholder)
.transform(BlurTransformation(13, 4)
,ColorFilterTransformation(0x99000000.toInt())
,FitCenter())
.into(downloaderBg)
disposables.add(
downloader.downloadTourPlans(downloadToken)
// .onErrorReturn { parseError(it);TourPreviewEntity() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe (
{ model ->
downloadTourTitleText.text = "${model.currentProgress}/${model.totalProgress}"
updateProgressBar(model.currentProgress, model.totalProgress)
if (model.currentProgress == model.totalProgress){
snackbar.showSnackBar(R.string.plan_types_download_completed)
handleBack()
}
}
,{error ->
Timber.e(error)
snackbar.showSnackBar(error.localizedMessage)
handleBack()
}
))
cancelDownloadText.setOnClickListener {handleBack() }
// downloadTour(it.tour.tour_id, downloadToken)
// view.findViewById<View>(R.id.close_current_button).setOnClickListener { handleBack() }
return view
}
private fun updateProgressBar(curr:Int, max:Int){
activity?.runOnUiThread {
progressBarDownload.max = max
progressBarDownload.progress = curr
}
}
@LayoutRes
fun getLayoutId() = R.layout.download_tour_layout
override fun handleBack(): Boolean {
downloadToken.isCancelled = true
return router.popController(this)
}
}
@PerScreen
@Component(
modules = [PlansDownloaderScreenModule::class],
dependencies = [AppComponent::class])
interface PlansDownloaderScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): PlansDownloaderScreenComponent
}
//
// val presenter: ArticlesScreenPresenter
fun inject(controller: DownloadPlansDialogController)
}
@Module
abstract class PlansDownloaderScreenModule{
@PerScreen
@Binds
abstract fun provideActivity(activity: RoomParkMainActivity): BaseRoomParkActivity
}
package com.biganto.visual.roompark.conductor.dialogs
import android.annotation.SuppressLint
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.ImageView
import android.widget.ProgressBar
import androidx.annotation.LayoutRes
import androidx.core.os.bundleOf
......@@ -20,6 +22,7 @@ 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
import timber.log.Timber
/**
......@@ -37,13 +40,13 @@ class PhotoDialogController : Controller {
lateinit var recyclerView : RecyclerView
@SuppressLint("SourceLockedOrientationActivity")
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
......@@ -79,12 +82,20 @@ class PhotoDialogController : Controller {
})
.into(photoView)
}
view.findViewById<ImageView>(R.id.close_current_button).setOnClickListener {
Timber.d("Clicked")
handleBack()
}
return view
}
@LayoutRes
fun getLayoutId() = R.layout.photo_viewer
@SuppressLint("SourceLockedOrientationActivity")
override fun handleBack(): Boolean {
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
return router.popCurrentController()
......
package com.biganto.visual.roompark.conductor.dialogs
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import androidx.annotation.LayoutRes
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.BaseRoomParkActivity
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.di.dagger.ActivityModule
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import com.biganto.visual.roompark.domain.interactor.SettingsInteractor
import com.biganto.visual.roompark.domain.model.bytesToSize
import com.biganto.visual.roompark.util.view_utils.snackbar.ISnackBarProvider
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.view.clicks
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
private const val TYPICAL_PLAN_SIZE = (1.2f*1024L*1024L).toLong()
class StartPlansDownloadingDialogController : Controller {
constructor() : super()
constructor(args: Bundle) : super(args)
private val detachDisposable = CompositeDisposable()
override fun onDetach(view: View) {
detachDisposable.clear()
super.onDetach(view)
}
@Inject
lateinit var interactor: SettingsInteractor
@Inject
lateinit var file: FileModule
lateinit var snackbar: ISnackBarProvider
@Inject
lateinit var rpActivity: RoomParkMainActivity
override fun onContextAvailable(context: Context) {
super.onContextAvailable(context)
DaggerStartPlansDownloadingDialogScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
val view = inflater.inflate(getLayoutId(), container, false)
snackbar = ActivityModule.provideSnackBar(rpActivity)
view.findViewById<MaterialTextView>(R.id.download_dialog_title)
.text = resources?.getString(R.string.download_all_plans_notice_text)
view.findViewById<MaterialTextView>(R.id.all_tours_size_textView)
.text = resources?.getString(R.string.download_all_plans_size_title
,"0")
file.freeSpace.bytesToSize().let {
view.findViewById<MaterialTextView>(R.id.disk_size_textView)
.text = resources?.getString(R.string.download_all_tours_disk_size_title,it)
}
view.findViewById<Button>(R.id.alert_dismiss_button)
.setOnClickListener { handleBack() }
view.findViewById<ImageView>(R.id.close_current_button)
.setOnClickListener { handleBack() }
detachDisposable.add(
interactor.fetchPlanTypesSizes()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe ({
view.findViewById<MaterialTextView>(R.id.all_tours_size_textView)
.text = resources?.getString(R.string.download_all_plans_size_title
,(it* TYPICAL_PLAN_SIZE).bytesToSize())
},
{err ->
Timber.e(err)
snackbar.showSnackBar(R.string.download_all_plans_errors_snackbar_message)
})
)
detachDisposable.add(
view.findViewById<Button>(R.id.alert_ok_button)
.clicks()
.debounce(200, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
router.replaceTopController(
RouterTransaction.with(DownloadPlansDialogController())
.pushChangeHandler(DialogChangeHandler())
.popChangeHandler(DialogChangeHandler())
)
}
)
view.findViewById<View>(R.id.close_current_button).setOnClickListener { handleBack() }
// view.setOnClickListener { handleBack() }
return view
}
@LayoutRes
fun getLayoutId() = R.layout.tours_download_dialog_screen
override fun handleBack(): Boolean {
return router.popCurrentController()
}
}
@PerScreen
@Component(
modules = [StartPlansDownloadingDialogScreenModule::class],
dependencies = [AppComponent::class])
interface StartPlansDownloadingDialogScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): StartPlansDownloadingDialogScreenComponent
}
fun inject(controller: StartPlansDownloadingDialogController)
}
@Module
abstract class StartPlansDownloadingDialogScreenModule{
@PerScreen
@Binds
abstract fun provideActivity(activity: RoomParkMainActivity): BaseRoomParkActivity
}
package com.biganto.visual.roompark.conductor.dialogs.tour_chooser
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.annotation.LayoutRes
import androidx.core.os.bundleOf
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.ActivityModule
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import com.biganto.visual.roompark.domain.interactor.ToursInteractor
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.domain.model.fromEntity
import com.biganto.visual.roompark.domain.use_case.DownloadUseCase
import com.biganto.visual.roompark.util.view_utils.snackbar.ISnackBarProvider
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import com.bluelinelabs.conductor.Controller
import com.bumptech.glide.Glide
import com.google.android.material.textview.MaterialTextView
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import jp.wasabeef.glide.transformations.BlurTransformation
import jp.wasabeef.glide.transformations.ColorFilterTransformation
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
internal const val DOWNLOAD_TOUR_ID_KEY = "DOWNLOAD_TOUR_ID_KEY"
class DownloadTourDialogController : Controller {
constructor(args: Bundle) : super(args)
constructor(tourArg: TourModel) : super(bundleOf(DOWNLOAD_TOUR_ID_KEY to tourArg))
lateinit var progressBarDownload: ProgressBar
lateinit var downloaderBg: ImageView
lateinit var downloadTourTitleText: MaterialTextView
lateinit var cancelDownloadText: MaterialTextView
override fun onContextAvailable(context: Context) {
super.onContextAvailable(context)
DaggerDownloaderScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
}
private val tour:TourModel by lazy {
args.getParcelable<TourModel>(DOWNLOAD_TOUR_ID_KEY)
}
private var downloadToken = DownloadUseCase.CancellationToken(false)
@Inject
lateinit var downloader: ToursInteractor
@Inject
lateinit var rpActivity: RoomParkMainActivity
lateinit var snackbar: ISnackBarProvider
private val disposables = CompositeDisposable()
override fun onDetach(view: View) {
disposables.clear()
super.onDetach(view)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
val view = inflater.inflate(getLayoutId(), container, false)
snackbar = ActivityModule.provideSnackBar(rpActivity)
progressBarDownload = view.findViewById(R.id.downloadProgress)
downloaderBg = view.findViewById(R.id.backgroundDownloader)
cancelDownloadText = view.findViewById(R.id.cancelDownloadButton)
downloadTourTitleText = view.findViewById(R.id.tourToDownloadTitle)
// progress.visibility = View.VISIBLE
downloadToken = DownloadUseCase.CancellationToken(false)
downloadTourTitleText.text = tour.title
Glide.with(view)
.load(tour.previewUrl)
.transform(BlurTransformation(13, 8)
,ColorFilterTransformation(0x99000000.toInt()))
.into(downloaderBg)
disposables.add(
downloader.downloadTour(tour.tour_id, downloadToken)
.onErrorReturn { Timber.e(it);TourPreviewEntity() }
// .onErrorReturn { parseError(it);TourPreviewEntity() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe (
{ model ->
updateProgressBar(model.downloadedFiles, model.overallFiles)
if (model.overallFiles == model.downloadedFiles)
activity?.let{
startPlayer(it, fromEntity(model))
handleBack()
}
}
,{error ->
Timber.e(error)
snackbar.showSnackBar(error.localizedMessage)
}
))
cancelDownloadText.setOnClickListener {handleBack() }
// downloadTour(it.tour.tour_id, downloadToken)
// view.findViewById<View>(R.id.close_current_button).setOnClickListener { handleBack() }
return view
}
private fun updateProgressBar(curr:Int, max:Int){
activity?.runOnUiThread {
progressBarDownload.max = max
progressBarDownload.progress = curr
}
}
@LayoutRes
fun getLayoutId() = R.layout.download_tour_layout
override fun handleBack(): Boolean {
downloadToken.isCancelled = true
return router.popController(this)
}
}
@PerScreen
@Component(
modules = [DownloaderScreenModule::class],
dependencies = [AppComponent::class])
interface DownloaderScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): DownloaderScreenComponent
}
//
// val presenter: ArticlesScreenPresenter
fun inject(controller: DownloadTourDialogController)
}
@Module
abstract class DownloaderScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
}
package com.biganto.visual.roompark.conductor.dialogs.tour_chooser
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import androidx.annotation.LayoutRes
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.BaseRoomParkActivity
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.di.dagger.ActivityModule
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
import com.biganto.visual.roompark.domain.interactor.SettingsInteractor
import com.biganto.visual.roompark.domain.model.bytesToSize
import com.biganto.visual.roompark.util.view_utils.snackbar.ISnackBarProvider
import com.bluelinelabs.conductor.Controller
import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.view.clicks
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
class StartToursDownloadingDialogController : Controller {
constructor():super()
constructor(args: Bundle):super(args)
private val detachDisposable = CompositeDisposable()
override fun onDetach(view: View) {
detachDisposable.clear()
super.onDetach(view)
}
@Inject
lateinit var downloader: SettingsInteractor
@Inject
lateinit var file: FileModule
lateinit var snackbar: ISnackBarProvider
@Inject
lateinit var rpActivity: RoomParkMainActivity
override fun onContextAvailable(context: Context) {
super.onContextAvailable(context)
DaggerStartToursDownloadingDialogScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity)
.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
val view = inflater.inflate(getLayoutId(), container, false)
view.findViewById<MaterialTextView>(R.id.download_dialog_title)
.text = resources?.getString(R.string.download_all_tours_notice_text)
view.findViewById<MaterialTextView>(R.id.all_tours_size_textView)
.text = resources?.getString(R.string.download_all_tours_size_title
,"0")
snackbar = ActivityModule.provideSnackBar(rpActivity)
file.freeSpace.bytesToSize().let {
view.findViewById<MaterialTextView>(R.id.disk_size_textView)
.text = resources?.getString(R.string.download_all_tours_disk_size_title,it)
}
view.findViewById<Button>(R.id.alert_dismiss_button)
.setOnClickListener { handleBack() }
view.findViewById<ImageView>(R.id.close_current_button)
.setOnClickListener { handleBack() }
detachDisposable.add(
downloader.fetchToursSizes()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
view.findViewById<MaterialTextView>(R.id.all_tours_size_textView)
.text = resources?.getString(R.string.download_all_tours_size_title
,it.bytesToSize())
}
)
detachDisposable.add(
view.findViewById<Button>(R.id.alert_ok_button)
.clicks()
.debounce(200, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.io())
.flatMap{ downloader.startToursDownloading()}
.observeOn(AndroidSchedulers.mainThread())
.subscribe ({
snackbar.showSnackBar(R.string.download_all_tours_start_snackbar_message)
handleBack()
},{
Timber.e(it)
snackbar.showSnackBar(R.string.download_all_tours_errors_snackbar_message)
handleBack()
}
)
)
view.findViewById<View>(R.id.close_current_button).setOnClickListener { handleBack() }
// view.setOnClickListener { handleBack() }
return view
}
@LayoutRes
fun getLayoutId() = R.layout.tours_download_dialog_screen
override fun handleBack(): Boolean {
return router.popCurrentController()
}
}
@PerScreen
@Component(
modules = [StartToursDownloadingDialogScreenModule::class],
dependencies = [AppComponent::class])
interface StartToursDownloadingDialogScreenComponent {
@Component.Factory
interface Factory{
fun create(
appComponent: AppComponent
,@BindsInstance activity: RoomParkMainActivity
): StartToursDownloadingDialogScreenComponent
}
fun inject(controller: StartToursDownloadingDialogController)
}
@Module
abstract class StartToursDownloadingDialogScreenModule{
@PerScreen
@Binds
abstract fun provideActivity(activity: RoomParkMainActivity): BaseRoomParkActivity
}
package com.biganto.visual.roompark.conductor.dialogs.tour_chooser
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyclerAdapter
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder
import com.biganto.visual.roompark.util.extensions.setGone
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.RouterTransaction
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.textview.MaterialTextView
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 09.04.2019.
*/
internal const val TOUR_MODEL_LIST = "CHOOSE_TOUR_LIST_KEY"
private fun formBundle(photos: ArrayList<TourModel>): Bundle {
val b = Bundle()
b.putParcelableArrayList(TOUR_MODEL_LIST,photos)
return b
}
class ChooseTourDialogController : Controller {
constructor(args: Bundle) : super(args)
constructor(items: ArrayList<TourModel>) : super(formBundle(items))
private lateinit var recyclerView : RecyclerView
private val detachDisposable = CompositeDisposable()
override fun onDetach(view: View) {
detachDisposable.clear()
super.onDetach(view)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
val view = inflater.inflate(getLayoutId(), container, false)
recyclerView = view.findViewById(R.id.toursList)
recyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
recyclerView.adapter = TourChooserAdapter()
recyclerView.itemAnimator = null
recyclerView.addItemDecoration(
DividerItemDecoration(activity,DividerItemDecoration.VERTICAL)
)
args.getParcelableArrayList<TourModel>(TOUR_MODEL_LIST)?.let {
(recyclerView.adapter as TourChooserAdapter).setItems(it)
}
detachDisposable.add(
(recyclerView.adapter as TourChooserAdapter)
.onItemClicked
.debounce(300L,TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(::onTourClicked)
)
// view.findViewById<View>(R.id.close_current_button).setOnClickListener { handleBack() }
view.setOnClickListener { handleBack() }
return view
}
private fun onTourClicked(tour: TourModel) {
when (tour.downloadState) {
DownloadState.Downloaded -> {
activity?.let { startPlayer(it, tour) }
handleBack()
}
else -> {
router.replaceTopController(
RouterTransaction.with(DownloadTourDialogController(tour))
.popChangeHandler(DialogChangeHandler())
.pushChangeHandler(DialogChangeHandler())
)
}
}
}
@LayoutRes
fun getLayoutId() = R.layout.tours_chooser_screen
override fun handleBack(): Boolean {
return router.popCurrentController()
}
}
internal class TourChooserAdapter:CommonRecyclerAdapter<TourChooserViewHolder,TourModel>(){
override val vhKlazz = TourChooserViewHolder::class
override fun getVhLayout(): Int = R.layout.tour_chooser_viewholder
}
internal class TourChooserViewHolder(itemView: View) :CommonViewHolder<TourModel>(itemView){
@BindView(R.id.tour_preview_imageView)
lateinit var tourPreview : ImageView
@BindView(R.id.tour_name)
lateinit var tourName : MaterialTextView
@BindView(R.id.tour_status_imageView)
lateinit var tourStatus : ImageView
@Inject
lateinit var activity:Context
override fun onViewBound(model: TourModel) {
tourName.text = model.title
Glide.with(tourPreview)
.load(model.previewUrl)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(tourPreview)
tourStatus.setGone(model.downloadState == DownloadState.Downloaded)
tourStatus.setImageDrawable(
itemView.context.resources.getDrawable(
when(model.downloadState){
DownloadState.Downloaded -> R.drawable.ic_download
DownloadState.Suspended -> R.drawable.ic_download
DownloadState.DownloadQueue -> R.drawable.ic_download
else -> R.drawable.ic_download
}
,null)
)
}
}
package com.biganto.visual.roompark.conductor.dialogs.tour_chooser
import android.content.Context
import android.content.Intent
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.player.BigantoPlayerActivity
import com.biganto.visual.roompark.player.unity_utils.LoadTourConfig
/**
* Created by Vladislav Bogdashkin on 07.04.2020.
*/
const val PLAY_TOUR_DATA_EXTRAS="BIGANTO_PLAYER_TOUR_DATA"
fun startPlayer(context:Context,tour: TourModel)
{
// hideToursList()
val playerIntent= Intent(context, BigantoPlayerActivity::class.java)
val tourConfig = LoadTourConfig(
tour.tour_id.toInt(),
tour.targetResolution,
FileModule.assetsDirectory(context),
tour.metaPredict,
tour.previewUrl,
context.resources?.getBoolean(R.bool.isTablet)?.not()?:true,
true
)
playerIntent.putExtra(PLAY_TOUR_DATA_EXTRAS,tourConfig)
context.startActivity(playerIntent)
}
\ No newline at end of file
......@@ -60,7 +60,7 @@ class BigantoMviConductorLifecycleListener<V : MvpView, P : MviPresenter<V, *>>
}
val viewMpv = callback?.mvpView ?: throw NullPointerException(
"MvpView returned from getMvpView() is null. Returned by " + controller.activity!!)
"MvpView returned from getMvpView() is null. Returned by ${controller.activity}")
if (viewStateWillBeRestored) {
callback!!.setRestoringViewState(true)
......
package com.biganto.visual.roompark.data
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.data.service.notification.INotificationCenter
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import timber.log.Timber
import javax.inject.Inject
class RoomParkMessageService : FirebaseMessagingService() {
@Inject
lateinit var notyCenter: INotificationCenter
init {
notyCenter = RoomParkApplication.component.provideNotifivations()
}
override fun onNewToken(p0: String) {
super.onNewToken(p0)
Timber.d("NEW TOKEN REGISTERED: ${p0}")
......@@ -19,6 +30,7 @@ class RoomParkMessageService : FirebaseMessagingService() {
// 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
......@@ -27,7 +39,8 @@ class RoomParkMessageService : FirebaseMessagingService() {
}
// Check if message contains a notification payload.
if (remoteMessage.notification != null) {
Timber.d("Message Notification Body: %s", remoteMessage.notification!!.body)
Timber.d("Message Notification Body: %s", remoteMessage.notification?.body)
notyCenter.showPushNotifyMessage(remoteMessage.notification?.body?:"Уведомление")
}
// Also if you intend on generating your own notifications as a result of a received FCM
// message, here is where that should be initiated. See sendNotification method below.
......
package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi
import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.ImageAlbumJunctionEntity
import com.biganto.visual.roompark.data.repository.mapper.fromRaw
......@@ -73,7 +73,9 @@ class AlbumsContractModule @Inject constructor(
arrayListOf(fetchTopLevelAlbumsDb,fetchTopLevelAlbumsApi)
)
.doOnNext { Timber.d("got entity $it") }
.map { fromEntity(it,::fromEntity) }
.map { fromEntity(it,::fromEntity).sortedByDescending{ album -> album.published } }
//endregion allAlbums
//region concrete Albums
......
......@@ -2,13 +2,15 @@ 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.api.room_park.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.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.SubscriptionTopic
import com.biganto.visual.roompark.domain.model.fromEntity
import io.reactivex.Completable
import io.reactivex.Observable
......@@ -37,6 +39,19 @@ class AuthContractModule @Inject constructor(
api.authenticate(email,password)
.map ( ::fromRaw )
.flatMap{ db.upsertUser(it) }
.flatMap {user ->
val userSubs = user.subscriptions?.map { it as SubscriptionEntity }
var newSubs = defaultSubsList(user)
if (!userSubs.isNullOrEmpty()){
newSubs = newSubs.filter {defSub ->
!userSubs.map { it.topic }.contains(defSub.topic)
}.toList()
}
if (newSubs.isNullOrEmpty()) return@flatMap Observable.just(user)
return@flatMap db.upsert(newSubs)
.toObservable()
.flatMap { db.refreshUser(user) }
}
.doOnNext{ Timber.d("user id: ${it.uuid}")}
.doOnNext { local.setRecentUser(it.uuid.toString()).blockingAwait() }
.map(::fromEntity)
......@@ -60,3 +75,41 @@ class AuthContractModule @Inject constructor(
} }
}
private fun defaultSubsList(owner:UserEntity) : List<SubscriptionEntity> = arrayListOf(
SubscriptionEntity().apply {
setTopic(SubscriptionTopic.Progress_1().topicName)
setOwner(owner)
setState(false)
}
,SubscriptionEntity().apply {
setTopic(SubscriptionTopic.Progress_2().topicName)
setOwner(owner)
setState(false)
}
,SubscriptionEntity().apply {
setTopic(SubscriptionTopic.Progress_3().topicName)
setOwner(owner)
setState(false)
}
,SubscriptionEntity().apply {
setTopic(SubscriptionTopic.Progress_kindergarden().topicName)
setOwner(owner)
setState(false)
}
,SubscriptionEntity().apply {
setTopic(SubscriptionTopic.News().topicName)
setOwner(owner)
setState(false)
}
,SubscriptionEntity().apply {
setTopic(SubscriptionTopic.Blog().topicName)
setOwner(owner)
setState(false)
}
,SubscriptionEntity().apply {
setTopic(SubscriptionTopic.Construction().topicName)
setOwner(owner)
setState(false)
}
)
\ No newline at end of file
......@@ -2,22 +2,20 @@ package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.androidplayer.data.repository.local.ILocalStore
import com.biganto.visual.roompark.data.local.UserState
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi
import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.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.db.requrey.model.UserEntity
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.rxkotlin.Observables
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
......@@ -27,14 +25,11 @@ import javax.inject.Inject
*/
//
const val TEST_DEAL_TOKEN =
"183|iauhqAKpzrex9g1JYFYcp4443_PU5tm8ioce8G-QhtQbjNP-_a8CnW0WBw8O7dj4Rm2xdRGTuqvkfAF2zyMUSg=="
class EstateRepository @Inject constructor(
private val local: ILocalStore,
local: ILocalStore,
private val api: IRoomParkApi,
private val db: IDb,
private val file: FileModule
private val db: IDb
): DealContract {
......@@ -42,50 +37,32 @@ class EstateRepository @Inject constructor(
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 ->
override fun fetchFavorites(user:UserEntity): Observable<List<EstateEntity>> =
Observables.zip(
api.getFavorites(user.authToken)
.doOnError(Timber::e)
.map { fromRawList(it, ::fromRaw) }
.doOnNext {
it.forEach { estate ->
,db.getUserFavorites(user.uuid)
.toList().toObservable()
.doOnError(Timber::e)
){apiList,dbList ->
apiList.forEach { estate ->
estate.setFavorite(true)
estate.user = user
}
}
.doOnNext(db::blockingUpsert)
}
dbList
.filterNotNull()
.filter { dbFav ->!apiList.map{it.id}.contains(dbFav.id) }
.let{ db.deleteEstate(it) }
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()
return@zip apiList
}
}
.flatMap {
db.getUserFavorites(it.uuid)
.doOnError(Timber::e)
.toList().toObservable()
}
.flatMap(db::upsertEstates)
.map { it.toList() }
.doOnNext{ db.refreshUser(user) }
override fun getFavorites(): Observable<List<EstateModel>> {
return Observable.mergeDelayError(
arrayListOf(
getFavoritesApi, getFavoritesDb
)
).map { fromEntity(it, ::fromEntity) }
.doOnError(Timber::e)
}
override fun fetchDeals(user:UserEntity): Observable<List<DealEntity>> =
getDealsApi(user)
private fun fetchEstateDb(id: Int) = db.getEstate(id)
......@@ -95,128 +72,30 @@ class EstateRepository @Inject constructor(
}
private fun getPlanTypesApi(estateId: Int): Observable<List<PlanPresetEntity>> =
api.getEstatePlanTypes(estateId)
.doOnNext { Timber.d("raw0 $it") }
.map { fromRawList(it, ::fromRaw) }
.map {
it.onEach { plan ->
val e = EstateEntity()
e.setId(estateId)
plan.estateId = e
}.toList()
}
.doOnNext(db::blockingUpsert)
.subscribeOn(Schedulers.io())
override fun getPlanTypes(estateId: Int): Observable<List<PlanPresetModel>> =
Observable.mergeDelayError(
arrayListOf(getPlanTypesApi(estateId))
).map { l -> List(l.size) { fromEntity(l[it]) } }
private fun getPlanApi(estateId: Int
, planId:Int
, furniture:Boolean? = null
, sizes:Boolean? = null
, walls:Boolean? = null
, electric:Boolean? = null) =
api.getDirectPlan(estateId, planId,
furniture?:false
,sizes?:false
,walls?:false
,electric?:false)
.map{
val sFile = getPlanFile(
estateId = estateId,
planId = planId,
furniture = furniture,
walls = walls,
sizes = sizes,
electric = electric)
file.saveFileToDisk(sFile,it)
sFile.path
}
.subscribeOn(Schedulers.io())
private fun getPlanFile(estateId: Int
, planId:Int
, furniture:Boolean? = null
, sizes:Boolean? = null
, walls:Boolean? = null
, electric:Boolean? = null)
= FileModule.getDirectory(file.context
,FileModule.FileDirectory.PlanTypeDir(
estateId = estateId,
planId = planId,
furniture = furniture,
walls = walls,
sizes = sizes,
electric = electric
))
override fun getEmptyPlan(estateId: Int
,planId:Int): Observable<String> =
Observable.mergeDelayError(
arrayListOf(getPlanApi(estateId,planId))
)
override fun getPlan(estateId: Int
,planId:Int
, furniture:Boolean?
, sizes:Boolean?
, walls:Boolean?
, electric:Boolean?): Observable<String> =
Observable.fromCallable { getPlanFile(
estateId = estateId,
planId = planId,
furniture = furniture,
walls = walls,
sizes = sizes,
electric = electric) }.switchMap {
if (it.exists()) Observable.just(it.path)
else getPlanApi(estateId
,planId
, furniture
, sizes
, walls
, electric
)
}
// 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)
override fun getDealsApi(user: UserEntity): Observable<List<DealEntity>> =
Observables.zip(
api.getDeals(user.authToken)
.doOnError(Timber::e)
.map { fromRawList(it, ::fromRaw) }
.doOnNext {
it.forEach { deal ->
.map { List(it.size){index -> fromRaw(it[index],user)} }
,db.getUserDeals(user.uuid)
.toList().toObservable()
.doOnError(Timber::e)
){apiList,dbList ->
apiList.forEach { deal ->
dbList?.firstOrNull { dbDeal-> dbDeal?.id == deal.id }?.let {
deal.setRead(it.read)
}
deal.user = user
}
dbList
.filterNotNull()
.filter { dbFav ->!apiList.map{it.id}.contains(dbFav.id) }
.let{ db.deleteDeal(it) }
return@zip apiList
}
.doOnNext(db::blockingUpsert)
}
.doOnNext { db.refreshUser(user) }
private val getDealsDb: Observable<List<DealEntity>> =
local.recentUser()
......@@ -229,21 +108,11 @@ class EstateRepository @Inject constructor(
.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)
.doOnError { Timber.e(it) }
......
package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi
import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.ArticleEntity
import com.biganto.visual.roompark.data.repository.mapper.fromRaw
......
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.api.room_park.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
......
package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.androidplayer.data.repository.local.ILocalStore
import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.EstateEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.PlanPresetEntity
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.data.repository.mapper.fromRaw
import com.biganto.visual.roompark.data.repository.mapper.fromRawList
import com.biganto.visual.roompark.domain.contract.FlatPlanContract
import com.biganto.visual.roompark.domain.model.FeatureModel
import com.biganto.visual.roompark.domain.model.PlanPresetModel
import com.biganto.visual.roompark.domain.model.fromEntity
import com.biganto.visual.roompark.domain.use_case.DownloadUseCase
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.util.*
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 20.04.2020.
*/
class PlanRepository @Inject constructor(
private val local: ILocalStore,
private val api: IRoomParkApi,
private val db: IDb,
private val file: FileModule
)
: FlatPlanContract {
private fun getPlanTypesApi(estateId: Int): Observable<List<PlanPresetEntity>> =
api.getEstatePlanTypes(estateId)
.doOnNext { Timber.d("raw0 $it") }
.map { fromRawList(it, ::fromRaw) }
.map {
it.onEach { plan ->
val e = EstateEntity()
e.setId(estateId)
plan.estateId = e
}.toList()
}
.doOnNext(db::blockingUpsert)
.subscribeOn(Schedulers.io())
override fun getPlansObservable(list:List<PlanFeaturesVariant>
,cancellationToken: DownloadUseCase.CancellationToken)
: Observable<String> {
return Observable.create { emitter ->
list
.asSequence()
.chunked(12)
.also { System.gc() }
.flatten()
.filter { !cancellationToken.isCancelled }
.forEach { variant->
val file = FileModule.getDirectory(
file.context
, FileModule.FileDirectory.PlanTypeDir(
estateId = variant.estateId,
planId = variant.planId,
furniture = variant.furniture,
walls = variant.walls,
sizes = variant.sizes,
electric = variant.electric
))
if (file.exists())
emitter.onNext(file.absolutePath)
else{
try {
Timber.w("Cancellation is: ${cancellationToken.isCancelled}")
if (cancellationToken.isCancelled){
emitter.onComplete()
}
file.parentFile.mkdirs()
file.writeText(
api.getDirectPlan(variant.estateId, variant.planId,
variant.furniture?:false
,variant.sizes?:false
,variant.walls?:false
,variant.electric?:false).blockingFirst()
)
emitter.onNext(file.absolutePath)
}
catch (e:Throwable){emitter.onError(e)}
}
}
emitter.onComplete()
}
}
override fun getPlanTypes(estateId: Int): Observable<List<PlanPresetModel>> =
Observable.mergeDelayError(
arrayListOf(getPlanTypesApi(estateId))
).map { l -> List(l.size) { fromEntity(l[it]) } }
private fun getPlanApi(estateId: Int
, planId:Int
, furniture:Boolean? = null
, sizes:Boolean? = null
, walls:Boolean? = null
, electric:Boolean? = null) =
api.getDirectPlan(estateId, planId,
furniture?:false
,sizes?:false
,walls?:false
,electric?:false)
.map {
val sFile = getPlanFile(
estateId = estateId,
planId = planId,
furniture = furniture,
walls = walls,
sizes = sizes,
electric = electric
)
file.saveFileToDisk(sFile, it)
sFile.absolutePath
}
.subscribeOn(Schedulers.io())
override fun getPlanFile(featuresVariant: PlanFeaturesVariant)
= getPlanFile(
estateId = featuresVariant.estateId,
planId = featuresVariant.planId,
furniture = featuresVariant.furniture,
sizes = featuresVariant.sizes,
walls = featuresVariant.walls,
electric = featuresVariant.electric
)
private fun getPlanFile(estateId: Int
, planId:Int
, furniture:Boolean? = null
, sizes:Boolean? = null
, walls:Boolean? = null
, electric:Boolean? = null)
= FileModule.getDirectory(file.context
, FileModule.FileDirectory.PlanTypeDir(
estateId = estateId,
planId = planId,
furniture = furniture,
walls = walls,
sizes = sizes,
electric = electric
))
// override fun getEmptyPlan(estateId: Int
// ,planId:Int): Observable<String> =
// Observable.mergeDelayError(
// arrayListOf(getPlanApi(estateId,planId))
// )
override fun getPlan(featuresVariant: PlanFeaturesVariant)
= getPlan(
estateId = featuresVariant.estateId,
planId = featuresVariant.planId,
furniture = featuresVariant.furniture,
sizes = featuresVariant.sizes,
walls = featuresVariant.walls,
electric = featuresVariant.electric
)
override fun getPlan(estateId: Int
,planId:Int
, furniture:Boolean?
, sizes:Boolean?
, walls:Boolean?
, electric:Boolean?): Observable<String> =
Observable.fromCallable { getPlanFile(
estateId = estateId,
planId = planId,
furniture = furniture,
walls = walls,
sizes = sizes,
electric = electric) }.flatMap {
if (it.exists()) Observable.just(it.path)
else getPlanApi(estateId
,planId
, furniture
, sizes
, walls
, electric
)
}
// fun getPlanRequestString(estateId: Int
// , planId:Int
// , furniture:Boolean
// , sizes:Boolean
// , walls:Boolean
// , electric:Boolean
// ) = api.getDirectPlan(estateId,planId,furniture,sizes,electric).
}
private fun BitSet.getOrNull(index:Int) =
if (index<0 || index>=this.size()) null
else get(index)
private fun Int.getBit(index: Int): Boolean? {
if (index<0) return null
return Integer.toBinaryString(shr(index)).last()=='1'
}
val PlanPresetModel.featuresVariants : List<PlanFeaturesVariant>
get() {
if (this.features.isNullOrEmpty()) return arrayListOf()
val maxInd = this.features.size
val f = features.indexOfFirst { it is FeatureModel.Furniture }
val s = features.indexOfFirst { it is FeatureModel.Sizes }
val e = features.indexOfFirst { it is FeatureModel.Electric }
val w = features.indexOfFirst { it is FeatureModel.Walls }
val resList = mutableListOf<PlanFeaturesVariant>()
var increment = 0
var allTrue: Boolean
do{
val v = PlanFeaturesVariant(
estateId,planId,
furniture = increment.getBit(f),
sizes = increment.getBit(s),
electric = increment.getBit(e),
walls = increment.getBit(w)
)
allTrue = v.furniture?:true && v.electric?:true && v.sizes?:true && v.walls?:true
resList.add(v)
increment++
}while (!allTrue)
return resList
}
data class PlanFeaturesVariant(
val estateId: Int,
val planId:Int,
val furniture:Boolean?,
val sizes:Boolean?,
val walls:Boolean?,
val electric:Boolean?
)
\ No newline at end of file
package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi
import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.api.room_park.raw.SubscriptionStatusRaw
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.SubscriptionEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import com.biganto.visual.roompark.domain.contract.SubscriptionContract
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.model.fromEntity
import io.reactivex.Completable
import io.reactivex.Observable
import timber.log.Timber
......@@ -37,22 +40,96 @@ class SubscriptionRepository @Inject constructor(
subInnerId:Int?,
topic: String,
topic_id: String?,
nuewState:Boolean
newState:Boolean
): Observable<SubscriptionEntity> {
var sub = subInnerId?.let {id ->
userEntity.subscriptions?.firstOrNull { sub -> sub.id == id }
val userSubs = userEntity.subscriptions
val sub = subInnerId?.let { id ->
userSubs?.firstOrNull { sub -> sub.id == id } as SubscriptionEntity?
}?:SubscriptionEntity()
.apply {
setOwner(userEntity)
setTopic(topic)
setNumber(topic_id)
}
sub.setState(newState)
return db.saveSubscription(sub)
}
fun saveSubscribtions(
userEntity: UserEntity,
apiSubs: List<SubscriptionStatusRaw>
): Observable<List<SubscriptionModel>>? {
val userSubs = userEntity.subscriptions
val newSubList = mutableListOf<SubscriptionEntity>()
apiSubs.forEach { apiSub ->
val cachedSub =
userSubs?.firstOrNull { s ->
s.topic == apiSub.topic && s.number == apiSub.estate_id
}
as SubscriptionEntity?
?: SubscriptionEntity()
.apply {
setOwner(userEntity)
setTopic(apiSub.topic)
setNumber(apiSub.estate_id)
}
if (sub == null) {
sub = SubscriptionEntity()
sub.setOwner(userEntity)
sub.setTopic(topic)
sub.setNumber(topic_id)
cachedSub.setState(apiSub.active)
newSubList.add(cachedSub)
}
(sub as SubscriptionEntity).setState(nuewState)
return db.saveSubscription(sub).toObservable()
userSubs?.filter { !newSubList.map { s ->s.id }.contains(it.id) }?.forEach {
(it as SubscriptionEntity).setState(false)
}
userSubs?.forEach {s ->
if (newSubList.firstOrNull { s.topic == it.topic && s.number == it.number} == null)
newSubList.add(s as SubscriptionEntity)
}
return db.upsert(newSubList)
.map {list -> list.map {
fromEntity(
it as SubscriptionEntity)
}}
.toObservable()
}
override fun subscribeTopicResult(
user:UserEntity,
subInnerId:Int,
deviceToken: String,
topic: String,
topic_id: String?
): Observable<List<SubscriptionModel>> =
api.subscribeTopic(
userToken = user.authToken,
deviceToken = deviceToken
,topicName = topic
,topicId = topic_id)
.flatMap { saveSubscribtions(user,it.subscriptions?: arrayListOf()) }
.doOnNext { db.refreshUser(user).blockingFirst() }
override fun unSubscribeTopicResult(
user:UserEntity,
subInnerId:Int,
deviceToken: String,
topic: String,
topic_id: String?
): Observable<List<SubscriptionModel>> =
api.unSuubscribeTopic(
userToken = user.authToken,
deviceToken = deviceToken
,topicName = topic
,topicId = topic_id)
.flatMap { saveSubscribtions(user,it.subscriptions?: arrayListOf()) }
.doOnNext { db.refreshUser(user) }
override fun subscribeTopic(
user:UserEntity,
subInnerId:Int,
......@@ -60,14 +137,14 @@ class SubscriptionRepository @Inject constructor(
topic: String,
topic_id: String?
): Completable = api.subscribeTopic(
userToken = if (topic_id!=null) TEST_DEAL_TOKEN else user.authToken,
userToken = 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()
saveSubscribeState(user,subInnerId,topic,topic_id,true)
.ignoreElements()
}
else error("Error subscription state!")
}
......@@ -81,14 +158,15 @@ class SubscriptionRepository @Inject constructor(
topic_id: String?
): Completable =
api.unSuubscribeTopic(
userToken = if (topic_id!=null) TEST_DEAL_TOKEN else user.authToken,
userToken = user.authToken,
deviceToken = deviceToken
,topicName = topic
,topicId = topic_id)
.doOnNext { Timber.d("$it") }
.flatMapCompletable {
if (it.status == SUBSCRIPTION_RESULT_STATUS){
saveSubscribeState(user,subInnerId,topic,topic_id,false).ignoreElements()
saveSubscribeState(user,subInnerId,topic,topic_id,false)
.ignoreElements()
}
else error("Error subscription state!")
}
......
package com.biganto.visual.roompark.data.data_provider
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.domain.contract.TourContract
import com.biganto.visual.roompark.domain.model.AuthInfoModel
import io.reactivex.Completable
import io.reactivex.Observable
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
//@Singleton
class ToursRepository @Inject constructor(
private val files: FileModule,
private val db:IDb
): TourContract {
override fun getMultiTourId(building: Int, number: Int): Observable<AuthInfoModel> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getOffer(offerId: Int): Observable<AuthInfoModel> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun deleteToursDbInfo(): Completable =
Completable.merge(
arrayListOf(
db.dropTourFileJuncTable(),
db.dropFileTable(),
db.dropTourTable(),
db.refreshEstatesWithTours()
)
)
// .concatWith { }
.doOnComplete { Timber.w("Completed --") }
.doOnError { Timber.e(it) }
}
package com.biganto.visual.roompark.data.memcache
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
import com.jakewharton.rxrelay2.Relay
import com.jakewharton.rxrelay2.ReplayRelay
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
/**
* Created by Vladislav Bogdashkin on 19.10.2018.
*/
/**
* @param[maxSize] max entries count before automatic save && flush
* @param[flushInterval] time without any actions with cache in SECONDS before automatic save && flush
* */
abstract class EntityCache<K,V>
constructor(
private val maxSize: Int = 300,
private val flushInterval: Long = TimeUnit.MINUTES.toSeconds(1)
) : Cache<K,V> {
private val disposable = CompositeDisposable()
protected abstract fun saveDelegate (values:List<V>)
protected abstract fun readDelegate (key:K) : V?
protected abstract fun deleteDelegate (value:V)
protected val notifier: Relay<V> = ReplayRelay.create<V>().toSerialized()
init {
disposable.addAll(
Observable.interval(flushInterval, flushInterval, TimeUnit.SECONDS)
.doOnTerminate { clear() }
.doOnDispose { clear() }
.subscribe { clear() }
)
}
override fun contains(key: K): Boolean = keyMap.containsKey(key)
protected val locker = Any()
private var lastFlushTime = System.nanoTime()
private val keyMap = ConcurrentHashMap<K, V>()
override val size: Int
get() = keyMap.size
override val toList
get() = keyMap.toList()
fun deleteEntity(key: K){
synchronized(locker) {
deleteItem(key)
}
}
private fun deleteItem(key:K){
synchronized(locker) {
lastFlushTime = System.nanoTime()
var toDelete = keyMap.remove(key)
if (toDelete == null)
toDelete = readDelegate(key)
if (toDelete == null) return // -> нет в хранилище
deleteDelegate(value = toDelete)
}
}
fun deleteEntitys(keys: List<K>){
synchronized(locker) {
keys.forEach { deleteItem(it)}
}
}
override fun set(key: K, value: V?) {
synchronized(locker) {
lastFlushTime = System.nanoTime()
if (keyMap.size > maxSize)
clear()
value?.let {
keyMap[key]=it
notifier.accept(it)
}
}
}
override fun remove(key: K) = keyMap.remove(key)
override fun get(key: K): V? {
synchronized(locker) {
lastFlushTime = System.nanoTime()
return keyMap[key] ?: readDelegate(key)
}
}
override fun saveAll(){
synchronized(locker)
{
if (keyMap.size > 0) {
Timber.d("Going to save items: ${keyMap.values.size}")
saveDelegate(keyMap.values.toList())
}
}
}
override fun clear() {
if (keyMap.size == 0) return
synchronized(locker)
{
saveAll()
keyMap.clear()
}
}
override fun removeAll(): List<V>? {
val values= keyMap.values.toList()
keyMap.clear()
return values
}
private fun recycle() {
if (keyMap.size<=0) return
val shouldRecycle = System.nanoTime() - lastFlushTime >= TimeUnit.MILLISECONDS.toNanos(flushInterval)
if (!shouldRecycle) return
keyMap.clear()
}
}
interface Cache<K,V> {
val size: Int
val toList:List<Pair<K,V>>
operator fun set(key: K, value: V?)
fun contains(key: K): Boolean
operator fun get(key: K): V?
fun remove(key: K): V?
fun removeAll(): List<V>?
fun clear()
fun saveAll()
}
/**
* Suppress warning Leaking This as we sure to make singletone instance of object in single-thread
* more info and
* @see <a href="https://stackoverflow.com/questions/3921616/leaking-this-in-constructor-warning">discussion</a>
*/
@Suppress("LeakingThis")
abstract class LifeCycleCache<K,V>(size:Int, secondsToFlush:Long)
: EntityCache<K, V>(maxSize=size,flushInterval = secondsToFlush)
, LifecycleObserver
{
init {
ProcessLifecycleOwner.get().lifecycle
.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onAppPaused() {
saveAll()
}
}
package com.biganto.visual.roompark.data.repository.api.biganto
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourFilesDataRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourFilesSimpleDataRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourPreviewRaw
import io.reactivex.*
import io.reactivex.schedulers.Schedulers
import okhttp3.HttpUrl
import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.Retrofit
import timber.log.Timber
import timber.log.Timber.e
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
/**
* Created by Vladislav Bogdashkin on 13.06.2018.
*/
@Singleton
class BigantoRetrofitRepository @Inject constructor(@Named("bigantoApi") retrofit:Retrofit)
: IBigantoApi{
private val api = retrofit.create(IBigantoMobileApi::class.java)
override fun provideHttpUrl(): HttpUrl {
return HttpUrl.parse(IBigantoMobileApi.BASE_URL)!!
}
override fun getAppVersion() = api
.getAppVersion()
.compose(RetrofitResponseValidation())
override fun getToursPreviewById(tourIds: List<String>): Observable<List<TourPreviewRaw>> {
return api.getToursPreviewById(ids = tourIds.joinToString(","))
.compose(RetrofitResponseValidation())
.subscribeOn(Schedulers.io())
}
override fun getTourMetaAsString(tour_id: String): Observable<String> = api
.getTourMetaAsString(ids = tour_id)
.map { it.toString() }
.doOnError { e(it) }
.subscribeOn(Schedulers.io())
override fun getTourFiles(tour_id: String, resolution: String): Observable<List<TourFilesDataRaw>> = api
.getTourFiles(ids = tour_id, resolution = resolution)
.compose(RetrofitResponseValidation())
.doOnError { e(it) }
.subscribeOn(Schedulers.io())
override fun getTourFilesSizes(ids: List<String>, resolution: String)
: Observable<List<TourFilesSimpleDataRaw>> = api
.getTourFilesSize(ids = ids.joinToString(separator = ","), resolution = resolution)
.compose(RetrofitResponseValidation())
.doOnError { e(it) }
.subscribeOn(Schedulers.io())
override fun downloadFile(uri: String, headers: Map<String, String>?): Flowable<ResponseBody> = api
.requestFile(headers ?: HashMap<String, String>(), uri)
.compose(FlowableRetrofitResponseValidation())
.doOnError(::e)
override fun getOfferTours(multiTourIds:List<Int>): Observable<List<TourPreviewRaw>> =
api
.getOfferTours(offerId = multiTourIds.joinToString(separator = ","))
.compose(RetrofitResponseValidation())
.map { it.values.flatten() }
.doOnError { e(it) }
.subscribeOn(Schedulers.io())
override fun getOfferTours(multiTourId:Int): Observable<List<TourPreviewRaw>> =
api
.getOfferTours(offerId = multiTourId.toString())
.compose(RetrofitResponseValidation())
.map { it[multiTourId.toString()]?.toList()?: error("No tours avaliable")}
.doOnError { e(it) }
.subscribeOn(Schedulers.io())
}
internal class RetrofitResponseValidation<T> : ObservableTransformer<Response<T>, T> {
override fun apply(responseObservable: Observable<Response<T>>): ObservableSource<T> {
return responseObservable.switchMap { resp ->
// Timber.d("reutrned code: %s",resp.code())
if (resp.code() == 200 || resp.code() == 206)
if (resp.body() == null)
return@switchMap Completable.complete().toObservable<T>().doOnNext{Timber.d("Completed responseBody")}
else return@switchMap Observable.just(resp.body())
Timber.d("Returning Exception!")
return@switchMap Observable.error<T>(retrofit2.HttpException(resp))
}
}
}
internal class RetrofitResponseValidationString<String> : ObservableTransformer<Response<String>, String> {
override fun apply(responseObservable: Observable<Response<String>>): ObservableSource<String> {
return responseObservable.switchMap<String> { resp ->
// Timber.d("reutrned code: %s",resp.code())
if (resp.code() == 200)
if (resp.body() == null)
return@switchMap Completable.complete().toObservable<String>().doOnNext{Timber.d("Completed responseBody")}
else return@switchMap Observable.just<String>(resp.body())
Timber.d("Returning Exception!")
return@switchMap Observable.error<String>(retrofit2.HttpException(resp))
}
}
}
internal class FlowableRetrofitResponseValidation<T> : FlowableTransformer<Response<T>, T> {
override fun apply(responseObservable: Flowable<Response<T>>): Flowable<T> {
return responseObservable.switchMap { resp ->
if (resp.code() == 200 || resp.code() == 206)
if (resp.body() == null)
return@switchMap Completable.complete().toFlowable<T>().doOnNext{Timber.d("Completed responseBody")}
else return@switchMap Flowable.just(resp.body())
Timber.d("Returning Exception!")
return@switchMap Flowable.error<T>(retrofit2.HttpException(resp))
}
}
}
//Allow to override HttpException with custom exception output, example:
//sealed class CustomApiException(val code:Int,val biganto_message:String):RuntimeException()
//{
// class RedirectException():CustomApiException(code = 300,biganto_message = "Redirect page..")
// class WrongAuthDataException():CustomApiException(code = 401,biganto_message = "Login or password not allowed")
// class BlockedUserException():CustomApiException(code = 403,biganto_message = "User has been banned")
// class ServerException():CustomApiException(code = 500,biganto_message = "Error server: undefined")
//
//}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.biganto
import com.biganto.visual.roompark.data.repository.api.biganto.raw.AppVersionRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourFilesDataRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourFilesSimpleDataRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourPreviewRaw
import io.reactivex.Flowable
import io.reactivex.Observable
import okhttp3.HttpUrl
import okhttp3.ResponseBody
/**
* Created by Vladislav Bogdashkin on 13.06.2018.
*/
interface IBigantoApi {
fun provideHttpUrl():HttpUrl
fun downloadFile(uri: String, headers: Map<String, String>?): Flowable<ResponseBody>
// fun getToursFiles(tour_ids: List<Int>, resolution: String): Flowable<Map<String, List<TourFileRaw>>>?
fun getTourMetaAsString(tour_id: String): Observable<String>
fun getTourFiles(tour_id: String, resolution: String): Observable<List<TourFilesDataRaw>>
fun getAppVersion(): Observable<AppVersionRaw>
fun getToursPreviewById(tourIds: List<String>): Observable<List<TourPreviewRaw>>
fun getOfferTours(multiTourId:Int): Observable<List<TourPreviewRaw>>
fun getOfferTours(multiTourIds: List<Int>): Observable<List<TourPreviewRaw>>
fun getTourFilesSizes(
ids: List<String>,
resolution: String
): Observable<List<TourFilesSimpleDataRaw>>
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.biganto
import com.biganto.visual.roompark.data.repository.api.biganto.raw.AppVersionRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourFilesDataRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourFilesSimpleDataRaw
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourPreviewRaw
import com.google.gson.JsonArray
import io.reactivex.Flowable
import io.reactivex.Observable
import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.http.*
import java.util.*
/**
* Created by Vladislav Bogdashkin on 13.06.2018.
*/
interface IBigantoMobileApi {
companion object{
//const val BASE_URL="http://local.biganto.com"
const val BASE_URL="https://biganto.com"
//const val API_URL="api-novus/"
const val API_URL="api/"
const val DELIMITER="?"
const val CLIENT_TYPE_PARAM="client"
const val CLIENT_VERSION_PARAM="client_version"
const val API_VERSION_PARAM="v"
const val AUTH_TOKEN="auth_token"
const val LANG_PARAM="lang"
const val DEFAULT_LANG_VALUE="en"
const val DEFAULT_CLIENT_TYPE="androidplayer"
//const val DEFAULT_CLIENT_TYPE="iosplayer"
const val OLD_CLIENT_TYPE="mobileplayer"
const val DEFAULT_CLIENT_VERSION="3.0"
const val DEFAULT_API_VERSION="2.0"
//region AppInfo
const val GET_APP_VERSION="misc.appVersion"
//endregion
//region Authentication
const val AUTH_METHOD="users/login/"
const val AUTH_METHOD_2_0="users.authorize"
const val AUTH_LOGIN_PARAM="email"
const val AUTH_PASSWORD_PARAM="password"
//endregion
//region GetEstates
const val GET_ESTATES_METHOD="estates.getList"
//endregion
//region Portfolio
const val GET_PORTFOLIO_ESTATES_METHOD="portfolio.getEstates"
const val GET_PORTFOLIO_TOURS_METHOD="portfolio.getTours"
//endregion
//region GetToursPreview
const val GET_TOURS_METHOD="tours.getBadges"//"tours.getList"
//const val PARENT_ESTATE_PARAM="id"
const val PARENT_ESTATE_PARAM="estate_id"
const val TOURS_BY_ID_PARAM="id"
const val TOURS_TYPES_PARAM="types"
const val DEFAULT_SUPPORTED_TOURS_TYPES = "virtual,real"
//endregion
//region GetToursMeta
const val GET_TOURS_META_METHOD="tours.getMeta"
const val GET_TOURS_META_ID="id"
//endregion
//region GetFiles
const val GET_TOURS_FILES_METHOD="tours.getFiles"
const val GET_TOURS_FILES_ID="id"
const val GET_TOURS_FILES_RESOLUTION="resolution"
//endregion
//region offers.GetTours
const val OFFER_GET_TOURS_METHOD="offers.getTours"
const val OFFER_GET_TOURS_ID="id"
//endregion
}
@GET("$API_URL$GET_TOURS_METHOD$DELIMITER")
fun getToursPreviewById(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
@Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION,
@Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION,
@Query(LANG_PARAM) languageCode: String = Locale.getDefault().language,
@Query(TOURS_TYPES_PARAM) toursTypes: String = DEFAULT_SUPPORTED_TOURS_TYPES,
@Query(TOURS_BY_ID_PARAM) ids: String
): Observable<Response<List<TourPreviewRaw>>>
@GET("$API_URL$GET_TOURS_META_METHOD$DELIMITER")
fun getTourMetaAsString(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
@Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION,
@Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION,
@Query(LANG_PARAM) languageCode: String = Locale.getDefault().language,
@Query(GET_TOURS_META_ID) ids: String
): Observable<JsonArray>
@GET("$API_URL$GET_TOURS_FILES_METHOD$DELIMITER")
fun getTourFiles(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
@Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION,
@Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION,
@Query(LANG_PARAM) languageCode: String = Locale.getDefault().language,
@Query(GET_TOURS_FILES_ID) ids: String,
@Query(GET_TOURS_FILES_RESOLUTION) resolution: String
): Observable<Response<List<TourFilesDataRaw>>>
@GET("$API_URL$GET_TOURS_FILES_METHOD$DELIMITER")
fun getTourFilesSize(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
@Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION,
@Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION,
@Query(LANG_PARAM) languageCode: String = Locale.getDefault().language,
@Query(GET_TOURS_FILES_ID) ids: String,
@Query(GET_TOURS_FILES_RESOLUTION) resolution: String
): Observable<Response<List<TourFilesSimpleDataRaw>>>
@GET("$API_URL$GET_APP_VERSION$DELIMITER")
fun getAppVersion(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
@Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION,
@Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION,
@Query(LANG_PARAM) languageCode: String = Locale.getDefault().language
): Observable<Response<AppVersionRaw>>
@GET("$API_URL$OFFER_GET_TOURS_METHOD$DELIMITER")
fun getOfferTours(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
@Query(CLIENT_VERSION_PARAM) clientVersion: String = DEFAULT_CLIENT_VERSION,
@Query(API_VERSION_PARAM) apiVersion: String = DEFAULT_API_VERSION,
@Query(LANG_PARAM) languageCode: String = Locale.getDefault().language,
@Query(OFFER_GET_TOURS_ID) offerId: String
): Observable<Response<Map<String,List<TourPreviewRaw>>>>
@Streaming
@GET
fun requestFile(
@HeaderMap headers: Map<String, String>?,
@Url fileUrl: String
): Flowable<Response<ResponseBody>>
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.biganto.raw
import java.util.*
/**
* Created by Vladislav Bogdashkin on 09.06.2018.
*/
data class ErrorRaw(
val code:Int,
val message:String
)
data class EstatesRaw(
val estates:List<EstateRaw>,
val errors:List<ErrorRaw>?
)
data class EstatesRoomParkRaw(
val estates:Map<String, EstateRoomParkRaw>,
val errors:List<ErrorRaw>?
)
data class EstateRoomParkRaw(
val avaliable:Boolean,
val long: String, //->long name
val short: String //->short name
)
data class TourOverviewsRoomParkRaw(
val tours:List<TourOverviewRoomParkRaw>,
val errors:List<ErrorRaw>?
)
data class TourOverviewRoomParkRaw(
val hidden:Boolean,
val id:String,
val preview: String,
val title: String,
val url:String
)
data class EstateRaw(
val id:Int,
val created:Date,
val title:String,
val preview:String?,
val cnt_tours:Int,
val errors:List<ErrorRaw>?
)
data class ToursPreviewRaw(
val list:List<TourPreviewRaw>,
val errors:List<ErrorRaw>?
)
data class TourPreviewRaw(
val id:Int,
val created:Date,
val updated:Date,
val type:String,
val title:String,
val preview:String,
val screen:String,
val hidden:Boolean,
val resolutions:List<Int>?,
val features:List<String>?,
val errors:List<ErrorRaw>?
)
data class TourFileRaw(
val size:Long,
val url:String,
val errors:List<ErrorRaw>?
)
data class TourFilesSimpleDataRaw(
val id:Int,
val resolution:Int,
val total_size:Long,
val errors:List<ErrorRaw>?
)
data class TourFilesDataRaw(
val files:List<TourFileRaw>,
val id:Int,
val resolution:Int,
val total_size:Long,
val errors:List<ErrorRaw>?
)
data class TourGetFilesRaw(
val data: Map<String,List<TourFileRaw>>,
val errors:List<ErrorRaw>?
)
data class OfferTours(
val data: Map<String,List<TourPreviewRaw>>,
val errors:List<ErrorRaw>?
)
data class AppVersionRaw(
val current_version : String,
val download_url : String?,
val message : String?,
val critical : Boolean,
val errors : List<ErrorRaw>?
)
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.retrofit.di
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.biganto.IBigantoMobileApi
import com.biganto.visual.roompark.data.repository.api.retrofit.util.*
import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkMobileApi
import com.biganto.visual.roompark.data.repository.api.room_park.raw.ErrorRaw
import com.biganto.visual.roompark.data.service.network.INetworkMonitor
import com.biganto.visual.roompark.data.service.network.LiveNetworkMonitor
import com.biganto.visual.roompark.data.service.network.NoNetworkException
......@@ -22,6 +23,7 @@ import retrofit2.converter.scalars.ScalarsConverterFactory
import timber.log.Timber
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Named
import javax.inject.Singleton
/**
......@@ -34,7 +36,7 @@ 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
val INTERCEPT_LOG_LEVEL = HttpLoggingInterceptor.Level.NONE
@Module
class RetrofitModule{
......@@ -102,7 +104,7 @@ class RetrofitModule{
@Provides
@Singleton
// @Named("roomParkApi")
@Named("roomParkApi")
fun provideRetrofitRoomParkApi(context: Application): Retrofit =
Retrofit.Builder()
.baseUrl(IRoomParkMobileApi.BASE_URL)
......@@ -113,4 +115,18 @@ class RetrofitModule{
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
@Provides
@Singleton
@Named("bigantoApi")
fun provideRetrofitBigantoApi(context: Application): Retrofit =
Retrofit.Builder()
.baseUrl(IBigantoMobileApi.BASE_URL)
.client(client(AppVersionManager(context),LiveNetworkMonitor(context)))
.addConverterFactory(NullOnEmptyConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(gsonConverterFactory())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.retrofit.util
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.ErrorRaw
import com.biganto.visual.roompark.data.repository.api.room_park.raw.ErrorRaw
import com.biganto.visual.roompark.domain.custom_exception.parseException
import com.google.gson.*
import timber.log.Timber
......
package com.biganto.visual.roompark.data.repository.api.retrofit.util
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.ErrorRaw
import com.biganto.visual.roompark.data.repository.api.room_park.raw.ErrorRaw
import com.biganto.visual.roompark.domain.custom_exception.parseException
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
......
package com.biganto.visual.roompark.data.repository.api
package com.biganto.visual.roompark.data.repository.api.room_park
import com.biganto.visual.roompark.data.repository.api.retrofit.DEFAULT_ARTICLE_PAGE_SIZE
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.*
import com.biganto.visual.roompark.data.repository.api.room_park.raw.*
import io.reactivex.Observable
/**
......@@ -51,6 +50,3 @@ interface IRoomParkApi {
fun getWebCamsList(): Observable<List<WebCamRaw>>
}
interface IBigantoApi {
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.api.retrofit
package com.biganto.visual.roompark.data.repository.api.room_park
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.*
import com.biganto.visual.roompark.data.repository.api.room_park.raw.*
import io.reactivex.Observable
import retrofit2.Response
import retrofit2.http.*
......@@ -142,7 +142,7 @@ interface IRoomParkMobileApi{
@Field(PASSWORD_AUTH_PARAM) pwd: String
): Observable<Response<AuthRaw>>
@POST("$API_URL${SUBSCRIBE_METHOD}$DELIMITER")
@POST("$API_URL$SUBSCRIBE_METHOD$DELIMITER")
@FormUrlEncoded
fun subscribe(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
......@@ -154,7 +154,7 @@ interface IRoomParkMobileApi{
@Field(TOPIC_SUBSCRIBTION_TOPIC_ID_PARAM) estateId: String?
): Observable<Response<StatusResponse>>
@POST("$API_URL${UNSUBSCRIBE_METHOD}$DELIMITER")
@POST("$API_URL$UNSUBSCRIBE_METHOD$DELIMITER")
@FormUrlEncoded
fun unsubscribe(
@Query(CLIENT_TYPE_PARAM) clientType: String = DEFAULT_CLIENT_TYPE,
......
package com.biganto.visual.roompark.data.repository.api.retrofit
package com.biganto.visual.roompark.data.repository.api.room_park
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.*
import com.biganto.visual.roompark.data.repository.api.room_park.raw.*
import com.biganto.visual.roompark.util.extensions.asInt
import io.reactivex.Completable
import io.reactivex.Observable
......@@ -12,6 +11,7 @@ import retrofit2.Response
import retrofit2.Retrofit
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
......@@ -19,7 +19,8 @@ import javax.inject.Inject
const val DEFAULT_ARTICLE_PAGE_SIZE = 10
class RetrofitRepository @Inject constructor(retrofit: Retrofit) : IRoomParkApi {
class RetrofitRepository @Inject constructor(@Named("roomParkApi") retrofit: Retrofit) :
IRoomParkApi {
private val api = retrofit.create(IRoomParkMobileApi::class.java)
......
package com.biganto.visual.roompark.data.repository.api.retrofit.raw
package com.biganto.visual.roompark.data.repository.api.room_park.raw
import com.google.gson.annotations.Expose
import java.util.*
/**
......@@ -13,7 +14,17 @@ data class AuthRaw(
val name:String
)
data class StatusResponse(val status:String)
data class StatusResponse(
val status:String,
val subscriptions:List<SubscriptionStatusRaw>?
)
data class SubscriptionStatusRaw(
val topic:String,
val estate_id: String,
@Expose
val active: Boolean = true
)
data class DealRaw(
val id:String,
......@@ -30,6 +41,7 @@ data class EstateRaw(
val id:Int,
val type:String,
val number:String,
val available:Boolean,
val common_info: CommonInfoRaw,
val plan_png:PlanRaw?,
val plan_jpg:PlanRaw?,
......
package com.biganto.visual.roompark.data.repository.db
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.model.*
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.Observable
import io.reactivex.Single
import io.requery.Persistable
import io.requery.reactivex.ReactiveResult
import io.requery.reactivex.ReactiveScalar
/**
* Created by Vladislav Bogdashkin on 29.10.2019.
*/
interface IDb {
fun upsertUser(entity: UserEntity): Observable<UserEntity>?
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>
......@@ -27,13 +32,55 @@ interface IDb {
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 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 saveSubscription(subscription: SubscriptionEntity): Observable<SubscriptionEntity>
fun getSubscription(id: Int): ReactiveResult<SubscriptionEntity>
fun deleteTourPreview(id: String): Int?
fun deleteTourPreview(entity: TourPreviewEntity)
fun deleteFiles(entity: List<FileEntity>)
fun deleteFile(entity: FileEntity)
fun deleteTourFilesJunction(tourId: String): ReactiveScalar<Int>
fun deleteTourFilesJunction(entity: List<TourFileJunctionEntity>)
fun getTourFilesJunctionUniqueFiles(tourId: String): List<TourFileJunctionEntity?>
fun getTourFilesJunction(tourId: String): ReactiveResult<TourFileJunctionEntity>
fun upsertFileEntity(entity: List<FileEntity>): Observable<Iterable<FileEntity>>
fun upsertFileEntity(entity: FileEntity): Observable<FileEntity>
fun flowableFileEntityes(uris: List<RevisionString>): Flowable<FileEntity>
fun getDownloadedSumFileEntityes(uris: List<RevisionString>): Long
fun fetchTouresWithStates(states: List<DownloadState>): MutableList<TourPreviewEntity>
fun fetchFileEntityes(uris: List<RevisionString>): MutableList<FileEntity>
fun pushFileEntities(uris: List<FileEntity>): Observable<Iterable<FileEntity>>
fun getFileEntity(uri: RevisionString): ReactiveResult<FileEntity>
fun getTourFiles(entity: TourPreviewEntity): MutableList<TourFileJunctionEntity>
fun getTourFiles(tourId: String): MutableList<TourFileJunctionEntity>
fun getTourFilesObservable(tourId: String): ReactiveResult<TourFileJunctionEntity>
fun upsertTourPreview(list: List<TourPreviewEntity>): Observable<Iterable<TourPreviewEntity>>
fun upsertTourPreview(entity: TourPreviewEntity): Observable<TourPreviewEntity>
fun upsertTourFileJunction(entity: List<TourFileJunctionEntity>): Observable<Iterable<TourFileJunctionEntity>>
fun upsertTourFileJunction(entity: TourFileJunctionEntity): Observable<TourFileJunctionEntity>
fun observableTourDownloadState(): Flowable<TourPreviewEntity>
fun getFileEntitysOrDefault(uri: RevisionString): FileEntity
fun getTourPreview(tourId: String): ReactiveResult<TourPreviewEntity>
fun getTourPreviewsObservableResult(estateId: Int): Observable<ReactiveResult<TourPreviewEntity>>
fun getEstateTourPreviews(estateId: Int): Observable<List<TourPreviewEntity>>
fun dropFileTable(): Completable
fun dropTourFileJuncTable(): Completable
fun dropTourTable(): Completable
fun refreshUser(userEntity: UserEntity): Observable<UserEntity>
fun refreshEntities(entities: List<Persistable>): Observable<Iterable<Persistable>>
fun refreshEstatesWithTours(): Completable
fun upsertEstates(entity: List<EstateEntity>): Observable<Iterable<EstateEntity>>?
fun upsertDeals(entity: List<DealEntity>): Observable<Iterable<DealEntity>>
fun deleteSubscriptions(entities: List<Subscription>)
fun deleteEstate(entity: List<EstateEntity>)
fun deleteEstate(entity: EstateEntity)
fun getUserDeals(uuid: Int): Observable<DealEntity?>
fun deleteDeal(entity: DealEntity)
fun deleteDeal(entity: List<DealEntity>)
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey
import android.app.Application
import com.biganto.visual.roompark.Models
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.*
import com.biganto.visual.roompark.di.dagger.DATABASE_VERSION
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreview
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import dagger.Module
import dagger.Provides
import io.reactivex.BackpressureStrategy
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.rxkotlin.toCompletable
import io.reactivex.schedulers.Schedulers
import io.requery.Persistable
import io.requery.android.sqlite.DatabaseSource
import io.requery.kotlin.notNull
import io.requery.reactivex.KotlinReactiveEntityStore
import io.requery.reactivex.ReactiveResult
import io.requery.sql.KotlinEntityDataStore
......@@ -23,6 +30,8 @@ import javax.inject.Inject
*/
private const val DATABASE_VERSION = 15
@Module
class DbModule{
......@@ -45,13 +54,34 @@ class RequeryRepository @Inject constructor(
)
: IDb {
override fun refreshUser(userEntity: UserEntity): Observable<UserEntity> =
store.refresh(userEntity).toObservable()
override fun refreshEstatesWithTours(): Completable =
store.select(EstateEntity::class)
.where(EstateEntity::multitourId.notNull())
.get()
.observable()
.toList()
.toObservable()
.doOnNext { Timber.d("bgg ${it.size}") }
.flatMapCompletable{ store.refresh(it).ignoreElement()}
override fun refreshEntities(entities:List<Persistable>): Observable<Iterable<Persistable>>
= store.refresh(entities).toObservable()
override fun dropTourTable() = store.delete(TourPreviewEntity::class).get().toCompletable()
override fun dropTourFileJuncTable() = store.delete(TourFileJunctionEntity::class).get().toCompletable()
override fun dropFileTable() = store.delete(FileEntity::class).get().toCompletable()
private inline fun <reified T : Persistable> fetchAll() =
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>> =
......@@ -60,6 +90,12 @@ class RequeryRepository @Inject constructor(
override fun <T : List<Persistable>> blockingUpsert(entity: T) =
store.upsert(entity).toObservable().blockingSubscribe()
override fun upsertDeals(entity:List<DealEntity>): Observable<Iterable<DealEntity>> =
store.upsert(entity).toObservable()
override fun upsertEstates(entity:List<EstateEntity>): Observable<Iterable<EstateEntity>> =
store.upsert(entity).toObservable()
override fun upsertEstate(entity:EstateEntity) =
store.upsert(entity).toObservable().blockingSubscribe()
......@@ -154,12 +190,17 @@ class RequeryRepository @Inject constructor(
.get()
.observableResult()
override fun getUserFavorites(uuid: Int): Observable<EstateEntity> =
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 getUserDeals(uuid: Int): Observable<DealEntity?> =
store.select(DealEntity::class)
.where(DealEntity.USER_ID.eq(uuid))
.get().observable()
override fun setArticleReadState(id:Int,state:Boolean): Completable =
store.update(ArticleEntity::class)
.set(ArticleEntity.READ,state)
......@@ -177,12 +218,226 @@ class RequeryRepository @Inject constructor(
.single()
.ignoreElement()
override fun saveSubscription(subscription:SubscriptionEntity) =
store.upsert(subscription)
override fun saveSubscription(subscription:SubscriptionEntity)
: Observable<SubscriptionEntity> = store.upsert(subscription)
.toObservable()
.doOnNext { store.refresh(subscription.owner).blockingGet() }
override fun getSubscription(id:Int): ReactiveResult<SubscriptionEntity> =
store.select(SubscriptionEntity::class)
.where(SubscriptionEntity.ID.eq(id))
.get()
///region tours files
override fun getTourPreviewsObservableResult(estateId: Int) = store
.select(TourPreviewEntity::class)
// .join(UserEntity::class).on(UserEntity::uuid.eq(estateId))
.where(TourPreviewEntity.ESTATE_ID.eq(estateId))
.get()
.observableResult()
override fun getTourPreview(tourId: String) = store
.select(TourPreviewEntity::class)
.where(TourPreviewEntity.ID.eq(tourId))
.get()
override fun getEstateTourPreviews(estateId: Int): Observable<List<TourPreviewEntity>> = store
.select(TourPreviewEntity::class)
.where(TourPreviewEntity.ESTATE_ID.eq(estateId))
.get()
.observable()
.toList()
.toObservable()
.map { it.toList() }
override fun upsertTourPreview(list: List<TourPreviewEntity>) = store
.upsert(list)
.doOnSuccess { Timber.d("Upsertd succses %s", it.count()) }
.toObservable()
override fun upsertTourPreview(entity: TourPreviewEntity) = store
.upsert(entity)
.toObservable()
override fun upsertTourFileJunction(entity: List<TourFileJunctionEntity>)
: Observable<Iterable<TourFileJunctionEntity>> = store.upsert(entity).toObservable()
override fun upsertTourFileJunction(entity: TourFileJunctionEntity) = store
.upsert(entity)
.toObservable()
override fun observableTourDownloadState() = store
.select(TourPreviewEntity::class)
.where(TourPreviewEntity.DOWNLOADED.notIn(arrayListOf(DownloadState.NotDownloaded, DownloadState.MetaPreparation)))
.get()
// .flowable()
// .
.observableResult()
.flatMap { it.observable() }
.toFlowable(BackpressureStrategy.BUFFER)
.subscribeOn(Schedulers.io())
override fun getFileEntitysOrDefault(uri: RevisionString) = store
.select(FileEntity::class)
.where(FileEntity.URI.eq(uri))
.get()
.firstOr {
val entity = FileEntity()
entity.setUri(uri)
entity.setDownloadedSize(0)
entity.setTotalSize(0)
entity.setDownloaded(false)
entity
}
override fun getFileEntity(uri: RevisionString) = store
.select(FileEntity::class)
.where(FileEntity.URI.eq(uri))
.get()
override fun getTourFiles(entity : TourPreviewEntity ) =store
.select(TourFileJunctionEntity::class)
.where(TourFileJunctionEntity.TOUR.eq(entity.id))
.get()
.toList()
override fun getTourFiles(tourId : String) =store
.select(TourFileJunctionEntity::class)
.where(TourFileJunctionEntity.TOUR.eq(tourId))
.get()
.toList()
override fun getTourFilesObservable(tourId : String) =store
.select(TourFileJunctionEntity::class)
.where(TourFileJunctionEntity.TOUR.eq(tourId))
.get()
override fun pushFileEntities(uris: List<FileEntity>) = store
.select(FileEntity::class)
.where(FileEntity.URI.`in`(uris.map { it.uri }.toList()))
.get()
.observable()
.toList()
.map {existedUris-> uris.filter{!existedUris.map {file -> file.uri.revisionUri() }.contains(it.uri.revisionUri()) }.toList()}
.flatMapObservable{ store.insert(it).toObservable() }
override fun fetchFileEntityes(uris: List<RevisionString>) = store
.select(FileEntity::class)
.where(FileEntity.URI.`in`(uris))
.get()
.toList()
override fun fetchTouresWithStates(states: List<DownloadState>) = store
.select(TourPreviewEntity::class)
.where(TourPreviewEntity.DOWNLOADED.`in`(states))
.get()
.toList()
override fun getDownloadedSumFileEntityes(uris: List<RevisionString>) = store
.select(FileEntity::class)
.where(FileEntity.URI.`in`(uris))
.get()
.map{ it.downloadedSize }
.sum()
// .observable()
override fun flowableFileEntityes(uris: List<RevisionString>) = store
.select(FileEntity::class)
.where(FileEntity.URI.`in`(uris))
.get()
.flowable()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
override fun upsertFileEntity(entity: FileEntity) = store
.upsert(entity)
.toObservable()
override fun upsertFileEntity(entity: List<FileEntity>) = store
.upsert(entity)
.toObservable()
override fun getTourFilesJunction(tourId:String) = store
.select(TourFileJunctionEntity::class)
.where(TourFileJunctionEntity.TOUR.eq(tourId))
.get()
override fun getTourFilesJunctionUniqueFiles(tourId:String):
List<TourFileJunctionEntity?> =
store
.raw(TourFileJunctionEntity::class,
"SELECT tfj.* from TourFileJunction tfj " +
"join TourFileJunction tfj2 on tfj2.file = tfj.File " +
"WHERE tfj2.tour = $tourId " +
"GROUP BY tfj.file " +
"HAVING COUNT(tfj.file) < 2"
)
.toList()
private fun <E:Persistable> deleteBlocking(entity:E) = store
.delete(entity)
.blockingAwait()
private fun <E:List<Persistable>> deleteBlocking(entity:E) = store
.delete(entity)
.blockingAwait()
override fun deleteTourFilesJunction(entity: List<TourFileJunctionEntity>) =
deleteBlocking(entity)
override fun deleteTourFilesJunction(tourId:String) = store
.delete(TourFileJunctionEntity::class)
.where(TourFileJunctionEntity.TOUR.eq(tourId))
.get()
override fun deleteEstate(entity:EstateEntity) = deleteBlocking(entity)
override fun deleteDeal(entity:DealEntity) = deleteBlocking(entity)
override fun deleteEstate(entity:List<EstateEntity>) = deleteBlocking(entity)
override fun deleteDeal(entity:List<DealEntity>) = deleteBlocking(entity)
override fun deleteFile(entity:FileEntity) = deleteBlocking(entity)
override fun deleteFiles(entity:List<FileEntity>) = deleteBlocking(entity)
override fun deleteTourPreview(entity: TourPreviewEntity) = deleteBlocking(entity)
override fun deleteSubscriptions(entities: List<Subscription>) =
deleteBlocking(entities)
override fun deleteTourPreview(id:String) = store
.delete(TourPreview::class)
.where(TourPreviewEntity.ID.eq(id))
.get()
.single()
.blockingGet()
///endregion
}
package com.biganto.visual.roompark.data.repository.db.requrey.model
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreview
import io.requery.*
/**
......@@ -14,6 +15,7 @@ interface Estate : Persistable {
val id: Int
val type: String
val number: String
val available: Boolean
@get:Nullable
val sectionBegin: Int?
@get:Nullable
......@@ -38,6 +40,11 @@ interface Estate : Persistable {
@get:Nullable
val multitourId: Int?
@get:Nullable
@get:OneToMany(mappedBy = "estate")
val tours: Set<TourPreview>?
@get:Nullable
val multitourPreview: String?
......@@ -73,6 +80,6 @@ interface Estate : Persistable {
@get:Nullable
@get:Column(name = "UserContainer")
@get:ForeignKey(references = User::class )
@get:OneToOne(mappedBy = "uuid",cascade = [CascadeAction.NONE])
@get:ManyToOne(cascade = [CascadeAction.NONE])
var user:User?
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey.model
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourFileRaw
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.utils.RevisionStringConverter
import io.requery.*
/**
* Created by Vladislav Bogdashkin on 15.06.2018.
*/
@Entity
interface File :Persistable {
// @get:Key
// var id: String
// @get:Convert(URIConverter::class)
@get:Key
@get:Convert(RevisionStringConverter::class)
val uri: RevisionString
val totalSize: Long
val downloadedSize: Long
@get:Nullable
val isDownloaded: Boolean
// @get:ForeignKey(references = Skybox::class)
// @get:ManyToOne(cascade = arrayOf(CascadeAction.NONE))
// var skyboxId:Skybox?
}
fun fromRaw(raw: TourFileRaw):FileEntity {
val entity = FileEntity()
entity.setDownloaded(false)
entity.setDownloadedSize(0L)
entity.setTotalSize(raw.size)
entity.setUri(RevisionString(raw.url.substring(raw.url.indexOf("asset"))))
return entity
}
fun fromRaw(raw: List<TourFileRaw>): List<FileEntity> =List(raw.size) { index-> fromRaw(raw[index]) }
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey.model
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.utils.RevisionStringConverter
import io.requery.*
/**
* Created by Vladislav Bogdashkin on 15.06.2018.
*/
@Entity
interface TourFileJunction :Persistable {
@get:Key
@get:Generated
val id: Int
// @get:OneToOne( cascade = [CascadeAction.NONE])
// val tour: TourPreview
//+@get:ForeignKey(delete = ReferentialAction.NO_ACTION, update = ReferentialAction.NO_ACTION, references = TourPreviewEntity::class)
val tour: String
// @get:OneToOne( cascade = [CascadeAction.NONE])
// val file: File
//@get:ForeignKey(delete = ReferentialAction.NO_ACTION, update = ReferentialAction.NO_ACTION, references = FileEntity::class)
@get:Convert(RevisionStringConverter::class)
val file: RevisionString
}
package com.biganto.visual.roomparkvr.data.repository.db.requery.model
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourPreviewRaw
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.model.Estate
import com.biganto.visual.roompark.data.repository.db.requrey.model.EstateEntity
import com.biganto.visual.roompark.data.repository.db.requrey.utils.IntListConverter
import com.biganto.visual.roompark.data.repository.db.requrey.utils.IsoDateConverter
import com.biganto.visual.roompark.data.repository.db.requrey.utils.RevisionStringConverter
import io.requery.*
import java.util.*
import kotlin.collections.ArrayList
import kotlin.math.abs
/**
* Created by Vladislav Bogdashkin on 15.06.2018.
*/
@Entity
interface TourPreview :Persistable {
@get:Key
val id: String
@get:ForeignKey(references = Estate::class)
@get:ManyToOne
var estate: Estate
//Obsolete
// @get:ForeignKey(references = Tour::class)
// @get:OneToOne(mappedBy = "id", cascade = arrayOf(CascadeAction.SAVE))
// var tour: Tour?
@get:Convert(IsoDateConverter::class)
val created: Date
@get:Convert(IsoDateConverter::class)
val updated: Date
val type: String
val screen: String
val title: String
val preview: String?
val hidden: Boolean
var isDownloaded: DownloadState
var overallSize:Long
var tempSize:Long
var downloadedSize:Long
var overallFiles:Int
var downloadedFiles:Int
var targetResolution : Int
@get:Convert(IntListConverter::class)
var resolutions:ArrayList<Int>
// @get:JunctionTable(name= "TourFilesRule")
// @get:ManyToMany( cascade = arrayOf(CascadeAction.NONE))
// @get:Convert(RevisionStringListConverter::class)
// var tourFiles: ArrayList<RevisionString>?
//
@get:Convert(RevisionStringConverter::class)
val metaFileEntityId: RevisionString?
}
fun fromRaw(raw: TourPreviewRaw, parentId:Int, baseUrl:String, userResolution:Int):TourPreviewEntity{
val tour=TourPreviewEntity()
tour.setId(raw.id.toString())
tour.setCreated(raw.created)
tour.setUpdated(raw.updated)
tour.setType(raw.type)
tour.setTitle(raw.title)
tour.setPreview("$baseUrl${raw.preview}")
tour.setScreen("$baseUrl${raw.screen}")
tour.setHidden(raw.hidden)
tour.resolutions= if (raw.resolutions != null && !raw.resolutions.isNullOrEmpty()) ArrayList(raw.resolutions)
else ArrayList(1024)
tour.targetResolution=raw.resolutions?.nearestResolution(userResolution)?:1024
val estate= EstateEntity()
estate.setId(parentId)
tour.estate=estate
tour.isDownloaded=DownloadState.NotDownloaded
tour.overallSize=0L
tour.downloadedSize=0L
tour.tempSize=0L
tour.overallFiles=0
tour.downloadedFiles=0
// val tourMeta= TourEntity()
// tourMeta.setId(raw.id.toString())
// tour.tour=tourMeta
return tour
}
fun List<Int>.nearestResolution(targetResolution:Int ):Int{
var lastScore=Int.MAX_VALUE
var lastRes=0
for (res in this){
var d = targetResolution-res
if (d>0) d*=2
d=abs(d)
if (d<lastScore){lastScore=d;lastRes=res}
}
// Timber.d("Resolutions: $this / t=$targetResolution final=$lastRes score=$lastScore")
return lastRes
}
fun fromRaw(raw: List<TourPreviewRaw>, parentId:Int, url:String, userResolution:Int):List<TourPreviewEntity> =List(raw.size) { index-> fromRaw(raw[index],parentId,url,userResolution) }
enum class DownloadState(val i:Int):Comparable<DownloadState>{
NotDownloaded(0),
MetaPreparation(1),
DownloadQueue(2),
Downloading(3),
Suspended(4),
Downloaded(5),
Crushed(6),
Deleting(7),
NotSynced(20);
}
......@@ -25,6 +25,10 @@ interface User : Persistable {
@get:OneToMany(cascade = [CascadeAction.DELETE])
val deals:List<Deal>?
@get:Nullable
@get:OneToMany(cascade = [CascadeAction.DELETE])
val estates:List<Estate>?
@get:Nullable
@get:OneToMany(cascade = [CascadeAction.DELETE])
val subscriptions:List<Subscription>?
......
package com.biganto.visual.roompark.data.repository.db.requrey.utils;
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString;
import io.requery.Converter;
/**
* Created by Vladislav Bogdashkin on 04.07.2018.
*/
public class RevisionStringConverter implements Converter<RevisionString, String> {
@SuppressWarnings("unchecked")
@Override
public Class<RevisionString> getMappedType() {
return RevisionString.class;
}
@Override
public Class<String> getPersistedType() {
return String.class;
}
@Override
public Integer getPersistedSize() {
return null;
}
@Override
public String convertToPersisted(RevisionString value) {
return value == null ? null : value.revisionUri();
}
@Override
public RevisionString convertToMapped(Class<? extends RevisionString> type,
String value) {
return value == null ? null : new RevisionString(value);
}
}
\ No newline at end of file
package com.biganto.visual.roompark.data.repository.db.requrey.utils;
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString;
import java.util.ArrayList;
import io.requery.Converter;
public class RevisionStringListConverter implements Converter<ArrayList<RevisionString>, String> {
private static final String stringDelimeter=">RS<";
@SuppressWarnings("unchecked")
@Override
public Class<ArrayList<RevisionString>> getMappedType() {
return (Class)ArrayList.class;
}
@Override
public Class<String> getPersistedType() {
return String.class;
}
@Override
public Integer getPersistedSize() {
return null;
}
@Override
public String convertToPersisted(ArrayList<RevisionString> value) {
if (value == null) {
return "";
}
StringBuilder sb = new StringBuilder();
int index = 0;
for (RevisionString str: value) {
if (index > 0) {
sb.append(stringDelimeter);
}
sb.append(str.revisionUri());
index++;
}
return sb.toString();
}
@Override
public ArrayList<RevisionString> convertToMapped(Class<? extends ArrayList<RevisionString>> type,
String value) {
ArrayList<RevisionString> list = new ArrayList<>();
if (value != null && !value.isEmpty())
for (String s : value.split(stringDelimeter))
list.add(new RevisionString(s));
return list;
}
}
......@@ -8,6 +8,7 @@ import com.google.gson.JsonElement
import dagger.Module
import io.reactivex.Observable
import kotlinx.io.IOException
import okio.Okio
import timber.log.Timber
import java.io.File
import javax.inject.Inject
......@@ -33,9 +34,14 @@ class FileModule @Inject constructor(val context: Application) {
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)
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(assetsFile(context), fileDir)
directory.mkdirs()
val file = File(directory, fileName)
......@@ -53,15 +59,45 @@ class FileModule @Inject constructor(val context: Application) {
file.writeText("[$jsonElement]") //to json array because core unity method parse data like TourData[] Estate[] etc..
}
fun saveFileToDiskObservable(file:File,content: String):Observable<Long> {
file.parentFile.mkdirs()
return Observable.create { emitter ->
val fileStorage = file
val sink = Okio.buffer(Okio.sink(fileStorage))
val buffer = sink.buffer()
var read = 0L
val step = 8192
val source =
content.byteInputStream()
var bytesRead = 0L
Timber.w("start read ")
file.writeText(content)
Timber.w("butes read ")
// sink.flush()
System.gc()
emitter.onNext(bytesRead)
// emitter.onComplete()
}
}
fun saveFileToDisk(file:File,content: String){
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 deleteFile(uri:String)= getFile(uri).delete()
fun deleteAssetFile(uri:String) =
getAssetFile(uri).delete()
fun getAssetFile(uri:String) =
File(assetsDirectory(context).plus(uri))
fun deleteAllCacheObservable() =
Observable.create<Pair<Int, Int>> {emitter ->
......@@ -80,18 +116,21 @@ class FileModule @Inject constructor(val context: Application) {
}
val getCoreCacheDirectory:String
get(){
return rootFolder.absolutePath
}
val freeSpace = rootFolder.freeSpace
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 assetsDirectory(context: Context): String =
getDirectory(context,FileDirectory.ToursDir("biganto")).absolutePath
fun assetsFile(context: Context): File = context.filesDir.absoluteFile
fun assetsFile(context: Context): File =
getDirectory(context,FileDirectory.ToursDir("biganto"))
}
......
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.api.room_park.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.*
......@@ -113,11 +113,11 @@ fun fromRaw(raw:DealRaw):DealEntity {
}
fun fromRaw(raw:DealRaw,user:UserEntity):DealEntity {
val entity = fromRaw(raw)
entity.user = user
return entity
}
fun fromRaw(raw:DealRaw,user:UserEntity):DealEntity =
fromRaw(raw).apply {
this.user = user
this.estate.user = user
}
......@@ -126,6 +126,7 @@ fun fromRaw(raw:EstateRaw):EstateEntity{
entity.setId(raw.id)
entity.setType(raw.type)
entity.setNumber(raw.number)
entity.setAvailable(raw.available)
entity.setSectionBegin(raw.common_info.section_begin)
entity.setSectionEnd(raw.common_info.section_end)
entity.setPlanJpgUrl(raw.plan_jpg?.url)
......
package com.biganto.visual.roompark.data.service.download
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.model.FileEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.TourFileJunctionEntity
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
/**
* Created by Vladislav Bogdashkin on 14.04.2020.
*/
data class TourFileData(
val fileUrl: RevisionString,
val tourId: String,
var tempDownloadedSize: Long = 0L,
var tempOverallFileSize: Long = 0L,
var fileDownloadedSize: Long = 0L,
var tempTourTotalDiff: Long = 0L,
var isDownloaded: Boolean = false,
val fatalState: DownloadState? = null
) {
constructor(entity: FileEntity, junction: TourFileJunctionEntity) : this(
fileUrl = junction.file
, tourId = junction.tour
, tempDownloadedSize = 0L
, tempOverallFileSize = entity.totalSize
, fileDownloadedSize = entity.downloadedSize
, tempTourTotalDiff = 0L
, isDownloaded = entity.isDownloaded
)
}
package com.biganto.visual.roompark.data.service.download
import android.app.Service
import android.content.Context
import android.content.Intent
import android.media.MediaScannerConnection
import android.os.IBinder
import androidx.core.math.MathUtils.clamp
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.data.repository.api.biganto.IBigantoApi
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.model.FileEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.TourFileJunctionEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.fromRaw
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.data.service.lifecycle.ForegroundLifecycleObserver
import com.biganto.visual.roompark.data.service.notification.INotificationCenter
import com.biganto.visual.roompark.domain.use_case.TOUR_IDS_TO_DOWNLOAD_KEY
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import com.jakewharton.rxrelay2.PublishRelay
import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import okio.Okio
import timber.log.Timber
import java.io.File
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import javax.inject.Singleton
/**
* Created by Vladislav Bogdashkin on 13.04.2020.
*/
private const val DB_ACCESS_CHUNK_SIZE = 256
private const val READ_SYNC_MILLS = 120L
private const val DEQUE_REQUEST_TIMEOUT_MILLS=100L
private const val META_PREDICTION="/tourMeta_"
private const val META_FILE_TYPE=".json"
const val DOWNLOAD_MANAGER_COMMAND_KEY = "TOURS_DOWNLOAD_MANAGER_COMMAND"
const val DOWNLOAD_MANAGER_ADD_IDS_TO_LOAD_COMMAND = "ADD_TOUR_IDS_TO_QUEUE"
const val DOWNLOAD_MANAGER_STOP_COMMAND = "STOP_SERVICE"
@Singleton
class DownloadManagerService @Inject constructor(
): Service() {
//region define dependencies
@Inject
lateinit var db: IDb
@Inject
lateinit var api: IBigantoApi
@Inject
lateinit var fileModule: FileModule
@Inject
lateinit var context: Context
@Inject
lateinit var notificationsCenter: INotificationCenter
@Inject
lateinit var appLifeCycle: ForegroundLifecycleObserver
//endregion
//region downloading flow
private val disposable = CompositeDisposable()
private val downloadQueue = ArrayDeque<String>()
private val deletingQueue = ArrayDeque<String>()
private var activeDownloading: AtomicBoolean = AtomicBoolean(false)
private fun downloadTourFiles(data: TourPreviewEntity) {
if (!downloadQueue.contains(data.id))
downloadQueue.add(data.id)
}
private fun deleteTourFiles(data: TourPreviewEntity) {
if (!deletingQueue.contains(data.id))
deletingQueue.add(data.id)
}
//endregion
init {
db = RoomParkApplication.component.providedb()
api = RoomParkApplication.component.provideBigantoApi()
context = RoomParkApplication.component.provideAppContext()
fileModule = RoomParkApplication.component.provideFileSystem()
appLifeCycle = RoomParkApplication.component.provideLifeCycle()
notificationsCenter = RoomParkApplication.component.provideNotifivations()
}
override fun onCreate() {
super.onCreate()
Timber.d("START SERVICE AND SEND NOTIFICATION")
startForeground(notificationsCenter.foregroundDownloadServiceChannelId
, notificationsCenter.foregroundDownloadServiceNotification)
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStart(intent: Intent?, startId: Int) {
Timber.d("Got intent: $intent")
val v = intent?.extras?.getString(DOWNLOAD_MANAGER_COMMAND_KEY)
Timber.d("Got intent: $v")
if (v == DOWNLOAD_MANAGER_STOP_COMMAND){
Timber.d("Got DOWNLOAD_MANAGER_STOP_COMMAND")
disposable.clear()
stopSelf()
}
super.onStart(intent, startId)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val v = intent?.extras?.getString(DOWNLOAD_MANAGER_COMMAND_KEY)
if (v == DOWNLOAD_MANAGER_STOP_COMMAND){
disposable.clear()
stopSelf()
}
// Timber.d(" GOT INTENT $intent with action ${intent?.action}")
// if (intent?.action == NOTIFICATION_INTENT_STOP_SERVICE_ACTION)
// {
// stopForeground(false)
// stopSelf()
// }
val command = intent?.getStringExtra(DOWNLOAD_MANAGER_COMMAND_KEY)
if (command == DOWNLOAD_MANAGER_ADD_IDS_TO_LOAD_COMMAND){
intent.getStringArrayListExtra(TOUR_IDS_TO_DOWNLOAD_KEY)
?.forEach { toursToDownloadObserver.accept(it) }
}
Timber.d(" ON START COMMAND,${disposable.size()}")
if (disposable.size() == 0)
attachDownloader()
return START_NOT_STICKY
}
override fun onDestroy() {
disposable.clear()
downloadQueue.forEach { id -> setTourStatus(id, DownloadState.NotDownloaded) }
downloadQueue.clear()
deletingQueue.forEach { id ->
val tour = db.getTourPreview(id).observable().blockingSubscribe { tour ->
setTourStatus(id,
if (tour.downloadedSize == tour.overallSize)
DownloadState.Downloaded
else DownloadState.Suspended
)
}
}
deletingQueue.clear()
super.onDestroy()
}
private fun setTourStatus(id:String,state:DownloadState) =
db.getTourPreview(id)
.observable()
.map { it.apply { isDownloaded = state } }
.flatMap { db.upsert(it).toObservable() }
.blockingSubscribe()
private fun writeFile(response: ResponseBody, model: TourFileData): Observable<TourFileData> {
return Observable.create<TourFileData> { sub ->
try {
if (model.fatalState == DownloadState.Crushed) {
sub.onNext(model)
sub.onComplete()
}
val fileStorage = fileModule.getFile(model.fileUrl.uri())
val sink = Okio.buffer(Okio.appendingSink(fileStorage))
val buffer = sink.buffer()
var read = 0L
val step = 8192
val source = response.source()
var timer = System.currentTimeMillis()
var stop: Boolean = false
sink.use {
while (!stop && { read = source.read(buffer, step.toLong());read }() != -1L) {
model.tempDownloadedSize += read
model.fileDownloadedSize += read
if (model.tempOverallFileSize == 0L)
model.tempTourTotalDiff += model.tempDownloadedSize
model.isDownloaded = (source.exhausted()
&& (model.fileDownloadedSize == model.tempOverallFileSize
|| model.tempOverallFileSize == 0L))
}
}
model.isDownloaded = (source.exhausted()
&& (model.fileDownloadedSize == model.tempOverallFileSize
|| model.tempOverallFileSize == 0L))
sub.onNext(model.copy())
model.tempTourTotalDiff = 0
model.tempDownloadedSize = 0
sub.onComplete()
sink.close()
// refreshGallery(file) //-> обновляем представление файлов в файловой системе android (чтобы их можно было просматривать через explorer и видеть размер, корректно открывать и т.п.)
} catch (e: Exception) {
Timber.e(e)
if (!sub.isDisposed)
sub.onError(e)
setTourStatus(model.tourId,DownloadState.Crushed)
}
}
}
private fun mergeFiles(files: List<FileEntity>) {
Timber.d("Merge files")
files.forEach { file ->
val entity: FileEntity? = db.getFileEntity(file.uri).firstOrNull()
entity?.let {
file.setDownloaded(it.isDownloaded)
file.setDownloadedSize(it.downloadedSize)
file.setTotalSize(it.totalSize)
}
}
Timber.d("save files: ${files.size}")
files.chunked(DB_ACCESS_CHUNK_SIZE).forEach {chunk ->
db.upsertFileEntity(chunk).blockingSubscribe { Timber.d("file saved ${chunk.size}") }
}
}
private fun setDownloadInfo(
id: String,
downloadedSize: Long? = null
, downloadedDiffSize: Long? = null
, totalSize: Long? = null
, resolution: Int? = null
, filesCount: Int? = null
, tempLoadedFiles: Int? = null
, totalSizedDiffSize: Long? = null
) =
db.getTourPreview(id).observable().map {entity ->
entity.also {
downloadedSize?.let { entity.downloadedSize = it }
downloadedDiffSize?.let { entity.downloadedSize += it }
totalSize?.let { entity.overallSize = it }
resolution?.let { entity.targetResolution = it }
filesCount?.let { entity.overallFiles = it }
tempLoadedFiles?.let { entity.downloadedFiles += it }
totalSizedDiffSize?.let { entity.overallSize += it }
if (entity.downloadedFiles == entity.overallFiles)
entity.isDownloaded = DownloadState.Downloaded
}
}
.flatMap { db.upsert(it).toObservable() }
private fun flowableFilesDownloading(tour: TourPreviewEntity) =
api.getTourFiles(tour.id, tour.targetResolution.toString())
.map { it.first() }
.flatMap { raw ->
var downloadedSize = 0L
var totalSize = 0L
val fileEntities = raw.files.map(::fromRaw)
mergeFiles(fileEntities)
val jlist = db.getTourFilesJunction(tour.id).toList()
Timber.d("jlist:: ${jlist?.size} start ${jlist?.firstOrNull()?.id}")
val junctionList = fileEntities
.map {file ->
val entity = jlist.firstOrNull{it.tour == tour.id && it.file.uri() == file.uri.uri()}
?: TourFileJunctionEntity().apply {
setTour(tour.id)
setFile(file.uri)
}
downloadedSize += file.downloadedSize
totalSize += file.totalSize
entity
}
Timber.d("junctionList:: ${junctionList.size} start ${junctionList.first().id}")
setDownloadInfo(
raw.id.toString()
, tempLoadedFiles = 0
, downloadedSize = downloadedSize
, totalSize = totalSize
, resolution = raw.resolution
, filesCount = raw.files.count()
).map { junctionList }
}
.flatMap{ db.upsertTourFileJunction(it) }
.flatMapIterable { it }
.flatMap { junction ->
db.getFileEntity(junction.file)
.observable()
.map { entity -> TourFileData(entity,junction) }
}
.toFlowable(BackpressureStrategy.BUFFER)
// .map(::validateFile)
.parallel(clamp(Runtime.getRuntime().availableProcessors() - 2, 2, 4))
.runOn(Schedulers.io())
.flatMap { model ->
if (model.isDownloaded) return@flatMap Flowable.just(model)
var header: HashMap<String, String>? = null
if (model.fileDownloadedSize > 0)
header = hashMapOf(Pair("Range", "bytes=${model.fileDownloadedSize}-"))
api.downloadFile(model.fileUrl.revisionUri(), header)
.doOnError {
Timber.e(it)
setTourStatus(model.tourId, DownloadState.Crushed)
}
.flatMap<TourFileData> {
writeFile(it, model)
.toFlowable(BackpressureStrategy.BUFFER)
.doOnCancel { Timber.d("CANCELLED") }
}
}
.flatMap { downloadInfo ->
db.upsertFileEntity(
FileEntity().apply {
setUri(downloadInfo.fileUrl)
setDownloadedSize(downloadInfo.fileDownloadedSize)
setTotalSize(downloadInfo.tempOverallFileSize)
setDownloaded(downloadInfo.isDownloaded)
}
)
.toFlowable(BackpressureStrategy.BUFFER)
.map { downloadInfo }
}
.sequential()
.toObservable()
.observeOn(Schedulers.computation())
// .doOnNext{Timber.d("7 ${it}")}
.flatMap { model ->
setDownloadInfo(
model.tourId
, totalSizedDiffSize = model.tempTourTotalDiff
, downloadedDiffSize = model.tempDownloadedSize
, tempLoadedFiles = if (model.isDownloaded) 1 else null
)
.map {
model.tempDownloadedSize = 0
model.tourId
}
}
// .delay(12L, TimeUnit.MILLISECONDS)
private val checkService =
Observable.interval(0L, DEQUE_REQUEST_TIMEOUT_MILLS, TimeUnit.MILLISECONDS)
.filter { hasTasks }
.filter { !activeDownloading.get() }
.switchMap<String> {
when {
deletingQueue.isNotEmpty() -> {
notifyDeleting()
deleteTourFromQueue()
}
downloadQueue.isNotEmpty() -> {
notifyDownloading()
downloadTourFromQueue()
}
else -> {
Timber.e("Empty queues!");return@switchMap Observable.empty()
}
}
}
private fun downloadTourFromQueue(): Observable<String> =
Observable.fromCallable { activeDownloading.set(true);downloadQueue.poll() }
.doOnNext { Timber.d("to load tour: ${it}") }
.flatMap { db.getTourPreview(it).observable() }
.filter {
Timber.d("to load tour: ${it.isDownloaded} ${it.id}")
val forward = it.isDownloaded != DownloadState.Downloaded
activeDownloading.set(forward)
Timber.d("to load tour: $forward")
if (!forward)
notifyDownloadProgress()
forward
}
.flatMap { tour ->
flowableFilesDownloading(tour)
.doFinally {
activeDownloading.set(false)
notifyDownloadProgress()
}
}
.map { it }
.subscribeOn(Schedulers.single())
private fun deleteTourFromQueue() =
Observable.fromCallable { activeDownloading.set(true);deletingQueue.poll() }
.flatMap { db.getTourPreview(it).observable() }
.filter {
val forward = it.isDownloaded == DownloadState.Deleting
activeDownloading.set(forward)
forward
}
.map { it.id }
.doOnNext {
deleteTourSync(it)
}
.onErrorResumeNext(Observable.empty())
.doFinally {
activeDownloading.set(false)
notifyDeleteProgress()
}
.subscribeOn(Schedulers.single())
private fun deleteTourSync(tourId:String) {
try {
db.getTourFilesJunctionUniqueFiles(tourId)
.filterNotNull()
.let { list ->
list.asSequence()
.map{ FileEntity().apply { setUri(it.file) } }
.chunked(DB_ACCESS_CHUNK_SIZE)
.forEach { db.deleteFiles( it) }
list.forEach { j -> if (!fileModule.deleteAssetFile(j.file.uri()))
Timber.w("Not success to delete file! \n" +
"short uri: ${j.file.uri()} \n" +
"full uri: ${fileModule.getAssetFile(j.file.uri())}")
}
}
val resultRowsDelete = db.deleteTourFilesJunction(tourId).value()
Timber.d("Deleted form TourFile Junction rows $resultRowsDelete")
db.deleteTourPreview(tourId)
} catch (err: java.lang.Exception) {
Timber.e(err)
error("can't delete tour")
}
}
private val hasTasks: Boolean
get() = downloadQueue.isNotEmpty() || deletingQueue.isNotEmpty()
//region Notifications
private fun notifyDownloadProgress() {
if (downloadQueue.isEmpty()) {
notificationsCenter
.completeProgressMessage(
resources.getString(R.string.on_all_tours_downloaded_notification_message)
)
if (!hasTasks) {
stopForeground(false)
stopSelf()
}
} else notifyDownloading()
}
private fun notifyDeleteProgress() {
if (deletingQueue.isEmpty()) {
notificationsCenter
.completeProgressMessage(
resources.getString(R.string.on_all_tours_deleted_notification_message)
)
if (!hasTasks) {
stopForeground(false)
stopSelf()
}
} else notifyDeleting()
}
private fun notifyDeleting() = notificationsCenter
.indeterminateProgressMessage(
progress = 1
, progressMax = (deletingQueue.size + 1)
, message =
String.format(
context.resources.getString(R.string.noty_tours_delete_left)
, deletingQueue.size)
)
private fun notifyDownloading() = notificationsCenter
.indeterminateProgressMessage(
progress = 1
, progressMax = (downloadQueue.size + 1)
, message = String.format(
context.resources.getString(R.string.noty_tours_download_left)
, downloadQueue.size)
)
//endregion Notifications
private val toursToDownloadObserver = PublishRelay.create<String>()
public fun addTourToQueue(id:String) = toursToDownloadObserver.accept(id)
private fun attachDownloader() {
disposable.add(
checkService
.subscribe {
if (!appLifeCycle.isAppForeground
&& !hasTasks
&& !activeDownloading.get()) {
stopForeground(true)
stopSelf()
}
}
)
// disposable.add(touresCache.observableToursForDeleting().subscribe(::deleteTourFiles))
disposable.add(
toursToDownloadObserver
.flatMap { db.getTourPreview(it).observable() }
.filter { it.isDownloaded != DownloadState.Downloaded }
.doOnNext {
it.isDownloaded = DownloadState.Downloading
it.downloadedFiles = 0
it.downloadedSize = 0L
it.tempSize = it.overallSize // <- overall changes due downloading!!
it.overallSize = 0L
}
.observeOn(Schedulers.computation())
.flatMap(::getMeta)
.subscribeOn(Schedulers.io())
.doOnError(Timber::e)
.subscribe { downloadQueue.add(it) }
)
}
private fun getMeta(tour: TourPreviewEntity): Observable<String> =
api.getTourMetaAsString(tour.id)
.map { meta ->
tour.apply {
val metaUri = RevisionString("$META_PREDICTION${tour.id}$META_FILE_TYPE")
setMetaFileEntityId(metaUri)
fileModule.saveFileToDisk(fileModule.getFile(metaUri.uri()), meta)
}
}
.flatMap { db.upsertTourPreview(it) }
.map { tour.id }
.onErrorReturn {
setTourStatus(tour.id,DownloadState.Crushed)
tour.id
}
//#endregion oldMethod
private fun refreshGallery(file: File) {
MediaScannerConnection.scanFile(context, arrayOf(file.path), null
)
{ path, uri ->
{}//Timber.d("Scanned $path")
}
}
}
package com.biganto.visual.roompark.data.service.lifecycle
/**
* Created by Vladislav Bogdashkin on 14.04.2020.
*/
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AppLifecycleListener @Inject constructor(): ForegroundLifecycleObserver {
private var isForeground=false
override val isAppForeground : Boolean
get() = isForeground
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() {
Timber.d("Returning to foreground…")
isForeground=true
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() {
Timber.d("Moving to background…")
isForeground=false
}
}
interface ForegroundLifecycleObserver : LifecycleObserver{
val isAppForeground : Boolean
}
\ No newline at end of file
package com.biganto.visual.roompark.data.service.notification
import android.annotation.TargetApi
import android.app.*
import android.app.PendingIntent.FLAG_ONE_SHOT
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.data.service.download.DOWNLOAD_MANAGER_COMMAND_KEY
import com.biganto.visual.roompark.data.service.download.DOWNLOAD_MANAGER_STOP_COMMAND
import com.biganto.visual.roompark.data.service.download.DownloadManagerService
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import javax.inject.Inject
import javax.inject.Singleton
/**
* Created by Vladislav Bogdashkin on 13.04.2020.
*/
const val ANDROID_CHANNEL_ID = "com.biganto.visual.androidplayer.data.services.downloader.DownloadManagerService.CHANNEL_ID"
const val TOURS_CHANNEL_ID = "com.biganto.visual.androidplayer.data.services.downloader.DownloadManagerService.TOURS_CHANNEL_ID"
const val PUSH_CHANNEL_ID = "com.biganto.visual.androidplayer.data.services.downloader.DownloadManagerService.PUSH_CHANNEL_ID"
const val DOWNLOAD_SERVICE_ID = 7990
const val TOUR_INFO_SERVICE_ID = 7991
const val PUSH_INFO_SERVICE_ID = 7992
const val NOTIFICATION_INTENT="NOTIFICATION_INTENT_KEY"
const val NOTIFICATION_START_SCREEN="NOTIFICATION_START_SCREEN_KEY"
const val NOTIFICATION_INTENT_SCREEN_TYPE="NOTIFICATION_SHOW_SCREEN"
const val NOTIFICATION_INTENT_STOP_SERVICE_ACTION="STOP_DOWNLOADS_SERVICE"
const val PENDING_REQUEST_CODE=0
interface INotificationCenter{
val foregroundDownloadServiceChannelId : Int
val foregroundDownloadServiceNotification: Notification
fun indeterminateProgressMessage(progress:Int=0
,progressMax:Int=0
,message: String)
fun completeProgressMessage(message: String)
fun donwloadServiceProgressNotfication(progress:Int=0
,progressMax:Int=0
,indeterminate:Boolean=true
,message: String)
val IncomingPushServiceNotification: Notification
fun showPushNotifyMessage(message: String)
}
@Singleton
class NotificationCenter @Inject constructor(val context: Application) : INotificationCenter{
private val updateProgressNotificationDelay_Milliseconds= 333
private var lastTimeProgressNotificationUpdated = 0L
private val actualNotifyManager:NotificationManager
get()= context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
createToursNotificationChannel()
createPushNotificationChannel()
}
}
private val builder = NotificationCompat.Builder(context, ANDROID_CHANNEL_ID)
private val toursNotyBuilder = NotificationCompat.Builder(context, TOURS_CHANNEL_ID)
private val pushNotyBuilder = NotificationCompat.Builder(context, PUSH_CHANNEL_ID)
private val notificationSystemColor = ContextCompat.getColor(context, R.color.colorAccent)
private val icon = BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher_round);
//Заготовка под интерфейсы для равзедения каналов нотификаций по разным инстансам
override val foregroundDownloadServiceChannelId = DOWNLOAD_SERVICE_ID
override val foregroundDownloadServiceNotification: Notification =
builder
.setContentTitle(context.getString(R.string.notification_content_title))//getString(R.string.app_name))
.setContentText(context.getString(R.string.notification_content_text))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.mipmap.ic_launcher)
.setColor(notificationSystemColor)
.build()
override val IncomingPushServiceNotification: Notification =
pushNotyBuilder
.setContentTitle(context.getString(R.string.notification_content_title))//getString(R.string.app_name))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.mipmap.ic_launcher)
.setColor(notificationSystemColor)
.build()
//Заготовка под переход в определенный экран приложения по нажатию на нотификацию
private val toDownloadsIntent =
Intent(context, RoomParkMainActivity::class.java)
.putExtra(NOTIFICATION_INTENT,NOTIFICATION_START_SCREEN)
// .putExtra(NOTIFICATION_INTENT_SCREEN_TYPE,R.id.tab_downloads)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
// private val stopServiceIntent=
// Intent(context, DownloadManagerService::class.java)
// .setAction(NOTIFICATION_INTENT_STOP_SERVICE_ACTION)
override fun indeterminateProgressMessage(progress:Int
, progressMax:Int
, message: String) {
if (progress!=0)
if (System.currentTimeMillis()-lastTimeProgressNotificationUpdated
<updateProgressNotificationDelay_Milliseconds)
return
lastTimeProgressNotificationUpdated=System.currentTimeMillis()
donwloadServiceProgressNotfication(progress, progressMax, true, message)
}
override fun completeProgressMessage(message: String){
donwloadServiceProgressNotfication(indeterminate = false,message = message)
}
override fun showPushNotifyMessage(message: String){
val pendingIntent = PendingIntent.getActivity(context
, PENDING_REQUEST_CODE
, toDownloadsIntent
, FLAG_ONE_SHOT
)
val noty = pushNotyBuilder
.setContentTitle(context.getString(R.string.notification_content_title))//getString(R.string.app_name))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.mipmap.ic_launcher)
.setColor(notificationSystemColor)
.setContentTitle(context.getString(R.string.notification_content_title))
.setContentIntent(pendingIntent)
.setContentText(message)
.setStyle(NotificationCompat.BigTextStyle()
.setBigContentTitle(context.getString(R.string.notification_content_title))
.bigText(
message
))
.setAutoCancel(true)
.build()
actualNotifyManager.notify(
PUSH_INFO_SERVICE_ID
, noty)
}
private val cancelServicePendingIntent: NotificationCompat.Action
by lazy {
val stopServiceIntent = Intent(context, DownloadManagerService::class.java)
stopServiceIntent.putExtra(DOWNLOAD_MANAGER_COMMAND_KEY, DOWNLOAD_MANAGER_STOP_COMMAND)
val pendingStopServiceIntent = PendingIntent.getService(context
, 546
, stopServiceIntent
, FLAG_UPDATE_CURRENT
)
NotificationCompat.Action.Builder(
R.drawable.ic_back,
"Отмена",
pendingStopServiceIntent).build()
}
override fun donwloadServiceProgressNotfication(progress:Int
, progressMax:Int
, indeterminate:Boolean
, message: String){
val b = (if (indeterminate) updateProgressNotificationIndeterminate
else updateProgressNotification)
b.setContentText(message)
b.setProgress(progress, progressMax,indeterminate)
actualNotifyManager.notify(
(if (indeterminate) DOWNLOAD_SERVICE_ID else TOUR_INFO_SERVICE_ID)
, b.build()
)
}
private val defPendingIntent by lazy {
PendingIntent.getActivity(context
, PENDING_REQUEST_CODE
, toDownloadsIntent
, FLAG_UPDATE_CURRENT
)
}
private val updateProgressNotificationIndeterminate by lazy {
builder
.setOnlyAlertOnce(true)
.setContentTitle(context.getString(R.string.notification_content_title))
.setSmallIcon(R.mipmap.ic_launcher)
.setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE)
.setColor(notificationSystemColor)
.setLargeIcon(icon)
.setContentIntent(defPendingIntent)
.addAction(cancelServicePendingIntent)
.setAutoCancel(true)
}
private val updateProgressNotification by lazy {
toursNotyBuilder
.setOnlyAlertOnce(true)
.setContentTitle(context.getString(R.string.notification_content_title))
.setSmallIcon(R.mipmap.ic_launcher)
.setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE)
.setColor(notificationSystemColor)
.setLargeIcon(icon)
.setContentIntent(defPendingIntent)
.addAction(cancelServicePendingIntent)
.setAutoCancel(true)
}
@TargetApi(Build.VERSION_CODES.O)
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(): String {
val channelId = ANDROID_CHANNEL_ID
val channelName = "Biganto Visual Download Service"
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_DEFAULT)
chan.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
chan.enableVibration(false)
chan.lightColor = R.color.colorPrimary;
val service = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return channelId
}
@TargetApi(Build.VERSION_CODES.O)
@RequiresApi(Build.VERSION_CODES.O)
private fun createToursNotificationChannel(): String {
val channelId = TOURS_CHANNEL_ID
val channelName = "Biganto Visual Tour info update"
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_DEFAULT)
chan.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
chan.enableVibration(true)
chan.lightColor = R.color.colorPrimary;
val service = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return channelId
}
@TargetApi(Build.VERSION_CODES.O)
@RequiresApi(Build.VERSION_CODES.O)
private fun createPushNotificationChannel(): String {
val channelId = PUSH_CHANNEL_ID
val channelName = "RoomPark push info update"
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_DEFAULT)
chan.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
chan.enableVibration(true)
chan.lightColor = R.color.colorPrimary
val service = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return channelId
}
private fun DownloadState.notyName() =
when(this){
DownloadState.Downloaded ->context.getString(R.string.state_downloaded_notify_message)
DownloadState.Crushed -> context.getString(R.string.state_crushed_notify_message)
DownloadState.Suspended -> context.getString(R.string.state_suspended_notify_message)
DownloadState.Downloading -> context.getString(R.string.state_downloading_notify_message)
else -> context.getString(R.string.state_else_notify_message)
}
}
......@@ -4,7 +4,7 @@ import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import com.biganto.visual.roompark.BuildConfig
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.AppVersionRaw
import com.biganto.visual.roompark.data.repository.api.room_park.raw.AppVersionRaw
import com.jakewharton.rxrelay2.BehaviorRelay
import dagger.Module
import dagger.Provides
......
......@@ -4,11 +4,14 @@ import android.app.Application
import com.biganto.visual.androidplayer.data.repository.local.ILocalStore
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.data.local.LocalStorage
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi
import com.biganto.visual.roompark.data.repository.api.biganto.IBigantoApi
import com.biganto.visual.roompark.data.repository.api.retrofit.di.RetrofitModule
import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.DbModule
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.data.service.lifecycle.ForegroundLifecycleObserver
import com.biganto.visual.roompark.data.service.notification.INotificationCenter
import com.biganto.visual.roompark.domain.contract.*
import dagger.BindsInstance
import dagger.Component
......@@ -17,10 +20,6 @@ import dagger.android.AndroidInjector
import dagger.android.support.AndroidSupportInjectionModule
import javax.inject.Singleton
/**
* Created by Vladislav Bogdashkin on 13.06.2018.
*/
......@@ -57,12 +56,23 @@ interface AppComponent : AndroidInjector<RoomParkApplication>{
fun provideLocal():ILocalStore
fun provideApi():IRoomParkApi
fun provideApi(): IRoomParkApi
fun provideBigantoApi(): IBigantoApi
fun providedb():IDb
fun provideUtils():DeviceUtilsContract
fun provideTour():TourContract
fun providePlan():FlatPlanContract
fun provideLifeCycle(): ForegroundLifecycleObserver
fun provideNotifivations(): INotificationCenter
fun provideAppContext():Application
fun provideFileSystem(): FileModule
......@@ -75,41 +85,5 @@ interface AppComponent : AndroidInjector<RoomParkApplication>{
@BindsInstance app:RoomParkApplication
):AppComponent
// @BindsInstance
// fun context(application: Application): Factory
// @BindsInstance
// fun retrofitModule(retrofit: RetrofitModule):Builder
// @BindsInstance
// @Named("roomParkApi")
// fun retrofit(
// retorfitModule: RetrofitModule
// ): DaggerDataComponent.Builder
//
// fun build(): AppComponent
}
// retorfitModule: RetrofitModule
// fun cache(): ICachedStore
// fun context(): Context
// fun db(): IDb
// fun api(): IApi
// fun roomApi(): IRoomParkApi
// fun fileModule(): FilesModule
//// fun appLifeCycle(): AppLifecycleListener
// fun networkMonitor(): INetworkMonitor
// fun versionMonitor(): IAppVersionControl
// fun versionNotifier(): IAppVersionNotifier
//
// fun estateRepo() : IEstateRepository
// fun tourRepo() : ITourRepository
// fun userRepo() : IUserRepository
}
\ No newline at end of file
......@@ -9,19 +9,6 @@ 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{
......
......@@ -2,12 +2,18 @@ package com.biganto.visual.roompark.di.dagger
import com.biganto.visual.roompark.data.data_provider.*
import com.biganto.visual.roompark.data.local.LocalStorage
import com.biganto.visual.roompark.data.repository.api.IRoomParkApi
import com.biganto.visual.roompark.data.repository.api.retrofit.RetrofitRepository
import com.biganto.visual.roompark.data.repository.api.biganto.BigantoRetrofitRepository
import com.biganto.visual.roompark.data.repository.api.biganto.IBigantoApi
import com.biganto.visual.roompark.data.repository.api.retrofit.di.RetrofitModule
import com.biganto.visual.roompark.data.repository.api.room_park.IRoomParkApi
import com.biganto.visual.roompark.data.repository.api.room_park.RetrofitRepository
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.DbModule
import com.biganto.visual.roompark.data.repository.db.requrey.RequeryRepository
import com.biganto.visual.roompark.data.service.lifecycle.AppLifecycleListener
import com.biganto.visual.roompark.data.service.lifecycle.ForegroundLifecycleObserver
import com.biganto.visual.roompark.data.service.notification.INotificationCenter
import com.biganto.visual.roompark.data.service.notification.NotificationCenter
import com.biganto.visual.roompark.domain.contract.*
import dagger.Binds
import dagger.Component
......@@ -49,6 +55,14 @@ abstract class ContractRepositoryModule {
@Binds
@Singleton
abstract fun provideDeviceContract(impl: DeviceUtilsRepository): DeviceUtilsContract
@Binds
@Singleton
abstract fun provideTourContract(impl: ToursRepository): TourContract
@Binds
@Singleton
abstract fun providePlanContract(impl: PlanRepository): FlatPlanContract
}
......@@ -56,93 +70,26 @@ abstract class ContractRepositoryModule {
@Module
abstract class DataModule {
// @Singleton
// @Binds
// abstract fun provideAuthContract(contract: AuthContractModule) : AuthContract
// @Binds
// abstract fun provideModule(m:RetrofitModule) : RetrofitModule
// @Binds
// abstract fun provideRpRetrofit(retrofit:Retrofit) : Retrofit
//
@Singleton
@Binds
abstract fun provideRoomParkApi(api: RetrofitRepository) : IRoomParkApi
abstract fun provideBigantoApi(bigantoApi:BigantoRetrofitRepository): IBigantoApi
// @Binds
// abstract fun provideStore(store: KotlinReactiveEntityStore<Persistable>) : KotlinReactiveEntityStore<Persistable>
@Singleton
@Binds
abstract fun provideDb(db: RequeryRepository) : IDb
// @Singleton
// @Binds
// abstract fun provideLocalStorage(local: UserHolder) : ILocalStore
abstract fun provideRoomParkApi(roomParkApi:RetrofitRepository): IRoomParkApi
/*
@Provides
@Singleton
fun provieApi(@Named("bigantoApi") retorfit:Retrofit): IApi {
return RetrofitRepository(retorfit)
}
@Provides
@Singleton
fun provieRoomParkApi(@Named("roomParkApi") retorfit:Retrofit): IRoomParkApi {
return RoomParkRetrofitRepository(retorfit)
}
@Provides
@Singleton
fun provideDb(context:Application): IDb {
return RequeryRepository(getRequeryDataStore(context))
}
@Binds
abstract fun provideNotyCenter(center: NotificationCenter): INotificationCenter
@Provides
@Singleton
fun provideFileModule(context:Application): FilesModule {
return FilesModule(context)
}
@Binds
abstract fun provideLifecycleObserver(obs:AppLifecycleListener): ForegroundLifecycleObserver
@Provides
@Singleton
fun providesNetworkListener(context:Application): INetworkMonitor {
return LiveNetworkMonitor(context)
}
@Binds
abstract fun provideDb(db: RequeryRepository) : IDb
@Provides
@Singleton
fun getRequeryDataStore(context:Application): KotlinReactiveEntityStore<Persistable> {
Timber.d("Kotlin store creating..")
val source = DatabaseSource(context, Models.DEFAULT, "BigantoPerfect", DATABASE_VERSION)
source.setLoggingEnabled(false)
// if ( BuildConfig.DEBUG) {
// // use this in development mode to drop and recreate the tables on every upgrade
// source.setTableCreationMode(TableCreationMode.DROP_CREATE)
// }
val store = KotlinEntityDataStore<Persistable>(source.configuration)
Timber.d("Kotlin store %s",source)
return KotlinReactiveEntityStore(store)
// // override onUpgrade to handle migrating to a new version
// val configuration = source.configuration
// return ReactiveSupport.toReactiveStore(
// EntityDataStore<Persistable>(configuration))
}
*/
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract
import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.data.repository.db.requrey.model.DealEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.EstateEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.PlanPresetModel
import io.reactivex.Completable
import io.reactivex.Observable
......@@ -13,20 +14,11 @@ import io.reactivex.Observable
interface DealContract{
fun getFavorites() : Observable<List<EstateModel>>
// fun getFavorites() : Observable<List<EstateModel>>
fun getEstate(estateId: Int): Observable<EstateModel>
fun getPlanTypes(estateId: Int): Observable<List<PlanPresetModel>>
fun getEmptyPlan(estateId: Int, planId: Int): Observable<String>
fun getPlan(
estateId: Int
, planId: Int
, furniture:Boolean?
, sizes:Boolean?
, walls:Boolean?
, electric:Boolean?): Observable<String>
fun getDeals(): Observable<List<DealModel>>
fun fetchEstate(building: Int, number: Int): Observable<EstateModel>
fun setDealRead(dealId: String): Completable
fun fetchDeals(user: UserEntity): Observable<List<DealEntity>>
fun fetchFavorites(user: UserEntity): Observable<List<EstateEntity>>
fun getDealsApi(user: UserEntity): Observable<List<DealEntity>>
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract
import com.biganto.visual.roompark.data.data_provider.PlanFeaturesVariant
import com.biganto.visual.roompark.domain.model.PlanPresetModel
import com.biganto.visual.roompark.domain.use_case.DownloadUseCase
import io.reactivex.Observable
import java.io.File
/**
* Created by Vladislav Bogdashkin on 20.04.2020.
*/
interface FlatPlanContract{
fun getPlanTypes(estateId: Int): Observable<List<PlanPresetModel>>
fun getPlan(
estateId: Int
, planId: Int
, furniture:Boolean?
, sizes:Boolean?
, walls:Boolean?
, electric:Boolean?): Observable<String>
fun getPlanFile(featuresVariant: PlanFeaturesVariant): File
fun getPlan(featuresVariant: PlanFeaturesVariant): Observable<String>
fun getPlansObservable(
list: List<PlanFeaturesVariant>,
cancellationToken: DownloadUseCase.CancellationToken
): Observable<String>
}
\ No newline at end of file
......@@ -2,6 +2,7 @@ package com.biganto.visual.roompark.domain.contract
import com.biganto.visual.roompark.data.repository.db.requrey.model.SubscriptionEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import io.reactivex.Completable
import io.reactivex.Observable
......@@ -32,8 +33,23 @@ interface SubscriptionContract{
subInnerId: Int?,
topic: String,
topic_id: String?,
nuewState: Boolean
newState: Boolean
): Observable<SubscriptionEntity>
fun saveSubscribeState(sub: SubscriptionEntity): Observable<SubscriptionEntity>
fun subscribeTopicResult(
user: UserEntity,
subInnerId: Int,
deviceToken: String,
topic: String,
topic_id: String?
): Observable<List<SubscriptionModel>>
fun unSubscribeTopicResult(
user: UserEntity,
subInnerId: Int,
deviceToken: String,
topic: String,
topic_id: String?
): Observable<List<SubscriptionModel>>
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.contract
import com.biganto.visual.roompark.domain.model.AuthInfoModel
import io.reactivex.Completable
import io.reactivex.Observable
......@@ -10,4 +11,5 @@ import io.reactivex.Observable
interface TourContract {
fun getMultiTourId(building:Int, number:Int) : Observable<AuthInfoModel>
fun getOffer(offerId:Int) : Observable<AuthInfoModel>
fun deleteToursDbInfo(): Completable
}
\ No newline at end of file
......@@ -2,7 +2,7 @@ package com.biganto.visual.roompark.domain.custom_exception
import androidx.annotation.StringRes
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.ErrorRaw
import com.biganto.visual.roompark.data.repository.api.room_park.raw.ErrorRaw
/**
......
......@@ -5,7 +5,6 @@ import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.model.SubscriptionTopic
import com.biganto.visual.roompark.domain.use_case.AlbumsUseCase
import com.biganto.visual.roompark.domain.use_case.SubscriptionUseCase
import io.reactivex.Completable
import io.reactivex.Observable
import java.util.concurrent.TimeUnit
import javax.inject.Inject
......@@ -41,7 +40,7 @@ class AlbumsInteractor @Inject constructor(
fun getSubscriptions(topic: SubscriptionTopic) =
subUc.getSubscriptions(topic)
fun switchSubscription(model: SubscriptionModel, newState: Boolean): Completable =
fun switchSubscription(model: SubscriptionModel, newState: Boolean) =
when(newState){
true -> subUc.subscribeTopic(model.id,model.topic)
false -> subUc.unSubscribeTopic(model.id,model.topic)
......
......@@ -4,7 +4,6 @@ import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.model.SubscriptionTopic
import com.biganto.visual.roompark.domain.use_case.FeedUseCase
import com.biganto.visual.roompark.domain.use_case.SubscriptionUseCase
import io.reactivex.Completable
import javax.inject.Inject
/**
......@@ -25,7 +24,7 @@ class ArticlesInteractor @Inject constructor(
fun getSubscriptions(feed: String) =
subUc.getSubscriptions(feedSubType(feed))
fun switchSubscription(model:SubscriptionModel,newState: Boolean): Completable =
fun switchSubscription(model:SubscriptionModel,newState: Boolean) =
when(newState){
true -> subUc.subscribeTopic(model.id,model.topic)
false -> subUc.unSubscribeTopic(model.id,model.topic)
......
......@@ -6,7 +6,6 @@ import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.model.SubscriptionTopic
import com.biganto.visual.roompark.domain.use_case.DealseUseCase
import com.biganto.visual.roompark.domain.use_case.SubscriptionUseCase
import io.reactivex.Completable
import io.reactivex.Observable
import timber.log.Timber
import javax.inject.Inject
......@@ -21,8 +20,7 @@ class DealInteractor @Inject constructor(
){
fun getDeal(id:String): Observable<DealModel> =
useCase.getDeals()
.doOnNext { Timber.d("$it") }
useCase.prefetchDeal()
.map {deals -> deals.first { it.id==id } }
fun getStatusList() = Observable.just(statusList.sortedBy{ it.orderId })
......@@ -34,7 +32,7 @@ class DealInteractor @Inject constructor(
fun getSubscriptions(dealId: String) =
subUc.getSubscriptions(SubscriptionTopic.Deals(dealId = dealId))
fun switchSubscription(model: SubscriptionModel, newState: Boolean): Completable =
fun switchSubscription(model: SubscriptionModel, newState: Boolean) =
when(newState){
true -> subUc.subscribeTopic(model.id,model.topic)
false -> subUc.unSubscribeTopic(model.id,model.topic)
......@@ -101,4 +99,4 @@ class DealInteractor @Inject constructor(
)
)
}
}
\ No newline at end of file
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.StatusModel
import com.biganto.visual.roompark.domain.use_case.DealseUseCase
import io.reactivex.Observable
......@@ -13,7 +14,9 @@ class DealsInteractor @Inject constructor(
val useCase:DealseUseCase
){
fun fetchDeals() = useCase.getDeals()
fun fetchDeals(): Observable<List<DealModel>> = useCase.prefetchDeal()
fun getDealsApi(): Observable<List<DealModel>> = useCase.getDeals()
// Single.just(arrayListOf(dealFlat, dealParkign))
fun getStatusList() = Observable.just(statusList.sortedBy{ it.orderId })
......
......@@ -17,7 +17,6 @@ class EstateInteractor @Inject constructor(
fun getPlanTypes(estateId: Int) =
useCase.getEstatePlanPresets(estateId)
fun getPlan(estateId: Int,planId:Int) = useCase.getPlan(estateId,planId)
fun getPlan(plan:PlanPresetModel) =
useCase.getPlan(
......
......@@ -15,6 +15,9 @@ class FavoritesInteractor @Inject constructor(
private val estateUseCase: EstateUseCase
) {
fun cachedFavorites() =
estateUseCase.prefetchFavorites()
fun getFavoritesForCurrentUser() =
estateUseCase.fetchFavorites()
// Single.just(parkingEstateSample )
......@@ -52,6 +55,7 @@ class FavoritesInteractor @Inject constructor(
albumId = 10,
multitourId = null,
url = null
,availableStatus = true
),
EstateModel(
id = 1905,
......@@ -81,7 +85,7 @@ class FavoritesInteractor @Inject constructor(
albumId = 10,
multitourId = null,
url = null
),
,availableStatus = true),
EstateModel(
id = 1774,
type = FlatType.valueOf("flat".toUpperCase()),
......@@ -118,6 +122,7 @@ class FavoritesInteractor @Inject constructor(
albumId = 10,
url = null,
multitourId = 5790
,availableStatus = true
// ,explications = arrayListOf<ExplicationListModel>(
// ExplicationListModel(
// planId = 0,
......
package com.biganto.visual.roompark.domain.interactor
import android.content.Context
import android.content.Intent
import android.os.Build
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.BaseRoomParkActivity
import com.biganto.visual.roompark.data.service.download.DOWNLOAD_MANAGER_ADD_IDS_TO_LOAD_COMMAND
import com.biganto.visual.roompark.data.service.download.DOWNLOAD_MANAGER_COMMAND_KEY
import com.biganto.visual.roompark.data.service.download.DownloadManagerService
import com.biganto.visual.roompark.domain.model.CachedDataModel
import com.biganto.visual.roompark.domain.model.PushSwitchModel
import com.biganto.visual.roompark.domain.model.SettingsModel
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.use_case.AuthUseCase
import com.biganto.visual.roompark.domain.use_case.SettingsUseCase
import com.biganto.visual.roompark.domain.use_case.SubscriptionUseCase
import io.reactivex.Completable
import com.biganto.visual.roompark.domain.use_case.*
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
......@@ -21,24 +25,75 @@ import javax.inject.Inject
class SettingsInteractor @Inject constructor(
private val auth: AuthUseCase,
private val settingsUseCase: SettingsUseCase,
private val activity: Context,
private val subUc: SubscriptionUseCase
private val activity: BaseRoomParkActivity,
private val subUc: SubscriptionUseCase,
private val toursUc: TourPreviewsUseCase,
private val planTypes: PlanTypesUseCase
){
fun downloadTourPlans(cancellationToken:DownloadUseCase.CancellationToken)
= planTypes.downloadAllPlanTypes(cancellationToken)
fun fetchToursSizes() = toursUc.fetchToursSizes()
fun fetchPlanTypesSizes() = planTypes.fetchNotDownloadedPlansSizes()
private fun startDownloadService() {
val i = Intent(activity, DownloadManagerService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.startForegroundService(i)
} else {
activity.startService(i)
}
}
private fun startDownloadService(ids:List<String>){
Timber.d(" gonna startService ++")
try {
val i = Intent(activity, DownloadManagerService::class.java)
i.putExtra(DOWNLOAD_MANAGER_COMMAND_KEY, DOWNLOAD_MANAGER_ADD_IDS_TO_LOAD_COMMAND)
i.putStringArrayListExtra(TOUR_IDS_TO_DOWNLOAD_KEY, ArrayList(ids))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.startForegroundService(i)
} else {
activity.startService(i)
}
}catch (e:Exception){
Timber.d(" gonna startService errror")
Timber.e(e)
}
}
fun startToursDownloading() =
toursUc.downloadAllDeelsAndEstates()
.doOnNext { Timber.d(" gonna startService") }
.doOnNext { this.startDownloadService() }
.delay(100,TimeUnit.MILLISECONDS)
.doOnNext { Timber.d(" gonna startServic222e") }
.doOnNext { tours ->
startDownloadService(tours.map { tour -> tour.id })
}
.doOnNext { Timber.d(" gonna startServsdfsfdsice") }
// .doOnComplete { Timber.w(" azzaza complete") }
fun getSubscriptions() =
subUc.getCurrentUserSubscriptions()
fun switchSubscription(model: SubscriptionModel, newState: Boolean): Completable =
fun switchSubscription(model: SubscriptionModel, newState: Boolean) =
when(newState){
true -> subUc.subscribeTopic(model.id,model.topic)
false -> subUc.unSubscribeTopic(model.id,model.topic)
}
private val plans
private val plans : Observable<CachedDataModel>
get() = settingsUseCase.planTypesSize.map {
CachedDataModel(activity.resources.getString(R.string.plans_cache),it,1)
CachedDataModel(activity.resources.getString(R.string.plans_cache), it, 1)
}
private val tours
......@@ -64,7 +119,7 @@ class SettingsInteractor @Inject constructor(
fun deleteCacheFiles() = settingsUseCase.clearAllCache()
fun getCacheInfo() =
fun getCacheInfo(): Observable<MutableList<CachedDataModel>> =
Observable.concatArray(plans, tours, feeds, albums, overall).toList().toObservable()
fun fetchSettings(): Observable<SettingsModel> = Observable.just(sampleSettings)
......
package com.biganto.visual.roompark.domain.interactor
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.domain.use_case.DownloadUseCase
import com.biganto.visual.roompark.domain.use_case.TourPreviewsUseCase
import io.reactivex.Observable
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 03.04.2020.
*/
class ToursInteractor @Inject constructor(
private val downloadUseCase: DownloadUseCase,
private val tourUseCase:TourPreviewsUseCase
){
fun getEstateTourList(multiTourId:Int?,estateId:Int) =
tourUseCase.fetchTourOffer(multiTourId
?: error("Отсутсвуют виртуальные туры для данного объекта"),estateId)
fun getEstateTourList(estate:EstateModel): Observable<List<TourModel>> =
tourUseCase.fetchTourOffer(estate.multitourId
?: error("Отсутсвуют виртуальные туры для данного объекта"),estate.id)
fun downloadTour(tourId:String,cancellationToken: DownloadUseCase.CancellationToken) =
downloadUseCase.startTourDownloading(tourId,cancellationToken)
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.model
import android.os.Parcel
import android.os.Parcelable
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import java.text.DecimalFormat
import java.util.*
import kotlin.math.max
/**
* Created by Vladislav Bogdashkin on 30.05.2018.
*/
data class TourModel (
val tour_id:String="-1",
val created : Date=Date(),
val updated:Date=Date(),
val type: TourType = TourType.VIRTUAL,
val title:String="",
val previewUrl:String?="",
val screen:String?=null,
val hidden:Boolean=false,
val metaUri:String?,
val parentId:Int=-1,
var downloadState: DownloadState =DownloadState.MetaPreparation,
val downloadedSize: Long=-1L,
val totalTourSize : Long=-1L,
val targetResolution : Int
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readSerializable() as Date,
parcel.readSerializable() as Date,
TourType.valueOf(parcel.readString()),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readByte() != 0.toByte(),
parcel.readString(),
parcel.readInt(),
DownloadState.valueOf(parcel.readString()),
parcel.readLong(),
parcel.readLong(),
parcel.readInt())
val metaPredict : String
get() = metaUri?.removeSuffix("$tour_id.json") ?: ""
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(tour_id)
parcel.writeSerializable(created)
parcel.writeSerializable(updated)
parcel.writeString(type.name)
parcel.writeString(title)
parcel.writeString(previewUrl)
parcel.writeString(screen)
parcel.writeByte(if (hidden) 1 else 0)
parcel.writeString(metaUri)
parcel.writeInt(parentId)
parcel.writeString(downloadState.name)
parcel.writeLong(downloadedSize)
parcel.writeLong(totalTourSize)
parcel.writeInt(targetResolution)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<TourModel> {
override fun createFromParcel(parcel: Parcel): TourModel {
return TourModel(parcel)
}
override fun newArray(size: Int): Array<TourModel?> {
return arrayOfNulls(size)
}
}
}
fun Long.bytesToSize():String{
if (this==-1L) return "~"
var scale=0
var rest:Long=this
var least=0f
// Timber.d("size to gb: %s",this)
while (rest>=1024) {
least=(rest%1024).toFloat()
rest /= 1024
scale++
}
least/=102.4f
// least*=100f
return when(scale)
{
0->"$rest.${least.format(0)}B"
1->"$rest.${least.format(0)}Kb"
2->"$rest.${least.format(0)}Mb"
3->"$rest.${least.format(0)}Gb"
4->"$rest.${least.format(0)}Tb"
else ->"$rest${least.format(0)}Bb" //BigantoByte
}
}
fun bytesToString(bytes:Long)=bytes.bytesToSize()
fun Float.format(fracDigits: Int): String {
val df = DecimalFormat()
df.maximumFractionDigits = fracDigits
return df.format(this)
}
enum class TourType(val i:Int)
{
VIRTUAL(0),
REAL(1)
}
fun fromEntity(entity: TourPreviewEntity) = TourModel(
tour_id = entity.id,
parentId = entity.estate.id,
created= entity.created,
updated = entity.created,
// updated = entity.updated,
type = when(entity.type){
"virtual"-> TourType.VIRTUAL
"real"-> TourType.REAL
else-> TourType.REAL
},
title = entity.title,
previewUrl = entity.preview,
metaUri= entity.metaFileEntityId?.uri(),
screen = entity.screen,
hidden = entity.hidden,
downloadState = entity.isDownloaded,//calcDownloadState(entity.tour as TourEntity?)
downloadedSize = entity.downloadedSize?:-1L,
totalTourSize = max(entity.overallSize?:0L,entity.tempSize?:0L),
targetResolution = entity.targetResolution
)
fun fromEntity(raw: List<TourPreviewEntity>):List<TourModel> =List(raw.size) { index-> fromEntity(raw[index])}
data class TourLoadProgressModel(
val downloadState: DownloadState,
val downloadedSize: Long=-1L,
val totalTourSize : Long=-1L
)
......@@ -51,6 +51,7 @@ data class EstateModel(
val id:Int,
val type:FlatType,
val number:String,
val availableStatus:Boolean,
val sectionBegin:Int?=null,
val sectionEnd:Int?=null,
val planPNG:PlanModel?,
......@@ -90,6 +91,7 @@ fun fromEntity(entity:EstateEntity): EstateModel {
id = entity.id,
type = FlatType.valueOf(entity.type.toUpperCase()),
number = entity.number,
availableStatus = entity.available,
sectionBegin = entity.sectionBegin,
sectionEnd = entity.sectionEnd,
planPNG = null,
......
......@@ -5,6 +5,6 @@ package com.biganto.visual.roompark.domain.model
*/
data class TourRequestModel(val building:Int, val flat:Int)
data class TourResponse(val response:List<TourModel>)
data class TourModel(val tourId:String, val title:Int, val preview:String?)
//data class TourRequestModel(val building:Int, val flat:Int)
//data class TourResponse(val response:List<TourModel>)
//data class TourModel(val tourId:String, val title:Int, val preview:String?)
package com.biganto.visual.roompark.domain.model
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.StreamRaw
import com.biganto.visual.roompark.data.repository.api.retrofit.raw.WebCamRaw
import com.biganto.visual.roompark.data.repository.api.room_park.raw.StreamRaw
import com.biganto.visual.roompark.data.repository.api.room_park.raw.WebCamRaw
/**
* Created by Vladislav Bogdashkin on 23.09.2019.
......
package com.biganto.visual.roompark.domain.use_case
import android.app.Application
import android.media.MediaScannerConnection
import com.biganto.visual.roompark.data.repository.api.biganto.IBigantoApi
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.RevisionString
import com.biganto.visual.roompark.data.repository.db.requrey.model.FileEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.TourFileJunctionEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.fromRaw
import com.biganto.visual.roompark.data.repository.file.FileModule
import com.biganto.visual.roompark.data.service.download.TourFileData
import com.biganto.visual.roompark.domain.contract.AuthContract
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import io.reactivex.Observable
import io.reactivex.rxkotlin.Observables
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import okio.Okio
import timber.log.Timber
import java.io.File
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 02.08.2019.
*/
private const val META_PREDICTION="/tourMeta_"
private const val META_FILE_TYPE=".json"
class DownloadUseCase @Inject constructor(
private val db: IDb,
private val api: IBigantoApi,
private val fileModule: FileModule,
private val auth: AuthContract,
private val context: Application
) {
private fun writeFile(
response: ResponseBody,
model: TourFileData,
token: CancellationToken
): Observable<TourFileData> {
return Observable.create<TourFileData> { sub ->
try {
if (token.isCancelled) {
sub.onNext(model)
sub.onComplete()
}
val fileStorage = fileModule.getFile(model.fileUrl.uri())
val sink =
if (model.fileDownloadedSize>0)
Okio.buffer(Okio.appendingSink(fileStorage))
else Okio.buffer(Okio.sink(fileStorage))
val buffer = sink.buffer()
var read = 0L
val step = 8192
val source = response.source()
var stop: Boolean = token.isCancelled
sink.use {
while (!stop && { read = source.read(buffer, step.toLong());read }() != -1L) {
model.tempDownloadedSize += read
model.fileDownloadedSize += read
if (model.tempOverallFileSize == 0L)
model.tempTourTotalDiff += model.tempDownloadedSize
if (token.isCancelled) {
model.isDownloaded = false
model.fileDownloadedSize = 0
sub.onComplete()
stop = true
source.buffer.flush()
}
}
}
model.isDownloaded = (source.exhausted()
&& (model.fileDownloadedSize == model.tempOverallFileSize
|| model.tempOverallFileSize == 0L))
sub.onNext(model.copy())
model.tempTourTotalDiff = 0
model.tempDownloadedSize = 0
sub.onComplete()
sink.close()
} catch (e: Throwable) {
Timber.e(e)
if (!sub.isDisposed)
sub.onError(e)
token.isCancelled = true
}
}
}
private fun mergeFiles(files: List<FileEntity>) {
Timber.d("Nerge files")
files.forEach { file ->
val entity: FileEntity? = db.getFileEntity(file.uri).firstOrNull()
entity?.let {
file.setDownloaded(it.isDownloaded)
file.setDownloadedSize(it.downloadedSize)
file.setTotalSize(it.totalSize)
}
}
Timber.d("save files: ${files.size}")
db.upsertFileEntity(files).blockingSubscribe { Timber.d("file saved") }
}
@Volatile
private var tourDbModel: TourPreviewEntity? = null
private fun setDownloadInfo(
id: String,
downloadedSize: Long? = null
, downloadedDiffSize: Long? = null
, totalSize: Long? = null
, resolution: Int? = null
, filesCount: Int? = null
, tempLoadedFiles: Int? = null
, totalSizedDiffSize: Long? = null
) {
if (tourDbModel?.id != id)
throw error("Wrong tour id")
tourDbModel?.let { entity ->
downloadedSize?.let { entity.downloadedSize = it }
downloadedDiffSize?.let { entity.downloadedSize += it }
totalSize?.let { entity.overallSize = it }
resolution?.let { entity.targetResolution = it }
filesCount?.let { entity.overallFiles = it }
tempLoadedFiles?.let { entity.downloadedFiles += it }
totalSizedDiffSize?.let { entity.overallSize += it }
if (entity.downloadedFiles == entity.overallFiles)
entity.isDownloaded = DownloadState.Downloaded
}
Timber.d(" tour: ${tourDbModel?.downloadedFiles} / ${tourDbModel?.overallFiles}")
}
private fun observableTourDownloading(tour: TourPreviewEntity, token: CancellationToken)
: Observable<TourPreviewEntity> =
api.getTourFiles(tour.id, tour.targetResolution.toString())
.map { tourDbModel = tour;it.first() }
.map { raw ->
var downloadedSize = 0L
var totalSize = 0L
val fileEntities = raw.files.map(::fromRaw)
mergeFiles(fileEntities)
val jlist = db.getTourFilesJunction(tour.id).toList()
val junctionList = fileEntities
.map {file ->
val entity = jlist.firstOrNull{it.tour == tour.id && it.file.uri() == file.uri.uri()}
?: TourFileJunctionEntity()
entity.setTour(tour.id)
entity.setFile(file.uri)
downloadedSize += file.downloadedSize
totalSize += file.totalSize
entity
}
setDownloadInfo(
raw.id.toString()
, downloadedSize = downloadedSize
, totalSize = totalSize
, resolution = raw.resolution
, filesCount = raw.files.count()
)
junctionList
}
.flatMap{ list ->
tourDbModel?.let {
Observables.zip(
db.upsertTourPreview(it), db.upsertTourFileJunction(list)
).map { list }
}
}
.flatMapIterable { it }
.flatMap { junction ->
db.getFileEntity(junction.file)
.observable()
.map { entity -> TourFileData(entity,junction) }
}
.toFlowable(BackpressureStrategy.BUFFER)
.parallel(4)
.runOn(Schedulers.io())
.filter { !token.isCancelled }
.flatMap { model ->
if (model.isDownloaded)
return@flatMap Flowable.just(model)
val header: HashMap<String, String>? =
if (model.fileDownloadedSize > 0)
hashMapOf(Pair("Range", "bytes=${model.fileDownloadedSize}-"))
else null
api.downloadFile(model.fileUrl.revisionUri(), header)
.flatMap<TourFileData> {
writeFile(it, model, token)
.toFlowable(BackpressureStrategy.BUFFER)
.doOnCancel { Timber.w("TOUR DOWNLOADING CANCELLED") }
}
.flatMap { downloadInfo ->
db.upsertFileEntity(
FileEntity().apply {
setUri(downloadInfo.fileUrl)
setDownloadedSize(downloadInfo.fileDownloadedSize)
setTotalSize(downloadInfo.tempOverallFileSize)
setDownloaded(downloadInfo.isDownloaded)
}
)
.toFlowable(BackpressureStrategy.BUFFER)
.map { downloadInfo }
}
}
.sequential()
.toObservable()
.map { model ->
setDownloadInfo(
model.tourId
, totalSizedDiffSize = model.tempTourTotalDiff
, downloadedDiffSize = model.tempDownloadedSize
, tempLoadedFiles = if (model.isDownloaded) 1 else null
)
model.tempDownloadedSize = 0
model.tourId
}
.delay(12L, TimeUnit.MILLISECONDS)
.flatMap { db.upsertTourPreview(tourDbModel!!) }
fun startTourDownloading(tourId: String, cancellataionToken: CancellationToken)
: Observable<TourPreviewEntity> = db.getTourPreview(tourId).observable()
.doOnNext {
it.isDownloaded = DownloadState.Downloading
it.downloadedFiles = 0
it.downloadedSize = 0L
it.tempSize = it.overallSize // <- overall changes due downloading!!
it.overallSize = 0L
}
.flatMap { db.upsertTourPreview(it) }
.flatMap(::getMeta)
.doOnError(Timber::e)
.flatMap { observableTourDownloading(it, cancellataionToken) }
private fun getMeta(tour: TourPreviewEntity) =
api.getTourMetaAsString(tour.id)
.map { meta ->
tour.apply {
val metaUri =
RevisionString("$META_PREDICTION${tour.id}$META_FILE_TYPE")
setMetaFileEntityId(metaUri)
fileModule.saveFileToDisk(
File(FileModule.assetsDirectory(context).plus(metaUri.uri()))
, meta
)
}
}
.onErrorReturn {
tour.isDownloaded = DownloadState.Crushed
db.upsertTourPreview(tour).blockingSubscribe()
tour
}
//#endregion oldMethod
private fun refreshGallery(file: File) {
MediaScannerConnection.scanFile(
context, arrayOf(file.path), null
)
{ path, uri ->
{}//Timber.d("Scanned $path")
}
}
data class CancellationToken(var isCancelled: Boolean)
}
\ No newline at end of file
package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.data.repository.db.requrey.model.DealEntity
import com.biganto.visual.roompark.domain.contract.AuthContract
import com.biganto.visual.roompark.domain.contract.DealContract
import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.fromEntity
import io.reactivex.Observable
import timber.log.Timber
import javax.inject.Inject
/**
......@@ -8,10 +14,23 @@ import javax.inject.Inject
*/
class DealseUseCase @Inject constructor(
private val contract: DealContract
private val contract: DealContract,
private val authContract: AuthContract
) {
fun getDeals() = contract.getDeals()
fun prefetchDeal(): Observable<List<DealModel>> =
authContract.currentUser()
.map {user ->
user.deals?.asSequence()
?.map { it as DealEntity }
?.filterNotNull()?.toList()
}
.map { fromEntity(it, ::fromEntity) }
fun getDeals(): Observable<List<DealModel>> =
authContract.currentUser()
.flatMap(contract::getDealsApi)
.map { fromEntity(it, ::fromEntity) }
fun setDealRead(id:String) = contract.setDealRead(id)
......
package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.data.repository.db.requrey.model.EstateEntity
import com.biganto.visual.roompark.domain.contract.AuthContract
import com.biganto.visual.roompark.domain.contract.DealContract
import com.biganto.visual.roompark.domain.contract.FlatPlanContract
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.fromEntity
import io.reactivex.Observable
import javax.inject.Inject
/**
......@@ -8,10 +14,24 @@ import javax.inject.Inject
*/
class EstateUseCase @Inject constructor(
private val contract: DealContract
private val contract: DealContract,
private val planContract: FlatPlanContract,
private val authContract: AuthContract
) {
fun fetchFavorites() = contract.getFavorites()
fun prefetchFavorites() =
authContract.currentUser()
.map {user -> user.estates?.asSequence()
?.map { it as EstateEntity }
?.filter{ it.favorite }?.filterNotNull()?.toList()
}
.map { fromEntity(it, ::fromEntity) }
fun fetchFavorites(): Observable<List<EstateModel>> =
authContract.currentUser()
.flatMap (contract::fetchFavorites)
.map { fromEntity(it, ::fromEntity) }
fun getEstate(estateId: Int) = contract.getEstate(estateId)
......@@ -19,11 +39,8 @@ class EstateUseCase @Inject constructor(
contract.fetchEstate(building, number)
fun getEstatePlanPresets(estateId: Int) =
contract.getPlanTypes(estateId)
planContract.getPlanTypes(estateId)
fun getPlan(estateId: Int, planId: Int) =
contract.getEmptyPlan(estateId, planId)
fun getPlan(
estateId: Int
......@@ -32,7 +49,7 @@ class EstateUseCase @Inject constructor(
, sizes: Boolean?
, walls: Boolean?
, electric: Boolean?
) = contract.getPlan(
) = planContract.getPlan(
estateId
, planId
, furniture
......
package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.data.data_provider.featuresVariants
import com.biganto.visual.roompark.data.repository.db.requrey.model.EstateEntity
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import com.biganto.visual.roompark.domain.contract.AuthContract
import com.biganto.visual.roompark.domain.contract.DealContract
import com.biganto.visual.roompark.domain.contract.FlatPlanContract
import io.reactivex.Observable
import io.reactivex.rxkotlin.Observables
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 20.04.2020.
*/
class PlanTypesUseCase @Inject constructor(
private val planContract: FlatPlanContract,
private val auth:AuthContract,
private val dealContract: DealContract
) {
private fun fetchUserEstates(user:UserEntity): Observable<List<EstateEntity>> =
Observables.zip(
dealContract.fetchDeals(user)
.map { it.map {deal -> deal.estate as EstateEntity } }
,dealContract.fetchFavorites(user))
{t1,t2 -> t1+t2}
private val fetchAllPlanTypes =
auth.currentUser()
.flatMap {user ->
fetchUserEstates(user)
.onErrorReturn { arrayListOf() }
.map { apiList ->
val list =
user.deals?.map { it.estate as EstateEntity }?.toMutableList()
?: mutableListOf()
list.addAll(user.estates?.map { it as EstateEntity } ?: arrayListOf())
(list+apiList).distinctBy { it.id }
}
}
fun fetchNotDownloadedPlansSizes(): Observable<Int> =
fetchAllPlanTypes
.flatMapIterable { it }
.flatMap {
planContract.getPlanTypes(it.id)
.map { plantypes -> plantypes.flatMap { plan -> plan.featuresVariants } }
}
.map { list -> list.sumBy { if (planContract.getPlanFile(it).exists()) 0 else 1 } }
.scan { t1: Int, t2: Int -> t1 + t2 }
fun downloadAllPlanTypes(cancellationToken: DownloadUseCase.CancellationToken): Observable<DownloadProgress> {
var listSize = 0
return fetchAllPlanTypes
.flatMapSingle {
Observable.fromIterable(it)
.flatMap { estateEntity ->
planContract.getPlanTypes(estateEntity.id)
.map { plantypes -> plantypes.flatMap { plan -> plan.featuresVariants } }
}
.toList()
}
.map { it.flatten() }
.filter { !cancellationToken.isCancelled }
.flatMap { list ->
planContract.getPlansObservable(list,cancellationToken)
.map { 1 }
.scan(0,
{ t1, t2 -> t1 + t2 })
.map { completed ->
DownloadProgress(completed, list.size)
}
}
}
}
// private fun downloadPlan(
// list: PlanFeaturesVariant
// , cancellationToken: DownloadUseCase.CancellationToken
// )
// : Observable<DownloadProgress> {
//
// val avaliablethread = true
//
// return Observable.fromIterable(list)
//
//
// .delay(100,TimeUnit.MILLISECONDS)
// .doOnNext { Timber.w("emmited") }
// .delay { variant ->
// planContract.getPlan(variant)}
// .filter { !cancellationToken.isCancelled }
//
// }
data class DownloadProgress(
val currentProgress:Int,
val totalProgress:Int
)
\ No newline at end of file
......@@ -2,6 +2,7 @@ package com.biganto.visual.roompark.domain.use_case
import com.biganto.visual.roompark.data.data_provider.AuthContractModule
import com.biganto.visual.roompark.domain.contract.FilesContract
import com.biganto.visual.roompark.domain.contract.TourContract
import io.reactivex.Observable
import javax.inject.Inject
......@@ -11,26 +12,28 @@ import javax.inject.Inject
class SettingsUseCase @Inject constructor(
private val authContract: AuthContractModule,
private val fileContract: FilesContract
private val fileContract: FilesContract,
private val tourContract: TourContract
){
fun signOut() = authContract.signOut()
fun clearAllCache() = fileContract.deleteAllFiles()
fun clearAllCache(): Observable<Pair<Int, Int>> =
tourContract.deleteToursDbInfo()
.andThen(fileContract.deleteAllFiles())
val planTypesSize
get() = Observable.just(fileContract.getPlansSize())
get() = Observable.defer { Observable.just(fileContract.getPlansSize()) }
val albumsSize
get() = Observable.just(fileContract.getAlbumSize())
get() = Observable.defer { Observable.just(fileContract.getAlbumSize()) }
val feedsSize
get() = Observable.just(fileContract.getFeedSize())
get() = Observable.defer { Observable.just(fileContract.getFeedSize()) }
val toursSize
get() = Observable.just(fileContract.getToursSize())
get() = Observable.defer { Observable.just(fileContract.getToursSize()) }
val overallSize
get() = Observable.just(fileContract.allCacheSize())
get() = Observable.defer { Observable.just(fileContract.allCacheSize()) }
}
\ No newline at end of file
......@@ -11,9 +11,8 @@ import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.model.SubscriptionTopic
import com.biganto.visual.roompark.domain.model.TitledSubscriptionModel
import com.biganto.visual.roompark.domain.model.fromEntity
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import io.reactivex.rxkotlin.Observables
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
......@@ -28,62 +27,79 @@ class SubscriptionUseCase @Inject constructor(
private val utils: DeviceUtilsContract,
private val auth: AuthContract
) {
fun subscribeTopic(subId: Int, topic: SubscriptionTopic): Completable =
Observable.zip(auth.currentUser(), utils.getDeviceId()
, BiFunction<UserEntity,String,SubscribeRequestModel> {
fun subscribeTopic(subId: Int, topic: SubscriptionTopic): Observable<List<TitledSubscriptionModel>> =
Observables.zip(auth.currentUser(), utils.getDeviceId()){
user, token -> SubscribeRequestModel(user,token)
})
.flatMapCompletable {
subscription.subscribeTopic(
it.user
}
.flatMap {requestModel ->
subscription.subscribeTopicResult(
requestModel.user
, subId
, it.deviceToken
, topic = topic.topicName,
topic_id = topic.topicId
).subscribeOn(Schedulers.io())
, requestModel.deviceToken
, topic = topic.topicName
, topic_id = topic.topicId
)
.map {subs -> subsToTitle(requestModel.user,subs)}
.subscribeOn(Schedulers.io())
}
fun unSubscribeTopic(subId: Int, topic: SubscriptionTopic): Completable =
Observable.zip(auth.currentUser(), utils.getDeviceId()
, BiFunction<UserEntity,String,SubscribeRequestModel> {
fun unSubscribeTopic(subId: Int, topic: SubscriptionTopic): Observable<List<TitledSubscriptionModel>> =
Observables.zip(auth.currentUser(), utils.getDeviceId()){
user, token -> SubscribeRequestModel(user,token)
})
.flatMapCompletable {
subscription.unSubscribeTopic(
it.user
}
.flatMap {requestModel ->
subscription.unSubscribeTopicResult(
requestModel.user
, subId
, it.deviceToken
, requestModel.deviceToken
, topic = topic.topicName,
topic_id = topic.topicId
).subscribeOn(Schedulers.io())
)
.map {subs -> subsToTitle(requestModel.user,subs)}
.subscribeOn(Schedulers.io())
}
fun getCurrentUserSubscriptions(): Observable<List<TitledSubscriptionModel>> =
auth.currentUser()
.map {user ->
val subList = user.subscriptions?: arrayListOf()
val list = List<TitledSubscriptionModel>(subList.size){i ->
val sub:SubscriptionModel = fromEntity(subList[i] as SubscriptionEntity)
private fun subsToTitle(user:UserEntity,subs:List<SubscriptionModel>?)
: List<TitledSubscriptionModel> {
val subList =
subs
?: user.subscriptions
?.asSequence()?.map { it as SubscriptionEntity }?.map(::fromEntity)?.toList()
?: arrayListOf()
return List(subList.size){ i ->
subToTitledSub(user.deals?.map { it as DealEntity }?: arrayListOf(),subList[i])
}
}
private fun subToTitledSub(deals:List<DealEntity>,sub:SubscriptionModel)
: TitledSubscriptionModel {
var title = SubscriptionTopic.titleByTopic(sub.topic)
if (sub.topic is SubscriptionTopic.Deals){
if (sub.topic is SubscriptionTopic.Deals) {
val deal =
user.deals?.firstOrNull { d->d.id == sub.topic.topicId } as DealEntity
title = "$title № ${deal.estate.number}"
deals.firstOrNull { d -> d.estateCrmId == sub.topic.topicId }
if (deal == null)
Timber.e("Deal not recognized: $sub ; $deals")
title = "$title № ${deal?.estate?.number}"
}
TitledSubscriptionModel(title,sub)
}
list
return TitledSubscriptionModel(title, sub)
}
fun getCurrentUserSubscriptions(): Observable<List<TitledSubscriptionModel>> =
auth.currentUser()
.map {user -> subsToTitle(user,null)}
fun getSubscriptions(topic: SubscriptionTopic): Observable<SubscriptionModel> =
auth.currentUser()
.map {user ->
var sub = user.subscriptions
?.firstOrNull { it.topic == topic.topicName && it.number == topic.topicId }
Timber.d("fetched topic: $sub")
if (sub == null) {
sub = SubscriptionEntity()
sub.setOwner(user)
......@@ -91,8 +107,8 @@ class SubscriptionUseCase @Inject constructor(
sub.setNumber(topic.topicId)
sub.setState(false)
}
Timber.w("sub is : $sub")
subscription.saveSubscribeState(sub as SubscriptionEntity).blockingFirst()
subscription.saveSubscribeState(sub as SubscriptionEntity)
.blockingFirst()
}
.map(::fromEntity)
}
......
package com.biganto.visual.roompark.domain.use_case
import android.content.res.Resources
import com.biganto.visual.roompark.data.repository.api.biganto.IBigantoApi
import com.biganto.visual.roompark.data.repository.api.biganto.raw.TourPreviewRaw
import com.biganto.visual.roompark.data.repository.db.IDb
import com.biganto.visual.roompark.data.repository.db.requrey.model.UserEntity
import com.biganto.visual.roompark.domain.contract.AuthContract
import com.biganto.visual.roompark.domain.contract.TourContract
import com.biganto.visual.roompark.domain.custom_exception.CustomApiException
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.domain.model.fromEntity
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.DownloadState
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.TourPreviewEntity
import com.biganto.visual.roomparkvr.data.repository.db.requery.model.fromRaw
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 19.06.2018.
*/
const val EMPTY_PARENT = -778
const val TOUR_IDS_TO_DOWNLOAD_KEY = "DOWNLOAD_MANAGER_IDS_TO LOAD"
class TourPreviewsUseCase @Inject constructor(
private val api:IBigantoApi
,private val db: IDb
,private val auth:AuthContract
,private val tours:TourContract
) {
fun getTourById(tourId: String): Observable<DownloadTourByIdResult> {
val tour = retrieveTour(tourId)
Timber.d("retrieveTour $tourId / $tour")
if (tour != null) {
if (tour.downloadState == DownloadState.Downloaded)
return Observable.just<DownloadTourByIdResult>(
DownloadTourByIdResult.ReadyToPlay(
tour
)
)
return setTourState(tour.tour_id, DownloadState.DownloadQueue)
.map<DownloadTourByIdResult> { DownloadTourByIdResult.DownloadStarted(tour) }
}
return syncTour(tourId, parentId = EMPTY_PARENT)
.map { it }
.map<DownloadTourByIdResult> { DownloadTourByIdResult.DownloadStarted(it) }
}
fun retrieveTour(tourId: String) =
db.getTourPreview(tourId)
.asSequence()
.map(::fromEntity)
.firstOrNull()
private fun setTourState(tourId: String, state: DownloadState): Observable<Boolean> =
db.getTourPreview(tourId)
.observable()
.doOnNext { it.isDownloaded = state }
.flatMap { db.upsertTourPreview(it).map { true } }
private fun syncTour(tourId: String, parentId: Int): Observable<TourModel> =
syncTour(tourId, parentId, calcTargetResolution())
private fun syncTour(tourId: String, estateId: Int, targetResolution: Int)
: Observable<TourModel> {
val request = ToursRemoteByIdRequestModel(
arrayListOf(tourId)
, estateId
, null
, targetResolution
)
return forceTourUpdate(request)
.map {
if (it.isNullOrEmpty())
throw CustomApiException.TourByIdNotFoundException()
it.first()
}
.doOnNext { it.isDownloaded = DownloadState.DownloadQueue }
.flatMap { db.upsertTourPreview(it) }
.map(::fromEntity)
.doOnError(Timber::e)
}
private fun calcTargetResolution(): Int {
val w = Resources.getSystem().displayMetrics.widthPixels
val h = Resources.getSystem().displayMetrics.heightPixels
return w.coerceAtLeast(h)
}
private fun forceTourUpdate(requestModel: ToursRemoteByIdRequestModel): Observable<List<TourPreviewEntity>> =
api.getToursPreviewById(requestModel.ids)
.subscribeOn(Schedulers.io())
.map {
fromRaw(
it
, requestModel.estateId
, api.provideHttpUrl().toString()
, requestModel.targetResolution
)
}
private fun fetchDbTourList(estateId: Int) =
db.getEstateTourPreviews(estateId)
.filter { !it.isNullOrEmpty() }
private fun fetchApiTourList(multitourId: Int, estateId: Int) =
auth.currentUser()
.map { it.targetResolution }
.flatMap { res ->
api.getOfferTours(multitourId)
.doOnError { Timber.e(it) }
.map {
mergeRaw(
it, TourRemoteRequestModel(
estateId,
api.provideHttpUrl().toString(),
res
)
)
}
.map { it }
.doOnNext { db.blockingUpsert(it) }
}
fun fetchTourOffer(multitourId: Int, parent: Int): Observable<List<TourModel>> =
Observable.mergeDelayError(
arrayListOf(fetchDbTourList(parent), fetchApiTourList(multitourId, parent))
)
.take(1)
.doOnError { Timber.e(it) }
.map(::fromEntity)
.subscribeOn(Schedulers.io())
private fun mergeRaw(raw: List<TourPreviewRaw>, requestRequestModel: TourRemoteRequestModel) =
raw.map { mergeRaw(it, requestRequestModel) }
private fun mergeRaw(raw: TourPreviewRaw, requestRequestModel: TourRemoteRequestModel)
: TourPreviewEntity {
Timber.d("got raw: $raw")
val entity = db.getTourPreview(raw.id.toString()).firstOrNull()
?: return fromRaw(
raw
, requestRequestModel.estateId
, api.provideHttpUrl().toString()
, requestRequestModel.targetResolution
)
val fromApi = fromRaw(
raw
, requestRequestModel.estateId
, api.provideHttpUrl().toString()
, requestRequestModel.targetResolution
)
if (fromApi.updated > entity.updated
&& entity.isDownloaded == DownloadState.Downloaded
) {
entity.isDownloaded =
DownloadState.NotSynced//maxState(entity.isDownloaded, fromApi.isDownloaded)
return entity
}
// In case< where we need to update tour parent, when it was downloaded by link
// if (fromApi.estate.id!=requestRequestModel.estateId)
// entity.estate=fromApi.estate
fromApi.setMetaFileEntityId(entity.metaFileEntityId)
fromApi.targetResolution = entity.targetResolution
fromApi.isDownloaded = entity.isDownloaded
fromApi.overallSize = entity.overallSize
fromApi.downloadedSize = entity.downloadedSize
fromApi.tempSize = entity.tempSize
fromApi.overallFiles = entity.overallFiles
fromApi.downloadedFiles = entity.downloadedFiles
return fromApi
}
private fun getUserEstates(user: UserEntity) = user.deals?.map { it.estate }?.plus(
user.estates
?.asSequence()
?.filter { it.favorite }
?.asIterable()
?: arrayListOf()
)
fun fetchToursSizes(): Observable<Long> =
auth.currentUser()
.map(::getUserEstates)
.map { it.filter { estates -> estates.multitourId != null }.toList() }
.map { estates ->
val toursOffersToLoad = mutableListOf<Int>()
estates.forEach {
if (it.tours.isNullOrEmpty())
toursOffersToLoad.add(it.multitourId!!)
}
Pair(estates, toursOffersToLoad)
}
.flatMap { pair ->
if (pair.second.isNotEmpty()) {
val loadedIds = mutableListOf<String>()
return@flatMap Observable.merge(
pair.second.map {
fetchTourOffer(
it, pair.first.first { estete -> estete.multitourId == it }.id
).doOnError { Timber.e(it) }
.doOnNext { tours -> loadedIds.addAll(tours.map { t -> t.tour_id }) }
}
).takeLast(1)
.doOnNext { db.refreshEntities(pair.first) }
.map { Pair(pair.first, loadedIds) }
} else return@flatMap Observable.just(Pair(pair.first, null))
}
.flatMap { pair ->
val estates = pair.first
val r = (estates.first().user as UserEntity).targetResolution
var knownSize =
estates.flatMap {
it.tours?.map { t -> t as TourPreviewEntity }
?.asIterable() ?: arrayListOf()
}
.filter { t -> t.overallSize > 0L }
.sumByLong { it.overallSize - it.downloadedSize }
val toursToLoadSize =
estates.flatMap {
it.tours?.map { t -> t as TourPreviewEntity }
?.asIterable() ?: arrayListOf()
}
.filter { t -> t.overallSize < 1L }
.map { it.id }.toMutableList()
pair.second?.let { toursToLoadSize.addAll(it) }
if (toursToLoadSize.isNullOrEmpty()) return@flatMap Observable.just(knownSize)
else return@flatMap api.getTourFilesSizes(
toursToLoadSize, r.toString()
)
.flatMapIterable { it }
.flatMap { rawSize ->
db.getTourPreview(rawSize.id.toString())
.observable()
.map { entity ->
knownSize += rawSize.total_size
entity.apply {
overallSize = rawSize.total_size
targetResolution = rawSize.resolution
}
entity
}
.doOnNext { db.upsert(it).blockingGet() }
}.map { knownSize }
}
.subscribeOn(Schedulers.io())
private inline fun <T> Iterable<T>.sumByLong(selector: (T) -> Long): Long {
var sum = 0L
for (element in this) {
sum += selector(element)
}
return sum
}
fun downloadAllDeelsAndEstates(): Observable<Iterable<TourPreviewEntity>> =
auth.currentUser()
.map { user ->
val estatesList = getUserEstates(user)
estatesList
?.asSequence()
?.filter { it.multitourId != null }
?.toList()
?.map {
Pair(
it.multitourId!!
, TourRemoteRequestModel(
estateId = it.id
, targetResolution = user.targetResolution
)
)
}
}
.map { list ->
list.map { pair ->
api.getOfferTours(pair.first)
.map {
mergeRaw(it, pair.second)
}
.map { it }
.blockingFirst()
}
.flatten()
}
.flatMap { db.upsertTourPreview(it) }
.subscribeOn(Schedulers.io())
}
data class TourRemoteRequestModel(val estateId: Int, val token: String? = null, val targetResolution: Int)
data class ToursRemoteByIdRequestModel(val ids: List<String>, val estateId: Int,
val token: String? = null, val targetResolution: Int)
sealed class DownloadTourByIdResult{
class ReadyToPlay(val tour:TourModel): DownloadTourByIdResult()
class DownloadStarted(val tour:TourModel): DownloadTourByIdResult()
class TourError(val id:String): DownloadTourByIdResult()
}
package com.biganto.visual.roompark.player
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Handler
import android.view.OrientationEventListener
import android.view.View
import android.view.animation.AccelerateInterpolator
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
import android.view.animation.Animation.AnimationListener
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.player.unity_utils.IBigantoUnityPlugin
import com.biganto.visual.roompark.player.unity_utils.LoadTourConfig
import com.biganto.visual.roompark.player.unity_utils.UnityOrientation
import com.biganto.visual.roompark.player.unity_utils.requests_raw.UPBaseRequest
import com.biganto.visual.roompark.player.unity_utils.requests_raw.UPBuildTourRequest
import com.biganto.visual.roompark.player.unity_utils.requests_raw.UPChangeOrientationRequest
import com.bumptech.glide.Glide
import com.google.gson.GsonBuilder
import com.unity3d.player.UnityPlayer
import kotlinx.android.synthetic.main.activity_biganto_player.*
//import kotlinx.android.synthetic.main.activity_biganto_player.*
import timber.log.Timber
const val PLAY_TOUR_DATA_EXTRAS="BIGANTO_PLAYER_TOUR_DATA"
const val ERROR_TOUR_BUILD_FINISH_CODE=8601
const val UNITY_SCENE_LOADED_FADE_MILLS=600L
private fun UPBaseRequest.requestToJson():String{
return GsonBuilder().create().toJson(this, javaClass)
}
class BigantoPlayerActivity : Activity(), UnityResponseListener {
lateinit var preview: ImageView
lateinit var previewBackground: FrameLayout
lateinit var biganto: ImageView
lateinit var progress: ProgressBar
lateinit var container: FrameLayout
lateinit var unityContainer: FrameLayout
private var mUnityPlayer: UnityPlayer? = null
private var bigantoPluginListener: IBigantoUnityPlugin? = null
private lateinit var orientationListener: OrientationListener
// @Inject
// lateinit var appLifeCycle: AppLifecycleListener
private lateinit var tourObj: LoadTourConfig
override fun onCreate(savedInstanceState: Bundle?) {
Timber.d("CREATING PLAYER ACTIVITY")
super.onCreate(savedInstanceState)
orientationListener = OrientationListener(this)
// appLifeCycle = BigantoApplication.component.appLifeCycle()
//
// ProcessLifecycleOwner.get().lifecycle
// .addObserver(appLifeCycle)
setContentView(R.layout.activity_biganto_player)
container = this.frameContainer
unityContainer = this.unityFrameContainer
preview = this.playerPreview
previewBackground = this.previewBack
biganto = this.bigantoLogo
progress = this.loadingBar
if (mUnityPlayer == null) {
mUnityPlayer = UnityPlayer(this)
}
tourObj = intent.getParcelableExtra(PLAY_TOUR_DATA_EXTRAS)
val animated = AnimatedVectorDrawableCompat.create(this, R.drawable.biganto_splash_anim)
animated?.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable?) {
biganto.post { animated.start() }
}
})
biganto.setImageDrawable(animated)
animated?.start()
Glide.with(this)
.load(tourObj.previewUri)
// .centerCrop()
.into(preview)
.also {
fadeOutAndHideImage(1f, 0f, 1500, biganto)
Handler().postDelayed(
{
fadeOutAndHideImage(0f, 1f, 1500, preview)
}
, 1200
)
}
}
private fun fadeOutAndHideImage(from: Float, to: Float, duration: Long, img: ImageView) {
val fadeOut = AlphaAnimation(from, to)
fadeOut.interpolator = AccelerateInterpolator()
fadeOut.duration = duration
fadeOut.setAnimationListener(object : AnimationListener {
override fun onAnimationEnd(animation: Animation) {
if (from > to) img.visibility = View.GONE
}
override fun onAnimationRepeat(animation: Animation) {}
override fun onAnimationStart(animation: Animation) {
if (from < to) img.visibility = View.VISIBLE
}
})
img.startAnimation(fadeOut)
}
private fun quitPlayer(){
mUnityPlayer?.stop()
mUnityPlayer?.quit()
}
override fun onBackPressed() {
finish()
}
@SuppressLint("SourceLockedOrientationActivity")
override fun onDestroy() {
this.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
quitPlayer()
super.onDestroy()
}
/**
* invokes from unity by AndroidJavaObject mechanics
*/
fun setUnityPlayerListener(listener: IBigantoUnityPlugin) {
this.bigantoPluginListener = listener
// Timber.d(" UNITY SETTING LISTENER THROGH ACTIVITY")
}
override fun unitySceneLoaded() {
runOnUiThread {
preview.visibility = View.VISIBLE
}
mUnityPlayer?.view?.visibility = View.VISIBLE
bigantoPluginListener?.sendUnityTourBuildRequest(UPBuildTourRequest(tourObj).requestToJson())
}
override fun unityTourBuilded() {
runOnUiThread {
//preview.visibility = View.GONE
fadeOutAndHideImage(1f, 0f, UNITY_SCENE_LOADED_FADE_MILLS, preview)
progress.visibility = View.GONE
previewBackground.visibility = View.GONE
biganto.visibility = View.GONE
}
mUnityPlayer?.view?.visibility = View.VISIBLE
}
override fun unityTourBuildFailed() {
Timber.d("Tour build failed! Finishing activity...")
finish()
}
override fun unityTourBuildProgress(progress: Int, total: Int) {
runOnUiThread {
preview.visibility = View.VISIBLE
}
mUnityPlayer?.view?.visibility = View.VISIBLE
}
override fun unityCrushed(message: String) {
runOnUiThread {
preview.visibility = View.VISIBLE
}
mUnityPlayer?.view?.visibility = View.VISIBLE
}
override fun onNewIntent(intent: Intent) {
Timber.d("PLAYER ACTIVITY GOT INTNET")
// To support deep linking, we need to make sure that the client can get access to
// the last sent intent. The clients access this through a JNI api that allows them
// to get the intent set on launch. To update that after launch we have to manually
// replace the intent with the one caught here.
setIntent(intent)
}
// Pause Unity
override fun onPause() {
super.onPause()
mUnityPlayer?.pause()
}
// Resume Unity
override fun onResume() {
Timber.d("PLAYER ACTIVITY RESUME")
super.onResume()
mUnityPlayer?.resume()
}
override fun onStart() {
super.onStart()
orientationListener.enable()
if (mUnityPlayer?.view?.parent != null) return
mUnityPlayer?.start()
unityContainer.addView(mUnityPlayer?.view)
}
override fun onStop() {
orientationListener.disable()
bigantoPluginListener = null
mUnityPlayer?.stop()
super.onStop()
}
// Low Memory Unity
override fun onLowMemory() {
super.onLowMemory()
mUnityPlayer?.lowMemory()
}
// Trim Memory Unity
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
mUnityPlayer?.lowMemory()
}
}
// This ensures the layout will be correct.
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
mUnityPlayer?.configurationChanged(newConfig)
}
// Notify Unity of the focus change.
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
mUnityPlayer?.windowFocusChanged(hasFocus)
}
//Uncomment this, if you want control backstack inside Unity engine
// // For some reason the multiple keyevent type is not supported by the ndk.
// // Force event injection by overriding dispatchKeyEvent().
// override public boolean dispatchKeyEvent(KeyEvent event)
// {
// if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
// return mUnityPlayer.injectEvent(event);
// return super.dispatchKeyEvent(event);
// }
//
// Pass any events not handled by (unfocused) views straight to UnityPlayer
// override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
// return mUnityPlayer?.injectEvent(event) ?: false;
// }
//
// override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
// return mUnityPlayer?.injectEvent(event) ?: false
// }
//
// override fun onTouchEvent(event: MotionEvent): Boolean {
// return mUnityPlayer?.injectEvent(event) ?: false
// }
// /*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }
inner class OrientationListener(context: Context) : OrientationEventListener(context) {
private val ROTATION_O = 1
private val ROTATION_90 = 2
private val ROTATION_180 = 3
private val ROTATION_270 = 4
var rotation = 0
override fun onOrientationChanged(orientation: Int) {
var newRotation = rotation
if ((orientation < 35 || orientation > 325) && rotation != ROTATION_O) { // PORTRAIT
newRotation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
} else if (orientation in 146..214 && rotation != ROTATION_180) { // REVERSE PORTRAIT
newRotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
} else if (orientation in 56..124 && rotation != ROTATION_270) { // REVERSE LANDSCAPE
newRotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
} else if (orientation in 236..304 && rotation != ROTATION_90) { //LANDSCAPE
newRotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
}
if (newRotation != rotation) {
rotation = newRotation
// UnityBridge.runOnUnityThread {
Timber.d("Unity change orientation request")
// UnityBridge.runOnUnityThread { bigantoPluginListener?.sendUnityChangeOrientationRequest(rotation.toUnityOrientationId().requestToJson()) }
// }
}
}
fun Int.toUnityOrientationId(): UPChangeOrientationRequest {
val orId = when (this) {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT -> UnityOrientation.Portrait
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE -> UnityOrientation.LandscapeLeft
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE -> UnityOrientation.LandscapeRight
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT -> UnityOrientation.PortraitUpsideDown
else -> UnityOrientation.Portrait
}
return UPChangeOrientationRequest(orId)
}
}
}
interface UnityResponseListener{
fun unitySceneLoaded()
fun unityTourBuilded()
fun unityTourBuildProgress(progress:Int, total:Int)
fun unityCrushed(message:String)
fun unityTourBuildFailed()
}
\ No newline at end of file
package com.biganto.visual.roompark.player.unity_utils
import android.content.Context
import com.unity3d.player.UnityPlayer
/**
* Created by Vladislav Bogdashkin on 08.08.2018.
*/
class BigantoTourPlayer(p0: Context) : UnityPlayer(p0)
{
// val title = "Bigant Visual Tour Player"
// override fun kill() {
//// }
}
\ No newline at end of file
package com.biganto.visual.roompark.player.unity_utils;
public interface IBigantoUnityPlugin{
public void sendUnitySceneLoadRequest(String request);
public void sendUnityTourBuildRequest(String request);
public void sendUnityFinishRequest(String request);
public void sendUnityPauseRequest(String request);
public void sendUnityErrorRequest(String request);
public void sendUnityChangeOrientationRequest(String request);
// void onTourSceneLoaded();
// void onTourScenePrepared(TourPlayerCallback callback);
// void onTourLoaded(TourReadyCallback callback);
}
package com.biganto.visual.roompark.player.unity_utils
import android.os.Parcel
import android.os.Parcelable
/**
* Created by Vladislav Bogdashkin on 08.05.2019.
*/
data class LoadTourConfig(
val tourId:Int,
val targetResolution:Int,
val cacheFolderUri:String,
val metaUri:String,
val previewUri:String?,
val AllowVR:Boolean,
val AllowDollhouse:Boolean) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readInt(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readInt()!= 0,
parcel.readInt() == 1
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(tourId)
parcel.writeInt(targetResolution)
parcel.writeString(cacheFolderUri)
parcel.writeString(metaUri)
parcel.writeString(previewUri)
parcel.writeInt(if (AllowVR)1 else 0 )
parcel.writeInt(if (AllowDollhouse)1 else 0)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<LoadTourConfig> {
override fun createFromParcel(parcel: Parcel): LoadTourConfig {
return LoadTourConfig(parcel)
}
override fun newArray(size: Int): Array<LoadTourConfig?> {
return arrayOfNulls(size)
}
}
}
\ No newline at end of file
package com.biganto.visual.roompark.player.unity_utils;
import android.os.Handler;
import timber.log.Timber;
/**
* Created by Vladislav Bogdashkin on 23.08.2018.
*/
public final class UnityBridge {
// Содержит ссылку на C# реализацию интерфейса
// Перенаправляет вызов в Unity поток
private static Handler unityMainThreadHandler;
// Функция перевода выполнения в Unity поток, потребуется в дальнейшем
public static void runOnUnityThread(Runnable runnable) {
if (unityMainThreadHandler != null && runnable != null) {
unityMainThreadHandler.post(runnable);
}
}
public static void unRegisterHandler() {
if(unityMainThreadHandler != null)
unityMainThreadHandler.getLooper().quit();
unityMainThreadHandler=null;
}
public static void registerHandler() {
Timber.d(" handler: %s",unityMainThreadHandler );
if(unityMainThreadHandler == null) {
// Так как эту функцию вызываем всегда на старте Unity,
// этот вызов идет из нужного нам в дальнейшем потока,
// создадим для него Handler
unityMainThreadHandler = new Handler();
}
Timber.d(" handler: %s",unityMainThreadHandler );
}
}
\ No newline at end of file
package com.biganto.visual.roompark.player.unity_utils;
import com.google.gson.annotations.SerializedName;
public enum UnityLayoutViewState{
@SerializedName("0")
BINDING (0),
@SerializedName("1")
READY (1),
@SerializedName("2")
DETACHED (2),
@SerializedName("3")
DESTROYED (3);
private final int value;
public int getValue() {
return value;
}
private UnityLayoutViewState(int value) {
this.value = value;
}
}
package com.biganto.visual.roompark.player.unity_utils;
/**
* Created by Vladislav Bogdashkin on 28.08.2018.
*/
public enum UnityOrientation {
Unknown (0),
// Portrait orientation.
Portrait (1),
// Portrait orientation, upside down.
PortraitUpsideDown (2),
// Landscape orientation, counter-clockwise from the portrait orientation.
LandscapeLeft (3),
LandscapeRight (4),
// Auto-rotates the screen as necessary toward any of the enabled orientations.
AutoRotation (5);
int id;
private UnityOrientation(int i){id = i;}
public int GetID(){return id;}
public boolean IsEmpty(){return this.equals(UnityOrientation.Unknown);}
public boolean Compare(int i){return id == i;}
public static UnityOrientation GetValue(int _id)
{
UnityOrientation[] As = UnityOrientation.values();
for (UnityOrientation A : As) {
if (A.Compare(_id))
return A;
}
return UnityOrientation.Unknown;
}
}
package com.biganto.visual.roompark.player.unity_utils;
import com.google.gson.annotations.SerializedName;
public enum UnityPlayerViewState{
@SerializedName("0")
NotInitialized(0),
@SerializedName("1")
PreLoading(1),
@SerializedName("2")
Ready(2),
@SerializedName("3")
Error(3);
private final int value;
public int getValue() {
return value;
}
private UnityPlayerViewState(int value) {
this.value = value;
}
}
package com.biganto.visual.roompark.player.unity_utils.requests_raw;
/**
* Created by Vladislav Bogdashkin on 20.08.2018.
*/
//Unity Player base communication request
public class UPBaseRequest {
}
package com.biganto.visual.roompark.player.unity_utils.requests_raw;
import com.biganto.visual.roompark.player.unity_utils.LoadTourConfig;
/**
* Created by Vladislav Bogdashkin on 20.08.2018.
*/
public class UPBuildTourRequest extends UPBaseRequest{
public int TourId;
public int TargetResolution;
public String AssetsDirectory;
public String MetaPredict;
public String tour_preview;
public Boolean AllowVR=true;
public Boolean AllowDollhouse=true;
public UPBuildTourRequest(){}
public UPBuildTourRequest(
int tour_id,
int target_tex_resolution,
String sd_core_folder_uri,
String meta_file_path,
String tour_preview,
boolean allow_vr,
boolean allow_dollhouse
) {
this.TourId = tour_id;
this.TargetResolution = target_tex_resolution;
this.AssetsDirectory = sd_core_folder_uri;
this.MetaPredict = meta_file_path;
this.tour_preview = tour_preview;
this.AllowVR = allow_vr;
this.AllowDollhouse = allow_dollhouse;
}
public UPBuildTourRequest(LoadTourConfig tour) {
this(tour.getTourId(), tour.getTargetResolution(), tour.getCacheFolderUri(),
tour.getMetaUri(), tour.getPreviewUri(),tour.getAllowVR(),tour.getAllowDollhouse());
}
}
package com.biganto.visual.roompark.player.unity_utils.requests_raw;
import com.biganto.visual.roompark.player.unity_utils.UnityOrientation;
/**
* Created by Vladislav Bogdashkin on 20.08.2018.
*/
public class UPChangeOrientationRequest extends UPBaseRequest {
UnityOrientation orientation;
public UPChangeOrientationRequest(){orientation=UnityOrientation.Portrait;}
public UPChangeOrientationRequest(UnityOrientation orientation){this.orientation=orientation;}
}
package com.biganto.visual.roompark.player.unity_utils.requests_raw;
/**
* Created by Vladislav Bogdashkin on 20.08.2018.
*/
public class UPFinishRequest extends UPBaseRequest{
}
package com.biganto.visual.roompark.player.unity_utils.requests_raw;
/**
* Created by Vladislav Bogdashkin on 20.08.2018.
*/
public class UPPauseRequest extends UPBaseRequest {
}
package com.biganto.visual.roompark.player.unity_utils.requests_raw;
/**
* Created by Vladislav Bogdashkin on 20.08.2018.
*/
public class UPSceneLoadingRequest extends UPBaseRequest{
}
......@@ -34,6 +34,7 @@ import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import jp.wasabeef.glide.transformations.BlurTransformation
import jp.wasabeef.glide.transformations.ColorFilterTransformation
import jp.wasabeef.glide.transformations.CropTransformation
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
......@@ -146,7 +147,6 @@ class AlbumsScreenController :
else silentCheck = false
silentCheck
}
.doOnNext { }
.debounce(600L, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.share()
......@@ -289,8 +289,9 @@ class AlbumsScreenController :
private fun loadGlideBlurred(url:String, context: Context, drawable: Target<Drawable>) =
Glide.with(context)
.load(url)
.transform(BlurTransformation(13, 2))
.transform(ColorFilterTransformation(0xCC000000.toInt()))
.transform(CropTransformation(nestedScrollView.width,nestedScrollView.height)
,BlurTransformation(8, 1)
,ColorFilterTransformation(0xCC000000.toInt()))
.into(drawable)
override fun onAlbumSelected(): Observable<AlbumPreviewModel>
......@@ -300,6 +301,7 @@ class AlbumsScreenController :
.debounce (300L,TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { (albumsRecyclerView.adapter as AlbumListAdapter).setItems(arrayListOf())}
.share()
override fun onPhotoSelected(): Observable<PhotoModel>
......
......@@ -48,13 +48,14 @@ class AlbumsScreenPresenter @Inject constructor(
.filter { restoreModel.sub != null }
.flatMap { newState ->
interactor.switchSubscription(restoreModel.sub!!, newState)
.andThen(
Observable.just<AlbumsScreenViewState>(
AlbumsScreenViewState.SubscriptionStatus(
newState
)
)
)
.map {subs ->
val s = subs.map { it.subModel }.firstOrNull {it.id == restoreModel.sub?.id}
restoreModel.sub = s
if (s!=null)
return@map AlbumsScreenViewState.SubscriptionStatus(s.state)
else
return@map AlbumsScreenViewState.SubscriptionError(!newState)
}
.onErrorReturn { AlbumsScreenViewState.SubscriptionError(!newState) }
}
......@@ -69,8 +70,9 @@ class AlbumsScreenPresenter @Inject constructor(
val headerItemSelected = intent(AlbumsScreen::onAlbumSelected)
.doOnNext { selectedIndex = it.albumId }
.doOnNext { restoreModel.currentIndex = it.albumId }
.flatMap<AlbumsScreenViewState> { model ->
.switchMap<AlbumsScreenViewState> { model ->
requestAlbum(model.albumId)
.onErrorReturn(::parseError)
.startWith(Observable.just<AlbumsScreenViewState>(AlbumsScreenViewState.HeaderAlbumChoosed(item = model))
)
}
......
......@@ -10,6 +10,8 @@ import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyc
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import java.text.SimpleDateFormat
import java.util.*
/**
......@@ -31,12 +33,17 @@ class AlbumsHeaderAdapter : CommonRecyclerAdapter<AlbumsHeaderViewHolder, AlbumP
class AlbumsHeaderViewHolder(itemView: View) : CommonViewHolder<AlbumPreviewModel>(itemView) {
private val dateFormatter = SimpleDateFormat("dd MMMM yyyy", Locale("ru"))
@BindView(R.id.preview) lateinit var preview:ImageView
@BindView(R.id.card_title) lateinit var articleTitle:TextView
@BindView(R.id.card_updated) lateinit var articleDate:TextView
override fun onViewBound(model: AlbumPreviewModel) {
articleTitle.text = model.title
articleDate.text = dateFormatter.format(model.published)
Glide.with(itemView)
.load(model.previewUrl)
.centerCrop()
......
......@@ -9,5 +9,6 @@ import io.reactivex.Observable
interface DealScreen : BigantoBaseContract<DealScreenViewState> {
fun onSubscription(): Observable<Boolean>
fun tourCardClicked(): Observable<Int>
}
......@@ -12,10 +12,13 @@ import com.biganto.visual.roompark.base.HeaderToolbarModel
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.conductor.dialogs.tour_chooser.ChooseTourDialogController
import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.typeDoubleString
import com.biganto.visual.roompark.domain.model.typeShortString
import com.biganto.visual.roompark.presentation.screen.estate.EstateScreenController
import com.biganto.visual.roompark.util.extensions.setGone
import com.biganto.visual.roompark.util.extensions.toRubly
import com.biganto.visual.roompark.util.monades.ExceptionString
import com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
......@@ -96,14 +99,21 @@ class DealScreenController :
@BindView(R.id.start_tour_image_view) lateinit var tourScreen: RoundedImageView
private var servedDeal : DealModel? = null
override fun tourCardClicked(): Observable<Int> =
tourScreen.clicks()
.map { 1 }
.debounce(320L, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
override fun onAttach(view: View) {
super.onAttach(view)
detachDisposable.addAll(
toFlatView.clicks()
.map { servedDeal?.estate?.id?: -1000}
.map { dealModel?.estate?.id?: -1000}
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
Timber.d("got card clicked $it")
......@@ -154,8 +164,7 @@ class DealScreenController :
else silentCheck = false
silentCheck
}
.doOnNext { }
.debounce(600L, TimeUnit.MILLISECONDS)
.debounce(400L, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
......@@ -189,6 +198,7 @@ class DealScreenController :
is DealScreenViewState.RestoreView -> render(viewState)
is DealScreenViewState.SubscriptionStatus -> render(viewState)
is DealScreenViewState.SubscriptionError -> render(viewState)
is DealScreenViewState.ToursLoaded -> render(viewState)
}
}
......@@ -206,6 +216,7 @@ class DealScreenController :
silentCheck = true
sw.isChecked = viewState.subState
}
toolBar.headerToolbar.invalidate()
}
private fun render(viewState: DealScreenViewState.SubscriptionError) {
......@@ -230,6 +241,17 @@ class DealScreenController :
silentCheck = true
sw.isChecked != sw.isChecked
}
viewState.restore.deal?.let { setUpDeal(it) }
}
private fun render(viewState: DealScreenViewState.ToursLoaded) {
router.pushController(RouterTransaction.with(
ChooseTourDialogController(ArrayList(viewState.tours))
)
.popChangeHandler(DialogChangeHandler())
.pushChangeHandler(DialogChangeHandler())
)
}
private fun render(viewState: DealScreenViewState.SomeError) =
......@@ -243,49 +265,56 @@ class DealScreenController :
private var dealModel:DealModel? = null
private fun render(viewState: DealScreenViewState.LoadDeal) {
servedDeal = viewState.estate
private fun setUpDeal(deal:DealModel){
dealModel = deal
dealModel?.let {
startTourView.setGone(it.estate.multitourId == null)
dealTitle.text = resources?.getString(
viewState.estate.estate.type.typeDoubleString(),
viewState.estate.estate.number
it.estate.type.typeDoubleString(),
it.estate.number
)
info1.title.text = resources?.getString(R.string.building)
info1.text.text = viewState.estate.estate.commonInfo?.building.toString()
info1.text.text = it.estate.commonInfo?.building.toString()
info2.title.text = resources?.getString(R.string.section_begin)
info2.text.text = viewState.estate.estate.commonInfo?.section_begin.toString()
info2.text.text = it.estate.commonInfo?.section_begin.toString()
info3.title.text = resources?.getString(R.string.floor)
info3.text.text = viewState.estate.estate.commonInfo?.floor.toString()
info3.text.text = it.estate.commonInfo?.floor.toString()
info4.title.text = resources?.getString(R.string.area)
info4.text.text =
resources?.getString(R.string.area_value,viewState.estate.estate.commonInfo?.area)
resources?.getString(R.string.area_value,it.estate.commonInfo?.area)
dealSum.text = viewState.estate.opportunitySum.toRubly()
dealPayed.text = viewState.estate.paymentSum.toRubly()
dealSumToPay.text = viewState.estate.amount_pay_sum.toRubly()
dealSum.text = it.opportunitySum.toRubly()
dealPayed.text = it.paymentSum.toRubly()
dealSumToPay.text = it.amount_pay_sum.toRubly()
viewState.estate.estate.multitourPreview?.let {
it.estate.multitourPreview?.let {url ->
Glide.with(tourScreen)
.load(it)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(tourScreen)
}
dealModel = viewState.estate
toolBar.setToolbar(HeaderToolbarModel(
true
,resources?.getString(R.string.deal_back_chevron_title)
,null
,true)
)
}
}
progressLayout.removeAllViews()
private fun render(viewState: DealScreenViewState.LoadDeal) {
setUpDeal(viewState.estate)
progressLayout.removeAllViews()
viewState.statusList.forEach {
val statusLayout = LayoutInflater.from(activity)
......@@ -294,9 +323,7 @@ class DealScreenController :
,false)
as LinearLayout
Timber.d("layouted: $statusLayout")
val statusCeil = statusLayout.findViewById<StatusProgressCeil>(R.id.status)
Timber.d("layouted ceail : $statusCeil")
val position = it.orderId
val statusCount = viewState.statusList.size
......@@ -311,11 +338,11 @@ class DealScreenController :
statusCeil.invalidate()
val statusTitle = statusLayout.findViewById<MaterialTextView>(R.id.title)
Timber.d("layouted statusTitle : $statusTitle")
statusTitle.text = it.shortTitle
progressLayout.addView(statusLayout)
}
progressLayout.invalidate()
}
private fun getComponent() = DaggerDealScreenComponent.factory()
......
......@@ -3,6 +3,8 @@ package com.biganto.visual.roompark.presentation.screen.deal
import android.content.Context
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.DealInteractor
import com.biganto.visual.roompark.domain.interactor.ToursInteractor
import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
......@@ -19,6 +21,7 @@ import javax.inject.Named
class DealScreenPresenter @Inject constructor(
private val interactor: DealInteractor,
private val toursInteractor: ToursInteractor,
private val context: Context,
@Named(SELECTED_DEAL_ID_KEY) private val dealId:String
)
......@@ -28,49 +31,74 @@ class DealScreenPresenter @Inject constructor(
override fun defaultErrorViewStateHandler() =
{ e: ExceptionString -> DealScreenViewState.SomeError(e) }
private var restoreModel = RestoreModel(null)
private var restoreModel = RestoreModel(null,null)
override fun detachView() {
super.detachView()
restoreStateObservable.accept(DealScreenViewState.RestoreView(restoreModel))
}
override fun bindIntents() {
val fetchDeal = interactor.getDeal(dealId)
private val fetchDeal = interactor.getDeal(dealId)
.doOnNext { restoreModel.deal = it }
.map<DealScreenViewState>{ deal ->
DealScreenViewState.LoadDeal(deal ,interactor.getStatusListSync())
}
override fun bindIntents() {
val setRead = interactor.setDealRead(dealId)
.andThen(Observable.just(DealScreenViewState.Idle()))
val onSubChecked = intent(DealScreen::onSubscription)
.filter { restoreModel.sub != null }
.flatMap { newState ->
interactor.switchSubscription(restoreModel.sub!!, newState)
.andThen(
Observable.just<DealScreenViewState>(
DealScreenViewState.SubscriptionStatus(newState)
)
)
.map {subs ->
val s = subs.map { it.subModel }.firstOrNull {it.id == restoreModel.sub?.id}
restoreModel.sub = s
if (s!=null)
return@map DealScreenViewState.SubscriptionStatus(s.state)
else
return@map DealScreenViewState.SubscriptionError(!newState)
}
.onErrorReturn { DealScreenViewState.SubscriptionError(!newState) }
}
val fetchSubscription = interactor.getSubscriptions(dealId)
.doAfterNext { restoreModel.sub = it }
val fetchSubscription =
fetchDeal.flatMap { fetchedDealViewState ->
interactor.getSubscriptions(
(fetchedDealViewState as DealScreenViewState.LoadDeal).estate.estate_id
)
.doOnNext { restoreModel.sub = it }
.map<DealScreenViewState> { DealScreenViewState.SubscriptionStatus(it.state) }
.startWith(Observable.just<DealScreenViewState>(DealScreenViewState.Idle()))
}
.onErrorReturn (::parseError)
val onStartTours = intent(DealScreen::tourCardClicked)
.map { restoreModel.deal }
.map{ it.estate }
.flatMap {estate -> toursInteractor.getEstateTourList(estate)
.map<DealScreenViewState> { DealScreenViewState.ToursLoaded(it) }
.onErrorReturn (::parseError)
}
val state = Observable.mergeDelayError(
arrayListOf(
restoreStateObservable,
fetchDeal,
setRead,
onSubChecked,
fetchSubscription
fetchSubscription,
onStartTours
))
.doOnError { Timber.e(it) }
.onErrorReturn(::parseError)
......@@ -83,4 +111,4 @@ class DealScreenPresenter @Inject constructor(
data class RestoreModel(var sub: SubscriptionModel?)
\ No newline at end of file
data class RestoreModel(var sub: SubscriptionModel?,var deal: DealModel?)
\ No newline at end of file
......@@ -3,6 +3,7 @@ package com.biganto.visual.roompark.presentation.screen.deal
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.DealModel
import com.biganto.visual.roompark.domain.model.StatusModel
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
......@@ -17,4 +18,5 @@ sealed class DealScreenViewState : BigantoBaseViewState() {
class RestoreView(val restore:RestoreModel) : DealScreenViewState()
class SubscriptionStatus(val subState: Boolean) : DealScreenViewState()
class SubscriptionError(val subState: Boolean) : DealScreenViewState()
class ToursLoaded(val tours:List<TourModel>) : DealScreenViewState()
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.deals
import com.biganto.visual.roompark.conductor.BigantoBaseContract
import com.biganto.visual.roompark.domain.model.DealPreviewModel
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface DealsScreen : BigantoBaseContract<DealsScreenViewState> {
fun tourCardClicked(): Observable<DealPreviewModel>
}
......@@ -9,12 +9,16 @@ import com.biganto.visual.roompark.base.HeaderToolbarModel
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.conductor.dialogs.tour_chooser.ChooseTourDialogController
import com.biganto.visual.roompark.domain.model.DealPreviewModel
import com.biganto.visual.roompark.presentation.screen.deal.DealScreenController
import com.biganto.visual.roompark.presentation.screen.deals.util.DealsListAdapter
import com.biganto.visual.roompark.presentation.screen.estate.EstateScreenController
import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber
import javax.inject.Inject
......@@ -90,13 +94,6 @@ class DealsScreenController :
.popChangeHandler(FadeChangeHandler())
.pushChangeHandler(FadeChangeHandler())
)
},
(dealsRecyclerView.adapter as DealsListAdapter)
.startTour
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
Timber.d("got tour clicked $it")
showMessage(R.string.tour_not_allowed)
}
)
}
......@@ -107,6 +104,12 @@ class DealsScreenController :
bindRecycler()
}
override fun tourCardClicked(): Observable<DealPreviewModel> =
(dealsRecyclerView.adapter as DealsListAdapter)
.startTour
.map { it }
.observeOn(AndroidSchedulers.mainThread())
override fun render(viewState: DealsScreenViewState) {
super.render(viewState)
Timber.d("Render state $viewState")
......@@ -114,6 +117,8 @@ class DealsScreenController :
is DealsScreenViewState.Idle -> render(viewState)
is DealsScreenViewState.DealsLoaded -> render(viewState)
is DealsScreenViewState.SomeError -> render(viewState)
is DealsScreenViewState.ToursLoaded -> render(viewState)
is DealsScreenViewState.RestoreView -> render(viewState)
}
}
......@@ -121,10 +126,25 @@ class DealsScreenController :
}
private fun render(viewState: DealsScreenViewState.RestoreView){
(dealsRecyclerView.adapter as DealsListAdapter).addItems(viewState.restore.list)
}
private fun render(viewState: DealsScreenViewState.DealsLoaded){
(dealsRecyclerView.adapter as DealsListAdapter).addItems(viewState.items)
}
private fun render(viewState: DealsScreenViewState.ToursLoaded) {
router.pushController(RouterTransaction.with(
ChooseTourDialogController(ArrayList(viewState.tours))
)
.popChangeHandler(DialogChangeHandler())
.pushChangeHandler(DialogChangeHandler())
)
}
private fun render(viewState: DealsScreenViewState.SomeError) =
showError(viewState.exception)
......
......@@ -2,8 +2,10 @@ package com.biganto.visual.roompark.presentation.screen.deals
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.DealsInteractor
import com.biganto.visual.roompark.domain.interactor.ToursInteractor
import com.biganto.visual.roompark.domain.model.DealPreviewModel
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
......@@ -15,12 +17,21 @@ import javax.inject.Inject
class DealsScreenPresenter @Inject constructor(
private val interactor: DealsInteractor
private val interactor: DealsInteractor,
private val toursInteractor: ToursInteractor
)
: BigantoBasePresenter<DealsScreen, DealsScreenViewState>() {
override fun defaultErrorViewStateHandler() =
{e:ExceptionString -> DealsScreenViewState.SomeError(e)}
{ e: ExceptionString -> DealsScreenViewState.SomeError(e) }
private val restoreModel = RestoreModel(mutableListOf())
override fun detachView() {
super.detachView()
restoreStateObservable.accept(DealsScreenViewState.RestoreView(restoreModel))
}
override fun bindIntents() {
......@@ -29,22 +40,60 @@ class DealsScreenPresenter @Inject constructor(
val fetchDeals = interactor.fetchDeals()
.flatMap { deals ->
getStatusList
.map { List(deals.size) { index ->
.map{
List(deals.size) { index ->
DealPreviewModel(
Pair(deals[index], it)
)
}
}
}
.doOnNext { restoreModel.list = it.toMutableList() }
.map<DealsScreenViewState>(DealsScreenViewState::DealsLoaded)
.onErrorReturn(::parseError)
val getDeals = interactor.getDealsApi()
.flatMap { deals ->
getStatusList
.map{
List(deals.size) { index ->
DealPreviewModel(
Pair(deals[index], it)
)
} }
}
.map(DealsScreenViewState::DealsLoaded)
}
}
.doOnNext { restoreModel.list = it.toMutableList() }
.map<DealsScreenViewState>(DealsScreenViewState::DealsLoaded)
.onErrorReturn(::parseError)
val onStartTours = intent(DealsScreen::tourCardClicked)
.flatMap {dealPreview ->
toursInteractor.getEstateTourList(dealPreview.tourId,dealPreview.estateId)
.map { DealsScreenViewState.ToursLoaded(it) }
}
val state = restoreStateObservable
.mergeWith(fetchDeals)
.doOnError{ Timber.e(it)}
val state = Observable.mergeDelayError(
arrayListOf(
restoreStateObservable,
fetchDeals,
getDeals,
onStartTours
)
)
.doOnError { Timber.e(it) }
.onErrorReturn(::parseError)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(DealsScreenViewState::class.java), DealsScreen::render)
}
}
data class RestoreModel(
var list:MutableList<DealPreviewModel>
)
\ No newline at end of file
......@@ -2,6 +2,7 @@ package com.biganto.visual.roompark.presentation.screen.deals
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.DealPreviewModel
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
......@@ -13,5 +14,7 @@ sealed class DealsScreenViewState : BigantoBaseViewState() {
class Idle : DealsScreenViewState()
class DealsLoaded(val items:List<DealPreviewModel>) : DealsScreenViewState()
class SomeError(val exception: ExceptionString) : DealsScreenViewState()
class ToursLoaded(val tours:List<TourModel>) : DealsScreenViewState()
class RestoreView(val restore:RestoreModel) : DealsScreenViewState()
}
......@@ -34,7 +34,7 @@ class DealsListAdapter : CommonRecyclerAdapter<DealViewHolder, DealPreviewModel>
override fun getVhLayout() = R.layout.deal_card_viewholder
private val onFlatClicked = PublishSubject.create<Int>()
private val onTourClickced = PublishSubject.create<Int>()
private val onTourClickced = PublishSubject.create<DealPreviewModel>()
override fun onBindViewHolder(holder: DealViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
......@@ -73,7 +73,7 @@ class DealViewHolder(itemView: View) : CommonViewHolder<DealPreviewModel>(itemVi
@BindView(R.id.deal_read) lateinit var dealReadFlag:View
val onStartFlatObs: Observable<Int?> get() = startFlat.clicks().map { bindedModel.estateId }
val onStartTourObs: Observable<Int?> get() = startTour.clicks().map { bindedModel.tourId }
val onStartTourObs: Observable<DealPreviewModel> get() = startTour.clicks().map { bindedModel }
override fun onViewBound(model: DealPreviewModel) {
......
......@@ -15,5 +15,6 @@ interface EstateScreen : BigantoBaseContract<EstateScreenViewState> {
fun switchElectric(): Observable<Pair<Int,Boolean>>
fun showCommonInfo(): Observable<Int>
fun showExplication(): Observable<Int>
fun tourCardClicked(): Observable<Int>
}
package com.biganto.visual.roompark.presentation.screen.estate
import android.annotation.SuppressLint
import android.net.Uri
import android.os.Bundle
import android.view.View
......@@ -18,11 +19,14 @@ import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.base.StatusState
import com.biganto.visual.roompark.base.StatusToolbarModel
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.conductor.dialogs.tour_chooser.ChooseTourDialogController
import com.biganto.visual.roompark.domain.model.*
import com.biganto.visual.roompark.presentation.screen.estate.util.FlatInfoAdapter
import com.biganto.visual.roompark.util.extensions.setGone
import com.biganto.visual.roompark.util.extensions.startUrl
import com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
import com.bluelinelabs.conductor.RouterTransaction
import com.bumptech.glide.Glide
import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.tabs.TabLayout
......@@ -70,6 +74,12 @@ class EstateScreenController :
.map { planTypesTabLayout.selectedTabPosition }
.observeOn(AndroidSchedulers.mainThread())
override fun tourCardClicked(): Observable<Int> =
tourScreen.clicks()
.map { 1 }
.debounce(320L, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
private fun ViewGroup.switchMatch() =
this.findViewById<SwitchMaterial>(R.id.switch1)
.checkedChanges()
......@@ -110,6 +120,7 @@ class EstateScreenController :
@Inject
override lateinit var injectedPresenter: EstateScreenPresenter
@BindView(R.id.flatTypesCustomView)
lateinit var flatTypeView: ViewGroup
......@@ -204,7 +215,8 @@ class EstateScreenController :
if (it.scrollY > flatTitle.measuredHeight) {
val status = estateModel?.to(
StatusToolbarModel(
StatusState.AVAILABLE
if (estateModel?.availableStatus == true) StatusState.AVAILABLE
else StatusState.SOLD_OUT
, null
, resources?.getString(
estateModel?.type?.typeShortString() ?: -1
......@@ -218,7 +230,8 @@ class EstateScreenController :
} else toolBar.setToolbar(
null,
StatusToolbarModel(
StatusState.AVAILABLE, null, null
if (estateModel?.availableStatus == true) StatusState.AVAILABLE
else StatusState.SOLD_OUT, null, null
)
)
}
......@@ -226,7 +239,8 @@ class EstateScreenController :
}
private fun bindRecycler() {
flatInfoRecyclerView.isNestedScrollingEnabled = true
flatScroll.isNestedScrollingEnabled = false
flatInfoRecyclerView.isNestedScrollingEnabled = false
flatInfoRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
flatInfoRecyclerView.adapter = FlatInfoAdapter()
......@@ -251,6 +265,9 @@ class EstateScreenController :
is EstateScreenViewState.PlanTypeSelected -> render(viewState)
is EstateScreenViewState.SomeError -> render(viewState)
is EstateScreenViewState.ShowEstateInfo -> render(viewState)
is EstateScreenViewState.ToursLoaded -> render(viewState)
is EstateScreenViewState.RestoreView -> render(viewState)
}
}
......@@ -258,56 +275,33 @@ class EstateScreenController :
}
private fun render(viewState: EstateScreenViewState.SomeError) =
showError(viewState.exception)
private var estateModel : EstateModel? = null
private fun render(viewState: EstateScreenViewState.LoadEstate) {
estateModel = viewState.estate
toolBar.setToolbar(
null, StatusToolbarModel(StatusState.AVAILABLE,null, null)
)
flatTitle.text = resources?.getString(viewState.estate.type.typeShortString()
,viewState.estate.number)
siteLink.setGone(viewState.estate.url == null)
viewState.estate.url?.let {url ->
siteLink.setOnClickListener{
activity?.startUrl(url)
setEstateInfo(viewState.estate)
}
}
startTour.setGone(viewState.estate.multitourId == null)
startTourDivider.setGone(viewState.estate.multitourId == null)
viewState.estate.multitourPreview?.let {
Glide.with(tourScreen)
.load(it)
.into(tourScreen)
}
when(viewState.estate.type){
FlatType.FLAT -> {
flatTypeView.setGone(false)
infoTabDivicder.setGone(false)
explicationTab.setGone(false)
}
else -> {
flatTypeView.setGone(true)
infoTabDivicder.setGone(true)
explicationTab.setGone(true)
}
}
private fun render(viewState: EstateScreenViewState.RestoreView) {
setEstateInfo(viewState.restore.estate)
}
private fun render(viewState: EstateScreenViewState.ShowEstateInfo) {
(flatInfoRecyclerView.adapter as FlatInfoAdapter).setItems(viewState.info)
}
private fun render(viewState: EstateScreenViewState.ToursLoaded) {
router.pushController(RouterTransaction.with(
ChooseTourDialogController(ArrayList(viewState.tours)))
.popChangeHandler(DialogChangeHandler())
.pushChangeHandler(DialogChangeHandler())
)
}
private fun render(viewState: EstateScreenViewState.LoadPlanTypes) {
planTypesTabLayout.removeAllTabs()
......@@ -378,6 +372,7 @@ class EstateScreenController :
}
@SuppressLint("SetJavaScriptEnabled")
private fun render(viewState: EstateScreenViewState.LoadPlan) {
planWebView.settings.javaScriptEnabled = true
planWebView.clearCache(true)
......@@ -385,11 +380,59 @@ class EstateScreenController :
planWebView.loadUrl(uri)
}
private fun setEstateInfo(estateModel: EstateModel?){
this.estateModel = estateModel
toolBar.setToolbar(
null,
StatusToolbarModel(
if (estateModel?.availableStatus == true) StatusState.AVAILABLE
else StatusState.SOLD_OUT
, null
, null
)
)
estateModel?.let {estate ->
flatTitle.text = resources?.getString(estate.type.typeShortString()
,estate.number)
}
siteLink.setGone(estateModel?.url == null)
estateModel?.url?.let {url ->
siteLink.setOnClickListener{
activity?.startUrl(url)
}
}
startTour.setGone(estateModel?.multitourId == null)
startTourDivider.setGone(estateModel?.multitourId == null)
estateModel?.multitourPreview?.let {
Glide.with(tourScreen)
.load(it)
.into(tourScreen)
}
when(estateModel?.type){
FlatType.FLAT -> {
flatTypeView.setGone(false)
infoTabDivicder.setGone(false)
explicationTab.setGone(false)
}
else -> {
flatTypeView.setGone(true)
infoTabDivicder.setGone(true)
explicationTab.setGone(true)
}
}
}
private fun getComponent() = DaggerEstateScreenComponent.factory()
.create(RoomParkApplication.component,activity as RoomParkMainActivity
,args.getInt(SELECTED_ESTATE_ID_KEY))
.inject(this)
override fun getLayoutId(): Int = R.layout.flat_full_card_screen
......
......@@ -5,6 +5,7 @@ import androidx.annotation.StringRes
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.EstateInteractor
import com.biganto.visual.roompark.domain.interactor.ToursInteractor
import com.biganto.visual.roompark.domain.model.*
import com.biganto.visual.roompark.presentation.screen.estate.util.DisplayInfoModel
import com.biganto.visual.roompark.util.extensions.toRubly
......@@ -23,16 +24,12 @@ import javax.inject.Named
class EstateScreenPresenter @Inject constructor(
private val interactor: EstateInteractor,
private val toursInteractor: ToursInteractor,
private val context: Context,
@Named(SELECTED_ESTATE_ID_KEY) private val estateId:Int
)
: BigantoBasePresenter<EstateScreen, EstateScreenViewState>() {
private var planList: List<PlanPresetModel>? = null
private var estate: EstateModel? = null
private var showType: InfoShowType = InfoShowType.COMMON_INFO
override fun defaultErrorViewStateHandler() =
{ e: ExceptionString -> EstateScreenViewState.SomeError(e) }
......@@ -40,20 +37,31 @@ class EstateScreenPresenter @Inject constructor(
private fun getPlan(plan: PlanPresetModel): Observable<EstateScreenViewState> =
interactor.getPlan(plan)
.map<EstateScreenViewState> { EstateScreenViewState.LoadPlan(it) }
.onErrorReturn (::parseError)
private var restoreModel = RestoreModel(null,null)
override fun detachView() {
super.detachView()
restoreStateObservable.accept(EstateScreenViewState.RestoreView(restoreModel))
}
override fun bindIntents() {
val prefetchCards = interactor.getEstate(estateId)
.doOnNext { estate = it.copy() }
.map { EstateScreenViewState.LoadEstate(it) }
.doOnNext { restoreModel.estate = it.copy() }
.map<EstateScreenViewState> { EstateScreenViewState.LoadEstate(it) }
.onErrorReturn (::parseError)
val fetchPlans = interactor.getPlanTypes(estateId)
.doOnNext { planList = it.toList() }
.map { EstateScreenViewState.LoadPlanTypes(it) }
.doOnNext {restoreModel.planList = it.toList() }
.map<EstateScreenViewState> { EstateScreenViewState.LoadPlanTypes(it) }
.onErrorReturn (::parseError)
val fetchPlan = intent(EstateScreen::planTypesTabSelected)
.map { planList?.get(it) }
.flatMap {planPreset ->
.map { restoreModel.planList?.get(it) }
.flatMap { planPreset ->
interactor.getPlan(planPreset)
.map<EstateScreenViewState> { plan -> EstateScreenViewState.LoadPlan(plan) }
.startWith(
......@@ -65,66 +73,80 @@ class EstateScreenPresenter @Inject constructor(
Observable.just<EstateScreenViewState>(
EstateScreenViewState.ShowEstateInfo(
showType
,if (showType== InfoShowType.COMMON_INFO)
mapCommonInfo(estate?.commonInfo)
restoreModel.showType
, if (restoreModel.showType == InfoShowType.COMMON_INFO)
mapCommonInfo(restoreModel.estate?.commonInfo)
else mapCommonInfo(planPreset.explication)
)
)
)
.onErrorReturn (::parseError)
}
val switchSizes = intent(EstateScreen::switchSizes)
.map { pair ->
val plan = planList?.first { it.planId == pair.first }
val plan = restoreModel.planList?.first { it.planId == pair.first }
plan?.switchFeature<FeatureModel.Sizes>(pair.second)
plan
}.flatMap(::getPlan)
val switchFurn = intent(EstateScreen::switchFurniture)
.map { pair ->
val plan = planList?.first { it.planId == pair.first }
val plan = restoreModel.planList?.first { it.planId == pair.first }
plan?.switchFeature<FeatureModel.Furniture>(pair.second)
plan
}.flatMap(::getPlan)
val switchWalls = intent(EstateScreen::switchWalls)
.map { pair ->
val plan = planList?.first { it.planId == pair.first }
val plan = restoreModel.planList?.first { it.planId == pair.first }
plan?.switchFeature<FeatureModel.Walls>(pair.second)
plan
}.flatMap(::getPlan)
val switchElectric = intent(EstateScreen::switchElectric)
.map { pair ->
val plan = planList?.first { it.planId == pair.first }
val plan = restoreModel.planList?.first { it.planId == pair.first }
plan?.switchFeature<FeatureModel.Electric>(pair.second)
plan
}.flatMap(::getPlan)
val showInfo = intent(EstateScreen::showCommonInfo)
.doOnNext { showType = InfoShowType.COMMON_INFO }
.map { estate?.commonInfo}
.map (::mapCommonInfo)
.map { EstateScreenViewState.ShowEstateInfo(showType,it) }
.doOnNext { restoreModel.showType = InfoShowType.COMMON_INFO }
.map { restoreModel.estate?.commonInfo }
.map(::mapCommonInfo)
.map { EstateScreenViewState.ShowEstateInfo(restoreModel.showType, it) }
val showExplication = intent(EstateScreen::showExplication)
.doOnNext { showType = InfoShowType.EXPLICATIONS }
.map { planList?.get(it)?.explication}
.map (::mapCommonInfo)
.map { EstateScreenViewState.ShowEstateInfo(showType,it) }
val state = restoreStateObservable
.mergeWith(prefetchCards)
.mergeWith(fetchPlans)
.mergeWith(fetchPlan)
.mergeWith(switchElectric)
.mergeWith(switchFurn)
.mergeWith(switchSizes)
.mergeWith(switchWalls)
.mergeWith(showInfo)
.mergeWith(showExplication)
.doOnNext { restoreModel.showType = InfoShowType.EXPLICATIONS }
.map { restoreModel.planList?.get(it)?.explication }
.map(::mapCommonInfo)
.map { EstateScreenViewState.ShowEstateInfo(restoreModel.showType, it) }
val onStartTours = intent(EstateScreen::tourCardClicked)
.map { restoreModel.estate }
.flatMap {estate -> toursInteractor.getEstateTourList(estate)
.map { EstateScreenViewState.ToursLoaded(it) }
}
val state = Observable.mergeDelayError(
arrayListOf(
restoreStateObservable,
prefetchCards,
fetchPlans,
fetchPlan,
switchElectric,
switchFurn,
switchSizes,
switchWalls,
showInfo,
onStartTours,
showExplication
)
)
.doOnError { Timber.e(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
......@@ -187,3 +209,8 @@ enum class InfoShowType{
COMMON_INFO,
EXPLICATIONS
}
data class RestoreModel(var planList: List<PlanPresetModel>? = null,
var estate: EstateModel? = null,
var showType: InfoShowType = InfoShowType.COMMON_INFO)
\ No newline at end of file
......@@ -3,6 +3,7 @@ package com.biganto.visual.roompark.presentation.screen.estate
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.PlanPresetModel
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.presentation.screen.estate.util.DisplayInfoModel
import com.biganto.visual.roompark.util.monades.ExceptionString
......@@ -19,4 +20,6 @@ sealed class EstateScreenViewState : BigantoBaseViewState() {
class LoadPlan(val planBody:String) : EstateScreenViewState()
class SomeError(val exception: ExceptionString) : EstateScreenViewState()
class ShowEstateInfo(val showType: InfoShowType, val info:List<DisplayInfoModel>) : EstateScreenViewState()
class ToursLoaded(val tours:List<TourModel>) : EstateScreenViewState()
class RestoreView(val restore:RestoreModel) : EstateScreenViewState()
}
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.favorites
import com.biganto.visual.roompark.conductor.BigantoBaseContract
import com.biganto.visual.roompark.domain.model.EstateModel
import io.reactivex.Observable
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
interface FavoritesScreen : BigantoBaseContract<FavoritesScreenViewState> {
fun tourCardClicked(): Observable<EstateModel>
}
......@@ -9,13 +9,18 @@ import com.biganto.visual.roompark.base.HeaderToolbarModel
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.conductor.dialogs.tour_chooser.ChooseTourDialogController
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.presentation.screen.estate.EstateScreenController
import com.biganto.visual.roompark.presentation.screen.favorites.util.FavoritesListAdapter
import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
......@@ -38,6 +43,11 @@ class FavoritesScreenController :
@BindView(R.id.favorites_cards_recycler_view)
lateinit var favoritesRecyclerView: RecyclerView
override fun tourCardClicked(): Observable<EstateModel> =
(favoritesRecyclerView.adapter as FavoritesListAdapter)
.startTour
.debounce(220L, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
override fun onAttach(view: View) {
super.onAttach(view)
......@@ -56,6 +66,7 @@ class FavoritesScreenController :
}
private fun setToolbar(){
R.string.area_living
favoritesRecyclerView.isNestedScrollingEnabled = false
toolBar.setToolbar(
......@@ -66,7 +77,7 @@ class FavoritesScreenController :
}
private fun bindRecycler() {
favoritesRecyclerView.isNestedScrollingEnabled = true
favoritesRecyclerView.isNestedScrollingEnabled = false
favoritesRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
favoritesRecyclerView.adapter = FavoritesListAdapter()
......@@ -93,6 +104,8 @@ class FavoritesScreenController :
is FavoritesScreenViewState.Idle -> render(viewState)
is FavoritesScreenViewState.FavoriteEstatesLoaded -> render(viewState)
is FavoritesScreenViewState.SomeError -> render(viewState)
is FavoritesScreenViewState.ToursLoaded -> render(viewState)
is FavoritesScreenViewState.RestoreView -> render(viewState)
}
}
......@@ -100,6 +113,20 @@ class FavoritesScreenController :
}
private fun render(viewState: FavoritesScreenViewState.RestoreView){
(favoritesRecyclerView.adapter as FavoritesListAdapter).addItems(viewState.restore.list)
}
private fun render(viewState: FavoritesScreenViewState.ToursLoaded) {
router.pushController(RouterTransaction.with(
ChooseTourDialogController(ArrayList(viewState.tours))
)
.popChangeHandler(DialogChangeHandler())
.pushChangeHandler(DialogChangeHandler())
)
}
private fun render(viewState: FavoritesScreenViewState.SomeError) =
showError(viewState.exception)
......
......@@ -2,7 +2,10 @@ package com.biganto.visual.roompark.presentation.screen.favorites
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.FavoritesInteractor
import com.biganto.visual.roompark.domain.interactor.ToursInteractor
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
......@@ -14,26 +17,62 @@ import javax.inject.Inject
class FavoritesScreenPresenter @Inject constructor(
private val interactor: FavoritesInteractor
private val interactor: FavoritesInteractor,
private val toursInteractor: ToursInteractor
)
: BigantoBasePresenter<FavoritesScreen, FavoritesScreenViewState>() {
override fun defaultErrorViewStateHandler() =
{e: ExceptionString -> FavoritesScreenViewState.SomeError(e)}
{ e: ExceptionString -> FavoritesScreenViewState.SomeError(e) }
private val restoreModel = RestoreModel(mutableListOf())
override fun detachView() {
super.detachView()
restoreStateObservable.accept(FavoritesScreenViewState.RestoreView(restoreModel))
}
override fun bindIntents() {
val prefetchCards = interactor.getFavoritesForCurrentUser()
.map { FavoritesScreenViewState.FavoriteEstatesLoaded(it) }
val prefetchCards = interactor.cachedFavorites()
.doOnNext { restoreModel.list = it.toMutableList() }
.map<FavoritesScreenViewState> { FavoritesScreenViewState.FavoriteEstatesLoaded(it) }
.onErrorReturn (::parseError)
val invalidateData = interactor.getFavoritesForCurrentUser()
.doOnNext { restoreModel.list = it.toMutableList() }
.map<FavoritesScreenViewState> { FavoritesScreenViewState.FavoriteEstatesLoaded(it) }
.onErrorReturn (::parseError)
val state = restoreStateObservable
.mergeWith(prefetchCards)
.doOnError{ Timber.e(it)}
val onStartTours = intent(FavoritesScreen::tourCardClicked)
.flatMap {estate -> toursInteractor.getEstateTourList(estate)
.map<FavoritesScreenViewState> { FavoritesScreenViewState.ToursLoaded(it) }
.onErrorReturn (::parseError)
}
val state = Observable.mergeDelayError(
arrayListOf(
restoreStateObservable,
prefetchCards,
invalidateData,
onStartTours
)
)
.doOnError { Timber.e(it) }
.onErrorReturn(::parseError)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(FavoritesScreenViewState::class.java), FavoritesScreen::render)
subscribeViewState(
state.cast(FavoritesScreenViewState::class.java),
FavoritesScreen::render
)
}
}
data class RestoreModel(
var list:MutableList<EstateModel>
)
\ No newline at end of file
......@@ -2,6 +2,7 @@ package com.biganto.visual.roompark.presentation.screen.favorites
import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.EstateModel
import com.biganto.visual.roompark.domain.model.TourModel
import com.biganto.visual.roompark.util.monades.ExceptionString
/**
......@@ -13,4 +14,6 @@ sealed class FavoritesScreenViewState : BigantoBaseViewState() {
class Idle : FavoritesScreenViewState()
class FavoriteEstatesLoaded(val items: List<EstateModel>) : FavoritesScreenViewState()
class SomeError(val exception: ExceptionString) : FavoritesScreenViewState()
class ToursLoaded(val tours:List<TourModel>) : FavoritesScreenViewState()
class RestoreView(val restore:RestoreModel) : FavoritesScreenViewState()
}
\ No newline at end of file
......@@ -14,6 +14,10 @@ import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyc
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder
import com.biganto.visual.roompark.util.extensions.setGone
import com.biganto.visual.roompark.util.extensions.startUrl
import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.view.clicks
import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject
/**
* Created by Vladislav Bogdashkin on 16.10.2019.
......@@ -22,11 +26,23 @@ import com.biganto.visual.roompark.util.extensions.startUrl
class FavoritesListAdapter : CommonRecyclerAdapter<FavoriteViewHolder,EstateModel>() {
override val vhKlazz = FavoriteViewHolder::class
override fun getVhLayout(): Int = R.layout.favorite_card_viewholder
private val onTourClickced = PublishSubject.create<EstateModel>()
override fun onBindViewHolder(holder: FavoriteViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
holder.onStartTourObs.subscribe(onTourClickced)
}
val startTour get() = onTourClickced
}
class FavoriteViewHolder(itemView: View) : CommonViewHolder<EstateModel>(itemView) {
@BindView(R.id.avaliable_text) lateinit var availableText: MaterialTextView
@BindView(R.id.avaliable_status) lateinit var availableStatus:View
@BindView(R.id.object_card_title) lateinit var estateTitle: TextView
@BindView(R.id.common_info_block) lateinit var commonInfo:View
@BindView(R.id.start_tour_button) lateinit var startTour:View
......@@ -45,10 +61,14 @@ class FavoriteViewHolder(itemView: View) : CommonViewHolder<EstateModel>(itemVie
init {
ButterKnife.bind(this, itemView)
}
val onStartTourObs: Observable<EstateModel> get() =
startTour.clicks().filter { bindedModel.availableStatus }.map { bindedModel }
override fun onViewBound(model: EstateModel) {
estateTitle.text =
itemView.context.resources?.getString(model.type.typeDoubleString(),model.number)
......@@ -57,6 +77,15 @@ class FavoriteViewHolder(itemView: View) : CommonViewHolder<EstateModel>(itemVie
startTour.setGone(model.type != FlatType.FLAT)
startTour.setGone(model.multitourId == null)
availableStatus.isEnabled = model.availableStatus
availableText.text = itemView.resources.getString(
if (model.availableStatus) R.string.estate_avalibale
else R.string.estate_sold_out
)
siteLink.setGone(model.url == null)
siteLinkDivider.setGone(model.url == null)
model.url?.let {url ->
......@@ -80,11 +109,11 @@ class FavoriteViewHolder(itemView: View) : CommonViewHolder<EstateModel>(itemVie
if (info.area == null) info1.visibility = View.GONE
else { info4.title().text = "Общая, м²"; info4.text().text = info.area.toString()}
if (info.price_meter == null) info1.visibility = View.GONE
else { info5.title().text = "Цена за м²"; info5.text().text = info.price_meter.toRubles()}
if (info.price_meter == null && !bindedModel.availableStatus) info1.visibility = View.GONE
else { info5.title().text = "Цена за м²"; info5.text().text = info.price_meter?.toRubles()}
if (info.price == null) info1.visibility = View.GONE
else { info6.title().text = "Стоимость"; info6.text().text = info.price.toRubles()}
if (info.price == null && !bindedModel.availableStatus) info1.visibility = View.GONE
else { info6.title().text = "Стоимость"; info6.text().text = info.price?.toRubles()}
if (true) info7.visibility = View.GONE
else { info7.title().text = "вщщ"; info7.text().text = info.building.toString()}
......
......@@ -89,7 +89,6 @@ class ArticlesScreenController :
lateinit var emptyListNotice: MaterialTextView
private fun setToolbar() {
articlesRecyclerView.isNestedScrollingEnabled = false
......
......@@ -43,29 +43,30 @@ class ArticlesScreenPresenter @Inject constructor(
.filter { restoreModel.sub != null }
.flatMap { newState ->
interactor.switchSubscription(restoreModel.sub!!, newState)
.andThen(
Observable.just<ArticlesScreenViewState>(
ArticlesScreenViewState.SubscriptionStatus(
newState
)
)
)
.map {subs ->
val s = subs.map { it.subModel }.firstOrNull {it.id == restoreModel.sub?.id}
restoreModel.sub = s
if (s!=null)
return@map ArticlesScreenViewState.SubscriptionStatus(s.state)
else
return@map ArticlesScreenViewState.SubscriptionError(!newState)
}
.onErrorReturn { ArticlesScreenViewState.SubscriptionError(!newState) }
}
val fetchSubscription = interactor.getSubscriptions(feedId)
.doOnNext { Timber.w("got smthng: $it") }
.doAfterNext { restoreModel.sub = it }
.map<ArticlesScreenViewState> { ArticlesScreenViewState.SubscriptionStatus(it.state) }
.startWith(Observable.just<ArticlesScreenViewState>(ArticlesScreenViewState.Idle()))
val prefetchCards = interactor.fetchArticles(feedId)
.doOnNext { restoreModel.articles.addAll(it.articles) }
.map<ArticlesScreenViewState> { ArticlesScreenViewState.ArticlesLoaded(it.articles) }
.startWith(Observable.just<ArticlesScreenViewState>(ArticlesScreenViewState.Idle()))
val getNewArticlesPage = intent(ArticlesScreen::requsetsNewArticles)
.flatMap {
interactor.fetchArticlesPage(feedId, FEED_PAGE_SIZE, it)
.flatMap {currentItem ->
interactor.fetchArticlesPage(feedId, FEED_PAGE_SIZE, currentItem)
.map<ArticlesScreenViewState> { articlePage ->
articlePage.articles
.asSequence()
......
......@@ -18,3 +18,4 @@ sealed class ArticlesScreenViewState : BigantoBaseViewState() {
class SubscriptionStatus(val subState: Boolean) : ArticlesScreenViewState()
class SubscriptionError(val subState: Boolean) : ArticlesScreenViewState()
}
......@@ -41,6 +41,9 @@ class AlbumCardViewHolder(itemView: View) : CommonViewHolder<AlbumPreviewModel>(
articleTitle.text = model.title
articleTitle.text = model.title
articleDate.text = dateFormatter.format(model.published)
Glide.with(preview)
.load(model.previewUrl)
.centerCrop()
......
......@@ -7,18 +7,14 @@ import android.view.Menu
import android.view.View
import android.view.ViewGroup
import androidx.annotation.NonNull
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.contains
import androidx.core.view.get
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import com.biganto.visual.roompark.base.ICollapsingToolBar
import com.biganto.visual.roompark.base.IConductorActivity
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.bottomnavigation.BottomNavigationView
import timber.log.Timber
/**
* Created by Vladislav Bogdashkin on 11.10.2019.
......@@ -119,53 +115,8 @@ class BNVRouterPagerAdapter(
router.backstack.forEach { it.controller().setOptionsMenuHidden(false) }
currentPrimaryRouter = router
}
drawToolbar(position)
}
fun drawToolbar(position: Int){
return
val toolBar = currentPrimaryRouter?.activity as ICollapsingToolBar
toolBar.showAll()
toolBar.appBar.setExpanded(false,true)
toolBar.appBar.setLiftable(true)
toolBar.appBar.setLifted(true)
toolBar.appBarScrollable(false)
toolBar.topAppBar.title = when(position){
0 -> "dsfsdf"
1 -> "ИЗБРАННОЕ"
2 -> "МОИ СДЕЛКИ"
3 -> "СМОТРЕТЬ\nКВАРТИРУ"
4 -> "НАСТРОЙКИ"
else -> ""
}
// toolBar.appBar.visibility=View.VISIBLE
val cc = (currentPrimaryRouter?.activity as IConductorActivity)
Timber.d(" posicione $position")
when(position){
0 -> {
Timber.d(" IN POSOIITION 0 ")
// toolBar.appBar.visibility = Toolbar.GONE;
// toolBar.topAppBar.visibility = View.GONE;
val params: CoordinatorLayout.LayoutParams = cc.conductorContainer.layoutParams as CoordinatorLayout.LayoutParams
params.behavior = null
}
1 ->{}
2 ->{}
3 -> {
toolBar.appBar.setLifted(false)
toolBar.appBar.setLiftable(false)
}
4 ->{}
else -> {}
}
}
override fun isViewFromObject(view: View, router: Any): Boolean {
require(router is Router) { "Non-router object in router stack!" }
......
......@@ -9,6 +9,7 @@ import io.reactivex.Observable
*/
interface SettingsScreen : BigantoBaseContract<SettingsScreenViewState> {
// fun downloadAllTours(): Observable<Int>
fun signOut(): Observable<Int>
fun clearCache(): Observable<Int>
fun refreshCacheInfo(): Observable<Int>
......
......@@ -7,16 +7,19 @@ import androidx.core.widget.NestedScrollView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import butterknife.OnClick
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.HeaderToolbarModel
import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.conductor.dialogs.StartPlansDownloadingDialogController
import com.biganto.visual.roompark.conductor.dialogs.change_handler.DialogChangeHandler
import com.biganto.visual.roompark.conductor.dialogs.tour_chooser.StartToursDownloadingDialogController
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.presentation.screen.settings.util.CahcedListAdapter
import com.biganto.visual.roompark.presentation.screen.settings.util.PushListAdapter
import com.biganto.visual.roompark.presentation.screen.splash.SplashScreenController
import com.biganto.visual.roompark.util.extensions.bytesToSize
import com.biganto.visual.roompark.util.extensions.setGone
import com.biganto.visual.roompark.util.monades.ExceptionString
import com.bluelinelabs.conductor.RouterTransaction
......@@ -47,10 +50,15 @@ class SettingsScreenController :
override fun clearCache(): Observable<Int> =
clearCacheButton.clicks()
.debounce ( 500, TimeUnit.MICROSECONDS )
.debounce ( 120, TimeUnit.MICROSECONDS )
.map { Timber.d("Clicked clear cache button"); 1 }
.observeOn(AndroidSchedulers.mainThread())
// override fun downloadAllTours(): Observable<Int> =
// toursDownloaderButton.clicks().filter { false }
// .map { 1 }.observeOn(AndroidSchedulers.mainThread())
private val refreshEmitter = BehaviorRelay.create<Int>()
override fun refreshCacheInfo(): Observable<Int> = refreshEmitter
......@@ -101,6 +109,14 @@ class SettingsScreenController :
@BindView(R.id.progress_lock_background)
lateinit var progressShame:View
@OnClick(R.id.downloadFlatCardsIcon)
fun onStartTourPlans(){
router.pushController(RouterTransaction.with(StartPlansDownloadingDialogController())
.pushChangeHandler(DialogChangeHandler())
.popChangeHandler(DialogChangeHandler())
)
}
private fun setToolbar(){
toolBar.appBar.liftOnScrollTargetViewId = R.id.nestedScrollContainer
pushRecycler.isNestedScrollingEnabled = false
......@@ -113,6 +129,15 @@ class SettingsScreenController :
)
}
@OnClick(R.id.downloadToursIcon)
fun startDownloadingDialog(){
router.pushController(RouterTransaction.with(StartToursDownloadingDialogController())
.pushChangeHandler(DialogChangeHandler())
.popChangeHandler(DialogChangeHandler())
)
}
private fun bindRecycler(){
pushRecycler.isNestedScrollingEnabled = true
pushRecycler.layoutManager =
......@@ -129,6 +154,7 @@ class SettingsScreenController :
override fun onViewBound(v: View) {
bottomNavigationController.show()
setToolbar()
bindRecycler()
}
......@@ -142,10 +168,12 @@ class SettingsScreenController :
is SettingsScreenViewState.SomeError -> render(viewState)
is SettingsScreenViewState.SignOut -> render(viewState)
is SettingsScreenViewState.OnCacheDeleting -> render(viewState)
is SettingsScreenViewState.OnPlanTypesPrefetch -> render(viewState)
is SettingsScreenViewState.LoadCachInfo -> render(viewState)
is SettingsScreenViewState.LoadSubscriptions -> render(viewState)
is SettingsScreenViewState.SubscriptionStatus -> render(viewState)
is SettingsScreenViewState.SubscriptionError -> render(viewState)
is SettingsScreenViewState.OnSizePrefetch -> render(viewState)
}
}
......@@ -195,11 +223,6 @@ class SettingsScreenController :
@SuppressLint("SetTextI18n")
private fun render(viewState: SettingsScreenViewState.LoadSettingsList){
toursDownloaderTitle.text = viewState.settings.offlineStoreData[0].title +
"(${viewState.settings.offlineStoreData[0].amountBytes.bytesToSize()})"
flatDownloaderTitle.text = viewState.settings.offlineStoreData[1].title +
"(${viewState.settings.offlineStoreData[1].amountBytes.bytesToSize()})"
}
private fun getComponent() = DaggerSettingsScreenComponent.factory()
......
package com.biganto.visual.roompark.presentation.screen.settings
import android.content.Context
import com.biganto.visual.roompark.base.BaseRoomParkActivity
import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.di.dagger.AppComponent
import com.biganto.visual.roompark.di.dagger.PerScreen
......@@ -33,7 +33,7 @@ abstract class SettingsScreenModule{
@PerScreen
@Binds
abstract fun provideContext(activity: RoomParkMainActivity): Context
abstract fun provideActivity(activity: RoomParkMainActivity): BaseRoomParkActivity
// @PerScreen
// @Binds
......
package com.biganto.visual.roompark.presentation.screen.settings
import android.content.Context
import com.biganto.visual.roompark.base.BaseRoomParkActivity
import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.SettingsInteractor
import com.biganto.visual.roompark.domain.model.CachedDataModel
import com.biganto.visual.roompark.domain.model.SubscriptionModel
import com.biganto.visual.roompark.domain.model.TitledSubscriptionModel
import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
* Created by Vladislav Bogdashkin on 30.09.2019.
*/
class SettingsScreenPresenter @Inject constructor(
private val interactor: SettingsInteractor,
private val activity: Context
private val activity: BaseRoomParkActivity
)
: BigantoBasePresenter<SettingsScreen, SettingsScreenViewState>() {
......@@ -35,34 +34,39 @@ class SettingsScreenPresenter @Inject constructor(
override fun defaultErrorViewStateHandler() =
{ e: ExceptionString -> SettingsScreenViewState.SomeError(e) }
// private val cacheSizeRefresher = PublishRelay.create<Int>()
// private val cahcObs =
// cacheSizeRefresher.flatMap {
// Observable.merge(
// arrayListOf(
// fetchToursSize, fetchPlansSize
// )
// )
// }
// private val fetchToursSize: Observable<SettingsScreenViewState> = interactor.fetchToursSizes()
// .map { SettingsScreenViewState.OnSizePrefetch(it) }
//
// private val fetchPlansSize: Observable<SettingsScreenViewState> = interactor.fetchPlanTypesSizes()
// .map { it* TYPICAL_PLAN_SIZE }
// .map { SettingsScreenViewState.OnPlanTypesPrefetch(it) }
override fun bindIntents() {
// val onDownloadTours = intent(SettingsScreen::downloadAllTours)
// .flatMap { interactor.startToursDownloading()
// .andThen(Observable.just(SettingsScreenViewState.Idle()))
// }
val onSubChecked = intent(SettingsScreen::onSubscription)
.flatMap { sub ->
interactor.switchSubscription(sub, !sub.state)
.andThen(
Observable.just<SettingsScreenViewState>(
SettingsScreenViewState.SubscriptionStatus(
sub.id, !sub.state
)
)
.doOnNext {
val ind = restoreModel
.subs
.indexOfFirst { it.subModel.id == sub.id }
restoreModel.subs[ind] =
TitledSubscriptionModel(
title = restoreModel.subs[ind].title,
subModel = SubscriptionModel(
topic = sub.topic,
id = restoreModel.subs[ind].subModel.id,
state = !sub.state
)
)
.map<SettingsScreenViewState> {list ->
restoreModel.subs = list.sortedBy { it.subModel.id }.toMutableList()
SettingsScreenViewState.LoadSubscriptions(restoreModel.subs)
}
)
.doOnError { Timber.e(it) }
.onErrorReturn { SettingsScreenViewState.SubscriptionError(sub.id, sub.state) }
}
......@@ -75,10 +79,13 @@ class SettingsScreenPresenter @Inject constructor(
.doOnNext { cached -> cached.sortBy { it.id } }
.doOnNext { restoreModel.cacheInfo = it }
.map { SettingsScreenViewState.LoadCachInfo(it) }
// .doOnNext {cacheSizeRefresher.accept(1) }
val fetchSubscriptions = interactor.getSubscriptions()
.doOnNext { restoreModel.subs = it.toMutableList() }
.map { SettingsScreenViewState.LoadSubscriptions(it) }
.map {list ->
restoreModel.subs = list.sortedBy { it.subModel.id }.toMutableList()
SettingsScreenViewState.LoadSubscriptions(restoreModel.subs)
}
val onSignOut = intent(SettingsScreen::signOut)
.flatMap {
......@@ -95,19 +102,27 @@ class SettingsScreenPresenter @Inject constructor(
.flatMap {
interactor.deleteCacheFiles()
.map<SettingsScreenViewState> {
Timber.d(" got progress: ${it.first} / ${it.second.toFloat()}")
// if (it.first == it.second)
// cacheSizeRefresher.accept(1)
SettingsScreenViewState.OnCacheDeleting(
it.first / it.second.toFloat()
)
}
.delay(600,TimeUnit.MILLISECONDS)
.startWith(SettingsScreenViewState.OnCacheDeleting(0f))
.doOnError { Timber.e(it) }
.onErrorReturn(::parseError)
.subscribeOn(Schedulers.io())
}
val state = Observable.mergeDelayError(
arrayListOf(
restoreStateObservable,
// cahcObs,
// fetchPlansSize,
fetchSettings,
onSignOut,
onClearCache,
......@@ -115,6 +130,8 @@ class SettingsScreenPresenter @Inject constructor(
fetchSubscriptions,
fetchCache,
onSubChecked
// onDownloadTours,
// fetchToursSize
)
)
.doOnError { Timber.e(it) }
......
......@@ -18,6 +18,8 @@ sealed class SettingsScreenViewState : BigantoBaseViewState() {
class SomeError(val exception: ExceptionString) : SettingsScreenViewState()
class SignOut() : SettingsScreenViewState()
class OnCacheDeleting(val progress:Float) : SettingsScreenViewState()
class OnSizePrefetch(val size:Long) : SettingsScreenViewState()
class OnPlanTypesPrefetch(val size:Long) : SettingsScreenViewState()
class LoadSubscriptions(val list:List<TitledSubscriptionModel>) : SettingsScreenViewState()
class LoadCachInfo(val list: List<CachedDataModel>) : SettingsScreenViewState()
class SubscriptionStatus(val subId:Int,val subState: Boolean) : SettingsScreenViewState()
......
......@@ -9,7 +9,7 @@ import io.reactivex.Observable
interface FindFlatScreen : BigantoBaseContract<FindFlatScreenViewState> {
fun getFlat() : Observable<FlatRequestModel>
fun openFlat() : Observable<Int>
fun openFlat() : Observable<FlatRequestModel>
}
data class FlatRequestModel(val building:Int,val number:Int)
\ No newline at end of file
......@@ -17,6 +17,7 @@ import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.google.android.material.button.MaterialButton
import com.google.android.material.tabs.TabLayout
import com.google.android.material.textfield.TextInputLayout
import com.jakewharton.rxbinding3.material.selections
import com.jakewharton.rxbinding3.view.clicks
import com.jakewharton.rxbinding3.view.keys
import com.jakewharton.rxbinding3.widget.afterTextChangeEvents
......@@ -36,24 +37,32 @@ class FindFlatScreenController :
, FindFlatScreenPresenter>()
, FindFlatScreen {
private val flatModel:FlatRequestModel get(){
return FlatRequestModel(
estateTabs[flatTabs.selectedTabPosition].building
, flatNumberInput.editText?.text.toString().toInt()
)
}
override fun openFlat(): Observable<Int> =
override fun openFlat(): Observable<FlatRequestModel> =
findFlatButton.clicks()
.map { 1 }
.mergeWith(flatNumberEditor.keys{ it.keyCode == KeyEvent.KEYCODE_ENTER }.map { 1 })
.filter { flatNumberInput.editText?.text?.isNotEmpty()?:false }
.map {flatModel}
.doOnNext{ flatNumberEditor.hideKeyboard() }
.observeOn(AndroidSchedulers.mainThread())
override fun getFlat(): Observable<FlatRequestModel> =
flatNumberEditor.afterTextChangeEvents()// keys{ it.keyCode == KeyEvent.KEYCODE_ENTER }
.filter { flatNumberInput.editText?.text?.isNotEmpty()?:false }
.map {
FlatRequestModel(
estateTabs[flatTabs.selectedTabPosition].building
, flatNumberInput.editText?.text.toString().toInt()
.map { 1 }
.mergeWith(
flatTabs.selections().map { 1 }
)
}
.debounce (300,TimeUnit.MILLISECONDS)
.filter { flatNumberInput.editText?.text?.isNotEmpty()?:false }
.mergeWith(flatTabs.clicks().map { 1 })
.map {flatModel}
.debounce (120,TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
override fun injectDependencies() {
......@@ -117,7 +126,9 @@ class FindFlatScreenController :
is FindFlatScreenViewState.SomeError -> render(viewState)
is FindFlatScreenViewState.FlatFounded -> render(viewState)
is FindFlatScreenViewState.FlatNotFound -> render(viewState)
is FindFlatScreenViewState.FlatSold -> render(viewState)
is FindFlatScreenViewState.StartFlat -> render(viewState)
is FindFlatScreenViewState.RestoreView -> render(viewState)
}
}
......@@ -127,6 +138,14 @@ class FindFlatScreenController :
private fun render(viewState: FindFlatScreenViewState.Idle){
}
private fun render(viewState: FindFlatScreenViewState.RestoreView){
val tabIndex = viewState.restore.building-1
Timber.d(":: ${viewState.restore.building} : $tabIndex")
flatTabs.getTabAt(tabIndex)?.select() //zero-based
flatNumberInput.editText?.setText(viewState.restore.number.toString())
}
private fun render(viewState: FindFlatScreenViewState.FlatFounded){
findFlatButton.isEnabled = true
findFlatButton.text = resources?.getString(R.string.flat_ready_to_watch)
......@@ -138,8 +157,14 @@ class FindFlatScreenController :
{findFlatButton.text = resources?.getString(it)},
{findFlatButton.text = it}
)
}
private fun render(viewState: FindFlatScreenViewState.FlatSold){
findFlatButton.isEnabled = false
viewState.exceptionString.selectHandler(
{findFlatButton.text = resources?.getString(it)},
{findFlatButton.text = it}
)
}
private fun render(viewState: FindFlatScreenViewState.StartFlat){
......
......@@ -27,15 +27,24 @@ class FindFlatScreenPresenter @Inject constructor(
//estateId
private var recentFlat:Int? =null
private var restoreModel = RestoreModel(-1,-1)
override fun detachView() {
super.detachView()
restoreStateObservable.accept(FindFlatScreenViewState.RestoreView(restoreModel))
}
override fun vsByCode(code: Int): (ExceptionString) -> FindFlatScreenViewState =
when (code) {
304 -> {message:ExceptionString ->FindFlatScreenViewState.FlatNotFound(message)}
307 -> {message:ExceptionString ->FindFlatScreenViewState.FlatSold(message)}
else -> super.vsByCode(code)
}
override fun bindIntents() {
val getFlatIntent = intent(FindFlatScreen::getFlat)
.doOnNext { restoreModel.building = it.building;restoreModel.number = it.number }
.doOnNext{recentFlat=null}
.doOnNext{Timber.d(" flat is $it")}
.flatMap { request ->
......@@ -47,6 +56,7 @@ class FindFlatScreenPresenter @Inject constructor(
.startWith(Observable.just(FindFlatScreenViewState.RequstFlatProgress()))
val startFlatIntent = intent(FindFlatScreen::openFlat)
.doOnNext { restoreModel.building = it.building;restoreModel.number = it.number }
.map {
if (recentFlat != null) {FindFlatScreenViewState.StartFlat(recentFlat as Int)}
else FindFlatScreenViewState.FlatNotFound(
......@@ -55,6 +65,7 @@ class FindFlatScreenPresenter @Inject constructor(
}
val state = restoreStateObservable
.mergeWith(getFlatIntent)
.mergeWith(startFlatIntent)
......@@ -65,3 +76,6 @@ class FindFlatScreenPresenter @Inject constructor(
subscribeViewState(state.cast(FindFlatScreenViewState::class.java), FindFlatScreen::render)
}
}
data class RestoreModel(
var building:Int,var number:Int)
......@@ -13,6 +13,8 @@ sealed class FindFlatScreenViewState : BigantoBaseViewState() {
class RequstFlatProgress : FindFlatScreenViewState()
class FlatFounded : FindFlatScreenViewState()
class FlatNotFound(val exceptionString:ExceptionString) : FindFlatScreenViewState()
class FlatSold(val exceptionString:ExceptionString) : FindFlatScreenViewState()
class StartFlat(val esateId:Int) : FindFlatScreenViewState()
class SomeError(val exception: ExceptionString) : FindFlatScreenViewState()
class RestoreView(val restore:RestoreModel) : FindFlatScreenViewState()
}
\ No newline at end of file
......@@ -5,8 +5,10 @@ import android.view.View
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.IBottomNavigation
import com.biganto.visual.roompark.util.view_utils.snackbar.SnackBarMessageType.*
import com.google.android.material.snackbar.Snackbar
import timber.log.Timber
import javax.inject.Inject
/**
......@@ -69,8 +71,26 @@ class SnackBarProvider @Inject constructor(val activity: Activity) : ISnackBarPr
snack?.dismiss()
snack = Snackbar
.make(parentView, message, length)
.setAction(actionText(type)) {}
.setActionTextColor(color(type))
// .setAction(actionText(type)) {}
// .setActionTextColor(color(type))
Timber.w(" act - ${activity} ")
Timber.w(" act - ${(activity as? IBottomNavigation)} ")
// (activity as? IBottomNavigation)?.let {bNAv ->
// Timber.w(" act $bNAv" )
// snack?.apply {
// Timber.w(" sna")
// view.layoutParams = (view.layoutParams as CoordinatorLayout.LayoutParams).apply {
// Timber.w(" fusc $this")
// setMargins(
// leftMargin,
// topMargin,
// rightMargin,
// 300
// )
// }
// }?.show()
// }
snack?.show()
}
......
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="128dp"
android:height="148dp"
android:viewportWidth="128"
android:viewportHeight="148">
<path
android:name="path"
android:pathData="M 51.2 125.075 L 51.2 81.005 L 12.8 58.925 L 12.8 102.995 L 51.2 125.075 Z M 64 73.6 L 64 147.2 L 0 110.4 L 0 0 L 12.8 7.36 L 12.8 44.152 L 64 73.6 Z"
android:fillColor="#b4272d"
android:fillAlpha="0"
android:strokeWidth="1"/>
<path
android:name="path_1"
android:pathData="M 85.28 61.28 L 85.28 134.88 L 72.48 127.52 L 72.48 68.692 L 21.28 39.492 L 21.28 24.164 Z"
android:fillColor="#b4272d"
android:fillAlpha="0"
android:strokeWidth="1"/>
<path
android:name="path_2"
android:pathData="M 106.404 48.557 L 106.404 122.157 L 93.604 114.797 L 93.604 55.969 L 42.404 26.77 L 42.404 11.441"
android:fillColor="#b4272d"
android:fillAlpha="0"
android:strokeWidth="1"/>
<path
android:name="path_3"
android:pathData="M 127.37 36.24 L 127.37 109.84 L 114.57 102.48 L 114.57 43.652 L 63.37 14.452 L 63.37 -0.876 Z"
android:fillColor="#b4272d"
android:fillAlpha="0"
android:strokeWidth="1"/>
</vector>
</aapt:attr>
<target android:name="path">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="fillAlpha"
android:duration="250"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/accelerate_interpolator"/>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="1000"
android:duration="250"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:anim/decelerate_interpolator"/>
</set>
</aapt:attr>
</target>
<target android:name="path_1">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="178"
android:duration="343"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/linear_interpolator"/>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="1250"
android:duration="250"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:anim/decelerate_interpolator"/>
</set>
</aapt:attr>
</target>
<target android:name="path_2">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="453"
android:duration="378"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/linear_interpolator"/>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="1500"
android:duration="250"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:anim/decelerate_interpolator"/>
</set>
</aapt:attr>
</target>
<target android:name="path_3">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="646"
android:duration="438"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/linear_interpolator"/>
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="1750"
android:duration="250"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:anim/decelerate_interpolator"/>
</set>
</aapt:attr>
</target>
</animated-vector>
......@@ -41,7 +41,7 @@
android:top="5dp"
android:bottom="5dp"
android:src="@drawable/ic_bell_on"
android:mipMap="true"
android:mipMap="false"
android:gravity="start"
android:tintMode="screen"
android:tint="@color/colorPrimaryDark" />
......
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:width="http://schemas.android.com/apk/res-auto">
<item android:state_checked="false">
<layer-list>
<item>
<shape android:shape="rectangle"
android:dither="true"
android:useLevel="false">
<size android:width="48dp" android:height="24dp"/>
<corners android:radius="12dp"/>
</shape>
</item>
<item
android:right="8dp">
<shape
android:shape="ring"
android:width="14dp"
android:height="15dp"
android:gravity="end"
android:tint="@color/colorPrimaryDark" />
</item>
</layer-list>
</item>
<item android:state_checked="true">
<layer-list>
<item>
<shape android:shape="rectangle"
android:dither="true"
android:useLevel="false">
<size android:width="48dp" android:height="24dp"/>
<solid android:color="#FFFFFFFF"/>
<corners android:radius="12dp" />
</shape>
</item>
<item
android:left="8dp">
<shape
android:shape="ring"
android:right="27dp"
android:top="5dp"
android:bottom="5dp"
android:gravity="start"
android:tintMode="screen"
android:tint="@color/colorPrimaryDark"/>
</item>
</layer-list>
</item>
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="2dip" />
<solid android:color="#33000000" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<corners android:radius="2dip" />
<solid android:color="#ccFFFFFF" />
</shape>
</clip>
</item>
<item
android:id="@android:id/progress"
>
<clip>
<shape>
<corners
android:radius="2dip" />
<solid android:color="#FFFFFFFF" />
</shape>
</clip>
</item>
</layer-list>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1536dp"
android:height="1536dp"
android:viewportWidth="1536"
android:viewportHeight="1536">
<path
android:pathData="M0,0h1536v1536h-1536z"
android:fillColor="#FFFFFF"
android:strokeColor="#00000000"
android:fillAlpha="1"/>
<group>
<clip-path
android:pathData="M418,287L1118,287L1118,357L418,357ZM629.07,496.38L911.21,496.38L911.21,565L629.07,565ZM842,565L911.21,565L911.21,981L842,981ZM629.07,565L701,565L701,981L629.07,981ZM629.07,981L911.21,981L911.21,1050.63L629.07,1050.63ZM418,357L488,357L488,1050.63L418,1050.63ZM418,1050.63L701.25,1050.63L701.25,1120.75L418,1120.75ZM1049,357L1118,357L1118,1190L1049,1190ZM628.33,1120.75L701.25,1120.75L701.25,1190L628.33,1190ZM628.33,1190L1118,1190L1118,1257L628.33,1257ZM628.33,1257L697.34,1257L697.34,1536L628.33,1536ZM628.33,1257"/>
<path
android:pathData="M413,282L1123,282L1123,1541L413,1541ZM413,282"
android:fillColor="#227F79"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</group>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1536dp"
android:height="1536dp"
android:viewportWidth="1536"
android:viewportHeight="1536">
<path
android:pathData="M0,0h1536v1536h-1536z"
android:fillColor="#FFFFFF"
android:strokeColor="#00000000"
android:fillAlpha="1"/>
<group>
<clip-path
android:pathData="M418,287L1118,287L1118,357L418,357ZM629.07,496.38L911.21,496.38L911.21,565L629.07,565ZM842,565L911.21,565L911.21,981L842,981ZM629.07,565L701,565L701,981L629.07,981ZM629.07,981L911.21,981L911.21,1050.63L629.07,1050.63ZM418,357L488,357L488,1050.63L418,1050.63ZM418,1050.63L701.25,1050.63L701.25,1120.75L418,1120.75ZM1049,357L1118,357L1118,1190L1049,1190ZM628.33,1120.75L701.25,1120.75L701.25,1190L628.33,1190ZM628.33,1190L1118,1190L1118,1257L628.33,1257ZM628.33,1257L697.34,1257L697.34,1536L628.33,1536ZM628.33,1257"/>
<path
android:pathData="M413,282L1123,282L1123,1541L413,1541ZM413,282"
android:fillColor="#227F79"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</group>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="178dp"
android:height="91dp"
android:viewportWidth="178"
android:viewportHeight="91">
<group>
<clip-path android:pathData="M73,0L105,0L105,3.156L73,3.156ZM82.617,9.531L95.535,9.531L95.535,12.734L82.617,12.734ZM92.375,12.734L95.535,12.734L95.535,31.781L92.375,31.781ZM82.617,12.734L85.953,12.734L85.953,31.781L82.617,31.781ZM82.617,31.781L95.535,31.781L95.535,34.879L82.617,34.879ZM73,3.156L76.156,3.156L76.156,34.879L73,34.879ZM73,34.879L85.922,34.879L85.922,38.145L73,38.145ZM101.844,3.156L105,3.156L105,41.25L101.844,41.25ZM82.613,38.145L85.922,38.145L85.922,41.25L82.613,41.25ZM82.613,41.25L105,41.25L105,44.379L82.613,44.379ZM82.613,44.379L85.77,44.379L85.77,57.145L82.613,57.145ZM82.613,44.379 M 0,0"/>
<path
android:pathData="M68,-5L110,-5L110,62.145L68,62.145ZM68,-5"
android:fillColor="#40A19B"
android:fillAlpha="1"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</group>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/bell_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.switchmaterial.SwitchMaterial
style="@style/Widget.MaterialComponents.CompoundButton.Switch.DefaultSwitchStyle"
android:id="@+id/switch1"
android:layout_width="48dp"
android:layout_height="24dp"
android:height="24dp"
android:checked="false"
app:switchMinWidth="48dp" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/download_container"
android:orientation="vertical">
<ImageView
android:id="@+id/backgroundDownloader"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#A62B2727"
android:scaleType="centerCrop"
android:clickable="true"
android:focusableInTouchMode="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/downloadingTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="32dp" android:layout_marginStart="32dp" android:gravity="center_horizontal"
android:textAlignment="center"
style="@style/NoticeTextDownloader"
app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="32dp" android:layout_marginBottom="32dp"
app:layout_constraintBottom_toBottomOf="@+id/backgroundDownloader"
app:layout_constraintVertical_bias="0.32999998"/>
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tourToDownloadTitle"
app:layout_constraintTop_toBottomOf="@+id/downloadingTitle" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="32dp" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="32dp" style="@style/TitleTextDownloader"/>
<ProgressBar
android:id="@+id/downloadProgress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="4dp"
android:layout_marginStart="32dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="32dp"
android:progressDrawable="@drawable/horizontal_progress_downloader"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tourToDownloadTitle" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/cancelDownloadButton"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
app:layout_constraintTop_toBottomOf="@+id/downloadProgress" android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="32dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="32dp"
android:layout_marginBottom="32dp" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.24000001" style="@style/CancelTextDownloader"
android:padding="8dp"
android:paddingStart="8dp" android:paddingEnd="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
......@@ -4,7 +4,11 @@
android:layout_width="284dp"
android:layout_height="189dp"
android:layout_margin="16dp"
app:cardElevation="0dp"
app:cardElevation="8dp"
app:cardMaxElevation="12dp"
android:padding="16dp"
app:cardCornerRadius="4dp"
app:cardForegroundColor="#00000000"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="false">
......
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frameContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".player.BigantoPlayerActivity">
<FrameLayout
android:id="@+id/unityFrameContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
</FrameLayout>
<FrameLayout
android:id="@+id/previewBack"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
</FrameLayout>
<ImageView
android:id="@+id/playerPreview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@color/colorPrimary"
android:visibility="invisible" />
<ImageView
android:id="@+id/bigantoLogo"
android:layout_width="148dp"
android:layout_height="128dp"
android:scaleType="fitCenter"
android:layout_gravity="center"
/>
<ProgressBar
android:id="@+id/loadingBar"
style="@style/BigantoProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_margin="16dp" />
</FrameLayout>
\ No newline at end of file
......@@ -120,13 +120,14 @@
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:background="@color/colorOpacityCardBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="@dimen/bottom_navigation_height"
android:layout_gravity="bottom"
android:fitsSystemWindows="true"
android:visibility="gone"
app:elevation="0dp"
app:itemBackground="@color/colorCommonBackground"
app:itemBackground="@color/colorPrimary"
app:itemHorizontalTranslationEnabled="false"
app:itemIconTint="@drawable/bottom_navigation_icon_selector"
app:labelVisibilityMode="unlabeled"
......
......@@ -35,7 +35,6 @@
android:layout_marginTop="32dp"
android:layout_marginBottom="4dp"
android:includeFontPadding="false"
android:text="Дом №1"
android:textAlignment="center" />
<com.google.android.material.textview.MaterialTextView
......@@ -49,7 +48,6 @@
android:gravity="center"
android:includeFontPadding="false"
android:maxLines="1"
android:text="14 декабря 2019г"
android:textAlignment="center" />
<FrameLayout
......
......@@ -4,9 +4,11 @@
android:layout_width="142dp"
android:layout_height="94dp"
android:layout_margin="16dp"
android:padding="8dp"
app:cardCornerRadius="4dp"
app:cardElevation="4dp"
app:cardMaxElevation="8dp"
app:cardForegroundColor="#00000000"
app:cardMaxElevation="6dp"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="false"
app:contentPadding="8dp">
......@@ -34,7 +36,6 @@
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginBottom="4dp"
android:text="Дом №1"
android:textAlignment="center" />
<com.google.android.material.textview.MaterialTextView
......@@ -47,7 +48,6 @@
android:layout_marginEnd="8dp"
android:gravity="center"
android:maxLines="1"
android:text="14 декабря 2019г"
android:textAlignment="center" />
<FrameLayout
......
......@@ -37,7 +37,8 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@color/colorOpacityBackground">
android:background="@color/colorOpacityBackground"
>
<LinearLayout
android:layout_width="match_parent"
......
......@@ -7,6 +7,7 @@
android:orientation="vertical">
<com.google.android.material.switchmaterial.SwitchMaterial
style="@style/Widget.MaterialComponents.CompoundButton.Switch.BellSwitchStyle"
android:id="@+id/switch1"
android:layout_width="48dp"
android:layout_height="24dp"
......
......@@ -21,6 +21,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:minHeight="80dp"
android:orientation="vertical"
tools:itemCount="1"
tools:listitem="@layout/photo_preview_viewholder" />
......
......@@ -43,7 +43,7 @@
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="КВАРТИРА\n№452" />
android:text="\n" />
<include
layout="@layout/horizontal_divider"
......@@ -139,8 +139,7 @@
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom|end"
android:includeFontPadding="false"
android:text="5 165 301 ₽" />
android:includeFontPadding="false" />
</LinearLayout>
......@@ -173,8 +172,7 @@
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom|end"
android:includeFontPadding="false"
android:text="1 332 543 ₽" />
android:includeFontPadding="false" />
</LinearLayout>
......@@ -207,8 +205,7 @@
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom|end"
android:includeFontPadding="false"
android:text="32 543 ₽" />
android:includeFontPadding="false" />
</LinearLayout>
<include
......
......@@ -20,7 +20,7 @@
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="КВАРТИРА\n№452" />
android:text="\n" />
<include
layout="@layout/horizontal_divider"
......@@ -138,8 +138,7 @@
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom|end"
android:includeFontPadding="false"
android:text="5 165 301 ₽" />
android:includeFontPadding="false" />
</LinearLayout>
<include
......@@ -178,8 +177,7 @@
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom|end"
android:includeFontPadding="false"
android:text="1 332 543 ₽" />
android:includeFontPadding="false" />
</LinearLayout>
......@@ -219,8 +217,7 @@
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="bottom|end"
android:includeFontPadding="false"
android:text="32 543 ₽" />
android:includeFontPadding="false" />
</LinearLayout>
......
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="284dp"
android:layout_height="189dp"
android:layout_margin="16dp"
app:cardElevation="4dp"
app:cardMaxElevation="8dp"
app:cardForegroundColor="#00000000"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="false">
<com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
android:id="@+id/preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:background="@drawable/default_image_placeholder"
android:cropToPadding="true"
android:foreground="@color/colorOpacityBackground"
android:scaleType="centerCrop"
app:image_corner_radius="4dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/card_title"
style="@style/Header_TextView.Inverted_Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="64dp"
android:layout_marginBottom="4dp"
android:text="Дом №1"
android:textAlignment="center" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView6"
style="@style/Common_Text.Inverted"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Последнее обновление"
android:textAlignment="center" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/card_updated"
style="@style/Accent_Minor_TextView.DatePlaceHolder"
android:layout_width="match_parent"
android:layout_height="24dp"
android:layout_marginStart="64dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="64dp"
android:gravity="center"
android:maxLines="1"
android:text="14 декабря 2019г"
android:textAlignment="center" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
\ No newline at end of file
......@@ -18,17 +18,16 @@
android:padding="16dp">
<FrameLayout
android:id="@+id/feed_read"
android:id="@+id/avaliable_status"
android:layout_width="12dp"
android:layout_height="12dp"
android:background="@drawable/new_feed_icon"
android:backgroundTint="@color/colorAccent"
android:background="@drawable/available_status"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/feed_date_text_view3"
<com.google.android.material.textview.MaterialTextView
android:id="@+id/avaliable_text"
style="@style/LiteText.Accent"
android:layout_width="0dp"
android:layout_height="wrap_content"
......@@ -38,7 +37,7 @@
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="@+id/feed_read"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/feed_read"
app:layout_constraintStart_toEndOf="@+id/avaliable_status"
app:layout_constraintTop_toTopOf="@+id/feed_read" />
<com.google.android.material.textview.MaterialTextView
......@@ -47,10 +46,10 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="КВАРТИРА\n№ 452"
android:text="\n"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/feed_date_text_view3" />
app:layout_constraintTop_toBottomOf="@+id/avaliable_text" />
<include
android:id="@+id/header_divider"
......
......@@ -74,7 +74,6 @@
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:includeFontPadding="false"
android:text="В «РУМЯНЦЕВО-ПАРК» ПРИСТУПИЛИ К МОНТАЖУ ОКОННЫХ БЛОКОВ"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
......
......@@ -44,7 +44,6 @@
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:includeFontPadding="false"
android:text="22 / 02 / 2019"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/feed_read"
......@@ -58,7 +57,6 @@
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:includeFontPadding="false"
android:text="В «РУМЯНЦЕВО-ПАРК» ПРИСТУПИЛИ К МОНТАЖУ ОКОННЫХ БЛОКОВ"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
......
......@@ -29,8 +29,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="В ЖК «РУМЯНЦЕВО-ПАРК» ИПОТЕЧНАЯ СТАВКА - 6,5%" />
android:paddingEnd="16dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/articleBodyRecyclerView"
......
......@@ -45,7 +45,6 @@
android:layout_marginTop="8dp"
android:layout_marginEnd="32dp"
android:includeFontPadding="false"
android:text="22 / 02 / 2019"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
......@@ -61,7 +60,6 @@
android:layout_marginEnd="32dp"
android:includeFontPadding="false"
android:maxLines="3"
android:text="В «РУМЯНЦЕВО-ПАРК» ПРИСТУПИЛИ К МОН ТАЖУ dadasdasd a 22ЫХ БЛОКОВ"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/guideline"
......
......@@ -97,7 +97,7 @@
<include
android:id="@+id/sizes_switcher"
layout="@layout/bell_switch_view"
layout="@layout/default_switch_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
......@@ -140,7 +140,7 @@
<include
android:id="@+id/furniture_switcher"
layout="@layout/bell_switch_view"
layout="@layout/default_switch_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
......@@ -183,7 +183,7 @@
<include
android:id="@+id/electricity_switcher"
layout="@layout/bell_switch_view"
layout="@layout/default_switch_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
......@@ -227,7 +227,7 @@
<include
android:id="@+id/walls_switcher"
layout="@layout/bell_switch_view"
layout="@layout/default_switch_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
......
......@@ -9,7 +9,7 @@
android:id="@+id/info_ceil_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Цена за м²" />
/>
<com.google.android.material.textview.MaterialTextView
style="@style/Common_Text.Default"
......@@ -17,5 +17,5 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="165 301 ₽" />
/>
</LinearLayout>
\ No newline at end of file
......@@ -22,16 +22,18 @@
<ImageView
android:id="@+id/close_current_button"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_width="56dp"
android:layout_height="64dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="0dp"
android:clickable="true"
android:contentDescription="@string/content_description_close"
android:focusable="true"
android:focusableInTouchMode="true"
android:focusableInTouchMode="false"
android:padding="16dp"
android:paddingEnd="8dp"
android:scaleType="fitXY"
android:src="@drawable/ic_close_circled" />
......
......@@ -22,8 +22,7 @@
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:orientation="vertical"
android:text="Договор готовится для подачи на гос. регистрацию b,kf,kf,fk">
android:orientation="vertical">
</com.google.android.material.textview.MaterialTextView>
</LinearLayout>
\ No newline at end of file
......@@ -56,9 +56,7 @@
android:layout_marginEnd="32dp"
android:layout_weight="1"
android:gravity="start|center_vertical"
android:text="Скачать карточки моих
квартир из избранного
и сделок (4 MB)" />
android:text="@string/download_all_plan_types_settings_with_sizes" />
<ImageView
android:id="@+id/downloadFlatCardsIcon"
......@@ -89,9 +87,7 @@
android:layout_marginEnd="32dp"
android:layout_weight="1"
android:gravity="start|center_vertical"
android:text="Скачать виртуальные туры
моих квартир из избранного
и сделок (477 MB)" />
android:text="@string/download_all_tours_settings_with_size" />
<ImageView
android:id="@+id/downloadToursIcon"
......
......@@ -16,7 +16,6 @@
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="@drawable/available_status"
android:fitsSystemWindows="true"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
......@@ -27,9 +26,8 @@
style="@style/Accent_Minor_TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_marginStart="8dp"
android:fitsSystemWindows="true"
android:text="СВОБОДНА"
app:layout_constraintBottom_toBottomOf="@+id/status_icon"
app:layout_constraintStart_toEndOf="@+id/status_icon"
app:layout_constraintTop_toTopOf="@+id/status_icon" />
......@@ -44,7 +42,6 @@
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:fitsSystemWindows="true"
android:text="СВОБОДНА"
app:layout_constraintBottom_toBottomOf="@+id/status_icon"
app:layout_constraintEnd_toStartOf="@+id/back_cross"
app:layout_constraintHorizontal_bias="0.0"
......@@ -53,11 +50,9 @@
<ImageView
android:id="@+id/back_cross"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="4dp"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:tint="@color/colorGray"
......
......@@ -12,8 +12,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start|center_vertical"
android:text="блабла" />
android:gravity="start|center_vertical" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/descriptionText"
......
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/tour_preview_imageView"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_bell_on" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tour_name"
style="@style/Common_Text.Notice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:ellipsize="end"
android:text="Тур"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tour_status_imageView"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/tour_preview_imageView"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/tour_status_imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="@+id/tour_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/tour_name"
app:srcCompat="@drawable/ic_back" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/playTourCardOpacityLight"
android:backgroundTintMode="src_atop"
android:clickable="true"
android:focusable="true">
<com.google.android.material.card.MaterialCardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
app:cardElevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.32999998">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/toursList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_marginBottom="8dp"
android:orientation="vertical"
tools:itemCount="3"
tools:listitem="@layout/tour_chooser_viewholder">
</androidx.recyclerview.widget.RecyclerView>
</com.google.android.material.card.MaterialCardView>
<ImageView
android:id="@+id/close_current_button"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:clickable="false"
android:contentDescription="@string/content_description_close"
android:focusable="false"
android:scaleType="fitXY"
android:src="@drawable/ic_close_circled"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/playTourCardOpacityLight"
android:backgroundTintMode="src_atop"
android:clickable="true"
android:focusable="true">
<com.google.android.material.card.MaterialCardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
app:cardElevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.32999998">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/download_dialog_title"
style="@style/Default_TextView.Header_Text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/download_all_tours_notice_text"
android:textAlignment="center" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/all_tours_size_textView"
style="@style/Default_TextView.Notice_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:text="@string/download_all_tours_size_title" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/disk_size_textView"
style="@style/Default_TextView.Notice_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:text="@string/download_all_tours_disk_size_title" />
<LinearLayout
android:id="@+id/buttons_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2">
<Button
android:id="@+id/alert_ok_button"
style="@style/DefaultButton.DismissButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/download_all_tours_yes_action" />
<Button
android:id="@+id/alert_dismiss_button"
style="@style/DefaultButton.DismissButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/download_all_tours_cancel_action"
android:visibility="visible" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<ImageView
android:id="@+id/close_current_button"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:clickable="false"
android:contentDescription="@string/content_description_close"
android:focusable="false"
android:scaleType="fitXY"
android:src="@drawable/ic_close_circled"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
\ No newline at end of file
......@@ -18,5 +18,6 @@
<item name="colorControlNormal">@color/colorAccent</item>
<item name="android:statusBarColor">@color/colorPrimary</item>
<item name="android:windowLightStatusBar">true</item>
<item name="snackbarStyle">@style/Widget.RoomPark.Snackbar</item>
</style>
</resources>
\ No newline at end of file
......@@ -40,7 +40,7 @@
<string name="api_error_2001">Тур не найден!</string>
<string name="api_error_default">Неизвестная ошибка сервера!</string>
<string name="no_network_error">No network!</string>
<string name="no_network_error">Нет сети!</string>
<string name="unknown_error">Unexpected error!</string>
<string name="cant_load_item_error">Can\'t load item!</string>
<string name="object_not_found_error">Object not found!</string>
......@@ -96,4 +96,51 @@
<string name="subscribe_error_message">Ошибка! Подписаться не удалось!</string>
<!--endregion-->
<string name="download_tour_title_text">Скачивается тур</string>
<string name="download_tour_cancel_text">Отмена</string>
<!-- region notifications-->
<string name="notification_content_title">Румянцево Парк</string>
<string name="notification_content_text">Загрузчик</string>
<string name="state_downloaded_notify_message">загружен</string>
<string name="state_crushed_notify_message">произошла ошибка</string>
<string name="state_suspended_notify_message">загрузка приостановлена</string>
<string name="state_downloading_notify_message">загружается</string>
<string name="state_else_notify_message" />
<!--endregion-->
<!-- region notifications-->
<string name="push_notification_content_title">Уведомление!</string>
<!--endregion-->
<string name="on_all_tours_downloaded_notification_message">Загрузка туров завершена</string>
<string name="on_all_tours_deleted_notification_message">Удаление туров завершено</string>
<string name="noty_tours_delete_left">Осталось удалить: %d%n</string>
<string name="noty_tours_download_left">Осталось загрузить: %d%n</string>
<string name="game_view_content_description" />
<string name="download_all_tours_settings_with_size">Скачать виртуальные туры моих квартир из избранного и сделок</string>
<string name="download_all_plan_types_settings_with_sizes">Скачать карточки моих квартир из избранного и сделок</string>
<string name="plan_types_download_completed">Загрузка планов завершена.</string>
<string name="download_plan_types_screen_title">Cкачиваются планировки</string>
<string name="download_all_tours_notice_text">Загрузить все туры?</string>
<string name="download_all_plans_notice_text">Загрузить все \n планировки?</string>
<string name="download_all_tours_size_title">Размер всех туров: %s</string>
<string name="download_all_plans_size_title">Размер всех планировок: %s</string>
<string name="download_all_tours_disk_size_title">Размер свободного места на диске: %s</string>
<string name="download_all_tours_yes_action">Скачать</string>
<string name="download_all_tours_cancel_action">Отмена</string>
<string name="download_all_tours_errors_snackbar_message">Ошибка при попытке загрузить туры!</string>
<string name="download_all_plans_errors_snackbar_message">Ошибка при попытке загрузить планировки!</string>
<string name="download_all_tours_start_snackbar_message">Загрузка туров началась!</string>
</resources>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
......@@ -10,11 +10,22 @@
<item name="colorAccent">@color/colorAccent</item>
<item name="materialCardViewStyle">@style/Widget.Biganto.MaterialCardView</item>
<item name="switchStyle">@style/Widget.MaterialComponents.CompoundButton.Switch.BellSwitchStyle</item>
<!-- <item name="switchStyle">@style/Widget.MaterialComponents.CompoundButton.Switch.BellSwitchStyle</item>-->
<item name="colorControlActivated">@color/colorAccent</item>
<item name="colorControlHighlight">@color/colorAccent</item>
<item name="colorControlNormal">@color/colorAccent</item>
<item name="android:statusBarColor">@color/colorPrimary</item>
<item name="snackbarStyle">@style/Widget.RoomPark.Snackbar</item>
</style>
<style name="Widget.RoomPark.Snackbar" parent="Widget.MaterialComponents.Snackbar" tools:override="true">
<item name="android:layout_margin">@null</item>
<!-- Use default Snackbar margins for top/left/right -->
<item name="android:layout_marginTop" tools:ignore="PrivateResource">@dimen/mtrl_snackbar_margin</item>
<item name="android:layout_marginLeft" tools:ignore="PrivateResource">@dimen/mtrl_snackbar_margin</item>
<item name="android:layout_marginRight" tools:ignore="PrivateResource">@dimen/mtrl_snackbar_margin</item>
<!-- Custom bottom margin, this could work for top/left/right too -->
<item name="android:layout_marginBottom">@dimen/bottom_navigation_height</item>
</style>
<style name="Widget.MainTextInputStyle" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
......@@ -32,6 +43,13 @@
<item name="trackTint">@drawable/bell_switch_track_tint</item>
</style>
<style name="Widget.MaterialComponents.CompoundButton.Switch.DefaultSwitchStyle" >
<item name="track" >@drawable/def_switch_track</item>
<item name="android:thumb" >@drawable/bell_switcher</item>
<item name="thumbTint">@color/colorPrimaryDark</item>
<item name="trackTint">@drawable/bell_switch_track_tint</item>
</style>
<style name="Widget.Biganto.MaterialCardView" parent="Widget.MaterialComponents.CardView">
<item name="cardUseCompatPadding">false</item>
<item name="strokeWidth">0dp</item>
......@@ -424,4 +442,28 @@
//endregion
<style name="NoticeTextDownloader" parent="Default_TextView">
<item name="android:textColor">@color/colorNoticeText</item>
<item name="android:text">@string/download_tour_title_text</item>
</style>
<style name="TitleTextDownloader" parent="Default_TextView">
<item name="android:textColor">@color/colorInvertedText</item>
</style>
<style name="BubbleTitleText" parent="Header_TextView.Main_Header">
</style>
<style name="CancelTextDownloader" parent="Default_TextView">
<item name="android:textColor">@color/colorError</item>
<item name="android:text">@string/download_tour_cancel_text</item>
</style>
<style name="BigantoProgressBar">
<item name="android:layout_width">@dimen/default_icon_width</item>
<item name="android:layout_height">@dimen/default_icon_height</item>
<item name="android:indeterminateTint">@color/colorPrimary</item>
</style>
</resources>
......@@ -5,8 +5,8 @@ ext {
minSdkVersion_RoomPark = 23
compileSdkVersion_RoomPark = 28
VERSION_CODE = 2
VERSION_NAME = "0.8.0"
VERSION_CODE = 6
VERSION_NAME = "0.9.0"
// supportLibraryVersion = '1.1.0-alpha05'
constrainLayoutVersion = '1.1.3'
......
Room Park official Android application
- - v.0.8.0 - -
- - v.0.9.0 - -
All common functionality, except tour launcher
Pre-release version
cheers by Vladislav Bogdashkin
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment