Add app details stream (Related, Similar, More by dev)
This commit is contained in:
@@ -172,7 +172,7 @@ dependencies {
|
||||
implementation "com.github.topjohnwu.libsu:core:${versions.libsu}"
|
||||
|
||||
//Love <3
|
||||
api("com.gitlab.AuroraOSS:gplayapi:9d327499ae")
|
||||
api("com.gitlab.AuroraOSS:gplayapi:101c69ecde")
|
||||
}
|
||||
|
||||
Properties props = new Properties()
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.epoxy.controller
|
||||
|
||||
import com.aurora.gplayapi.data.models.StreamBundle
|
||||
import com.aurora.store.view.epoxy.groups.CarouselModelGroup
|
||||
import com.aurora.store.view.epoxy.groups.CarouselShimmerGroup
|
||||
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
|
||||
|
||||
open class DetailsCarouselController(private val callbacks: Callbacks) :
|
||||
GenericCarouselController(callbacks) {
|
||||
|
||||
override fun buildModels(streamBundle: StreamBundle?) {
|
||||
setFilterDuplicates(true)
|
||||
if (streamBundle == null) {
|
||||
for (i in 1..2) {
|
||||
add(
|
||||
CarouselShimmerGroup()
|
||||
.id(i)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (streamBundle.streamClusters.isNotEmpty()) {
|
||||
if (streamBundle.streamClusters.size == 1) {
|
||||
streamBundle
|
||||
.streamClusters
|
||||
.values
|
||||
.filter { applyFilter(it) }
|
||||
.forEach { streamCluster ->
|
||||
streamCluster.clusterAppList.forEach {
|
||||
add(
|
||||
AppListViewModel_()
|
||||
.id(it.id)
|
||||
.app(it)
|
||||
.click { _ -> callbacks.onAppClick(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
streamBundle
|
||||
.streamClusters
|
||||
.values
|
||||
.filter { applyFilter(it) }
|
||||
.forEach { streamCluster ->
|
||||
add(CarouselModelGroup(streamCluster, callbacks))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,6 +324,8 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
|
||||
inflateBetaSubscription(B.layoutDetailsBeta, app)
|
||||
}
|
||||
|
||||
inflateAppStream(B.epoxyRecyclerStream, app)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,12 +29,14 @@ import android.widget.RelativeLayout
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.airbnb.epoxy.EpoxyRecyclerView
|
||||
import com.aurora.Constants
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.Review
|
||||
import com.aurora.gplayapi.data.models.*
|
||||
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||
import com.aurora.gplayapi.helpers.ReviewsHelper
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.ViewState
|
||||
import com.aurora.store.data.model.ExodusReport
|
||||
import com.aurora.store.data.model.Report
|
||||
import com.aurora.store.data.network.HttpClient
|
||||
@@ -47,11 +49,14 @@ import com.aurora.store.util.extensions.load
|
||||
import com.aurora.store.util.extensions.show
|
||||
import com.aurora.store.util.extensions.toast
|
||||
import com.aurora.store.view.custom.RatingView
|
||||
import com.aurora.store.view.epoxy.controller.DetailsCarouselController
|
||||
import com.aurora.store.view.epoxy.controller.GenericCarouselController
|
||||
import com.aurora.store.view.epoxy.views.*
|
||||
import com.aurora.store.view.epoxy.views.details.ReviewViewModel_
|
||||
import com.aurora.store.view.epoxy.views.details.ScreenshotView
|
||||
import com.aurora.store.view.epoxy.views.details.ScreenshotViewModel_
|
||||
import com.aurora.store.view.ui.commons.BaseActivity
|
||||
import com.aurora.store.viewmodel.details.DetailsClusterViewModel
|
||||
import nl.komponents.kovenant.task
|
||||
import nl.komponents.kovenant.ui.failUi
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
@@ -69,7 +74,8 @@ abstract class BaseDetailsActivity : BaseActivity() {
|
||||
B.txtInstalls.text = CommonUtil.addDiPrefix(app.installs)
|
||||
B.txtSize.text = CommonUtil.addSiPrefix(app.size)
|
||||
B.txtRating.text = app.labeledRating
|
||||
B.txtSdk.text = String.format("Target SDK %s", "${app.targetSdk}")
|
||||
B.txtSdk.text = ("Target SDK ${app.targetSdk}")
|
||||
B.txtUpdated.text = app.updatedOn
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
B.txtDescription.justificationMode = Layout.JUSTIFICATION_MODE_INTER_WORD
|
||||
@@ -280,6 +286,57 @@ abstract class BaseDetailsActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
fun inflateAppStream(epoxyRecyclerView: EpoxyRecyclerView, app: App) {
|
||||
app.detailsStreamUrl?.let {
|
||||
val VM = ViewModelProvider(this).get(DetailsClusterViewModel::class.java)
|
||||
|
||||
val carouselController =
|
||||
DetailsCarouselController(object : GenericCarouselController.Callbacks {
|
||||
override fun onHeaderClicked(streamCluster: StreamCluster) {
|
||||
if (streamCluster.clusterBrowseUrl.isNotEmpty())
|
||||
openStreamBrowseActivity(streamCluster.clusterBrowseUrl)
|
||||
else
|
||||
toast("Browse page unavailable")
|
||||
}
|
||||
|
||||
override fun onClusterScrolled(streamCluster: StreamCluster) {
|
||||
VM.observeCluster(streamCluster)
|
||||
}
|
||||
|
||||
override fun onAppClick(app: App) {
|
||||
openDetailsActivity(app)
|
||||
}
|
||||
|
||||
override fun onAppLongClick(app: App) {
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
VM.liveData.observe(this, {
|
||||
when (it) {
|
||||
is ViewState.Empty -> {
|
||||
}
|
||||
is ViewState.Loading -> {
|
||||
|
||||
}
|
||||
is ViewState.Error -> {
|
||||
|
||||
}
|
||||
is ViewState.Status -> {
|
||||
|
||||
}
|
||||
is ViewState.Success<*> -> {
|
||||
carouselController.setData(it.data as StreamBundle)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
epoxyRecyclerView.setController(carouselController)
|
||||
|
||||
VM.getStreamBundle(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBetaActions(B: LayoutDetailsBetaBinding, isSubscribed: Boolean) {
|
||||
if (isSubscribed) {
|
||||
B.btnBetaAction.text = getString(R.string.action_leave)
|
||||
@@ -305,7 +362,6 @@ abstract class BaseDetailsActivity : BaseActivity() {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
private fun openScreenshotActivity(app: App, position: Int) {
|
||||
val intent = Intent(
|
||||
this,
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* Aurora Store is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Aurora Store is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.viewmodel.details
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.AuthData
|
||||
import com.aurora.gplayapi.data.models.StreamBundle
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||
import com.aurora.gplayapi.helpers.StreamHelper
|
||||
import com.aurora.store.data.RequestState
|
||||
import com.aurora.store.data.ViewState
|
||||
import com.aurora.store.data.network.HttpClient
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.util.Log
|
||||
import com.aurora.store.viewmodel.BaseAndroidViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
|
||||
class DetailsClusterViewModel(application: Application) : BaseAndroidViewModel(application) {
|
||||
|
||||
private var authData: AuthData = AuthProvider.with(application).getAuthData()
|
||||
private var appDetailsHelper = AppDetailsHelper(authData).using(HttpClient.getPreferredClient())
|
||||
private var streamHelper = StreamHelper(authData)
|
||||
|
||||
val liveData: MutableLiveData<ViewState> = MutableLiveData()
|
||||
var streamBundle: StreamBundle = StreamBundle()
|
||||
|
||||
lateinit var type: StreamHelper.Type
|
||||
lateinit var category: StreamHelper.Category
|
||||
|
||||
override fun observe() {
|
||||
|
||||
}
|
||||
|
||||
fun getStreamBundle(
|
||||
streamUrl: String
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
supervisorScope {
|
||||
try {
|
||||
streamBundle = appDetailsHelper.getDetailsStream(streamUrl)
|
||||
liveData.postValue(ViewState.Success(streamBundle))
|
||||
requestState = RequestState.Complete
|
||||
} catch (e: Exception) {
|
||||
requestState = RequestState.Pending
|
||||
liveData.postValue(ViewState.Error(e.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun observeCluster(streamCluster: StreamCluster) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
supervisorScope {
|
||||
try {
|
||||
if (streamCluster.hasNext()) {
|
||||
val newCluster =
|
||||
streamHelper.getNextStreamCluster(streamCluster.clusterNextPageUrl)
|
||||
updateCluster(newCluster)
|
||||
liveData.postValue(ViewState.Success(streamBundle))
|
||||
} else {
|
||||
Log.i("End of cluster")
|
||||
streamCluster.clusterNextPageUrl = String()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
liveData.postValue(ViewState.Error(e.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCluster(newCluster: StreamCluster) {
|
||||
streamBundle.streamClusters[newCluster.id]?.apply {
|
||||
clusterAppList.addAll(newCluster.clusterAppList)
|
||||
clusterNextPageUrl = newCluster.clusterNextPageUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,6 @@
|
||||
android:shape="rectangle">
|
||||
<solid android:color="?colorAccent" />
|
||||
<size
|
||||
android:width="1dp"
|
||||
android:width="1.25dp"
|
||||
android:height="12dp" />
|
||||
</shape>
|
||||
@@ -18,6 +18,8 @@
|
||||
-->
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -88,6 +90,18 @@
|
||||
android:id="@+id/layout_details_beta"
|
||||
layout="@layout/layout_details_beta" />
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/epoxy_recycler_stream"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/padding_small"
|
||||
android:paddingEnd="@dimen/padding_small"
|
||||
app:itemSpacing="@dimen/margin_small"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/view_app" />
|
||||
|
||||
<include
|
||||
android:id="@+id/layout_details_dev"
|
||||
layout="@layout/layout_details_dev" />
|
||||
|
||||
@@ -33,70 +33,84 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/layout_extras"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/padding_small"
|
||||
app:alignContent="space_between"
|
||||
app:alignItems="center"
|
||||
app:dividerDrawableHorizontal="@drawable/divider"
|
||||
app:dividerDrawableVertical="@drawable/divider_line"
|
||||
app:flexDirection="row"
|
||||
app:flexWrap="wrap"
|
||||
app:showDividerHorizontal="middle"
|
||||
app:showDividerVertical="middle">
|
||||
android:scrollbars="none">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_rating"
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:drawablePadding="@dimen/padding_small"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
app:drawableStartCompat="@drawable/ic_star"
|
||||
app:drawableTint="?colorAccent"
|
||||
tools:text="3.5" />
|
||||
android:divider="@drawable/divider_line"
|
||||
android:orientation="horizontal"
|
||||
android:showDividers="middle">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_installs"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:drawablePadding="@dimen/padding_small"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
app:drawableStartCompat="@drawable/ic_download"
|
||||
app:drawableTint="?colorAccent"
|
||||
tools:text="2500" />
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_rating"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:drawablePadding="@dimen/padding_small"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
app:drawableStartCompat="@drawable/ic_star"
|
||||
app:drawableTint="?colorAccent"
|
||||
tools:text="3.5" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_size"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:drawablePadding="@dimen/padding_small"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
app:drawableTint="?colorAccent"
|
||||
tools:text="25 MB" />
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_installs"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:drawablePadding="@dimen/padding_small"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
app:drawableStartCompat="@drawable/ic_download"
|
||||
app:drawableTint="?colorAccent"
|
||||
tools:text="2500" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_sdk"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:drawablePadding="@dimen/padding_small"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
app:drawableTint="?colorAccent"
|
||||
tools:text="Android 7+" />
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_size"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:drawablePadding="@dimen/padding_small"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
app:drawableTint="?colorAccent"
|
||||
tools:text="25 MB" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_sdk"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:drawablePadding="@dimen/padding_small"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
app:drawableTint="?colorAccent"
|
||||
tools:text="Android 7+" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_updated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:drawablePadding="@dimen/padding_small"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
app:drawableTint="?colorAccent"
|
||||
tools:text="Jan 21 2020" />
|
||||
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_description"
|
||||
|
||||
Reference in New Issue
Block a user