diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/themes/ThemeRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/themes/ThemeRepository.kt index fcb6b4ad6670..cb3824867a93 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/themes/ThemeRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/themes/ThemeRepository.kt @@ -91,7 +91,17 @@ class ThemeRepository @Inject constructor( val activationResult: OnThemeActivated = dispatcher.dispatchAndAwait( ThemeActionBuilder.newActivateThemeAction( - SiteThemePayload(selectedSite.get(), ThemeModel().apply { this.themeId = themeId }) + SiteThemePayload( + selectedSite.get(), + ThemeModel( + siteId = selectedSite.get().localId(), + themeId = themeId, + name = themeId, + demoUrl = null, + active = false, + isWpComTheme = false + ) + ) ) ) @@ -113,9 +123,17 @@ class ThemeRepository @Inject constructor( private suspend fun installThemeIfNeeded(themeId: String): Result { val installationResult: OnThemeInstalled = dispatcher.dispatchAndAwait( ThemeActionBuilder.newInstallThemeAction( - // The Default constructor ThemeModel() is deprecated. - // We should add a new method to ThemeStore install a theme by themeId - SiteThemePayload(selectedSite.get(), ThemeModel().apply { this.themeId = themeId }) + SiteThemePayload( + selectedSite.get(), + ThemeModel( + siteId = selectedSite.get().localId(), + themeId = themeId, + name = themeId, + demoUrl = null, + active = false, + isWpComTheme = false + ) + ) ) ) diff --git a/libs/fluxc-tests/src/test/java/org/wordpress/android/fluxc/theme/ThemeStoreUnitTest.java b/libs/fluxc-tests/src/test/java/org/wordpress/android/fluxc/theme/ThemeStoreUnitTest.java deleted file mode 100644 index 4139ee4b974f..000000000000 --- a/libs/fluxc-tests/src/test/java/org/wordpress/android/fluxc/theme/ThemeStoreUnitTest.java +++ /dev/null @@ -1,158 +0,0 @@ -package org.wordpress.android.fluxc.theme; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -import android.content.Context; - -import com.yarolegovich.wellsql.WellSql; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.wordpress.android.fluxc.Dispatcher; -import org.wordpress.android.fluxc.TestSiteSqlUtils; -import org.wordpress.android.fluxc.model.SiteModel; -import org.wordpress.android.fluxc.model.ThemeModel; -import org.wordpress.android.fluxc.network.rest.wpcom.theme.ThemeRestClient; -import org.wordpress.android.fluxc.persistence.SiteSqlUtils; -import org.wordpress.android.fluxc.persistence.ThemeSqlUtils; -import org.wordpress.android.fluxc.persistence.WellSqlConfig; -import org.wordpress.android.fluxc.site.SiteUtils; -import org.wordpress.android.fluxc.store.ThemeStore; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(RobolectricTestRunner.class) -public class ThemeStoreUnitTest { - private final ThemeStore mThemeStore = new ThemeStore(new Dispatcher(), Mockito.mock(ThemeRestClient.class)); - - @Before - public void setUp() { - Context appContext = RuntimeEnvironment.getApplication().getApplicationContext(); - WellSqlConfig config = new WellSqlConfig(appContext); - WellSql.init(config); - config.reset(); - } - - @Test - public void testActiveTheme() throws SiteSqlUtils.DuplicateSiteException { - final SiteModel site = SiteUtils.generateWPComSite(); - TestSiteSqlUtils.INSTANCE.getSiteSqlUtils().insertOrUpdateSite(site); - assertThat(ThemeSqlUtils.getActiveThemeForSite(site)).isEmpty(); - - final ThemeModel firstTheme = generateTestTheme(site.getId(), "first-active", "First Active"); - final ThemeModel secondTheme = generateTestTheme(site.getId(), "second-active", "Second Active"); - firstTheme.setActive(true); - secondTheme.setActive(true); - - // set first theme active and verify - mThemeStore.setActiveThemeForSite(site, firstTheme); - List activeThemes = ThemeSqlUtils.getActiveThemeForSite(site); - assertNotNull(activeThemes); - assertEquals(1, activeThemes.size()); - ThemeModel firstActiveTheme = activeThemes.get(0); - assertEquals(firstTheme.getThemeId(), firstActiveTheme.getThemeId()); - assertEquals(firstTheme.getName(), firstActiveTheme.getName()); - - // set second theme active and verify - mThemeStore.setActiveThemeForSite(site, secondTheme); - activeThemes = ThemeSqlUtils.getActiveThemeForSite(site); - assertNotNull(activeThemes); - assertEquals(1, activeThemes.size()); - ThemeModel secondActiveTheme = activeThemes.get(0); - assertEquals(secondTheme.getThemeId(), secondActiveTheme.getThemeId()); - assertEquals(secondTheme.getName(), secondActiveTheme.getName()); - } - - @Test - public void testInsertOrUpdateTheme() throws SiteSqlUtils.DuplicateSiteException { - final SiteModel site = SiteUtils.generateJetpackSiteOverRestOnly(); - TestSiteSqlUtils.INSTANCE.getSiteSqlUtils().insertOrUpdateSite(site); - - final String testThemeId = "fluxc-ftw"; - final String testThemeName = "FluxC FTW"; - final String testUpdatedName = testThemeName + " v2"; - final ThemeModel insertTheme = generateTestTheme(site.getId(), testThemeId, testThemeName); - - // verify theme doesn't already exist - assertNull(mThemeStore.getInstalledThemeByThemeId(site, testThemeId)); - - // insert new theme and verify it exists - ThemeSqlUtils.insertOrUpdateSiteTheme(site, insertTheme); - ThemeModel insertedTheme = mThemeStore.getInstalledThemeByThemeId(site, testThemeId); - assertNotNull(insertedTheme); - assertEquals(testThemeName, insertedTheme.getName()); - - // update the theme and verify the updated attributes - insertedTheme.setName(testUpdatedName); - ThemeSqlUtils.insertOrUpdateSiteTheme(site, insertedTheme); - insertedTheme = mThemeStore.getInstalledThemeByThemeId(site, testThemeId); - assertNotNull(insertedTheme); - assertEquals(testUpdatedName, insertedTheme.getName()); - } - - @Test - public void testInsertOrReplaceWpThemes() { - final List firstTestThemes = generateThemesTestList(20); - final List secondTestThemes = generateThemesTestList(30); - final List thirdTestThemes = generateThemesTestList(10); - - // first add 20 themes and make sure the count is correct - ThemeSqlUtils.insertOrReplaceWpComThemes(firstTestThemes); - assertEquals(20, mThemeStore.getWpComThemes(ids(firstTestThemes)).size()); - - // next add a larger list of themes (with 20 being duplicates) and make sure the count is correct - ThemeSqlUtils.insertOrReplaceWpComThemes(secondTestThemes); - assertEquals(30, mThemeStore.getWpComThemes(ids(secondTestThemes)).size()); - - // lastly add a smaller list of themes (all duplicates) and make sure count is correct - ThemeSqlUtils.insertOrReplaceWpComThemes(thirdTestThemes); - assertEquals(10, mThemeStore.getWpComThemes(ids(thirdTestThemes)).size()); - } - - @Test - public void testRemoveThemesWithNoSite() { - final List testThemes = generateThemesTestList(20); - final List themeIds = ids(testThemes); - - // insert and verify count - assertEquals(0, mThemeStore.getWpComThemes(themeIds).size()); - ThemeSqlUtils.insertOrReplaceWpComThemes(testThemes); - assertEquals(testThemes.size(), mThemeStore.getWpComThemes(themeIds).size()); - - // remove and verify count - ThemeSqlUtils.removeWpComThemes(); - assertEquals(0, mThemeStore.getWpComThemes(themeIds).size()); - } - - private ThemeModel generateTestTheme(int siteId, String themeId, String themeName) { - @SuppressWarnings("deprecation") ThemeModel theme = new ThemeModel(); - theme.setLocalSiteId(siteId); - theme.setThemeId(themeId); - theme.setName(themeName); - return theme; - } - - private List generateThemesTestList(int num) { - List testThemes = new ArrayList<>(); - for (int i = 0; i < num; ++i) { - testThemes.add(generateTestTheme(0, "themeid" + i, "themename" + i)); - } - return testThemes; - } - - private List ids(List themes) { - List themeIds = new ArrayList<>(); - for (ThemeModel theme : themes) { - themeIds.add(theme.getThemeId()); - } - return themeIds; - } -} diff --git a/libs/fluxc/schemas/org.wordpress.android.fluxc.persistence.WPAndroidDatabase/31.json b/libs/fluxc/schemas/org.wordpress.android.fluxc.persistence.WPAndroidDatabase/31.json new file mode 100644 index 000000000000..aaa96d1fc669 --- /dev/null +++ b/libs/fluxc/schemas/org.wordpress.android.fluxc.persistence.WPAndroidDatabase/31.json @@ -0,0 +1,373 @@ +{ + "formatVersion": 1, + "database": { + "version": 31, + "identityHash": "170c715c7279c4691cfd3d797a79fa3b", + "entities": [ + { + "tableName": "FeatureFlagConfigurations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` INTEGER NOT NULL, `created_at` INTEGER NOT NULL, `modified_at` INTEGER NOT NULL, `source` TEXT NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "modifiedAt", + "columnName": "modified_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + } + }, + { + "tableName": "Domains", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`siteLocalId` INTEGER NOT NULL, `domain` TEXT NOT NULL, `primaryDomain` INTEGER NOT NULL, `wpcomDomain` INTEGER NOT NULL, PRIMARY KEY(`domain`))", + "fields": [ + { + "fieldPath": "siteLocalId", + "columnName": "siteLocalId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "domain", + "columnName": "domain", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "primaryDomain", + "columnName": "primaryDomain", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "wpcomDomain", + "columnName": "wpcomDomain", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "domain" + ] + } + }, + { + "tableName": "BlazeCampaigns", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`siteId` INTEGER NOT NULL, `campaignId` TEXT NOT NULL, `title` TEXT NOT NULL, `imageUrl` TEXT, `startTime` TEXT NOT NULL, `durationInDays` INTEGER NOT NULL, `uiStatus` TEXT NOT NULL, `impressions` INTEGER NOT NULL, `clicks` INTEGER NOT NULL, `targetUrn` TEXT, `totalBudget` REAL NOT NULL, `spentBudget` REAL NOT NULL, `isEndlessCampaign` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`siteId`, `campaignId`))", + "fields": [ + { + "fieldPath": "siteId", + "columnName": "siteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "campaignId", + "columnName": "campaignId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "imageUrl", + "columnName": "imageUrl", + "affinity": "TEXT" + }, + { + "fieldPath": "startTime", + "columnName": "startTime", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "durationInDays", + "columnName": "durationInDays", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uiStatus", + "columnName": "uiStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "impressions", + "columnName": "impressions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "clicks", + "columnName": "clicks", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "targetUrn", + "columnName": "targetUrn", + "affinity": "TEXT" + }, + { + "fieldPath": "totalBudget", + "columnName": "totalBudget", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "spentBudget", + "columnName": "spentBudget", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "isEndlessCampaign", + "columnName": "isEndlessCampaign", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "siteId", + "campaignId" + ] + }, + "indices": [ + { + "name": "index_BlazeCampaigns_siteId", + "unique": false, + "columnNames": [ + "siteId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_BlazeCampaigns_siteId` ON `${TABLE_NAME}` (`siteId`)" + } + ] + }, + { + "tableName": "BlazeCampaignObjectives", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `suitableForDescription` TEXT NOT NULL, `locale` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "suitableForDescription", + "columnName": "suitableForDescription", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "locale", + "columnName": "locale", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "BlazeTargetingLanguages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `locale` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "locale", + "columnName": "locale", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "BlazeTargetingDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `locale` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "locale", + "columnName": "locale", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "BlazeTargetingTopics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `description` TEXT NOT NULL, `locale` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "locale", + "columnName": "locale", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "ThemeEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`siteId` INTEGER NOT NULL, `themeId` TEXT NOT NULL, `name` TEXT NOT NULL, `demoUrl` TEXT, `active` INTEGER NOT NULL, `isWpComTheme` INTEGER NOT NULL, PRIMARY KEY(`siteId`, `themeId`, `isWpComTheme`))", + "fields": [ + { + "fieldPath": "siteId", + "columnName": "siteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "themeId", + "columnName": "themeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "demoUrl", + "columnName": "demoUrl", + "affinity": "TEXT" + }, + { + "fieldPath": "active", + "columnName": "active", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isWpComTheme", + "columnName": "isWpComTheme", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "siteId", + "themeId", + "isWpComTheme" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '170c715c7279c4691cfd3d797a79fa3b')" + ] + } +} \ No newline at end of file diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/model/ThemeModel.java b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/model/ThemeModel.java deleted file mode 100644 index 5492a60694f1..000000000000 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/model/ThemeModel.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.wordpress.android.fluxc.model; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.yarolegovich.wellsql.core.Identifiable; -import com.yarolegovich.wellsql.core.annotation.Column; -import com.yarolegovich.wellsql.core.annotation.PrimaryKey; -import com.yarolegovich.wellsql.core.annotation.Table; - -import org.wordpress.android.util.StringUtils; - -import java.io.Serializable; - -@Table -public class ThemeModel implements Identifiable, Serializable { - private static final long serialVersionUID = 5966516212440517166L; - - @PrimaryKey @Column private int mId; - - @Column private int mLocalSiteId; - @NonNull @Column private String mThemeId; - @NonNull @Column private String mName; - @Nullable @Column private String mDemoUrl; - @Column private boolean mActive; - @Column private boolean mIsWpComTheme; - - @Deprecated - @SuppressWarnings("DeprecatedIsStillUsed") - public ThemeModel() { - this.mId = 0; - this.mLocalSiteId = 0; - this.mThemeId = ""; - this.mName = ""; - this.mDemoUrl = null; - this.mActive = false; - this.mIsWpComTheme = false; - } - - /** - * Use when creating a WP.com theme. - */ - public ThemeModel( - @NonNull String themeId, - @NonNull String name, - @Nullable String demoUrl) { - this.mThemeId = themeId; - this.mName = name; - this.mDemoUrl = demoUrl; - } - - /** - * Use when creating a Jetpack theme. - */ - public ThemeModel( - @NonNull String themeId, - @NonNull String name, - boolean active - ) { - this.mThemeId = themeId; - this.mName = name; - this.mActive = active; - } - - @Override - public int getId() { - return mId; - } - - @Override - public void setId(int id) { - mId = id; - } - - @Override - @SuppressWarnings("ConditionCoveredByFurtherCondition") - public boolean equals(@Nullable Object other) { - if (other == null || !(other instanceof ThemeModel)) { - return false; - } - ThemeModel otherTheme = (ThemeModel) other; - return getId() == otherTheme.getId() - && getLocalSiteId() == otherTheme.getLocalSiteId() - && StringUtils.equals(getThemeId(), otherTheme.getThemeId()) - && StringUtils.equals(getName(), otherTheme.getName()) - && StringUtils.equals(getDemoUrl(), otherTheme.getDemoUrl()) - && getActive() == otherTheme.getActive() - && isWpComTheme() == otherTheme.isWpComTheme(); - } - - public int getLocalSiteId() { - return mLocalSiteId; - } - - public void setLocalSiteId(int localSiteId) { - this.mLocalSiteId = localSiteId; - } - - @NonNull - public String getThemeId() { - return mThemeId; - } - - public void setThemeId(@NonNull String themeId) { - mThemeId = themeId; - } - - @NonNull - public String getName() { - return mName; - } - - public void setName(@NonNull String name) { - mName = name; - } - - @Nullable - public String getDemoUrl() { - return mDemoUrl; - } - - public void setDemoUrl(@Nullable String demoUrl) { - mDemoUrl = demoUrl; - } - - public boolean getActive() { - return mActive; - } - - public void setActive(boolean active) { - mActive = active; - } - - public boolean isWpComTheme() { - return mIsWpComTheme; - } - - public void setIsWpComTheme(boolean isWpComTheme) { - mIsWpComTheme = isWpComTheme; - } -} diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/model/ThemeModel.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/model/ThemeModel.kt new file mode 100644 index 000000000000..097e8d031661 --- /dev/null +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/model/ThemeModel.kt @@ -0,0 +1,17 @@ +package org.wordpress.android.fluxc.model + +import androidx.room.Entity +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId + +@Entity( + tableName = "ThemeEntity", + primaryKeys = ["siteId", "themeId", "isWpComTheme"] +) +data class ThemeModel( + val siteId: LocalId, + val themeId: String, + val name: String, + val demoUrl: String?, + val active: Boolean, + val isWpComTheme: Boolean +) diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/theme/ThemeRestClient.java b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/theme/ThemeRestClient.java deleted file mode 100644 index 03b6bd5a440c..000000000000 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/theme/ThemeRestClient.java +++ /dev/null @@ -1,181 +0,0 @@ -package org.wordpress.android.fluxc.network.rest.wpcom.theme; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.volley.RequestQueue; - -import org.wordpress.android.fluxc.Dispatcher; -import org.wordpress.android.fluxc.generated.ThemeActionBuilder; -import org.wordpress.android.fluxc.generated.endpoint.WPCOMREST; -import org.wordpress.android.fluxc.model.SiteModel; -import org.wordpress.android.fluxc.model.ThemeModel; -import org.wordpress.android.fluxc.network.UserAgent; -import org.wordpress.android.fluxc.network.rest.wpcom.BaseWPComRestClient; -import org.wordpress.android.fluxc.network.rest.wpcom.WPComGsonRequest; -import org.wordpress.android.fluxc.network.rest.wpcom.auth.AccessToken; -import org.wordpress.android.fluxc.network.rest.wpcom.theme.WPComThemeResponse.WPComThemeListResponse; -import org.wordpress.android.fluxc.store.ThemeStore.FetchedCurrentThemePayload; -import org.wordpress.android.fluxc.store.ThemeStore.FetchedWpComThemesPayload; -import org.wordpress.android.fluxc.store.ThemeStore.SiteThemePayload; -import org.wordpress.android.fluxc.store.ThemeStore.ThemesError; -import org.wordpress.android.util.AppLog; -import org.wordpress.android.util.StringUtils; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - -@Singleton -public class ThemeRestClient extends BaseWPComRestClient { - @Inject public ThemeRestClient( - Context appContext, - Dispatcher dispatcher, - @Named("regular") RequestQueue requestQueue, - AccessToken accessToken, - UserAgent userAgent) { - super(appContext, dispatcher, requestQueue, accessToken, userAgent); - } - - /** - * [Undocumented!] Endpoint: v1.1/sites/$siteId/themes/$themeId/install - */ - public void installTheme(@NonNull final SiteModel site, @NonNull final ThemeModel theme) { - String themeId = theme.getThemeId(); - if (!site.isWPComAtomic()) { - themeId = getThemeIdWithWpComSuffix(theme); - } - String url = WPCOMREST.sites.site(site.getSiteId()).themes.theme(themeId).install.getUrlV1_1(); - add(WPComGsonRequest.buildPostRequest(url, null, JetpackThemeResponse.class, - (response, headers) -> { - AppLog.d(AppLog.T.API, "Received response to Jetpack theme installation request."); - ThemeModel responseTheme = createThemeFromJetpackResponse(response); - SiteThemePayload payload = new SiteThemePayload(site, responseTheme); - mDispatcher.dispatch(ThemeActionBuilder.newInstalledThemeAction(payload)); - }, error -> { - AppLog.d(AppLog.T.API, "Received error response to Jetpack theme installation request."); - SiteThemePayload payload = new SiteThemePayload(site, theme); - payload.error = new ThemesError(error.apiError, error.message); - mDispatcher.dispatch(ThemeActionBuilder.newInstalledThemeAction(payload)); - })); - } - - /** - * Endpoint: v1.1/sites/$siteId/themes/mine - * - * @see Documentation - */ - public void activateTheme(@NonNull final SiteModel site, @NonNull final ThemeModel theme) { - String url = WPCOMREST.sites.site(site.getSiteId()).themes.mine.getUrlV1_1(); - Map params = new HashMap<>(); - params.put("theme", theme.getThemeId()); - - add(WPComGsonRequest.buildPostRequest(url, params, WPComThemeResponse.class, - (response, headers) -> { - AppLog.d(AppLog.T.API, "Received response to theme activation request."); - SiteThemePayload payload = new SiteThemePayload(site, theme); - payload.theme.setActive(StringUtils.equals(theme.getThemeId(), response.id)); - mDispatcher.dispatch(ThemeActionBuilder.newActivatedThemeAction(payload)); - }, error -> { - AppLog.d(AppLog.T.API, "Received error response to theme activation request."); - SiteThemePayload payload = new SiteThemePayload(site, theme); - payload.error = new ThemesError(error.apiError, error.message); - mDispatcher.dispatch(ThemeActionBuilder.newActivatedThemeAction(payload)); - })); - } - - /** - * [Undocumented!] Endpoint: v1.2/themes - * - * @see Previous version - */ - public void fetchWpComThemes(@Nullable String filter, int resultsLimit) { - String url = WPCOMREST.themes.getUrlV1_2(); - Map params = new HashMap<>(); - params.put("number", String.valueOf(resultsLimit)); - if (filter != null) { - params.put("filter", filter); - } - add(WPComGsonRequest.buildGetRequest(url, params, WPComThemeListResponse.class, - (response, headers) -> { - AppLog.d(AppLog.T.API, "Received response to WP.com themes fetch request."); - List themes = createThemeListFromArrayResponse(response); - FetchedWpComThemesPayload payload = new FetchedWpComThemesPayload(themes); - mDispatcher.dispatch(ThemeActionBuilder.newFetchedWpComThemesAction(payload)); - }, error -> { - AppLog.e(AppLog.T.API, "Received error response to WP.com themes fetch request."); - ThemesError themeError = new ThemesError(error.apiError, error.message); - FetchedWpComThemesPayload payload = new FetchedWpComThemesPayload(themeError); - mDispatcher.dispatch(ThemeActionBuilder.newFetchedWpComThemesAction(payload)); - })); - } - - /** - * Endpoint: v1.1/sites/$siteId/themes/mine; same endpoint for both Jetpack and WP.com sites! - * - * @see Documentation - */ - public void fetchCurrentTheme(@NonNull final SiteModel site) { - String url = WPCOMREST.sites.site(site.getSiteId()).themes.mine.getUrlV1_1(); - add(WPComGsonRequest.buildGetRequest(url, null, WPComThemeResponse.class, - (response, headers) -> { - AppLog.d(AppLog.T.API, "Received response to current theme fetch request."); - ThemeModel responseTheme = createThemeFromWPComResponse(response); - FetchedCurrentThemePayload payload = new FetchedCurrentThemePayload(site, responseTheme); - mDispatcher.dispatch(ThemeActionBuilder.newFetchedCurrentThemeAction(payload)); - }, error -> { - AppLog.e(AppLog.T.API, "Received error response to current theme fetch request."); - ThemesError themeError = new ThemesError(error.apiError, error.message); - FetchedCurrentThemePayload payload = new FetchedCurrentThemePayload(site, themeError); - mDispatcher.dispatch(ThemeActionBuilder.newFetchedCurrentThemeAction(payload)); - })); - } - - @NonNull - private static ThemeModel createThemeFromWPComResponse(@NonNull WPComThemeResponse response) { - return new ThemeModel( - response.id, - response.name, - response.demo_uri - ); - } - - @NonNull - private static ThemeModel createThemeFromJetpackResponse(@NonNull JetpackThemeResponse response) { - return new ThemeModel( - response.id, - response.name, - response.active - ); - } - - @NonNull - private static List createThemeListFromArrayResponse(@NonNull WPComThemeListResponse response) { - final List themeList = new ArrayList<>(); - for (WPComThemeResponse item : response.themes) { - themeList.add(createThemeFromWPComResponse(item)); - } - return themeList; - } - - /** - * Must provide theme slug with -wpcom suffix to install a WP.com theme on a Jetpack site. - * - * @see Documentation - */ - @NonNull - private String getThemeIdWithWpComSuffix(@NonNull ThemeModel theme) { - if (theme.getThemeId().endsWith("-wpcom")) { - return theme.getThemeId(); - } - - return theme.getThemeId() + "-wpcom"; - } -} diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/theme/ThemeRestClient.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/theme/ThemeRestClient.kt new file mode 100644 index 000000000000..ae07c67073e8 --- /dev/null +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpcom/theme/ThemeRestClient.kt @@ -0,0 +1,186 @@ +package org.wordpress.android.fluxc.network.rest.wpcom.theme + +import android.content.Context +import com.android.volley.RequestQueue +import org.wordpress.android.fluxc.Dispatcher +import org.wordpress.android.fluxc.generated.ThemeActionBuilder +import org.wordpress.android.fluxc.generated.endpoint.WPCOMREST +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.model.ThemeModel +import org.wordpress.android.fluxc.network.UserAgent +import org.wordpress.android.fluxc.network.rest.wpcom.BaseWPComRestClient +import org.wordpress.android.fluxc.network.rest.wpcom.WPComGsonRequest +import org.wordpress.android.fluxc.network.rest.wpcom.auth.AccessToken +import org.wordpress.android.fluxc.network.rest.wpcom.theme.WPComThemeResponse.WPComThemeListResponse +import org.wordpress.android.fluxc.store.ThemeStore.FetchedCurrentThemePayload +import org.wordpress.android.fluxc.store.ThemeStore.FetchedWpComThemesPayload +import org.wordpress.android.fluxc.store.ThemeStore.SiteThemePayload +import org.wordpress.android.fluxc.store.ThemeStore.ThemesError +import org.wordpress.android.util.AppLog +import org.wordpress.android.util.AppLog.T +import org.wordpress.android.util.StringUtils +import javax.inject.Inject +import javax.inject.Named +import javax.inject.Singleton + +@Singleton +class ThemeRestClient @Inject constructor( + appContext: Context, + dispatcher: Dispatcher, + @Named("regular") requestQueue: RequestQueue, + accessToken: AccessToken, + userAgent: UserAgent +) : BaseWPComRestClient(appContext, dispatcher, requestQueue, accessToken, userAgent) { + + /** + * [Undocumented!] Endpoint: v1.1/sites/$siteId/themes/$themeId/install + */ + fun installTheme(site: SiteModel, theme: ThemeModel) { + val themeId = if (!site.isWPComAtomic) { + getThemeIdWithWpComSuffix(theme) + } else { + theme.themeId + } + val url = WPCOMREST.sites.site(site.siteId).themes.theme(themeId).install.getUrlV1_1() + add( + WPComGsonRequest.buildPostRequest( + url, null, JetpackThemeResponse::class.java, + { response, _ -> + AppLog.d(T.API, "Received response to Jetpack theme installation request.") + val responseTheme = createThemeFromJetpackResponse(response) + val payload = SiteThemePayload(site, responseTheme) + mDispatcher.dispatch(ThemeActionBuilder.newInstalledThemeAction(payload)) + }, + { error -> + AppLog.d(T.API, "Received error response to Jetpack theme installation request.") + val payload = SiteThemePayload(site, theme) + payload.error = ThemesError(error.apiError, error.message) + mDispatcher.dispatch(ThemeActionBuilder.newInstalledThemeAction(payload)) + } + ) + ) + } + + /** + * Endpoint: v1.1/sites/$siteId/themes/mine + * + * @see Documentation + */ + fun activateTheme(site: SiteModel, theme: ThemeModel) { + val url = WPCOMREST.sites.site(site.siteId).themes.mine.getUrlV1_1() + val params = mapOf("theme" to theme.themeId) + + add( + WPComGsonRequest.buildPostRequest( + url, params, WPComThemeResponse::class.java, + { response, _ -> + AppLog.d(T.API, "Received response to theme activation request.") + val activeTheme = theme.copy(active = StringUtils.equals(theme.themeId, response.id)) + val payload = SiteThemePayload(site, activeTheme) + mDispatcher.dispatch(ThemeActionBuilder.newActivatedThemeAction(payload)) + }, + { error -> + AppLog.d(T.API, "Received error response to theme activation request.") + val payload = SiteThemePayload(site, theme) + payload.error = ThemesError(error.apiError, error.message) + mDispatcher.dispatch(ThemeActionBuilder.newActivatedThemeAction(payload)) + } + ) + ) + } + + /** + * [Undocumented!] Endpoint: v1.2/themes + * + * @see Previous version + */ + fun fetchWpComThemes(filter: String?, resultsLimit: Int) { + val url = WPCOMREST.themes.getUrlV1_2() + val params = mutableMapOf("number" to resultsLimit.toString()) + filter?.let { params["filter"] = it } + + add( + WPComGsonRequest.buildGetRequest( + url, params, WPComThemeListResponse::class.java, + { response, _ -> + AppLog.d(T.API, "Received response to WP.com themes fetch request.") + val themes = createThemeListFromArrayResponse(response) + val payload = FetchedWpComThemesPayload(themes) + mDispatcher.dispatch(ThemeActionBuilder.newFetchedWpComThemesAction(payload)) + }, + { error -> + AppLog.e(T.API, "Received error response to WP.com themes fetch request.") + val themeError = ThemesError(error.apiError, error.message) + val payload = FetchedWpComThemesPayload(themeError) + mDispatcher.dispatch(ThemeActionBuilder.newFetchedWpComThemesAction(payload)) + } + ) + ) + } + + /** + * Endpoint: v1.1/sites/$siteId/themes/mine; same endpoint for both Jetpack and WP.com sites! + * + * @see Documentation + */ + fun fetchCurrentTheme(site: SiteModel) { + val url = WPCOMREST.sites.site(site.siteId).themes.mine.getUrlV1_1() + add( + WPComGsonRequest.buildGetRequest( + url, null, WPComThemeResponse::class.java, + { response, _ -> + AppLog.d(T.API, "Received response to current theme fetch request.") + val responseTheme = createThemeFromWPComResponse(response) + val payload = FetchedCurrentThemePayload(site, responseTheme) + mDispatcher.dispatch(ThemeActionBuilder.newFetchedCurrentThemeAction(payload)) + }, + { error -> + AppLog.e(T.API, "Received error response to current theme fetch request.") + val themeError = ThemesError(error.apiError, error.message) + val payload = FetchedCurrentThemePayload(site, themeError) + mDispatcher.dispatch(ThemeActionBuilder.newFetchedCurrentThemeAction(payload)) + } + ) + ) + } + + private fun createThemeFromWPComResponse(response: WPComThemeResponse): ThemeModel { + return ThemeModel( + siteId = LocalId(0), + themeId = response.id, + name = response.name, + demoUrl = response.demo_uri, + active = false, + isWpComTheme = false + ) + } + + private fun createThemeFromJetpackResponse(response: JetpackThemeResponse): ThemeModel { + return ThemeModel( + siteId = LocalId(0), + themeId = response.id, + name = response.name, + demoUrl = null, + active = response.active, + isWpComTheme = false + ) + } + + private fun createThemeListFromArrayResponse(response: WPComThemeListResponse): List { + return response.themes.map { createThemeFromWPComResponse(it) } + } + + /** + * Must provide theme slug with -wpcom suffix to install a WP.com theme on a Jetpack site. + * + * @see Documentation + */ + private fun getThemeIdWithWpComSuffix(theme: ThemeModel): String { + return if (theme.themeId.endsWith("-wpcom")) { + theme.themeId + } else { + theme.themeId + "-wpcom" + } + } +} diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/ThemeSqlUtils.java b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/ThemeSqlUtils.java deleted file mode 100644 index 865ce41c51e2..000000000000 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/ThemeSqlUtils.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.wordpress.android.fluxc.persistence; - -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.wellsql.generated.ThemeModelTable; -import com.yarolegovich.wellsql.WellSql; - -import org.wordpress.android.fluxc.model.SiteModel; -import org.wordpress.android.fluxc.model.ThemeModel; - -import java.util.List; - -public class ThemeSqlUtils { - public static void insertOrUpdateSiteTheme(@NonNull SiteModel site, @NonNull ThemeModel theme) { - List existing = WellSql.select(ThemeModel.class) - .where().beginGroup() - .equals(ThemeModelTable.THEME_ID, theme.getThemeId()) - .equals(ThemeModelTable.LOCAL_SITE_ID, site.getId()) - .equals(ThemeModelTable.IS_WP_COM_THEME, false) - .endGroup().endWhere().getAsModel(); - - // Make sure the local id of the theme is set correctly - theme.setLocalSiteId(site.getId()); - // Always remove WP.com flag while storing as a site associate theme as we might be saving - // a copy of a wp.com theme after an activation - theme.setIsWpComTheme(false); - - if (existing.isEmpty()) { - // theme is not in the local DB so we insert it - WellSql.insert(theme).asSingleTransaction(true).execute(); - } else { - // theme already exists in the local DB so we update the existing row with the passed theme - WellSql.update(ThemeModel.class).whereId(existing.get(0).getId()) - .put(theme, new UpdateAllExceptId<>(ThemeModel.class)).execute(); - } - } - - public static void insertOrReplaceWpComThemes(@NonNull List themes) { - // remove existing WP.com themes - removeWpComThemes(); - - // ensure WP.com flag is set before inserting - for (ThemeModel theme : themes) { - theme.setIsWpComTheme(true); - } - - WellSql.insert(themes).asSingleTransaction(true).execute(); - } - - public static void insertOrReplaceActiveThemeForSite(@NonNull SiteModel site, @NonNull ThemeModel theme) { - // find any existing active theme for the site and unset active flag - List existing = getActiveThemeForSite(site); - if (!existing.isEmpty()) { - for (ThemeModel activeTheme : existing) { - activeTheme.setActive(false); - WellSql.update(ThemeModel.class) - .whereId(activeTheme.getId()) - .put(activeTheme).execute(); - } - } - - // make sure active flag is set - theme.setActive(true); - insertOrUpdateSiteTheme(site, theme); - } - - @NonNull - public static List getActiveThemeForSite(@NonNull SiteModel site) { - return WellSql.select(ThemeModel.class) - .where().beginGroup() - .equals(ThemeModelTable.LOCAL_SITE_ID, site.getId()) - .equals(ThemeModelTable.ACTIVE, true) - .endGroup().endWhere().getAsModel(); - } - - @NonNull - public static List getWpComThemes(@NonNull List themeIds) { - return WellSql.select(ThemeModel.class) - .where() - .equals(ThemeModelTable.IS_WP_COM_THEME, true) - .isIn(ThemeModelTable.THEME_ID, themeIds) - .endWhere().getAsModel(); - } - - @Nullable - public static ThemeModel getWpComThemeByThemeId(@NonNull String themeId) { - if (TextUtils.isEmpty(themeId)) { - return null; - } - - List matches = WellSql.select(ThemeModel.class) - .where().beginGroup() - .equals(ThemeModelTable.THEME_ID, themeId) - .equals(ThemeModelTable.IS_WP_COM_THEME, true) - .endGroup().endWhere().getAsModel(); - - if (matches == null || matches.isEmpty()) { - return null; - } - - return matches.get(0); - } - - @Nullable - public static ThemeModel getSiteThemeByThemeId(@NonNull SiteModel siteModel, @NonNull String themeId) { - if (TextUtils.isEmpty(themeId)) { - return null; - } - List matches = WellSql.select(ThemeModel.class) - .where().beginGroup() - .equals(ThemeModelTable.LOCAL_SITE_ID, siteModel.getId()) - .equals(ThemeModelTable.THEME_ID, themeId) - .equals(ThemeModelTable.IS_WP_COM_THEME, false) - .endGroup().endWhere().getAsModel(); - - if (matches == null || matches.isEmpty()) { - return null; - } - - return matches.get(0); - } - - public static void removeWpComThemes() { - WellSql.delete(ThemeModel.class) - .where() - .equals(ThemeModelTable.IS_WP_COM_THEME, true) - .endWhere().execute(); - } -} diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/WPAndroidDatabase.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/WPAndroidDatabase.kt index 4b6ef90421e2..d272614b549f 100644 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/WPAndroidDatabase.kt +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/WPAndroidDatabase.kt @@ -10,6 +10,7 @@ import androidx.room.TypeConverters import androidx.room.migration.AutoMigrationSpec import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase +import org.wordpress.android.fluxc.model.ThemeModel import org.wordpress.android.fluxc.persistence.FeatureFlagConfigDao.FeatureFlag import org.wordpress.android.fluxc.persistence.blaze.BlazeCampaignsDao import org.wordpress.android.fluxc.persistence.blaze.BlazeCampaignsDao.BlazeCampaignEntity @@ -19,12 +20,14 @@ import org.wordpress.android.fluxc.persistence.blaze.BlazeTargetingDao import org.wordpress.android.fluxc.persistence.blaze.BlazeTargetingDeviceEntity import org.wordpress.android.fluxc.persistence.blaze.BlazeTargetingLanguageEntity import org.wordpress.android.fluxc.persistence.blaze.BlazeTargetingTopicEntity +import org.wordpress.android.fluxc.persistence.converters.LocalIdConverter import org.wordpress.android.fluxc.persistence.coverters.StringListConverter +import org.wordpress.android.fluxc.persistence.dao.ThemeDao import org.wordpress.android.fluxc.persistence.domains.DomainDao import org.wordpress.android.fluxc.persistence.domains.DomainDao.DomainEntity @Database( - version = 30, + version = 31, entities = [ FeatureFlag::class, DomainEntity::class, @@ -33,6 +36,7 @@ import org.wordpress.android.fluxc.persistence.domains.DomainDao.DomainEntity BlazeTargetingLanguageEntity::class, BlazeTargetingDeviceEntity::class, BlazeTargetingTopicEntity::class, + ThemeModel::class, ], autoMigrations = [ AutoMigration(from = 11, to = 12), @@ -47,11 +51,13 @@ import org.wordpress.android.fluxc.persistence.domains.DomainDao.DomainEntity AutoMigration(from = 27, to = 28), AutoMigration(from = 28, to = 29), AutoMigration(from = 29, to = 30, spec = AutoMigration29to30::class), + AutoMigration(from = 30, to = 31), ] ) @TypeConverters( value = [ - StringListConverter::class + StringListConverter::class, + LocalIdConverter::class ] ) abstract class WPAndroidDatabase : RoomDatabase() { @@ -65,6 +71,8 @@ abstract class WPAndroidDatabase : RoomDatabase() { abstract fun blazeObjectivesDao(): BlazeObjectivesDao + internal abstract fun themeDao(): ThemeDao + @Suppress("MemberVisibilityCanBePrivate") companion object { const val WP_DB_NAME = "wp-android-database" diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/WellSqlConfig.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/WellSqlConfig.kt index cb8ffcd905f8..2dac973422f1 100644 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/WellSqlConfig.kt +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/WellSqlConfig.kt @@ -40,7 +40,7 @@ open class WellSqlConfig : DefaultWellConfig { annotation class AddOn override fun getDbVersion(): Int { - return 234 + return 235 } override fun getDbName(): String { @@ -2264,9 +2264,14 @@ open class WellSqlConfig : DefaultWellConfig { 233 -> migrateAddOn(ADDON_WOOCOMMERCE, version) { db.execSQL("DROP TABLE IF EXISTS WCOrderShipmentTrackingModel") } + 234 -> migrateAddOn(ADDON_WOOCOMMERCE, version) { db.execSQL("DROP TABLE IF EXISTS WCShippingLabelCreationEligibility") } + + 235 -> migrate(version) { + db.execSQL("DROP TABLE IF EXISTS ThemeModel") + } } } db.setTransactionSuccessful() diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/converters/LocalIdConverter.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/converters/LocalIdConverter.kt similarity index 100% rename from libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/converters/LocalIdConverter.kt rename to libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/converters/LocalIdConverter.kt diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/dao/ThemeDao.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/dao/ThemeDao.kt new file mode 100644 index 000000000000..4488acda8379 --- /dev/null +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/dao/ThemeDao.kt @@ -0,0 +1,68 @@ +package org.wordpress.android.fluxc.persistence.dao + +import androidx.room.Dao +import androidx.room.Query +import androidx.room.Transaction +import androidx.room.Upsert +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId +import org.wordpress.android.fluxc.model.ThemeModel + +@Dao +internal abstract class ThemeDao { + @Query( + """ + SELECT * FROM ThemeEntity + WHERE siteId = :siteId + AND active = 1 + """ + ) + abstract suspend fun getActiveThemesForSite(siteId: LocalId): List + + @Query( + """ + SELECT * FROM ThemeEntity + WHERE isWpComTheme = 1 + AND themeId IN (:themeIds) + """ + ) + abstract suspend fun getWpComThemes(themeIds: List): List + + @Query( + """ + SELECT * FROM ThemeEntity + WHERE themeId = :themeId + AND isWpComTheme = 1 + LIMIT 1 + """ + ) + abstract suspend fun getWpComThemeByThemeId(themeId: String): ThemeModel? + + @Query( + """ + SELECT * FROM ThemeEntity + WHERE siteId = :siteId + AND themeId = :themeId + AND isWpComTheme = 0 + LIMIT 1 + """ + ) + abstract suspend fun getSiteThemeByThemeId( + siteId: LocalId, + themeId: String + ): ThemeModel? + + @Transaction + open suspend fun replaceAllWpComThemes(themes: List) { + deleteWpComThemes() + upsertThemes(themes) + } + + @Query("DELETE FROM ThemeEntity WHERE isWpComTheme = 1") + protected abstract suspend fun deleteWpComThemes() + + @Upsert + abstract suspend fun upsert(theme: ThemeModel) + + @Upsert + abstract suspend fun upsertThemes(themes: List) +} diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/store/ThemeStore.java b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/store/ThemeStore.java deleted file mode 100644 index 2122ed7e6c14..000000000000 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/store/ThemeStore.java +++ /dev/null @@ -1,308 +0,0 @@ -package org.wordpress.android.fluxc.store; - -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; -import org.wordpress.android.fluxc.Dispatcher; -import org.wordpress.android.fluxc.Payload; -import org.wordpress.android.fluxc.action.ThemeAction; -import org.wordpress.android.fluxc.annotations.action.Action; -import org.wordpress.android.fluxc.annotations.action.IAction; -import org.wordpress.android.fluxc.model.SiteModel; -import org.wordpress.android.fluxc.model.ThemeModel; -import org.wordpress.android.fluxc.network.BaseRequest.BaseNetworkError; -import org.wordpress.android.fluxc.network.rest.wpcom.theme.ThemeRestClient; -import org.wordpress.android.fluxc.persistence.ThemeSqlUtils; -import org.wordpress.android.util.AppLog; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Singleton; - -@Singleton -public class ThemeStore extends Store { - // Payloads - public static class FetchWPComThemesPayload extends Payload { - @Nullable public String filter; - public int resultsLimit; - - public FetchWPComThemesPayload(@Nullable String filter, int resultsLimit) { - this.filter = filter; - this.resultsLimit = resultsLimit; - } - } - - public static class FetchedCurrentThemePayload extends Payload { - @NonNull public SiteModel site; - @Nullable public ThemeModel theme; - - public FetchedCurrentThemePayload(@NonNull SiteModel site, @NonNull ThemesError error) { - this.site = site; - this.error = error; - } - - public FetchedCurrentThemePayload(@NonNull SiteModel site, @NonNull ThemeModel theme) { - this.site = site; - this.theme = theme; - } - } - - public static class FetchedWpComThemesPayload extends Payload { - @NonNull public List themes; - - public FetchedWpComThemesPayload(@NonNull ThemesError error) { - this.error = error; - this.themes = new ArrayList<>(); - } - - public FetchedWpComThemesPayload(@NonNull List themes) { - this.themes = themes; - } - } - - public static class SiteThemePayload extends Payload { - @NonNull public SiteModel site; - @NonNull public ThemeModel theme; - - public SiteThemePayload(@NonNull SiteModel site, @NonNull ThemeModel theme) { - this.site = site; - this.theme = theme; - } - } - - public enum ThemeErrorType { - GENERIC_ERROR, - UNAUTHORIZED, - NOT_AVAILABLE, - THEME_NOT_FOUND, - THEME_ALREADY_INSTALLED, - UNKNOWN_THEME, - MISSING_THEME; - - @NonNull - public static ThemeErrorType fromString(@NonNull String type) { - for (ThemeErrorType v : ThemeErrorType.values()) { - if (type.equalsIgnoreCase(v.name())) { - return v; - } - } - return GENERIC_ERROR; - } - } - - @SuppressWarnings("WeakerAccess") - public static class ThemesError implements OnChangedError { - @NonNull public ThemeErrorType type; - @Nullable public String message; - - public ThemesError(@NonNull String type, @Nullable String message) { - this.type = ThemeErrorType.fromString(type); - this.message = message; - } - - public ThemesError(@NonNull ThemeErrorType type) { - this.type = type; - } - } - - public static class OnWpComThemesChanged extends OnChanged { - } - - @SuppressWarnings("WeakerAccess") - public static class OnCurrentThemeFetched extends OnChanged { - @NonNull public SiteModel site; - @Nullable public ThemeModel theme; - - public OnCurrentThemeFetched(@NonNull SiteModel site, @Nullable ThemeModel theme) { - this.site = site; - this.theme = theme; - } - } - - @SuppressWarnings("WeakerAccess") - public static class OnThemeActivated extends OnChanged { - @NonNull public SiteModel site; - @NonNull public ThemeModel theme; - - public OnThemeActivated(@NonNull SiteModel site, @NonNull ThemeModel theme) { - this.site = site; - this.theme = theme; - } - } - - @SuppressWarnings("WeakerAccess") - public static class OnThemeInstalled extends OnChanged { - @NonNull public SiteModel site; - @NonNull public ThemeModel theme; - - public OnThemeInstalled(@NonNull SiteModel site, @NonNull ThemeModel theme) { - this.site = site; - this.theme = theme; - } - } - - private final ThemeRestClient mThemeRestClient; - - @Inject public ThemeStore(Dispatcher dispatcher, ThemeRestClient themeRestClient) { - super(dispatcher); - mThemeRestClient = themeRestClient; - } - - @Subscribe(threadMode = ThreadMode.ASYNC) - @Override - @SuppressWarnings("rawtypes") - public void onAction(Action action) { - IAction actionType = action.getType(); - if (!(actionType instanceof ThemeAction)) { - return; - } - switch ((ThemeAction) actionType) { - case FETCH_WP_COM_THEMES: - fetchWpComThemes((FetchWPComThemesPayload) action.getPayload()); - break; - case FETCHED_WP_COM_THEMES: - handleWpComThemesFetched((FetchedWpComThemesPayload) action.getPayload()); - break; - case FETCH_CURRENT_THEME: - fetchCurrentTheme((SiteModel) action.getPayload()); - break; - case FETCHED_CURRENT_THEME: - handleCurrentThemeFetched((FetchedCurrentThemePayload) action.getPayload()); - break; - case ACTIVATE_THEME: - activateTheme((SiteThemePayload) action.getPayload()); - break; - case ACTIVATED_THEME: - handleThemeActivated((SiteThemePayload) action.getPayload()); - break; - case INSTALL_THEME: - installTheme((SiteThemePayload) action.getPayload()); - break; - case INSTALLED_THEME: - handleThemeInstalled((SiteThemePayload) action.getPayload()); - break; - } - } - - @Override - public void onRegister() { - AppLog.d(AppLog.T.API, "ThemeStore onRegister"); - } - - @NonNull - public List getWpComThemes(@NonNull List themeIds) { - return ThemeSqlUtils.getWpComThemes(themeIds); - } - - @Nullable - public ThemeModel getInstalledThemeByThemeId(@NonNull SiteModel siteModel, @NonNull String themeId) { - if (TextUtils.isEmpty(themeId)) { - return null; - } - return ThemeSqlUtils.getSiteThemeByThemeId(siteModel, themeId); - } - - @Nullable - @SuppressWarnings("WeakerAccess") - public ThemeModel getWpComThemeByThemeId(@NonNull String themeId) { - if (TextUtils.isEmpty(themeId)) { - return null; - } - return ThemeSqlUtils.getWpComThemeByThemeId(themeId); - } - - public void setActiveThemeForSite(@NonNull SiteModel site, @NonNull ThemeModel theme) { - ThemeSqlUtils.insertOrReplaceActiveThemeForSite(site, theme); - } - - private void fetchWpComThemes(@NonNull FetchWPComThemesPayload payload) { - mThemeRestClient.fetchWpComThemes(payload.filter, payload.resultsLimit); - } - - private void handleWpComThemesFetched(@NonNull FetchedWpComThemesPayload payload) { - OnWpComThemesChanged event = new OnWpComThemesChanged(); - if (payload.isError()) { - event.error = payload.error; - } else { - ThemeSqlUtils.insertOrReplaceWpComThemes(payload.themes); - } - emitChange(event); - } - - private void fetchCurrentTheme(@NonNull SiteModel site) { - if (site.isUsingWpComRestApi()) { - mThemeRestClient.fetchCurrentTheme(site); - } else { - ThemesError error = new ThemesError(ThemeErrorType.NOT_AVAILABLE); - FetchedCurrentThemePayload payload = new FetchedCurrentThemePayload(site, error); - handleCurrentThemeFetched(payload); - } - } - - private void handleCurrentThemeFetched(@NonNull FetchedCurrentThemePayload payload) { - OnCurrentThemeFetched event = new OnCurrentThemeFetched(payload.site, payload.theme); - if (payload.isError()) { - event.error = payload.error; - } else { - if (payload.theme != null) { - ThemeSqlUtils.insertOrReplaceActiveThemeForSite(payload.site, payload.theme); - } else { - AppLog.w(AppLog.T.THEMES, "Fetched current theme payload theme is null."); - } - } - emitChange(event); - } - - private void installTheme(@NonNull SiteThemePayload payload) { - if (payload.site.isJetpackConnected() && payload.site.isUsingWpComRestApi()) { - mThemeRestClient.installTheme(payload.site, payload.theme); - } else { - payload.error = new ThemesError(ThemeErrorType.NOT_AVAILABLE); - handleThemeInstalled(payload); - } - } - - private void handleThemeInstalled(@NonNull SiteThemePayload payload) { - OnThemeInstalled event = new OnThemeInstalled(payload.site, payload.theme); - if (payload.isError()) { - event.error = payload.error; - } else { - ThemeSqlUtils.insertOrUpdateSiteTheme(payload.site, payload.theme); - } - emitChange(event); - } - - private void activateTheme(@NonNull SiteThemePayload payload) { - if (payload.site.isUsingWpComRestApi()) { - mThemeRestClient.activateTheme(payload.site, payload.theme); - } else { - payload.error = new ThemesError(ThemeErrorType.NOT_AVAILABLE); - handleThemeActivated(payload); - } - } - - private void handleThemeActivated(@NonNull SiteThemePayload payload) { - OnThemeActivated event = new OnThemeActivated(payload.site, payload.theme); - if (payload.isError()) { - event.error = payload.error; - } else { - ThemeModel activatedTheme; - // payload theme doesn't have all the data so we grab a copy of the database theme and update active flag - if (payload.site.isJetpackConnected()) { - activatedTheme = getInstalledThemeByThemeId(payload.site, payload.theme.getThemeId()); - } else { - activatedTheme = getWpComThemeByThemeId(payload.theme.getThemeId()); - } - if (activatedTheme != null) { - setActiveThemeForSite(payload.site, activatedTheme); - } - } - emitChange(event); - } -} diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/store/ThemeStore.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/store/ThemeStore.kt new file mode 100644 index 000000000000..5031dd19b5a9 --- /dev/null +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/store/ThemeStore.kt @@ -0,0 +1,281 @@ +package org.wordpress.android.fluxc.store + +import kotlinx.coroutines.runBlocking +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import org.wordpress.android.fluxc.Dispatcher +import org.wordpress.android.fluxc.Payload +import org.wordpress.android.fluxc.action.ThemeAction +import org.wordpress.android.fluxc.annotations.action.Action +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.model.ThemeModel +import org.wordpress.android.fluxc.network.BaseRequest.BaseNetworkError +import org.wordpress.android.fluxc.network.rest.wpcom.theme.ThemeRestClient +import org.wordpress.android.fluxc.persistence.WPAndroidDatabase +import org.wordpress.android.fluxc.store.Store.OnChangedError +import org.wordpress.android.util.AppLog +import org.wordpress.android.util.AppLog.T +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ThemeStore @Inject constructor( + dispatcher: Dispatcher, + private val themeRestClient: ThemeRestClient, + private val database: WPAndroidDatabase +) : Store(dispatcher) { + + // Payloads + class FetchWPComThemesPayload( + @JvmField val filter: String? = null, + @JvmField val resultsLimit: Int + ) : Payload() + + class FetchedCurrentThemePayload : Payload { + @JvmField + val site: SiteModel + @JvmField + val theme: ThemeModel? + + constructor(site: SiteModel, error: ThemesError) : super() { + this.site = site + this.theme = null + this.error = error + } + + constructor(site: SiteModel, theme: ThemeModel) : super() { + this.site = site + this.theme = theme + } + } + + class FetchedWpComThemesPayload : Payload { + @JvmField + val themes: List + + constructor(error: ThemesError) : super() { + this.error = error + this.themes = emptyList() + } + + constructor(themes: List) : super() { + this.themes = themes + } + } + + class SiteThemePayload( + @JvmField val site: SiteModel, + @JvmField val theme: ThemeModel + ) : Payload() + + enum class ThemeErrorType { + GENERIC_ERROR, + UNAUTHORIZED, + NOT_AVAILABLE, + THEME_NOT_FOUND, + THEME_ALREADY_INSTALLED, + UNKNOWN_THEME, + MISSING_THEME; + + companion object { + fun fromString(type: String): ThemeErrorType { + return values().find { it.name.equals(type, ignoreCase = true) } + ?: GENERIC_ERROR + } + } + } + + class ThemesError : OnChangedError { + @JvmField + val type: ThemeErrorType + @JvmField + val message: String? + + constructor(type: String, message: String?) { + this.type = ThemeErrorType.fromString(type) + this.message = message + } + + constructor(type: ThemeErrorType) { + this.type = type + this.message = null + } + } + + class OnWpComThemesChanged : OnChanged() + + class OnCurrentThemeFetched( + @JvmField val site: SiteModel, + @JvmField val theme: ThemeModel? + ) : OnChanged() + + class OnThemeActivated( + @JvmField val site: SiteModel, + @JvmField val theme: ThemeModel + ) : OnChanged() + + class OnThemeInstalled( + @JvmField val site: SiteModel, + @JvmField val theme: ThemeModel + ) : OnChanged() + + @Subscribe(threadMode = ThreadMode.ASYNC) + @Suppress("UNCHECKED_CAST") + override fun onAction(action: Action<*>) { + val actionType = action.type + if (actionType !is ThemeAction) { + return + } + when (actionType) { + ThemeAction.FETCH_WP_COM_THEMES -> + fetchWpComThemes(action.payload as FetchWPComThemesPayload) + ThemeAction.FETCHED_WP_COM_THEMES -> + handleWpComThemesFetched(action.payload as FetchedWpComThemesPayload) + ThemeAction.FETCH_CURRENT_THEME -> + fetchCurrentTheme(action.payload as SiteModel) + ThemeAction.FETCHED_CURRENT_THEME -> + handleCurrentThemeFetched(action.payload as FetchedCurrentThemePayload) + ThemeAction.ACTIVATE_THEME -> + activateTheme(action.payload as SiteThemePayload) + ThemeAction.ACTIVATED_THEME -> + handleThemeActivated(action.payload as SiteThemePayload) + ThemeAction.INSTALL_THEME -> + installTheme(action.payload as SiteThemePayload) + ThemeAction.INSTALLED_THEME -> + handleThemeInstalled(action.payload as SiteThemePayload) + } + } + + override fun onRegister() { + AppLog.d(T.API, "ThemeStore onRegister") + } + + fun getWpComThemes(themeIds: List): List = runBlocking { + database.themeDao().getWpComThemes(themeIds) + } + + fun getInstalledThemeByThemeId(siteModel: SiteModel, themeId: String): ThemeModel? { + if (themeId.isEmpty()) { + return null + } + return runBlocking { + database.themeDao().getSiteThemeByThemeId(siteModel.localId(), themeId) + } + } + + fun getWpComThemeByThemeId(themeId: String): ThemeModel? { + if (themeId.isEmpty()) { + return null + } + return runBlocking { + database.themeDao().getWpComThemeByThemeId(themeId) + } + } + + fun setActiveThemeForSite(site: SiteModel, theme: ThemeModel) = runBlocking { + // Deactivate all currently active themes for the site + val activeThemes = database.themeDao().getActiveThemesForSite(site.localId()) + database.themeDao().upsertThemes(activeThemes.map { it.copy(active = false) }) + + // Insert or update the new active theme + val activeTheme = theme.copy( + siteId = site.localId(), + isWpComTheme = false, + active = true + ) + database.themeDao().upsert(activeTheme) + } + + private fun fetchWpComThemes(payload: FetchWPComThemesPayload) { + themeRestClient.fetchWpComThemes(payload.filter, payload.resultsLimit) + } + + private fun handleWpComThemesFetched(payload: FetchedWpComThemesPayload) { + val event = OnWpComThemesChanged() + if (payload.isError) { + event.error = payload.error + } else { + runBlocking { + val wpComThemes = payload.themes.map { it.copy(isWpComTheme = true) } + database.themeDao().replaceAllWpComThemes(wpComThemes) + } + } + emitChange(event) + } + + private fun fetchCurrentTheme(site: SiteModel) { + if (site.isUsingWpComRestApi) { + themeRestClient.fetchCurrentTheme(site) + } else { + val error = ThemesError(ThemeErrorType.NOT_AVAILABLE) + val payload = FetchedCurrentThemePayload(site, error) + handleCurrentThemeFetched(payload) + } + } + + private fun handleCurrentThemeFetched(payload: FetchedCurrentThemePayload) { + val event = OnCurrentThemeFetched(payload.site, payload.theme) + if (payload.isError) { + event.error = payload.error + } else { + if (payload.theme != null) { + setActiveThemeForSite(payload.site, payload.theme) + } else { + AppLog.w(T.THEMES, "Fetched current theme payload theme is null.") + } + } + emitChange(event) + } + + private fun installTheme(payload: SiteThemePayload) { + if (payload.site.isJetpackConnected && payload.site.isUsingWpComRestApi) { + themeRestClient.installTheme(payload.site, payload.theme) + } else { + payload.error = ThemesError(ThemeErrorType.NOT_AVAILABLE) + handleThemeInstalled(payload) + } + } + + private fun handleThemeInstalled(payload: SiteThemePayload) { + val event = OnThemeInstalled(payload.site, payload.theme) + if (payload.isError) { + event.error = payload.error + } else { + runBlocking { + val siteTheme = payload.theme.copy( + siteId = payload.site.localId(), + isWpComTheme = false + ) + database.themeDao().upsert(siteTheme) + } + } + emitChange(event) + } + + private fun activateTheme(payload: SiteThemePayload) { + if (payload.site.isUsingWpComRestApi) { + themeRestClient.activateTheme(payload.site, payload.theme) + } else { + payload.error = ThemesError(ThemeErrorType.NOT_AVAILABLE) + handleThemeActivated(payload) + } + } + + private fun handleThemeActivated(payload: SiteThemePayload) { + val event = OnThemeActivated(payload.site, payload.theme) + if (payload.isError) { + event.error = payload.error + } else { + // payload theme doesn't have all the data so we grab a copy of the database theme and update active flag + val activatedTheme = if (payload.site.isJetpackConnected) { + getInstalledThemeByThemeId(payload.site, payload.theme.themeId) + } else { + getWpComThemeByThemeId(payload.theme.themeId) + } + activatedTheme?.let { + setActiveThemeForSite(payload.site, it) + } + } + emitChange(event) + } +} diff --git a/libs/fluxc/src/test/java/org/wordpress/android/fluxc/persistence/ThemeDaoTest.kt b/libs/fluxc/src/test/java/org/wordpress/android/fluxc/persistence/ThemeDaoTest.kt new file mode 100644 index 000000000000..3da0bdd3da11 --- /dev/null +++ b/libs/fluxc/src/test/java/org/wordpress/android/fluxc/persistence/ThemeDaoTest.kt @@ -0,0 +1,135 @@ +package org.wordpress.android.fluxc.persistence + +import androidx.room.Room +import androidx.test.platform.app.InstrumentationRegistry +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId +import org.wordpress.android.fluxc.persistence.dao.ThemeDao +import org.wordpress.android.fluxc.utils.createTestTheme +import java.io.IOException + +@RunWith(RobolectricTestRunner::class) +class ThemeDaoTest { + private lateinit var dao: ThemeDao + private lateinit var db: WPAndroidDatabase + + @Before + fun createDb() { + val context = InstrumentationRegistry.getInstrumentation().context + db = Room.inMemoryDatabaseBuilder( + context, WPAndroidDatabase::class.java + ).allowMainThreadQueries().build() + dao = db.themeDao() + } + + @After + @Throws(IOException::class) + fun closeDb() { + db.close() + } + + @Test + fun `when upserting new theme, then theme is inserted`(): Unit = runTest { + val theme = createTestTheme(SITE_ID_1, THEME_ID_1, "Theme 1") + + dao.upsert(theme) + + val result = dao.getSiteThemeByThemeId(LocalId(SITE_ID_1), THEME_ID_1) + assertThat(result).isEqualTo(theme) + } + + @Test + fun `when upserting existing theme, then theme is updated`(): Unit = runTest { + val theme = createTestTheme(SITE_ID_1, THEME_ID_1, "Theme 1") + dao.upsert(theme) + + val updatedTheme = theme.copy(name = "Updated Theme") + dao.upsert(updatedTheme) + + val result = dao.getSiteThemeByThemeId(LocalId(SITE_ID_1), THEME_ID_1) + assertThat(result).isEqualTo(updatedTheme) + } + + @Test + fun `when replacing all WP-com themes, then all previous WP-com themes are replaced`(): Unit = runTest { + val theme1 = createTestTheme(0, THEME_ID_1, "WP.com Theme 1", isWpComTheme = true) + val theme2 = createTestTheme(0, THEME_ID_2, "WP.com Theme 2", isWpComTheme = true) + dao.replaceAllWpComThemes(listOf(theme1, theme2)) + + val newTheme = createTestTheme(0, THEME_ID_3, "New WP.com Theme", isWpComTheme = true) + dao.replaceAllWpComThemes(listOf(newTheme)) + + val result = dao.getWpComThemes(listOf(THEME_ID_1, THEME_ID_2, THEME_ID_3)) + assertThat(result).containsOnly(newTheme) + } + + @Test + fun `when getting active themes for site, then only active themes are returned`(): Unit = runTest { + val activeTheme = createTestTheme(SITE_ID_1, THEME_ID_1, "Active Theme", active = true) + val inactiveTheme = createTestTheme(SITE_ID_1, THEME_ID_2, "Inactive Theme", active = false) + dao.upsertThemes(listOf(activeTheme, inactiveTheme)) + + val result = dao.getActiveThemesForSite(LocalId(SITE_ID_1)) + + assertThat(result).containsOnly(activeTheme) + } + + @Test + fun `when getting WP-com themes by IDs, then themes with matching IDs are returned`(): Unit = runTest { + val theme1 = createTestTheme(0, THEME_ID_1, "WP.com Theme 1", isWpComTheme = true) + val theme2 = createTestTheme(0, THEME_ID_2, "WP.com Theme 2", isWpComTheme = true) + val theme3 = createTestTheme(0, THEME_ID_3, "WP.com Theme 3", isWpComTheme = true) + dao.replaceAllWpComThemes(listOf(theme1, theme2, theme3)) + + val result = dao.getWpComThemes(listOf(THEME_ID_1, THEME_ID_3)) + + assertThat(result).containsOnly(theme1, theme3) + } + + @Test + fun `when getting WP-com theme by theme ID, then correct theme is returned`(): Unit = runTest { + val theme = createTestTheme(0, THEME_ID_1, "WP.com Theme", isWpComTheme = true) + dao.replaceAllWpComThemes(listOf(theme)) + + val result = dao.getWpComThemeByThemeId(THEME_ID_1) + + assertThat(result).isEqualTo(theme) + } + + @Test + fun `when getting WP-com theme by non-existent theme ID, then null is returned`(): Unit = runTest { + val result = dao.getWpComThemeByThemeId("non-existent") + + assertThat(result).isNull() + } + + @Test + fun `when getting site theme by theme ID, then correct theme is returned`(): Unit = runTest { + val theme = createTestTheme(SITE_ID_1, THEME_ID_1, "Site Theme") + dao.upsert(theme) + + val result = dao.getSiteThemeByThemeId(LocalId(SITE_ID_1), THEME_ID_1) + + assertThat(result).isEqualTo(theme) + } + + @Test + fun `when getting site theme by non-existent theme ID, then null is returned`(): Unit = runTest { + val result = dao.getSiteThemeByThemeId(LocalId(SITE_ID_1), "non-existent") + + assertThat(result).isNull() + } + + private companion object { + const val SITE_ID_1 = 1 + const val THEME_ID_1 = "theme-1" + const val THEME_ID_2 = "theme-2" + const val THEME_ID_3 = "theme-3" + } +} diff --git a/libs/fluxc/src/test/java/org/wordpress/android/fluxc/store/ThemeStoreUnitTest.kt b/libs/fluxc/src/test/java/org/wordpress/android/fluxc/store/ThemeStoreUnitTest.kt new file mode 100644 index 000000000000..47821e8107d9 --- /dev/null +++ b/libs/fluxc/src/test/java/org/wordpress/android/fluxc/store/ThemeStoreUnitTest.kt @@ -0,0 +1,79 @@ +package org.wordpress.android.fluxc.store + +import androidx.room.Room +import androidx.test.platform.app.InstrumentationRegistry +import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.robolectric.RobolectricTestRunner +import org.wordpress.android.fluxc.Dispatcher +import org.wordpress.android.fluxc.persistence.WPAndroidDatabase +import org.wordpress.android.fluxc.utils.createTestTheme +import org.wordpress.android.fluxc.utils.generateWPComSite +import java.io.IOException + +@RunWith(RobolectricTestRunner::class) +class ThemeStoreUnitTest { + private lateinit var database: WPAndroidDatabase + private lateinit var themeStore: ThemeStore + + @Before + fun setUp() { + val context = InstrumentationRegistry.getInstrumentation().context + database = Room.inMemoryDatabaseBuilder( + context, WPAndroidDatabase::class.java + ).allowMainThreadQueries().build() + + themeStore = ThemeStore( + Dispatcher(), + mock(), + database + ) + } + + @After + @Throws(IOException::class) + fun closeDb() { + database.close() + } + + @Test + fun `when setting active theme, then previous theme is deactivated and new theme is activated`() { + val site = generateWPComSite() + val firstTheme = createTestTheme(site.id, "first-active", "First Active") + val secondTheme = createTestTheme(site.id, "second-active", "Second Active") + + // Set first theme active and verify + themeStore.setActiveThemeForSite(site, firstTheme) + val firstActiveTheme = themeStore.getInstalledThemeByThemeId(site, firstTheme.themeId) + assertThat(firstActiveTheme!!.active).isTrue + + // Set second theme active and verify + themeStore.setActiveThemeForSite(site, secondTheme) + val secondActiveTheme = themeStore.getInstalledThemeByThemeId(site, secondTheme.themeId) + assertThat(secondActiveTheme!!.active).isTrue + + // Verify first theme is no longer active + val deactivatedFirstTheme = themeStore.getInstalledThemeByThemeId(site, firstTheme.themeId) + assertThat(deactivatedFirstTheme!!.active).isFalse + } + + @Test + fun `when setting active theme for site, then theme can be retrieved by theme id`() { + val testThemeId = "fluxc-ftw" + val testThemeName = "FluxC FTW" + val site = generateWPComSite() + val testTheme = createTestTheme(0, testThemeId, testThemeName) // id is set in setActiveThemeForSite + + themeStore.setActiveThemeForSite(site, testTheme) + + val retrievedTheme = themeStore.getInstalledThemeByThemeId(site, testThemeId) + assertThat(retrievedTheme) + .usingRecursiveComparison() + .ignoringFields("active") + .isEqualTo(testTheme) + } +} diff --git a/libs/fluxc/src/test/java/org/wordpress/android/fluxc/utils/SiteTestUtils.kt b/libs/fluxc/src/test/java/org/wordpress/android/fluxc/utils/SiteTestUtils.kt new file mode 100644 index 000000000000..a158ca8ad509 --- /dev/null +++ b/libs/fluxc/src/test/java/org/wordpress/android/fluxc/utils/SiteTestUtils.kt @@ -0,0 +1,12 @@ +package org.wordpress.android.fluxc.utils + +import org.wordpress.android.fluxc.model.SiteModel + +fun generateWPComSite() = SiteModel().apply { + url = "" + xmlRpcUrl = "" + siteId = 556 + setIsWPCom(true) + setIsVisible(true) + origin = SiteModel.ORIGIN_WPCOM_REST +} diff --git a/libs/fluxc/src/test/java/org/wordpress/android/fluxc/utils/ThemeTestUtils.kt b/libs/fluxc/src/test/java/org/wordpress/android/fluxc/utils/ThemeTestUtils.kt new file mode 100644 index 000000000000..21834ecb9764 --- /dev/null +++ b/libs/fluxc/src/test/java/org/wordpress/android/fluxc/utils/ThemeTestUtils.kt @@ -0,0 +1,20 @@ +package org.wordpress.android.fluxc.utils + +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId +import org.wordpress.android.fluxc.model.ThemeModel + +fun createTestTheme( + siteId: Int, + themeId: String, + name: String, + isWpComTheme: Boolean = false, + active: Boolean = false, + demoUrl: String? = null +) = ThemeModel( + siteId = LocalId(siteId), + themeId = themeId, + name = name, + demoUrl = demoUrl, + active = active, + isWpComTheme = isWpComTheme +)