Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/trunk' into merge/24.4-rc-2-to-t…
Browse files Browse the repository at this point in the history
…runk
  • Loading branch information
oguzkocer committed Mar 11, 2024
2 parents 53f18ab + f6a8606 commit e31732b
Show file tree
Hide file tree
Showing 74 changed files with 4,768 additions and 512 deletions.
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Fixes #

-----

## Testing Checklist:
## Testing Checklist (strike-out the not-applying and unnecessary ones):

- [ ] WordPress.com sites and self-hosted Jetpack sites.
- [ ] Portrait and landscape orientations.
Expand Down
4 changes: 3 additions & 1 deletion RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

24.5
-----

* [*] [internal] Block editor: Remove code associated to Story block [https://github.com/wordpress-mobile/WordPress-Android/pull/20400]
* [*] [Jetpack-only] Fixes broken links on some notifications [https://github.com/wordpress-mobile/WordPress-Android/pull/20417]
* [**] [internal] Block editor: Upgrade React Native to version 0.73.3 [#20167]

24.4
-----
Expand Down
19 changes: 8 additions & 11 deletions WordPress/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import se.bjurr.violations.lib.model.SEVERITY
plugins {
id "com.android.application"
id "org.jetbrains.kotlin.android"
id "org.jetbrains.kotlin.kapt"
id "org.jetbrains.kotlin.plugin.parcelize"
id "org.jetbrains.kotlin.plugin.allopen"
id "io.sentry.android.gradle"
id "se.bjurr.violations.violation-comments-to-github-gradle-plugin"
id "com.google.gms.google-services"
id "com.google.dagger.hilt.android"
id "org.jetbrains.kotlinx.kover"
id "com.google.devtools.ksp"
}

sentry {
Expand Down Expand Up @@ -141,9 +141,9 @@ android {
buildConfigField "boolean", "PLANS_IN_SITE_CREATION", "false"
buildConfigField "boolean", "READER_IMPROVEMENTS", "false"
buildConfigField "boolean", "BLOGANUARY_DASHBOARD_NUDGE", "false"
buildConfigField "boolean", "IN_APP_REVIEWS", "false"
buildConfigField "boolean", "DYNAMIC_DASHBOARD_CARDS", "false"
buildConfigField "boolean", "STATS_TRAFFIC_TAB", "false"
buildConfigField "boolean", "READER_DISCOVER_NEW_ENDPOINT", "false"

// Override these constants in jetpack product flavor to enable/ disable features
buildConfigField "boolean", "ENABLE_SITE_CREATION", "true"
Expand All @@ -158,6 +158,7 @@ android {
buildConfigField "boolean", "BLAZE_MANAGE_CAMPAIGNS", "false"
buildConfigField "boolean", "DASHBOARD_PERSONALIZATION", "false"
buildConfigField "boolean", "ENABLE_SITE_MONITORING", "false"
buildConfigField "boolean", "SYNC_PUBLISHING", "false"

manifestPlaceholders = [magicLinkScheme:"wordpress"]
}
Expand Down Expand Up @@ -335,16 +336,12 @@ static def addBuildConfigFieldsFromPrefixedProperties(variant, properties, prefi
variant.buildConfigField "String", it.key.toUpperCase(), "\"${it.value}\""
}
}
kapt {
// Enable to infer error types in stubs (see: https://kotlinlang.org/docs/kapt.html#non-existent-type-correction)
correctErrorTypes true
}

dependencies {
implementation 'androidx.webkit:webkit:1.10.0'
implementation "androidx.navigation:navigation-compose:$androidxComposeNavigationVersion"
compileOnly project(path: ':libs:annotations')
kapt project(':libs:processors')
ksp project(':libs:processors')
implementation (project(path:':libs:networking')) {
exclude group: "com.android.volley"
exclude group: 'org.wordpress', module: 'utils'
Expand Down Expand Up @@ -446,7 +443,7 @@ dependencies {
exclude group: 'androidx.appcompat', module: 'appcompat'
}
implementation "com.github.bumptech.glide:glide:$glideVersion"
kapt "com.github.bumptech.glide:compiler:$glideVersion"
ksp "com.github.bumptech.glide:ksp:$glideVersion"
implementation "com.github.bumptech.glide:volley-integration:$glideVersion"
implementation "com.github.indexos.media-for-mobile:domain:$indexosMediaForMobileVersion"
implementation "com.github.indexos.media-for-mobile:android:$indexosMediaForMobileVersion"
Expand All @@ -460,9 +457,9 @@ dependencies {
exclude group: 'com.android.support', module: 'support-annotations'
}
implementation "com.google.dagger:dagger-android-support:$gradle.ext.daggerVersion"
kapt "com.google.dagger:dagger-android-processor:$gradle.ext.daggerVersion"
ksp "com.google.dagger:dagger-android-processor:$gradle.ext.daggerVersion"
implementation "com.google.dagger:hilt-android:$gradle.ext.daggerVersion"
kapt "com.google.dagger:hilt-compiler:$gradle.ext.daggerVersion"
ksp "com.google.dagger:hilt-compiler:$gradle.ext.daggerVersion"

testImplementation("androidx.arch.core:core-testing:$androidxArchCoreVersion", {
exclude group: 'com.android.support', module: 'support-compat'
Expand Down Expand Up @@ -517,7 +514,7 @@ dependencies {
androidTestImplementation (name:'cloudtestingscreenshotter_lib', ext:'aar') // Screenshots on Firebase Cloud Testing
androidTestImplementation "androidx.work:work-testing:$androidxWorkManagerVersion"
androidTestImplementation "com.google.dagger:hilt-android-testing:$gradle.ext.daggerVersion"
kaptAndroidTest "com.google.dagger:hilt-android-compiler:$gradle.ext.daggerVersion"
kspAndroidTest "com.google.dagger:hilt-android-compiler:$gradle.ext.daggerVersion"
// Enables Java 8+ API desugaring support
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$androidDesugarVersion"
lintChecks "org.wordpress:lint:$wordPressLintVersion"
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package org.wordpress.android.ui.notifications

import android.text.SpannableStringBuilder
import android.text.style.ClickableSpan
import android.widget.TextView
import androidx.test.platform.app.InstrumentationRegistry
import dagger.hilt.android.testing.HiltAndroidTest
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertTrue
import org.junit.Test
import org.wordpress.android.fluxc.tools.FormattableContent
import org.wordpress.android.fluxc.tools.FormattableRange
import org.wordpress.android.ui.notifications.blocks.NoteBlockClickableSpan
import org.wordpress.android.ui.notifications.utils.NotificationsUtils

@HiltAndroidTest
class NotificationsUtilsTest {
@Test
fun testSpannableHasCharacterAtIndex() {
val spannableStringBuilder = SpannableStringBuilder("This is only a test.")

assertTrue(NotificationsUtils.spannableHasCharacterAtIndex(spannableStringBuilder, 's', 3))
assertFalse(NotificationsUtils.spannableHasCharacterAtIndex(spannableStringBuilder, 's', 4))

// Test with bogus params
assertFalse(NotificationsUtils.spannableHasCharacterAtIndex(null, 'b', -1))
}

@Test
fun testGetSpannableContentForRangesAndSkipInvalidUrls() {
// Create a FormattableContent object
val range1 = FormattableRange(indices = listOf(10, 14), url = "https://example.com", type = "a")
val range2 = FormattableRange(indices = listOf(5, 20), url = "", type = "a") // invalid url to skip
val formattableContent = FormattableContent(
text = "This is a test content with a link",
ranges = listOf(range1, range2)
)

// Create a TextView object
val textView = TextView(InstrumentationRegistry.getInstrumentation().context)

// Call the method with the created objects
val result = NotificationsUtils.getSpannableContentForRanges(formattableContent, textView, false) {}

// Check the result
assertNotNull(result)
assertEquals("This is a test content with a link", result.toString())

// Check if the link is correctly set
val spans = result.getSpans(10, 14, ClickableSpan::class.java)
assertTrue(spans.size == 1)
assertEquals("https://example.com", (spans[0] as NoteBlockClickableSpan).formattableRange.url)
}

@Test
fun testGetSpannableContentForRangesWithNoRanges() {
// Create a FormattableContent object with no ranges
val formattableContent = FormattableContent(text = "This is a test content with no link")

// Create a TextView object
val textView = TextView(InstrumentationRegistry.getInstrumentation().context)

// Call the method with the created objects
val result = NotificationsUtils.getSpannableContentForRanges(formattableContent, textView, false) {}

// Check the result
assertNotNull(result)
assertEquals("This is a test content with no link", result.toString())

// Check if no ClickableSpan is set
val spans = result.getSpans(0, result.length, ClickableSpan::class.java)
assertTrue(spans.isEmpty())
}

@Test
fun testGetSpannableContentForRangesWithInvalidIndex() {
// Create a FormattableContent object with a range with an invalid index
val range = FormattableRange(indices = listOf(50, 54), url = "https://example.com", type = "a")
val formattableContent = FormattableContent(text = "This is a test content", ranges = listOf(range))

// Create a TextView object
val textView = TextView(InstrumentationRegistry.getInstrumentation().context)

// Call the method with the created objects
val result = NotificationsUtils.getSpannableContentForRanges(formattableContent, textView, false) {}

// Check the result
assertNotNull(result)
assertEquals("This is a test content", result.toString())

// Check if no ClickableSpan is set
val spans = result.getSpans(0, result.length, ClickableSpan::class.java)
assertTrue(spans.isEmpty())
}

@Test
fun testGetSpannableContentForRangesWithNullUrl() {
// Create a FormattableContent object with a range with a null URL
val range = FormattableRange(indices = listOf(10, 14), url = null, type = "a")
val formattableContent = FormattableContent(text = "This is a test content with a link", ranges = listOf(range))

// Create a TextView object
val textView = TextView(InstrumentationRegistry.getInstrumentation().context)

// Call the method with the created objects
val result = NotificationsUtils.getSpannableContentForRanges(formattableContent, textView, false) {}

// Check the result
assertNotNull(result)
assertEquals("This is a test content with a link", result.toString())

// Check if no ClickableSpan is set for the range with the null URL
val spans = result.getSpans(10, 14, ClickableSpan::class.java)
assertTrue(spans.isEmpty())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.wordpress.android.modules

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import org.wordpress.android.ui.posts.IPostFreshnessChecker
import org.wordpress.android.ui.posts.PostFreshnessCheckerImpl
import javax.inject.Singleton

@InstallIn(SingletonComponent::class)
@Module
class PostModule {
@Singleton
@Provides
fun providePostFreshnessChecker(): IPostFreshnessChecker = PostFreshnessCheckerImpl()
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class ActivityNavigator @Inject constructor() {

fun navigateToCampaignDetailPage(
context: Context,
campaignId: Int,
campaignId: String,
campaignDetailPageSource: CampaignDetailPageSource
) {
context.startActivity(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ import org.wordpress.android.ui.blaze.blazecampaigns.campaignlisting.CampaignLis
@SuppressLint("ParcelCreator")
sealed class BlazeCampaignPage : Parcelable {
data class CampaignListingPage(val source: CampaignListingPageSource) : BlazeCampaignPage()
data class CampaignDetailsPage(val campaignId: Int, val source: CampaignDetailPageSource) : BlazeCampaignPage()
data class CampaignDetailsPage(val campaignId: String, val source: CampaignDetailPageSource) : BlazeCampaignPage()
object Done: BlazeCampaignPage()
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ private const val CAMPAIGN_DETAIL_CAMPAIGN_ID = "campaign_detail_campaign_id"
@AndroidEntryPoint
class CampaignDetailFragment : Fragment(), CampaignDetailWebViewClient.CampaignDetailWebViewClientListener {
companion object {
fun newInstance(campaignId: Int, source: CampaignDetailPageSource) = CampaignDetailFragment().apply {
fun newInstance(campaignId: String, source: CampaignDetailPageSource) = CampaignDetailFragment().apply {
arguments = Bundle().apply {
putSerializable(CAMPAIGN_DETAIL_PAGE_SOURCE, source)
putInt(CAMPAIGN_DETAIL_CAMPAIGN_ID, campaignId)
putString(CAMPAIGN_DETAIL_CAMPAIGN_ID, campaignId)
}
}
}
Expand Down Expand Up @@ -110,7 +110,7 @@ class CampaignDetailFragment : Fragment(), CampaignDetailWebViewClient.CampaignD
?: CampaignDetailPageSource.UNKNOWN
}

private fun getCampaignId() = requireArguments().getInt(CAMPAIGN_DETAIL_CAMPAIGN_ID)
private fun getCampaignId() = requireArguments().getString(CAMPAIGN_DETAIL_CAMPAIGN_ID) ?: ""

override fun onRedirectToExternalBrowser(url: String) = viewModel.onRedirectToExternalBrowser(url)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ class CampaignDetailViewModel @Inject constructor(
@Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher
) : ScopedViewModel(bgDispatcher) {
private lateinit var pageSource: CampaignDetailPageSource
private var campaignId: Int = 0
private var campaignId: String = ""

private val _actionEvents = Channel<BlazeActionEvent>(Channel.BUFFERED)
val actionEvents = _actionEvents.receiveAsFlow()

private val _uiState = MutableStateFlow<CampaignDetailUiState>(CampaignDetailUiState.Preparing)
val uiState = _uiState as StateFlow<CampaignDetailUiState>

fun start(campaignId: Int, campaignDetailPageSource: CampaignDetailPageSource) {
fun start(campaignId: String, campaignDetailPageSource: CampaignDetailPageSource) {
this.campaignId = campaignId
this.pageSource = campaignDetailPageSource

Expand Down Expand Up @@ -76,7 +76,7 @@ class CampaignDetailViewModel @Inject constructor(
pathComponents = arrayOf(
ADVERTISING_PATH,
CAMPAIGNS_PATH,
campaignId.toString(),
campaignId,
extractAndSanitizeSiteUrl()
),
source = pageSource.trackingName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,26 @@ class FetchCampaignListUseCase @Inject constructor(
private val store: BlazeCampaignsStore,
private val mapper: CampaignListingUIModelMapper
) {
companion object {
const val PAGE_SIZE = 10
}

@Suppress("ReturnCount")
suspend fun execute(site: SiteModel, page: Int): Result<NetworkError, List<CampaignModel>> {
val result = store.fetchBlazeCampaigns(site, page)
if (result.isError || result.model == null) return Result.Failure(GenericError)
suspend fun execute(
site: SiteModel,
offset: Int,
pageSize: Int = PAGE_SIZE
): Result<NetworkResult, FetchedCampaignsResult> {
val result = store.fetchBlazeCampaigns(site = site, offset = offset, perPage = pageSize)
if (result.isError || result.model == null) return Result.Failure(GenericResult)
val campaigns = result.model!!.campaigns
if (campaigns.isEmpty()) return Result.Failure(NoCampaigns)
return Result.Success(mapper.mapToCampaignModels(campaigns))
return Result.Success(
FetchedCampaignsResult(
campaigns = mapper.mapToCampaignModels(campaigns),
totalItems = result.model!!.totalItems
)
)
}
}

Expand All @@ -24,14 +37,19 @@ class GetCampaignListFromDbUseCase @Inject constructor(
private val mapper: CampaignListingUIModelMapper
) {
suspend fun execute(site: SiteModel): Result<NoCampaigns, List<CampaignModel>> {
val result = store.getBlazeCampaigns(site)
if (result.campaigns.isEmpty()) return Result.Failure(NoCampaigns)
return Result.Success(mapper.mapToCampaignModels(result.campaigns))
val campaigns = store.getBlazeCampaigns(site)
if (campaigns.isEmpty()) return Result.Failure(NoCampaigns)
return Result.Success(mapper.mapToCampaignModels(campaigns))
}
}

sealed interface NetworkError
data class FetchedCampaignsResult(
val campaigns: List<CampaignModel>,
val totalItems: Int
)

sealed interface NetworkResult

object GenericError : NetworkError
object GenericResult : NetworkResult

object NoCampaigns : NetworkError
object NoCampaigns : NetworkResult
Loading

0 comments on commit e31732b

Please sign in to comment.