diff --git a/AnkiDroid/src/main/assets/scripts/ankidroid.js b/AnkiDroid/src/main/assets/scripts/ankidroid.js index 2efa4be9f27c..f7d6db78c438 100644 --- a/AnkiDroid/src/main/assets/scripts/ankidroid.js +++ b/AnkiDroid/src/main/assets/scripts/ankidroid.js @@ -12,78 +12,66 @@ globalThis.ankidroid.userAction = function(number) { } }; +globalThis.ankidroid.scale = 1; + (() => { - const swipeDistance = 40; + const maxMovement = 20; let startX = 0, startY = 0, scrollX = 0, scrollY = 0, - tapTimer = null; + tapTimer = null, + isSingleTouch = false; document.ontouchstart = function (event) { + if (event.touches.length > 1) { + isSingleTouch = false; + return; + } startX = event.touches[0].clientX; startY = event.touches[0].clientY; scrollX = window.scrollX; scrollY = window.scrollY; + isSingleTouch = true; }; document.ontouchend = function (event) { - if (tapTimer != null) { - sendRequest("tap://double") - preventEvent(event); - - clearTimeout(tapTimer); - tapTimer = null; - return; - } - tapTimer = setTimeout(() => { - processTap(event); - tapTimer = null; - }, 200); - }; + if (!isSingleTouch || isTextSelected() || isLink(event)) return; - function processTap(event) { - if (isLink(event) || isTextSelected() || window.scrollX !== scrollX || window.scrollY !== scrollY) return; let endX = event.changedTouches[0].clientX, endY = event.changedTouches[0].clientY, - deltaX = endX - startX, - deltaY = endY - startY, - absDeltaX = Math.abs(deltaX), - absDeltaY = Math.abs(deltaY); + deltaX = Math.abs(endX - startX), + deltaY = Math.abs(endY - startY), + threshold = maxMovement / globalThis.ankidroid.scale; + console.log(threshold + " " + deltaX + " " + deltaY); + if (deltaX > threshold || deltaY > threshold) return; - if (absDeltaX > swipeDistance || absDeltaY > swipeDistance) { - if (absDeltaX > absDeltaY) { - if (deltaX > 0) { - sendRequest("swipe://right"); - } else { - sendRequest("swipe://left"); - } - } else { - if (deltaY > 0) { - sendRequest("swipe://down"); - } else { - sendRequest("swipe://up"); - } - } - preventEvent(event); - return; + if (tapTimer != null) { + window.location.href = "tap://double"; + event.preventDefault(); + clearTimeout(tapTimer); + tapTimer = null; + return } - let column = Math.floor(endX / (window.innerWidth / 3)), - row = Math.floor(endY / (window.innerHeight / 3)); + tapTimer = setTimeout(() => { + let column = Math.floor(endX / (window.innerWidth / 3)), + row = Math.floor(endY / (window.innerHeight / 3)); - if (column < 0 || column > 2 || row < 0 || row > 2) return; + if (column < 0 || column > 2 || row < 0 || row > 2) return; - let columnLabels = ["Left", "Center", "Right"], - rowLabels = ["top", "mid", "bottom"]; - column = columnLabels[column]; - row = rowLabels[row]; - let target = row + column; + let columnLabels = ["Left", "Center", "Right"], + rowLabels = ["top", "mid", "bottom"]; + column = columnLabels[column]; + row = rowLabels[row]; + let target = row + column; - sendRequest(`tap://${target}`); - preventEvent(event); + window.location.href = `tap://${target}` + event.preventDefault(); + tapTimer = null; + }, 200); } function isLink(e) { @@ -110,14 +98,4 @@ globalThis.ankidroid.userAction = function(number) { function isTextSelected() { return !document.getSelection().isCollapsed; } - - function preventEvent(e) { - if (e.cancelable) { - e.preventDefault(); - } - } - - function sendRequest(request) { - window.location.href = request; - } })(); \ No newline at end of file diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/CardViewerFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/CardViewerFragment.kt index bfce6969f17e..3718370c3aee 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/CardViewerFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/CardViewerFragment.kt @@ -18,6 +18,8 @@ package com.ichi2.anki.previewer import android.content.Intent import android.net.Uri import android.os.Bundle +import android.view.GestureDetector +import android.view.MotionEvent import android.view.View import android.webkit.CookieManager import android.webkit.WebChromeClient @@ -43,6 +45,7 @@ import com.ichi2.anki.snackbar.showSnackbar import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import timber.log.Timber +import kotlin.math.abs abstract class CardViewerFragment(@LayoutRes layout: Int) : Fragment(layout) { protected abstract val viewModel: CardViewerViewModel @@ -82,6 +85,7 @@ abstract class CardViewerFragment(@LayoutRes layout: Int) : Fragment(layout) { // allow videos to autoplay via our JavaScript eval mediaPlaybackRequiresUserGesture = false } + val baseUrl = CollectionHelper.getMediaDirectory(requireContext()).toURI().toString() loadDataWithBaseURL( baseUrl, @@ -91,6 +95,43 @@ abstract class CardViewerFragment(@LayoutRes layout: Int) : Fragment(layout) { null ) } + + val gestListener = object : GestureDetector.SimpleOnGestureListener() { + @Suppress("KotlinConstantConditions") + override fun onFling( + e1: MotionEvent?, + e2: MotionEvent, + velocityX: Float, + velocityY: Float + ): Boolean { + if (e1 == null) return super.onFling(e1, e2, velocityX, velocityY) + val deltaX = e2.x - e1.x + val deltaY = e2.y - e1.y + + if (abs(deltaX) > abs(deltaY)) { + if (deltaX > 0) { + Timber.d("RIGHT") + } else { + Timber.d("LEFT") + } + } else { + if (deltaY > 0) { + Timber.d("DOWN") + } else { + Timber.d("UP") + } + } + + return super.onFling(e1, e2, velocityX, velocityY) + } + } + + val gest = GestureDetector(requireContext(), gestListener) + + webView.setOnTouchListener { _, event -> gest.onTouchEvent(event) } + +// webView.setOnScrollChangeListener() + viewModel.eval .flowWithLifecycle(lifecycle) .onEach { eval -> @@ -119,55 +160,57 @@ abstract class CardViewerFragment(@LayoutRes layout: Int) : Fragment(layout) { .launchIn(lifecycleScope) } - private fun onCreateWebViewClient(savedInstanceState: Bundle?): WebViewClient { - return object : WebViewClient() { - override fun onPageFinished(view: WebView?, url: String?) { - viewModel.onPageFinished(isAfterRecreation = savedInstanceState != null) - } + open inner class CardViewerWebViewClient(val savedInstanceState: Bundle?) : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + viewModel.onPageFinished(isAfterRecreation = savedInstanceState != null) + } - override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { - return handleOrOpenUrl(request.url) - } + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + return handleOrOpenUrl(request.url) + } - @Suppress("DEPRECATION") // necessary in API 23 - @Deprecated("Deprecated in Java") - override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { - if (view == null || url == null) return super.shouldOverrideUrlLoading(view, url) - return handleOrOpenUrl(url.toUri()) + @Suppress("DEPRECATION") // necessary in API 23 + @Deprecated("Deprecated in Java") + override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { + if (view == null || url == null) return super.shouldOverrideUrlLoading(view, url) + return handleOrOpenUrl(url.toUri()) + } + + protected open fun handleUrl(url: Uri): Boolean { + when (url.scheme) { + "playsound" -> viewModel.playSoundFromUrl(url.toString()) + "videoended" -> viewModel.onVideoFinished() + "videopause" -> viewModel.onVideoPaused() + "tts-voices" -> TtsVoicesDialogFragment().show(childFragmentManager, null) + else -> return false } + return true + } - fun handleOrOpenUrl(url: Uri): Boolean { - if (handleUrl(url)) return true - return try { - openUrl(url) - true - } catch (_: Throwable) { - Timber.w("Could not open url") - false - } + private fun handleOrOpenUrl(url: Uri): Boolean { + if (handleUrl(url)) return true + return try { + openUrl(url) + true + } catch (_: Throwable) { + Timber.w("Could not open url") + false } + } - override fun onReceivedError( - view: WebView, - request: WebResourceRequest, - error: WebResourceError - ) { - viewModel.mediaErrorHandler.processFailure(request) { filename: String -> - showMediaErrorSnackbar(filename) - } + override fun onReceivedError( + view: WebView, + request: WebResourceRequest, + error: WebResourceError + ) { + viewModel.mediaErrorHandler.processFailure(request) { filename: String -> + showMediaErrorSnackbar(filename) } } } - protected open fun handleUrl(url: Uri): Boolean { - when (url.scheme) { - "playsound" -> viewModel.playSoundFromUrl(url.toString()) - "videoended" -> viewModel.onVideoFinished() - "videopause" -> viewModel.onVideoPaused() - "tts-voices" -> TtsVoicesDialogFragment().show(childFragmentManager, null) - else -> return false - } - return true + protected open fun onCreateWebViewClient(savedInstanceState: Bundle?): WebViewClient { + return CardViewerWebViewClient(savedInstanceState) } private fun onCreateWebChromeClient(): WebChromeClient { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt index 1ff542425c13..33a4b0c8396c 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt @@ -24,6 +24,7 @@ import android.text.style.UnderlineSpan import android.view.MenuItem import android.view.View import android.webkit.WebView +import android.webkit.WebViewClient import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.StringRes import androidx.appcompat.view.menu.MenuBuilder @@ -55,6 +56,7 @@ import com.ichi2.anki.utils.navBarNeedsScrim import com.ichi2.libanki.sched.Counts import com.ichi2.utils.increaseHorizontalPaddingOfOverflowMenuIcons import kotlinx.coroutines.launch +import timber.log.Timber class ReviewerFragment : CardViewerFragment(R.layout.reviewer2), @@ -125,7 +127,7 @@ class ReviewerFragment : setupReviewerSettings(view) setupActions(view) setupCounts(view) - lifecycle.addObserver(viewModel.autoAdvance) + lifecycle.addObserver(viewModel.autoAdvance) // TODO remover isso daqui e fazer tipo um onStop() } private fun setupActions(view: View) { @@ -236,16 +238,24 @@ class ReviewerFragment : return true } - override fun handleUrl(url: Uri): Boolean { - if (super.handleUrl(url)) { - return true - } - when (url.scheme) { - "tap" -> viewModel.onTap(url.host!!) - "swipe" -> viewModel.onTap(url.host!!) - else -> return false + override fun onCreateWebViewClient(savedInstanceState: Bundle?): WebViewClient { + return object : CardViewerWebViewClient(savedInstanceState) { + override fun onScaleChanged(view: WebView?, oldScale: Float, newScale: Float) { + super.onScaleChanged(view, oldScale, newScale) + view?.evaluateJavascript("globalThis.ankidroid.scale = $newScale", null) + } + + override fun handleUrl(url: Uri): Boolean { + if (super.handleUrl(url)) { + return true + } + when (url.scheme) { + "tap" -> Timber.d(url.host) + else -> return false + } + return true + } } - return true } private fun setupAnswerButtons(view: View) {