Skip to content

Commit

Permalink
Merge pull request #12558 from woocommerce/issue/introduce-other-stat…
Browse files Browse the repository at this point in the history
…s-complications

[HACK Week: Wear App] Introduce Order Count and Visitors count complications
  • Loading branch information
ThomazFB authored Sep 9, 2024
2 parents 43961f2 + 0fcf472 commit f65273e
Show file tree
Hide file tree
Showing 8 changed files with 310 additions and 38 deletions.
36 changes: 36 additions & 0 deletions WooCommerce-Wear/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,42 @@
android:value="1800" />
</service>

<service
android:name=".wear.complications.ordercount.OrderCountComplicationService"
android:exported="true"
android:icon="@drawable/img_woo_bubble_monochrome"
android:label="@string/order_count_complication_label"
android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER">
<intent-filter>
<action android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST" />
</intent-filter>

<meta-data
android:name="android.support.wearable.complications.SUPPORTED_TYPES"
android:value="SHORT_TEXT" />
<meta-data
android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
android:value="1800" />
</service>

<service
android:name=".wear.complications.visitorscount.VisitorsCountComplicationService"
android:exported="true"
android:icon="@drawable/img_woo_bubble_monochrome"
android:label="@string/visitors_count_complication_label"
android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER">
<intent-filter>
<action android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST" />
</intent-filter>

<meta-data
android:name="android.support.wearable.complications.SUPPORTED_TYPES"
android:value="SHORT_TEXT" />
<meta-data
android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
android:value="1800" />
</service>

<service
android:name=".wear.complications.shortcut.AppShortcutComplicationService"
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.woocommerce.android.wear.complications

import android.icu.text.CompactDecimalFormat
import com.woocommerce.android.wear.complications.FetchStatsForComplications.StatType.ORDER_COUNT
import com.woocommerce.android.wear.complications.FetchStatsForComplications.StatType.ORDER_TOTALS
import com.woocommerce.android.wear.complications.FetchStatsForComplications.StatType.VISITORS_COUNT
import com.woocommerce.android.wear.ui.login.LoginRepository
import com.woocommerce.android.wear.ui.stats.datasource.StatsRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.firstOrNull
import org.wordpress.android.fluxc.model.SiteModel
import javax.inject.Inject

class FetchStatsForComplications @Inject constructor(
private val coroutineScope: CoroutineScope,
private val statsRepository: StatsRepository,
private val loginRepository: LoginRepository,
private val decimalFormat: CompactDecimalFormat
) {
suspend operator fun invoke(statType: StatType): String {
val site = coroutineScope.async {
loginRepository.selectedSiteFlow
.filterNotNull()
.firstOrNull()
}.await() ?: return DEFAULT_EMPTY_VALUE

return when (statType) {
ORDER_TOTALS -> fetchTodayOrderTotals(site)
ORDER_COUNT -> fetchTodayOrderCount(site)
VISITORS_COUNT -> fetchTodayVisitors(site)
}
}

private suspend fun fetchTodayOrderTotals(
site: SiteModel
) = site.let { statsRepository.fetchRevenueStats(it) }
.getOrNull()
?.parseTotal()
?.totalSales
?.let { decimalFormat.format(it) }
?: DEFAULT_EMPTY_VALUE

private suspend fun fetchTodayOrderCount(
site: SiteModel
) = site.let { statsRepository.fetchRevenueStats(it) }
.getOrNull()
?.parseTotal()
?.ordersCount?.toString()
?: DEFAULT_EMPTY_VALUE

private suspend fun fetchTodayVisitors(
site: SiteModel
) = site.let { statsRepository.fetchVisitorStats(it) }
.getOrNull()
?.toString()
?: DEFAULT_EMPTY_VALUE

enum class StatType {
ORDER_TOTALS,
ORDER_COUNT,
VISITORS_COUNT
}

companion object {
const val DEFAULT_EMPTY_VALUE = "N/A"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.woocommerce.android.wear.complications.ordercount

import androidx.wear.watchface.complications.data.ComplicationData
import androidx.wear.watchface.complications.data.ComplicationType
import androidx.wear.watchface.complications.datasource.ComplicationRequest
import androidx.wear.watchface.complications.datasource.SuspendingComplicationDataSourceService
import com.woocommerce.android.R
import com.woocommerce.android.wear.complications.FetchStatsForComplications
import com.woocommerce.android.wear.complications.FetchStatsForComplications.StatType.ORDER_COUNT
import com.woocommerce.android.wear.complications.createTextComplicationData
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class OrderCountComplicationService : SuspendingComplicationDataSourceService() {

@Inject lateinit var fetchStatsForComplications: FetchStatsForComplications

override fun getPreviewData(type: ComplicationType): ComplicationData? {
return when (type) {
ComplicationType.SHORT_TEXT -> createTextComplicationData(
context = applicationContext,
content = getString(R.string.order_count_complication_preview_value),
description = getString(R.string.order_count_complication_preview_description)
)

else -> null
}
}

override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? {
return when (request.complicationType) {
ComplicationType.SHORT_TEXT -> createTextComplicationData(
context = applicationContext,
content = fetchStatsForComplications(ORDER_COUNT),
description = getString(R.string.order_count_complication_preview_description)
)

else -> null
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import androidx.wear.watchface.complications.data.ComplicationType
import androidx.wear.watchface.complications.datasource.ComplicationRequest
import androidx.wear.watchface.complications.datasource.SuspendingComplicationDataSourceService
import com.woocommerce.android.R
import com.woocommerce.android.wear.complications.FetchStatsForComplications
import com.woocommerce.android.wear.complications.FetchStatsForComplications.StatType.ORDER_TOTALS
import com.woocommerce.android.wear.complications.createTextComplicationData
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class OrderTotalsComplicationService : SuspendingComplicationDataSourceService() {

@Inject lateinit var fetchTodayOrderTotals: FetchTodayOrderTotals
@Inject lateinit var fetchStatsForComplications: FetchStatsForComplications

override fun getPreviewData(type: ComplicationType): ComplicationData? {
return when (type) {
Expand All @@ -30,7 +32,7 @@ class OrderTotalsComplicationService : SuspendingComplicationDataSourceService()
return when (request.complicationType) {
ComplicationType.SHORT_TEXT -> createTextComplicationData(
context = applicationContext,
content = fetchTodayOrderTotals(),
content = fetchStatsForComplications(ORDER_TOTALS),
description = getString(R.string.order_totals_complication_preview_description)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.woocommerce.android.wear.complications.visitorscount

import androidx.wear.watchface.complications.data.ComplicationData
import androidx.wear.watchface.complications.data.ComplicationType
import androidx.wear.watchface.complications.datasource.ComplicationRequest
import androidx.wear.watchface.complications.datasource.SuspendingComplicationDataSourceService
import com.woocommerce.android.R
import com.woocommerce.android.wear.complications.FetchStatsForComplications
import com.woocommerce.android.wear.complications.FetchStatsForComplications.StatType.VISITORS_COUNT
import com.woocommerce.android.wear.complications.createTextComplicationData
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class VisitorsCountComplicationService : SuspendingComplicationDataSourceService() {

@Inject lateinit var fetchStatsForComplications: FetchStatsForComplications

override fun getPreviewData(type: ComplicationType): ComplicationData? {
return when (type) {
ComplicationType.SHORT_TEXT -> createTextComplicationData(
context = applicationContext,
content = getString(R.string.visitors_count_complication_preview_value),
description = getString(R.string.visitors_count_complication_preview_description)
)

else -> null
}
}

override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? {
return when (request.complicationType) {
ComplicationType.SHORT_TEXT -> createTextComplicationData(
context = applicationContext,
content = fetchStatsForComplications(VISITORS_COUNT),
description = getString(R.string.visitors_count_complication_preview_description)
)

else -> null
}
}
}
6 changes: 6 additions & 0 deletions WooCommerce-Wear/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,10 @@
<string name="order_totals_complication_label">Order Totals</string>
<string name="order_totals_complication_preview_value">$42k</string>
<string name="order_totals_complication_preview_description">Display your store today\'s order totals</string>
<string name="order_count_complication_label">Order Count</string>
<string name="order_count_complication_preview_value">7</string>
<string name="order_count_complication_preview_description">Display your store today\'s order count</string>
<string name="visitors_count_complication_label">Visitors Count</string>
<string name="visitors_count_complication_preview_value">42</string>
<string name="visitors_count_complication_preview_description">Display your store today\'s visitors count</string>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.woocommerce.android.wear.complications

import android.icu.text.CompactDecimalFormat
import com.woocommerce.android.BaseUnitTest
import com.woocommerce.android.wear.ui.login.LoginRepository
import com.woocommerce.android.wear.ui.stats.datasource.StatsRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.model.WCRevenueStatsModel
import kotlin.test.Test

@OptIn(ExperimentalCoroutinesApi::class)
class FetchStatsForComplicationsTest : BaseUnitTest() {
private lateinit var sut: FetchStatsForComplications
private val coroutineScope: CoroutineScope = TestScope(coroutinesTestRule.testDispatcher)
private val statsRepository: StatsRepository = mock()
private val loginRepository: LoginRepository = mock()
private val decimalFormat: CompactDecimalFormat = mock()

@Before
fun setUp() {
sut = FetchStatsForComplications(coroutineScope, statsRepository, loginRepository, decimalFormat)
}

@Test
fun `returns formatted order totals when site is selected`() = testBlocking {
val site = SiteModel()
val total = mock<WCRevenueStatsModel.Total> {
on { totalSales } doReturn 100.0
}
val revenueStats = mock<WCRevenueStatsModel> {
on { parseTotal() } doReturn total
}
whenever(loginRepository.selectedSiteFlow).thenReturn(MutableStateFlow(site))
whenever(statsRepository.fetchRevenueStats(site)).thenReturn(Result.success(revenueStats))
whenever(decimalFormat.format(100.0)).thenReturn("100")

val result = sut(FetchStatsForComplications.StatType.ORDER_TOTALS)

assertThat(result).isEqualTo("100")
}

@Test
fun `returns default value when order totals fetch fails`() = testBlocking {
val site = SiteModel()
whenever(loginRepository.selectedSiteFlow).thenReturn(MutableStateFlow(site))
whenever(statsRepository.fetchRevenueStats(site)).thenReturn(Result.failure(Exception()))

val result = sut(FetchStatsForComplications.StatType.ORDER_TOTALS)

assertThat(result).isEqualTo(FetchStatsForComplications.DEFAULT_EMPTY_VALUE)
}

@Test
fun `returns order count when site is selected`() = testBlocking {
val site = SiteModel()
val total = mock<WCRevenueStatsModel.Total> {
on { ordersCount } doReturn 10
}
val revenueStats = mock<WCRevenueStatsModel> {
on { parseTotal() } doReturn total
}
whenever(loginRepository.selectedSiteFlow).thenReturn(MutableStateFlow(site))
whenever(statsRepository.fetchRevenueStats(site)).thenReturn(Result.success(revenueStats))

val result = sut(FetchStatsForComplications.StatType.ORDER_COUNT)

assertThat(result).isEqualTo("10")
}

@Test
fun `returns default value when order count fetch fails`() = testBlocking {
val site = SiteModel()
whenever(loginRepository.selectedSiteFlow).thenReturn(MutableStateFlow(site))
whenever(statsRepository.fetchRevenueStats(site)).thenReturn(Result.failure(Exception()))

val result = sut(FetchStatsForComplications.StatType.ORDER_COUNT)

assertThat(result).isEqualTo(FetchStatsForComplications.DEFAULT_EMPTY_VALUE)
}

@Test
fun `returns visitors count when site is selected`() = testBlocking {
val site = SiteModel()
whenever(loginRepository.selectedSiteFlow).thenReturn(MutableStateFlow(site))
whenever(statsRepository.fetchVisitorStats(site)).thenReturn(Result.success(100))

val result = sut(FetchStatsForComplications.StatType.VISITORS_COUNT)

assertThat(result).isEqualTo("100")
}

@Test
fun `returns default value when visitors count fetch fails`() = testBlocking {
val site = SiteModel()
whenever(loginRepository.selectedSiteFlow).thenReturn(MutableStateFlow(site))
whenever(statsRepository.fetchVisitorStats(site)).thenReturn(Result.failure(Exception()))

val result = sut(FetchStatsForComplications.StatType.VISITORS_COUNT)

assertThat(result).isEqualTo(FetchStatsForComplications.DEFAULT_EMPTY_VALUE)
}
}

0 comments on commit f65273e

Please sign in to comment.