From ebebf2fc061ac699e1e12145ee0078258815e338 Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Wed, 19 Jul 2023 10:20:35 -0400 Subject: [PATCH 01/14] Begin. --- .../wikipedia/page/PageFragmentLoadState.kt | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt index 24a8a5f7d0b..52551e6bede 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt @@ -146,31 +146,24 @@ class PageFragmentLoadState(private var model: PageViewModel, fragment.requireActivity().invalidateOptionsMenu() fragment.callback()?.onPageUpdateProgressBar(true) model.page = null - val delayLoadHtml = title.prefixedText.contains(":") - if (!delayLoadHtml) { - bridge.resetHtml(title) - } + + // kick off loading mobile-html contents into the WebView. + bridge.resetHtml(title) + if (title.namespace() === Namespace.SPECIAL) { // Short-circuit the entire process of fetching the Summary, since Special: pages // are not supported in RestBase. - bridge.resetHtml(title) leadImagesHandler.loadLeadImage() fragment.requireActivity().invalidateOptionsMenu() fragment.onPageMetadataLoaded() return } - disposables.add(Observable.zip(ServiceFactory.getRest(title.wikiSite) - .getSummaryResponse(title.prefixedText, null, model.cacheControl.toString(), - if (model.isInReadingList) OfflineCacheInterceptor.SAVE_HEADER_SAVE else null, - title.wikiSite.languageCode, UriUtil.encodeURL(title.prefixedText)), - if (app.isOnline && AccountUtil.isLoggedIn) ServiceFactory.get(title.wikiSite).getWatchedInfo(title.prefixedText) + disposables.add((if (app.isOnline && AccountUtil.isLoggedIn) ServiceFactory.get(title.wikiSite).getWatchedInfo(title.prefixedText) else if (app.isOnline && !AccountUtil.isLoggedIn) AnonymousNotificationHelper.observableForAnonUserInfo(title.wikiSite) - else Observable.just(MwQueryResponse())) { first, second -> Pair(first, second) } + else Observable.just(MwQueryResponse())) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ pair -> - val pageSummaryResponse = pair.first - val watchedResponse = pair.second + .subscribe({ watchedResponse -> val isWatched = watchedResponse.query?.firstPage()?.watched ?: false val hasWatchlistExpiry = watchedResponse.query?.firstPage()?.hasWatchlistExpiry() ?: false if (pageSummaryResponse.body() == null) { @@ -180,9 +173,6 @@ class PageFragmentLoadState(private var model: PageViewModel, if (OfflineCacheInterceptor.SAVE_HEADER_SAVE == pageSummaryResponse.headers()[OfflineCacheInterceptor.SAVE_HEADER]) { showPageOfflineMessage(pageSummaryResponse.headers().getInstant("date")) } - if (delayLoadHtml) { - bridge.resetHtml(title) - } fragment.onPageMetadataLoaded() if (AnonymousNotificationHelper.shouldCheckAnonNotifications(watchedResponse)) { From 57409998cb15a815e748cd9862cc714175f4eae1 Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Wed, 19 Jul 2023 13:23:15 -0400 Subject: [PATCH 02/14] Retrieve metadata from html. --- .../bridge/JavaScriptActionHandler.kt | 20 ++++++ .../java/org/wikipedia/page/PageFragment.kt | 66 ++++++++++++++++- .../wikipedia/page/PageFragmentLoadState.kt | 71 +++++++------------ 3 files changed, 110 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt index ccc052943b8..d2fc6e9b1dc 100644 --- a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt +++ b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt @@ -57,6 +57,26 @@ object JavaScriptActionHandler { return "pcs.c1.Page.getRevision();" } + fun requestMetadata(): String { + return "var metadata = {};" + + "metadata.revision = parseInt(pcs.c1.Page.getRevision());" + + "metadata.leadImage = pcs.c1.Page.getLeadImage();" + + "metadata.protection = pcs.c1.Page.getProtection();" + + "var m = document.head.querySelector('meta[property=\"mw:pageId\"]');" + + "if (m) metadata.pageId = parseInt(m.getAttribute('content'));" + + "m = document.head.querySelector('meta[property=\"mw:pageNamespace\"]');" + + "if (m) metadata.pageNamespace = parseInt(m.getAttribute('content'));" + + "m = document.head.querySelector('title');" + + "if (m) metadata.title = m.innerHTML;" + + "m = document.body.querySelector('#pcs-edit-section-title-description');" + + "if (m) {" + + " metadata.description = m.innerHTML;" + + " metadata.descriptionSource = m.getAttribute('data-description-source');" + + " metadata.wikibaseItem = m.getAttribute('data-wikdata-entity-id');" + + "}" + + "metadata;" + } + fun expandCollapsedTables(expand: Boolean): String { return "pcs.c1.Page.expandOrCollapseTables($expand);" + "var hideableSections = document.getElementsByClassName('pcs-section-hideable-header'); " + diff --git a/app/src/main/java/org/wikipedia/page/PageFragment.kt b/app/src/main/java/org/wikipedia/page/PageFragment.kt index e10f3baab06..5704bcf32ba 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragment.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragment.kt @@ -57,6 +57,7 @@ import org.wikipedia.dataclient.WikiSite import org.wikipedia.dataclient.mwapi.MwQueryPage import org.wikipedia.dataclient.okhttp.HttpStatusException import org.wikipedia.dataclient.okhttp.OkHttpWebViewClient +import org.wikipedia.dataclient.page.PageSummary import org.wikipedia.dataclient.watch.Watch import org.wikipedia.descriptions.DescriptionEditActivity import org.wikipedia.diff.ArticleEditDetailsActivity @@ -79,6 +80,7 @@ import org.wikipedia.page.references.PageReferences import org.wikipedia.page.references.ReferenceDialog import org.wikipedia.page.shareafact.ShareHandler import org.wikipedia.page.tabs.Tab +import org.wikipedia.pageimages.db.PageImage import org.wikipedia.readinglist.LongPressMenu import org.wikipedia.readinglist.ReadingListBehaviorsUtil import org.wikipedia.readinglist.database.ReadingListPage @@ -94,6 +96,7 @@ import org.wikipedia.views.ViewUtil import org.wikipedia.watchlist.WatchlistExpiry import org.wikipedia.watchlist.WatchlistExpiryDialog import org.wikipedia.wiktionary.WiktionaryDialog +import retrofit2.Response import java.util.* class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.CommunicationBridgeListener, ThemeChooserDialog.Callback, @@ -406,6 +409,16 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi } updateProgressBar(false) webView.visibility = View.VISIBLE + + bridge.evaluateImmediate(JavaScriptActionHandler.requestMetadata()) { + if (!isAdded) { + return@evaluateImmediate + } + val metadata = JsonUtil.decodeFromString(it) + L.d(">>>> " + metadata) + } + + bridge.evaluate(JavaScriptActionHandler.getRevision()) { value -> if (!isAdded || value == null || value == "null") { return@evaluate @@ -441,6 +454,55 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi } } + private fun createPageModel(response: Response) { + + val pageSummary = response.body() + val page = pageSummary?.toPage(model.title!!) + model.page = page + model.title = page?.title + model.title?.let { title -> + if (!response.raw().request.url.fragment.isNullOrEmpty()) { + title.fragment = response.raw().request.url.fragment + } + if (title.description.isNullOrEmpty()) { + app.appSessionEvent.noDescription() + } + if (!title.isMainPage) { + title.displayText = page?.displayTitle.orEmpty() + } + leadImagesHandler.loadLeadImage() + + //fragment.requireActivity().invalidateOptionsMenu() + + // Update our history entry, in case the Title was changed (i.e. normalized) + val curEntry = model.curEntry + curEntry?.let { + model.curEntry = HistoryEntry(title, it.source, timestamp = it.timestamp) + model.curEntry!!.referrer = it.referrer + } + + // Update our tab list to prevent ZH variants issue. + app.tabList.getOrNull(app.tabCount - 1)?.setBackStackPositionTitle(title) + + // Save the thumbnail URL to the DB + val pageImage = PageImage(title, pageSummary?.thumbnailUrl) + Completable.fromAction { AppDatabase.instance.pageImagesDao().insertPageImage(pageImage) }.subscribeOn(Schedulers.io()).subscribe() + title.thumbUrl = pageImage.imageName + } + } + + + + + + + + + + + + + private fun handleInternalLink(title: PageTitle) { if (!isResumed) { return @@ -722,7 +784,9 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi } } bridge.addListener("link", linkHandler) - bridge.addListener("setup") { _, _ -> onPageSetupEvent() } + bridge.addListener("setup") { _, _ -> + onPageSetupEvent() + } bridge.addListener("final_setup") { _, _ -> if (!isAdded) { return@addListener diff --git a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt index 52551e6bede..8146f120cec 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt @@ -9,6 +9,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable import org.wikipedia.R import org.wikipedia.WikipediaApp import org.wikipedia.auth.AccountUtil @@ -19,6 +20,7 @@ import org.wikipedia.dataclient.ServiceFactory import org.wikipedia.dataclient.mwapi.MwQueryResponse import org.wikipedia.dataclient.okhttp.OfflineCacheInterceptor import org.wikipedia.dataclient.page.PageSummary +import org.wikipedia.dataclient.page.Protection import org.wikipedia.history.HistoryEntry import org.wikipedia.notifications.AnonymousNotificationHelper import org.wikipedia.page.leadimages.LeadImagesHandler @@ -158,6 +160,7 @@ class PageFragmentLoadState(private var model: PageViewModel, fragment.onPageMetadataLoaded() return } + disposables.add((if (app.isOnline && AccountUtil.isLoggedIn) ServiceFactory.get(title.wikiSite).getWatchedInfo(title.prefixedText) else if (app.isOnline && !AccountUtil.isLoggedIn) AnonymousNotificationHelper.observableForAnonUserInfo(title.wikiSite) else Observable.just(MwQueryResponse())) @@ -166,13 +169,11 @@ class PageFragmentLoadState(private var model: PageViewModel, .subscribe({ watchedResponse -> val isWatched = watchedResponse.query?.firstPage()?.watched ?: false val hasWatchlistExpiry = watchedResponse.query?.firstPage()?.hasWatchlistExpiry() ?: false - if (pageSummaryResponse.body() == null) { - throw RuntimeException("Summary response was invalid.") - } - createPageModel(pageSummaryResponse, isWatched, hasWatchlistExpiry) - if (OfflineCacheInterceptor.SAVE_HEADER_SAVE == pageSummaryResponse.headers()[OfflineCacheInterceptor.SAVE_HEADER]) { - showPageOfflineMessage(pageSummaryResponse.headers().getInstant("date")) - } + + //createPageModel(pageSummaryResponse, isWatched, hasWatchlistExpiry) + //if (OfflineCacheInterceptor.SAVE_HEADER_SAVE == pageSummaryResponse.headers()[OfflineCacheInterceptor.SAVE_HEADER]) { + // showPageOfflineMessage(pageSummaryResponse.headers().getInstant("date")) + //} fragment.onPageMetadataLoaded() if (AnonymousNotificationHelper.shouldCheckAnonNotifications(watchedResponse)) { @@ -207,45 +208,23 @@ class PageFragmentLoadState(private var model: PageViewModel, Toast.LENGTH_LONG).show() } - private fun createPageModel(response: Response, - isWatched: Boolean, - hasWatchlistExpiry: Boolean) { - if (!fragment.isAdded || response.body() == null) { - return - } - val pageSummary = response.body() - val page = pageSummary?.toPage(model.title!!) - model.page = page - model.isWatched = isWatched - model.hasWatchlistExpiry = hasWatchlistExpiry - model.title = page?.title - model.title?.let { title -> - if (!response.raw().request.url.fragment.isNullOrEmpty()) { - title.fragment = response.raw().request.url.fragment - } - if (title.description.isNullOrEmpty()) { - app.appSessionEvent.noDescription() - } - if (!title.isMainPage) { - title.displayText = page?.displayTitle.orEmpty() - } - leadImagesHandler.loadLeadImage() - fragment.requireActivity().invalidateOptionsMenu() - - // Update our history entry, in case the Title was changed (i.e. normalized) - val curEntry = model.curEntry - curEntry?.let { - model.curEntry = HistoryEntry(title, it.source, timestamp = it.timestamp) - model.curEntry!!.referrer = it.referrer - } - - // Update our tab list to prevent ZH variants issue. - app.tabList.getOrNull(app.tabCount - 1)?.setBackStackPositionTitle(title) + @Serializable + class JsPageMetadata { + val pageId: Long = 0 + val ns: Int = 0 + val revision: Long = 0 + val title: String = "" + val description: String = "" + val descriptionSource: String = "" + val wikibaseItem = "" + val protection: Protection? = null + val leadImage: JsLeadImage? = null + } - // Save the thumbnail URL to the DB - val pageImage = PageImage(title, pageSummary?.thumbnailUrl) - Completable.fromAction { AppDatabase.instance.pageImagesDao().insertPageImage(pageImage) }.subscribeOn(Schedulers.io()).subscribe() - title.thumbUrl = pageImage.imageName - } + @Serializable + class JsLeadImage { + val source: String = "" + val width: Int = 0 + val height: Int = 0 } } From 178dcdce9093a025ba1ddf5b7327565e861718a0 Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Fri, 21 Jul 2023 12:05:24 -0400 Subject: [PATCH 03/14] Right. --- .../bridge/JavaScriptActionHandler.kt | 2 + .../java/org/wikipedia/page/PageFragment.kt | 108 ++++++++++-------- .../wikipedia/page/PageFragmentLoadState.kt | 3 +- .../java/org/wikipedia/page/PageProperties.kt | 22 ++-- .../page/leadimages/LeadImagesHandler.kt | 2 +- 5 files changed, 75 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt index d2fc6e9b1dc..9939b3cc1ba 100644 --- a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt +++ b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt @@ -66,6 +66,8 @@ object JavaScriptActionHandler { "if (m) metadata.pageId = parseInt(m.getAttribute('content'));" + "m = document.head.querySelector('meta[property=\"mw:pageNamespace\"]');" + "if (m) metadata.pageNamespace = parseInt(m.getAttribute('content'));" + + "m = document.head.querySelector('meta[property=\"dc:modified\"]');" + + "if (m) metadata.timeStamp = parseInt(m.getAttribute('content'));" + "m = document.head.querySelector('title');" + "if (m) metadata.title = m.innerHTML;" + "m = document.body.querySelector('#pcs-edit-section-title-description');" + diff --git a/app/src/main/java/org/wikipedia/page/PageFragment.kt b/app/src/main/java/org/wikipedia/page/PageFragment.kt index 5704bcf32ba..73864f91637 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragment.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragment.kt @@ -52,6 +52,7 @@ import org.wikipedia.database.AppDatabase import org.wikipedia.databinding.FragmentPageBinding import org.wikipedia.databinding.GroupFindReferencesInPageBinding import org.wikipedia.dataclient.RestService +import org.wikipedia.dataclient.Service import org.wikipedia.dataclient.ServiceFactory import org.wikipedia.dataclient.WikiSite import org.wikipedia.dataclient.mwapi.MwQueryPage @@ -163,7 +164,6 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi lateinit var sidePanelHandler: SidePanelHandler lateinit var shareHandler: ShareHandler lateinit var editHandler: EditHandler - var revision = 0L private val shouldCreateNewTab get() = currentTab.backStack.isNotEmpty() private val backgroundTabPosition get() = 0.coerceAtLeast(foregroundTabPosition - 1) @@ -414,21 +414,13 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi if (!isAdded) { return@evaluateImmediate } - val metadata = JsonUtil.decodeFromString(it) - L.d(">>>> " + metadata) - } - - - bridge.evaluate(JavaScriptActionHandler.getRevision()) { value -> - if (!isAdded || value == null || value == "null") { - return@evaluate - } - try { - revision = value.replace("\"", "").toLong() - } catch (e: Exception) { - L.e(e) + JsonUtil.decodeFromString(it)?.let { metadata -> + // compose a Page object from the metadata that was received. + L.d(">>>> " + metadata) + createPageModel(metadata) } } + bridge.evaluate(JavaScriptActionHandler.getSections()) { value -> if (!isAdded) { return@evaluate @@ -444,58 +436,77 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi sidePanelHandler.setEnabled(true) } } - bridge.evaluate(JavaScriptActionHandler.getProtection()) { value -> - if (!isAdded) { - return@evaluate - } - model.page?.let { page -> - page.pageProperties.protection = JsonUtil.decodeFromString(value) + } + + private fun createPageModel(metadata: PageFragmentLoadState.JsPageMetadata) { + if (model.title == null) { + return + } + var newTitle = model.title!! + if (metadata.title.isNotEmpty()) { + // TODO: handle redirected title? + newTitle = PageTitle(model.title!!.prefixedText, model.title!!.wikiSite, model.title!!.thumbUrl) + newTitle.displayText = metadata.title + newTitle.fragment = title!!.fragment + } + if (metadata.description.isNotEmpty()) { + newTitle.description = metadata.description + } + + model.title = newTitle + model.page = Page(newTitle, pageProperties = PageProperties(newTitle, newTitle.isMainPage)) + model.page?.pageProperties?.let { + it.pageId = metadata.pageId + it.namespace = Namespace.of(metadata.ns) + it.revisionId = metadata.revision + it.protection = metadata.protection + it.descriptionSource = metadata.descriptionSource + if (metadata.timeStamp.isNotEmpty()) { + it.lastModified = DateUtil.iso8601DateParse(metadata.timeStamp) } + it.displayTitle = metadata.title + it.isMainPage = model.title!!.isMainPage + it.wikiBaseItem = metadata.wikibaseItem + it.leadImageUrl = metadata.leadImage?.source + it.leadImageWidth = metadata.leadImage?.width ?: 0 + it.leadImageHeight = metadata.leadImage?.height ?: 0 + + //leadImageName = UriUtil.decodeURL(pageSummary.leadImageName.orEmpty()), + //geo = pageSummary.geo, } - } - private fun createPageModel(response: Response) { - val pageSummary = response.body() - val page = pageSummary?.toPage(model.title!!) - model.page = page - model.title = page?.title - model.title?.let { title -> - if (!response.raw().request.url.fragment.isNullOrEmpty()) { - title.fragment = response.raw().request.url.fragment - } - if (title.description.isNullOrEmpty()) { + + model.title?.let { + //if (!response.raw().request.url.fragment.isNullOrEmpty()) { + // it.fragment = response.raw().request.url.fragment + //} + if (it.description.isNullOrEmpty()) { app.appSessionEvent.noDescription() } - if (!title.isMainPage) { - title.displayText = page?.displayTitle.orEmpty() - } - leadImagesHandler.loadLeadImage() - - //fragment.requireActivity().invalidateOptionsMenu() + //if (!it.isMainPage) { + // it.displayText = page?.displayTitle.orEmpty() + //} // Update our history entry, in case the Title was changed (i.e. normalized) val curEntry = model.curEntry curEntry?.let { - model.curEntry = HistoryEntry(title, it.source, timestamp = it.timestamp) + model.curEntry = HistoryEntry(model.title!!, it.source, timestamp = it.timestamp) model.curEntry!!.referrer = it.referrer } // Update our tab list to prevent ZH variants issue. - app.tabList.getOrNull(app.tabCount - 1)?.setBackStackPositionTitle(title) + app.tabList.getOrNull(app.tabCount - 1)?.setBackStackPositionTitle(it) // Save the thumbnail URL to the DB - val pageImage = PageImage(title, pageSummary?.thumbnailUrl) + val pageImage = PageImage(it, ImageUrlUtil.getUrlForPreferredSize(page?.pageProperties?.leadImageUrl.orEmpty(), Service.PREFERRED_THUMB_SIZE)) Completable.fromAction { AppDatabase.instance.pageImagesDao().insertPageImage(pageImage) }.subscribeOn(Schedulers.io()).subscribe() - title.thumbUrl = pageImage.imageName + it.thumbUrl = pageImage.imageName } - } - - - - - + leadImagesHandler.loadLeadImage() + requireActivity().invalidateOptionsMenu() + } @@ -668,7 +679,7 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi return@post } model.title?.let { - callback()?.onPageRequestGallery(it, fileName, it.wikiSite, revision, GalleryActivity.SOURCE_NON_LEAD_IMAGE, options) + callback()?.onPageRequestGallery(it, fileName, it.wikiSite, model.page?.pageProperties?.revisionId ?: 0, GalleryActivity.SOURCE_NON_LEAD_IMAGE, options) } } } @@ -1050,7 +1061,6 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi updateProgressBar(true) pageRefreshed = isRefresh references = null - revision = 0 pageFragmentLoadState.load(pushBackStack) scrollTriggerListener.stagedScrollY = stagedScrollY } diff --git a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt index 8146f120cec..a22f14bc4e3 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt @@ -210,10 +210,11 @@ class PageFragmentLoadState(private var model: PageViewModel, @Serializable class JsPageMetadata { - val pageId: Long = 0 + val pageId: Int = 0 val ns: Int = 0 val revision: Long = 0 val title: String = "" + val timeStamp: String = "" val description: String = "" val descriptionSource: String = "" val wikibaseItem = "" diff --git a/app/src/main/java/org/wikipedia/page/PageProperties.kt b/app/src/main/java/org/wikipedia/page/PageProperties.kt index f191cf6ea3a..c5bc9aecaa2 100644 --- a/app/src/main/java/org/wikipedia/page/PageProperties.kt +++ b/app/src/main/java/org/wikipedia/page/PageProperties.kt @@ -18,21 +18,21 @@ import java.util.* @Parcelize @TypeParceler() data class PageProperties constructor( - val pageId: Int = 0, - val namespace: Namespace, - val revisionId: Long = 0, - val lastModified: Date = Date(), - val displayTitle: String = "", + var pageId: Int = 0, + var namespace: Namespace, + var revisionId: Long = 0, + var lastModified: Date = Date(), + var displayTitle: String = "", private var editProtectionStatus: String = "", - val isMainPage: Boolean = false, + var isMainPage: Boolean = false, /** Nullable URL with no scheme. For example, foo.bar.com/ instead of http://foo.bar.com/. */ - val leadImageUrl: String? = null, + var leadImageUrl: String? = null, val leadImageName: String? = null, - val leadImageWidth: Int = 0, - val leadImageHeight: Int = 0, + var leadImageWidth: Int = 0, + var leadImageHeight: Int = 0, val geo: Location? = null, - val wikiBaseItem: String? = null, - val descriptionSource: String? = null, + var wikiBaseItem: String? = null, + var descriptionSource: String? = null, // FIXME: This is not a true page property, since it depends on current user. var canEdit: Boolean = false ) : Parcelable { diff --git a/app/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.kt b/app/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.kt index 5989a61b5de..122d0d48cde 100644 --- a/app/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.kt +++ b/app/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.kt @@ -232,7 +232,7 @@ class LeadImagesHandler(private val parentFragment: PageFragment, leadImageUrl!!, true) GalleryActivity.setTransitionInfo(hitInfo) val options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, pageHeaderView.imageView, activity.getString(R.string.transition_page_gallery)) - callback?.onPageRequestGallery(it, filename, wiki, parentFragment.revision, GalleryActivity.SOURCE_LEAD_IMAGE, options) + callback?.onPageRequestGallery(it, filename, wiki, page?.pageProperties?.revisionId ?: 0, GalleryActivity.SOURCE_LEAD_IMAGE, options) } } } From f0a81c88886d6f575267644a7c71621fc0a8dcc2 Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Mon, 24 Jul 2023 11:16:08 -0400 Subject: [PATCH 04/14] Keep going. --- .../bridge/JavaScriptActionHandler.kt | 2 +- .../java/org/wikipedia/page/PageFragment.kt | 18 ++++++------------ .../wikipedia/page/PageFragmentLoadState.kt | 2 +- .../java/org/wikipedia/page/PageProperties.kt | 2 +- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt index 9939b3cc1ba..36d8a2cfc55 100644 --- a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt +++ b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt @@ -67,7 +67,7 @@ object JavaScriptActionHandler { "m = document.head.querySelector('meta[property=\"mw:pageNamespace\"]');" + "if (m) metadata.pageNamespace = parseInt(m.getAttribute('content'));" + "m = document.head.querySelector('meta[property=\"dc:modified\"]');" + - "if (m) metadata.timeStamp = parseInt(m.getAttribute('content'));" + + "if (m) metadata.timeStamp = m.getAttribute('content');" + "m = document.head.querySelector('title');" + "if (m) metadata.title = m.innerHTML;" + "m = document.body.querySelector('#pcs-edit-section-title-description');" + diff --git a/app/src/main/java/org/wikipedia/page/PageFragment.kt b/app/src/main/java/org/wikipedia/page/PageFragment.kt index 73864f91637..434b80bcdc3 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragment.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragment.kt @@ -418,6 +418,7 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi // compose a Page object from the metadata that was received. L.d(">>>> " + metadata) createPageModel(metadata) + onPageMetadataLoaded() } } @@ -468,31 +469,24 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi it.isMainPage = model.title!!.isMainPage it.wikiBaseItem = metadata.wikibaseItem it.leadImageUrl = metadata.leadImage?.source + it.leadImageName = UriUtil.getFilenameFromUploadUrl(it.leadImageUrl.orEmpty()) it.leadImageWidth = metadata.leadImage?.width ?: 0 it.leadImageHeight = metadata.leadImage?.height ?: 0 - //leadImageName = UriUtil.decodeURL(pageSummary.leadImageName.orEmpty()), + // TODO: //geo = pageSummary.geo, } - - model.title?.let { - //if (!response.raw().request.url.fragment.isNullOrEmpty()) { - // it.fragment = response.raw().request.url.fragment - //} if (it.description.isNullOrEmpty()) { app.appSessionEvent.noDescription() } - //if (!it.isMainPage) { - // it.displayText = page?.displayTitle.orEmpty() - //} // Update our history entry, in case the Title was changed (i.e. normalized) val curEntry = model.curEntry - curEntry?.let { - model.curEntry = HistoryEntry(model.title!!, it.source, timestamp = it.timestamp) - model.curEntry!!.referrer = it.referrer + curEntry?.let { entry -> + model.curEntry = HistoryEntry(model.title!!, entry.source, timestamp = entry.timestamp) + model.curEntry!!.referrer = entry.referrer } // Update our tab list to prevent ZH variants issue. diff --git a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt index a22f14bc4e3..bc049de7652 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt @@ -174,7 +174,7 @@ class PageFragmentLoadState(private var model: PageViewModel, //if (OfflineCacheInterceptor.SAVE_HEADER_SAVE == pageSummaryResponse.headers()[OfflineCacheInterceptor.SAVE_HEADER]) { // showPageOfflineMessage(pageSummaryResponse.headers().getInstant("date")) //} - fragment.onPageMetadataLoaded() + // fragment.onPageMetadataLoaded() if (AnonymousNotificationHelper.shouldCheckAnonNotifications(watchedResponse)) { checkAnonNotifications(title) diff --git a/app/src/main/java/org/wikipedia/page/PageProperties.kt b/app/src/main/java/org/wikipedia/page/PageProperties.kt index c5bc9aecaa2..2eea697409a 100644 --- a/app/src/main/java/org/wikipedia/page/PageProperties.kt +++ b/app/src/main/java/org/wikipedia/page/PageProperties.kt @@ -27,7 +27,7 @@ data class PageProperties constructor( var isMainPage: Boolean = false, /** Nullable URL with no scheme. For example, foo.bar.com/ instead of http://foo.bar.com/. */ var leadImageUrl: String? = null, - val leadImageName: String? = null, + var leadImageName: String? = null, var leadImageWidth: Int = 0, var leadImageHeight: Int = 0, val geo: Location? = null, From c974feac423a8490d15d1fe8bbc3db5f4fbe5435 Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Mon, 24 Jul 2023 15:23:52 -0400 Subject: [PATCH 05/14] Further untangle. --- .../bridge/JavaScriptActionHandler.kt | 2 +- .../java/org/wikipedia/page/PageFragment.kt | 25 +++---- .../wikipedia/page/PageFragmentLoadState.kt | 65 +++++-------------- .../java/org/wikipedia/util/ImageUrlUtil.kt | 9 +++ 4 files changed, 36 insertions(+), 65 deletions(-) diff --git a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt index 36d8a2cfc55..0dd151af9b2 100644 --- a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt +++ b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt @@ -153,7 +153,7 @@ object JavaScriptActionHandler { return "" } val showTalkLink = !(model.page!!.title.namespace() === Namespace.TALK) - val showMapLink = model.page!!.pageProperties.geo != null + val showMapLink = true //model.page!!.pageProperties.geo != null val editedDaysAgo = TimeUnit.MILLISECONDS.toDays(Date().time - model.page!!.pageProperties.lastModified.time) // TODO: page-library also supports showing disambiguation ("similar pages") links and diff --git a/app/src/main/java/org/wikipedia/page/PageFragment.kt b/app/src/main/java/org/wikipedia/page/PageFragment.kt index 434b80bcdc3..cbb5e908bc1 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragment.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragment.kt @@ -58,7 +58,6 @@ import org.wikipedia.dataclient.WikiSite import org.wikipedia.dataclient.mwapi.MwQueryPage import org.wikipedia.dataclient.okhttp.HttpStatusException import org.wikipedia.dataclient.okhttp.OkHttpWebViewClient -import org.wikipedia.dataclient.page.PageSummary import org.wikipedia.dataclient.watch.Watch import org.wikipedia.descriptions.DescriptionEditActivity import org.wikipedia.diff.ArticleEditDetailsActivity @@ -97,7 +96,6 @@ import org.wikipedia.views.ViewUtil import org.wikipedia.watchlist.WatchlistExpiry import org.wikipedia.watchlist.WatchlistExpiryDialog import org.wikipedia.wiktionary.WiktionaryDialog -import retrofit2.Response import java.util.* class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.CommunicationBridgeListener, ThemeChooserDialog.Callback, @@ -247,6 +245,7 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi sidePanelHandler.log() leadImagesHandler.dispose() disposables.clear() + pageFragmentLoadState.disposables.clear() webView.clearAllListeners() (webView.parent as ViewGroup).removeView(webView) Prefs.isSuggestedEditsHighestPriorityEnabled = false @@ -385,6 +384,8 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi bridge.onPcsReady() bridge.execute(JavaScriptActionHandler.mobileWebChromeShim()) } + + onPageMetadataLoaded() } } @@ -418,7 +419,6 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi // compose a Page object from the metadata that was received. L.d(">>>> " + metadata) createPageModel(metadata) - onPageMetadataLoaded() } } @@ -468,7 +468,7 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi it.displayTitle = metadata.title it.isMainPage = model.title!!.isMainPage it.wikiBaseItem = metadata.wikibaseItem - it.leadImageUrl = metadata.leadImage?.source + it.leadImageUrl = ImageUrlUtil.getThumbUrlFromCommonsUrl(metadata.leadImage?.source.orEmpty(), DimenUtil.calculateLeadImageWidth()) it.leadImageName = UriUtil.getFilenameFromUploadUrl(it.leadImageUrl.orEmpty()) it.leadImageWidth = metadata.leadImage?.width ?: 0 it.leadImageHeight = metadata.leadImage?.height ?: 0 @@ -502,12 +502,6 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi requireActivity().invalidateOptionsMenu() } - - - - - - private fun handleInternalLink(title: PageTitle) { if (!isResumed) { return @@ -944,7 +938,7 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi } fun onPageMetadataLoaded() { - updateQuickActionsAndMenuOptions() + updateBookmarkAndMenuOptionsFromDao() if (model.page == null) { return } @@ -957,12 +951,11 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi requireActivity().invalidateOptionsMenu() model.readingListPage?.let { page -> model.title?.let { title -> - disposables.add(Completable.fromAction { - page.thumbUrl.equals(title.thumbUrl, true) - if (!page.thumbUrl.equals(title.thumbUrl, true) || !page.description.equals(title.description, true)) { + if (!page.thumbUrl.equals(title.thumbUrl, true) || !page.description.equals(title.description, true)) { + disposables.add(Completable.fromAction { AppDatabase.instance.readingListPageDao().updateMetadataByTitle(page, title.description, title.thumbUrl) - } - }.subscribeOn(Schedulers.io()).subscribe()) + }.subscribeOn(Schedulers.io()).subscribe()) + } } } if (!errorState) { diff --git a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt index bc049de7652..49a7f153391 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt @@ -2,7 +2,6 @@ package org.wikipedia.page import android.widget.Toast import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.schedulers.Schedulers @@ -15,24 +14,17 @@ import org.wikipedia.WikipediaApp import org.wikipedia.auth.AccountUtil import org.wikipedia.bridge.CommunicationBridge import org.wikipedia.bridge.JavaScriptActionHandler -import org.wikipedia.database.AppDatabase import org.wikipedia.dataclient.ServiceFactory import org.wikipedia.dataclient.mwapi.MwQueryResponse -import org.wikipedia.dataclient.okhttp.OfflineCacheInterceptor -import org.wikipedia.dataclient.page.PageSummary import org.wikipedia.dataclient.page.Protection -import org.wikipedia.history.HistoryEntry import org.wikipedia.notifications.AnonymousNotificationHelper import org.wikipedia.page.leadimages.LeadImagesHandler import org.wikipedia.page.tabs.Tab -import org.wikipedia.pageimages.db.PageImage import org.wikipedia.settings.Prefs import org.wikipedia.staticdata.UserTalkAliasData import org.wikipedia.util.DateUtil -import org.wikipedia.util.UriUtil import org.wikipedia.util.log.L import org.wikipedia.views.ObservableWebView -import retrofit2.Response import java.time.Instant import java.time.LocalDateTime import java.time.ZoneId @@ -44,13 +36,8 @@ class PageFragmentLoadState(private var model: PageViewModel, private var leadImagesHandler: LeadImagesHandler, private var currentTab: Tab) { - private fun interface ErrorCallback { - fun call(error: Throwable) - } - - private var networkErrorCallback: ErrorCallback? = null private val app = WikipediaApp.instance - private val disposables = CompositeDisposable() + val disposables = CompositeDisposable() fun load(pushBackStack: Boolean) { if (pushBackStack && model.title != null && model.curEntry != null) { @@ -58,7 +45,13 @@ class PageFragmentLoadState(private var model: PageViewModel, updateCurrentBackStackItem() currentTab.pushBackStackItem(PageBackStackItem(model.title!!, model.curEntry!!)) } - pageLoadCheckReadingLists() + // clear any remaining disposables from the previous page load. + disposables.clear() + + // point of no return: null out the current page object. + model.page = null + model.readingListPage = null + pageLoadFromNetwork() } fun loadFromBackStack(isRefresh: Boolean = false) { @@ -117,37 +110,11 @@ class PageFragmentLoadState(private var model: PageViewModel, bridge.execute(JavaScriptActionHandler.setTopMargin(leadImagesHandler.topMargin)) } - private fun commonSectionFetchOnCatch(caught: Throwable) { - if (!fragment.isAdded) { - return - } - val callback = networkErrorCallback - networkErrorCallback = null - fragment.requireActivity().invalidateOptionsMenu() - callback?.call(caught) - } - - private fun pageLoadCheckReadingLists() { - model.title?.let { - disposables.clear() - disposables.add(Completable.fromAction { model.readingListPage = AppDatabase.instance.readingListPageDao().findPageInAnyList(it) } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doAfterTerminate { pageLoadFromNetwork { fragment.onPageLoadError(it) } } - .subscribe()) - } - } - - private fun pageLoadFromNetwork(errorCallback: ErrorCallback) { + private fun pageLoadFromNetwork() { model.title?.let { title -> fragment.updateQuickActionsAndMenuOptions() - networkErrorCallback = errorCallback - if (!fragment.isAdded) { - return - } fragment.requireActivity().invalidateOptionsMenu() fragment.callback()?.onPageUpdateProgressBar(true) - model.page = null // kick off loading mobile-html contents into the WebView. bridge.resetHtml(title) @@ -167,21 +134,23 @@ class PageFragmentLoadState(private var model: PageViewModel, .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ watchedResponse -> - val isWatched = watchedResponse.query?.firstPage()?.watched ?: false - val hasWatchlistExpiry = watchedResponse.query?.firstPage()?.hasWatchlistExpiry() ?: false + model.isWatched = watchedResponse.query?.firstPage()?.watched ?: false + model.hasWatchlistExpiry = watchedResponse.query?.firstPage()?.hasWatchlistExpiry() ?: false + + fragment.updateQuickActionsAndMenuOptions() + fragment.requireActivity().invalidateOptionsMenu() - //createPageModel(pageSummaryResponse, isWatched, hasWatchlistExpiry) //if (OfflineCacheInterceptor.SAVE_HEADER_SAVE == pageSummaryResponse.headers()[OfflineCacheInterceptor.SAVE_HEADER]) { // showPageOfflineMessage(pageSummaryResponse.headers().getInstant("date")) //} - // fragment.onPageMetadataLoaded() if (AnonymousNotificationHelper.shouldCheckAnonNotifications(watchedResponse)) { checkAnonNotifications(title) } }) { - L.e("Page details network response error: ", it) - commonSectionFetchOnCatch(it) + L.e(it) + fragment.requireActivity().invalidateOptionsMenu() + fragment.onPageLoadError(it) } ) } diff --git a/app/src/main/java/org/wikipedia/util/ImageUrlUtil.kt b/app/src/main/java/org/wikipedia/util/ImageUrlUtil.kt index 35638fb97a0..036615d4681 100644 --- a/app/src/main/java/org/wikipedia/util/ImageUrlUtil.kt +++ b/app/src/main/java/org/wikipedia/util/ImageUrlUtil.kt @@ -12,4 +12,13 @@ object ImageUrlUtil { original } } + + fun getThumbUrlFromCommonsUrl(url: String, size: Int): String { + val fileName = UriUtil.getFilenameFromUploadUrl(url) + if (!url.contains("/wikipedia/commons/") || url.contains("/wikipedia/commons/thumb/")) { + return url + } + return url.replace("/wikipedia/commons/", "/wikipedia/commons/thumb/") + + "/" + size + "px-" + fileName + } } From 5b0918dc9121ff251390c88d7cab85c9b0efe2a3 Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Mon, 24 Jul 2023 16:21:18 -0400 Subject: [PATCH 06/14] Correctly show offline toast. --- .../dataclient/okhttp/OkHttpWebViewClient.kt | 11 +- .../java/org/wikipedia/page/PageFragment.kt | 119 ++++++++++-------- .../wikipedia/page/PageFragmentLoadState.kt | 24 ---- 3 files changed, 77 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt b/app/src/main/java/org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt index f3953604c11..a12c7e219dc 100644 --- a/app/src/main/java/org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt +++ b/app/src/main/java/org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt @@ -27,6 +27,8 @@ abstract class OkHttpWebViewClient : WebViewClient() { abstract val model: PageViewModel abstract val linkHandler: LinkHandler + var lastPageHtmlResponseHeaders: Headers? = null + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { if (model.shouldLoadAsMobileWeb) { // If the page was loaded as Mobile Web, then pass all link clicks through @@ -46,12 +48,12 @@ abstract class OkHttpWebViewClient : WebViewClient() { } var response: WebResourceResponse try { - val shouldLogLatency = request.url.encodedPath?.contains(RestService.PAGE_HTML_ENDPOINT) == true - if (shouldLogLatency) { + val isPageHtmlCall = request.url.encodedPath?.contains(RestService.PAGE_HTML_ENDPOINT) == true + if (isPageHtmlCall) { WikipediaApp.instance.appSessionEvent.pageFetchStart() } val rsp = request(request) - if (rsp.networkResponse != null && shouldLogLatency) { + if (rsp.networkResponse != null && isPageHtmlCall) { WikipediaApp.instance.appSessionEvent.pageFetchEnd() } response = if (CONTENT_TYPE_OGG == rsp.header(HEADER_CONTENT_TYPE) || @@ -59,6 +61,9 @@ abstract class OkHttpWebViewClient : WebViewClient() { rsp.close() return super.shouldInterceptRequest(view, request) } else { + if (isPageHtmlCall) { + lastPageHtmlResponseHeaders = rsp.headers + } // noinspection ConstantConditions WebResourceResponse(rsp.body!!.contentType()!!.type + "/" + rsp.body!!.contentType()!!.subtype, rsp.body!!.contentType()!!.charset(Charset.defaultCharset())!!.name(), diff --git a/app/src/main/java/org/wikipedia/page/PageFragment.kt b/app/src/main/java/org/wikipedia/page/PageFragment.kt index cbb5e908bc1..0cefb84dc44 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragment.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragment.kt @@ -12,6 +12,7 @@ import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView import android.widget.LinearLayout +import android.widget.Toast import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.animation.doOnEnd import androidx.core.app.ActivityCompat @@ -57,6 +58,7 @@ import org.wikipedia.dataclient.ServiceFactory import org.wikipedia.dataclient.WikiSite import org.wikipedia.dataclient.mwapi.MwQueryPage import org.wikipedia.dataclient.okhttp.HttpStatusException +import org.wikipedia.dataclient.okhttp.OfflineCacheInterceptor import org.wikipedia.dataclient.okhttp.OkHttpWebViewClient import org.wikipedia.dataclient.watch.Watch import org.wikipedia.descriptions.DescriptionEditActivity @@ -96,6 +98,9 @@ import org.wikipedia.views.ViewUtil import org.wikipedia.watchlist.WatchlistExpiry import org.wikipedia.watchlist.WatchlistExpiryDialog import org.wikipedia.wiktionary.WiktionaryDialog +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId import java.util.* class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.CommunicationBridgeListener, ThemeChooserDialog.Callback, @@ -134,6 +139,7 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi private val scrollTriggerListener = WebViewScrollTriggerListener() private val pageRefreshListener = OnRefreshListener { refreshPage() } private val pageActionItemCallback = PageActionItemCallback() + private val pageWebViewClient = PageWebViewClient() private lateinit var bridge: CommunicationBridge private lateinit var leadImagesHandler: LeadImagesHandler @@ -177,8 +183,16 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentPageBinding.inflate(inflater, container, false) + webView = binding.pageWebView - initWebViewListeners() + webView.addOnUpOrCancelMotionEventListener { + // update our session, since it's possible for the user to remain on the page for + // a long time, and we wouldn't want the session to time out. + app.appSessionEvent.touchSession() + } + webView.addOnContentHeightChangedListener(scrollTriggerListener) + webView.webViewClient = pageWebViewClient + binding.pageRefreshContainer.setColorSchemeResources(ResourceUtil.getThemedAttributeId(requireContext(), R.attr.progressive_color)) binding.pageRefreshContainer.scrollableChild = webView binding.pageRefreshContainer.setOnRefreshListener(pageRefreshListener) @@ -355,55 +369,6 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi activity.intent.hasExtra(Constants.INTENT_APP_SHORTCUT_CONTINUE_READING))) } - private fun initWebViewListeners() { - webView.addOnUpOrCancelMotionEventListener { - // update our session, since it's possible for the user to remain on the page for - // a long time, and we wouldn't want the session to time out. - app.appSessionEvent.touchSession() - } - webView.addOnContentHeightChangedListener(scrollTriggerListener) - webView.webViewClient = object : OkHttpWebViewClient() { - - override val model get() = this@PageFragment.model - - override val linkHandler get() = this@PageFragment.linkHandler - - override fun onPageFinished(view: WebView, url: String) { - bridge.evaluateImmediate("(function() { return (typeof pcs !== 'undefined'); })();") { pcsExists -> - if (!isAdded) { - return@evaluateImmediate - } - // TODO: This is a bit of a hack: If PCS does not exist in the current page, then - // it's implied that this page was loaded via Mobile Web (e.g. the Main Page) and - // doesn't support PCS, meaning that we will never receive the `setup` event that - // tells us the page is finished loading. In such a case, we must infer that the - // page has now loaded and trigger the remaining logic ourselves. - if ("true" != pcsExists) { - onPageSetupEvent() - bridge.onMetadataReady() - bridge.onPcsReady() - bridge.execute(JavaScriptActionHandler.mobileWebChromeShim()) - } - - onPageMetadataLoaded() - } - } - - override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { - onPageLoadError(RuntimeException(description)) - } - - override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) { - if (!request.url.toString().contains(RestService.PAGE_HTML_ENDPOINT)) { - // If the request is anything except the main mobile-html content request, then - // don't worry about any errors and let the WebView deal with it. - return - } - onPageLoadError(HttpStatusException(errorResponse.statusCode, request.url.toString(), UriUtil.decodeURL(errorResponse.reasonPhrase))) - } - } - } - private fun onPageSetupEvent() { if (!isAdded) { return @@ -962,6 +927,11 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi editHandler.setPage(model.page) webView.visibility = View.VISIBLE } + + if (OfflineCacheInterceptor.SAVE_HEADER_SAVE == pageWebViewClient.lastPageHtmlResponseHeaders?.get(OfflineCacheInterceptor.SAVE_HEADER)) { + showPageOfflineMessage(pageWebViewClient.lastPageHtmlResponseHeaders?.getInstant("date")) + } + maybeShowAnnouncement() bridge.onMetadataReady() // Explicitly set the top margin (even though it might have already been set in the setup @@ -1326,6 +1296,16 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi requireActivity().finish() } + private fun showPageOfflineMessage(dateHeader: Instant?) { + if (!isAdded || dateHeader == null) { + return + } + val localDate = LocalDateTime.ofInstant(dateHeader, ZoneId.systemDefault()).toLocalDate() + val dateStr = DateUtil.getShortDateString(localDate) + Toast.makeText(requireContext().applicationContext, + getString(R.string.page_offline_notice_last_date, dateStr), Toast.LENGTH_LONG).show() + } + private inner class AvCallback : AvPlayer.Callback { override fun onSuccess() { avPlayer?.stop() @@ -1542,6 +1522,45 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi } } + inner class PageWebViewClient : OkHttpWebViewClient() { + override val model get() = this@PageFragment.model + override val linkHandler get() = this@PageFragment.linkHandler + + override fun onPageFinished(view: WebView, url: String) { + bridge.evaluateImmediate("(function() { return (typeof pcs !== 'undefined'); })();") { pcsExists -> + if (!isAdded) { + return@evaluateImmediate + } + // TODO: This is a bit of a hack: If PCS does not exist in the current page, then + // it's implied that this page was loaded via Mobile Web (e.g. the Main Page) and + // doesn't support PCS, meaning that we will never receive the `setup` event that + // tells us the page is finished loading. In such a case, we must infer that the + // page has now loaded and trigger the remaining logic ourselves. + if ("true" != pcsExists) { + onPageSetupEvent() + bridge.onMetadataReady() + bridge.onPcsReady() + bridge.execute(JavaScriptActionHandler.mobileWebChromeShim()) + } + + onPageMetadataLoaded() + } + } + + override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { + onPageLoadError(RuntimeException(description)) + } + + override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) { + if (!request.url.toString().contains(RestService.PAGE_HTML_ENDPOINT)) { + // If the request is anything except the main mobile-html content request, then + // don't worry about any errors and let the WebView deal with it. + return + } + onPageLoadError(HttpStatusException(errorResponse.statusCode, request.url.toString(), UriUtil.decodeURL(errorResponse.reasonPhrase))) + } + } + companion object { private const val ARG_THEME_CHANGE_SCROLLED = "themeChangeScrolled" private val REFRESH_SPINNER_ADDITIONAL_OFFSET = (16 * DimenUtil.densityScalar).toInt() diff --git a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt index 49a7f153391..db05408a4bb 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt @@ -1,6 +1,5 @@ package org.wikipedia.page -import android.widget.Toast import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -9,7 +8,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.serialization.Serializable -import org.wikipedia.R import org.wikipedia.WikipediaApp import org.wikipedia.auth.AccountUtil import org.wikipedia.bridge.CommunicationBridge @@ -22,12 +20,8 @@ import org.wikipedia.page.leadimages.LeadImagesHandler import org.wikipedia.page.tabs.Tab import org.wikipedia.settings.Prefs import org.wikipedia.staticdata.UserTalkAliasData -import org.wikipedia.util.DateUtil import org.wikipedia.util.log.L import org.wikipedia.views.ObservableWebView -import java.time.Instant -import java.time.LocalDateTime -import java.time.ZoneId class PageFragmentLoadState(private var model: PageViewModel, private var fragment: PageFragment, @@ -140,17 +134,11 @@ class PageFragmentLoadState(private var model: PageViewModel, fragment.updateQuickActionsAndMenuOptions() fragment.requireActivity().invalidateOptionsMenu() - //if (OfflineCacheInterceptor.SAVE_HEADER_SAVE == pageSummaryResponse.headers()[OfflineCacheInterceptor.SAVE_HEADER]) { - // showPageOfflineMessage(pageSummaryResponse.headers().getInstant("date")) - //} - if (AnonymousNotificationHelper.shouldCheckAnonNotifications(watchedResponse)) { checkAnonNotifications(title) } }) { L.e(it) - fragment.requireActivity().invalidateOptionsMenu() - fragment.onPageLoadError(it) } ) } @@ -165,18 +153,6 @@ class PageFragmentLoadState(private var model: PageViewModel, } } - private fun showPageOfflineMessage(dateHeader: Instant?) { - if (!fragment.isAdded || dateHeader == null) { - return - } - // TODO: Use LocalDate.ofInstant() instead once it is available in SDK 34. - val localDate = LocalDateTime.ofInstant(dateHeader, ZoneId.systemDefault()).toLocalDate() - val dateStr = DateUtil.getShortDateString(localDate) - Toast.makeText(fragment.requireContext().applicationContext, - fragment.getString(R.string.page_offline_notice_last_date, dateStr), - Toast.LENGTH_LONG).show() - } - @Serializable class JsPageMetadata { val pageId: Int = 0 From 884be6ff1d5f464667249625f1f6ef04b30a3e1b Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Tue, 25 Jul 2023 07:53:07 -0400 Subject: [PATCH 07/14] Add redirect message. --- .../org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt | 2 ++ app/src/main/java/org/wikipedia/page/PageFragment.kt | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/app/src/main/java/org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt b/app/src/main/java/org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt index a12c7e219dc..260dbbfab6d 100644 --- a/app/src/main/java/org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt +++ b/app/src/main/java/org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt @@ -28,6 +28,7 @@ abstract class OkHttpWebViewClient : WebViewClient() { abstract val linkHandler: LinkHandler var lastPageHtmlResponseHeaders: Headers? = null + var lastPageHtmlWasRedirect = false override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { if (model.shouldLoadAsMobileWeb) { @@ -63,6 +64,7 @@ abstract class OkHttpWebViewClient : WebViewClient() { } else { if (isPageHtmlCall) { lastPageHtmlResponseHeaders = rsp.headers + lastPageHtmlWasRedirect = rsp.priorResponse?.isRedirect == true } // noinspection ConstantConditions WebResourceResponse(rsp.body!!.contentType()!!.type + "/" + rsp.body!!.contentType()!!.subtype, diff --git a/app/src/main/java/org/wikipedia/page/PageFragment.kt b/app/src/main/java/org/wikipedia/page/PageFragment.kt index 0cefb84dc44..63dcdc2cca7 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragment.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragment.kt @@ -408,6 +408,11 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi if (model.title == null) { return } + + if (pageWebViewClient.lastPageHtmlWasRedirect) { + L.d(">>>>>>>> Redirected from " + model.title?.displayText) + } + var newTitle = model.title!! if (metadata.title.isNotEmpty()) { // TODO: handle redirected title? From d28569af7347f4da3e0fdad852e0f80ade81d618 Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Tue, 25 Jul 2023 08:57:42 -0400 Subject: [PATCH 08/14] Correctly show messages. --- .../org/wikipedia/bridge/JavaScriptActionHandler.kt | 2 +- .../dataclient/okhttp/OkHttpWebViewClient.kt | 8 ++++++-- .../main/java/org/wikipedia/page/PageFragment.kt | 13 ++++++------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt index 0dd151af9b2..36d8a2cfc55 100644 --- a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt +++ b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt @@ -153,7 +153,7 @@ object JavaScriptActionHandler { return "" } val showTalkLink = !(model.page!!.title.namespace() === Namespace.TALK) - val showMapLink = true //model.page!!.pageProperties.geo != null + val showMapLink = model.page!!.pageProperties.geo != null val editedDaysAgo = TimeUnit.MILLISECONDS.toDays(Date().time - model.page!!.pageProperties.lastModified.time) // TODO: page-library also supports showing disambiguation ("similar pages") links and diff --git a/app/src/main/java/org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt b/app/src/main/java/org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt index 260dbbfab6d..745b23a4a2f 100644 --- a/app/src/main/java/org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt +++ b/app/src/main/java/org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt @@ -17,6 +17,7 @@ import org.wikipedia.util.log.L import java.io.IOException import java.io.InputStream import java.nio.charset.Charset +import java.time.Instant abstract class OkHttpWebViewClient : WebViewClient() { /* @@ -27,7 +28,7 @@ abstract class OkHttpWebViewClient : WebViewClient() { abstract val model: PageViewModel abstract val linkHandler: LinkHandler - var lastPageHtmlResponseHeaders: Headers? = null + var lastPageHtmlOfflineDate: Instant? = null var lastPageHtmlWasRedirect = false override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { @@ -52,6 +53,7 @@ abstract class OkHttpWebViewClient : WebViewClient() { val isPageHtmlCall = request.url.encodedPath?.contains(RestService.PAGE_HTML_ENDPOINT) == true if (isPageHtmlCall) { WikipediaApp.instance.appSessionEvent.pageFetchStart() + lastPageHtmlOfflineDate = null } val rsp = request(request) if (rsp.networkResponse != null && isPageHtmlCall) { @@ -63,8 +65,10 @@ abstract class OkHttpWebViewClient : WebViewClient() { return super.shouldInterceptRequest(view, request) } else { if (isPageHtmlCall) { - lastPageHtmlResponseHeaders = rsp.headers lastPageHtmlWasRedirect = rsp.priorResponse?.isRedirect == true + if (OfflineCacheInterceptor.SAVE_HEADER_SAVE == rsp.headers[OfflineCacheInterceptor.SAVE_HEADER]) { + lastPageHtmlOfflineDate = rsp.headers.getInstant("date") + } } // noinspection ConstantConditions WebResourceResponse(rsp.body!!.contentType()!!.type + "/" + rsp.body!!.contentType()!!.subtype, diff --git a/app/src/main/java/org/wikipedia/page/PageFragment.kt b/app/src/main/java/org/wikipedia/page/PageFragment.kt index 63dcdc2cca7..4685b1efc81 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragment.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragment.kt @@ -12,7 +12,6 @@ import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView import android.widget.LinearLayout -import android.widget.Toast import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.animation.doOnEnd import androidx.core.app.ActivityCompat @@ -24,6 +23,7 @@ import androidx.lifecycle.lifecycleScope import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.snackbar.Snackbar import com.google.android.material.textview.MaterialTextView import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Completable @@ -58,7 +58,6 @@ import org.wikipedia.dataclient.ServiceFactory import org.wikipedia.dataclient.WikiSite import org.wikipedia.dataclient.mwapi.MwQueryPage import org.wikipedia.dataclient.okhttp.HttpStatusException -import org.wikipedia.dataclient.okhttp.OfflineCacheInterceptor import org.wikipedia.dataclient.okhttp.OkHttpWebViewClient import org.wikipedia.dataclient.watch.Watch import org.wikipedia.descriptions.DescriptionEditActivity @@ -410,7 +409,8 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi } if (pageWebViewClient.lastPageHtmlWasRedirect) { - L.d(">>>>>>>> Redirected from " + model.title?.displayText) + FeedbackUtil.showMessage(requireActivity(), getString(R.string.redirected_from_snackbar, + model.title?.displayText), Snackbar.LENGTH_SHORT) } var newTitle = model.title!! @@ -933,8 +933,8 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi webView.visibility = View.VISIBLE } - if (OfflineCacheInterceptor.SAVE_HEADER_SAVE == pageWebViewClient.lastPageHtmlResponseHeaders?.get(OfflineCacheInterceptor.SAVE_HEADER)) { - showPageOfflineMessage(pageWebViewClient.lastPageHtmlResponseHeaders?.getInstant("date")) + if (pageWebViewClient.lastPageHtmlOfflineDate != null) { + showPageOfflineMessage(pageWebViewClient.lastPageHtmlOfflineDate) } maybeShowAnnouncement() @@ -1307,8 +1307,7 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi } val localDate = LocalDateTime.ofInstant(dateHeader, ZoneId.systemDefault()).toLocalDate() val dateStr = DateUtil.getShortDateString(localDate) - Toast.makeText(requireContext().applicationContext, - getString(R.string.page_offline_notice_last_date, dateStr), Toast.LENGTH_LONG).show() + FeedbackUtil.showMessage(requireActivity(), getString(R.string.page_offline_notice_last_date, dateStr), Snackbar.LENGTH_SHORT) } private inner class AvCallback : AvPlayer.Callback { From fea377466182de96f4350f77063af6221ebe9ec1 Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Tue, 25 Jul 2023 09:02:28 -0400 Subject: [PATCH 09/14] Correctly handle no lead image. --- .../java/org/wikipedia/page/PageFragment.kt | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/wikipedia/page/PageFragment.kt b/app/src/main/java/org/wikipedia/page/PageFragment.kt index 4685b1efc81..f20d7ed28c3 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragment.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragment.kt @@ -438,13 +438,20 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi it.displayTitle = metadata.title it.isMainPage = model.title!!.isMainPage it.wikiBaseItem = metadata.wikibaseItem - it.leadImageUrl = ImageUrlUtil.getThumbUrlFromCommonsUrl(metadata.leadImage?.source.orEmpty(), DimenUtil.calculateLeadImageWidth()) - it.leadImageName = UriUtil.getFilenameFromUploadUrl(it.leadImageUrl.orEmpty()) - it.leadImageWidth = metadata.leadImage?.width ?: 0 - it.leadImageHeight = metadata.leadImage?.height ?: 0 + if (metadata.leadImage?.source.isNullOrEmpty()) { + it.leadImageUrl = null + it.leadImageName = null + it.leadImageWidth = 0 + it.leadImageHeight = 0 + } else { + it.leadImageUrl = ImageUrlUtil.getThumbUrlFromCommonsUrl(metadata.leadImage?.source.orEmpty(), DimenUtil.calculateLeadImageWidth()) + it.leadImageName = UriUtil.getFilenameFromUploadUrl(it.leadImageUrl.orEmpty()) + it.leadImageWidth = metadata.leadImage?.width ?: 0 + it.leadImageHeight = metadata.leadImage?.height ?: 0 + } - // TODO: - //geo = pageSummary.geo, + // TODO: get geo location from metadata + // it.geo = ... } model.title?.let { From 602e0b8acb54f53e8d61caa483656c9dc1aad7d0 Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Tue, 25 Jul 2023 09:11:10 -0400 Subject: [PATCH 10/14] Properly urldecode lead image url. --- app/src/main/java/org/wikipedia/page/PageFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/wikipedia/page/PageFragment.kt b/app/src/main/java/org/wikipedia/page/PageFragment.kt index f20d7ed28c3..c5021745b87 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragment.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragment.kt @@ -444,7 +444,7 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi it.leadImageWidth = 0 it.leadImageHeight = 0 } else { - it.leadImageUrl = ImageUrlUtil.getThumbUrlFromCommonsUrl(metadata.leadImage?.source.orEmpty(), DimenUtil.calculateLeadImageWidth()) + it.leadImageUrl = ImageUrlUtil.getThumbUrlFromCommonsUrl(UriUtil.decodeURL(metadata.leadImage?.source.orEmpty()), DimenUtil.calculateLeadImageWidth()) it.leadImageName = UriUtil.getFilenameFromUploadUrl(it.leadImageUrl.orEmpty()) it.leadImageWidth = metadata.leadImage?.width ?: 0 it.leadImageHeight = metadata.leadImage?.height ?: 0 From 2c4205541277b5eb762ce7ebb1e518878bd8f411 Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Tue, 25 Jul 2023 09:32:57 -0400 Subject: [PATCH 11/14] Correctly load mobileWeb pages. --- .../java/org/wikipedia/bridge/JavaScriptActionHandler.kt | 4 ++-- app/src/main/java/org/wikipedia/page/PageFragment.kt | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt index 36d8a2cfc55..c69047e1420 100644 --- a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt +++ b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt @@ -69,10 +69,10 @@ object JavaScriptActionHandler { "m = document.head.querySelector('meta[property=\"dc:modified\"]');" + "if (m) metadata.timeStamp = m.getAttribute('content');" + "m = document.head.querySelector('title');" + - "if (m) metadata.title = m.innerHTML;" + + "if (m) metadata.title = m.textContent;" + "m = document.body.querySelector('#pcs-edit-section-title-description');" + "if (m) {" + - " metadata.description = m.innerHTML;" + + " metadata.description = m.textContent;" + " metadata.descriptionSource = m.getAttribute('data-description-source');" + " metadata.wikibaseItem = m.getAttribute('data-wikdata-entity-id');" + "}" + diff --git a/app/src/main/java/org/wikipedia/page/PageFragment.kt b/app/src/main/java/org/wikipedia/page/PageFragment.kt index c5021745b87..61d988e7a7f 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragment.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragment.kt @@ -380,8 +380,6 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi return@evaluateImmediate } JsonUtil.decodeFromString(it)?.let { metadata -> - // compose a Page object from the metadata that was received. - L.d(">>>> " + metadata) createPageModel(metadata) } } @@ -916,6 +914,8 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi fun onPageMetadataLoaded() { updateBookmarkAndMenuOptionsFromDao() + binding.pageRefreshContainer.isEnabled = true + binding.pageRefreshContainer.isRefreshing = false if (model.page == null) { return } @@ -923,8 +923,6 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi articleInteractionEvent = ArticleInteractionEvent(model.title?.wikiSite?.dbName()!!, pageProperties.pageId) } editHandler.setPage(model.page) - binding.pageRefreshContainer.isEnabled = true - binding.pageRefreshContainer.isRefreshing = false requireActivity().invalidateOptionsMenu() model.readingListPage?.let { page -> model.title?.let { title -> @@ -1549,6 +1547,7 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi // page has now loaded and trigger the remaining logic ourselves. if ("true" != pcsExists) { onPageSetupEvent() + leadImagesHandler.loadLeadImage() bridge.onMetadataReady() bridge.onPcsReady() bridge.execute(JavaScriptActionHandler.mobileWebChromeShim()) From 00d1764de8bad6f88fdeb4e046da8914822b958f Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Tue, 25 Jul 2023 10:03:25 -0400 Subject: [PATCH 12/14] Correctly load non-pcs pages. --- .../wikipedia/bridge/CommunicationBridge.kt | 3 +- .../bridge/JavaScriptActionHandler.kt | 13 +- .../java/org/wikipedia/page/PageFragment.kt | 114 +++++++++--------- .../wikipedia/page/PageFragmentLoadState.kt | 7 +- 4 files changed, 66 insertions(+), 71 deletions(-) diff --git a/app/src/main/java/org/wikipedia/bridge/CommunicationBridge.kt b/app/src/main/java/org/wikipedia/bridge/CommunicationBridge.kt index 858c5e41243..01a542dc092 100644 --- a/app/src/main/java/org/wikipedia/bridge/CommunicationBridge.kt +++ b/app/src/main/java/org/wikipedia/bridge/CommunicationBridge.kt @@ -7,7 +7,6 @@ import android.os.Message import android.webkit.* import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonObject -import org.wikipedia.bridge.JavaScriptActionHandler.setUp import org.wikipedia.dataclient.RestService import org.wikipedia.dataclient.ServiceFactory import org.wikipedia.json.JsonUtil @@ -173,7 +172,7 @@ class CommunicationBridge constructor(private val communicationBridgeListener: C @get:Synchronized @get:JavascriptInterface val setupSettings: String - get() = setUp(communicationBridgeListener.webView.context, + get() = JavaScriptActionHandler.setUp(communicationBridgeListener.webView.context, communicationBridgeListener.model.title!!, communicationBridgeListener.isPreview, communicationBridgeListener.toolbarMargin) } diff --git a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt index c69047e1420..f6d197be609 100644 --- a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt +++ b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt @@ -49,14 +49,6 @@ object JavaScriptActionHandler { return "pcs.c1.Page.getTableOfContents()" } - fun getProtection(): String { - return "pcs.c1.Page.getProtection()" - } - - fun getRevision(): String { - return "pcs.c1.Page.getRevision();" - } - fun requestMetadata(): String { return "var metadata = {};" + "metadata.revision = parseInt(pcs.c1.Page.getRevision());" + @@ -181,10 +173,11 @@ object JavaScriptActionHandler { "})" } - fun mobileWebChromeShim(): String { + fun mobileWebChromeShim(toolbarMargin: Int): String { + val topMargin = DimenUtil.roundedPxToDp(toolbarMargin.toFloat()) return "(function() {" + "let style = document.createElement('style');" + - "style.innerHTML = '.header-chrome { visibility: hidden; margin-top: 48px; height: 0px; } #page-secondary-actions { display: none; } .mw-footer { padding-bottom: 72px; } .page-actions-menu { display: none; } .minerva__tab-container { display: none; }';" + + "style.innerHTML = '.header-chrome { visibility: hidden; margin-top: ${topMargin}px; height: 0px; } #page-secondary-actions { display: none; } .mw-footer { padding-bottom: 72px; } .page-actions-menu { display: none; } .minerva__tab-container { display: none; }';" + "document.head.appendChild(style);" + "})();" } diff --git a/app/src/main/java/org/wikipedia/page/PageFragment.kt b/app/src/main/java/org/wikipedia/page/PageFragment.kt index 61d988e7a7f..2de385a7c6f 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragment.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragment.kt @@ -368,40 +368,44 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi activity.intent.hasExtra(Constants.INTENT_APP_SHORTCUT_CONTINUE_READING))) } - private fun onPageSetupEvent() { + private fun onPageSetupEvent(havePcs: Boolean = true) { if (!isAdded) { return } updateProgressBar(false) webView.visibility = View.VISIBLE - bridge.evaluateImmediate(JavaScriptActionHandler.requestMetadata()) { - if (!isAdded) { - return@evaluateImmediate - } - JsonUtil.decodeFromString(it)?.let { metadata -> - createPageModel(metadata) + if (havePcs) { + bridge.evaluateImmediate(JavaScriptActionHandler.requestMetadata()) { + if (!isAdded) { + return@evaluateImmediate + } + JsonUtil.decodeFromString(it)?.let { metadata -> + createPageModel(metadata) + } } - } - bridge.evaluate(JavaScriptActionHandler.getSections()) { value -> - if (!isAdded) { - return@evaluate - } - model.page?.let { page -> - sections = JsonUtil.decodeFromString(value) - sections?.let { sections -> - sections.add(0, Section(0, 0, model.title?.displayText.orEmpty(), model.title?.displayText.orEmpty(), "")) - page.sections = sections + bridge.evaluate(JavaScriptActionHandler.getSections()) { value -> + if (!isAdded) { + return@evaluate } + model.page?.let { page -> + sections = JsonUtil.decodeFromString(value) + sections?.let { sections -> + sections.add(0, Section(0, 0, model.title?.displayText.orEmpty(), model.title?.displayText.orEmpty(), "")) + page.sections = sections + } - sidePanelHandler.setupForNewPage(page) - sidePanelHandler.setEnabled(true) + sidePanelHandler.setupForNewPage(page) + sidePanelHandler.setEnabled(true) + } } + } else { + createPageModel(null) } } - private fun createPageModel(metadata: PageFragmentLoadState.JsPageMetadata) { + private fun createPageModel(metadata: PageFragmentLoadState.JsPageMetadata?) { if (model.title == null) { return } @@ -412,44 +416,47 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi } var newTitle = model.title!! - if (metadata.title.isNotEmpty()) { - // TODO: handle redirected title? - newTitle = PageTitle(model.title!!.prefixedText, model.title!!.wikiSite, model.title!!.thumbUrl) - newTitle.displayText = metadata.title - newTitle.fragment = title!!.fragment - } - if (metadata.description.isNotEmpty()) { - newTitle.description = metadata.description + + if (metadata != null) { + if (metadata.title.isNotEmpty()) { + newTitle = PageTitle(model.title!!.prefixedText, model.title!!.wikiSite, model.title!!.thumbUrl) + newTitle.displayText = metadata.title + newTitle.fragment = title!!.fragment + } + if (metadata.description.isNotEmpty()) { + newTitle.description = metadata.description + } } model.title = newTitle model.page = Page(newTitle, pageProperties = PageProperties(newTitle, newTitle.isMainPage)) model.page?.pageProperties?.let { - it.pageId = metadata.pageId - it.namespace = Namespace.of(metadata.ns) - it.revisionId = metadata.revision - it.protection = metadata.protection - it.descriptionSource = metadata.descriptionSource - if (metadata.timeStamp.isNotEmpty()) { - it.lastModified = DateUtil.iso8601DateParse(metadata.timeStamp) - } - it.displayTitle = metadata.title it.isMainPage = model.title!!.isMainPage - it.wikiBaseItem = metadata.wikibaseItem - if (metadata.leadImage?.source.isNullOrEmpty()) { - it.leadImageUrl = null - it.leadImageName = null - it.leadImageWidth = 0 - it.leadImageHeight = 0 - } else { - it.leadImageUrl = ImageUrlUtil.getThumbUrlFromCommonsUrl(UriUtil.decodeURL(metadata.leadImage?.source.orEmpty()), DimenUtil.calculateLeadImageWidth()) - it.leadImageName = UriUtil.getFilenameFromUploadUrl(it.leadImageUrl.orEmpty()) - it.leadImageWidth = metadata.leadImage?.width ?: 0 - it.leadImageHeight = metadata.leadImage?.height ?: 0 + if (metadata != null) { + it.pageId = metadata.pageId + it.namespace = Namespace.of(metadata.ns) + it.revisionId = metadata.revision + it.protection = metadata.protection + it.descriptionSource = metadata.descriptionSource + if (metadata.timeStamp.isNotEmpty()) { + it.lastModified = DateUtil.iso8601DateParse(metadata.timeStamp) + } + it.displayTitle = metadata.title + it.wikiBaseItem = metadata.wikibaseItem + if (metadata.leadImage?.source.isNullOrEmpty()) { + it.leadImageUrl = null + it.leadImageName = null + it.leadImageWidth = 0 + it.leadImageHeight = 0 + } else { + it.leadImageUrl = ImageUrlUtil.getThumbUrlFromCommonsUrl(UriUtil.decodeURL(metadata.leadImage?.source.orEmpty()), DimenUtil.calculateLeadImageWidth()) + it.leadImageName = UriUtil.getFilenameFromUploadUrl(it.leadImageUrl.orEmpty()) + it.leadImageWidth = metadata.leadImage?.width ?: 0 + it.leadImageHeight = metadata.leadImage?.height ?: 0 + } + // TODO: get geo location from metadata + // it.geo = ... } - - // TODO: get geo location from metadata - // it.geo = ... } model.title?.let { @@ -1546,11 +1553,10 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi // tells us the page is finished loading. In such a case, we must infer that the // page has now loaded and trigger the remaining logic ourselves. if ("true" != pcsExists) { - onPageSetupEvent() - leadImagesHandler.loadLeadImage() + onPageSetupEvent(false) bridge.onMetadataReady() bridge.onPcsReady() - bridge.execute(JavaScriptActionHandler.mobileWebChromeShim()) + bridge.execute(JavaScriptActionHandler.mobileWebChromeShim(toolbarMargin)) } onPageMetadataLoaded() diff --git a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt index db05408a4bb..c58438bc6be 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt @@ -113,12 +113,9 @@ class PageFragmentLoadState(private var model: PageViewModel, // kick off loading mobile-html contents into the WebView. bridge.resetHtml(title) + // The final step is to fetch the watched status of the page (in the background), + // but not if it's a Special page, which can't be watched. if (title.namespace() === Namespace.SPECIAL) { - // Short-circuit the entire process of fetching the Summary, since Special: pages - // are not supported in RestBase. - leadImagesHandler.loadLeadImage() - fragment.requireActivity().invalidateOptionsMenu() - fragment.onPageMetadataLoaded() return } From f93d9715ed4d620e387a81149101cf847fc8e200 Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Thu, 21 Sep 2023 17:20:14 -0400 Subject: [PATCH 13/14] Lint. --- .../java/org/wikipedia/page/PageFragment.kt | 29 +++++++++++++++---- .../wikipedia/page/PageFragmentLoadState.kt | 3 -- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/wikipedia/page/PageFragment.kt b/app/src/main/java/org/wikipedia/page/PageFragment.kt index c7874458c22..24be2e38107 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragment.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragment.kt @@ -7,7 +7,14 @@ import android.content.Intent import android.content.res.Configuration import android.net.Uri import android.os.Bundle -import android.view.* +import android.view.ActionMode +import android.view.ActionProvider +import android.view.Gravity +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView @@ -35,8 +42,12 @@ import kotlinx.serialization.json.float import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive -import org.wikipedia.* +import org.wikipedia.BackPressedHandler +import org.wikipedia.Constants import org.wikipedia.Constants.InvokeSource +import org.wikipedia.LongPressHandler +import org.wikipedia.R +import org.wikipedia.WikipediaApp import org.wikipedia.activity.FragmentUtil.getCallback import org.wikipedia.analytics.eventplatform.ArticleFindInPageInteractionEvent import org.wikipedia.analytics.eventplatform.ArticleInteractionEvent @@ -89,7 +100,16 @@ import org.wikipedia.settings.Prefs import org.wikipedia.suggestededits.PageSummaryForEdit import org.wikipedia.talk.TalkTopicsActivity import org.wikipedia.theme.ThemeChooserDialog -import org.wikipedia.util.* +import org.wikipedia.util.ActiveTimer +import org.wikipedia.util.DateUtil +import org.wikipedia.util.DimenUtil +import org.wikipedia.util.FeedbackUtil +import org.wikipedia.util.GeoUtil +import org.wikipedia.util.ImageUrlUtil +import org.wikipedia.util.ResourceUtil +import org.wikipedia.util.ShareUtil +import org.wikipedia.util.ThrowableUtil +import org.wikipedia.util.UriUtil import org.wikipedia.util.log.L import org.wikipedia.views.ObservableWebView import org.wikipedia.views.PageActionOverflowView @@ -99,9 +119,8 @@ import org.wikipedia.watchlist.WatchlistExpiryDialog import org.wikipedia.wiktionary.WiktionaryDialog import java.time.Instant import java.time.LocalDate -import java.time.LocalDateTime import java.time.ZoneId -import java.util.* +import java.util.Date class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.CommunicationBridgeListener, ThemeChooserDialog.Callback, ReferenceDialog.Callback, WiktionaryDialog.Callback, WatchlistExpiryDialog.Callback { diff --git a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt index a16cdec9278..15ddaa74642 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt @@ -22,9 +22,6 @@ import org.wikipedia.settings.Prefs import org.wikipedia.staticdata.UserTalkAliasData import org.wikipedia.util.log.L import org.wikipedia.views.ObservableWebView -import java.time.Instant -import java.time.LocalDate -import java.time.ZoneId class PageFragmentLoadState(private var model: PageViewModel, private var fragment: PageFragment, From c0c989b98d34ebdeaf034b498985062eb459ade5 Mon Sep 17 00:00:00 2001 From: Dmitry Brant Date: Tue, 18 Jun 2024 10:08:14 -0400 Subject: [PATCH 14/14] Use correct constructor. --- app/src/main/java/org/wikipedia/page/PageFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/wikipedia/page/PageFragment.kt b/app/src/main/java/org/wikipedia/page/PageFragment.kt index 2dbe2f1f77c..f789b4186d9 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragment.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragment.kt @@ -492,7 +492,7 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi } model.title = newTitle - model.page = Page(newTitle, pageProperties = PageProperties(newTitle, newTitle.isMainPage)) + model.page = Page(newTitle, pageProperties = PageProperties(namespace = newTitle.namespace(), displayTitle = newTitle.displayText, isMainPage = newTitle.isMainPage)) model.page?.pageProperties?.let { it.isMainPage = model.title!!.isMainPage if (metadata != null) {