From ff18585543432a5dd29b598ac84daf20220a2b10 Mon Sep 17 00:00:00 2001 From: KOWX712 Date: Sun, 24 Nov 2024 21:46:49 +0800 Subject: [PATCH] Initial support for multiple language, add save boot hash in WebUI Language available: en-US, ru-RU, tl-PH, zh-CN, zh-TW. Add feature to save verifiedBootHash in WebUI --- module/webroot/index.html | 175 +++++++++++---------- module/webroot/index.js | 244 +++++++++++++++++++++++++----- module/webroot/locales/en-US.json | 47 ++++++ module/webroot/locales/ru-RU.json | 47 ++++++ module/webroot/locales/tl-PH.json | 47 ++++++ module/webroot/locales/zh-CN.json | 47 ++++++ module/webroot/locales/zh-TW.json | 47 ++++++ module/webroot/styles.css | 174 +++++++++++++++++---- 8 files changed, 672 insertions(+), 156 deletions(-) create mode 100644 module/webroot/locales/en-US.json create mode 100644 module/webroot/locales/ru-RU.json create mode 100644 module/webroot/locales/tl-PH.json create mode 100644 module/webroot/locales/zh-CN.json create mode 100644 module/webroot/locales/zh-TW.json diff --git a/module/webroot/index.html b/module/webroot/index.html index 3b7af45..f878d7a 100644 --- a/module/webroot/index.html +++ b/module/webroot/index.html @@ -1,93 +1,51 @@ - - Document + Document - -
-
Tricky Addon - Update Target List
+ +
+
Tricky Addon - Update Target List
No Connection Icon
-
-
-
- -
-

Instructions

-
    -
  • Save and Update -
      -
    • Save the current configuration and update target.txt immediately.
    • -

    • -
    -
  • -
  • Refresh -
      -
    • Refresh app list and exclude list.
    • -

    • -
    -
  • -
  • Select All & Deselect All -
      -
    • Select or deselect all apps in the current interface.
    • -

    • -
    -
  • -
  • Select From DenyList -
      -
    • Available in Magisk only, select apps that in the DenyList. Recommended.
    • -

    • -
    -
  • -
  • Deselect Unnecessary -
      -
    • Unnecessary category: Xposed module, root manager, root-related apps, and general apps - that never check bootloader status. This option requrie Internet connection.
    • - -

    • -
    -
  • -
  • Set AOSP & Valid Keybox -
      -
    • Replace tricky store keybox.xml
      AOSP keybox: Device - Integrity.
      Valid keybox: Strong Integrity, fallback to Basic - Integrity if revoked.
      AOSP keybox will be replaced if there's no more valid keybox. - Valid keybox option require Internet connection.
    • -

    • -
    -
  • -
  • Set Verified Boot Hash -
      -
    • Get it from KeyAttesation Demo. Fix abnormal boot state by reset ro.boot.vbmeta.digest. -
    • -

    • -
    -
  • -
+
+ +
+ + + + +
+ + +
+ + +
+ + +
+ +
+ +
- +
+ +
-
- -
-
-
- -
- + + +
+
+ +
+

+
    +
    -
    Loading...
    -
    Credit to j-hc/zygisk-detach WebUI
    -
    - + +
    +
    + +
    + +
    +
    + + +
    +
    + +
    +

    Module Name Line 1

    +

    Module Name Line 2

    +

    by KOWX712

    +
    +

    + : + t.me/kowchannel +

    +

    + GitHub: + github.com/KOWX712/Tricky-Addon-Update-Target-List +

    +
    +

    Acknowledgment

    +

    j-hc/zygisk-detach: WebUI template

    +
    +
    +
    + + + + \ No newline at end of file diff --git a/module/webroot/index.js b/module/webroot/index.js index ef6882d..e31ebcf 100644 --- a/module/webroot/index.js +++ b/module/webroot/index.js @@ -1,22 +1,132 @@ -let e = 0; -const appTemplate = document.getElementById("app-template").content; -const appListContainer = document.getElementById("apps-list"); -const loadingIndicator = document.querySelector(".loading"); +// Header Elements +const title = document.querySelector('.header'); +const helpButton = document.getElementById('help-button'); +const noConnection = document.querySelector('.no-connection'); +const languageButton = document.querySelector('.language-button'); +const languageMenu = document.querySelector('.language-menu'); +const languageOptions = document.querySelectorAll('.language-option'); + +// Loading and Prompt Elements +const loadingIndicator = document.querySelector('.loading'); +const prompt = document.getElementById('prompt'); + +// Floating Button +const floatingBtn = document.querySelector('.floating-btn'); + +// Search and Menu Elements +const searchInput = document.getElementById('search'); +const clearBtn = document.getElementById('clear-btn'); const searchMenuContainer = document.querySelector('.search-menu-container'); -const searchInput = document.getElementById("search"); -const clearBtn = document.getElementById("clear-btn"); -const title = document.querySelector('.title-container'); -const noConnection = document.querySelector(".no-connection"); -const helpButton = document.getElementById("help-button"); -const helpOverlay = document.getElementById("help-overlay"); -const closeHelp = document.getElementById("close-help"); const searchCard = document.querySelector('.search-card'); const menu = document.querySelector('.menu'); -const selectDenylistElement = document.getElementById("select-denylist"); -const floatingBtn = document.querySelector('.floating-btn'); +const menuButton = document.getElementById('menu-button'); +const menuOptions = document.getElementById('menu-options'); +const selectDenylistElement = document.getElementById('select-denylist'); + +// Applist Elements +const appTemplate = document.getElementById('app-template').content; +const appListContainer = document.getElementById('apps-list'); + +// Help Overlay Elements +const helpOverlay = document.getElementById('help-overlay'); +const closeHelp = document.getElementById('close-help'); +const helpList = document.getElementById('help-list'); + +// BootHash Overlay Elements +const bootHashOverlay = document.getElementById('boot-hash-overlay'); +const card = document.getElementById('boot-hash-card'); +const inputBox = document.getElementById('boot-hash-input'); +const saveButton = document.getElementById('boot-hash-save-button'); + const basePath = "set-path"; + +// Variables +let e = 0; let excludeList = []; let isRefreshing = false; +let translations = {}; +let currentLang = 'en-US'; + +// Function to detect user's default language +function detectUserLanguage() { + const userLang = navigator.language || navigator.userLanguage; + const langCode = userLang.split('-')[0]; + const availableLanguages = ['en-US', 'ru-RU', 'tl-PH', 'zh-CN', 'zh-TW']; + if (availableLanguages.includes(userLang)) { + return userLang; + } + else if (availableLanguages.includes(langCode)) { + return langCode; + } + else { + return 'en-US'; + } +} + +// Load translations dynamically based on the selected language +async function loadTranslations(lang) { + try { + const response = await fetch(`/locales/${lang}.json`); + translations = await response.json(); + applyTranslations(); + } catch (error) { + console.error(`Error loading translations for ${lang}:`, error); + if (lang !== 'en-US') { + console.log("Falling back to English."); + loadTranslations('en-US'); + } + } +} + +// Function to apply translations to all elements with data-i18n attributes +function applyTranslations() { + document.querySelectorAll("[data-i18n]").forEach((el) => { + const key = el.getAttribute("data-i18n"); + if (translations[key]) { + if (el.hasAttribute("placeholder")) { + el.setAttribute("placeholder", translations[key]); + } else { + el.textContent = translations[key]; + } + } + }); + updateHelpMenu(); +} + +// Language selection event listener +document.querySelectorAll(".language-option").forEach((button) => { + button.addEventListener("click", (e) => { + const lang = e.target.getAttribute("data-lang"); + loadTranslations(lang); + }); +}); + +// Function to dynamically update the help menu +function updateHelpMenu() { + helpList.innerHTML = ""; + const helpSections = [ + { title: "save_and_update_button", description: "save_and_update_description" }, + { title: "refresh", description: "refresh_description" }, + { title: "select_deselect", description: "select_description" }, + { title: "select_denylist", description: "select_denylist_description" }, + { title: "deselect_unnecessary", description: "deselect_unnecessary_description" }, + { title: "set_keybox", description: "set_aosp_keybox_description" }, + { title: "set_verified_boot_hash", description: "set_verified_boot_hash_description" } + ]; + helpSections.forEach((section) => { + const listItem = document.createElement("li"); + listItem.innerHTML = `${translations[section.title]}`; + const description = document.createElement("ul"); + const descriptionItem = document.createElement("li"); + descriptionItem.textContent = translations[section.description] || `[Missing: ${section.description}]`; + description.appendChild(descriptionItem); + listItem.appendChild(description); + helpList.appendChild(listItem); + const emptyLine = document.createElement("li"); + emptyLine.innerHTML = "
    "; + helpList.appendChild(emptyLine); + }); +} // Function to execute shell commands async function execCommand(command) { @@ -172,7 +282,7 @@ async function runExtraScript() { noConnection.style.display = "none"; } catch (error) { console.error("Failed to execute Extra script:", error); - showPrompt("Please check your Internet connection", false); + showPrompt("no_internet", false); noConnection.style.display = "flex"; } } @@ -251,10 +361,10 @@ async function aospkb() { const destinationPath = "/data/adb/tricky_store/keybox.xml"; await execCommand(`xxd -r -p ${sourcePath} | base64 -d > ${destinationPath}`); console.log("AOSP keybox copied successfully."); - showPrompt("AOSP keybox set successfully."); + showPrompt("aosp_key_set"); } catch (error) { console.error("Failed to copy AOSP keybox:", error); - showPrompt("Failed to update keybox.", false); + showPrompt("key_set_error", false); } } @@ -269,34 +379,30 @@ async function extrakb() { } await execCommand(`xxd -r -p ${sourcePath} | base64 -d > ${destinationPath}`); console.log("Valid keybox copied successfully."); - showPrompt("Valid keybox set successfully."); + showPrompt("valid_key_set"); } catch (error) { console.error("Failed to copy valid keybox:", error); await aospkb(); - showPrompt("No valid keybox found, replaced with AOSP keybox.", false); + showPrompt("no_valid_fallback", false); } } // Function to handle Verified Boot Hash async function setBootHash() { - const overlay = document.getElementById("overlay"); - const card = document.getElementById("boot-hash-card"); - const inputBox = document.getElementById("boot-hash-input"); - const saveButton = document.getElementById("save-button"); const showCard = () => { - overlay.style.display = "flex"; + bootHashOverlay.style.display = "flex"; card.style.display = "flex"; requestAnimationFrame(() => { - overlay.classList.add("show"); + bootHashOverlay.classList.add("show"); card.classList.add("show"); }); document.body.style.overflow = "hidden"; }; const closeCard = () => { - overlay.classList.remove("show"); + bootHashOverlay.classList.remove("show"); card.classList.remove("show"); setTimeout(() => { - overlay.style.display = "none"; + bootHashOverlay.style.display = "none"; card.style.display = "none"; document.body.style.overflow = "auto"; }, 200); @@ -312,27 +418,60 @@ async function setBootHash() { console.warn("Failed to read boot_hash file. Defaulting to empty input."); inputBox.value = ""; } - // Save button click handler saveButton.addEventListener("click", async () => { const inputValue = inputBox.value.trim(); try { await execCommand(`echo "${inputValue}" > /data/adb/boot_hash`); await execCommand(`su -c resetprop -n ro.boot.vbmeta.digest ${inputValue}`); - showPrompt("Verified Boot Hash saved successfully."); + showPrompt("boot_hash_set"); closeCard(); } catch (error) { console.error("Failed to update boot_hash:", error); - showPrompt("Failed to update Verified Boot Hash.", false); + showPrompt("boot_hash_set_error", false); } }); - overlay.addEventListener("click", (event) => { - if (event.target === overlay) closeCard(); + bootHashOverlay.addEventListener("click", (event) => { + if (event.target === bootHashOverlay) closeCard(); }); } +// Function to show about overlay +function aboutMenu() { + const aboutOverlay = document.getElementById('about-overlay'); + const menu = document.getElementById('about-menu'); + const closeAbout = document.getElementById('close-about'); + const showMenu = () => { + aboutOverlay.style.display = 'flex'; + setTimeout(() => { + aboutOverlay.style.opacity = '1'; + menu.style.opacity = '1'; + }, 10); + document.body.style.overflow = 'hidden'; + }; + const hideMenu = () => { + aboutOverlay.style.opacity = '0'; + menu.style.opacity = '0'; + setTimeout(() => { + aboutOverlay.style.display = 'none'; + document.body.style.overflow = 'auto'; + }, 200); + }; + showMenu(); + closeAbout.addEventListener('click', (event) => { + event.stopPropagation(); + hideMenu(); + }); + aboutOverlay.addEventListener('click', (event) => { + if (!menu.contains(event.target)) { + hideMenu(); + } + }); + menu.addEventListener('click', (event) => event.stopPropagation()); +} + // Function to show the prompt with a success or error message -function showPrompt(message, isSuccess = true) { - const prompt = document.getElementById('prompt'); +function showPrompt(key, isSuccess = true) { + const message = translations[key] || key; prompt.textContent = message; prompt.classList.toggle('error', !isSuccess); if (window.promptTimeout) { @@ -350,9 +489,7 @@ function showPrompt(message, isSuccess = true) { // Function to toggle menu option function setupMenuToggle() { - const menuButton = document.getElementById('menu-button'); const menuIcon = menuButton.querySelector('.menu-icon'); - const menuOptions = document.getElementById('menu-options'); let menuOpen = false; let menuAnimating = false; @@ -378,7 +515,7 @@ function setupMenuToggle() { } }); - const closeMenuItems = ['refresh', 'select-all', 'deselect-all', 'select-denylist', 'deselect-unnecessary', 'aospkb', 'extrakb', 'boot-hash']; + const closeMenuItems = ['refresh', 'select-all', 'deselect-all', 'select-denylist', 'deselect-unnecessary', 'aospkb', 'extrakb', 'boot-hash', 'about']; closeMenuItems.forEach(id => { const item = document.getElementById(id); if (item) { @@ -482,14 +619,14 @@ document.getElementById("save").addEventListener("click", async () => { // Execute UpdateTargetList.sh try { await execCommand("/data/adb/tricky_store/UpdateTargetList.sh"); - showPrompt("Config and target.txt updated"); + showPrompt("saved_and_updated"); } catch (error) { console.error("Failed to update target list:", error); - showPrompt("Config saved, but failed to update target list", false); + showPrompt("saved_not_updated", false); } } catch (error) { console.error("Failed to update EXCLUDE file:", error); - showPrompt("Failed to save config", false); + showPrompt("save_error", false); } await readExcludeFile(); await refreshAppList(); @@ -497,6 +634,8 @@ document.getElementById("save").addEventListener("click", async () => { // Initial load document.addEventListener('DOMContentLoaded', async () => { + const userLang = detectUserLanguage(); + await loadTranslations(userLang); setupMenuToggle(); document.getElementById("refresh").addEventListener("click", refreshAppList); document.getElementById("select-all").addEventListener("click", selectAllApps); @@ -506,10 +645,32 @@ document.addEventListener('DOMContentLoaded', async () => { document.getElementById("aospkb").addEventListener("click", aospkb); document.getElementById("extrakb").addEventListener("click", extrakb); document.getElementById("boot-hash").addEventListener("click", setBootHash); + document.getElementById("about").addEventListener("click", aboutMenu); await fetchAppList(); checkMagisk(); - runExtraScript(); loadingIndicator.style.display = "none"; + runExtraScript(); +}); + +// Toggle the visibility of the language menu when clicking the button +languageButton.addEventListener("click", (event) => { + event.stopPropagation(); + const isVisible = languageMenu.classList.contains("show"); + if (isVisible) { + languageMenu.classList.remove("show"); + } else { + languageMenu.classList.add("show"); + } +}); +document.addEventListener("click", (event) => { + if (!languageButton.contains(event.target) && !languageMenu.contains(event.target)) { + languageMenu.classList.remove("show"); + } +}); +languageOptions.forEach(option => { + option.addEventListener("click", () => { + languageMenu.classList.remove("show"); + }); }); // Scroll event @@ -526,6 +687,9 @@ window.addEventListener('scroll', () => { searchMenuContainer.style.transform = 'translateY(0)'; floatingBtn.style.transform = 'translateY(-100px)'; } + if (languageMenu.classList.contains("show")) { + languageMenu.classList.remove("show"); + } lastScrollY = window.scrollY; }); diff --git a/module/webroot/locales/en-US.json b/module/webroot/locales/en-US.json new file mode 100644 index 0000000..a35a639 --- /dev/null +++ b/module/webroot/locales/en-US.json @@ -0,0 +1,47 @@ +{ + "title": "Tricky Addon - Update Target List", + "search_placeholder": "Search", + "save_and_update_button": "Save and Update", + "boot_hash_save_button": "Save", + "loading": "Loading...", + "boot_hash_input_placeholder": "Paste your verified Boot Hash here", + + "refresh": "Refresh", + "select_all": "Select All", + "deselect_all": "Deselect All", + "select_denylist": "Select From DenyList", + "deselect_unnecessary": "Deselect Unnecessary", + "set_aosp_keybox": "Set AOSP Keybox", + "set_valid_keybox": "Set Valid Keybox", + "set_verified_boot_hash": "Set Verified Boot Hash", + "about": "About", + + "help_instructions": "Instructions", + "save_and_update_description": "Save the current configuration and update target.txt immediately.", + "refresh_description": "Refresh app list and exclude list.", + "select_deselect": "Select & Deselect All", + "select_description": "Select or deselect all apps in the current interface.", + "select_denylist_description": "Available in Magisk only, select apps that are in the DenyList. Recommended.", + "deselect_unnecessary_description": "Unnecessary category: Xposed module, root manager, root-related apps, and general apps that never check bootloader status. This option requires an Internet connection.", + "set_keybox": "Set AOSP & Valid Keybox", + "set_aosp_keybox_description": "Replace tricky store keybox.xml. AOSP keybox will be replaced if there's no more valid keybox. Valid keybox option requires Internet connection.", + "set_verified_boot_hash_description": "Get verifiedBootHash value from Key Attestation Demo. Fix abnormal boot state by resetting ro.boot.vbmeta.digest.", + + "module_name_line1": "Tricky Addon", + "module_name_line2": "Update Target List", + "by": "by ", + "telegram_channel": "Telegram Channel", + "github": "GitHub", + "acknowledgment": "Acknowledgment", + + "no_internet": "Please check your Internet connection", + "aosp_key_set": "AOSP keybox set successfully", + "key_set_error": "Failed to update keybox", + "valid_key_set": "Valid keybox set successfully", + "no_valid_fallback": "No valid keybox found, replaced with AOSP keybox.", + "boot_hash_set": "Verified Boot Hash saved successfully", + "boot_hash_set_error": "Failed to update Verified Boot Hash", + "saved_and_updated": "Config and target.txt updated", + "saved_not_updated": "Config saved, but failed to update target list", + "save_error": "Failed to save config" +} \ No newline at end of file diff --git a/module/webroot/locales/ru-RU.json b/module/webroot/locales/ru-RU.json new file mode 100644 index 0000000..9f9be19 --- /dev/null +++ b/module/webroot/locales/ru-RU.json @@ -0,0 +1,47 @@ +{ + "title": "Tricky Addon - Обновить список целей", + "search_placeholder": "Поиск", + "save_and_update_button": "Сохранить и обновить", + "boot_hash_save_button": "Сохранить", + "loading": "Загрузка...", + "boot_hash_input_placeholder": "Вставьте свой проверенный Boot Hash сюда", + + "refresh": "Обновить", + "select_all": "Выбрать все", + "deselect_all": "Отменить выбор всех", + "select_denylist": "Выбрать из DenyList", + "deselect_unnecessary": "Отменить выбор ненужных", + "set_aosp_keybox": "Установить AOSP Keybox", + "set_valid_keybox": "Установить действующий Keybox", + "set_verified_boot_hash": "Установить Verified Boot Hash", + "about": "О программе", + + "help_instructions": "Инструкции", + "save_and_update_description": "Сохраните текущую конфигурацию и немедленно обновите target.txt.", + "refresh_description": "Обновить список приложений и список исключений.", + "select_deselect": "Выбрать и отменить выбор всех", + "select_description": "Выбрать или отменить выбор всех приложений в текущем интерфейсе.", + "select_denylist_description": "Доступно только в Magisk, выберите приложения, которые находятся в DenyList. Рекомендуется.", + "deselect_unnecessary_description": "Ненужные категории: модули Xposed, менеджеры root, приложения, связанные с root, и общие приложения, которые никогда не проверяют статус загрузчика. Этот параметр требует подключения к интернету.", + "set_keybox": "Установить AOSP и действующий Keybox", + "set_aosp_keybox_description": "Замените tricky store keybox.xml. AOSP keybox будет заменен, если не будет найден действующий keybox. Опция с действующим keybox требует подключения к интернету.", + "set_verified_boot_hash_description": "Получите значение verifiedBootHash из Key Attestation Demo. Исправьте аномальное состояние загрузки, сбросив ro.boot.vbmeta.digest.", + + "module_name_line1": "Tricky Addon", + "module_name_line2": "Обновить список целей", + "by": "от ", + "telegram_channel": "Канал в Telegram", + "github": "GitHub", + "acknowledgment": "Благодарности", + + "no_internet": "Пожалуйста, проверьте ваше подключение к интернету", + "aosp_key_set": "AOSP keybox успешно установлен", + "key_set_error": "Не удалось обновить keybox", + "valid_key_set": "Действующий keybox успешно установлен", + "no_valid_fallback": "Не найден действующий keybox, заменен на AOSP keybox.", + "boot_hash_set": "Verified Boot Hash успешно сохранен", + "boot_hash_set_error": "Не удалось обновить Verified Boot Hash", + "saved_and_updated": "Конфигурация и target.txt обновлены", + "saved_not_updated": "Конфигурация сохранена, но не удалось обновить список целей", + "save_error": "Не удалось сохранить конфигурацию" +} \ No newline at end of file diff --git a/module/webroot/locales/tl-PH.json b/module/webroot/locales/tl-PH.json new file mode 100644 index 0000000..459146c --- /dev/null +++ b/module/webroot/locales/tl-PH.json @@ -0,0 +1,47 @@ +{ + "title": "Tricky Addon - I-update ang Target List", + "search_placeholder": "Maghanap", + "save_and_update_button": "I-save at I-update", + "boot_hash_save_button": "I-save", + "loading": "Naglo-load...", + "boot_hash_input_placeholder": "I-paste ang iyong verified Boot Hash dito", + + "refresh": "I-refresh", + "select_all": "Piliin Lahat", + "deselect_all": "Huwag Pumili ng Lahat", + "select_denylist": "Piliin mula sa DenyList", + "deselect_unnecessary": "Huwag Pumili ng Hindi Kinakailangan", + "set_aosp_keybox": "I-set ang AOSP Keybox", + "set_valid_keybox": "I-set ang Valid Keybox", + "set_verified_boot_hash": "I-set ang Verified Boot Hash", + "about": "Tungkol", + + "help_instructions": "Mga Tagubilin", + "save_and_update_description": "I-save ang kasalukuyang configuration at i-update ang target.txt agad.", + "refresh_description": "I-refresh ang listahan ng apps at exclude list.", + "select_deselect": "Piliin & Huwag Pumili ng Lahat", + "select_description": "Piliin o huwag piliin ang lahat ng apps sa kasalukuyang interface.", + "select_denylist_description": "Available lang sa Magisk, piliin ang mga app na nasa DenyList. Inirerekomenda.", + "deselect_unnecessary_description": "Hindi kinakailangang kategorya: Xposed module, root manager, root-related apps, at mga karaniwang apps na hindi kailanman nire-refresh ang bootloader status. Nangangailangan ng koneksyon sa internet.", + "set_keybox": "I-set ang AOSP at Valid Keybox", + "set_aosp_keybox_description": "Palitan ang tricky store keybox.xml. Palitan ang AOSP keybox kung walang valid keybox. Nangangailangan ng koneksyon sa internet ang valid keybox option.", + "set_verified_boot_hash_description": "Kunin ang verifiedBootHash mula sa Key Attestation Demo. Ayusin ang abnormal na boot state sa pamamagitan ng pag-reset ng ro.boot.vbmeta.digest.", + + "module_name_line1": "Tricky Addon", + "module_name_line2": "I-update ang Target List", + "by": "ni ", + "telegram_channel": "Telegram Channel", + "github": "GitHub", + "acknowledgment": "Pagkilala", + + "no_internet": "Pakitingnan ang iyong koneksyon sa Internet", + "aosp_key_set": "Matagumpay na na-set ang AOSP Keybox", + "key_set_error": "Nabigong i-update ang keybox", + "valid_key_set": "Matagumpay na na-set ang Valid Keybox", + "no_valid_fallback": "Walang valid na keybox na natagpuan, pinalitan ng AOSP keybox.", + "boot_hash_set": "Matagumpay na na-save ang Verified Boot Hash", + "boot_hash_set_error": "Nabigong i-update ang Verified Boot Hash", + "saved_and_updated": "Na-save ang config at na-update ang target.txt", + "saved_not_updated": "Na-save ang config, ngunit nabigong i-update ang target list", + "save_error": "Nabigong i-save ang config" +} \ No newline at end of file diff --git a/module/webroot/locales/zh-CN.json b/module/webroot/locales/zh-CN.json new file mode 100644 index 0000000..e856266 --- /dev/null +++ b/module/webroot/locales/zh-CN.json @@ -0,0 +1,47 @@ +{ + "title": "TS插件 - 更新目标列表", + "search_placeholder": "搜索", + "save_and_update_button": "保存并更新", + "boot_hash_save_button": "保存", + "loading": "加载中...", + "boot_hash_input_placeholder": "在此粘贴您的哈希值", + + "refresh": "刷新", + "select_all": "全选", + "deselect_all": "取消全选", + "select_denylist": "从排除列表中选择", + "deselect_unnecessary": "取消选择非必应用", + "set_aosp_keybox": "设置 AOSP 密钥", + "set_valid_keybox": "设置有效密钥", + "set_verified_boot_hash": "设置哈希值", + "about": "关于", + + "help_instructions": "使用指南", + "save_and_update_description": "保存当前配置并立即更新目标列表(target.txt)。", + "refresh_description": "刷新应用列表和排除列表。", + "select_deselect": "全选 & 取消全选", + "select_description": "选择或取消选择当前界面中的所有应用。", + "select_denylist_description": "仅适用于 Magisk,选择在排除列表中的应用。推荐使用。", + "deselect_unnecessary_description": "非必要分类:Xposed 模块、root 管理器、与 root 相关的应用,以及从不检查 bootloader 状态的通用应用。此功能需连网使用。", + "set_keybox": "设置 AOSP & 有效密钥", + "set_aosp_keybox_description": "替换 Tricky Store 的密钥(keybox.xml)。如果没有有效密钥,将替换为 AOSP 密钥。有效密钥选项需连网使用。", + "set_verified_boot_hash_description": "从 Key Attestation Demo 获取 verifiedBootHash(哈希值)。通过重置 ro.boot.vbmeta.digest 修复异常 boot 状态。", + + "module_name_line1": "TS插件", + "module_name_line2": "更新目标列表", + "by": "作者:", + "telegram_channel": "TG频道", + "github": "GitHub", + "acknowledgment": "特别鸣谢", + + "no_internet": "请检查您的网络连接", + "aosp_key_set": "成功设置 AOSP 密钥", + "key_set_error": "更新密钥失败", + "valid_key_set": "成功设置有效密钥", + "no_valid_fallback": "未找到有效密钥,已替换为 AOSP 密钥。", + "boot_hash_set": "哈希值重置成功", + "boot_hash_set_error": "哈希值重置失败", + "saved_and_updated": "成功保存配置和更新目标列表", + "saved_not_updated": "配置已保存,但更新目标列表失败", + "save_error": "保存配置失败" +} \ No newline at end of file diff --git a/module/webroot/locales/zh-TW.json b/module/webroot/locales/zh-TW.json new file mode 100644 index 0000000..b8ca405 --- /dev/null +++ b/module/webroot/locales/zh-TW.json @@ -0,0 +1,47 @@ +{ + "title": "TS插件 - 更新目標列表", + "search_placeholder": "搜尋", + "save_and_update_button": "保存並更新", + "boot_hash_save_button": "保存", + "loading": "加載中...", + "boot_hash_input_placeholder": "在此粘貼您的哈希值", + + "refresh": "刷新", + "select_all": "全選", + "deselect_all": "取消全選", + "select_denylist": "從排除列表中選擇", + "deselect_unnecessary": "取消選擇非必應用", + "set_aosp_keybox": "設置 AOSP 密鑰", + "set_valid_keybox": "設置有效密鑰", + "set_verified_boot_hash": "設置哈希值", + "about": "關於", + + "help_instructions": "使用指南", + "save_and_update_description": "保存當前配置並立即更新目標列表(target.txt)。", + "refresh_description": "刷新應用列表和排除列表。", + "select_deselect": "全選 & 取消全選", + "select_description": "選擇或取消選擇當前界面中的所有應用。", + "select_denylist_description": "僅適用於 Magisk,選擇在排除列表中的應用。推薦使用。", + "deselect_unnecessary_description": "非必要分類:Xposed 模塊、root 管理器、與 root 相關的應用,以及從不檢查 bootloader 狀態的通用應用。此功能需連網使用。", + "set_keybox": "設置 AOSP & 有效密鑰", + "set_aosp_keybox_description": "替換 Tricky Store 的密鑰(keybox.xml)。如果沒有有效密鑰,將替換為 AOSP 密鑰。有效密鑰選項需連網使用。", + "set_verified_boot_hash_description": "從 Key Attestation Demo 獲取 verifiedBootHash(哈希值)。通過重置 ro.boot.vbmeta.digest 修復異常 boot 狀態。", + + "module_name_line1": "TS插件", + "module_name_line2": "更新目標列表", + "by": "作者:", + "telegram_channel": "TG頻道", + "github": "GitHub", + "acknowledgment": "特別鳴謝", + + "no_internet": "請檢查您的網路連接", + "aosp_key_set": "成功設置 AOSP 密鑰", + "key_set_error": "更新密鑰失敗", + "valid_key_set": "成功設置有效密鑰", + "no_valid_fallback": "未找到有效密鑰,已替換為 AOSP 密鑰。", + "boot_hash_set": "哈希值重置成功", + "boot_hash_set_error": "哈希值重置失敗", + "saved_and_updated": "成功保存配置和更新目標列表", + "saved_not_updated": "配置已保存,但更新目標列表失敗", + "save_error": "保存配置失敗" +} \ No newline at end of file diff --git a/module/webroot/styles.css b/module/webroot/styles.css index 8b69458..e051faf 100644 --- a/module/webroot/styles.css +++ b/module/webroot/styles.css @@ -6,7 +6,7 @@ body { overflow: hidden; } -.title-container { +.header { display: flex; align-items: center; justify-content: space-between; @@ -16,13 +16,12 @@ body { width: calc(100% - 17px); background-color: #F5F5F5; transition: transform 0.3s ease; - z-index: 1000; + z-index: 1100; } #title { font-size: 18px; font-weight: bold; - padding-left: 10px; } .no-connection { @@ -32,18 +31,68 @@ body { } .no-connection .wifi-icon { - width: 20px; - height: 20px; - margin-right: 5px; + width: 18px; + height: 18px; + margin-right: 0; filter: invert(0.6) sepia(0) saturate(0) hue-rotate(180deg) brightness(0.8) contrast(1); } +.language-dropdown { + position: relative; + display: inline-block; +} + +.language-button { + background: none; + border: none; + font-size: 24px; + color: #333; + margin-left: 10px; +} + +.language-menu { + display: flex; + padding: 5px; + flex-direction: column; + position: absolute; + right: 0; + background-color: #fff; + min-width: 120px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + z-index: 2000; + border: 1px solid #ccc; + border-radius: 8px; + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease; +} + +.language-menu.show { + display: block; + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.language-option { + padding: 10px; + text-align: left; + background: none; + border: none; + width: 100%; + font-size: 16px; + color: #333; + width: auto; + white-space: nowrap; +} + .help-button { margin-right: auto; - padding: 0 10px; + padding: 0 7px; background: none; border: none; - font-size: 24px; + font-size: 23px; align-items: center; justify-content: center; } @@ -128,14 +177,14 @@ body { color: inherit; } -.overlay { +.boot-hash-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: rgba(0, 0, 0, 0.5); - z-index: 1000; + z-index: 1200; justify-content: center; align-items: center; opacity: 0; @@ -153,7 +202,7 @@ body { background-color: #fff; border-radius: 18px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); - z-index: 1100; + z-index: 1200; padding: 20px; display: none; flex-direction: column; @@ -162,7 +211,7 @@ body { transition: opacity 0.2s ease; } -.overlay.show { +.boot-hash-overlay.show { visibility: visible; opacity: 1; } @@ -173,10 +222,10 @@ body { } .input-box { - width: calc(100% - 23px); + width: calc(100% - 20px); height: 100px; resize: none; - padding: 10px; + padding: 9px; font-size: 16px; background-color: #FFF; border: 1px solid #ccc; @@ -188,19 +237,86 @@ body { justify-content: flex-start; } -.button { +.boot-hash-save-button { padding: 10px 20px; border: none; border-radius: 38px; font-size: 18px; -} - -.save-button { background-color: #007bff; color: white; margin-left: auto; } +.about-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1100; + display: none; + justify-content: center; + align-items: center; + opacity: 0; + transition: opacity 0.2s ease; +} + +.about-menu { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: #fff; + border-radius: 8px; + padding: 25px 30px; + padding-right: 50px; + z-index: 1200; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); + opacity: 0; + display: flex; + flex-direction: column; + gap: 15px; + transition: opacity 0.2s ease; +} + +.close-about { + position: absolute; + top: 15px; + right: 12px; + background: none; + border: none; + font-size: 18px; + color: #ccc; +} + +.about-content p { + margin: 0; + font-size: 16px; + text-align: left; +} + +.about-content p[data-i18n="module_name_line1"] { + font-size: 26px; +} + +.about-content p[data-i18n="module_name_line2"] { + font-size: 22px; +} + +.about-content p span[data-i18n="by"] { + font-size: 14px; +} + +.about-content p[data-i18n="acknowledgment"] { + font-size: 18px; + text-align: left; +} + +.about-content p:not([data-i18n]) { + font-size: 16px; +} + #apps-list { margin-top: 100px; } @@ -330,7 +446,7 @@ body { .menu-options li { cursor: default; - padding: 15px 12px; + padding: 13px 12px; text-align: left; } @@ -445,19 +561,21 @@ body { } .loading { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; color: #6E6E6E; display: flex; justify-content: center; align-items: center; - height: calc(100vh - 164px); font-size: 24px; + z-index: 1000; } -.acknowledgment { - color: #6E6E6E; - padding: 20px; - text-align: center; - font-size: 14px; +.footer { + padding: 25px; } @media (prefers-color-scheme: dark) { @@ -466,16 +584,19 @@ body { color: #eee; } - .title-container { + .header { background-color: #121212; } + .language-button, + .language-option, .input-box, .help-button { color: #fff; } .help-menu, + .about-menu, .boot-hash-card, .card, .search-input, @@ -491,6 +612,7 @@ body { color: white; } + .language-menu, .input-box, .menu-options, #menu-button {