Commit 69b6e42f authored by Vladislav Bogdashkin's avatar Vladislav Bogdashkin 🎣

photo screen firest edition

parent 976eb422
...@@ -84,9 +84,6 @@ dependencies { ...@@ -84,9 +84,6 @@ dependencies {
//Logger: Timber //Logger: Timber
implementation "com.jakewharton.timber:timber:$timberVersion" implementation "com.jakewharton.timber:timber:$timberVersion"
//PhotoView
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
//Crashlytics //Crashlytics
implementation('com.crashlytics.sdk.android:crashlytics:2.10.0@aar') { implementation('com.crashlytics.sdk.android:crashlytics:2.10.0@aar') {
transitive = true; transitive = true;
......
...@@ -23,18 +23,21 @@ class AlbumsContractModule @Inject constructor( ...@@ -23,18 +23,21 @@ class AlbumsContractModule @Inject constructor(
): DevProgressContract { ): DevProgressContract {
override fun getAlbumPreviews(albumId: Int): Observable<List<AlbumPhotoPreviewModel>> { override fun getAlbumPreviews(albumId: Int): Observable<List<AlbumPhotoPreviewModel>> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. TODO("zzz")
} }
override fun getAlbumPreview(albumId: Int): Observable<AlbumPreviewModel> =
db.getAlbum(albumId)
.map (::fromEntity)
override fun getProgressCards(): Observable<List<AlbumPreviewModel>> = fetchTopLevelAlbums() override fun getProgressCards(): Observable<List<AlbumPreviewModel>> = fetchTopLevelAlbums()
override fun getProgressAlbumList(albumId: Int): Observable<List<AlbumPreviewModel>> = override fun getProgressAlbumList(albumId: Int): Observable<List<AlbumPreviewModel>> =
fetchAlbums(albumId) fetchAlbums(albumId)
override fun getAlbumPhoto(photoId: Int): Observable<PhotoModel> { override fun getAlbumPhoto(photoId: Int): Observable<PhotoModel> =
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. db.getPhoto(photoId).map(::fromEntity)
}
override fun getWebCamsList(): Observable<WebCamListModel> = override fun getWebCamsList(): Observable<WebCamListModel> =
fetchWebCams() fetchWebCams()
......
...@@ -24,4 +24,6 @@ interface IDb { ...@@ -24,4 +24,6 @@ interface IDb {
fun getFeed(feedAlias: String): ReactiveResult<FeedEntity> fun getFeed(feedAlias: String): ReactiveResult<FeedEntity>
fun getArticle(id: Int): ReactiveResult<ArticleEntity> fun getArticle(id: Int): ReactiveResult<ArticleEntity>
fun getPhotos(albumId: Int): Observable<GalleryPhotoEntity> fun getPhotos(albumId: Int): Observable<GalleryPhotoEntity>
fun getPhoto(photoId: Int): Observable<GalleryPhotoEntity>
fun getAlbum(albumId: Int): Observable<ImageAlbumEntity>
} }
\ No newline at end of file
...@@ -91,11 +91,21 @@ class RequeryRepository @Inject constructor( ...@@ -91,11 +91,21 @@ class RequeryRepository @Inject constructor(
).toList() ).toList()
) )
override fun getAlbum(albumId: Int): Observable<ImageAlbumEntity> =
store.select(ImageAlbumEntity::class)
.where(ImageAlbumEntity.ID.eq(albumId))
.get().observable()
override fun getPhotos(albumId: Int): Observable<GalleryPhotoEntity> = override fun getPhotos(albumId: Int): Observable<GalleryPhotoEntity> =
store.select(GalleryPhotoEntity::class) store.select(GalleryPhotoEntity::class)
.where(GalleryPhotoEntity.ALBUM_ID.eq(albumId)) .where(GalleryPhotoEntity.ALBUM_ID.eq(albumId))
.get().observable() .get().observable()
override fun getPhoto(photoId: Int): Observable<GalleryPhotoEntity> =
store.select(GalleryPhotoEntity::class)
.where(GalleryPhotoEntity.ID.eq(photoId))
.get().observable()
override fun fetchArticles(feedAlias: String, pageSize: Int, startIndex: Int) override fun fetchArticles(feedAlias: String, pageSize: Int, startIndex: Int)
: Observable<ArticleEntity> = : Observable<ArticleEntity> =
fetchAll<ArticleEntity>() fetchAll<ArticleEntity>()
......
...@@ -12,6 +12,7 @@ interface DevProgressContract{ ...@@ -12,6 +12,7 @@ interface DevProgressContract{
fun getProgressCards(): io.reactivex.Observable<List<AlbumPreviewModel>> fun getProgressCards(): io.reactivex.Observable<List<AlbumPreviewModel>>
fun getProgressAlbumList(albumId:Int): io.reactivex.Observable<List<AlbumPreviewModel>> fun getProgressAlbumList(albumId:Int): io.reactivex.Observable<List<AlbumPreviewModel>>
fun getAlbumPreviews(albumId:Int): io.reactivex.Observable<List<AlbumPhotoPreviewModel>> fun getAlbumPreviews(albumId:Int): io.reactivex.Observable<List<AlbumPhotoPreviewModel>>
fun getAlbumPreview(albumId:Int): Observable<AlbumPreviewModel>
fun getAlbumPhoto(photoId:Int): io.reactivex.Observable<PhotoModel> fun getAlbumPhoto(photoId:Int): io.reactivex.Observable<PhotoModel>
fun getWebCamsList(): io.reactivex.Observable<WebCamListModel> fun getWebCamsList(): io.reactivex.Observable<WebCamListModel>
fun getWebCamStream(camId:Int): io.reactivex.Observable<WebCamModel> fun getWebCamStream(camId:Int): io.reactivex.Observable<WebCamModel>
......
...@@ -15,7 +15,7 @@ class AlbumsInteractor @Inject constructor( ...@@ -15,7 +15,7 @@ class AlbumsInteractor @Inject constructor(
fun fetchHeaderAlbums() = useCase.getProgressAlbums() fun fetchHeaderAlbums() = useCase.getProgressAlbums()
fun fetchAlbumPhotos(parentId: Int): Observable<List<AlbumSortedModel>> = fun fetchAlbumPhotos(parentId: Int): Observable<List<AlbumSortedModel>> =
useCase.getChildAlbum(parentId) useCase.getChildAlbums(parentId)
.flatMap { .flatMap {
Observable.fromIterable(it).flatMap { alb -> Observable.fromIterable(it).flatMap { alb ->
useCase.getPhotos(alb.albumId).take(1) useCase.getPhotos(alb.albumId).take(1)
......
...@@ -15,7 +15,7 @@ class PhotoInteractor @Inject constructor( ...@@ -15,7 +15,7 @@ class PhotoInteractor @Inject constructor(
fun fetchHeaderAlbums() = useCase.getProgressAlbums() fun fetchHeaderAlbums() = useCase.getProgressAlbums()
fun fetchAlbumPhotos(parentId: Int): Observable<List<AlbumSortedModel>> = fun fetchAlbumPhotos(parentId: Int): Observable<List<AlbumSortedModel>> =
useCase.getChildAlbum(parentId) useCase.getChildAlbums(parentId)
.flatMap { .flatMap {
Observable.fromIterable(it).flatMap { alb -> Observable.fromIterable(it).flatMap { alb ->
useCase.getPhotos(alb.albumId).take(1) useCase.getPhotos(alb.albumId).take(1)
...@@ -31,6 +31,12 @@ class PhotoInteractor @Inject constructor( ...@@ -31,6 +31,12 @@ class PhotoInteractor @Inject constructor(
} }
.debounce(80L, TimeUnit.MILLISECONDS) // to reduce double list draw effect .debounce(80L, TimeUnit.MILLISECONDS) // to reduce double list draw effect
fun fetchPhotos(albumId: Int) =
useCase.getPhotos(albumId).take(1)
fun fetchPhoto(photoId:Int) = useCase.getPhotoByid(photoId)
fun getAlbum(albumId:Int) = useCase.getDirectAlbum(albumId)
} }
......
...@@ -13,12 +13,16 @@ class AlbumsUseCase @Inject constructor( ...@@ -13,12 +13,16 @@ class AlbumsUseCase @Inject constructor(
fun getProgressAlbums() = contract.getProgressCards() fun getProgressAlbums() = contract.getProgressCards()
fun getChildAlbum(parentAlbumId:Int) = fun getChildAlbums(parentAlbumId:Int) =
contract.getProgressAlbumList(parentAlbumId) contract.getProgressAlbumList(parentAlbumId)
fun getPhotos(parentAlbumId:Int) = fun getPhotos(parentAlbumId:Int) =
contract.getAlbumPhotoList(parentAlbumId) contract.getAlbumPhotoList(parentAlbumId)
fun getPhotoByid(photoId:Int) = contract.getAlbumPhoto(photoId)
fun getDirectAlbum(albumId:Int) =
contract.getAlbumPreview(albumId)
} }
\ No newline at end of file
...@@ -2,7 +2,7 @@ package com.biganto.visual.roompark.presentation.screen.albums ...@@ -2,7 +2,7 @@ package com.biganto.visual.roompark.presentation.screen.albums
import com.biganto.visual.roompark.conductor.BigantoBaseContract import com.biganto.visual.roompark.conductor.BigantoBaseContract
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.presentation.screen.photo.AlbumsScreenViewState import com.biganto.visual.roompark.domain.model.PhotoModel
import io.reactivex.Observable import io.reactivex.Observable
/** /**
...@@ -11,4 +11,5 @@ import io.reactivex.Observable ...@@ -11,4 +11,5 @@ import io.reactivex.Observable
interface AlbumsScreen : BigantoBaseContract<AlbumsScreenViewState> { interface AlbumsScreen : BigantoBaseContract<AlbumsScreenViewState> {
fun onAlbumSelected(): Observable<AlbumPreviewModel> fun onAlbumSelected(): Observable<AlbumPreviewModel>
fun onPhotoSelected(): Observable<PhotoModel>
} }
\ No newline at end of file
...@@ -16,12 +16,13 @@ import com.biganto.visual.roompark.base.RoomParkApplication ...@@ -16,12 +16,13 @@ import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.presentation.screen.photo.AlbumsScreenPresenter import com.biganto.visual.roompark.domain.model.PhotoModel
import com.biganto.visual.roompark.presentation.screen.photo.AlbumsScreenViewState import com.biganto.visual.roompark.presentation.screen.albums.util.AlbumListAdapter
import com.biganto.visual.roompark.presentation.screen.photo.util.AlbumsHeaderAdapter import com.biganto.visual.roompark.presentation.screen.albums.util.AlbumsHeaderAdapter
import com.biganto.visual.roompark.presentation.screen.photo.util.AlbumListAdapter import com.biganto.visual.roompark.presentation.screen.photo.PhotoScreenController
import com.biganto.visual.roompark.util.extensions.scaleCenterCrop import com.biganto.visual.roompark.util.extensions.scaleCenterCrop
import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import io.reactivex.Observable import io.reactivex.Observable
...@@ -122,8 +123,7 @@ class AlbumsScreenController : ...@@ -122,8 +123,7 @@ class AlbumsScreenController :
lateinit var rpActivity: RoomParkMainActivity lateinit var rpActivity: RoomParkMainActivity
fun getComponent() = DaggerAlbumsScreenComponent fun getComponent() = DaggerAlbumsScreenComponent.factory()
.factory()
.create(RoomParkApplication.component .create(RoomParkApplication.component
,activity as RoomParkMainActivity ,activity as RoomParkMainActivity
,args.getInt(com.biganto.visual.roompark.presentation.screen.photo.SELECTED_ALBUM_INDEX_KEY)) ,args.getInt(com.biganto.visual.roompark.presentation.screen.photo.SELECTED_ALBUM_INDEX_KEY))
...@@ -141,6 +141,7 @@ class AlbumsScreenController : ...@@ -141,6 +141,7 @@ class AlbumsScreenController :
is AlbumsScreenViewState.AlbumsSelected -> render(viewState) is AlbumsScreenViewState.AlbumsSelected -> render(viewState)
is AlbumsScreenViewState.AlbumsListLoaded-> render(viewState) is AlbumsScreenViewState.AlbumsListLoaded-> render(viewState)
is AlbumsScreenViewState.HeaderAlbumChoosed-> render(viewState) is AlbumsScreenViewState.HeaderAlbumChoosed-> render(viewState)
is AlbumsScreenViewState.PhotoSelected -> render(viewState)
is AlbumsScreenViewState.SomeError -> render(viewState) is AlbumsScreenViewState.SomeError -> render(viewState)
} }
} }
...@@ -149,6 +150,10 @@ class AlbumsScreenController : ...@@ -149,6 +150,10 @@ class AlbumsScreenController :
} }
private fun render(viewState: AlbumsScreenViewState.PhotoSelected){
router.pushController(RouterTransaction.with(PhotoScreenController(viewState.photoId)))
}
private fun render(viewState: AlbumsScreenViewState.SomeError) = private fun render(viewState: AlbumsScreenViewState.SomeError) =
showError(viewState.exception) showError(viewState.exception)
...@@ -200,11 +205,21 @@ class AlbumsScreenController : ...@@ -200,11 +205,21 @@ class AlbumsScreenController :
override fun onAlbumSelected(): Observable<AlbumPreviewModel> override fun onAlbumSelected(): Observable<AlbumPreviewModel>
= (headersRecyclerView.adapter as AlbumsHeaderAdapter) = (headersRecyclerView.adapter as AlbumsHeaderAdapter)
.onItemClicked .onItemClicked
.map { it }
.debounce (300L,TimeUnit.MILLISECONDS) .debounce (300L,TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnNext { (albumsRecyclerView.adapter as AlbumListAdapter).setItems(arrayListOf())} .doOnNext { (albumsRecyclerView.adapter as AlbumListAdapter).setItems(arrayListOf())}
override fun onPhotoSelected(): Observable<PhotoModel>
= (albumsRecyclerView.adapter as AlbumListAdapter)
.photoObs
.map { it }
.debounce (300L,TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
override fun getLayoutId(): Int = R.layout.albums_screen override fun getLayoutId(): Int = R.layout.albums_screen
......
...@@ -33,24 +33,20 @@ class AlbumsScreenPresenter @Inject constructor( ...@@ -33,24 +33,20 @@ class AlbumsScreenPresenter @Inject constructor(
val fetchParents = interactor.fetchHeaderAlbums() val fetchParents = interactor.fetchHeaderAlbums()
.filter { !it.isNullOrEmpty() } .filter { !it.isNullOrEmpty() }
.map { AlbumsScreenViewState.AlbumsListLoaded(it, selectedIndex) } .map { AlbumsScreenViewState.AlbumsListLoaded(it, selectedIndex) }
// .concatMap<AlbumsScreenViewState> {
// Observable.just(
// AlbumsScreenViewState.HeaderAlbumChoosed(
// it.list.first { alb -> alb.albumId == selectedIndex })
// )
// }
val fetchSelected = requestAlbum(selectedIndex) val fetchSelected = requestAlbum(selectedIndex)
val headerItemSelected = intent(AlbumsScreen::onAlbumSelected) val headerItemSelected = intent(AlbumsScreen::onAlbumSelected)
.doOnNext { selectedIndex = it.albumId } .doOnNext { selectedIndex = it.albumId }
.flatMap<AlbumsScreenViewState> { model -> .flatMap<AlbumsScreenViewState> { model ->
requestAlbum(model.albumId) requestAlbum(model.albumId)
.startWith(Observable.just(AlbumsScreenViewState.HeaderAlbumChoosed(item = model))) .startWith(Observable.just(AlbumsScreenViewState.HeaderAlbumChoosed(item = model)))
} }
val photoSelected = intent(AlbumsScreen::onPhotoSelected)
.map { AlbumsScreenViewState.PhotoSelected(it.photoId) }
...@@ -58,6 +54,7 @@ class AlbumsScreenPresenter @Inject constructor( ...@@ -58,6 +54,7 @@ class AlbumsScreenPresenter @Inject constructor(
.mergeWith(fetchParents) .mergeWith(fetchParents)
.mergeWith(fetchSelected) .mergeWith(fetchSelected)
.mergeWith(headerItemSelected) .mergeWith(headerItemSelected)
.mergeWith(photoSelected)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
subscribeViewState(state.cast(AlbumsScreenViewState::class.java), AlbumsScreen::render) subscribeViewState(state.cast(AlbumsScreenViewState::class.java), AlbumsScreen::render)
......
...@@ -6,7 +6,6 @@ import android.widget.TextView ...@@ -6,7 +6,6 @@ import android.widget.TextView
import butterknife.BindView import butterknife.BindView
import com.biganto.visual.roompark.R import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.presentation.screen.photo.util.AlbumsHeaderViewHolder
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyclerAdapter 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.presentation.screen.settings.util.CommonViewHolder
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
...@@ -39,6 +38,7 @@ class AlbumsHeaderViewHolder(itemView: View) : CommonViewHolder<AlbumPreviewMode ...@@ -39,6 +38,7 @@ class AlbumsHeaderViewHolder(itemView: View) : CommonViewHolder<AlbumPreviewMode
@BindView(R.id.card_title) lateinit var articleTitle:TextView @BindView(R.id.card_title) lateinit var articleTitle:TextView
@BindView(R.id.card_updated) lateinit var articleDate:TextView @BindView(R.id.card_updated) lateinit var articleDate:TextView
override fun onViewBound(model: AlbumPreviewModel) { override fun onViewBound(model: AlbumPreviewModel) {
// articleDate.text = dateFormatter.format(model.published) // articleDate.text = dateFormatter.format(model.published)
articleTitle.text = model.title articleTitle.text = model.title
......
package com.biganto.visual.roompark.presentation.screen.favorites.util package com.biganto.visual.roompark.presentation.screen.albums.util
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
...@@ -10,13 +10,16 @@ import com.biganto.visual.roompark.base.RoomParkApplication ...@@ -10,13 +10,16 @@ import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.domain.model.AlbumSortedModel import com.biganto.visual.roompark.domain.model.AlbumSortedModel
import com.biganto.visual.roompark.domain.model.PhotoModel import com.biganto.visual.roompark.domain.model.PhotoModel
import com.biganto.visual.roompark.domain.model.PhotoResolutionModel import com.biganto.visual.roompark.domain.model.PhotoResolutionModel
import com.biganto.visual.roompark.presentation.screen.photo.util.AlbumViewHolder
import com.biganto.visual.roompark.presentation.screen.photo.util.PhotosAdapter import com.biganto.visual.roompark.presentation.screen.photo.util.PhotosAdapter
import com.biganto.visual.roompark.presentation.screen.photo.util.PhotosViewHolder import com.biganto.visual.roompark.presentation.screen.photo.util.PhotosViewHolder
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyclerAdapter 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.presentation.screen.settings.util.CommonViewHolder
import com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView import com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
import com.jakewharton.rxbinding3.view.detaches
import com.jakewharton.rxrelay2.BehaviorRelay
import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber
/** /**
* Created by Vladislav Bogdashkin on 16.10.2019. * Created by Vladislav Bogdashkin on 16.10.2019.
...@@ -28,6 +31,20 @@ class AlbumListAdapter : CommonRecyclerAdapter<AlbumViewHolder, AlbumSortedModel ...@@ -28,6 +31,20 @@ class AlbumListAdapter : CommonRecyclerAdapter<AlbumViewHolder, AlbumSortedModel
override fun getVhLayout(): Int = R.layout.date_album_viewholder override fun getVhLayout(): Int = R.layout.date_album_viewholder
private val disp = CompositeDisposable()
val photoObs = BehaviorRelay.create<PhotoModel>()
override fun onBindViewHolder(holder: AlbumViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
holder.setIsRecyclable(false)
disp.addAll(
holder.photoClicksObservable()
?.doOnNext { Timber.d("photo clicked $it") }
?.takeUntil( holder.itemView.detaches() )
?.subscribe(photoObs)
)
}
} }
class AlbumViewHolder(itemView: View) : CommonViewHolder<AlbumSortedModel>(itemView) { class AlbumViewHolder(itemView: View) : CommonViewHolder<AlbumSortedModel>(itemView) {
...@@ -38,6 +55,11 @@ class AlbumListAdapter : CommonRecyclerAdapter<AlbumViewHolder, AlbumSortedModel ...@@ -38,6 +55,11 @@ class AlbumListAdapter : CommonRecyclerAdapter<AlbumViewHolder, AlbumSortedModel
lateinit var photosRecyclerView: RecyclerView lateinit var photosRecyclerView: RecyclerView
// @BindView(R.id.camStatusIcon) lateinit var camStatusIcon:ImageView // @BindView(R.id.camStatusIcon) lateinit var camStatusIcon:ImageView
private var adapter:PhotosAdapter? = null
fun photoClicksObservable() =
adapter?.onItemClicked
override fun onViewBound(model: AlbumSortedModel) { override fun onViewBound(model: AlbumSortedModel) {
albumTitle.text = model.title albumTitle.text = model.title
photosRecyclerView.isNestedScrollingEnabled = false photosRecyclerView.isNestedScrollingEnabled = false
...@@ -46,10 +68,10 @@ class AlbumListAdapter : CommonRecyclerAdapter<AlbumViewHolder, AlbumSortedModel ...@@ -46,10 +68,10 @@ class AlbumListAdapter : CommonRecyclerAdapter<AlbumViewHolder, AlbumSortedModel
(photosRecyclerView.layoutManager as StaggeredGridLayoutManager) (photosRecyclerView.layoutManager as StaggeredGridLayoutManager)
.gapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS .gapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
val adapter = PhotosAdapter() adapter = PhotosAdapter()
photosRecyclerView.adapter = adapter photosRecyclerView.adapter = adapter
photosRecyclerView.itemAnimator = null photosRecyclerView.itemAnimator = null
adapter.setItems(model.items) adapter?.setItems(model.items)
} }
} }
...@@ -68,7 +90,7 @@ class PhotosViewHolder(itemView: View) : CommonViewHolder<PhotoModel>(itemView) ...@@ -68,7 +90,7 @@ class PhotosViewHolder(itemView: View) : CommonViewHolder<PhotoModel>(itemView)
} }
override fun onViewBound(model: PhotoModel) { override fun onViewBound(model: PhotoModel) {
com.biganto.visual.roompark.presentation.screen.photo.util.lowelest()?.let { model.resolutionList.lowelest()?.let {
picassoAsync picassoAsync
.load(it.url) .load(it.url)
.centerCrop() .centerCrop()
......
...@@ -14,7 +14,7 @@ import com.biganto.visual.roompark.domain.model.AlbumPreviewModel ...@@ -14,7 +14,7 @@ import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.domain.model.ArticlePreviewModel import com.biganto.visual.roompark.domain.model.ArticlePreviewModel
import com.biganto.visual.roompark.domain.model.FeedModel import com.biganto.visual.roompark.domain.model.FeedModel
import com.biganto.visual.roompark.domain.model.WebCamModel import com.biganto.visual.roompark.domain.model.WebCamModel
import com.biganto.visual.roompark.presentation.screen.photo.AlbumsScreenController import com.biganto.visual.roompark.presentation.screen.albums.AlbumsScreenController
import com.biganto.visual.roompark.presentation.screen.article.ArticleScreenController import com.biganto.visual.roompark.presentation.screen.article.ArticleScreenController
import com.biganto.visual.roompark.presentation.screen.feed_list.ArticlesScreenController import com.biganto.visual.roompark.presentation.screen.feed_list.ArticlesScreenController
import com.biganto.visual.roompark.presentation.screen.feeds.utils.AlbumsPreviewAdapter import com.biganto.visual.roompark.presentation.screen.feeds.utils.AlbumsPreviewAdapter
......
package com.biganto.visual.roompark.presentation.screen.photo package com.biganto.visual.roompark.presentation.screen.photo
import com.biganto.visual.roompark.conductor.BigantoBaseContract import com.biganto.visual.roompark.conductor.BigantoBaseContract
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import io.reactivex.Observable
/** /**
* Created by Vladislav Bogdashkin on 30.09.2019. * Created by Vladislav Bogdashkin on 30.09.2019.
*/ */
interface PhotoScreen : BigantoBaseContract<PhotoScreenViewState> { interface PhotoScreen : BigantoBaseContract<PhotoScreenViewState> {
fun onAlbumSelected(): Observable<AlbumPreviewModel>
} }
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.photo package com.biganto.visual.roompark.presentation.screen.photo
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.widget.NestedScrollView import androidx.viewpager2.widget.ViewPager2
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView import butterknife.BindView
import com.biganto.visual.roompark.R import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.IBottomNavigation import com.biganto.visual.roompark.base.IBottomNavigation
import com.biganto.visual.roompark.base.RoomParkApplication import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.base.RoomParkMainActivity import com.biganto.visual.roompark.base.RoomParkMainActivity
import com.biganto.visual.roompark.conductor.BigantoBaseController import com.biganto.visual.roompark.conductor.BigantoBaseController
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel import com.biganto.visual.roompark.presentation.screen.photo.util.PhotoViewerAdapter
import com.biganto.visual.roompark.presentation.screen.photo.util.AlbumListAdapter
import com.biganto.visual.roompark.util.extensions.scaleCenterCrop
import com.biganto.visual.roompark.util.view_utils.grid.CeilsDecoration
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import jp.wasabeef.picasso.transformations.BlurTransformation
import jp.wasabeef.picasso.transformations.ColorFilterTransformation
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
...@@ -47,57 +34,17 @@ class PhotoScreenController : ...@@ -47,57 +34,17 @@ class PhotoScreenController :
constructor(id: Int) : super(bundleOf(SELECTED_ALBUM_INDEX_KEY to id)) constructor(id: Int) : super(bundleOf(SELECTED_ALBUM_INDEX_KEY to id))
@BindView(R.id.headers_recycler_view) @BindView(R.id.photoTitle)
lateinit var headersRecyclerView: RecyclerView lateinit var albumTitle: MaterialTextView
@BindView(R.id.albums_recycler_view) @BindView(R.id.photo_frame)
lateinit var albumsRecyclerView: RecyclerView lateinit var photoViewPager: ViewPager2
@BindView(R.id.photo_albums_container)
lateinit var nestedScrollView: NestedScrollView
@BindView(R.id.header_album_title)
lateinit var currentAlbomTitle: MaterialTextView
private val photosBackgroundTarget = object : com.squareup.picasso.Target {
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
// nestedScrollView.background = placeHolderDrawable
}
override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
Timber.e(e)
}
override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
bitmap?.scaleCenterCrop(nestedScrollView)?.let {
nestedScrollView.background = BitmapDrawable(activity?.resources, it)
}
}
}
private fun bindRecycler() { private fun bindRecycler() {
headersRecyclerView.isNestedScrollingEnabled = false photoViewPager.isNestedScrollingEnabled = false
headersRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false)
headersRecyclerView.adapter = PhotoHeaderAdapter()
headersRecyclerView.itemAnimator = null
if (headersRecyclerView.itemDecorationCount == 0)
headersRecyclerView.addItemDecoration(
CeilsDecoration(1
, resources?.getDimensionPixelSize(R.dimen.ceil_grid_padding))
)
albumsRecyclerView.isNestedScrollingEnabled = false photoViewPager.adapter = PhotoViewerAdapter()
albumsRecyclerView.layoutManager =
LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
albumsRecyclerView.adapter = AlbumListAdapter()
albumsRecyclerView.itemAnimator = null
} }
override fun onViewBound(v: View) { override fun onViewBound(v: View) {
...@@ -132,11 +79,13 @@ class PhotoScreenController : ...@@ -132,11 +79,13 @@ class PhotoScreenController :
lateinit var bottomNavigation: IBottomNavigation lateinit var bottomNavigation: IBottomNavigation
override fun render(viewState: PhotoScreenViewState) { override fun render(viewState: PhotoScreenViewState) {
Timber.d("render: $viewState")
when(viewState){ when(viewState){
is PhotoScreenViewState.Idle -> render(viewState) is PhotoScreenViewState.Idle -> render(viewState)
is PhotoScreenViewState.PhotoSelected -> render(viewState) is PhotoScreenViewState.PhotoSelected -> render(viewState)
is PhotoScreenViewState.PhotoFetched -> render(viewState)
is PhotoScreenViewState.PhotoListLoaded -> render(viewState) is PhotoScreenViewState.PhotoListLoaded -> render(viewState)
is PhotoScreenViewState.HeaderAlbumChoosed -> render(viewState) is PhotoScreenViewState.AldumFetched-> render(viewState)
is PhotoScreenViewState.SomeError -> render(viewState) is PhotoScreenViewState.SomeError -> render(viewState)
} }
} }
...@@ -152,56 +101,25 @@ class PhotoScreenController : ...@@ -152,56 +101,25 @@ class PhotoScreenController :
lateinit var picassoAsync:Picasso lateinit var picassoAsync:Picasso
private fun render(viewState: PhotoScreenViewState.PhotoListLoaded) { private fun render(viewState: PhotoScreenViewState.PhotoListLoaded) {
(headersRecyclerView.adapter as PhotoHeaderAdapter).setItems( (photoViewPager.adapter as PhotoViewerAdapter).setItems(
viewState.list.asSequence().sortedByDescending { it.published }.toList() viewState.list.asSequence().sortedBy{it.sort}.toList()
)
headersRecyclerView.let {
it.scrollToPosition(
(it.adapter as PhotoHeaderAdapter).getItemPosition(viewState.selectedAlbumId)
) )
} }
viewState.list.first { it.albumId == viewState.selectedAlbumId }.let { private fun render(viewState: PhotoScreenViewState.AldumFetched) {
currentAlbomTitle.text = it.title albumTitle.text = viewState.model.title
picassoAsync
.load(it.previewUrl)
.transform(BlurTransformation(activity, 13, 2))
.transform(ColorFilterTransformation(0xCC000000.toInt()))
.into(photosBackgroundTarget)
} }
}
private fun render(viewState: PhotoScreenViewState.HeaderAlbumChoosed) { private fun render(viewState: PhotoScreenViewState.PhotoFetched){
(headersRecyclerView.adapter as PhotoHeaderAdapter) (photoViewPager.adapter as PhotoViewerAdapter).setItems(arrayListOf(viewState.model))
.getItemPosition(viewState.item.albumId).let {
headersRecyclerView.scrollToPosition(it)
} }
currentAlbomTitle.text = viewState.item.title
picassoAsync
.load(viewState.item.previewUrl)
.transform(BlurTransformation(activity, 13, 2))
.transform(ColorFilterTransformation(0xCC000000.toInt()))
.into(photosBackgroundTarget)
}
private fun render(viewState: PhotoScreenViewState.PhotoSelected){ private fun render(viewState: PhotoScreenViewState.PhotoSelected){
(albumsRecyclerView.adapter as AlbumListAdapter).setItems( // (photoViewPager.adapter as PhotoViewerAdapter).setItems(arrayListOf(viewState.model))
viewState.list.asSequence().sortedByDescending { it.published }.toList()
)
} }
override fun onAlbumSelected(): Observable<AlbumPreviewModel>
= (headersRecyclerView.adapter as PhotoHeaderAdapter)
.onItemClicked
.debounce (300L,TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { (albumsRecyclerView.adapter as AlbumListAdapter).setItems(arrayListOf())}
override fun getLayoutId(): Int = R.layout.albums_screen override fun getLayoutId(): Int = R.layout.photo_view_screen
} }
\ No newline at end of file
...@@ -3,8 +3,10 @@ package com.biganto.visual.roompark.presentation.screen.photo ...@@ -3,8 +3,10 @@ package com.biganto.visual.roompark.presentation.screen.photo
import com.biganto.visual.roompark.conductor.BigantoBasePresenter import com.biganto.visual.roompark.conductor.BigantoBasePresenter
import com.biganto.visual.roompark.domain.interactor.PhotoInteractor import com.biganto.visual.roompark.domain.interactor.PhotoInteractor
import com.biganto.visual.roompark.util.monades.ExceptionString import com.biganto.visual.roompark.util.monades.ExceptionString
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named import javax.inject.Named
...@@ -35,10 +37,30 @@ class PhotoScreenPresenter @Inject constructor( ...@@ -35,10 +37,30 @@ class PhotoScreenPresenter @Inject constructor(
// requestAlbum(model.albumId) // requestAlbum(model.albumId)
// } // }
Timber.d("fetch photoId: $selectedIndex")
val fetchAlbum = interactor.fetchPhoto(selectedIndex)
.doOnNext{Timber.d("got photo: $it")}
.flatMap { photo ->
Observable.merge<PhotoScreenViewState>(
arrayListOf(
interactor.getAlbum(photo.albumId)
.map { PhotoScreenViewState.AldumFetched(it) }
,
interactor.fetchPhotos(photo.albumId)
.doOnNext{Timber.d("got photos: $it")}
.map<PhotoScreenViewState> { PhotoScreenViewState.PhotoListLoaded(it) }
.startWith(Observable.just(PhotoScreenViewState.PhotoFetched(photo)))
))
}
val state = restoreStateObservable val state = restoreStateObservable
.mergeWith(fetchAlbum)
// .mergeWith(fetchParents) // .mergeWith(fetchParents)
// .mergeWith(fetchSelected) // .mergeWith(fetchSelected)
// .mergeWith(headerItemSelected) // .mergeWith(headerItemSelected)
......
package com.biganto.visual.roompark.presentation.screen.photo package com.biganto.visual.roompark.presentation.screen.photo
import com.biganto.visual.roompark.conductor.BigantoBaseViewState import com.biganto.visual.roompark.conductor.BigantoBaseViewState
import com.biganto.visual.roompark.domain.model.AlbumPreviewModel
import com.biganto.visual.roompark.domain.model.PhotoModel import com.biganto.visual.roompark.domain.model.PhotoModel
import com.biganto.visual.roompark.domain.model.PhotoResolutionModel import com.biganto.visual.roompark.domain.model.PhotoResolutionModel
import com.biganto.visual.roompark.util.monades.ExceptionString import com.biganto.visual.roompark.util.monades.ExceptionString
...@@ -13,6 +14,8 @@ import com.biganto.visual.roompark.util.monades.ExceptionString ...@@ -13,6 +14,8 @@ import com.biganto.visual.roompark.util.monades.ExceptionString
sealed class PhotoScreenViewState : BigantoBaseViewState() { sealed class PhotoScreenViewState : BigantoBaseViewState() {
class Idle : PhotoScreenViewState() class Idle : PhotoScreenViewState()
class PhotoListLoaded(val list:List<PhotoModel>) : PhotoScreenViewState() class PhotoListLoaded(val list:List<PhotoModel>) : PhotoScreenViewState()
class PhotoFetched(val model:PhotoModel) : PhotoScreenViewState()
class PhotoSelected(val model:PhotoResolutionModel) : PhotoScreenViewState() class PhotoSelected(val model:PhotoResolutionModel) : PhotoScreenViewState()
class SomeError(val exception: ExceptionString) : PhotoScreenViewState() class SomeError(val exception: ExceptionString) : PhotoScreenViewState()
class AldumFetched(val model:AlbumPreviewModel) : PhotoScreenViewState()
} }
\ No newline at end of file
package com.biganto.visual.roompark.presentation.screen.photo.util package com.biganto.visual.roompark.presentation.screen.photo.util
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import androidx.recyclerview.widget.StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
import butterknife.BindView import butterknife.BindView
import com.biganto.visual.roompark.R import com.biganto.visual.roompark.R
import com.biganto.visual.roompark.base.RoomParkApplication import com.biganto.visual.roompark.base.RoomParkApplication
import com.biganto.visual.roompark.domain.model.AlbumSortedModel
import com.biganto.visual.roompark.domain.model.PhotoModel import com.biganto.visual.roompark.domain.model.PhotoModel
import com.biganto.visual.roompark.domain.model.PhotoResolutionModel import com.biganto.visual.roompark.domain.model.PhotoResolutionModel
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyclerAdapter import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyclerAdapter
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder
import com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView import com.biganto.visual.roompark.util.view_utils.image_view.RoundedImageView
import com.google.android.material.textview.MaterialTextView
/** /**
* Created by Vladislav Bogdashkin on 16.10.2019. * Created by Vladislav Bogdashkin on 16.10.2019.
*/ */
class AlbumListAdapter : CommonRecyclerAdapter<AlbumViewHolder, AlbumSortedModel>() {
override val vhKlazz = AlbumViewHolder::class
override fun getVhLayout(): Int = R.layout.date_album_viewholder
}
class AlbumViewHolder(itemView: View) : CommonViewHolder<AlbumSortedModel>(itemView) {
@BindView(R.id.date_title_textview)
lateinit var albumTitle: MaterialTextView
@BindView(R.id.photos_recyclerview)
lateinit var photosRecyclerView: RecyclerView
// @BindView(R.id.camStatusIcon) lateinit var camStatusIcon:ImageView
override fun onViewBound(model: AlbumSortedModel) {
albumTitle.text = model.title
photosRecyclerView.isNestedScrollingEnabled = false
photosRecyclerView.layoutManager =
StaggeredGridLayoutManager(4, RecyclerView.VERTICAL)
(photosRecyclerView.layoutManager as StaggeredGridLayoutManager)
.gapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
val adapter = PhotosAdapter()
photosRecyclerView.adapter = adapter
photosRecyclerView.itemAnimator = null
adapter.setItems(model.items)
}
}
class PhotosAdapter : CommonRecyclerAdapter<PhotosViewHolder, PhotoModel>() { class PhotosAdapter : CommonRecyclerAdapter<PhotosViewHolder, PhotoModel>() {
override val vhKlazz = PhotosViewHolder::class override val vhKlazz = PhotosViewHolder::class
......
...@@ -9,10 +9,7 @@ import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyc ...@@ -9,10 +9,7 @@ import com.biganto.visual.roompark.presentation.screen.settings.util.CommonRecyc
import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder import com.biganto.visual.roompark.presentation.screen.settings.util.CommonViewHolder
import com.github.chrisbanes.photoview.PhotoView import com.github.chrisbanes.photoview.PhotoView
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import jp.wasabeef.picasso.transformations.BlurTransformation
import timber.log.Timber
/** /**
...@@ -45,20 +42,12 @@ class PhotoViewerViewHolder(itemView: View) : CommonViewHolder<PhotoModel>(itemV ...@@ -45,20 +42,12 @@ class PhotoViewerViewHolder(itemView: View) : CommonViewHolder<PhotoModel>(itemV
photoDesc.visibility = if (model.description.isNullOrBlank()) View.GONE else View.VISIBLE photoDesc.visibility = if (model.description.isNullOrBlank()) View.GONE else View.VISIBLE
model.description?.let{ photoDesc.text = it} model.description?.let{ photoDesc.text = it}
// articleDate.text = dateFormatter.format(model.published)
model.optimalResolution(photoView.width,photoView.height).let { model.optimalResolution(photoView.width,photoView.height).let {
picassoAsync picassoAsync
.load(it.url) .load(it.url)
.transform(BlurTransformation(itemView.context,13,2)) // .transform(BlurTransformation(itemView.context,13,2))
.into(photoView,object :Callback{ .into(photoView)
override fun onSuccess() {
picassoAsync.load(it.url).into(photoView)
}
override fun onError(e: Exception?) {
Timber.e(e)
}
})
} }
} }
} }
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"> android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorOpacityBackground"
android:orientation="vertical">
<com.github.chrisbanes.photoview.PhotoView <com.github.chrisbanes.photoview.PhotoView
android:id="@+id/photo_view" android:id="@+id/photo_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
/>
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/photo_description" android:id="@+id/photo_description"
...@@ -21,5 +25,4 @@ ...@@ -21,5 +25,4 @@
android:paddingBottom="16dp" android:paddingBottom="16dp"
android:textAlignment="center" /> android:textAlignment="center" />
</FrameLayout> </FrameLayout>
\ No newline at end of file
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
android:background="@color/colorOpacityBackground"> android:background="@color/colorOpacityBackground">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/textView10" android:id="@+id/photoTitle"
style="@style/Common_Text.Inverted" style="@style/Common_Text.Inverted"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<com.github.chrisbanes.photoview.PhotoView <androidx.viewpager2.widget.ViewPager2
android:id="@+id/photo_frame" android:id="@+id/photo_frame"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
app:layout_constraintBottom_toTopOf="@+id/guideline2" app:layout_constraintBottom_toTopOf="@+id/guideline2"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView10" app:layout_constraintTop_toBottomOf="@+id/photoTitle"
app:srcCompat="@drawable/default_image_placeholder" /> app:srcCompat="@drawable/default_image_placeholder" />
<androidx.constraintlayout.widget.Guideline <androidx.constraintlayout.widget.Guideline
...@@ -78,20 +78,4 @@ ...@@ -78,20 +78,4 @@
app:layout_constraintStart_toEndOf="@+id/show_full_button" app:layout_constraintStart_toEndOf="@+id/show_full_button"
app:layout_constraintTop_toTopOf="@+id/guideline2" /> app:layout_constraintTop_toTopOf="@+id/guideline2" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView11"
style="@style/Feed"
android:textColor="@color/colorInvertedText"
android:background="@color/colorOpacityCardBackground"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="64dp"
android:paddingTop="16dp"
android:paddingEnd="64dp"
android:paddingBottom="16dp"
android:text="На рынке новостроек квартиры без отделки часто находят отклик у покупателей."
android:textAlignment="center"
app:layout_constraintBottom_toTopOf="@+id/guideline2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ 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