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 7cefd8aaee5..42b359d83d3 100644 --- a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt +++ b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt @@ -50,12 +50,26 @@ 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());" + + "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('meta[property=\"dc:modified\"]');" + + "if (m) metadata.timeStamp = m.getAttribute('content');" + + "m = document.head.querySelector('title');" + + "if (m) metadata.title = m.textContent;" + + "m = document.body.querySelector('#pcs-edit-section-title-description');" + + "if (m) {" + + " metadata.description = m.textContent;" + + " metadata.descriptionSource = m.getAttribute('data-description-source');" + + " metadata.wikibaseItem = m.getAttribute('data-wikdata-entity-id');" + + "}" + + "metadata;" } fun expandCollapsedTables(expand: Boolean): String { 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 eccc02d8fcb..9291abc9d44 100644 --- a/app/src/main/java/org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt +++ b/app/src/main/java/org/wikipedia/dataclient/okhttp/OkHttpWebViewClient.kt @@ -16,6 +16,7 @@ import org.wikipedia.util.UriUtil import org.wikipedia.util.log.L import java.io.IOException import java.nio.charset.Charset +import java.time.Instant abstract class OkHttpWebViewClient : WebViewClient() { /* @@ -26,6 +27,9 @@ abstract class OkHttpWebViewClient : WebViewClient() { abstract val model: PageViewModel abstract val linkHandler: LinkHandler + var lastPageHtmlOfflineDate: Instant? = null + var lastPageHtmlWasRedirect = false + 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 @@ -54,12 +58,13 @@ 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() + lastPageHtmlOfflineDate = null } val rsp = request(request) - if (rsp.networkResponse != null && shouldLogLatency) { + if (rsp.networkResponse != null && isPageHtmlCall) { WikipediaApp.instance.appSessionEvent.pageFetchEnd() } val contentType = rsp.header(HEADER_CONTENT_TYPE).orEmpty() @@ -68,6 +73,12 @@ abstract class OkHttpWebViewClient : WebViewClient() { rsp.close() return null } else { + if (isPageHtmlCall) { + 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, 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 8dc9f85b83f..f789b4186d9 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragment.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragment.kt @@ -68,6 +68,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.donate.CampaignCollection @@ -94,6 +95,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.places.PlacesActivity import org.wikipedia.readinglist.LongPressMenu import org.wikipedia.readinglist.ReadingListBehaviorsUtil @@ -103,8 +105,10 @@ import org.wikipedia.suggestededits.PageSummaryForEdit import org.wikipedia.talk.TalkTopicsActivity import org.wikipedia.theme.ThemeChooserDialog import org.wikipedia.util.ActiveTimer +import org.wikipedia.util.DateUtil import org.wikipedia.util.DimenUtil import org.wikipedia.util.FeedbackUtil +import org.wikipedia.util.ImageUrlUtil import org.wikipedia.util.ResourceUtil import org.wikipedia.util.ShareUtil import org.wikipedia.util.ThrowableUtil @@ -118,6 +122,8 @@ import org.wikipedia.watchlist.WatchlistExpiryDialog import org.wikipedia.wiktionary.WiktionaryDialog import java.time.Duration import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.CommunicationBridgeListener, ThemeChooserDialog.Callback, ReferenceDialog.Callback, WiktionaryDialog.Callback, WatchlistExpiryDialog.Callback { @@ -153,6 +159,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 @@ -197,7 +204,13 @@ 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) @@ -381,91 +394,159 @@ 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 + inner class PageWebViewClient : OkHttpWebViewClient() { + override val model get() = this@PageFragment.model + override val linkHandler get() = this@PageFragment.linkHandler - 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(DimenUtil.roundedPxToDp(((requireActivity() as AppCompatActivity).supportActionBar?.height ?: 0).toFloat()), - DimenUtil.roundedPxToDp(binding.pageActionsTabLayout.height.toFloat()))) - } + 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(false) + bridge.onMetadataReady() + bridge.onPcsReady() + bridge.execute(JavaScriptActionHandler.mobileWebChromeShim(DimenUtil.roundedPxToDp(((requireActivity() as AppCompatActivity).supportActionBar?.height ?: 0).toFloat()), + DimenUtil.roundedPxToDp(binding.pageActionsTabLayout.height.toFloat()))) } + + onPageMetadataLoaded() } + } - 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))) + 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() { + private fun onPageSetupEvent(havePcs: Boolean = true) { if (!isAdded) { return } updateProgressBar(false) webView.visibility = View.VISIBLE - bridge.evaluate(JavaScriptActionHandler.getRevision()) { value -> - if (!isAdded || value == null || value == "null") { - return@evaluate + + if (havePcs) { + bridge.evaluateImmediate(JavaScriptActionHandler.requestMetadata()) { + if (!isAdded) { + return@evaluateImmediate + } + JsonUtil.decodeFromString(it)?.let { metadata -> + createPageModel(metadata) + } } - try { - revision = value.replace("\"", "").toLong() - } catch (e: Exception) { - L.e(e) + + 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) + model.isReadMoreLoaded = false + } } + } else { + createPageModel(null) } - bridge.evaluate(JavaScriptActionHandler.getSections()) { value -> - if (!isAdded) { - return@evaluate + } + + private fun createPageModel(metadata: PageFragmentLoadState.JsPageMetadata?) { + if (model.title == null) { + return + } + + if (pageWebViewClient.lastPageHtmlWasRedirect) { + FeedbackUtil.showMessage(requireActivity(), getString(R.string.redirected_from_snackbar, + model.title?.displayText), Snackbar.LENGTH_SHORT) + } + + var newTitle = model.title!! + + 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 } - 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 - } + if (metadata.description.isNotEmpty()) { + newTitle.description = metadata.description + } + } - sidePanelHandler.setupForNewPage(page) - sidePanelHandler.setEnabled(true) - model.isReadMoreLoaded = false + model.title = newTitle + 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) { + 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 = ... } } - bridge.evaluate(JavaScriptActionHandler.getProtection()) { value -> - if (!isAdded) { - return@evaluate + + model.title?.let { + if (it.description.isNullOrEmpty()) { + app.appSessionEvent.noDescription() } - model.page?.let { page -> - page.pageProperties.protection = JsonUtil.decodeFromString(value) - updateQuickActionsAndMenuOptions() + + // Update our history entry, in case the Title was changed (i.e. normalized) + val curEntry = model.curEntry + 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. + app.tabList.getOrNull(app.tabCount - 1)?.setBackStackPositionTitle(it) + + // Save the thumbnail URL to the DB + val pageImage = PageImage(it, ImageUrlUtil.getUrlForPreferredSize(page?.pageProperties?.leadImageUrl.orEmpty(), Service.PREFERRED_THUMB_SIZE)) + it.thumbUrl = pageImage.imageName + lifecycleScope.launch { + AppDatabase.instance.pageImagesDao().insertPageImage(pageImage) } } + + leadImagesHandler.loadLeadImage() + requireActivity().invalidateOptionsMenu() } private fun handleInternalLink(title: PageTitle) { @@ -903,8 +984,11 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi binding.pageRefreshContainer.setProgressViewOffset(false, -swipeOffset, swipeOffset) } - fun onPageMetadataLoaded(redirectedFrom: String? = null) { - updateQuickActionsAndMenuOptions() + fun onPageMetadataLoaded() { + updateBookmarkAndMenuOptionsFromDao() + binding.pageRefreshContainer.isEnabled = true + binding.pageRefreshContainer.isRefreshing = false + if (model.page == null) { return } @@ -912,26 +996,24 @@ 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() - redirectedFrom?.let { - FeedbackUtil.showMessage(requireActivity(), getString(R.string.redirected_from_snackbar, it), Snackbar.LENGTH_SHORT) - } + 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) { editHandler.setPage(model.page) webView.visibility = View.VISIBLE } + if (pageWebViewClient.lastPageHtmlOfflineDate != null) { + showPageOfflineMessage(pageWebViewClient.lastPageHtmlOfflineDate) + } maybeShowAnnouncement() bridge.onMetadataReady() // Explicitly set the top margin (even though it might have already been set in the setup @@ -1277,6 +1359,15 @@ class PageFragment : Fragment(), BackPressedHandler, CommunicationBridge.Communi requireActivity().finish() } + private fun showPageOfflineMessage(dateHeader: Instant?) { + if (!isAdded || dateHeader == null) { + return + } + val localDate = LocalDate.ofInstant(dateHeader, ZoneId.systemDefault()) + val dateStr = DateUtil.getShortDateString(localDate) + FeedbackUtil.showMessage(requireActivity(), getString(R.string.page_offline_notice_last_date, dateStr), Snackbar.LENGTH_SHORT) + } + private inner class AvCallback : AvPlayer.Callback { override fun onSuccess() { avPlayer?.stop() diff --git a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt index 85fce389063..1f69b0315fe 100644 --- a/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt +++ b/app/src/main/java/org/wikipedia/page/PageFragmentLoadState.kt @@ -1,11 +1,10 @@ package org.wikipedia.page -import android.widget.Toast import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.async import kotlinx.coroutines.launch -import org.wikipedia.R +import kotlinx.serialization.Serializable import org.wikipedia.WikipediaApp import org.wikipedia.auth.AccountUtil import org.wikipedia.bridge.CommunicationBridge @@ -13,23 +12,14 @@ 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.history.HistoryEntry +import org.wikipedia.dataclient.page.Protection 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.LocalDate -import java.time.ZoneId class PageFragmentLoadState(private var model: PageViewModel, private var fragment: PageFragment, @@ -44,6 +34,10 @@ class PageFragmentLoadState(private var model: PageViewModel, updateCurrentBackStackItem() currentTab.pushBackStackItem(PageBackStackItem(model.title!!, model.curEntry!!)) } + + // point of no return: null out the current page object. + model.page = null + model.readingListPage = null pageLoad() } @@ -124,25 +118,18 @@ class PageFragmentLoadState(private var model: PageViewModel, fragment.updateQuickActionsAndMenuOptions() 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. + // The onPageFinished callback, which should be called by the WebView, will signal + // the next stage of the loading process. + 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. - bridge.resetHtml(title) - leadImagesHandler.loadLeadImage() - fragment.requireActivity().invalidateOptionsMenu() - fragment.onPageMetadataLoaded() return@launch } - val pageSummaryRequest = async { - ServiceFactory.getRest(title.wikiSite).getSummaryResponseSuspend(title.prefixedText, null, model.cacheControl.toString(), - if (model.isInReadingList) OfflineCacheInterceptor.SAVE_HEADER_SAVE else null, title.wikiSite.languageCode, UriUtil.encodeURL(title.prefixedText)) - } val watchedRequest = async { if (WikipediaApp.instance.isOnline && AccountUtil.isLoggedIn) { ServiceFactory.get(title.wikiSite).getWatchedStatus(title.prefixedText) @@ -153,22 +140,12 @@ class PageFragmentLoadState(private var model: PageViewModel, } } - val pageSummaryResponse = pageSummaryRequest.await() val watchedResponse = watchedRequest.await() - val isWatched = watchedResponse.query?.firstPage()?.watched ?: false - val hasWatchlistExpiry = watchedResponse.query?.firstPage()?.hasWatchlistExpiry() ?: false - if (pageSummaryResponse.body() == null) { - throw RuntimeException("Summary response was invalid.") - } - val redirectedFrom = if (pageSummaryResponse.raw().priorResponse?.isRedirect == true) model.title?.displayText else null - createPageModel(pageSummaryResponse, isWatched, hasWatchlistExpiry) - if (OfflineCacheInterceptor.SAVE_HEADER_SAVE == pageSummaryResponse.headers()[OfflineCacheInterceptor.SAVE_HEADER]) { - showPageOfflineMessage(pageSummaryResponse.headers().getInstant("date")) - } - if (delayLoadHtml) { - bridge.resetHtml(title) - } - fragment.onPageMetadataLoaded(redirectedFrom) + model.isWatched = watchedResponse.query?.firstPage()?.watched ?: false + model.hasWatchlistExpiry = watchedResponse.query?.firstPage()?.hasWatchlistExpiry() ?: false + + fragment.updateQuickActionsAndMenuOptions() + fragment.requireActivity().invalidateOptionsMenu() if (AnonymousNotificationHelper.shouldCheckAnonNotifications(watchedResponse)) { checkAnonNotifications(title) @@ -187,59 +164,24 @@ class PageFragmentLoadState(private var model: PageViewModel, } } - private fun showPageOfflineMessage(dateHeader: Instant?) { - if (!fragment.isAdded || dateHeader == null) { - return - } - val localDate = LocalDate.ofInstant(dateHeader, ZoneId.systemDefault()) - 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 + val ns: Int = 0 + val revision: Long = 0 + val title: String = "" + val timeStamp: String = "" + val description: String = "" + val descriptionSource: String = "" + val wikibaseItem = "" + val protection: Protection? = null + val leadImage: JsLeadImage? = null } - 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()) { - WikipediaApp.instance.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) - model.curEntry?.let { - model.curEntry = HistoryEntry(title, it.source, timestamp = it.timestamp).apply { - referrer = it.referrer - } - } - - // Update our tab list to prevent ZH variants issue. - WikipediaApp.instance.tabList.getOrNull(WikipediaApp.instance.tabCount - 1)?.setBackStackPositionTitle(title) - - // Save the thumbnail URL to the DB - val pageImage = PageImage(title, pageSummary?.thumbnailUrl) - - fragment.lifecycleScope.launch { - AppDatabase.instance.pageImagesDao().insertPageImage(pageImage) - } - title.thumbUrl = pageImage.imageName - } + @Serializable + class JsLeadImage { + val source: String = "" + val width: Int = 0 + val height: Int = 0 } } diff --git a/app/src/main/java/org/wikipedia/page/PageProperties.kt b/app/src/main/java/org/wikipedia/page/PageProperties.kt index d2ffea2b9df..7c266084695 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.Date @Parcelize @TypeParceler() data class PageProperties( - 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, - val leadImageName: String? = null, - val leadImageWidth: Int = 0, - val leadImageHeight: Int = 0, + var leadImageUrl: String? = null, + var leadImageName: String? = null, + 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 cfacf4ed6e0..80399bab5f6 100644 --- a/app/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.kt +++ b/app/src/main/java/org/wikipedia/page/leadimages/LeadImagesHandler.kt @@ -229,7 +229,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, true, options) + callback?.onPageRequestGallery(it, filename, wiki, page?.pageProperties?.revisionId ?: 0, true, options) } } } 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 + } }