Add app details stream (Related, Similar, More by dev)

This commit is contained in:
Rahul Kumar Patel
2021-02-20 03:57:26 +05:30
parent c6f528c82e
commit 209e3f69dd
8 changed files with 315 additions and 60 deletions

View File

@@ -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()

View File

@@ -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))
}
}
}
}
}
}

View File

@@ -324,6 +324,8 @@ class AppDetailsActivity : BaseDetailsActivity() {
inflateBetaSubscription(B.layoutDetailsBeta, app)
}
inflateAppStream(B.epoxyRecyclerStream, app)
}
}

View File

@@ -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,

View File

@@ -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
}
}
}

View File

@@ -21,6 +21,6 @@
android:shape="rectangle">
<solid android:color="?colorAccent" />
<size
android:width="1dp"
android:width="1.25dp"
android:height="12dp" />
</shape>

View File

@@ -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" />

View File

@@ -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"