1/* ============================================================= 2 | DigiKash â Mobile Dashboard JS controller 3 | Active only on viewports < 992px (matchMedia guard). 4 | Handles: sticky-header scroll state, scroll-aware tab bar, 5 | balance hide, sheets 6 | (More / Language / Apps / Notifications), wallet carousel 7 | dots, segmented insight switcher. 8 ============================================================= */ 9(function () { 10 "use strict"; 11 12 var mobileMq = window.matchMedia("(max-width: 991.98px)"); 13 14 /* ---------- Body scroll lock ---------- */ 15 var lockCount = 0; 16 var scrollBehaviorReady = false; 17 function lockBody() { 18 lockCount++; 19 document.body.classList.add("dk-scroll-lock"); 20 } 21 function unlockBody() { 22 lockCount = Math.max(0, lockCount - 1); 23 if (lockCount === 0) document.body.classList.remove("dk-scroll-lock"); 24 } 25 26 /* ---------- Sheet / panel registry ---------- */ 27 var openables = []; 28 function registerOpenable(opener) { 29 openables.push(opener); 30 } 31 function closeAll() {
32 openables.forEach(function (o) { try { o.close(); } catch (e) {} }); 33 } 34 35 /* ---------- Sheet helper (works for bottom sheet + side panel) ---------- */ 36 function bindSheet(triggerSelector, panelSelector, backdropSelector, closeSelector) { 37 var panel = document.querySelector(panelSelector); 38 var backdrop = backdropSelector ? document.querySelector(backdropSelector) : null; 39 if (!panel) return null; 40 if (panel.__dkSheetApi) return panel.__dkSheetApi; 41 42 function triggers() { 43 return document.querySelectorAll(triggerSelector); 44 } 45 46 function open() { 47 // close any others first 48 openables.forEach(function (o) { if (o.panel !== panel) o.close(); }); 49 panel.classList.add("is-open"); 50 if (backdrop) backdrop.classList.add("is-open"); 51 panel.setAttribute("aria-hidden", "false"); 52 lockBody(); 53 triggers().forEach(function (t) { t.setAttribute("aria-expanded", "true"); }); 54 } 55 function close() { 56 if (!panel.classList.contains("is-open")) return; 57 panel.classList.remove("is-open"); 58 if (backdrop) backdrop.classList.remove("is-open"); 59 panel.setAttribute("aria-hidden", "true"); 60 unlockBody(); 61 triggers().forEach(function (t) { t.setAttribute("aria-expanded", "false"); }); 62 } 63 64 document.addEventListener("click", function (e) { 65 var trigger = e.target.closest(triggerSelector); 66 if (!trigger) return; 67 68 e.preventDefault(); 69 e.stopPropagation(); 70 71 if (panel.classList.contains("is-open")) close(); 72 else open(); 73 }); 74 75 if (backdrop) backdrop.addEventListener("click", close); 76 if (closeSelector) { 77 panel.querySelectorAll(closeSelector).forEach(function (b) { 78 b.addEventListener("click", close); 79 }); 80 } 81 var api = { open: open, close: close, panel: panel }; 82 panel.__dkSheetApi = api; 83 registerOpenable(api); 84 return api; 85 } 86 87 /* ---------- Sticky header scroll state + scroll-aware tab bar ---------- */ 88 function initScrollBehavior() { 89 if (scrollBehaviorReady) return; 90 scrollBehaviorReady = true; 91 92 var header = document.querySelector(".dk-mobile-header"); 93 var tabbar = document.querySelector(".dk-tabbar"); 94 95 if (document.querySelector("[data-mobile-scroll-stable]")) { 96 if (header) header.setAttribute("data-scrolled", "0"); 97 if (tabbar) tabbar.setAttribute("data-hidden", "0"); 98 99 return; 100 } 101 102 var ticking = false; 103 var lastHeaderState = null; 104 var lastTabbarState = null; 105 106 function update() { 107 var y = window.scrollY || window.pageYOffset || 0; 108 var nextHeaderState = y > 8 ? "1" : "0"; 109 var nextTabbarState = "0"; 110 111 if (header && lastHeaderState !== nextHeaderState) { 112 header.setAttribute("data-scrolled", nextHeaderState); 113 lastHeaderState = nextHeaderState; 114 } 115 116 if (tabbar) { 117 if (lastTabbarState !== nextTabbarState) { 118 tabbar.setAttribute("data-hidden", nextTabbarState); 119 lastTabbarState = nextTabbarState; 120 } 121 } 122 123 ticking = false; 124 } 125 window.addEventListener("scroll", function () { 126 if (!ticking) { 127 window.requestAnimationFrame(update); 128 ticking = true; 129 } 130 }, { passive: true }); 131 update(); 132 } 133 134 /* ---------- Wallet carousel dots ---------- */ 135 function initWalletDots() { 136 var track = document.querySelector(".dk-wallet-track"); 137 var dots = document.querySelectorAll(".dk-dots span"); 138 if (!track || !dots.length) return; 139 function setActive() { 140 var card = track.querySelector(".dk-wallet-card"); 141 if (!card) return; 142 var step = card.offsetWidth + 12; 143 var idx = Math.round(track.scrollLeft / step); 144 idx = Math.max(0, Math.min(dots.length - 1, idx));
145 dots.forEach(function (d, i) { d.setAttribute("data-active", i === idx ? "1" : "0"); }); 146 } 147 track.addEventListener("scroll", function () { window.requestAnimationFrame(setActive); }, { passive: true }); 148 setActive(); 149 } 150 151 /* ---------- Hide / show balance ---------- */ 152 function initBalanceToggle() { 153 var stored = (function () { try { return localStorage.getItem("dk_hide_bal"); } catch (e) { return null; } })(); 154 var hidden = stored === "1"; 155 var balanceTargets = ".dk-wc__amount, [data-balance-mask]"; 156 function apply() { 157 document.querySelectorAll(balanceTargets).forEach(function (el) { 158 el.setAttribute("data-hidden", hidden ? "1" : "0"); 159 }); 160 document.querySelectorAll(".dk-balance-eye i").forEach(function (i) { 161 i.className = hidden ? "fa fa-eye-slash" : "fa fa-eye"; 162 }); 163 document.querySelectorAll(".dk-balance-eye").forEach(function (btn) { 164 btn.setAttribute("aria-pressed", hidden ? "true" : "false"); 165 }); 166 } 167 function toggle(e) { 168 if (e) { 169 e.preventDefault(); 170 e.stopPropagation(); 171 } 172 hidden = !hidden; 173 try { localStorage.setItem("dk_hide_bal", hidden ? "1" : "0"); } catch (er) {} 174 apply(); 175 } 176 document.querySelectorAll(".dk-balance-eye").forEach(function (btn) { 177 if (btn.__dkBalanceToggleBound) return; 178 btn.__dkBalanceToggleBound = true; 179 btn.addEventListener("click", toggle); 180 btn.addEventListener("keydown", function (e) { 181 if (e.key !== "Enter" && e.key !== " ") return; 182 toggle(e); 183 }); 184 }); 185 apply(); 186 } 187 188 /* ---------- Insight segmented control ---------- */ 189 function initInsightSegments() { 190 document.querySelectorAll(".dk-seg").forEach(function (seg) { 191 var btns = seg.querySelectorAll(".dk-seg__btn"); 192 btns.forEach(function (b) { 193 b.addEventListener("click", function () { 194 btns.forEach(function (x) { 195 x.setAttribute("data-active", "0"); 196 x.setAttribute("aria-selected", "false"); 197 }); 198 b.setAttribute("data-active", "1"); 199 b.setAttribute("aria-selected", "true"); 200 var mode = b.getAttribute("data-mode"); 201 var insight = seg.closest(".dk-insight"); 202 if (!insight) return; 203 insight.setAttribute("data-mode", mode); 204 // Toggle big-number panels 205 insight.querySelectorAll("[data-big]").forEach(function (p) { 206 p.style.display = p.getAttribute("data-big") === mode ? "" : "none"; 207 }); 208 // Notify chart 209 try { 210 window.dispatchEvent(new CustomEvent("dk:insight-mode", { detail: { mode: mode } })); 211 } catch (e) {} 212 }); 213 }); 214 }); 215 } 216 217 function notify(type, message) { 218 if (window.notifyEvs && message) { 219 window.notifyEvs(type, message); 220 } 221 } 222 223 function copyTextToClipboard(text) { 224 if (navigator.clipboard && navigator.clipboard.writeText) { 225 return navigator.clipboard.writeText(text); 226 } 227 228 return new Promise(function (resolve, reject) { 229 var ta = document.createElement("textarea"); 230 ta.value = text; 231 ta.setAttribute("readonly", "readonly"); 232 ta.style.position = "fixed"; 233 ta.style.top = "-1000px"; 234 document.body.appendChild(ta); 235 ta.select(); 236 237 try { 238 var ok = document.execCommand("copy"); 239 document.body.removeChild(ta); 240 if (ok) resolve(); 241 else reject(new Error("Copy command failed")); 242 } catch (error) { 243 document.body.removeChild(ta); 244 reject(error); 245 } 246 }); 247 } 248 249 function markWalletQrButton(button) { 250 var label = button.querySelector("[data-wallet-qr-action-label]"); 251 var successLabel = button.getAttribute("data-wallet-qr-success-label"); 252 253 if (label && successLabel && !button.getAttribute("data-wallet-qr-default-label")) { 254 button.setAttribute("data-wallet-qr-default-label", label.textContent); 255 } 256 257 if (label && successLabel) { 258 label.textContent = successLabel; 259 } 260 261 button.classList.add("is-done"); 262 263 setTimeout(function () { 264 if (label && successLabel) { 265 label.textContent = button.getAttribute("data-wallet-qr-default-label"); 266 } 267 button.classList.remove("is-done"); 268 }, 1200); 269 } 270 271 function walletQrSvg(button) { 272 var card = button.closest("[data-wallet-qr-card]"); 273 if (!card) return null; 274 275 return card.querySelector("[data-wallet-qr-svg] svg"); 276 } 277 278 function downloadWalletQr(button) { 279 var svg = walletQrSvg(button); 280 if (!svg) return; 281 282 var source = new XMLSerializer().serializeToString(svg); 283 if (source.indexOf("xmlns=") === -1) { 284 source = source.replace("<svg", '<svg xmlns="http://www.w3.org/2000/svg"'); 285 } 286 287 var blob = new Blob([source], { type: "image/svg+xml;charset=utf-8" }); 288 var url = window.URL.createObjectURL(blob); 289 var link = document.createElement("a"); 290 291 link.href = url; 292 link.download = button.getAttribute("data-wallet-qr-filename") || "wallet-qr.svg"; 293 document.body.appendChild(link); 294 link.click(); 295 document.body.removeChild(link); 296 window.URL.revokeObjectURL(url); 297 markWalletQrButton(button); 298 notify("success", button.getAttribute("data-toast-message") || "QR code downloaded"); 299 } 300 301 function initWalletQrActions() { 302 if (document.documentElement.getAttribute("data-wallet-qr-actions-ready") === "1") return; 303 document.documentElement.setAttribute("data-wallet-qr-actions-ready", "1"); 304 305 document.addEventListener("click", function (event) { 306 var copyButton = event.target.closest("[data-wallet-qr-copy]"); 307 var shareButton = event.target.closest("[data-wallet-qr-share]"); 308 var downloadButton = event.target.closest("[data-wallet-qr-download]"); 309 310 if (copyButton) { 311 event.preventDefault(); 312 copyTextToClipboard(copyButton.getAttribute("data-wallet-qr-link") || "").then(function () { 313 markWalletQrButton(copyButton); 314 notify("success", copyButton.getAttribute("data-toast-message") || "Receive link copied"); 315 }).catch(function () { 316 notify("error", "Could not copy. Please copy the link manually."); 317 }); 318 return; 319 } 320 321 if (shareButton) { 322 event.preventDefault(); 323 324 var shareUrl = shareButton.getAttribute("data-wallet-qr-link") || ""; 325 var shareData = { 326 title: shareButton.getAttribute("data-wallet-qr-title") || document.title, 327 text: shareButton.getAttribute("data-wallet-qr-text") || "", 328 url: shareUrl, 329 }; 330 331 if (navigator.share) { 332 navigator.share(shareData).then(function () { 333 markWalletQrButton(shareButton); 334 }).catch(function () {}); 335 return; 336 } 337 338 copyTextToClipboard(shareUrl).then(function () { 339 markWalletQrButton(shareButton); 340 notify("success", shareButton.getAttribute("data-toast-message") || "Receive link copied"); 341 }).catch(function () { 342 notify("error", "Could not share. Please copy the link manually."); 343 }); 344 return; 345 } 346 347 if (downloadButton) { 348 event.preventDefault(); 349 downloadWalletQr(downloadButton); 350 } 351 }); 352 353 document.querySelectorAll("[data-wallet-qr-link-input]").forEach(function (input) { 354 input.addEventListener("focus", function () { input.select(); }); 355 input.addEventListener("click", function () { input.select(); }); 356 }); 357 } 358 359 /* ---------- Copy buttons ---------- */ 360 function initCopy() { 361 document.querySelectorAll("[data-dk-copy]").forEach(function (btn) { 362 btn.addEventListener("click", function (e) { 363 e.preventDefault(); 364 e.stopPropagation(); 365 var text = btn.getAttribute("data-dk-copy"); 366 if (!text) return; 367 copyTextToClipboard(text).then(function () { 368 btn.classList.add("is-copied"); 369 setTimeout(function () { btn.classList.remove("is-copied"); }, 800); 370 }).catch(function () {}); 371 }); 372 }); 373 } 374 375 /* ---------- KYC dismiss ---------- */ 376 function initKycDismiss() { 377 document.querySelectorAll(".dk-kyc__close").forEach(function (b) { 378 b.addEventListener("click", function () { 379 var card = b.closest(".dk-kyc"); 380 if (card) card.style.display = "none"; 381 }); 382 }); 383 } 384 385 /* ---------- Esc closes panels ---------- */ 386 function initEsc() { 387 document.addEventListener("keydown", function (e) { 388 if (e.key === "Escape") closeAll(); 389 }); 390 } 391 392 /* ---------- Payment QR scanner ---------- */ 393 function initQrScanner() { 394 var modal = document.querySelector("[data-dk-qr-scanner-modal]"); 395 var backdrop = document.querySelector("[data-dk-qr-scanner-backdrop]"); 396 if (!modal || modal.getAttribute("data-ready") === "1") return; 397 398 modal.setAttribute("data-ready", "1"); 399 400 var video = modal.querySelector("[data-dk-qr-scanner-video]"); 401 var canvas = modal.querySelector("[data-dk-qr-scanner-canvas]"); 402 var status = modal.querySelector("[data-dk-qr-scanner-status]"); 403 var input = modal.querySelector("[data-dk-qr-scanner-input]"); 404 var manualForm = modal.querySelector("[data-dk-qr-scanner-manual]"); 405 var permissionButton = modal.querySelector("[data-dk-qr-scanner-permission]"); 406 var closeButtons = modal.querySelectorAll("[data-dk-qr-scanner-close]"); 407 var stream = null; 408 var detector = null; 409 var frameRequest = null; 410 var scanning = false;
411 var lastScanAt = 0; 412 413 function scannerButtons() { 414 return document.querySelectorAll("[data-dk-qr-scanner-open]"); 415 } 416 417 function setScannerButtonsExpanded(isExpanded) { 418 scannerButtons().forEach(function (button) { 419 button.setAttribute("aria-expanded", isExpanded ? "true" : "false"); 420 }); 421 } 422 423 function message(key, fallback) { 424 return modal.getAttribute("data-" + key + "-label") || fallback; 425 } 426 427 function setStatus(text) { 428 if (status) status.textContent = text; 429 } 430 431 function showPermissionButton() { 432 if (permissionButton) permissionButton.hidden = false; 433 } 434 435 function hidePermissionButton() { 436 if (permissionButton) permissionButton.hidden = true; 437 } 438 439 function isPermissionError(error) { 440 return error && ["NotAllowedError", "PermissionDeniedError", "SecurityError"].indexOf(error.name) !== -1; 441 } 442 443 function escapeRegExp(value) { 444 return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); 445 } 446 447 function configuredPaymentLinkBase() { 448 var fallback = window.location.origin + "/payment-link"; 449 var rawBase = modal.getAttribute("data-payment-link-base-url") || fallback; 450 451 try { 452 return new URL(rawBase, window.location.origin); 453 } catch (error) { 454 return new URL(fallback); 455 } 456 } 457 458 function configuredSendMoneyBase() { 459 var fallback = window.location.origin + "/user/send-money/create"; 460 var rawBase = modal.getAttribute("data-send-money-base-url") || fallback; 461 462 try { 463 return new URL(rawBase, window.location.origin); 464 } catch (error) { 465 return new URL(fallback); 466 } 467 } 468 469 function configuredAgentCashOutUrlTemplate() { 470 var fallback = window.location.origin + "/user/agent/qr/__TOKEN__/cash-out"; 471 var rawTemplate = modal.getAttribute("data-agent-cash-out-url-template") || fallback; 472 473 try { 474 return new URL(rawTemplate, window.location.origin); 475 } catch (error) { 476 return new URL(fallback); 477 } 478 } 479 480 function looksLikeWalletId(value) { 481 return /^DK[-A-Z0-9]+$/i.test(value) 482 || /^[0-9]{6,}$/.test(value) 483 || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value); 484 } 485 486 function paymentLinkUrl(value) { 487 var raw = (value || "").trim(); 488 if (!raw) return null; 489 490 if (/^[a-z0-9.-]+\.[a-z]{2,}(\/|$)/i.test(raw)) { 491 raw = "https://" + raw; 492 } 493 494 try { 495 var base = configuredPaymentLinkBase(); 496 var pathPrefix = base.pathname.replace(/\/+$/, "") || "/payment-link"; 497 var allowedOrigins = [window.location.origin]; 498 if (allowedOrigins.indexOf(base.origin) === -1) allowedOrigins.push(base.origin); 499 var url = new URL(raw, window.location.origin); 500 var path = url.pathname.replace(/\/+$/, ""); 501 var routePattern = new RegExp("^" + escapeRegExp(pathPrefix) + "/[A-Za-z0-9_-]+$"); 502 503 if (allowedOrigins.indexOf(url.origin) === -1) return null; 504 if (!routePattern.test(path)) return null; 505 506 return url; 507 } catch (error) { 508 return null; 509 } 510 } 511 512 function sendMoneyUrl(value) { 513 var raw = (value || "").trim(); 514 if (!raw) return null; 515 516 var base = configuredSendMoneyBase(); 517 518 if (looksLikeWalletId(raw)) { 519 var walletUrl = new URL(base.href); 520 walletUrl.searchParams.set("recipient", raw); 521 522 return walletUrl; 523 } 524 525 if (/^[a-z0-9.-]+\.[a-z]{2,}(\/|$)/i.test(raw)) { 526 raw = "https://" + raw; 527 } 528 529 try { 530 var allowedOrigins = [window.location.origin]; 531 if (allowedOrigins.indexOf(base.origin) === -1) allowedOrigins.push(base.origin); 532 533 var url = new URL(raw, window.location.origin); 534 var basePath = base.pathname.replace(/\/+$/, ""); 535 var path = url.pathname.replace(/\/+$/, ""); 536 var recipient = (url.searchParams.get("recipient") || "").trim(); 537 538 if (allowedOrigins.indexOf(url.origin) === -1) return null; 539 if (path !== basePath) return null; 540 if (!recipient) return null; 541 542 return url; 543 } catch (error) { 544 return null; 545 } 546 } 547 548 function agentCashOutUrl(value) { 549 var raw = (value || "").trim(); 550 if (!raw) return null; 551 552 var template = configuredAgentCashOutUrlTemplate(); 553 var tokenPlaceholder = "__TOKEN__";
554 555 if (/^aqr_[A-Za-z0-9_-]+$/.test(raw)) { 556 var tokenUrl = new URL(template.href); 557 tokenUrl.pathname = tokenUrl.pathname.replace(tokenPlaceholder, raw); 558 559 return tokenUrl; 560 } 561 562 if (/^[a-z0-9.-]+\.[a-z]{2,}(\/|$)/i.test(raw)) { 563 raw = "https://" + raw; 564 } 565 566 try { 567 var allowedOrigins = [window.location.origin]; 568 if (allowedOrigins.indexOf(template.origin) === -1) allowedOrigins.push(template.origin); 569 570 var url = new URL(raw, window.location.origin); 571 var templatePath = template.pathname.replace(/\/+$/, ""); 572 var path = url.pathname.replace(/\/+$/, ""); 573 var encodedPlaceholder = encodeURIComponent(tokenPlaceholder); 574 var routePattern = new RegExp( 575 "^" + escapeRegExp(templatePath) 576 .replace(escapeRegExp(tokenPlaceholder), "[A-Za-z0-9_-]+") 577 .replace(escapeRegExp(encodedPlaceholder), "[A-Za-z0-9_-]+") + "$" 578 ); 579 580 if (allowedOrigins.indexOf(url.origin) === -1) return null; 581 if (!routePattern.test(path)) return null; 582 583 return url; 584 } catch (error) { 585 return null; 586 } 587 } 588 589 function paymentQrUrl(value) { 590 return paymentLinkUrl(value) || sendMoneyUrl(value) || agentCashOutUrl(value); 591 } 592 593 function stopCamera() { 594 scanning = false; 595 if (frameRequest) { 596 window.cancelAnimationFrame(frameRequest); 597 frameRequest = null; 598 } 599 if (stream) { 600 stream.getTracks().forEach(function (track) { track.stop(); }); 601 stream = null; 602 } 603 if (video) { 604 video.pause(); 605 video.srcObject = null; 606 } 607 } 608 609 function closeScanner() { 610 if (!modal.classList.contains("is-open")) return; 611 stopCamera(); 612 modal.classList.remove("is-open"); 613 modal.setAttribute("aria-hidden", "true"); 614 if (backdrop) backdrop.classList.remove("is-open"); 615 setScannerButtonsExpanded(false); 616 unlockBody(); 617 } 618 619 function openPayment(value) { 620 var url = paymentQrUrl(value); 621 if (!url) { 622 setStatus(message("invalid", "This QR code is not a valid payment, wallet, or agent QR.")); 623 return false; 624 } 625 626 setStatus(message("detected", "QR code detected. Opening...")); 627 stopCamera(); 628 window.location.href = url.href; 629 630 return true; 631 } 632 633 function scanFrame(now) { 634 if (!scanning || !detector || !video) return; 635 636 if (now - lastScanAt < 220 || video.readyState < 2) { 637 frameRequest = window.requestAnimationFrame(scanFrame); 638 return; 639 } 640 641 lastScanAt = now; 642 detector.detect(video).then(function (codes) { 643 if (codes && codes.length) { 644 var value = codes[0].rawValue || ""; 645 if (openPayment(value)) return; 646 } 647 648 frameRequest = window.requestAnimationFrame(scanFrame); 649 }).catch(function () { 650 frameRequest = window.requestAnimationFrame(scanFrame); 651 }); 652 } 653 654 function requestCamera() { 655 return navigator.mediaDevices.getUserMedia({ 656 video: { facingMode: { ideal: "environment" } }, 657 audio: false, 658 }).catch(function () { 659 return navigator.mediaDevices.getUserMedia({ 660 video: true, 661 audio: false, 662 }); 663 }); 664 } 665 666 function createDetector() { 667 if (!("BarcodeDetector" in window)) { 668 return Promise.resolve(null); 669 } 670 671 return Promise.resolve(
672 window.BarcodeDetector.getSupportedFormats 673 ? window.BarcodeDetector.getSupportedFormats() 674 : ["qr_code"] 675 ).then(function (formats) { 676 if (formats.length && formats.indexOf("qr_code") === -1) { 677 return null; 678 } 679 680 return new window.BarcodeDetector({ formats: ["qr_code"] }); 681 }); 682 } 683 684 function startCamera() { 685 stopCamera(); 686 hidePermissionButton(); 687 setStatus(message("starting", "Starting camera...")); 688 689 if (!video || !navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { 690 setStatus(message("camera-error", "Camera access is blocked or unavailable. Paste the QR link below.")); 691 showPermissionButton(); 692 return; 693 } 694 695 requestCamera().then(function (mediaStream) { 696 if (!modal.classList.contains("is-open")) { 697 mediaStream.getTracks().forEach(function (track) { track.stop(); }); 698 return Promise.reject(new Error("Scanner closed")); 699 } 700 701 stream = mediaStream; 702 video.srcObject = mediaStream; 703 704 return video.play(); 705 }).then(function () { 706 return createDetector(); 707 }).then(function (barcodeDetector) { 708 detector = barcodeDetector; 709 710 if (!detector) { 711 if (canvas) canvas.hidden = true; 712 setStatus(message("unsupported", "Camera QR scanning is not supported on this browser. Paste the QR link below.")); 713 return; 714 } 715 716 return null; 717 }).then(function () { 718 if (!modal.classList.contains("is-open")) { 719 stopCamera(); 720 return; 721 } 722 723 if (!detector) { 724 return; 725 } 726 727 scanning = true; 728 lastScanAt = 0; 729 hidePermissionButton(); 730 setStatus(message("ready", "Point your camera at a payment link or wallet QR code.")); 731 frameRequest = window.requestAnimationFrame(scanFrame); 732 }).catch(function (error) { 733 if (error && error.message === "Scanner closed") return; 734 showPermissionButton(); 735 setStatus( 736 isPermissionError(error) 737 ? message("camera-denied", "Camera permission is blocked. Enable camera access from browser site settings, then tap Allow Camera.") 738 : message("camera-permission", "Camera permission is required to scan QR codes. Tap Allow Camera and approve access.") 739 ); 740 }); 741 } 742 743 function openScanner() { 744 if (modal.classList.contains("is-open")) return; 745 closeAll(); 746 modal.classList.add("is-open"); 747 modal.setAttribute("aria-hidden", "false"); 748 if (backdrop) backdrop.classList.add("is-open"); 749 setScannerButtonsExpanded(true); 750 lockBody(); 751 startCamera(); 752 } 753 754 if (permissionButton) { 755 permissionButton.addEventListener("click", function (event) { 756 event.preventDefault(); 757 startCamera(); 758 }); 759 } 760 761 document.addEventListener("click", function (event) { 762 var button = event.target.closest("[data-dk-qr-scanner-open]"); 763 if (!button) return; 764 765 event.preventDefault(); 766 openScanner(); 767 }); 768 769 closeButtons.forEach(function (button) { 770 button.addEventListener("click", closeScanner); 771 }); 772 773 if (backdrop) { 774 backdrop.addEventListener("click", closeScanner); 775 } 776 777 if (manualForm) { 778 manualForm.addEventListener("submit", function (event) { 779 event.preventDefault(); 780 openPayment(input ? input.value : ""); 781 }); 782 } 783 784 document.addEventListener("keydown", function (event) { 785 if (event.key === "Escape") closeScanner(); 786 }); 787 } 788 789 /* ---------- Init only on mobile widths ---------- */ 790 function bootMobile() { 791 if (!mobileMq.matches) return; 792 bindSheet(".dk-open-more", ".dk-more-sheet", ".dk-more-backdrop", ".dk-sheet-close"); 793 bindSheet(".dk-open-lang", ".dk-lang-sheet", ".dk-lang-backdrop", ".dk-sheet-close"); 794 bindSheet(".dk-open-apps", ".dk-apps-sheet", ".dk-apps-backdrop", ".dk-sheet-close"); 795 bindSheet(".dk-open-notif", ".dk-notif-panel", ".dk-notif-backdrop", ".dk-sheet-close"); 796 initScrollBehavior(); 797 initWalletDots(); 798 initInsightSegments(); 799 initCopy(); 800 initKycDismiss(); 801 initEsc(); 802 } 803 804 function bootShared() { 805 initBalanceToggle(); 806 initQrScanner(); 807 initWalletQrActions(); 808 } 809 810 if (document.readyState === "loading") { 811 document.addEventListener("DOMContentLoaded", function () { 812 bootMobile(); 813 bootShared(); 814 }); 815 } else { 816 bootMobile(); 817 bootShared(); 818 } 819 820 // Also run on viewport change so resizing into mobile activates. 821 if (mobileMq.addEventListener) { 822 mobileMq.addEventListener("change", function () { bootMobile(); }); 823 } else if (mobileMq.addListener) { 824 mobileMq.addListener(function () { bootMobile(); }); 825 } 826 827 // Expose for inline use. 828 window.DKMobile = { 829 closeAll: closeAll, 830 }; 831})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.