Brion VIBBER has uploaded a new change for review. https://gerrit.wikimedia.org/r/116653
Change subject: Work in progress: save scroll position by element instead of raw offset ...................................................................... Work in progress: save scroll position by element instead of raw offset Instead of saving scroll position as a Y offset in pixels, save a CSS-style query selector against the nearest section header or content section chunk and numbered subparagraph, with an offset against that. Comes closer to keeping us on the 'same' position on rotation, and should survive various minor editing scenarios. Not quite perfect right now; as with the previous fragment support we'll show the loaded HTML before we actually scroll to it. Need to be smarter about the 'loading' crossfade state, so we stay in the loading indicator until the desired scroll position is visible and reached. Another smarter thing would be to scroll as soon as the position exists, then keep adding subsequent sections, rather than wait for all HTML to be applied before scrolling. Bug: 61512 Change-Id: Ia08941adbdd09f5c21eda03deb26e40ac26be792 --- M wikipedia/assets/bundle-test.js M wikipedia/assets/bundle.js M wikipedia/src/main/java/org/wikipedia/page/PageViewFragment.java M www/js/main.js M www/js/sections.js 5 files changed, 254 insertions(+), 13 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/apps/android/wikipedia refs/changes/53/116653/1 diff --git a/wikipedia/assets/bundle-test.js b/wikipedia/assets/bundle-test.js index 470914c..678d135 100644 --- a/wikipedia/assets/bundle-test.js +++ b/wikipedia/assets/bundle-test.js @@ -55,6 +55,78 @@ bridge.sendMessage( "imagesListResponse", { "images": imageURLs }); } ); +bridge.registerListener( 'scrollToSelector', function( payload ) { + var target = document.querySelector( payload.selector ); + if ( target ) { + var top = target.offsetTop + payload.offset; + window.scrollTo( 0, top ); + } else { + console.log('no exist'); + } +}); + +function getScrollPositionByElement() { + // The system tells us where we are in pixels + var top = window.scrollY; + + // But if we rotate, or the page is edited, that'll be useless. + // Find the top-level paragraph-ish element within the current section that we're in at the top... + + // Could consider using document.elementFromPoint but this may be weird. + var content = document.getElementById('content'); + var el = null; + var section = null; + for ( el = content.firstChild; el.nextSibling !== null; el = el.nextSibling ) { + if ( el.offsetTop >= top ) { + // We passed it + break; + } else { + section = el; + } + } + if (section === null ) { + return { + selector: "html", + offset: top + }; + } + + // Now look within the section + var target = null, n = 0; + for ( el = section.firstChild; el.nextSibling !== null; el = el.nextSibling ) { + if (el.nodeType === Node.ELEMENT_NODE ) { + if ( el.offsetTop >= top ) { + // We passed it + break; + } else { + target = el; + } + n++; + } + } + + if ( target ) { + return { + selector: "#" + section.id + " > *:nth-child(" + n + ")", + offset: top - target.offsetTop + }; + } else { + return { + selector: '#' + section.id, + offset: top - section.offsetTop + }; + } +} + +function updateScrollPosition() { + var positionData = getScrollPositionByElement(); + bridge.sendMessage( "updateScrollPosition", positionData ); +} + +window.addEventListener('scroll', function() { + updateScrollPosition(); +}); + },{"./bridge":1}],3:[function(require,module,exports){ var bridge = require("../js/bridge"); bridge.registerListener( "injectScript", function( payload ) { diff --git a/wikipedia/assets/bundle.js b/wikipedia/assets/bundle.js index 3ddbe0f..0ba78b1 100644 --- a/wikipedia/assets/bundle.js +++ b/wikipedia/assets/bundle.js @@ -103,6 +103,78 @@ bridge.sendMessage( "imagesListResponse", { "images": imageURLs }); } ); +bridge.registerListener( 'scrollToSelector', function( payload ) { + var target = document.querySelector( payload.selector ); + if ( target ) { + var top = target.offsetTop + payload.offset; + window.scrollTo( 0, top ); + } else { + console.log('no exist'); + } +}); + +function getScrollPositionByElement() { + // The system tells us where we are in pixels + var top = window.scrollY; + + // But if we rotate, or the page is edited, that'll be useless. + // Find the top-level paragraph-ish element within the current section that we're in at the top... + + // Could consider using document.elementFromPoint but this may be weird. + var content = document.getElementById('content'); + var el = null; + var section = null; + for ( el = content.firstChild; el.nextSibling !== null; el = el.nextSibling ) { + if ( el.offsetTop >= top ) { + // We passed it + break; + } else { + section = el; + } + } + if (section === null ) { + return { + selector: "html", + offset: top + }; + } + + // Now look within the section + var target = null, n = 0; + for ( el = section.firstChild; el.nextSibling !== null; el = el.nextSibling ) { + if (el.nodeType === Node.ELEMENT_NODE ) { + if ( el.offsetTop >= top ) { + // We passed it + break; + } else { + target = el; + } + n++; + } + } + + if ( target ) { + return { + selector: "#" + section.id + " > *:nth-child(" + n + ")", + offset: top - target.offsetTop + }; + } else { + return { + selector: '#' + section.id, + offset: top - section.offsetTop + }; + } +} + +function updateScrollPosition() { + var positionData = getScrollPositionByElement(); + bridge.sendMessage( "updateScrollPosition", positionData ); +} + +window.addEventListener('scroll', function() { + updateScrollPosition(); +}); + },{"./bridge":2}],5:[function(require,module,exports){ var bridge = require("./bridge"); @@ -124,7 +196,7 @@ var content = document.createElement( "div" ); content.innerHTML = payload.section.text; - content.id = "#content_block_0"; + content.id = "content_block_0"; content = transformer.transform( "leadSection", content ); content = transformer.transform( "section", content ); document.getElementById( "content" ).appendChild( content ); @@ -162,8 +234,12 @@ bridge.sendMessage( "requestSection", { index: payload.index + 1 } ); } else { document.getElementById( "loading_sections").className = ""; - if ( typeof payload.fragment === "string" ) { - scrollToSection( payload.fragment ); + if ( typeof payload.scrollSelector === "string" ) { + var target = document.querySelector( payload.scrollSelector ); + if ( target ) { + var top = target.offsetTop + payload.scrollOffset; + window.scrollTo( 0, top ); + } } } }); diff --git a/wikipedia/src/main/java/org/wikipedia/page/PageViewFragment.java b/wikipedia/src/main/java/org/wikipedia/page/PageViewFragment.java index c8da5b7..ce3e274 100644 --- a/wikipedia/src/main/java/org/wikipedia/page/PageViewFragment.java +++ b/wikipedia/src/main/java/org/wikipedia/page/PageViewFragment.java @@ -21,7 +21,8 @@ private static final String KEY_TITLE = "title"; private static final String KEY_PAGE = "page"; private static final String KEY_STATE = "state"; - private static final String KEY_SCROLL_Y = "scrollY"; + private static final String KEY_SCROLL_SELECTOR = "scrollSelector"; + private static final String KEY_SCROLL_OFFSET = "scrollOffset"; private static final String KEY_CURRENT_HISTORY_ENTRY = "currentHistoryEntry"; private static final String KEY_QUICK_RETURN_BAR_ID = "quickReturnBarId"; @@ -49,7 +50,8 @@ private WikipediaApp app; private Api api; - private int scrollY; + private String scrollSelector; + private int scrollOffset; private int quickReturnBarId; private View quickReturnBar; @@ -105,7 +107,8 @@ outState.putParcelable(KEY_TITLE, title); outState.putParcelable(KEY_PAGE, page); outState.putInt(KEY_STATE, state); - outState.putInt(KEY_SCROLL_Y, webView.getScrollY()); + outState.putString(KEY_SCROLL_SELECTOR, scrollSelector); + outState.putInt(KEY_SCROLL_OFFSET, scrollOffset); outState.putParcelable(KEY_CURRENT_HISTORY_ENTRY, curEntry); outState.putInt(KEY_QUICK_RETURN_BAR_ID, quickReturnBarId); } @@ -124,7 +127,8 @@ page = savedInstanceState.getParcelable(KEY_PAGE); } state = savedInstanceState.getInt(KEY_STATE); - scrollY = savedInstanceState.getInt(KEY_SCROLL_Y); + scrollSelector = savedInstanceState.getString(KEY_SCROLL_SELECTOR); + scrollOffset = savedInstanceState.getInt(KEY_SCROLL_OFFSET); curEntry = savedInstanceState.getParcelable(KEY_CURRENT_HISTORY_ENTRY); quickReturnBarId = savedInstanceState.getInt(KEY_QUICK_RETURN_BAR_ID); } @@ -145,7 +149,6 @@ bridge = new CommunicationBridge(webView, "file:///android_asset/index.html"); setupMessageHandlers(); - Utils.addUtilityMethodsToBridge(getActivity(), bridge); Utils.setupDirectionality(title.getSite().getLanguage(), bridge); linkHandler = new LinkHandler(getActivity(), bridge, title.getSite()); app = (WikipediaApp)getActivity().getApplicationContext(); @@ -180,12 +183,27 @@ wrapper.put("section", page.getSections().get(index).toJSON()); wrapper.put("index", index); wrapper.put("isLast", index == page.getSections().size() - 1); - wrapper.put("fragment", page.getTitle().getFragment()); + if (scrollSelector != null) { + wrapper.put("scrollSelector", scrollSelector); + wrapper.put("scrollOffset", scrollOffset); + } else if (page.getTitle().getFragment() != null) { + wrapper.put("scrollSelector", "#" + page.getTitle().getFragment()); // check for encoding issues? + wrapper.put("scrollOffset", 0); + } bridge.sendMessage("displaySection", wrapper); } catch (JSONException e) { // Won't happen throw new RuntimeException(e); } + } + }); + + bridge.addListener( "updateScrollPosition", new CommunicationBridge.JSEventListener() { + @Override + public void onMessage(String messageType, JSONObject messagePayload) { + Log.d("Wikipedia", "updateScrollPosition: " + messagePayload.toString()); + scrollSelector = messagePayload.optString("selector"); + scrollOffset = messagePayload.optInt("offset"); } }); } @@ -211,7 +229,6 @@ case STATE_COMPLETE_FETCH: displayLeadSection(page); populateNonLeadSections(page); - webView.setScrollY(scrollY); break; } } diff --git a/www/js/main.js b/www/js/main.js index ab9ae9c..3b386dd 100644 --- a/www/js/main.js +++ b/www/js/main.js @@ -15,3 +15,75 @@ } bridge.sendMessage( "imagesListResponse", { "images": imageURLs }); } ); + +bridge.registerListener( 'scrollToSelector', function( payload ) { + var target = document.querySelector( payload.selector ); + if ( target ) { + var top = target.offsetTop + payload.offset; + window.scrollTo( 0, top ); + } else { + console.log('no exist'); + } +}); + +function getScrollPositionByElement() { + // The system tells us where we are in pixels + var top = window.scrollY; + + // But if we rotate, or the page is edited, that'll be useless. + // Find the top-level paragraph-ish element within the current section that we're in at the top... + + // Could consider using document.elementFromPoint but this may be weird. + var content = document.getElementById('content'); + var el = null; + var section = null; + for ( el = content.firstChild; el.nextSibling !== null; el = el.nextSibling ) { + if ( el.offsetTop >= top ) { + // We passed it + break; + } else { + section = el; + } + } + if (section === null ) { + return { + selector: "html", + offset: top + }; + } + + // Now look within the section + var target = null, n = 0; + for ( el = section.firstChild; el.nextSibling !== null; el = el.nextSibling ) { + if (el.nodeType === Node.ELEMENT_NODE ) { + if ( el.offsetTop >= top ) { + // We passed it + break; + } else { + target = el; + } + n++; + } + } + + if ( target ) { + return { + selector: "#" + section.id + " > *:nth-child(" + n + ")", + offset: top - target.offsetTop + }; + } else { + return { + selector: '#' + section.id, + offset: top - section.offsetTop + }; + } +} + +function updateScrollPosition() { + var positionData = getScrollPositionByElement(); + bridge.sendMessage( "updateScrollPosition", positionData ); +} + +window.addEventListener('scroll', function() { + updateScrollPosition(); +}); diff --git a/www/js/sections.js b/www/js/sections.js index dc48dd3..e4e17b5 100644 --- a/www/js/sections.js +++ b/www/js/sections.js @@ -12,7 +12,7 @@ var content = document.createElement( "div" ); content.innerHTML = payload.section.text; - content.id = "#content_block_0"; + content.id = "content_block_0"; content = transformer.transform( "leadSection", content ); content = transformer.transform( "section", content ); document.getElementById( "content" ).appendChild( content ); @@ -50,8 +50,12 @@ bridge.sendMessage( "requestSection", { index: payload.index + 1 } ); } else { document.getElementById( "loading_sections").className = ""; - if ( typeof payload.fragment === "string" ) { - scrollToSection( payload.fragment ); + if ( typeof payload.scrollSelector === "string" ) { + var target = document.querySelector( payload.scrollSelector ); + if ( target ) { + var top = target.offsetTop + payload.scrollOffset; + window.scrollTo( 0, top ); + } } } }); -- To view, visit https://gerrit.wikimedia.org/r/116653 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ia08941adbdd09f5c21eda03deb26e40ac26be792 Gerrit-PatchSet: 1 Gerrit-Project: apps/android/wikipedia Gerrit-Branch: master Gerrit-Owner: Brion VIBBER <br...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits