Skip to content

Commit 55ad563

Browse files
authored
Merge branch 'trunk' into issue/CMM-1037-StatsEmptyView
2 parents e9ea108 + c538897 commit 55ad563

File tree

21 files changed

+735
-261
lines changed

21 files changed

+735
-261
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.wordpress.android.modules
2+
3+
import dagger.Binds
4+
import dagger.Module
5+
import dagger.hilt.InstallIn
6+
import dagger.hilt.components.SingletonComponent
7+
import org.wordpress.android.ui.newstats.datasource.StatsDataSource
8+
import org.wordpress.android.ui.newstats.datasource.StatsDataSourceImpl
9+
10+
@InstallIn(SingletonComponent::class)
11+
@Module
12+
abstract class StatsModule {
13+
@Binds
14+
abstract fun bindStatsDataSource(impl: StatsDataSourceImpl): StatsDataSource
15+
}

WordPress/src/main/java/org/wordpress/android/ui/jetpack/scan/ScanFragment.kt

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import org.wordpress.android.ui.pages.SnackbarMessageHolder
3030
import org.wordpress.android.ui.prefs.EmptyViewRecyclerView
3131
import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures
3232
import org.wordpress.android.ui.utils.UiHelpers
33-
import org.wordpress.android.util.ColorUtils
3433
import org.wordpress.android.util.extensions.getSerializableCompat
3534
import org.wordpress.android.util.extensions.getSerializableExtraCompat
3635
import org.wordpress.android.util.image.ImageManager
@@ -94,28 +93,27 @@ class ScanFragment : Fragment(R.layout.scan_fragment) {
9493

9594
private fun ScanFragmentBinding.setupObservers() {
9695
viewModel.uiState.observe(
97-
viewLifecycleOwner,
98-
{ uiState ->
99-
uiHelpers.updateVisibility(progressBar, uiState.loadingVisible)
100-
uiHelpers.updateVisibility(recyclerView, uiState.contentVisible)
101-
uiHelpers.updateVisibility(actionableEmptyView, uiState.errorVisible)
102-
103-
when (uiState) {
104-
is ContentUiState -> updateContentLayout(uiState)
105-
106-
is FullScreenLoadingUiState -> { // Do Nothing
107-
}
108-
109-
is ErrorUiState.NoConnection,
110-
is ErrorUiState.GenericRequestFailed,
111-
is ErrorUiState.ScanRequestFailed,
112-
is ErrorUiState.MultisiteNotSupported,
113-
is ErrorUiState.VaultPressActiveOnSite -> updateErrorLayout(uiState as ErrorUiState)
96+
viewLifecycleOwner
97+
) { uiState ->
98+
uiHelpers.updateVisibility(progressBar, uiState.loadingVisible)
99+
uiHelpers.updateVisibility(recyclerView, uiState.contentVisible)
100+
uiHelpers.updateVisibility(actionableEmptyView, uiState.errorVisible)
101+
102+
when (uiState) {
103+
is ContentUiState -> updateContentLayout(uiState)
104+
105+
is FullScreenLoadingUiState -> { // Do Nothing
114106
}
107+
108+
is ErrorUiState.NoConnection,
109+
is ErrorUiState.GenericRequestFailed,
110+
is ErrorUiState.ScanRequestFailed,
111+
is ErrorUiState.MultisiteNotSupported,
112+
is ErrorUiState.VaultPressActiveOnSite -> updateErrorLayout(uiState)
115113
}
116-
)
114+
}
117115

118-
viewModel.snackbarEvents.observeEvent(viewLifecycleOwner, { it.showSnackbar() })
116+
viewModel.snackbarEvents.observeEvent(viewLifecycleOwner) { it.showSnackbar() }
119117

120118
viewModel.navigationEvents.observeEvent(
121119
viewLifecycleOwner
@@ -147,10 +145,6 @@ class ScanFragment : Fragment(R.layout.scan_fragment) {
147145
private fun ScanFragmentBinding.updateErrorLayout(state: ErrorUiState) {
148146
uiHelpers.setTextOrHide(actionableEmptyView.title, state.title)
149147
uiHelpers.setTextOrHide(actionableEmptyView.subtitle, state.subtitle)
150-
actionableEmptyView.image.setImageResource(state.image)
151-
state.imageColorResId?.let {
152-
ColorUtils.setImageResourceWithTint(actionableEmptyView.image, state.image, it)
153-
} ?: actionableEmptyView.image.setImageResource(state.image)
154148
state.buttonText?.let { uiHelpers.setTextOrHide(actionableEmptyView.button, state.buttonText) }
155149
state.action?.let { action -> actionableEmptyView.button.setOnClickListener { action.invoke() } }
156150
}

WordPress/src/main/java/org/wordpress/android/ui/jetpack/scan/ScanViewModel.kt

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package org.wordpress.android.ui.jetpack.scan
22

3-
import androidx.annotation.ColorRes
4-
import androidx.annotation.DrawableRes
53
import androidx.annotation.StringRes
64
import androidx.lifecycle.LiveData
75
import androidx.lifecycle.MediatorLiveData
@@ -366,53 +364,35 @@ class ScanViewModel @Inject constructor(
366364
data class ContentUiState(val items: List<JetpackListItemState>) : UiState(contentVisible = true)
367365

368366
sealed class ErrorUiState : UiState(errorVisible = true) {
369-
abstract val image: Int
370-
open val imageColorResId: Int? = null
371367
abstract val title: UiString
372368
abstract val subtitle: UiString
373369
open val buttonText: UiString? = null
374370
open val action: (() -> Unit)? = null
375371

376372
data class NoConnection(override val action: () -> Unit) : ErrorUiState() {
377-
@DrawableRes
378-
override val image = R.drawable.img_illustration_cloud_off_152dp
379373
override val title = UiStringRes(R.string.scan_no_network_title)
380374
override val subtitle = UiStringRes(R.string.scan_no_network_subtitle)
381375
override val buttonText = UiStringRes(R.string.retry)
382376
}
383377

384378
data class GenericRequestFailed(override val action: () -> Unit) : ErrorUiState() {
385-
@DrawableRes
386-
override val image = R.drawable.img_illustration_cloud_off_152dp
387379
override val title = UiStringRes(R.string.scan_request_failed_title)
388380
override val subtitle = UiStringRes(R.string.scan_request_failed_subtitle)
389381
override val buttonText = UiStringRes(R.string.contact_support)
390382
}
391383

392384
data class ScanRequestFailed(override val action: () -> Unit) : ErrorUiState() {
393-
@DrawableRes
394-
override val image = R.drawable.img_illustration_empty_results_216dp
395385
override val title = UiStringRes(R.string.scan_start_request_failed_title)
396386
override val subtitle = UiStringRes(R.string.scan_start_request_failed_subtitle)
397387
override val buttonText = UiStringRes(R.string.contact_support)
398388
}
399389

400390
object MultisiteNotSupported : ErrorUiState() {
401-
@DrawableRes
402-
override val image = R.drawable.ic_baseline_security_white_24dp
403-
404-
@ColorRes
405-
override val imageColorResId = R.color.gray
406391
override val title = UiStringRes(R.string.scan_multisite_not_supported_title)
407392
override val subtitle = UiStringRes(R.string.scan_multisite_not_supported_subtitle)
408393
}
409394

410395
data class VaultPressActiveOnSite(override val action: () -> Unit) : ErrorUiState() {
411-
@DrawableRes
412-
override val image = R.drawable.ic_shield_warning_white
413-
414-
@ColorRes
415-
override val imageColorResId = R.color.error_60
416396
override val title = UiStringRes(R.string.scan_vault_press_active_on_site_title)
417397
override val subtitle = UiStringRes(R.string.scan_vault_press_active_on_site_subtitle)
418398
override val buttonText = UiStringRes(R.string.scan_vault_press_active_on_site_button_text)

WordPress/src/main/java/org/wordpress/android/ui/jetpack/scan/history/ScanHistoryFragment.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@ class ScanHistoryFragment : Fragment(R.layout.scan_history_fragment), MenuProvid
9696

9797
private fun FullscreenErrorWithRetryBinding.updateErrorLayout(uiState: ErrorUiState) {
9898
uiHelpers.setTextOrHide(errorTitle, uiState.title)
99-
uiHelpers.updateVisibility(errorImage, true)
100-
errorImage.setImageResource(uiState.img)
99+
uiHelpers.updateVisibility(errorImage, false)
101100
errorRetry.setOnClickListener { uiState.retry.invoke() }
102101
}
103102

WordPress/src/main/java/org/wordpress/android/ui/jetpack/scan/history/ScanHistoryViewModel.kt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package org.wordpress.android.ui.jetpack.scan.history
22

33
import android.annotation.SuppressLint
44
import android.os.Parcelable
5-
import androidx.annotation.DrawableRes
65
import androidx.lifecycle.LiveData
76
import androidx.lifecycle.MutableLiveData
87
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -90,21 +89,14 @@ class ScanHistoryViewModel @Inject constructor(
9089

9190
sealed class ErrorUiState : UiState(errorVisible = true) {
9291
abstract val title: UiString
93-
abstract val img: Int
9492
abstract val retry: () -> Unit
9593

9694
data class NoConnection(override val retry: () -> Unit) : ErrorUiState() {
9795
override val title: UiString = UiStringRes(R.string.scan_history_no_connection)
98-
99-
@DrawableRes
100-
override val img: Int = R.drawable.img_illustration_cloud_off_152dp
10196
}
10297

10398
data class RequestFailed(override val retry: () -> Unit) : ErrorUiState() {
10499
override val title: UiString = UiStringRes(R.string.scan_history_request_failed)
105-
106-
@DrawableRes
107-
override val img: Int = R.drawable.img_illustration_cloud_off_152dp
108100
}
109101
}
110102
}

WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteItemsViewModelSlice.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,18 @@ class SiteItemsViewModelSlice @Inject constructor(
6060
private suspend fun rebuildSiteItemsForJetpackCapabilities(site: SiteModel) {
6161
jetpackCapabilitiesUseCase.getJetpackPurchasedProducts(site.siteId).collect { purchasedProducts ->
6262
// if the site has scan or backup enabled, then only rebuild the site items
63-
if(purchasedProducts.scan || purchasedProducts.backup) {
63+
if (purchasedProducts.scan || purchasedProducts.backup) {
6464
val items = siteItemsBuilder.build(
6565
getParams(
6666
shouldEnableFocusPoints = false,
6767
site = site,
6868
backupAvailable = purchasedProducts.backup,
69-
scanAvailable = purchasedProducts.scan && !site.isWPCom && !site.isWPComAtomic
69+
scanAvailable = purchasedProducts.scan
7070
)
7171
)
7272
_uiModel.postValue(items)
7373
}
74-
} // end collect
74+
}
7575
}
7676

7777
fun getParams(

WordPress/src/main/java/org/wordpress/android/ui/newstats/NewStatsActivity.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ import kotlinx.coroutines.launch
4040
import org.wordpress.android.R
4141
import org.wordpress.android.ui.compose.theme.AppThemeM3
4242
import org.wordpress.android.ui.main.BaseAppCompatActivity
43-
import org.wordpress.android.ui.newstats.todaysstat.TodaysStatsCard
44-
import org.wordpress.android.ui.newstats.todaysstat.TodaysStatsViewModel
43+
import org.wordpress.android.ui.newstats.todaysstats.TodaysStatsCard
44+
import org.wordpress.android.ui.newstats.todaysstats.TodaysStatsViewModel
4545
import org.wordpress.android.ui.newstats.viewsstats.ViewsStatsCard
4646
import org.wordpress.android.ui.newstats.viewsstats.ViewsStatsViewModel
4747

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package org.wordpress.android.ui.newstats.datasource
2+
3+
/**
4+
* Data source interface for fetching stats data.
5+
* This abstraction allows mocking the data layer in tests without needing access to uniffi objects.
6+
*/
7+
interface StatsDataSource {
8+
/**
9+
* Initializes the data source with the access token.
10+
*/
11+
fun init(accessToken: String)
12+
13+
/**
14+
* Fetches stats data for a specific site.
15+
*
16+
* @param siteId The WordPress.com site ID
17+
* @param unit The time unit for the stats (HOUR, DAY, etc.)
18+
* @param quantity The number of data points to fetch
19+
* @param endDate The end date for the stats period (format: yyyy-MM-dd)
20+
* @return Result containing the stats data or an error
21+
*/
22+
suspend fun fetchStatsVisits(
23+
siteId: Long,
24+
unit: StatsUnit,
25+
quantity: Int,
26+
endDate: String
27+
): StatsVisitsDataResult
28+
}
29+
30+
/**
31+
* Time unit for stats data.
32+
*/
33+
enum class StatsUnit {
34+
HOUR,
35+
DAY
36+
}
37+
38+
/**
39+
* Result wrapper for stats visits fetch operation.
40+
*/
41+
sealed class StatsVisitsDataResult {
42+
data class Success(val data: StatsVisitsData) : StatsVisitsDataResult()
43+
data class Error(val message: String) : StatsVisitsDataResult()
44+
}
45+
46+
/**
47+
* Stats visits data from the API.
48+
* Contains all the data points for views, visitors, likes, comments, and posts.
49+
*/
50+
data class StatsVisitsData(
51+
val visits: List<VisitsDataPoint>,
52+
val visitors: List<VisitorsDataPoint>,
53+
val likes: List<LikesDataPoint>,
54+
val comments: List<CommentsDataPoint>,
55+
val posts: List<PostsDataPoint>
56+
)
57+
58+
/**
59+
* Data point for visits/views.
60+
*/
61+
data class VisitsDataPoint(
62+
val period: String,
63+
val visits: Long
64+
)
65+
66+
/**
67+
* Data point for visitors.
68+
*/
69+
data class VisitorsDataPoint(
70+
val period: String,
71+
val visitors: Long
72+
)
73+
74+
/**
75+
* Data point for likes.
76+
*/
77+
data class LikesDataPoint(
78+
val period: String,
79+
val likes: Long
80+
)
81+
82+
/**
83+
* Data point for comments.
84+
*/
85+
data class CommentsDataPoint(
86+
val period: String,
87+
val comments: Long
88+
)
89+
90+
/**
91+
* Data point for posts.
92+
*/
93+
data class PostsDataPoint(
94+
val period: String,
95+
val posts: Long
96+
)

0 commit comments

Comments
 (0)