Skip to content

Commit e65b439

Browse files
authored
Merge pull request #17308 from wordpress-mobile/issue/17279-blogging-reminders-resolver
[Blogging Reminders Sync] Implement blogging reminders resolver
2 parents bf8788d + 8f8f76e commit e65b439

File tree

21 files changed

+648
-52
lines changed

21 files changed

+648
-52
lines changed

WordPress/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ android {
122122
buildConfigField "boolean", "JETPACK_POWERED_BOTTOM_SHEET", "false"
123123
buildConfigField "boolean", "JETPACK_SHARED_LOGIN", "false"
124124
buildConfigField "boolean", "JETPACK_LOCAL_USER_FLAGS", "false"
125+
buildConfigField "boolean", "JETPACK_BLOGGING_REMINDERS_SYNC", "false"
125126
buildConfigField "boolean", "JETPACK_PROVIDER_SYNC", "false"
126127

127128
// Override these constants in jetpack product flavor to enable/ disable features
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.wordpress.android.bloggingreminders
2+
3+
import org.wordpress.android.analytics.AnalyticsTracker.Stat
4+
import org.wordpress.android.bloggingreminders.BloggingRemindersSyncAnalyticsTracker.ErrorType.Companion.ERROR_TYPE
5+
import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper
6+
import javax.inject.Inject
7+
8+
class BloggingRemindersSyncAnalyticsTracker @Inject constructor(
9+
private val analyticsTracker: AnalyticsTrackerWrapper
10+
) {
11+
fun trackStart() = analyticsTracker.track(Stat.BLOGGING_REMINDERS_SYNC_START)
12+
13+
fun trackSuccess(remindersSyncedCount: Int) = analyticsTracker.track(
14+
Stat.BLOGGING_REMINDERS_SYNC_SUCCESS, mapOf(REMINDERS_SYNCED_COUNT to remindersSyncedCount)
15+
)
16+
17+
fun trackFailed(errorType: ErrorType) =
18+
analyticsTracker.track(Stat.BLOGGING_REMINDERS_SYNC_FAILED, mapOf(ERROR_TYPE to errorType.value))
19+
20+
sealed class ErrorType(val value: String) {
21+
object QueryBloggingRemindersError : ErrorType("query_blogging_reminders_error")
22+
23+
object UpdateBloggingRemindersError : ErrorType("update_blogging_reminders_error")
24+
25+
companion object {
26+
const val ERROR_TYPE = "error_type"
27+
}
28+
}
29+
}
30+
31+
const val REMINDERS_SYNCED_COUNT = "reminders_synced_count"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.wordpress.android.bloggingreminders
2+
3+
import org.wordpress.android.util.BuildConfigWrapper
4+
import org.wordpress.android.util.config.JetpackBloggingRemindersSyncFeatureConfig
5+
import javax.inject.Inject
6+
7+
class JetpackBloggingRemindersSyncFlag @Inject constructor(
8+
private val jetpackBloggingRemindersSyncFeatureConfig: JetpackBloggingRemindersSyncFeatureConfig,
9+
private val buildConfigWrapper: BuildConfigWrapper
10+
) {
11+
fun isEnabled() = jetpackBloggingRemindersSyncFeatureConfig.isEnabled() && buildConfigWrapper.isJetpackApp
12+
}

WordPress/src/main/java/org/wordpress/android/bloggingreminders/provider/BloggingRemindersProvider.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,23 @@ import kotlinx.coroutines.flow.first
66
import kotlinx.coroutines.runBlocking
77
import org.wordpress.android.WordPress
88
import org.wordpress.android.fluxc.model.BloggingRemindersModel
9-
import org.wordpress.android.fluxc.model.SiteModel
109
import org.wordpress.android.fluxc.store.BloggingRemindersStore
1110
import org.wordpress.android.fluxc.store.SiteStore
1211
import org.wordpress.android.provider.query.QueryContentProvider
1312
import org.wordpress.android.provider.query.QueryResult
13+
import org.wordpress.android.util.config.JetpackProviderSyncFeatureConfig
1414
import org.wordpress.android.util.publicdata.ClientVerification
1515
import org.wordpress.android.util.signature.SignatureNotFoundException
1616
import javax.inject.Inject
1717

18-
typealias SiteModelBloggingReminderMap = Map<SiteModel, BloggingRemindersModel>
18+
typealias SiteIDBloggingReminderMap = Map<Long?, BloggingRemindersModel?>
1919

2020
class BloggingRemindersProvider : QueryContentProvider() {
2121
@Inject lateinit var bloggingRemindersStore: BloggingRemindersStore
2222
@Inject lateinit var siteStore: SiteStore
2323
@Inject lateinit var queryResult: QueryResult
2424
@Inject lateinit var clientVerification: ClientVerification
25+
@Inject lateinit var jetpackProviderSyncFeatureConfig: JetpackProviderSyncFeatureConfig
2526

2627
override fun onCreate(): Boolean {
2728
return true
@@ -36,6 +37,9 @@ class BloggingRemindersProvider : QueryContentProvider() {
3637
sortOrder: String?
3738
): Cursor? {
3839
inject()
40+
if (!jetpackProviderSyncFeatureConfig.isEnabled()) {
41+
return null
42+
}
3943
return context?.let {
4044
try {
4145
if (clientVerification.canTrust(callingPackage)) {
@@ -47,13 +51,9 @@ class BloggingRemindersProvider : QueryContentProvider() {
4751
bloggingRemindersModel.enabledDays.isNotEmpty()
4852
}
4953
val filteredSiteIds = filteredBloggingReminders.map { bloggingReminder ->
50-
bloggingReminder.siteId
51-
}
52-
val filteredSiteModels = allSiteModels.filter { siteModel ->
53-
filteredSiteIds.contains(siteModel.id)
54+
siteStore.getSiteIdForLocalId(bloggingReminder.siteId)
5455
}
55-
val result: SiteModelBloggingReminderMap =
56-
filteredSiteModels.zip(filteredBloggingReminders).toMap()
56+
val result: SiteIDBloggingReminderMap = filteredSiteIds.zip(filteredBloggingReminders).toMap()
5757
queryResult.createCursor(result)
5858
}
5959
} else null
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package org.wordpress.android.bloggingreminders.resolver
2+
3+
import android.database.Cursor
4+
import com.google.gson.reflect.TypeToken
5+
import kotlinx.coroutines.CoroutineScope
6+
import kotlinx.coroutines.flow.first
7+
import kotlinx.coroutines.launch
8+
import org.wordpress.android.bloggingreminders.BloggingRemindersSyncAnalyticsTracker
9+
import org.wordpress.android.bloggingreminders.BloggingRemindersSyncAnalyticsTracker.ErrorType
10+
import org.wordpress.android.bloggingreminders.JetpackBloggingRemindersSyncFlag
11+
import org.wordpress.android.bloggingreminders.provider.BloggingRemindersProvider
12+
import org.wordpress.android.bloggingreminders.provider.SiteIDBloggingReminderMap
13+
import org.wordpress.android.fluxc.store.BloggingRemindersStore
14+
import org.wordpress.android.fluxc.store.SiteStore
15+
import org.wordpress.android.modules.APPLICATION_SCOPE
16+
import org.wordpress.android.provider.query.QueryResult
17+
import org.wordpress.android.resolver.ContentResolverWrapper
18+
import org.wordpress.android.ui.prefs.AppPrefsWrapper
19+
import org.wordpress.android.util.publicdata.WordPressPublicData
20+
import org.wordpress.android.viewmodel.ContextProvider
21+
import javax.inject.Inject
22+
import javax.inject.Named
23+
24+
class BloggingRemindersResolver @Inject constructor(
25+
private val jetpackBloggingRemindersSyncFlag: JetpackBloggingRemindersSyncFlag,
26+
private val contextProvider: ContextProvider,
27+
private val wordPressPublicData: WordPressPublicData,
28+
private val queryResult: QueryResult,
29+
private val contentResolverWrapper: ContentResolverWrapper,
30+
private val appPrefsWrapper: AppPrefsWrapper,
31+
private val bloggingRemindersSyncAnalyticsTracker: BloggingRemindersSyncAnalyticsTracker,
32+
private val siteStore: SiteStore,
33+
private val bloggingRemindersStore: BloggingRemindersStore,
34+
@Named(APPLICATION_SCOPE) private val coroutineScope: CoroutineScope
35+
) {
36+
fun trySyncBloggingReminders(onSuccess: () -> Unit, onFailure: () -> Unit) {
37+
val isFeatureFlagEnabled = jetpackBloggingRemindersSyncFlag.isEnabled()
38+
if (!isFeatureFlagEnabled) {
39+
onFailure()
40+
return
41+
}
42+
val isFirstTry = appPrefsWrapper.getIsFirstTryBloggingRemindersSyncJetpack()
43+
if (!isFirstTry) {
44+
onFailure()
45+
return
46+
}
47+
bloggingRemindersSyncAnalyticsTracker.trackStart()
48+
appPrefsWrapper.saveIsFirstTryBloggingRemindersSyncJetpack(false)
49+
val bloggingRemindersResultCursor = getBloggingRemindersSyncResultCursor()
50+
if (bloggingRemindersResultCursor != null) {
51+
val siteModelBloggingReminderMap = queryResult.getValue<SiteIDBloggingReminderMap>(
52+
bloggingRemindersResultCursor, object : TypeToken<SiteIDBloggingReminderMap?>() {}.type
53+
) ?: emptyMap()
54+
if (siteModelBloggingReminderMap.isNotEmpty()) {
55+
val success = syncBloggingReminders(siteModelBloggingReminderMap)
56+
if (success) onSuccess() else onFailure()
57+
} else {
58+
bloggingRemindersSyncAnalyticsTracker.trackSuccess(0)
59+
onSuccess()
60+
}
61+
} else {
62+
bloggingRemindersSyncAnalyticsTracker.trackFailed(ErrorType.QueryBloggingRemindersError)
63+
onFailure()
64+
}
65+
}
66+
67+
private fun getBloggingRemindersSyncResultCursor(): Cursor? {
68+
val wordpressBloggingRemindersSyncUriValue =
69+
"content://${wordPressPublicData.currentPackageId()}.${BloggingRemindersProvider::class.simpleName}"
70+
return contentResolverWrapper.queryUri(
71+
contextProvider.getContext().contentResolver,
72+
wordpressBloggingRemindersSyncUriValue
73+
)
74+
}
75+
76+
@Suppress("TooGenericExceptionCaught", "SwallowedException")
77+
private fun syncBloggingReminders(siteIdBloggingReminderMap: SiteIDBloggingReminderMap): Boolean {
78+
try {
79+
coroutineScope.launch {
80+
var remindersSyncedCount = 0
81+
for ((siteId, bloggingReminder) in siteIdBloggingReminderMap) {
82+
if (siteId == null || bloggingReminder == null) {
83+
continue
84+
}
85+
val siteLocalId = siteStore.getLocalIdForRemoteSiteId(siteId)
86+
val isBloggingReminderAlreadySet = bloggingRemindersStore.bloggingRemindersModel(siteLocalId)
87+
.first().enabledDays.isNotEmpty()
88+
if (siteLocalId != 0 && !isBloggingReminderAlreadySet) {
89+
remindersSyncedCount = remindersSyncedCount.inc()
90+
bloggingRemindersStore.updateBloggingReminders(bloggingReminder.copy(siteId = siteLocalId))
91+
}
92+
}
93+
bloggingRemindersSyncAnalyticsTracker.trackSuccess(remindersSyncedCount)
94+
}
95+
return true
96+
} catch (exception: Exception) {
97+
bloggingRemindersSyncAnalyticsTracker.trackFailed(ErrorType.UpdateBloggingRemindersError)
98+
return false
99+
}
100+
}
101+
}

WordPress/src/main/java/org/wordpress/android/provider/query/QueryResult.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,23 @@ package org.wordpress.android.provider.query
33
import android.database.Cursor
44
import android.database.MatrixCursor
55
import com.google.gson.Gson
6+
import java.lang.reflect.Type
67
import javax.inject.Inject
78

89
class QueryResult @Inject constructor() {
910
@Suppress("TooGenericExceptionCaught", "SwallowedException")
10-
inline fun <reified T> getValue(cursor: Cursor): T? {
11+
inline fun <reified T : Any> getValue(cursor: Cursor, type: Type? = null): T? {
1112
cursor.moveToFirst()
1213
val value: String = cursor.getString(0)
1314
return try {
14-
Gson().fromJson(value, T::class.java)
15+
if (type != null) Gson().fromJson(value, type) else Gson().fromJson(value, T::class.java)
1516
} catch (exception: Exception) {
1617
null
1718
}
1819
}
1920

20-
inline fun <reified T> createCursor(value: T): Cursor {
21-
val valueJson = Gson().toJson(value)
21+
inline fun <reified T : Any> createCursor(value: T): Cursor {
22+
val valueJson = Gson().toJson(value, value::class.java)
2223
val matrixCursor = MatrixCursor(arrayOf(KEY_QUERY_RESULT))
2324
matrixCursor.newRow().add(KEY_QUERY_RESULT, valueJson)
2425
return matrixCursor

WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.wordpress.android.WordPress;
3737
import org.wordpress.android.analytics.AnalyticsTracker;
3838
import org.wordpress.android.analytics.AnalyticsTracker.Stat;
39+
import org.wordpress.android.bloggingreminders.resolver.BloggingRemindersResolver;
3940
import org.wordpress.android.fluxc.Dispatcher;
4041
import org.wordpress.android.fluxc.generated.AccountActionBuilder;
4142
import org.wordpress.android.fluxc.generated.SiteActionBuilder;
@@ -165,6 +166,7 @@
165166
import static org.wordpress.android.ui.JetpackConnectionSource.NOTIFICATIONS;
166167

167168
import dagger.hilt.android.AndroidEntryPoint;
169+
import kotlin.Unit;
168170

169171
/**
170172
* Main activity which hosts sites, reader, me and notifications pages
@@ -256,6 +258,7 @@ public class WPMainActivity extends LocaleAwareActivity implements
256258
@Inject MySiteDashboardTodaysStatsCardFeatureConfig mTodaysStatsCardFeatureConfig;
257259
@Inject QuickStartTracker mQuickStartTracker;
258260
@Inject SharedLoginResolver mSharedLoginResolver;
261+
@Inject BloggingRemindersResolver mBloggingRemindersResolver;
259262

260263
@Inject BuildConfigWrapper mBuildConfigWrapper;
261264

@@ -1609,6 +1612,9 @@ public void onSiteChanged(OnSiteChanged event) {
16091612
mSelectedSiteRepository.updateSite(site);
16101613
}
16111614
}
1615+
mBloggingRemindersResolver.trySyncBloggingReminders(
1616+
() -> Unit.INSTANCE, () -> Unit.INSTANCE
1617+
);
16121618
}
16131619

16141620
@SuppressWarnings("unused")

WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,10 @@ public enum UndeletablePrefKey implements PrefKey {
280280
IS_FIRST_TRY_LOGIN_JETPACK,
281281

282282
// Indicates if this is the first time we try to get the user flags in Jetpack automatically
283-
IS_FIRST_TRY_USER_FLAGS_JETPACK
283+
IS_FIRST_TRY_USER_FLAGS_JETPACK,
284+
285+
// Indicates if this is the first time we try sync the blogging reminders in Jetpack automatically
286+
IS_FIRST_TRY_BLOGGING_REMINDERS_SYNC_JETPACK
284287
}
285288

286289
static SharedPreferences prefs() {
@@ -1448,4 +1451,12 @@ public static Boolean getIsFirstTryUserFlagsJetpack() {
14481451
public static void saveIsFirstTryUserFlagsJetpack(final boolean isFirstTry) {
14491452
setBoolean(UndeletablePrefKey.IS_FIRST_TRY_USER_FLAGS_JETPACK, isFirstTry);
14501453
}
1454+
1455+
public static Boolean getIsFirstTryBloggingRemindersSyncJetpack() {
1456+
return getBoolean(UndeletablePrefKey.IS_FIRST_TRY_BLOGGING_REMINDERS_SYNC_JETPACK, true);
1457+
}
1458+
1459+
public static void saveIsFirstTryBloggingRemindersSyncJetpack(final boolean isFirstTry) {
1460+
setBoolean(UndeletablePrefKey.IS_FIRST_TRY_BLOGGING_REMINDERS_SYNC_JETPACK, isFirstTry);
1461+
}
14511462
}

WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefsWrapper.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,11 @@ class AppPrefsWrapper @Inject constructor() {
241241

242242
fun saveIsFirstTryUserFlagsJetpack(isFirstTry: Boolean) = AppPrefs.saveIsFirstTryUserFlagsJetpack(isFirstTry)
243243

244+
fun getIsFirstTryBloggingRemindersSyncJetpack(): Boolean = AppPrefs.getIsFirstTryBloggingRemindersSyncJetpack()
245+
246+
fun saveIsFirstTryBloggingRemindersSyncJetpack(isFirstTry: Boolean) =
247+
AppPrefs.saveIsFirstTryBloggingRemindersSyncJetpack(isFirstTry)
248+
244249
fun getAllPrefs(): Map<String, Any?> = AppPrefs.getAllPrefs()
245250

246251
fun setString(prefKey: PrefKey, value: String) {

WordPress/src/main/java/org/wordpress/android/userflags/provider/UserFlagsProvider.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import org.wordpress.android.provider.query.QueryResult
99
import org.wordpress.android.ui.prefs.AppPrefs.DeletablePrefKey
1010
import org.wordpress.android.ui.prefs.AppPrefs.UndeletablePrefKey
1111
import org.wordpress.android.ui.prefs.AppPrefsWrapper
12+
import org.wordpress.android.util.config.JetpackProviderSyncFeatureConfig
1213
import org.wordpress.android.util.publicdata.ClientVerification
1314
import org.wordpress.android.util.signature.SignatureNotFoundException
1415
import javax.inject.Inject
@@ -18,6 +19,7 @@ class UserFlagsProvider : QueryContentProvider() {
1819
@Inject lateinit var siteStore: SiteStore
1920
@Inject lateinit var queryResult: QueryResult
2021
@Inject lateinit var clientVerification: ClientVerification
22+
@Inject lateinit var jetpackProviderSyncFeatureConfig: JetpackProviderSyncFeatureConfig
2123

2224
private val userFlagsKeysSet: Set<String> = setOf(
2325
DeletablePrefKey.MAIN_PAGE_INDEX.name,
@@ -28,6 +30,8 @@ class UserFlagsProvider : QueryContentProvider() {
2830
DeletablePrefKey.VIDEO_OPTIMIZE_WIDTH.name,
2931
DeletablePrefKey.VIDEO_OPTIMIZE_QUALITY.name,
3032
DeletablePrefKey.STRIP_IMAGE_LOCATION.name,
33+
DeletablePrefKey.SUPPORT_EMAIL.name,
34+
DeletablePrefKey.SUPPORT_NAME.name,
3135
DeletablePrefKey.GUTENBERG_DEFAULT_FOR_NEW_POSTS.name,
3236
DeletablePrefKey.USER_IN_GUTENBERG_ROLLOUT_GROUP.name,
3337
DeletablePrefKey.SHOULD_AUTO_ENABLE_GUTENBERG_FOR_THE_NEW_POSTS.name,
@@ -66,6 +70,9 @@ class UserFlagsProvider : QueryContentProvider() {
6670
sortOrder: String?
6771
): Cursor? {
6872
inject()
73+
if (!jetpackProviderSyncFeatureConfig.isEnabled()) {
74+
return null
75+
}
6976
return context?.let {
7077
try {
7178
if (clientVerification.canTrust(callingPackage)) {

0 commit comments

Comments
 (0)