Skip to content

Commit

Permalink
Merge pull request #13266 from woocommerce/issue/12760-enlarge-receip…
Browse files Browse the repository at this point in the history
…t-contents

[Receipts] Enlarge receipt contents so that it's easily readable
  • Loading branch information
AnirudhBhat authored Jan 17, 2025
2 parents d167642 + 1167603 commit df4ac5a
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 3 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*** For entries which are touching the Android Wear app's, start entry with `[WEAR]` too.
21.5
-----
- [**] Enhanced WebView to dynamically scale receipt content, ensuring optimal fit across all device screen sizes. [https://github.com/woocommerce/woocommerce-android/pull/13266]
- [*] Fixes missing text in Blaze Campaigns card on larger display and font sizes [https://github.com/woocommerce/woocommerce-android/pull/13300]
- [*] Puerto Rico is now available in the list of countries that are supported by in-person payments [https://github.com/woocommerce/woocommerce-android/pull/13200]
- [*] Removed the outdated feedback survey for shipping labels [https://github.com/woocommerce/woocommerce-android/pull/13319]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.woocommerce.android.ui.payments.receipt.preview

import javax.inject.Inject

class ReceiptHtmlInterceptor @Inject constructor() {

fun interceptHtmlContent(originalHtml: String): String {
return if (originalHtml.contains("<head>")) {
originalHtml.replace(
"<head>",
"<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
)
} else {
originalHtml
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.core.view.MenuProvider
Expand All @@ -18,6 +20,9 @@ import com.woocommerce.android.util.PrintHtmlHelper
import com.woocommerce.android.util.UiHelpers
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar
import dagger.hilt.android.AndroidEntryPoint
import java.io.IOException
import java.net.MalformedURLException
import java.net.URL
import javax.inject.Inject

@AndroidEntryPoint
Expand All @@ -28,6 +33,8 @@ class ReceiptPreviewFragment : BaseFragment(R.layout.fragment_receipt_preview),

@Inject lateinit var uiMessageResolver: UIMessageResolver

@Inject lateinit var receiptHtmlInterceptor: ReceiptHtmlInterceptor

private var _binding: FragmentReceiptPreviewBinding? = null
private val binding get() = _binding!!

Expand Down Expand Up @@ -84,16 +91,48 @@ class ReceiptPreviewFragment : BaseFragment(R.layout.fragment_receipt_preview),
} else {
with(binding.receiptPreviewPreviewWebview) {
webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView,
webResourceRequest: WebResourceRequest
): Boolean {
return viewModel.isReceiptDomainTrustable(webResourceRequest.url.toString())
}

override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
return interceptAndModifyReceiptResponse(request)
}

override fun onPageFinished(view: WebView, url: String) {
viewModel.onReceiptLoaded()
}
}
settings.loadWithOverviewMode = true
settings.useWideViewPort = true
}
}
}

private fun interceptAndModifyReceiptResponse(request: WebResourceRequest): WebResourceResponse? {
return try {
val connection = URL(request.url.toString()).openConnection()
val inputStream = connection.getInputStream()
val originalHtml = inputStream.bufferedReader().use { it.readText() }

val modifiedHtml = receiptHtmlInterceptor.interceptHtmlContent(originalHtml)

WebResourceResponse(
"text/html",
"UTF-8",
modifiedHtml.byteInputStream()
)
} catch (e: MalformedURLException) {
throw IllegalArgumentException("Invalid receipt URL: ${request.url}", e)
} catch (e: IOException) {
throw IOException("Failed to read content from receipt URL: ${request.url}", e)
}
}

private fun initObservers(binding: FragmentReceiptPreviewBinding) {
viewModel.viewStateData.observe(viewLifecycleOwner) {
UiHelpers.updateVisibility(binding.receiptPreviewPreviewWebview, it.isContentVisible)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import com.woocommerce.android.R.string
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare
import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content
import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading
Expand All @@ -12,11 +13,14 @@ import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult
import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.CANCELLED
import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.FAILED
import com.woocommerce.android.util.PrintHtmlHelper.PrintJobResult.STARTED
import com.woocommerce.android.util.WooLog
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar
import com.woocommerce.android.viewmodel.ScopedViewModel
import com.woocommerce.android.viewmodel.navArgs
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import java.net.URI
import java.net.URISyntaxException
import javax.inject.Inject

@HiltViewModel
Expand All @@ -25,6 +29,7 @@ class ReceiptPreviewViewModel
savedState: SavedStateHandle,
private val paymentsFlowTracker: PaymentsFlowTracker,
private val paymentReceiptShare: PaymentReceiptShare,
private val selectedSite: SelectedSite,
) : ScopedViewModel(savedState) {
private val args: ReceiptPreviewFragmentArgs by savedState.navArgs()

Expand All @@ -39,6 +44,24 @@ class ReceiptPreviewViewModel
viewState.value = Content
}

fun isReceiptDomainTrustable(receiptUrl: String): Boolean {
return selectedSite.getIfExists()?.let { site ->
getDomainName(site.url) == getDomainName(receiptUrl)
} ?: false
}

private fun getDomainName(url: String): String? {
return try {
val uri = URI(url)
uri.host?.let {
if (it.startsWith("www.")) it.substring(WWW_PREFIX_LENGTH) else it
}
} catch (e: URISyntaxException) {
WooLog.e(WooLog.T.ORDERS, "Error parsing domain name from receipt url: $url")
return null
}
}

fun onPrintClicked() {
launch {
paymentsFlowTracker.trackPrintReceiptTapped()
Expand Down Expand Up @@ -90,4 +113,8 @@ class ReceiptPreviewViewModel
object Loading : ReceiptPreviewViewState(isProgressVisible = true)
object Content : ReceiptPreviewViewState(isContentVisible = true)
}

companion object {
private const val WWW_PREFIX_LENGTH = 4
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import com.woocommerce.android.ui.payments.receipt.preview.ReceiptHtmlInterceptor
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test

class ReceiptHtmlInterceptorTest {

private val interceptor = ReceiptHtmlInterceptor()

@Test
fun `given original html, when head is present, then should add viewport meta tag`() {
val originalHtml = """
<html>
<head><title>Test</title></head>
<body>Content</body>
</html>
""".trimIndent()

val modifiedHtml = interceptor.interceptHtmlContent(originalHtml)

assertTrue(
modifiedHtml.contains("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">")
)
}

@Test
fun `given original content, when head is missing, then return original content`() {
val originalHtml = """
<html>
<body>Content</body>
</html>
""".trimIndent()

val modifiedHtml = interceptor.interceptHtmlContent(originalHtml)

assertEquals(originalHtml, modifiedHtml)
}

@Test
fun `given empty content, then handle empty input`() {
val originalHtml = ""

val modifiedHtml = interceptor.interceptHtmlContent(originalHtml)

assertEquals(originalHtml, modifiedHtml)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.payments.receipt.preview

import androidx.lifecycle.SavedStateHandle
import com.woocommerce.android.R
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.ui.payments.receipt.PaymentReceiptShare
import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Content
import com.woocommerce.android.ui.payments.receipt.preview.ReceiptPreviewViewModel.ReceiptPreviewViewState.Loading
Expand All @@ -18,13 +19,15 @@ import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.wordpress.android.fluxc.model.SiteModel

@ExperimentalCoroutinesApi
class ReceiptPreviewViewModelTest : BaseUnitTest() {
private lateinit var viewModel: ReceiptPreviewViewModel

private val paymentsFlowTracker: PaymentsFlowTracker = mock()
private val paymentReceiptShare: PaymentReceiptShare = mock()
private val selectedSite: SelectedSite = mock()

private val savedState: SavedStateHandle = ReceiptPreviewFragmentArgs(
receiptUrl = "testing url",
Expand All @@ -34,7 +37,12 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() {

@Before
fun setUp() {
viewModel = ReceiptPreviewViewModel(savedState, paymentsFlowTracker, paymentReceiptShare)
viewModel = ReceiptPreviewViewModel(
savedState,
paymentsFlowTracker,
paymentReceiptShare,
selectedSite
)
}

@Test
Expand Down Expand Up @@ -186,4 +194,32 @@ class ReceiptPreviewViewModelTest : BaseUnitTest() {

verify(paymentsFlowTracker).trackPrintReceiptSucceeded()
}

@Test
fun `given valid receipt domain, then isReceiptTrustable returns true`() =
testBlocking {
whenever(selectedSite.getIfExists()).thenReturn(
SiteModel().apply {
url = "https://www.woocommerce.com"
origin = SiteModel.ORIGIN_WPAPI
}
)
val receiptUrl = "https://www.woocommerce.com/receipt"

assertThat(viewModel.isReceiptDomainTrustable(receiptUrl)).isTrue()
}

@Test
fun `given invalid receipt domain, then isReceiptTrustable returns false`() =
testBlocking {
whenever(selectedSite.getIfExists()).thenReturn(
SiteModel().apply {
url = "https://www.woocommerce.com"
origin = SiteModel.ORIGIN_WPAPI
}
)
val receiptUrl = "https://www.wocommerce.com/receipt"

assertThat(viewModel.isReceiptDomainTrustable(receiptUrl)).isFalse()
}
}

0 comments on commit df4ac5a

Please sign in to comment.