├── home
├── src
│ ├── mock
│ │ └── mock-config.json5
│ ├── main
│ │ ├── resources
│ │ │ ├── base
│ │ │ │ ├── profile
│ │ │ │ │ ├── backup_config.json
│ │ │ │ │ └── main_pages.json
│ │ │ │ ├── media
│ │ │ │ │ ├── background.png
│ │ │ │ │ ├── foreground.png
│ │ │ │ │ ├── startIcon.png
│ │ │ │ │ └── layered_image.json
│ │ │ │ └── element
│ │ │ │ │ └── color.json
│ │ │ ├── rawfile
│ │ │ │ ├── fox.ico
│ │ │ │ ├── cathrome
│ │ │ │ │ ├── contextMenus.js
│ │ │ │ │ ├── contentSettings.js
│ │ │ │ │ ├── liny.js
│ │ │ │ │ ├── eventListener.js
│ │ │ │ │ ├── cathrome.js
│ │ │ │ │ ├── commands.js
│ │ │ │ │ └── i18n.js
│ │ │ │ └── home.html
│ │ │ ├── dark
│ │ │ │ └── element
│ │ │ │ │ └── color.json
│ │ │ └── resfile
│ │ │ │ └── what
│ │ │ │ └── Paw.svg
│ │ ├── ets
│ │ │ ├── processes
│ │ │ │ └── deveowlopers_options.ets
│ │ │ ├── homebackupability
│ │ │ │ └── HomeBackupAbility.ets
│ │ │ ├── utils
│ │ │ │ ├── environment_tools.ets
│ │ │ │ ├── print_tools.ets
│ │ │ │ ├── resource_tools.ets
│ │ │ │ ├── clipboard_tools.ets
│ │ │ │ ├── performance_tools.ets
│ │ │ │ ├── html_tools.ets
│ │ │ │ ├── audio_tools.ets
│ │ │ │ ├── want_tools.ets
│ │ │ │ ├── link_tools.ets
│ │ │ │ ├── preview_tools.ets
│ │ │ │ ├── any_concurrent_tools.ets
│ │ │ │ ├── permission_tools.ets
│ │ │ │ ├── drag_drop_tools.ets
│ │ │ │ ├── color_tools.ets
│ │ │ │ └── kv_store_tools.ets
│ │ │ ├── components
│ │ │ │ ├── layouts
│ │ │ │ │ └── linysAniContainer.ets
│ │ │ │ ├── toggles
│ │ │ │ │ ├── linysLockToggle.ets
│ │ │ │ │ └── linysSwitchWithText.ets
│ │ │ │ ├── texts
│ │ │ │ │ ├── linysTextSubtitleDivision.ets
│ │ │ │ │ ├── linysTextArea.ets
│ │ │ │ │ ├── linysSymbol.ets
│ │ │ │ │ ├── linysTextTitle.ets
│ │ │ │ │ ├── linysLink.ets
│ │ │ │ │ └── linysText.ets
│ │ │ │ ├── linysProgressInfo.ets
│ │ │ │ ├── buttons
│ │ │ │ │ ├── linysImageButton.ets
│ │ │ │ │ ├── linysTimeoutButtonWithText.ets
│ │ │ │ │ ├── linysCapsuleButtonWithText.ets
│ │ │ │ │ ├── linysCapsuleButton.ets
│ │ │ │ │ ├── linysWindowButton.ets
│ │ │ │ │ ├── linysTimeoutButton.ets
│ │ │ │ │ ├── linysConfirmDenyButtons.ets
│ │ │ │ │ └── linysShowButton.ets
│ │ │ │ ├── linysFileNameErrorDisplay.ets
│ │ │ │ ├── linysLockSlider.ets
│ │ │ │ ├── linysProgress.ets
│ │ │ │ └── linysPathTree.ets
│ │ │ ├── workers
│ │ │ │ ├── Bookmarks_loader.ets
│ │ │ │ ├── Homepage_background_loader.ets
│ │ │ │ ├── History_index_saver.ets
│ │ │ │ ├── History_index_loader.ets
│ │ │ │ └── History_indexer.ets
│ │ │ ├── homeabilitystage
│ │ │ │ └── HomeAbilityStage.ets
│ │ │ ├── blocks
│ │ │ │ ├── contents
│ │ │ │ │ ├── meowWhatsNew.ets
│ │ │ │ │ ├── meowCreditsRepos.ets
│ │ │ │ │ └── meowCreditsUsers.ets
│ │ │ │ ├── panels
│ │ │ │ │ ├── meowAnimationManager.ets
│ │ │ │ │ └── meowHomepageManager.ets
│ │ │ │ └── modules
│ │ │ │ │ └── meowExtensionView.ets
│ │ │ ├── dialogs
│ │ │ │ ├── quicks
│ │ │ │ │ ├── woofQuickUA.ets
│ │ │ │ │ └── woofQuickSE.ets
│ │ │ │ ├── woofControlFrame.ets
│ │ │ │ ├── web
│ │ │ │ │ ├── woofAlert.ets
│ │ │ │ │ ├── woofConfirm.ets
│ │ │ │ │ ├── woofHttpAuthPrompt.ets
│ │ │ │ │ └── woofPrompt.ets
│ │ │ │ ├── managers
│ │ │ │ │ └── ManageListElement.ets
│ │ │ │ ├── prompts
│ │ │ │ │ ├── woofPromptOK.ets
│ │ │ │ │ ├── woofWantJump.ets
│ │ │ │ │ └── woofWantProtectedResources.ets
│ │ │ │ └── contents
│ │ │ │ │ ├── woofQR.ets
│ │ │ │ │ └── woofRecentFaultLogs.ets
│ │ │ ├── objects
│ │ │ │ ├── CatstensionBridgeObj.ets
│ │ │ │ ├── HistoryDataSource.ets
│ │ │ │ └── ExtWebNode.ets
│ │ │ └── hosts
│ │ │ │ ├── bunch_of_user_agents.ets
│ │ │ │ ├── bunch_of_search_engines.ets
│ │ │ │ ├── bunch_of_defaults.ets
│ │ │ │ └── bunch_of_extensions.ets
│ │ └── module.json5
│ ├── test
│ │ ├── List.test.ets
│ │ └── LocalUnit.test.ets
│ └── ohosTest
│ │ ├── ets
│ │ └── test
│ │ │ ├── List.test.ets
│ │ │ └── Ability.test.ets
│ │ └── module.json5
├── .gitignore
├── oh-package.json5
├── hvigorfile.ts
├── obfuscation-rules.txt
└── build-profile.json5
├── .gitattributes
├── AppScope
├── resources
│ └── base
│ │ ├── media
│ │ ├── app_icon.png
│ │ ├── background.png
│ │ ├── foreground.png
│ │ └── layered_image.json
│ │ └── element
│ │ └── string.json
└── app.json5
├── .gitignore
├── oh-package.json5
├── hvigorfile.ts
├── code-linter.json5
├── auto_build.bat
├── auto_build_ohos.bat
├── oh-package-lock.json5
├── LICENSE
├── hvigor
└── hvigor-config.json5
└── README.md
/home/src/mock/mock-config.json5:
--------------------------------------------------------------------------------
1 | {
2 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.ets linguist-language=ts
--------------------------------------------------------------------------------
/home/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /oh_modules
3 | /.preview
4 | /build
5 | /.cxx
6 | /.test
--------------------------------------------------------------------------------
/home/src/main/resources/base/profile/backup_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "allowToBackupRestore": true
3 | }
--------------------------------------------------------------------------------
/home/src/main/resources/base/profile/main_pages.json:
--------------------------------------------------------------------------------
1 | {
2 | "src": [
3 | "pages/Index"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/home/src/main/ets/processes/deveowlopers_options.ets:
--------------------------------------------------------------------------------
1 | // This is long-term temporary
2 | // So it's common to see this empty
--------------------------------------------------------------------------------
/home/src/main/resources/rawfile/fox.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awaLiny2333/LinysBrowser_NEXT/HEAD/home/src/main/resources/rawfile/fox.ico
--------------------------------------------------------------------------------
/home/src/test/List.test.ets:
--------------------------------------------------------------------------------
1 | import localUnitTest from './LocalUnit.test';
2 |
3 | export default function testsuite() {
4 | localUnitTest();
5 | }
--------------------------------------------------------------------------------
/AppScope/resources/base/media/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awaLiny2333/LinysBrowser_NEXT/HEAD/AppScope/resources/base/media/app_icon.png
--------------------------------------------------------------------------------
/AppScope/resources/base/media/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awaLiny2333/LinysBrowser_NEXT/HEAD/AppScope/resources/base/media/background.png
--------------------------------------------------------------------------------
/AppScope/resources/base/media/foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awaLiny2333/LinysBrowser_NEXT/HEAD/AppScope/resources/base/media/foreground.png
--------------------------------------------------------------------------------
/home/src/ohosTest/ets/test/List.test.ets:
--------------------------------------------------------------------------------
1 | import abilityTest from './Ability.test';
2 |
3 | export default function testsuite() {
4 | abilityTest();
5 | }
--------------------------------------------------------------------------------
/AppScope/resources/base/element/string.json:
--------------------------------------------------------------------------------
1 | {
2 | "string": [
3 | {
4 | "name": "app_name",
5 | "value": "BrowserCat"
6 | }
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/home/src/main/resources/base/media/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awaLiny2333/LinysBrowser_NEXT/HEAD/home/src/main/resources/base/media/background.png
--------------------------------------------------------------------------------
/home/src/main/resources/base/media/foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awaLiny2333/LinysBrowser_NEXT/HEAD/home/src/main/resources/base/media/foreground.png
--------------------------------------------------------------------------------
/home/src/main/resources/base/media/startIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awaLiny2333/LinysBrowser_NEXT/HEAD/home/src/main/resources/base/media/startIcon.png
--------------------------------------------------------------------------------
/AppScope/resources/base/media/layered_image.json:
--------------------------------------------------------------------------------
1 | {
2 | "layered-image": {
3 | "background": "$media:background",
4 | "foreground": "$media:foreground"
5 | }
6 | }
--------------------------------------------------------------------------------
/home/src/main/resources/base/media/layered_image.json:
--------------------------------------------------------------------------------
1 | {
2 | "layered-image":
3 | {
4 | "background" : "$media:background",
5 | "foreground" : "$media:foreground"
6 | }
7 | }
--------------------------------------------------------------------------------
/home/src/main/resources/rawfile/cathrome/contextMenus.js:
--------------------------------------------------------------------------------
1 | // Currently empty api.
2 | // TODO: Finish contextMenus.js
3 |
4 | class cathrome_contextMenus {
5 | constructor() {
6 | }
7 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /oh_modules
3 | /local.properties
4 | /.idea
5 | **/build
6 | /.hvigor
7 | .cxx
8 | /.clangd
9 | /.clang-format
10 | /.clang-tidy
11 | **/.test
12 | /.appanalyzer
--------------------------------------------------------------------------------
/home/src/main/resources/rawfile/cathrome/contentSettings.js:
--------------------------------------------------------------------------------
1 | // Currently empty api.
2 | // TODO: Finish contentSettings.js
3 |
4 | class cathrome_contentSettings {
5 | constructor() {
6 | }
7 | }
--------------------------------------------------------------------------------
/home/oh-package.json5:
--------------------------------------------------------------------------------
1 | {
2 | "name": "home",
3 | "version": "1.0.0",
4 | "description": "Meow.",
5 | "main": "",
6 | "author": "",
7 | "license": "",
8 | "dependencies": {}
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/home/src/main/resources/rawfile/home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Meow
7 |
8 |
9 |
--------------------------------------------------------------------------------
/oh-package.json5:
--------------------------------------------------------------------------------
1 | {
2 | "modelVersion": "5.0.0",
3 | "description": "Meow.",
4 | "dependencies": {
5 | },
6 | "devDependencies": {
7 | "@ohos/hypium": "1.0.19",
8 | "@ohos/hamock": "1.0.0"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/hvigorfile.ts:
--------------------------------------------------------------------------------
1 | import { appTasks } from '@ohos/hvigor-ohos-plugin';
2 |
3 | export default {
4 | system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
6 | }
7 |
--------------------------------------------------------------------------------
/home/src/ohosTest/module.json5:
--------------------------------------------------------------------------------
1 | {
2 | "module": {
3 | "name": "home_test",
4 | "type": "feature",
5 | "deviceTypes": [
6 | "default",
7 | "tablet"
8 | ],
9 | "deliveryWithInstall": true,
10 | "installationFree": false
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/home/src/main/resources/rawfile/cathrome/liny.js:
--------------------------------------------------------------------------------
1 | class cathrome_liny {
2 | constructor() {
3 | }
4 |
5 | test(data) {
6 | console.log(`[Extension][CatsBridge][js] Test! data=[${data}]! Is this the same as output from [arkTs]?`);
7 | CatsBridge.liny_test(data);
8 | }
9 | }
--------------------------------------------------------------------------------
/home/src/main/resources/base/element/color.json:
--------------------------------------------------------------------------------
1 | {
2 | "color": [
3 | {
4 | "name": "start_window_background",
5 | "value": "#E1E9E3"
6 | },
7 | {
8 | "name": "block_color",
9 | "value": "#CDD7CD"
10 | },
11 | {
12 | "name": "font_color_title",
13 | "value": "#243B24"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/home/src/main/resources/dark/element/color.json:
--------------------------------------------------------------------------------
1 | {
2 | "color": [
3 | {
4 | "name": "start_window_background",
5 | "value": "#0F1A0F"
6 | },
7 | {
8 | "name": "block_color",
9 | "value": "#2A392A"
10 | },
11 | {
12 | "name": "font_color_title",
13 | "value": "#E1E9E3"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/AppScope/app.json5:
--------------------------------------------------------------------------------
1 | {
2 | "app": {
3 | "bundleName": "meow.liny.browser.cat.uwu",
4 | "vendor": "Linys Workshop of Cats and Catzardry",
5 | "versionCode": 1000028,
6 | "versionName": "1.7.10",
7 | "icon": "$media:layered_image",
8 | "label": "$string:app_name",
9 | "multiAppMode": {
10 | "multiAppModeType": "appClone",
11 | "maxCount": 5
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/code-linter.json5:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "**/*.ets"
4 | ],
5 | "ignore": [
6 | "**/src/ohosTest/**/*",
7 | "**/src/test/**/*",
8 | "**/src/mock/**/*",
9 | "**/node_modules/**/*",
10 | "**/oh_modules/**/*",
11 | "**/build/**/*",
12 | "**/.preview/**/*"
13 | ],
14 | "ruleSet": [
15 | "plugin:@performance/recommended",
16 | "plugin:@typescript-eslint/recommended"
17 | ],
18 | "rules": {
19 | }
20 | }
--------------------------------------------------------------------------------
/home/src/main/ets/homebackupability/HomeBackupAbility.ets:
--------------------------------------------------------------------------------
1 | import { hilog } from '@kit.PerformanceAnalysisKit';
2 | import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit';
3 |
4 | export default class HomeBackupAbility extends BackupExtensionAbility {
5 | async onBackup() {
6 | hilog.info(0x0000, 'testTag', 'onBackup ok');
7 | }
8 |
9 | async onRestore(bundleVersion: BundleVersion) {
10 | hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion));
11 | }
12 | }
--------------------------------------------------------------------------------
/home/hvigorfile.ts:
--------------------------------------------------------------------------------
1 | import { hapTasks } from '@ohos/hvigor-ohos-plugin';
2 |
3 | export default {
4 | system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
5 | plugins: [], /* Custom plugin to extend the functionality of Hvigor. */
6 | config: {
7 | ohos: {
8 | overrides: {
9 | buildOption: {
10 | arkOptions: {
11 | buildProfileFields: {
12 | linysBuildTime: (new Date().toLocaleString()),
13 | }
14 | }
15 | }
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/home/src/main/ets/utils/environment_tools.ets:
--------------------------------------------------------------------------------
1 | import { display } from '@kit.ArkUI';
2 |
3 | /**
4 | * Retrieves the context from AppStorage (from abilityStage).
5 | * @returns Context.
6 | * */
7 | export function meowContext() {
8 | return AppStorage.get('context') as Context;
9 | }
10 |
11 | /**
12 | * Converts px to vp.
13 | * @param px The true px.
14 | * @returns The equivalent vp.
15 | * */
16 | export function meowPx2vp(px: number) {
17 | try {
18 | return px / display.getDefaultDisplaySync().densityPixels;
19 | } catch (e) {
20 | // This is not good.
21 | console.error('[px2vp] Error: ' + e);
22 | return px;
23 | }
24 | }
--------------------------------------------------------------------------------
/auto_build.bat:
--------------------------------------------------------------------------------
1 | "C:\Program Files\Huawei\DevEco Studio\tools\node\node.exe" "C:\Program Files\Huawei\DevEco Studio\tools\hvigor\bin\hvigorw.js" --sync -p product=default --analyze=normal --parallel --incremental --daemon
2 | "C:\Program Files\Huawei\DevEco Studio\tools\node\node.exe" "C:\Program Files\Huawei\DevEco Studio\tools\hvigor\bin\hvigorw.js" --mode module -p product=default assembleHap --analyze=normal --parallel --incremental --daemon
3 |
4 | set PROJECT_PATH="E:\linys\Harmony_Projects\Linys_Browser_NEXT\"
5 |
6 | echo F| xcopy %PROJECT_PATH%"home\build\default\outputs\default\home-default-unsigned.hap" %PROJECT_PATH%"build_auto\HMOS-home-default-unsigned.hap" /Y
--------------------------------------------------------------------------------
/home/src/main/resources/rawfile/cathrome/eventListener.js:
--------------------------------------------------------------------------------
1 | // Helper to manage event listeners (similar to chrome.events.Event)
2 | class cathrome_eventListener {
3 | constructor() {
4 | /** @private */
5 | this.listeners = [];
6 | }
7 |
8 | /** @param {function} callback */
9 | addListener(callback) {
10 | this.listeners.push(callback);
11 | }
12 |
13 | /** @param {function} callback */
14 | removeListener(callback) {
15 | this.listeners = this.listeners.filter(l => l !== callback);
16 | }
17 |
18 | /** @param {...any} args */
19 | fire(...args) {
20 | this.listeners.forEach(listener => listener(...args));
21 | }
22 | }
--------------------------------------------------------------------------------
/home/src/main/ets/components/layouts/linysAniContainer.ets:
--------------------------------------------------------------------------------
1 | import { animation_default } from "../../hosts/bunch_of_defaults";
2 |
3 | // @Reusable // Is this ok? Nope, animation would be lost
4 | @Component
5 | struct linysAniContainer {
6 | // Contents
7 | @BuilderParam contents: () => void;
8 | // UI
9 | @State vis: Visibility = Visibility.None;
10 |
11 | build() {
12 | Column({ space: 12 }) {
13 | this.contents();
14 | }
15 | .visibility(this.vis)
16 | .animation(animation_default())
17 | .alignItems(HorizontalAlign.Start)
18 | .justifyContent(FlexAlign.Start)
19 | .onAppear(() => {
20 | this.vis = Visibility.Visible;
21 | })
22 | }
23 | }
24 |
25 | export default linysAniContainer;
--------------------------------------------------------------------------------
/auto_build_ohos.bat:
--------------------------------------------------------------------------------
1 | "D:\Program Files\Huawei\DevEco Studio\tools\node\node.exe" "D:\Program Files\Huawei\DevEco Studio\tools\hvigor\bin\hvigorw.js" --sync -p product=ohos --analyze=normal --parallel --incremental --daemon
2 | "D:\Program Files\Huawei\DevEco Studio\tools\node\node.exe" "D:\Program Files\Huawei\DevEco Studio\tools\hvigor\bin\hvigorw.js" --mode module -p product=ohos assembleHap --analyze=normal --parallel --incremental --daemon
3 |
4 | set PROJECT_PATH="E:\linys\Harmony_Projects\Linys_Browser_NEXT\"
5 |
6 | echo F| xcopy %PROJECT_PATH%"home\build\ohos\outputs\default\home-default-unsigned.hap" %PROJECT_PATH%"build_auto\OHOS-home-default-unsigned.hap" /Y
7 | echo F| xcopy %PROJECT_PATH%"home\build\ohos\outputs\default\home-default-signed.hap" %PROJECT_PATH%"build_auto\entry.hap" /Y
8 |
--------------------------------------------------------------------------------
/home/src/main/resources/rawfile/cathrome/cathrome.js:
--------------------------------------------------------------------------------
1 | class cathrome {
2 | constructor() {
3 | // Chrome apis
4 | this.alarms = new cathrome_alarms();
5 | this.action = new cathrome_action();
6 | this.bookmarks = new cathrome_bookmarks();
7 | this.browsingData = new cathrome_browsingData();
8 | this.commands = new cathrome_commands();
9 | this.contentSettings = new cathrome_contentSettings();
10 | this.contextMenus = new cathrome_contextMenus();
11 | this.declarativeNetRequest = new cathrome_declarativeNetRequest();
12 | this.i18n = new cathrome_i18n();
13 |
14 | // BrowserCat stuffs
15 | this.liny = new cathrome_liny();
16 | this.liny.test('New cathrome constructed! uwu_!!!');
17 | }
18 | }
19 |
20 | chrome = new cathrome();
--------------------------------------------------------------------------------
/home/src/main/ets/utils/print_tools.ets:
--------------------------------------------------------------------------------
1 | import { print } from '@kit.BasicServicesKit';
2 |
3 | /**
4 | * Print web!
5 | * @param controller The webview controller.
6 | * */
7 | export function print_web(controller: WebviewController, context: Context) {
8 | let print_adapter: print.PrintDocumentAdapter | undefined = AppStorage.get('print_adapter') as print.PrintDocumentAdapter | undefined;
9 | if (print_adapter == undefined) {
10 | let jobName = 'webPrint' + Date.now().toString();
11 | print_adapter = controller.createWebPrintDocumentAdapter(jobName);
12 | print.print(jobName, print_adapter, null, context).then(() => {
13 | print_adapter = undefined;
14 | AppStorage.set('print_adapter', undefined);
15 | });
16 | } else {
17 | console.log('[Meow][meowWebView] Cannot print! Another print job going on?');
18 | }
19 | }
--------------------------------------------------------------------------------
/home/src/main/ets/utils/resource_tools.ets:
--------------------------------------------------------------------------------
1 | import { meowContext } from './environment_tools';
2 |
3 | /**
4 | * Analyses a ResourceStr and converts it to a string object.
5 | * @param resourceStr A Resource, indicating the identifier of the ResourceStr.
6 | * @returns A string, the string content retrieved.
7 | * @example resource_to_string($r('app.string.Whats_new_content_1'))
8 | * */
9 | export function resource_to_string(resourceStr: Resource, context?: Context) {
10 | try {
11 | if (context) {
12 | return context.resourceManager.getStringSync(resourceStr.id);
13 | }
14 | return meowContext().resourceManager.getStringSync(resourceStr.id);
15 | // TODO: replace deprecated getStringSync when api20 is eventually released
16 | } catch (error) {
17 | console.error('[resource_to_string] ' + error);
18 | return 'meow Error!';
19 | }
20 | }
--------------------------------------------------------------------------------
/home/src/main/ets/utils/clipboard_tools.ets:
--------------------------------------------------------------------------------
1 | import lazy { BusinessError, pasteboard } from '@kit.BasicServicesKit';
2 |
3 | export function copy(content: string) {
4 | let systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard();
5 | let pasteData: pasteboard.PasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, content);
6 |
7 | systemPasteboard.setData(pasteData).then(() => {
8 | console.info('Succeeded in setting PasteData. Copied \"' + content + "\"");
9 | }).catch((err: BusinessError) => {
10 | console.error('Failed to set PasteData. Cause: ' + err.message);
11 | });
12 | }
13 |
14 | // export async function read_first_of_board() {
15 | // let systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard();
16 | // let text: string = (await systemPasteboard.getData()).getPrimaryText();
17 | // return text;
18 | // }
--------------------------------------------------------------------------------
/home/src/main/ets/utils/performance_tools.ets:
--------------------------------------------------------------------------------
1 | import lazy { hidebug } from '@kit.PerformanceAnalysisKit';
2 |
3 | @Concurrent
4 | export function getPss_sync(): bigint {
5 | return hidebug.getPss();
6 | }
7 |
8 | @Concurrent
9 | export function getVss_sync(): bigint {
10 | return hidebug.getVss();
11 | }
12 |
13 | @Concurrent
14 | export function getCPU_sync(): number {
15 | return hidebug.getCpuUsage();
16 | }
17 |
18 | @Concurrent
19 | export function getSharedDirty_sync(): bigint {
20 | return hidebug.getSharedDirty();
21 | }
22 |
23 | @Concurrent
24 | export function getPrivateDirty_sync(): bigint {
25 | return hidebug.getPrivateDirty();
26 | }
27 |
28 | @Concurrent
29 | export function getNativeHeapSize_sync(): bigint {
30 | return hidebug.getNativeHeapSize();
31 | }
32 |
33 | @Concurrent
34 | export function getGraphicsMemory_sync(): number {
35 | return hidebug.getGraphicsMemorySync();
36 | }
37 |
--------------------------------------------------------------------------------
/home/src/main/ets/components/toggles/linysLockToggle.ets:
--------------------------------------------------------------------------------
1 | import { fontSize_Large } from '../../hosts/bunch_of_defaults';
2 |
3 | @Component
4 | struct linysLockToggle {
5 | @Link locked: boolean;
6 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
7 | @State color: ResourceColor | undefined = undefined;
8 | // animation
9 | @State triggerValueReplace: number = 0;
10 |
11 | build() {
12 | SymbolGlyph(this.locked ? $r('sys.symbol.lock') : $r('sys.symbol.lock_open'))
13 | .onClick(() => {
14 | this.locked = !this.locked;
15 | this.triggerValueReplace += 1;
16 | })// .clickEffect(click_effect_default())
17 | .fontSize(fontSize_Large())
18 | .symbolEffect(new ReplaceSymbolEffect(EffectScope.WHOLE), this.triggerValueReplace)
19 | .fontColor([this.color ? this.color : this.color_current_font])
20 | }
21 | }
22 |
23 | export default linysLockToggle
--------------------------------------------------------------------------------
/home/src/main/ets/components/texts/linysTextSubtitleDivision.ets:
--------------------------------------------------------------------------------
1 | import { fontSize_Large } from '../../hosts/bunch_of_defaults';
2 |
3 | @Component
4 | struct linysTextSubtitleDivision {
5 | // Colors
6 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
7 | @Prop icon: ResourceStr = '';
8 | @Prop text: ResourceStr = $r('app.string.HomeAbility_label');
9 | @Prop color: ResourceColor = "";
10 | @Prop max_lines: number = 1;
11 | opa: number = 0.6;
12 |
13 | build() {
14 | Row({ space: 8 }) {
15 | if (this.icon) {
16 | Text(this.icon)
17 | .fontColor(this.color == "" ? this.color_current_font : this.color)
18 | .fontSize(fontSize_Large() + 4)
19 | .fontWeight(FontWeight.Medium)
20 | }
21 | Text(this.text)
22 | .fontColor(this.color == "" ? this.color_current_font : this.color)
23 | .fontSize(fontSize_Large())
24 | .fontWeight(FontWeight.Bold)
25 | }
26 | .opacity(this.opa)
27 | }
28 | }
29 |
30 | export default linysTextSubtitleDivision;
31 |
--------------------------------------------------------------------------------
/oh-package-lock.json5:
--------------------------------------------------------------------------------
1 | {
2 | "meta": {
3 | "stableOrder": true,
4 | "enableUnifiedLockfile": false
5 | },
6 | "lockfileVersion": 3,
7 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
8 | "specifiers": {
9 | "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0",
10 | "@ohos/hypium@1.0.19": "@ohos/hypium@1.0.19"
11 | },
12 | "packages": {
13 | "@ohos/hamock@1.0.0": {
14 | "name": "@ohos/hamock",
15 | "version": "1.0.0",
16 | "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==",
17 | "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har",
18 | "registryType": "ohpm"
19 | },
20 | "@ohos/hypium@1.0.19": {
21 | "name": "@ohos/hypium",
22 | "version": "1.0.19",
23 | "integrity": "sha512-cEjDgLFCm3cWZDeRXk7agBUkPqjWxUo6AQeiu0gEkb3J8ESqlduQLSIXeo3cCsm8U/asL7iKjF85ZyOuufAGSQ==",
24 | "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.19.har",
25 | "registryType": "ohpm"
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/home/src/main/ets/utils/html_tools.ets:
--------------------------------------------------------------------------------
1 | let chars: string[] =
2 | ["\"", "\'", "&", "<", ">", String.fromCharCode(8194), String.fromCharCode(8195), String.fromCharCode(160),
3 | "©", "®", "™", "×", "÷"]
4 | let codes: string[] =
5 | [""", "'", "&", "<", ">", " ", " ", " ",
6 | "©", "®", "™", "×", "÷"]
7 |
8 | export function encode_string_to_html_code(input: string) {
9 | let result = "";
10 | for (let index = 0; index < input.length; index++) {
11 | // Check each character
12 | let char = input.substring(index, index + 1);
13 | if (chars.includes(char)) {
14 | // Encode special chars
15 | result += codes[chars.indexOf(char)];
16 | } else {
17 | // No action for else chars
18 | result += char;
19 | }
20 | }
21 | return result;
22 | }
23 |
24 | export function decode_html_code_to_string(input: string) {
25 | let result = input;
26 | for (let index = 0; index < codes.length; index++) {
27 | result = result.replaceAll(codes[index], chars[index]);
28 | }
29 | return result;
30 | }
--------------------------------------------------------------------------------
/home/obfuscation-rules.txt:
--------------------------------------------------------------------------------
1 | # Define project specific obfuscation rules here.
2 | # You can include the obfuscation configuration files in the current module's build-profile.json5.
3 | #
4 | # For more details, see
5 | # https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
6 |
7 | # Obfuscation options:
8 | # -disable-obfuscation: disable all obfuscations
9 | # -enable-property-obfuscation: obfuscate the property names
10 | # -enable-toplevel-obfuscation: obfuscate the names in the global scope
11 | # -compact: remove unnecessary blank spaces and all line feeds
12 | # -remove-log: remove all console.* statements
13 | # -print-namecache: print the name cache that contains the mapping from the old names to new names
14 | # -apply-namecache: reuse the given cache file
15 |
16 | # Keep options:
17 | # -keep-property-name: specifies property names that you want to keep
18 | # -keep-global-name: specifies names that you want to keep in the global scope
19 |
20 | -enable-property-obfuscation
21 | -enable-toplevel-obfuscation
22 | -enable-filename-obfuscation
23 | -enable-export-obfuscation
--------------------------------------------------------------------------------
/home/src/main/ets/utils/audio_tools.ets:
--------------------------------------------------------------------------------
1 | import lazy { audio } from '@kit.AudioKit';
2 | import lazy { BusinessError } from '@kit.BasicServicesKit';
3 |
4 | export function activate_mix_audio_session() {
5 | let audioManager = audio.getAudioManager();
6 | let audioSessionManager: audio.AudioSessionManager = audioManager.getSessionManager();
7 |
8 | let strategy: audio.AudioSessionStrategy = {
9 | concurrencyMode: audio.AudioConcurrencyMode.CONCURRENCY_MIX_WITH_OTHERS
10 | };
11 |
12 | try {
13 | audioSessionManager.setAudioSessionScene(audio.AudioSessionScene.AUDIO_SESSION_SCENE_MEDIA);
14 | } catch (error) {
15 | console.error(`[activate_mix_audio_session] Failed: ${error}`);
16 | }
17 |
18 | audioSessionManager.activateAudioSession(strategy).then(() => {
19 | console.info('[activate_mix_audio_session] Succeeded in doing activateAudioSession! CONCURRENCY_MIX_WITH_OTHERS!');
20 | }).catch((err: BusinessError) => {
21 | console.error(`[activate_mix_audio_session] Failed to activateAudioSession (CONCURRENCY_MIX_WITH_OTHERS). Code: ${err.code}, message: ${err.message}`);
22 | });
23 | }
--------------------------------------------------------------------------------
/home/src/main/ets/components/linysProgressInfo.ets:
--------------------------------------------------------------------------------
1 | import { animation_default, click_effect_default } from '../hosts/bunch_of_defaults';
2 | import linysText from './texts/linysText';
3 |
4 | @Component
5 | struct linysProgressInfo {
6 | @Prop progress: string = "qwq";
7 | @Prop notification: ResourceStr = "The task desc.";
8 | @State is_hov: boolean = false;
9 |
10 | build() {
11 | Row({ space: 10 }) {
12 | linysText({ text: this.notification, max_lines: 3 })
13 | .layoutWeight(1)
14 | linysText({ text: this.progress })
15 | } // Reindexing indicator
16 | .padding({
17 | left: 15,
18 | right: 15,
19 | top: 8,
20 | bottom: 8
21 | })
22 | .brightness(this.is_hov ? 1.1 : 1)
23 | .backgroundColor($r('sys.color.comp_background_tertiary'))
24 | .animation(animation_default())
25 | .alignItems(VerticalAlign.Center)
26 | .borderRadius(12)
27 | .clickEffect(click_effect_default())
28 | .constraintSize({ maxWidth: 500 })
29 | .onHover((ish) => {
30 | this.is_hov = ish;
31 | })
32 | }
33 | }
34 |
35 | export default linysProgressInfo;
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 awa_Liny
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/home/build-profile.json5:
--------------------------------------------------------------------------------
1 | {
2 | "apiType": "stageMode",
3 | "buildOption": {
4 | "externalNativeOptions": {
5 | "abiFilters": [
6 | "arm64-v8a",
7 | "x86_64"
8 | ]
9 | },
10 | "arkOptions": {
11 | "buildProfileFields": {
12 | "linysBuildTime": "0",
13 | }
14 | },
15 | "sourceOption": {
16 | "workers": [
17 | './src/main/ets/workers/History_indexer.ets',
18 | './src/main/ets/workers/History_index_loader.ets',
19 | './src/main/ets/workers/History_index_saver.ets',
20 | './src/main/ets/workers/Bookmarks_loader.ets',
21 | './src/main/ets/workers/Homepage_background_loader.ets'
22 | ]
23 | },
24 | },
25 | "buildOptionSet": [
26 | {
27 | "name": "release",
28 | "arkOptions": {
29 | "obfuscation": {
30 | "ruleOptions": {
31 | "enable": false,
32 | "files": [
33 | "./obfuscation-rules.txt"
34 | ]
35 | }
36 | }
37 | }
38 | },
39 | ],
40 | "targets": [
41 | {
42 | "name": "default"
43 | },
44 | {
45 | "name": "ohosTest",
46 | }
47 | ]
48 | }
--------------------------------------------------------------------------------
/home/src/main/ets/utils/want_tools.ets:
--------------------------------------------------------------------------------
1 | // Wants
2 |
3 | /**
4 | * Respond to want.
5 | * @param uri the want uri
6 | * @param action the want action
7 | * @param type the want type
8 | * @returns True if a want is detected and processed.
9 | * */
10 | export function check_want(uri: string, action: string, type: string, storage: LocalStorage) {
11 | if (uri.length == 0) {
12 | console.log('[Meow][init] Didn\'t load want for an empty want.');
13 | return false;
14 | }
15 | console.log('[Meow][want] Load want with uri: ' + uri + ', action: ' + action + ', type: ' + type + ', storage id: ' + storage.get('my_window_id'));
16 |
17 | // Respond to want
18 | if (uri.substring(0, 7) == 'file://') {
19 | want_file(uri, storage);
20 | } else {
21 | if (action == 'ohos.want.action.viewData') {
22 | want_web(uri, storage);
23 | }
24 | }
25 | uri = "";
26 | return true;
27 | }
28 |
29 | function want_file(uri: string, storage: LocalStorage) {
30 | console.log('[Meow][Want] Want uri is [' + uri + '].');
31 | storage.set('universal_new_tab_gateway', [uri, false]);
32 | }
33 |
34 | function want_web(uri: string, storage: LocalStorage) {
35 | console.log('[Meow][want] Load want uri: ' + uri);
36 | storage.set('universal_new_tab_gateway', [uri, false]);
37 | }
--------------------------------------------------------------------------------
/home/src/main/ets/components/texts/linysTextArea.ets:
--------------------------------------------------------------------------------
1 | @Component
2 | struct linysTextArea {
3 | @Link text: string;
4 | init_text: string = '';
5 | max_lines: number | undefined = undefined;
6 | // Colors
7 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
8 | @StorageLink('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
9 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
10 | // Actions
11 | onSubmitAction?: (enterKey: EnterKeyType) => void;
12 |
13 | build() {
14 | TextArea({ text: this.init_text }) // linysText({ text: this.link, max_lines: 5 })// link
15 | .maxLines(this.max_lines)
16 | .fontWeight(FontWeight.Regular)
17 | .fontColor(this.color_current_font)
18 | .caretColor(this.color_current_font)
19 | .selectedBackgroundColor(this.color_current_font)
20 | .width("100%")
21 | .padding(10)
22 | .onSubmit((enterKey) => {
23 | if (this.onSubmitAction) {
24 | this.onSubmitAction(enterKey);
25 | }
26 | })
27 | .onChange((v) => {
28 | this.text = v;
29 | })
30 | }
31 | }
32 |
33 | export default linysTextArea;
--------------------------------------------------------------------------------
/home/src/main/ets/components/buttons/linysImageButton.ets:
--------------------------------------------------------------------------------
1 | import { animation_default, click_effect_default, fontSize_Icon_Button } from '../../hosts/bunch_of_defaults';
2 | import { path_2_uri } from '../../utils/storage_tools';
3 |
4 | @Component
5 | struct linysImageButton {
6 | @Prop path: string;
7 | // Settings
8 | can_hover: boolean = false;
9 | radius: number = 10;
10 | // UI
11 | @State isHov: boolean = false;
12 | // Actions
13 | execution?: () => void;
14 |
15 | // aboutToAppear(): void {
16 | // console.log(`[linysImageButton] path=${this.path}`);
17 | // }
18 |
19 | build() {
20 | Image(this.path ? path_2_uri(this.path) : '')
21 | .brightness(this.isHov ? 1.1 : 1)
22 | .animation(animation_default())
23 | .height(fontSize_Icon_Button() + (this.can_hover ? 10 : 0))
24 | .width(fontSize_Icon_Button() + (this.can_hover ? 10 : 0))
25 | .padding(this.can_hover ? 5 : 0)
26 | .borderRadius(this.can_hover ? this.radius : 0)
27 | .clickEffect(click_effect_default())
28 | .onHover((isHv) => {
29 | if (this.can_hover) {
30 | this.isHov = isHv;
31 | }
32 | })
33 | .onClick(() => {
34 | if (this.execution) {
35 | this.execution();
36 | }
37 | })
38 | }
39 | }
40 |
41 | export default linysImageButton;
--------------------------------------------------------------------------------
/home/src/main/ets/workers/Bookmarks_loader.ets:
--------------------------------------------------------------------------------
1 | import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
2 | import { bunch_of_bookmarks } from '../hosts/bunch_of_bookmarks';
3 |
4 | const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
5 |
6 | /**
7 | * Defines the event handler to be called when the worker thread receives a message sent by the host thread.
8 | * The event handler is executed in the worker thread.
9 | *
10 | * @param event message data
11 | */
12 | workerPort.onmessage = async (event: MessageEvents) => {
13 | let html_bookmarks = event.data as string;
14 | let bookmarks = new bunch_of_bookmarks('Bookmarks~Meow');
15 | bookmarks.import_html(html_bookmarks, true);
16 | bookmarks.init = true;
17 | workerPort.postMessageWithSharedSendable(bookmarks);
18 | };
19 |
20 | /**
21 | * Defines the event handler to be called when the worker receives a message that cannot be deserialized.
22 | * The event handler is executed in the worker thread.
23 | *
24 | * @param event message data
25 | */
26 | workerPort.onmessageerror = (event: MessageEvents) => {
27 | };
28 |
29 | /**
30 | * Defines the event handler to be called when an exception occurs during worker execution.
31 | * The event handler is executed in the worker thread.
32 | *
33 | * @param event error message
34 | */
35 | workerPort.onerror = (event: ErrorEvent) => {
36 | };
--------------------------------------------------------------------------------
/hvigor/hvigor-config.json5:
--------------------------------------------------------------------------------
1 | {
2 | "modelVersion": "5.0.0",
3 | "dependencies": {
4 | },
5 | "execution": {
6 | // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */
7 | // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */
8 | // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */
9 | // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */
10 | // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */
11 | },
12 | "logging": {
13 | // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
14 | },
15 | "debugging": {
16 | // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
17 | },
18 | "nodeOptions": {
19 | // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/
20 | // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/home/src/main/ets/components/buttons/linysTimeoutButtonWithText.ets:
--------------------------------------------------------------------------------
1 | import { animation_default } from '../../hosts/bunch_of_defaults';
2 | import linysText from '../texts/linysText';
3 | import linysTimeoutButton from './linysTimeoutButton';
4 |
5 | @Component
6 | struct linysTimeoutButtonWithText {
7 | // Info
8 | @State desc_text: ResourceStr = 'desc';
9 | @State button_text: ResourceStr = ' OwO ';
10 | // Settings / Accessibility
11 | @StorageLink('preferred_hand_left_or_right') preferred_hand: string = 'right';
12 | onExecution?: () => void;
13 |
14 | build() {
15 | Row({ space: 10 }) {
16 | Row() {
17 | linysText({ text: this.desc_text, max_lines: 10 })
18 | } // Text
19 | .justifyContent(this.preferred_hand == 'right' ? FlexAlign.End : FlexAlign.Start)
20 | .animation(animation_default())
21 | .layoutWeight(1)
22 |
23 | linysTimeoutButton({
24 | text: this.button_text,
25 | onExecution: () => {
26 | if (this.onExecution) {
27 | this.onExecution();
28 | }
29 | }
30 | }) // Button
31 | }
32 | .justifyContent(this.preferred_hand == 'right' ? FlexAlign.End : FlexAlign.Start)
33 | .direction(this.preferred_hand == 'right' ? Direction.Ltr : Direction.Rtl)
34 | .animation(animation_default())
35 | .width('100%')
36 | }
37 | }
38 |
39 | export default linysTimeoutButtonWithText;
--------------------------------------------------------------------------------
/home/src/main/ets/components/texts/linysSymbol.ets:
--------------------------------------------------------------------------------
1 | import { animation_default, click_effect_default, fontSize_Icon_Button } from '../../hosts/bunch_of_defaults';
2 |
3 | // @Reusable // Will this affect anything? // Weird layout bugs
4 | @Component
5 | struct linysSymbol {
6 | @Prop symbol_glyph_target: string = 'sys.symbol.arrow_left';
7 | // Colors
8 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
9 | // Status
10 | @Prop color: ResourceColor = "";
11 | @Prop font_weight: FontWeight = FontWeight.Regular;
12 | @Prop font_size: number | undefined = undefined;
13 | // Settings
14 | can_hover: boolean = false;
15 | radius: number = 10;
16 | // UI
17 | @State isHov: boolean = false;
18 |
19 | build() {
20 | SymbolGlyph($r(this.symbol_glyph_target))
21 | .fontWeight(this.font_weight)
22 | .fontSize(this.font_size || fontSize_Icon_Button())
23 | .fontColor([this.color == "" ? this.color_current_font : this.color])
24 | .brightness(this.isHov ? 1.1 : 1)
25 | .animation(animation_default())
26 | .padding(this.can_hover ? 5 : 0)
27 | .borderRadius(this.can_hover ? this.radius : 0)
28 | .clickEffect(click_effect_default())
29 | .onHover((isHv) => {
30 | if (this.can_hover) {
31 | this.isHov = isHv;
32 | }
33 | })
34 | }
35 | }
36 |
37 | export default linysSymbol
--------------------------------------------------------------------------------
/home/src/main/ets/components/buttons/linysCapsuleButtonWithText.ets:
--------------------------------------------------------------------------------
1 | import { animation_default } from '../../hosts/bunch_of_defaults';
2 | import linysText from '../texts/linysText';
3 | import linysCapsuleButton from './linysCapsuleButton';
4 |
5 | // @Reusable
6 | @Component
7 | struct linysCapsuleButtonWithText {
8 | // Info
9 | @Prop desc_text: ResourceStr = 'desc';
10 | @Prop button_text: ResourceStr = ' OwO ';
11 | // Settings / Accessibility
12 | @StorageLink('preferred_hand_left_or_right') preferred_hand: string = 'right';
13 | onExecution?: () => void;
14 |
15 | build() {
16 | Row({ space: 10 }) {
17 | Row() {
18 | linysText({ text: this.desc_text, max_lines: 10 })
19 | .animation(animation_default())
20 | } // Text
21 | // .opacity(0.9)
22 | .justifyContent(this.preferred_hand == 'right' ? FlexAlign.End : FlexAlign.Start)
23 | .animation(animation_default())
24 | .layoutWeight(1)
25 |
26 | linysCapsuleButton({ text: this.button_text })// Button
27 | .onClick(() => {
28 | if (this.onExecution) {
29 | this.onExecution();
30 | }
31 | })
32 | }
33 | .justifyContent(this.preferred_hand == 'right' ? FlexAlign.End : FlexAlign.Start)
34 | .direction(this.preferred_hand == 'right' ? Direction.Ltr : Direction.Rtl)
35 | .animation(animation_default())
36 | .width('100%')
37 | }
38 | }
39 |
40 | export default linysCapsuleButtonWithText;
--------------------------------------------------------------------------------
/home/src/main/ets/components/texts/linysTextTitle.ets:
--------------------------------------------------------------------------------
1 | import lazy { animation_default } from '../../hosts/bunch_of_defaults';
2 | import { fontSize_Large } from '../../hosts/bunch_of_defaults';
3 |
4 | // @Reusable
5 | @Component
6 | struct linysTextTitle {
7 | // Colors
8 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
9 | @Prop text: ResourceStr = $r('app.string.HomeAbility_label');
10 | @Prop color: ResourceColor = "";
11 | @Prop max_lines: number = 1;
12 | @Prop font_size: number | undefined = undefined;
13 | // UI
14 | can_hover: boolean = false;
15 | @State isHov: boolean = false;
16 |
17 | build() {
18 | Text(this.text)
19 | .brightness(this.isHov ? 1.1 : 1)
20 | .fontSize(this.font_size || fontSize_Large())
21 | .animation(this.can_hover ? animation_default() : { duration: 0 })
22 | .padding(this.can_hover ? {
23 | left: 7,
24 | right: 7,
25 | top: 5,
26 | bottom: 5
27 | } : 0)
28 | .borderRadius(this.can_hover ? 7.5 : 0)
29 | .fontColor(this.color == "" ? this.color_current_font : this.color)
30 | .fontWeight(FontWeight.Bold)
31 | .maxLines(this.max_lines)
32 | .textOverflow({ overflow: TextOverflow.Ellipsis })
33 | .onHover((isHv) => {
34 | if (this.can_hover) {
35 | this.isHov = isHv;
36 | }
37 | })
38 | }
39 | }
40 |
41 | export default linysTextTitle;
42 |
--------------------------------------------------------------------------------
/home/src/main/ets/utils/link_tools.ets:
--------------------------------------------------------------------------------
1 | import { common, OpenLinkOptions } from '@kit.AbilityKit';
2 | import { uniformTypeDescriptor as utd } from '@kit.ArkData';
3 | import { systemShare } from '@kit.ShareKit';
4 | import { BusinessError } from '@kit.BasicServicesKit';
5 |
6 | /**
7 | * Opens uri with filemanager.
8 | * @param uri the uri.
9 | * */
10 | export function open_file_uri(uri: string, context: Context) {
11 | // Open folder
12 | let link: string = 'filemanager://openDirectory';
13 | let openLinkOptions: OpenLinkOptions = {
14 | parameters: {
15 | 'fileUri': uri
16 | }
17 | };
18 | try {
19 | (context as common.UIAbilityContext).openLink(link, openLinkOptions)
20 | } catch (e) {
21 | console.error('[link_tools] ' + e);
22 | }
23 | }
24 |
25 | /**
26 | * Shares a link through system share.
27 | * @param link The link.
28 | * @param title The title.
29 | * */
30 | export function share_link(link: string, title: string, context: Context) {
31 | let data: systemShare.SharedData = new systemShare.SharedData({
32 | utd: utd.UniformDataType.HYPERLINK,
33 | description: link,
34 | title: title,
35 | content: link
36 | });
37 | let controller: systemShare.ShareController = new systemShare.ShareController(data);
38 | controller.show((context as common.UIAbilityContext), {
39 | previewMode: systemShare.SharePreviewMode.DEFAULT,
40 | selectionMode: systemShare.SelectionMode.BATCH
41 | }).catch((e: BusinessError) => {
42 | console.error('[share_link] Error: ' + JSON.stringify(e));
43 | });
44 | }
--------------------------------------------------------------------------------
/home/src/main/resources/resfile/what/Paw.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/home/src/main/ets/utils/preview_tools.ets:
--------------------------------------------------------------------------------
1 | import { BusinessError } from '@kit.BasicServicesKit';
2 | import { filePreview } from '@kit.PreviewKit';
3 | import { common } from '@kit.AbilityKit';
4 | import { meowContext } from './environment_tools';
5 |
6 | const image_suffixes = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg'];
7 | const image_mimes = ['jpeg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg+xml'];
8 |
9 | export function preview_image(uri: string, storage: LocalStorage) {
10 | // Determine file name
11 | const split_name_result = uri.split('/');
12 | const file_name = split_name_result[split_name_result.length-1];
13 | // Determine file suffix
14 | const file_suffix_result = file_name.split('.');
15 | const file_suffix = file_suffix_result[file_suffix_result.length-1];
16 | // Determine mime
17 | let mime: string = 'image/' + image_mimes[image_suffixes.indexOf(file_suffix.toLowerCase())];
18 | // Show
19 | let displayInfo: filePreview.DisplayInfo = {
20 | x: 100,
21 | y: 100,
22 | width: 800,
23 | height: 800
24 | };
25 | let fileInfo: filePreview.PreviewInfo = {
26 | title: '(ฅ^・ﻌ・^)ฅ - ' + file_name,
27 | uri: uri,
28 | mimeType: mime
29 | };
30 |
31 | let context = storage.get('context') as Context;
32 | filePreview.openPreview(context, fileInfo, displayInfo).then(() => {
33 | console.info('[preview_image] Succeeded in opening preview');
34 | }).catch((err: BusinessError) => {
35 | console.error(`[preview_image] Failed to open preview, err.code = ${err.code}, err.message = ${err.message}`);
36 | });
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/home/src/main/ets/components/buttons/linysCapsuleButton.ets:
--------------------------------------------------------------------------------
1 | import {
2 | animation_default, capsule_bar_height, click_effect_default, fontSize_Large
3 | } from '../../hosts/bunch_of_defaults';
4 |
5 | // @Reusable
6 | // Can I?
7 | @Component
8 | struct linysCapsuleButton {
9 | @Prop text: ResourceStr = "";
10 | // Colors
11 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
12 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
13 | @StorageLink('color_current_back') color_current_back: ResourceColor = $r('app.color.font_color_title');
14 | @Prop color_button: ResourceColor = "";
15 | @Prop color_text: ResourceColor = "";
16 | @StorageLink('effects') effects: boolean = false;
17 | opa: number = 0.9;
18 | font_weight: FontWeight = FontWeight.Medium;
19 |
20 | build() {
21 | Button(this.text)
22 | .opacity(this.effects ? 1 : this.opa)
23 | .type(ButtonType.Capsule)
24 | .backgroundColor(this.color_button == "" ? this.color_current_back : this.color_button)
25 | .fontColor(this.color_text == "" ? (this.effects ? this.color_current_font : this.color_current_primary) : this.color_text)
26 | .fontSize(fontSize_Large())
27 | .fontWeight(this.font_weight)
28 | .height(capsule_bar_height())
29 | .border({
30 | width: 2,
31 | color: "transparent"
32 | })
33 | .clickEffect(click_effect_default())
34 | .animation(animation_default())
35 | }
36 | }
37 |
38 | export default linysCapsuleButton;
--------------------------------------------------------------------------------
/home/src/main/ets/components/buttons/linysWindowButton.ets:
--------------------------------------------------------------------------------
1 | import { animation_default } from '../../hosts/bunch_of_defaults';
2 |
3 | @Component
4 | struct linysWindowButton {
5 | @State hover_on: boolean = false;
6 | @State press_on: boolean = false;
7 | @Prop symbol: Resource = $r('sys.symbol.plus');
8 | // Colors
9 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
10 | // Environment
11 | @LocalStorageLink('my_window_alias') my_window_alias: string = '';
12 | @LocalStorageLink('on_focus') on_focus: boolean = true;
13 | // Actions
14 | execution?: () => void;
15 |
16 | build() {
17 | Row() {
18 | SymbolGlyph(this.symbol)
19 | .fontWeight(FontWeight.Normal)
20 | .fontSize(this.hover_on ? 18 : 16)
21 | .fontColor([this.color_current_font])
22 | }
23 | .alignItems(VerticalAlign.Center)
24 | .justifyContent(FlexAlign.Center)
25 | .animation(animation_default())
26 | .backgroundColor(this.hover_on ? $r('sys.color.comp_background_secondary') : undefined)
27 | .brightness(this.press_on ? 1.08 : undefined)
28 | .width(28.5)
29 | .height(28.5)
30 | .borderRadius(4)
31 | .onClick(() => {
32 | if (this.execution) {
33 | this.execution();
34 | }
35 | })
36 | .onTouch((e) => {
37 | if (e.type == TouchType.Down) {
38 | this.press_on = true;
39 | } else {
40 | this.press_on = false;
41 | }
42 | })
43 | .opacity(this.on_focus ? 1 : 0.4)
44 | .onHover((h) => {
45 | this.hover_on = h;
46 | })
47 | }
48 | }
49 |
50 | export default linysWindowButton;
--------------------------------------------------------------------------------
/home/src/main/ets/homeabilitystage/HomeAbilityStage.ets:
--------------------------------------------------------------------------------
1 | import { AbilityStage, Want } from '@kit.AbilityKit';
2 |
3 | export default class HomeAbilityStage extends AbilityStage {
4 | onCreate(): void {
5 | AppStorage.setOrCreate('context', this.context);
6 | }
7 |
8 | onAcceptWant(want: Want): string {
9 | let instanceKey = '';
10 | let windowIds: string[] | undefined = AppStorage.get('windowIds');
11 |
12 | if (want.parameters) {
13 | // instanceKey
14 | let key = want.parameters.instanceKey;
15 | if (key) {
16 | // Specially call for a new window.
17 | console.log('[Meow][HomeAbility][onAcceptWant]! Key = [' + key + ']');
18 | instanceKey = 'Meow_' + key;
19 | } else {
20 | console.log('[Meow][HomeAbility][onAcceptWant]! Key is undefined.');
21 | if (windowIds) {
22 | // Call the last of the already opened windows.
23 | let focus_window_id = AppStorage.get('THE_WANT_HANDLER_OF_THE_WINDOWS') as string;
24 | if (focus_window_id == '') {
25 | // That window is closed;
26 | instanceKey = windowIds[windowIds.length-1];
27 | } else {
28 | // Give want to that window.
29 | instanceKey = focus_window_id;
30 | }
31 | } else {
32 | // Initial key for the very first window of the entire app.
33 | instanceKey = 'Meow_meow';
34 | }
35 | }
36 | }
37 | // So that HomeAbility class could read and use this as window id.
38 | AppStorage.setOrCreate('LAST_WINDOW_INSTANCE_KEY', instanceKey);
39 |
40 | // Report back
41 | return instanceKey;
42 | }
43 | }
--------------------------------------------------------------------------------
/home/src/main/ets/blocks/contents/meowWhatsNew.ets:
--------------------------------------------------------------------------------
1 | import linysText from '../../components/texts/linysText';
2 | import { animation_default } from '../../hosts/bunch_of_defaults';
3 | import { resource_to_string } from '../../utils/resource_tools';
4 |
5 | @Component
6 | struct meowWhatsNew {
7 | @State new_things_resource: Resource = $r("app.string.Whats_new_content");
8 | @State new_things: string = resource_to_string(this.new_things_resource);
9 | @State new_things_list: string[] = this.new_things.split('\n');
10 |
11 | build() {
12 | Column({ space: 8 }) {
13 | ForEach(this.new_things_list, (text: string, index: number) => {
14 | Row({ space: 5 }) {
15 | linysText({ text: (index + 1).toString() + '.' })
16 | linysText({
17 | text: text,
18 | max_lines: 48
19 | })
20 | .opacity(0.8)
21 | .layoutWeight(1)
22 | }
23 | // .width('90%')
24 | .alignItems(VerticalAlign.Top)
25 | })
26 | }
27 | .alignItems(HorizontalAlign.Start)
28 | .padding(10)
29 | .borderRadius(13.5)
30 | .backgroundColor($r('sys.color.comp_background_tertiary'))
31 | .width("100%")
32 | .animation(animation_default())
33 | .onAreaChange(() => {
34 | this.update_resource_string();
35 | })
36 | }
37 |
38 | /**
39 | * Convert the Resource to string again.
40 | *
41 | * Usually called to deal with the change of language.
42 | * */
43 | update_resource_string() {
44 | this.new_things = resource_to_string(this.new_things_resource);
45 | this.new_things_list = this.new_things.split('\n');
46 | }
47 | }
48 |
49 | export default meowWhatsNew;
--------------------------------------------------------------------------------
/home/src/test/LocalUnit.test.ets:
--------------------------------------------------------------------------------
1 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
2 |
3 | export default function localUnitTest() {
4 | describe('localUnitTest', () => {
5 | // Defines a test suite. Two parameters are supported: test suite name and test suite function.
6 | beforeAll(() => {
7 | // Presets an action, which is performed only once before all test cases of the test suite start.
8 | // This API supports only one parameter: preset action function.
9 | });
10 | beforeEach(() => {
11 | // Presets an action, which is performed before each unit test case starts.
12 | // The number of execution times is the same as the number of test cases defined by **it**.
13 | // This API supports only one parameter: preset action function.
14 | });
15 | afterEach(() => {
16 | // Presets a clear action, which is performed after each unit test case ends.
17 | // The number of execution times is the same as the number of test cases defined by **it**.
18 | // This API supports only one parameter: clear action function.
19 | });
20 | afterAll(() => {
21 | // Presets a clear action, which is performed after all test cases of the test suite end.
22 | // This API supports only one parameter: clear action function.
23 | });
24 | it('assertContain', 0, () => {
25 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
26 | let a = 'abc';
27 | let b = 'b';
28 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
29 | expect(a).assertContain(b);
30 | expect(a).assertEqual(a);
31 | });
32 | });
33 | }
--------------------------------------------------------------------------------
/home/src/main/ets/blocks/contents/meowCreditsRepos.ets:
--------------------------------------------------------------------------------
1 | import linysText from '../../components/texts/linysText';
2 | import { animation_default } from '../../hosts/bunch_of_defaults';
3 | import { resource_to_string } from '../../utils/resource_tools';
4 | import { common } from '@kit.AbilityKit';
5 |
6 | @Component
7 | struct meowCreditsRepos {
8 | @State credits_repos_and_projs_resource: Resource = $r("app.string.Credits_repos_and_projs");
9 | @State credits_repos_and_projs: string = resource_to_string(this.credits_repos_and_projs_resource);
10 | @State credits_repos_and_projs_list: string[] = this.credits_repos_and_projs.split('\n');
11 |
12 | build() {
13 | Column({ space: 8 }) {
14 | linysText({
15 | text: $r('app.string.Credits'),
16 | max_lines: 3
17 | })
18 |
19 | ForEach(this.credits_repos_and_projs_list, (text: string, index: number) => {
20 | linysText({
21 | text: text,
22 | max_lines: 48
23 | })
24 | .opacity(0.8)
25 | })
26 | }
27 | .alignItems(HorizontalAlign.Start)
28 | .padding(10)
29 | .borderRadius(13.5)
30 | .backgroundColor($r('sys.color.comp_background_tertiary'))
31 | .width("100%")
32 | .animation(animation_default())
33 | .onAreaChange(() => {
34 | this.update_resource_string();
35 | })
36 | }
37 |
38 | /**
39 | * Convert the Resource to string again.
40 | *
41 | * Usually called to deal with the change of language.
42 | * */
43 | update_resource_string() {
44 | this.credits_repos_and_projs = resource_to_string(this.credits_repos_and_projs_resource);
45 | this.credits_repos_and_projs_list = this.credits_repos_and_projs.split('\n');
46 | }
47 | }
48 |
49 | export default meowCreditsRepos;
50 |
--------------------------------------------------------------------------------
/home/src/main/ets/dialogs/quicks/woofQuickUA.ets:
--------------------------------------------------------------------------------
1 | import meowUAManager from '../../blocks/panels/meowUAManager';
2 | import { animation_default } from '../../hosts/bunch_of_defaults';
3 | import { bunch_of_user_agents } from '../../hosts/bunch_of_user_agents';
4 | import lazy woofControlFrame from '../woofControlFrame';
5 |
6 | @CustomDialog
7 | struct woofQuickUA {
8 | controller: CustomDialogController;
9 | // Color
10 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
11 | // Count
12 | @LocalStorageLink('opened_dialog_controllers') opened_dialog_controllers: CustomDialogController[] = [];
13 |
14 | aboutToAppear(): void {
15 | this.opened_dialog_controllers.push(this.controller);
16 | console.log(`[Dialog][Open] Now dialog count: ${this.opened_dialog_controllers.length}!`);
17 | }
18 |
19 | aboutToDisappear(): void {
20 | this.opened_dialog_controllers.splice(this.opened_dialog_controllers.indexOf(this.controller), 1);
21 | console.log(`[Dialog][Close] Now dialog count: ${this.opened_dialog_controllers.length}!`);
22 | }
23 |
24 | build() {
25 | woofControlFrame({
26 | title: $r('app.string.Settings_general_custom_ua'),
27 | controller: this.controller
28 | }) {
29 | Scroll() {
30 | meowUAManager()
31 | }
32 | .borderRadius(13.5)
33 | .layoutWeight(1)
34 | .scrollable(ScrollDirection.Vertical)
35 | .edgeEffect(EdgeEffect.Spring)
36 | // .backgroundColor(this.effects ? undefined : this.color_current_primary)
37 | .constraintSize({ maxHeight: 92.5 + 48.5 * bunch_of_user_agents.list_of_user_agents.length })
38 | .animation(animation_default())
39 | }
40 | }
41 | }
42 |
43 | export default woofQuickUA;
--------------------------------------------------------------------------------
/home/src/main/ets/utils/any_concurrent_tools.ets:
--------------------------------------------------------------------------------
1 | import { get_folder_size_Sync } from './storage_tools';
2 | import { taskpool } from '@kit.ArkTS';
3 | import { getGraphicsMemory_sync, getPrivateDirty_sync, getPss_sync, getSharedDirty_sync, getVss_sync } from './performance_tools';
4 | import { meowContext } from './environment_tools';
5 |
6 | @Concurrent
7 | function get_sandbox_folder_size_concurrent(path: string): number {
8 | return get_folder_size_Sync(path, true);
9 | }
10 |
11 | /**
12 | * Get size of any folder, including its contents, in sandbox in a concurrent way.
13 | * @param folder_path the folder path.
14 | * @param sandbox_root to set sandbox root as root or not.
15 | * @returns a number of folder size.
16 | * */
17 | export async function get_sandbox_folder_size(folder_path: string, sandbox_root: boolean): Promise {
18 | let path: string;
19 | if (sandbox_root) {
20 | const filesDir = meowContext().filesDir;
21 | path = filesDir + '/' + folder_path;
22 | } else {
23 | path = folder_path;
24 | }
25 | return await taskpool.execute(get_sandbox_folder_size_concurrent, path) as number;
26 | }
27 |
28 | export async function getPss(): Promise {
29 | return await taskpool.execute(getPss_sync) as bigint;
30 | }
31 |
32 | export async function getVss(): Promise {
33 | return await taskpool.execute(getVss_sync) as bigint;
34 | }
35 |
36 | export async function getSharedDirty(): Promise {
37 | return await taskpool.execute(getSharedDirty_sync) as bigint;
38 | }
39 |
40 | export async function getPrivateDirty(): Promise {
41 | return await taskpool.execute(getPrivateDirty_sync) as bigint;
42 | }
43 |
44 | export async function getVRAM(): Promise {
45 | return await taskpool.execute(getGraphicsMemory_sync) as number;
46 | }
--------------------------------------------------------------------------------
/home/src/main/ets/components/toggles/linysSwitchWithText.ets:
--------------------------------------------------------------------------------
1 | import { animation_default } from '../../hosts/bunch_of_defaults';
2 | import linysText from '../texts/linysText';
3 |
4 | // @Reusable
5 | @Component
6 | struct linysSwitchWithText {
7 | // Links
8 | @Prop text: ResourceStr = "";
9 | @Link toggle_state: boolean;
10 | onExecution?: () => void;
11 | // Settings / Accessibility
12 | @StorageLink('preferred_hand_left_or_right') preferred_hand: string = 'right';
13 | // Colors
14 | @StorageLink('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
15 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
16 | @StorageLink('color_current_back') color_current_back: ResourceColor = $r('app.color.font_color_title');
17 | @StorageLink('effects') effects: boolean = false;
18 |
19 | build() {
20 | Row({ space: 10 }) {
21 | Row() {
22 | linysText({ text: this.text, max_lines: 5 })
23 | }
24 | // .opacity(0.9)
25 | .layoutWeight(1)
26 | .justifyContent(FlexAlign.Start)
27 |
28 | Toggle({ isOn: this.toggle_state, type: ToggleType.Switch })
29 | .switchPointColor(this.effects ? this.color_current_font : this.color_current_secondary)
30 | .opacity(!this.toggle_state && this.effects ? 0.85 : 1)
31 | .selectedColor(this.color_current_back)
32 | .onChange(isOn => {
33 | this.toggle_state = isOn;
34 | if (this.onExecution) {
35 | this.onExecution();
36 | }
37 | })
38 | } // Toggle
39 | .direction(this.preferred_hand == 'right' ? Direction.Ltr : Direction.Rtl)
40 | .animation(animation_default())
41 | .width("100%")
42 | }
43 | }
44 |
45 | export default linysSwitchWithText;
--------------------------------------------------------------------------------
/home/src/main/ets/components/linysFileNameErrorDisplay.ets:
--------------------------------------------------------------------------------
1 | import { animation_default } from "../hosts/bunch_of_defaults";
2 | import linysText from "./texts/linysText";
3 |
4 | @Component
5 | struct linysFileNameErrorDisplay {
6 | @Prop file_name: string = 'uwu';
7 | @Prop error_code: number = 0;
8 | chars: string[] = ['', '', '\\', '/', ':', '*', '?', '\'', '<', '>', '|'];
9 |
10 | build() {
11 | Column({ space: 10 }) {
12 | linysText({
13 | text: $r('app.string.Index_downloads_illegal_chars_in_filename'),
14 | max_lines: 3,
15 | is_description: true,
16 | is_expanded: true
17 | }) // File name error prompt
18 | .visibility(this.error_code > 0 ? Visibility.Visible : Visibility.None)
19 | .animation(animation_default())
20 |
21 | Row() {
22 | linysText({
23 | text: $r('app.string.Index_downloads_filename_length'),
24 | max_lines: 3,
25 | })
26 | linysText({
27 | text: this.file_name.length.toString(),
28 | max_lines: 3,
29 | })
30 | } // File name too long
31 | .visibility(this.error_code == 1 ? Visibility.Visible : Visibility.None)
32 | .animation(animation_default())
33 |
34 | Row() {
35 | linysText({
36 | text: $r('app.string.Index_downloads_filename_illegal'),
37 | max_lines: 3,
38 | })
39 | linysText({
40 | text: this.chars[this.error_code],
41 | max_lines: 3,
42 | })
43 | } // File name illegal
44 | .visibility(this.error_code >= 2 ? Visibility.Visible : Visibility.None)
45 | .animation(animation_default())
46 | }
47 | .visibility(this.error_code > 0 ? Visibility.Visible : Visibility.None)
48 | .alignItems(HorizontalAlign.Start)
49 | }
50 | }
51 |
52 | export default linysFileNameErrorDisplay
--------------------------------------------------------------------------------
/home/src/ohosTest/ets/test/Ability.test.ets:
--------------------------------------------------------------------------------
1 | import { hilog } from '@kit.PerformanceAnalysisKit';
2 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
3 |
4 | export default function abilityTest() {
5 | describe('ActsAbilityTest', () => {
6 | // Defines a test suite. Two parameters are supported: test suite name and test suite function.
7 | beforeAll(() => {
8 | // Presets an action, which is performed only once before all test cases of the test suite start.
9 | // This API supports only one parameter: preset action function.
10 | })
11 | beforeEach(() => {
12 | // Presets an action, which is performed before each unit test case starts.
13 | // The number of execution times is the same as the number of test cases defined by **it**.
14 | // This API supports only one parameter: preset action function.
15 | })
16 | afterEach(() => {
17 | // Presets a clear action, which is performed after each unit test case ends.
18 | // The number of execution times is the same as the number of test cases defined by **it**.
19 | // This API supports only one parameter: clear action function.
20 | })
21 | afterAll(() => {
22 | // Presets a clear action, which is performed after all test cases of the test suite end.
23 | // This API supports only one parameter: clear action function.
24 | })
25 | it('assertContain', 0, () => {
26 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
27 | hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
28 | let a = 'abc';
29 | let b = 'b';
30 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
31 | expect(a).assertContain(b);
32 | expect(a).assertEqual(a);
33 | })
34 | })
35 | }
--------------------------------------------------------------------------------
/home/src/main/ets/components/texts/linysLink.ets:
--------------------------------------------------------------------------------
1 | import { animation_default, fontSize_Normal } from '../../hosts/bunch_of_defaults';
2 | import { LengthMetrics } from '@kit.ArkUI';
3 |
4 | @Component
5 | struct linysLink {
6 | // Colors
7 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
8 | @Prop text: ResourceStr = $r('app.string.HomeAbility_label');
9 | @Prop color: ResourceColor = "";
10 | @Prop max_lines: number = 1;
11 | @Prop font_weight: FontWeight = FontWeight.Regular;
12 | @State link: string = "huawei.com";
13 | @LocalStorageLink('universal_new_tab_gateway') universal_new_tab_gateway: (string | boolean)[] = ['', false];
14 | // In fact this param is designed for closing some opened panel
15 | @Link will_be_reversed_boolean: boolean;
16 | @State isHov: boolean = false;
17 |
18 | build() {
19 | Text(this.text)
20 | .brightness(this.isHov ? 1.1 : 1)
21 | .animation(animation_default())
22 | .padding({
23 | left: 7,
24 | right: 7,
25 | top: 5,
26 | bottom: 5
27 | })
28 | .borderRadius(7.5)
29 | .fontColor(this.color == "" ? this.color_current_font : this.color)
30 | .fontSize(fontSize_Normal())
31 | .fontWeight(this.font_weight)
32 | .textAlign(TextAlign.Start)
33 | .maxLines(this.max_lines)
34 | .lineSpacing(LengthMetrics.vp(4))
35 | .textOverflow({ overflow: TextOverflow.Ellipsis })
36 | .onClick(() => {
37 | this.universal_new_tab_gateway = [this.link, false];
38 | this.will_be_reversed_boolean = !this.will_be_reversed_boolean;
39 | })
40 | .onHover((isHv) => {
41 | this.isHov = isHv;
42 | })
43 | .accessibilityText(this.text + ',' + this.link)
44 | .accessibilityDescription($r('app.string.Accessibility_desc_linysLink'))
45 | }
46 | }
47 |
48 | export default linysLink
--------------------------------------------------------------------------------
/home/src/main/ets/workers/Homepage_background_loader.ets:
--------------------------------------------------------------------------------
1 | import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
2 | import { arrayBuffer_2_sendable_pixelMap, sandbox_read_arrayBuffer } from '../utils/storage_tools';
3 |
4 | const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
5 | let pathDir: string = '';
6 |
7 | /**
8 | * Defines the event handler to be called when the worker thread receives a message sent by the host thread.
9 | * The event handler is executed in the worker thread.
10 | *
11 | * @param event message data
12 | */
13 | workerPort.onmessage = async (event: MessageEvents) => {
14 | if (typeof event.data == 'string') {
15 | // Is pathDir
16 | pathDir = event.data as string;
17 | }
18 | // Start
19 | console.log("[Meow][meowAppSettings] Start loading homepage background!");
20 | sandbox_read_arrayBuffer('homepage_background_arrayBuffer', pathDir)?.then((homepage_background_array_buffer) => {
21 | console.log("[Meow][meowAppSettings] Finished reading homepage background!");
22 | if (homepage_background_array_buffer) {
23 | arrayBuffer_2_sendable_pixelMap(homepage_background_array_buffer).then((pm) => {
24 | console.log("[Meow][meowAppSettings] Finished processing homepage background!");
25 | workerPort.postMessageWithSharedSendable(pm);
26 | })
27 | }
28 | });
29 | };
30 |
31 | /**
32 | * Defines the event handler to be called when the worker receives a message that cannot be deserialized.
33 | * The event handler is executed in the worker thread.
34 | *
35 | * @param event message data
36 | */
37 | workerPort.onmessageerror = (event: MessageEvents) => {
38 | };
39 |
40 | /**
41 | * Defines the event handler to be called when an exception occurs during worker execution.
42 | * The event handler is executed in the worker thread.
43 | *
44 | * @param event error message
45 | */
46 | workerPort.onerror = (event: ErrorEvent) => {
47 | };
--------------------------------------------------------------------------------
/home/src/main/ets/components/linysLockSlider.ets:
--------------------------------------------------------------------------------
1 | import { animation_default } from '../hosts/bunch_of_defaults';
2 | import linysLockToggle from './toggles/linysLockToggle';
3 | import linysText from './texts/linysText';
4 |
5 | // @Reusable
6 | @Component
7 | struct linysLockSlider {
8 | @State is_locked: boolean = true;
9 | @Prop slider_min: number = 0;
10 | @Prop slider_max: number = 100;
11 | @Prop display: string | undefined = undefined;
12 | @Link slider_value: number;
13 | // Colors
14 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
15 | @StorageLink('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
16 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
17 | // Operations
18 | onTouchUp?: () => void;
19 |
20 | build() {
21 | Row({ space: 5 }) {
22 | linysLockToggle({ locked: this.is_locked })
23 |
24 | Slider({
25 | min: this.slider_min,
26 | max: this.slider_max,
27 | value: this.slider_value,
28 | style: SliderStyle.InSet,
29 | })
30 | .enabled(!this.is_locked)
31 | .opacity(!this.is_locked ? 1 : 0.5)
32 | .animation(animation_default())
33 | .layoutWeight(1)
34 | .blockColor(this.color_current_primary)
35 | .selectedColor(this.color_current_font)
36 | .onChange((value) => {
37 | this.slider_value = value;
38 | })
39 | .onTouch(e => {
40 | if (e.type == TouchType.Up) {
41 | // this.bunch_of_settings.set('animation_damping_coefficient',
42 | // this.slider_value);
43 | if (this.onTouchUp) {
44 | this.onTouchUp();
45 | }
46 | }
47 | })
48 | linysText({ text: this.display ? this.display : this.slider_value.toString() })
49 | .margin({ right: 5 })
50 | }
51 | }
52 | }
53 |
54 | export default linysLockSlider;
--------------------------------------------------------------------------------
/home/src/main/ets/utils/permission_tools.ets:
--------------------------------------------------------------------------------
1 | import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
2 | import { meowContext } from './environment_tools';
3 |
4 | // import { webview } from '@kit.ArkWeb';
5 |
6 | // webview.WebviewController.setWebDebuggingAccess(true);
7 | let atManager = abilityAccessCtrl.createAtManager();
8 |
9 | /**
10 | * Requests permission from user.
11 | * @param p Permissions[]
12 | * */
13 | export function request_user_permission(context:common.UIAbilityContext ,p: Permissions[]) {
14 | if (p.length == 0) {
15 | return;
16 | }
17 | atManager.requestPermissionsFromUser(context, p);
18 | }
19 |
20 | /**
21 | * Analyzes a list of protected resource types and return their according permissions.
22 | * @param prt the list of protected resource types.
23 | * @returns a list of Permissions.
24 | * */
25 | export function permissions_for_ProtectedResourceTypes(prt: string[]) {
26 | let permissions: Permissions[] = [];
27 | for (let index = 0; index < prt.length; index++) {
28 | const ProtectedResourceType = prt[index];
29 | if (ProtectedResourceType == 'TYPE_AUDIO_CAPTURE') {
30 | permissions.push('ohos.permission.MICROPHONE');
31 | }
32 | if (ProtectedResourceType == 'TYPE_VIDEO_CAPTURE') {
33 | permissions.push('ohos.permission.CAMERA');
34 | }
35 | }
36 | return permissions;
37 | }
38 |
39 | /**
40 | * Analyzes a list of protected resource types and return their according descriptions in human language.
41 | * @param prt the list of protected resource types.
42 | * @returns list of ResourceStr.
43 | * */
44 | export function desc_for_ProtectedResourceTypes(prt: string[]) {
45 | let result: ResourceStr[] = [];
46 | for (let index = 0; index < prt.length; index++) {
47 | const r = prt[index];
48 | if ($r('app.string.protected_resource_'.concat(r))) {
49 | result.push($r('app.string.protected_resource_'.concat(r)));
50 | } else {
51 | result.push('[Error] Unknown Protected Resource Type? ' + r);
52 | }
53 | }
54 | return result;
55 | }
--------------------------------------------------------------------------------
/home/src/main/ets/dialogs/woofControlFrame.ets:
--------------------------------------------------------------------------------
1 | import linysSymbol from '../components/texts/linysSymbol';
2 | import linysTextTitle from '../components/texts/linysTextTitle';
3 | import { animation_default } from '../hosts/bunch_of_defaults';
4 |
5 | @Component
6 | struct woofControlFrame {
7 | // Environment
8 | @LocalStorageLink('tablet_mode') tablet_mode: boolean = false;
9 | @LocalStorageLink('compact_mode') compact_mode: boolean = false;
10 | // Pass in content components
11 | @BuilderParam content_section: () => void;
12 | // Information
13 | title: ResourceStr = 'Meow';
14 | @Prop space_column: number = 2;
15 | @Prop show_title: boolean = true;
16 | // Control
17 | controller?: CustomDialogController;
18 | // Color
19 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
20 | // animation
21 | @State off_y_contents: number = 120;
22 | @StorageLink('effects') effects: boolean = false;
23 |
24 | build() {
25 | Column({ space: this.space_column }) {
26 | Row() {
27 | linysTextTitle({
28 | text: this.title,
29 | max_lines: 3
30 | })
31 | .layoutWeight(1)
32 | linysSymbol({
33 | symbol_glyph_target: 'sys.symbol.xmark',
34 | can_hover: true,
35 | })
36 | .onClick(() => {
37 | if (this.controller) {
38 | this.controller.close();
39 | }
40 | })
41 | }
42 | .visibility(this.show_title ? Visibility.Visible : Visibility.None)
43 | .animation(animation_default())
44 |
45 | // Contents
46 | this.content_section();
47 | }
48 | .offset({ y: this.off_y_contents })
49 | .useEffect(this.effects, EffectType.WINDOW_EFFECT)
50 | .padding(this.compact_mode ? 10 : 15)
51 | .animation(animation_default())
52 | .backgroundColor(this.effects ? undefined : this.color_current_primary)
53 | .onAppear(() => {
54 | this.off_y_contents = 0;
55 | })
56 | }
57 | }
58 |
59 | export default woofControlFrame;
--------------------------------------------------------------------------------
/home/src/main/ets/components/linysProgress.ets:
--------------------------------------------------------------------------------
1 | @Component
2 | struct linysProgress {
3 | // Colors
4 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
5 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
6 | @State bar_height: Length = 8;
7 | @State bar_width: Length = "100%";
8 | @Watch('on_update') @Prop is_loading: boolean = false;
9 | @Watch('on_update') @Prop percentage: number = 0;
10 | @State display_rate: number = 0.0;
11 | @State color_progress: ResourceColor = "";
12 | @State color_undone: ResourceColor = "";
13 | @State show: boolean = false;
14 |
15 | build() {
16 | Row() {
17 | Row()
18 | .height("100%")
19 | .layoutWeight(this.display_rate)
20 | .backgroundColor(this.color_progress == "" ? this.color_current_font : this.color_progress)
21 | .animation({ duration: this.show ? 500 : 0, curve: Curve.EaseInOut })
22 |
23 | Row()
24 | .height("100%")
25 | .layoutWeight(1 - (this.display_rate))
26 | .backgroundColor(this.color_undone == "" ? this.color_current_primary : this.color_undone)
27 | .animation({ duration: this.show ? 500 : 0, curve: Curve.EaseInOut })
28 | }
29 | .height(this.show ? this.bar_height : 0)
30 | .width(this.bar_width)
31 | .animation({ duration: 200, curve: Curve.ExtremeDeceleration })
32 | .accessibilityText(this.percentage?.toString() + "%")
33 | .accessibilityDescription($r('app.string.Accessibility_desc_linysProgress'))
34 | .accessibilityGroup(true)
35 | }
36 |
37 | on_update() {
38 | this.display_rate = this.percentage / 100
39 | if (this.is_loading) {
40 | this.show = true;
41 | } else {
42 | setTimeout(() => {
43 | // Auto hide after is not loading
44 | this.show = false;
45 | }, 500)
46 | setTimeout(() => {
47 | // Reset progress bar
48 | this.display_rate = 0;
49 | }, 800)
50 | }
51 | }
52 | }
53 |
54 | export default linysProgress
--------------------------------------------------------------------------------
/home/src/main/ets/dialogs/web/woofAlert.ets:
--------------------------------------------------------------------------------
1 | import linysConfirmDenyButtons from '../../components/buttons/linysConfirmDenyButtons';
2 | import linysText from '../../components/texts/linysText';
3 | import linysTextTitle from '../../components/texts/linysTextTitle';
4 |
5 | @CustomDialog
6 | struct woofAlert {
7 | @Link event: OnAlertEvent | undefined;
8 | controller: CustomDialogController;
9 | // Colors
10 | @StorageLink('effects') effects: boolean = false;
11 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
12 | // Count
13 | @LocalStorageLink('opened_dialog_controllers') opened_dialog_controllers: CustomDialogController[] = [];
14 |
15 | aboutToAppear(): void {
16 | this.opened_dialog_controllers.push(this.controller);
17 | console.log(`[Dialog][Open] Now dialog count: ${this.opened_dialog_controllers.length}!`);
18 | }
19 |
20 | aboutToDisappear(): void {
21 | this.event?.result.handleCancel();
22 | this.opened_dialog_controllers.splice(this.opened_dialog_controllers.indexOf(this.controller), 1);
23 | console.log(`[Dialog][Close] Now dialog count: ${this.opened_dialog_controllers.length}!`);
24 | }
25 |
26 | build() {
27 | Column({ space: 15 }) {
28 | linysTextTitle({ text: this.event?.url, max_lines: 2 }) // Page url
29 | .width("100%")
30 |
31 | linysText({ text: this.event?.message, max_lines: 999 }) // Prompt message
32 | .width("100%")
33 |
34 | linysConfirmDenyButtons({
35 | onConfirm: () => {
36 | if (this.controller) {
37 | this.event?.result.handleConfirm();
38 | this.controller.close();
39 | }
40 | },
41 | confirm_double_confirm: true
42 | }) // Buttons
43 | }
44 | .padding(20)
45 | .alignItems(HorizontalAlign.Start)
46 | .justifyContent(FlexAlign.Center)
47 | .width("100%")
48 | .backgroundColor(this.effects ? undefined : this.color_current_primary)
49 | .useEffect(this.effects, EffectType.WINDOW_EFFECT)
50 | }
51 | }
52 |
53 | export default woofAlert;
--------------------------------------------------------------------------------
/home/src/main/ets/blocks/contents/meowCreditsUsers.ets:
--------------------------------------------------------------------------------
1 | import linysText from '../../components/texts/linysText';
2 | import { animation_default } from '../../hosts/bunch_of_defaults';
3 | import { resource_to_string } from '../../utils/resource_tools';
4 |
5 | @Component
6 | struct meowCreditsUsers {
7 | @State credits_devs_and_users_resource: Resource = $r("app.string.Credits_devs_and_users");
8 | @State credits_devs_and_users: string = resource_to_string(this.credits_devs_and_users_resource);
9 | @State credits_devs_and_users_list: string[] = this.credits_devs_and_users.split('\n');
10 |
11 | build() {
12 | Scroll() {
13 | Column({ space: 8 }) {
14 | linysText({
15 | text: $r('app.string.Credits_issues'),
16 | max_lines: 10
17 | })
18 |
19 | ForEach(this.credits_devs_and_users_list, (text: string, index: number) => {
20 | linysText({
21 | text: text,
22 | max_lines: 48
23 | })
24 | .opacity(0.8)
25 | })
26 | }
27 | .alignItems(HorizontalAlign.Start)
28 | .padding(10)
29 | .borderRadius(13.5)
30 | .backgroundColor($r('sys.color.comp_background_tertiary'))
31 | .width("100%")
32 | .animation(animation_default())
33 | .onAreaChange(() => {
34 | this.update_resource_string();
35 | })
36 | }
37 | // .nestedScroll({ scrollForward: NestedScrollMode.SELF_FIRST, scrollBackward: NestedScrollMode.SELF_FIRST })
38 | .width("100%")
39 | .height(180)
40 | .scrollable(ScrollDirection.Vertical)
41 | .edgeEffect(EdgeEffect.Spring)
42 | .borderRadius(13.5)
43 | .animation(animation_default())
44 |
45 | }
46 |
47 | /**
48 | * Convert the Resource to string again.
49 | *
50 | * Usually called to deal with the change of language.
51 | * */
52 | update_resource_string() {
53 | this.credits_devs_and_users = resource_to_string(this.credits_devs_and_users_resource);
54 | this.credits_devs_and_users_list = this.credits_devs_and_users.split('\n');
55 | }
56 | }
57 |
58 | export default meowCreditsUsers;
59 |
--------------------------------------------------------------------------------
/home/src/main/ets/components/buttons/linysTimeoutButton.ets:
--------------------------------------------------------------------------------
1 | import { animation_default, animation_popup_duration, capsule_bar_height, click_effect_default, fontSize_Large } from '../../hosts/bunch_of_defaults';
2 |
3 | @Component
4 | struct linysTimeoutButton {
5 | @Prop text: ResourceStr = ' ';
6 | @StorageLink('effects') effects: boolean = false;
7 | // editing
8 | @State one_more_step: boolean = false;
9 | // Colors
10 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
11 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
12 | @StorageLink('color_current_back') color_current_back: ResourceColor = $r('app.color.font_color_title');
13 | // Operations
14 | onExecution?: () => void;
15 | // Timeout
16 | confirm_timeout_id: number | undefined = undefined;
17 | opa: number = 0.9;
18 |
19 | build() {
20 | Button(this.text)// Reset
21 | .opacity(this.effects ? 1 : this.opa)
22 | .type(ButtonType.Capsule)
23 | // .useEffect(this.effects, EffectType.WINDOW_EFFECT)
24 | .backgroundColor(this.one_more_step ? this.color_current_primary : this.color_current_back)
25 | .fontColor(this.one_more_step ? this.color_current_font :(this.effects ? this.color_current_font : this.color_current_primary))
26 | .border({
27 | width: 2,
28 | color: this.one_more_step ? this.color_current_font : "transparent"
29 | })
30 | .animation(animation_default())
31 | .fontSize(fontSize_Large())
32 | .fontWeight(FontWeight.Medium)
33 | .height(capsule_bar_height())
34 | .clickEffect(click_effect_default())
35 | .onClick(() => {
36 | if (this.one_more_step) {
37 | if (this.onExecution) {
38 | this.onExecution();
39 | }
40 | this.one_more_step = false;
41 | return;
42 | }
43 | this.one_more_step = true;
44 | clearTimeout(this.confirm_timeout_id);
45 | this.confirm_timeout_id = setTimeout(() => {
46 | this.one_more_step = false;
47 | }, animation_popup_duration())
48 | })
49 | }
50 | }
51 |
52 | export default linysTimeoutButton
--------------------------------------------------------------------------------
/home/src/main/ets/components/buttons/linysConfirmDenyButtons.ets:
--------------------------------------------------------------------------------
1 | import { animation_default } from '../../hosts/bunch_of_defaults';
2 | import linysCapsuleButton from './linysCapsuleButton';
3 | import linysTimeoutButton from './linysTimeoutButton';
4 |
5 | // @Reusable
6 | @Component
7 | struct linysConfirmDenyButtons {
8 | onConfirm?: () => void;
9 | onDeny?: () => void;
10 | confirm_double_confirm: boolean = false;
11 | deny_double_confirm: boolean = false;
12 | confirm_text: ResourceStr = ' ';
13 | deny_text: ResourceStr = ' '
14 | @Prop confirm_enabled: boolean = true;
15 | @Prop deny_enabled: boolean = true;
16 | // Settings / Accessibility
17 | @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
18 |
19 | build() {
20 | Row({ space: 10 }) {
21 | if (this.onDeny) {
22 | if (this.deny_double_confirm) {
23 | linysTimeoutButton({
24 | text: this.deny_text, onExecution: () => {
25 | if (this.onDeny) {
26 | this.onDeny();
27 | }
28 | }
29 | })
30 | } else {
31 | linysCapsuleButton({ text: this.deny_text }) // Deny
32 | .onClick(() => {
33 | if (this.onDeny) {
34 | this.onDeny();
35 | }
36 | })
37 | .enabled(this.deny_enabled)
38 | .opacity(this.deny_enabled ? 1 : 0.7)
39 | }
40 | }
41 |
42 | if (this.onConfirm) {
43 | if (this.confirm_double_confirm) {
44 | linysTimeoutButton({
45 | text: this.confirm_text, onExecution: () => {
46 | if (this.onConfirm) {
47 | this.onConfirm();
48 | }
49 | }
50 | })
51 | } else {
52 | linysCapsuleButton({ text: this.confirm_text }) // Submit
53 | .onClick(() => {
54 | if (this.onConfirm) {
55 | this.onConfirm();
56 | }
57 | })
58 | .enabled(this.confirm_enabled)
59 | .opacity(this.confirm_enabled ? 1 : 0.7)
60 | }
61 | }
62 | }
63 | .justifyContent(this.preferred_hand_left_or_right == 'right' ? FlexAlign.End : FlexAlign.Start)
64 | .animation(animation_default())
65 | .width('100%')
66 | }
67 | }
68 |
69 | export default linysConfirmDenyButtons;
--------------------------------------------------------------------------------
/home/src/main/ets/components/buttons/linysShowButton.ets:
--------------------------------------------------------------------------------
1 | import { animation_default, click_effect_default, fontSize_Large } from '../../hosts/bunch_of_defaults';
2 | import linysSymbol from '../texts/linysSymbol';
3 |
4 | // @Reusable // Is this OK?
5 | @Component
6 | struct linysShowButton {
7 | // Colors
8 | @StorageLink('effects') effects: boolean = false;
9 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
10 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
11 | @StorageLink('color_current_back') color_current_back: ResourceColor = $r('app.color.font_color_title');
12 | // Statuses
13 | @Prop show: boolean = false;
14 | @Prop symbol_glyph_target: string = "sys.symbol.square_grid_2x2";
15 | @Prop color_false: ResourceColor = "";
16 | @Prop color_true: ResourceColor = "";
17 | @Prop text: ResourceStr = $r('app.string.Index_tabs_title');
18 | // UI
19 | show_margin: Padding | number = { left: 5, right: 5 }
20 | @State isHov: boolean = false;
21 |
22 | build() {
23 | Row() {
24 | linysSymbol({
25 | symbol_glyph_target: this.symbol_glyph_target,
26 | color: this.show
27 | ? ((this.color_true == "") && !this.effects ? this.color_current_primary : this.color_current_font)
28 | : (this.color_false == "" ? this.color_current_font : this.color_false)
29 | })
30 |
31 | Text(this.text)
32 | .fontSize(fontSize_Large() - 2)
33 | .fontColor(this.effects ? this.color_current_font : this.color_current_primary)
34 | .margin({ left: 4, right: 3 })
35 | .visibility(this.show ? Visibility.Visible : Visibility.None)
36 | .animation(animation_default())
37 | }
38 | .brightness(this.isHov ? 1.1 : 1)
39 | .padding(this.show ? 5 : 4)
40 | .margin(this.show ? this.show_margin : 0)
41 | .backgroundColor(this.show ? (this.color_false == "" ? this.color_current_back : this.color_false) : "transparent")
42 | .animation(animation_default())
43 | .onHover((isHv) => {
44 | this.isHov = isHv;
45 | })
46 | .clip(true)
47 | .borderRadius(10)
48 | .clickEffect(click_effect_default())
49 | .accessibilityText(this.text as string)
50 | .accessibilityDescription($r('app.string.Accessibility_desc_linysShowButton'))
51 | .accessibilityGroup(true)
52 | }
53 | }
54 |
55 | export default linysShowButton;
--------------------------------------------------------------------------------
/home/src/main/ets/dialogs/managers/ManageListElement.ets:
--------------------------------------------------------------------------------
1 | import linysText from '../../components/texts/linysText';
2 | import { animation_default, click_effect_default } from '../../hosts/bunch_of_defaults';
3 |
4 | @Component
5 | struct ManageListElement {
6 | @State text: string = "huawei.com";
7 | @Prop index: number;
8 | @Link selected: boolean[];
9 | @Link selected_number: number;
10 | @Prop selecting: boolean;
11 | // Color
12 | @StorageLink('effects') effects: boolean = false;
13 | @StorageLink('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
14 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
15 | // UI
16 | @State isHov: boolean = false;
17 |
18 | build() {
19 | Column() {
20 | linysText({
21 | color: this.selected[this.index] && !this.effects ? this.color_current_secondary : this.color_current_font,
22 | text: this.text,
23 | font_weight: this.selected[this.index] ? FontWeight.Bold : FontWeight.Regular
24 | })
25 | }
26 | .backgroundColor(this.selected[this.index] ? (this.effects ? this.color_current_secondary : this.color_current_font) : 'transparent')
27 | .brightness(this.isHov ? 1.1 : 1)
28 | .animation(animation_default())
29 | .alignItems(HorizontalAlign.Start)
30 | .width("100%")
31 | .padding({
32 | left: 10,
33 | right: 10,
34 | top: 5,
35 | bottom: 5
36 | })
37 | .clickEffect(click_effect_default())
38 | .onClick(() => {
39 | if (this.selecting) {
40 | // Select
41 | this.select_unselect();
42 | }
43 | })
44 | .width("100%")
45 | .gesture(
46 | LongPressGesture({ repeat: false })
47 | .onAction(() => {
48 | this.select_unselect();
49 | })
50 | )
51 | .onMouse((e) => {
52 | if (e.button == MouseButton.Right && e.action == MouseAction.Press) {
53 | // Right click
54 | this.select_unselect();
55 | }
56 | })
57 | .onHover((isHv) => {
58 | this.isHov = isHv;
59 | })
60 | }
61 |
62 | /**
63 | * Toggles the state
64 | * */
65 | select_unselect() {
66 | if (this.selected[this.index]) {
67 | this.selected_number -= 1;
68 | } else {
69 | this.selected_number += 1;
70 | }
71 | this.selected[this.index] = !this.selected[this.index];
72 | }
73 | }
74 |
75 | export default ManageListElement;
--------------------------------------------------------------------------------
/home/src/main/ets/objects/CatstensionBridgeObj.ets:
--------------------------------------------------------------------------------
1 | import { bunch_of_extensions, extension } from '../hosts/bunch_of_extensions';
2 | import { get_message_from_messages } from '../processes/extension_actions';
3 |
4 | export class CatsBridge {
5 | id: string;
6 | ext: extension;
7 |
8 | constructor(ext: extension) {
9 | this.id = ext.id;
10 | this.ext = ext;
11 | console.warn(this.log_head('Constructed!'));
12 | }
13 |
14 | log_head(text: string) {
15 | const head = `[Extension][${this.ext.name}][${this.ext.id}][CatsBridge]`;
16 | if (text.substring(0, 1) == '[') {
17 | return head + text;
18 | } else {
19 | return `${head} ` + text;
20 | }
21 | }
22 |
23 | chrome_action_setTitle(title: string) {
24 | this.ext.action_default_title = title;
25 | (AppStorage.get('bunch_of_extensions') as bunch_of_extensions).update_timestamp();
26 | }
27 |
28 | chrome_action_getTitle() {
29 | return this.ext.action_default_title;
30 | }
31 |
32 | chrome_action_getPopup() {
33 | return this.ext.action_default_popup;
34 | }
35 |
36 | chrome_action_setPopup(popup: string, tabId?: number) {
37 | if (popup == '') {
38 | this.ext.action_default_popup = undefined;
39 | try {
40 | this.ext.controller.loadUrl(this.ext.path_full + '/' + this.ext.action_default_popup);
41 | } catch (e) {
42 | console.error(this.log_head('[chrome_action_setPopup] loadUrl Failed: ' + e));
43 | }
44 | } else {
45 | this.ext.action_default_popup = popup;
46 | }
47 | }
48 |
49 | chrome_i18n_getAcceptLanguages() {
50 | return ['en', 'zh_CN'];
51 | }
52 |
53 | chrome_i18n_getUILanguage() {
54 | return 'en';
55 | }
56 |
57 | chrome_i18n_getMessage(messageName: string): string | undefined {
58 | if (this.ext.messages) {
59 | return get_message_from_messages(this.ext.messages, messageName);
60 | } else {
61 | return undefined;
62 | }
63 | }
64 |
65 | /**
66 | * Test method.
67 | * @param data Anything to be logged.
68 | * */
69 | liny_test(data: string) {
70 | console.log(`[Extension][CatsBridge][arkTs] Test! data=[${data}]!`);
71 | }
72 | }
73 |
74 | export function CatsBridgeMethods() {
75 | return ['constructor', 'chrome_action_setTitle', 'chrome_action_getTitle', 'chrome_action_getPopup', 'chrome_action_setPopup', 'chrome_i18n_getAcceptLanguages',
76 | 'chrome_i18n_getUILanguage', 'chrome_i18n_getMessage',
77 | 'liny_test'];
78 | }
--------------------------------------------------------------------------------
/home/src/main/resources/rawfile/cathrome/commands.js:
--------------------------------------------------------------------------------
1 | // Currently empty api.
2 | // TODO: Finish commands.js
3 |
4 | /**
5 | * A standalone class that mirrors the features of the chrome.commands API.
6 | * The internal logic for interacting with the operating system or browser is omitted.
7 | */
8 | class cathrome_commands {
9 | static Command = class {
10 | constructor(options = {}) {
11 | this.description = options.description;
12 | this.name = options.name;
13 | this.shortcut = options.shortcut;
14 | }
15 | };
16 |
17 | constructor() {
18 | /**
19 | * Fired when a registered command is activated using a keyboard shortcut.
20 | * The callback is expected to receive `(command: string, tab?: object) => void`.
21 | * @type {cathrome_eventListener}
22 | */
23 | this.onCommand = new cathrome_eventListener();
24 |
25 | // Placeholder for internal state, mimicking registered commands.
26 | /** @private @type {Command[]} */
27 | this._registeredCommands = [
28 | // {
29 | // name: "example-command-1",
30 | // description: "Toggles a feature.",
31 | // shortcut: "Ctrl+Shift+1"
32 | // },
33 | // {
34 | // name: "_execute_action",
35 | // description: "Reserved for the extension's primary action.",
36 | // shortcut: "Ctrl+U"
37 | // }
38 | ];
39 | }
40 |
41 | fire(command, tab) {
42 | this.onCommand.fire(command, tab);
43 | }
44 |
45 | /**
46 | * Returns all the registered extension commands for this extension and their shortcut (if active).
47 | *
48 | * In a real environment, this would involve async browser communication.
49 | * Here, we use a callback to mimic the API's structure and immediately return placeholder data.
50 | *
51 | * @param {function(Command[]): void} callback A function that is called with an array of Command objects.
52 | * @returns {void}
53 | */
54 | getAll(callback) {
55 | if (typeof callback === 'function') {
56 | // Return a copy of the placeholder data to the callback.
57 | callback(this._registeredCommands.slice());
58 | }
59 | // NOTE: The real API supports a Promise return style as well, but we stick to the
60 | // common callback pattern for simplicity in this standalone structure.
61 | }
62 | }
--------------------------------------------------------------------------------
/home/src/main/ets/dialogs/prompts/woofPromptOK.ets:
--------------------------------------------------------------------------------
1 | import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
2 | import linysText from '../../components/texts/linysText';
3 | import { animation_default } from '../../hosts/bunch_of_defaults';
4 | import lazy woofControlFrame from '../woofControlFrame';
5 |
6 | @CustomDialog
7 | struct woofPromptOK {
8 | controller: CustomDialogController;
9 | @Prop title: ResourceStr = $r('app.string.Settings_toolbox_import_settings')
10 | @Prop desc: ResourceStr = $r('app.string.Settings_toolbox_import_settings_ok')
11 | // Colors
12 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
13 | @StorageLink('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
14 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
15 | // Status
16 | @State content_height: number = 233;
17 | // Settings / Accessibility
18 | @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
19 | // Count
20 | @LocalStorageLink('opened_dialog_controllers') opened_dialog_controllers: CustomDialogController[] = [];
21 |
22 | aboutToAppear(): void {
23 | this.opened_dialog_controllers.push(this.controller);
24 | console.log(`[Dialog][Open] Now dialog count: ${this.opened_dialog_controllers.length}!`);
25 | }
26 |
27 | aboutToDisappear(): void {
28 | this.opened_dialog_controllers.splice(this.opened_dialog_controllers.indexOf(this.controller), 1);
29 | console.log(`[Dialog][Close] Now dialog count: ${this.opened_dialog_controllers.length}!`);
30 | }
31 |
32 | build() {
33 | woofControlFrame({
34 | title: this.title,
35 | controller: this.controller
36 | }) {
37 | Scroll() {
38 | Column({ space: 15 }) {
39 | linysText({
40 | text: this.desc,
41 | max_lines: 5
42 | })
43 | .width("100%")
44 | linysCapsuleButton({ text: $r('app.string.OK') })
45 | .onClick(()=>{
46 | if (this.controller) {
47 | this.controller.close();
48 | }
49 | })
50 | }
51 | .alignItems(this.preferred_hand_left_or_right == 'right' ? HorizontalAlign.End : HorizontalAlign.Start)
52 | .animation(animation_default())
53 | .justifyContent(FlexAlign.Center)
54 | .width("100%")
55 | .onAreaChange((_o, n) => {
56 | this.content_height = n.height as number;
57 | })
58 | }
59 | .layoutWeight(1)
60 | .edgeEffect(EdgeEffect.Spring)
61 | .constraintSize({ maxHeight: this.content_height })
62 | }
63 | }
64 | }
65 |
66 | export default woofPromptOK;
--------------------------------------------------------------------------------
/home/src/main/ets/dialogs/web/woofConfirm.ets:
--------------------------------------------------------------------------------
1 | import linysConfirmDenyButtons from '../../components/buttons/linysConfirmDenyButtons';
2 | import linysText from '../../components/texts/linysText';
3 | import linysTextArea from '../../components/texts/linysTextArea';
4 | import linysTextTitle from '../../components/texts/linysTextTitle';
5 |
6 | @CustomDialog
7 | struct woofConfirm {
8 | // @Link event: OnConfirmEvent | undefined;
9 | controller: CustomDialogController;
10 | @LocalStorageLink('screen_height') screen_height: number = 0;
11 | // Colors
12 | @StorageLink('effects') effects: boolean = false;
13 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
14 | // Count
15 | @LocalStorageLink('opened_dialog_controllers') opened_dialog_controllers: CustomDialogController[] = [];
16 | // Actions
17 | title: ResourceStr = '';
18 | desc: string = '';
19 | @State desc_edit: string = ''; // To fool the TextArea
20 | onConfirm?: () => void;
21 | onDeny?: () => void;
22 |
23 | aboutToAppear(): void {
24 | this.opened_dialog_controllers.push(this.controller);
25 | console.log(`[Dialog][Open] Now dialog count: ${this.opened_dialog_controllers.length}!`);
26 | }
27 |
28 | aboutToDisappear(): void {
29 | if (this.onDeny) {
30 | this.onDeny();
31 | }
32 | this.opened_dialog_controllers.splice(this.opened_dialog_controllers.indexOf(this.controller), 1);
33 | console.log(`[Dialog][Close] Now dialog count: ${this.opened_dialog_controllers.length}!`);
34 | }
35 |
36 | build() {
37 | Column({ space: 15 }) {
38 | linysTextTitle({ text: this.title, max_lines: 2 }) // Page url
39 | .width("100%")
40 |
41 | linysTextArea({
42 | init_text:this.desc,
43 | text: this.desc_edit
44 | })
45 | .constraintSize({ maxHeight: Math.max(0, this.screen_height - 160) })
46 |
47 | linysConfirmDenyButtons({
48 | onConfirm: () => {
49 | if (this.onConfirm) {
50 | this.onConfirm();
51 | }
52 | if (this.controller) {
53 | this.controller.close();
54 | }
55 | },
56 | onDeny: () => {
57 | if (this.onDeny) {
58 | this.onDeny();
59 | }
60 | if (this.controller) {
61 | this.controller.close();
62 | }
63 | },
64 | deny_double_confirm: true,
65 | confirm_double_confirm: true,
66 | }) // Buttons
67 | }
68 | .padding(20)
69 | .alignItems(HorizontalAlign.Start)
70 | .justifyContent(FlexAlign.Center)
71 | .width("100%")
72 | .backgroundColor(this.effects ? undefined : this.color_current_primary)
73 | .useEffect(this.effects, EffectType.WINDOW_EFFECT)
74 | }
75 | }
76 |
77 | export default woofConfirm;
--------------------------------------------------------------------------------
/home/src/main/ets/blocks/panels/meowAnimationManager.ets:
--------------------------------------------------------------------------------
1 | import { bunch_of_settings } from '../../hosts/bunch_of_settings';
2 | import linysLockSlider from '../../components/linysLockSlider';
3 | import linysText from '../../components/texts/linysText';
4 | import linysTimeoutButtonWithText from '../../components/buttons/linysTimeoutButtonWithText';
5 |
6 | @Component
7 | struct meowAnimationManager {
8 | // Colors
9 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
10 | @StorageLink('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
11 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
12 | // editing
13 | @StorageLink('animation_response') animation_response: number = 36;
14 | @StorageLink('animation_damping_coefficient') animation_damping_coefficient: number = 20;
15 |
16 | build() {
17 | Column({ space: 10 }) {
18 |
19 | linysText({
20 | text: $r('app.string.Settings_appearance_animation_response')
21 | }) // Slider and display of animation_response
22 |
23 | linysLockSlider({
24 | slider_min: 1,
25 | slider_max: 100,
26 | display: (0.01 * this.animation_response).toFixed(2),
27 | slider_value: this.animation_response,
28 | onTouchUp: () => {
29 | bunch_of_settings.set('animation_response', this.animation_response);
30 | }
31 | })
32 |
33 | linysText({
34 | text: $r('app.string.Settings_appearance_animation_damping_coefficient')
35 | }) // Slider and display of animation damping coefficient
36 |
37 | linysLockSlider({
38 | slider_min: 0,
39 | slider_max: 75,
40 | slider_value: this.animation_damping_coefficient,
41 | display: (0.01 * this.animation_damping_coefficient).toFixed(2),
42 | onTouchUp: () => {
43 | bunch_of_settings.set('animation_damping_coefficient',
44 | this.animation_damping_coefficient);
45 | }
46 | })
47 |
48 | linysTimeoutButtonWithText({
49 | desc_text: $r('app.string.Settings_appearance_animation_reset'),
50 | button_text: ' ',
51 | onExecution: () => {
52 | // Reset
53 | bunch_of_settings.reset('animation_response');
54 | bunch_of_settings.reset('animation_damping_coefficient');
55 | // Sync
56 | this.animation_response = bunch_of_settings.get('animation_response') as number;
57 | console.log(this.animation_response.toString())
58 | this.animation_damping_coefficient = bunch_of_settings.get('animation_damping_coefficient') as number;
59 | }
60 | })
61 | }
62 | .alignItems(HorizontalAlign.Start)
63 | .justifyContent(FlexAlign.Start)
64 | }
65 | }
66 |
67 | export default meowAnimationManager;
--------------------------------------------------------------------------------
/home/src/main/ets/components/texts/linysText.ets:
--------------------------------------------------------------------------------
1 | import lazy { animation_default } from '../../hosts/bunch_of_defaults';
2 | import { click_effect_default, fontSize_Normal } from '../../hosts/bunch_of_defaults';
3 | import { LengthMetrics } from '@kit.ArkUI';
4 |
5 | // @Reusable // Will this affect anything?
6 | @Component
7 | struct linysText {
8 | // Colors
9 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
10 | @Prop text: ResourceStr = $r('app.string.HomeAbility_label');
11 | @Prop color: ResourceColor = "";
12 | @Prop font_weight: FontWeight = FontWeight.Regular;
13 | is_description: boolean = false;
14 | max_lines: number = 1;
15 | overflow_type: TextOverflow = TextOverflow.Ellipsis;
16 | // UI
17 | @Prop is_expanded: boolean = false;
18 | can_hover: boolean = false;
19 | @State isHov: boolean = false;
20 |
21 | build() {
22 | if (this.is_description) {
23 | Scroll() {
24 | Text(this.text)
25 | .fontColor(this.color == "" ? this.color_current_font : this.color)
26 | .fontSize(fontSize_Normal())
27 | .fontWeight(this.font_weight)
28 | .maxLines(!this.is_expanded ? 1 : undefined)
29 | .lineSpacing(LengthMetrics.vp(4))
30 | .textOverflow({ overflow: this.overflow_type })
31 | .animation(animation_default())
32 | }
33 | .nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.PARENT_FIRST })
34 | .align(Alignment.TopStart)
35 | .scrollable(ScrollDirection.Vertical)
36 | .scrollBar(BarState.Off)
37 | // .edgeEffect(EdgeEffect.Spring)
38 | .constraintSize(this.is_expanded ? {} : { maxHeight: 40 })
39 | .animation(animation_default())
40 | .borderRadius(12)
41 | .padding(10)
42 | .backgroundColor($r('sys.color.comp_background_tertiary'))
43 | .width('100%')
44 | .onClick(() => {
45 | this.is_expanded = !this.is_expanded;
46 | })
47 | .clickEffect(click_effect_default())
48 | .hoverEffect(HoverEffect.Highlight)
49 |
50 | } else {
51 | Text(this.text)
52 | .brightness(this.isHov ? 1.1 : 1)
53 | .fontWeight(this.font_weight)
54 | .animation(this.can_hover ? animation_default() : { duration: 0 })
55 | .padding(this.can_hover ? {
56 | left: 7,
57 | right: 7,
58 | top: 5,
59 | bottom: 5
60 | } : 0)
61 | .borderRadius(this.can_hover ? 7.5 : 0)
62 | .fontColor(this.color == "" ? this.color_current_font : this.color)
63 | .fontSize(fontSize_Normal())
64 | .maxLines(this.max_lines)
65 | .lineSpacing(LengthMetrics.vp(4))
66 | .textOverflow({ overflow: this.overflow_type })
67 | .onHover((isHv) => {
68 | if (this.can_hover) {
69 | this.isHov = isHv;
70 | }
71 | })
72 | }
73 | }
74 | }
75 |
76 | export default linysText;
--------------------------------------------------------------------------------
/home/src/main/ets/dialogs/prompts/woofWantJump.ets:
--------------------------------------------------------------------------------
1 | import linysConfirmDenyButtons from '../../components/buttons/linysConfirmDenyButtons';
2 | import linysText from '../../components/texts/linysText';
3 | import linysTextArea from '../../components/texts/linysTextArea';
4 | import linysTextTitle from '../../components/texts/linysTextTitle';
5 | import { jump_external_link } from '../../utils/url_tools';
6 |
7 | @CustomDialog
8 | struct woofWantJump {
9 | controller: CustomDialogController;
10 | @Prop link: string = '';
11 | // Colors
12 | @StorageLink('effects') effects: boolean = false;
13 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
14 | // UI display
15 | @LocalStorageLink('screen_height') screen_height: number = 0;
16 | @State fixed_height_title: number = 0;
17 | @State fixed_height_quest: number = 0;
18 | // Count
19 | @LocalStorageLink('opened_dialog_controllers') opened_dialog_controllers: CustomDialogController[] = [];
20 |
21 | aboutToAppear(): void {
22 | this.opened_dialog_controllers.push(this.controller);
23 | console.log(`[Dialog][Open] Now dialog count: ${this.opened_dialog_controllers.length}!`);
24 | }
25 |
26 | aboutToDisappear(): void {
27 | this.opened_dialog_controllers.splice(this.opened_dialog_controllers.indexOf(this.controller), 1);
28 | console.log(`[Dialog][Close] Now dialog count: ${this.opened_dialog_controllers.length}!`);
29 | }
30 |
31 | build() {
32 | Column({ space: 15 }) {
33 | linysTextTitle({ text: $r('app.string.LinkJumper_webpage_asks_for_jump') })// This page is asking for a jump
34 | .width("100%")
35 | .onAreaChange((o, n) => {
36 | this.fixed_height_title = n.height as number;
37 | })
38 |
39 | linysTextArea({ init_text: this.link, text: this.link }) // Edit
40 | .constraintSize({
41 | maxHeight: Math.max(0, this.screen_height * 0.9 - this.fixed_height_quest - this.fixed_height_title - 35 - 45 - 40)
42 | })
43 |
44 | linysText({ text: $r('app.string.LinkJumper_allow_jump') })// allow?
45 | .width("100%")
46 | .onAreaChange((o, n) => {
47 | this.fixed_height_quest = n.height as number;
48 | })
49 |
50 | linysConfirmDenyButtons({
51 | onConfirm: () => {
52 | if (this.controller) {
53 | this.controller.close();
54 | }
55 | jump_external_link(this.link, this.getUIContext().getHostContext()!);
56 | },
57 | onDeny: () => {
58 | if (this.controller) {
59 | this.controller.close();
60 | }
61 | }
62 | }) // Buttons
63 | }
64 | .padding(20)
65 | .alignItems(HorizontalAlign.Start)
66 | .justifyContent(FlexAlign.Center)
67 | .width("100%")
68 | .backgroundColor(this.effects ? undefined : this.color_current_primary)
69 | .useEffect(this.effects, EffectType.WINDOW_EFFECT)
70 | }
71 | }
72 |
73 | export default woofWantJump;
--------------------------------------------------------------------------------
/home/src/main/ets/dialogs/contents/woofQR.ets:
--------------------------------------------------------------------------------
1 | import linysText from '../../components/texts/linysText';
2 | import linysTextTitle from '../../components/texts/linysTextTitle';
3 | import { click_effect_default } from '../../hosts/bunch_of_defaults';
4 | import { copy } from '../../utils/clipboard_tools';
5 | import lazy woofControlFrame from '../woofControlFrame';
6 |
7 | @CustomDialog
8 | struct woofQR {
9 | controller: CustomDialogController;
10 | @Prop link: string = '';
11 | @Prop title: string = '';
12 | // Colors
13 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
14 | @StorageLink('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
15 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
16 | // Status
17 | @State content_height: number = 233;
18 | @LocalStorageLink('opened_dialog_controllers') opened_dialog_controllers: CustomDialogController[] = [];
19 |
20 | aboutToAppear(): void {
21 | this.opened_dialog_controllers.push(this.controller);
22 | console.log(`[Dialog][Open] Now dialog count: ${this.opened_dialog_controllers.length}!`);
23 | }
24 |
25 | aboutToDisappear(): void {
26 | this.opened_dialog_controllers.splice(this.opened_dialog_controllers.indexOf(this.controller), 1);
27 | console.log(`[Dialog][Close] Now dialog count: ${this.opened_dialog_controllers.length}!`);
28 | }
29 |
30 | build() {
31 | woofControlFrame({
32 | title: $r('app.string.QR_code'),
33 | controller: this.controller
34 | }) {
35 | Scroll() {
36 | Column({ space: 10 }) {
37 | QRCode(this.link)
38 | .color(this.color_current_font)
39 | .backgroundColor("transparent")
40 | .padding(20)
41 |
42 | Column() {
43 | linysTextTitle({
44 | text: this.title,
45 | max_lines: 5,
46 | can_hover: true
47 | })
48 | .width("100%")
49 | .clickEffect(click_effect_default())
50 | .onClick(() => {
51 | copy(this.title);
52 | })
53 |
54 | linysText({
55 | text: this.link,
56 | font_weight: FontWeight.Bold,
57 | max_lines: 10,
58 | can_hover: true
59 | })
60 | .width("100%")
61 | .opacity(0.7)
62 | .clickEffect(click_effect_default())
63 | .onClick(() => {
64 | copy(this.link);
65 | })
66 | } // Info
67 |
68 | }
69 | .alignItems(HorizontalAlign.Center)
70 | .justifyContent(FlexAlign.Center)
71 | .width("100%")
72 | .onAreaChange((_o, n) => {
73 | this.content_height = n.height as number;
74 | })
75 | }
76 | .layoutWeight(1)
77 | .edgeEffect(EdgeEffect.Spring)
78 | .constraintSize({ maxHeight: this.content_height })
79 | }
80 | }
81 | }
82 |
83 | export default woofQR;
--------------------------------------------------------------------------------
/home/src/main/ets/blocks/modules/meowExtensionView.ets:
--------------------------------------------------------------------------------
1 | import { animation_default } from '../../hosts/bunch_of_defaults';
2 | import { bunch_of_extensions } from '../../hosts/bunch_of_extensions';
3 | import { createEWeb, ExtNodeController } from '../../objects/ExtWebNode';
4 | import { detach_show_extension } from '../../processes/web_actions';
5 |
6 | @Component
7 | struct meowExtensionView {
8 | @StorageLink('bunch_of_extensions') bunch_of_extensions: bunch_of_extensions = new bunch_of_extensions();
9 | // UI
10 | @LocalStorageLink('tablet_mode') tablet_mode: boolean = false;
11 | @LocalStorageLink('compact_mode') compact_mode: boolean = false; // Like PuraX
12 | @StorageLink('effects') effects: boolean = false;
13 | @LocalStorageLink('my_window_id') my_window_id: string = '';
14 | // Ext
15 | @LocalStorageLink('showing_ext_id') showing_ext_id: string = '';
16 | @LocalStorageLink('showing_ext_node_controller') showing_ext_node_controller: ExtNodeController | undefined = undefined;
17 |
18 | aboutToAppear(): void {
19 | // Init web to create the ExtWeb Nodes for extensions loaded from disk.
20 | if (this.bunch_of_extensions.NodeControllers.length == 0) {
21 | for (let index = 0; index < this.bunch_of_extensions.extensions.length; index++) {
22 | this.bunch_of_extensions.NodeControllers.push(new ExtNodeController(createEWeb(this.getUIContext(), this.bunch_of_extensions.extensions[index])));
23 | }
24 | console.log(`[meowExtensionView] bunch_of_extensions.extensions.lengtgh = ${this.bunch_of_extensions.extensions.length}. Created NodeControllers!`);
25 | } else {
26 | console.log(`[meowExtensionView] I'm a sub window. I found there are already created NodeControllers. I have nothing to do. Oh sad sad...`);
27 | }
28 | }
29 |
30 | build() {
31 | // if (this.showing_ext_node_controller) {
32 | Stack() {
33 | Row()
34 | .width('100%')
35 | .height('100%')
36 | .backgroundColor($r('sys.color.comp_background_tertiary'))
37 | .onClick(() => {
38 | let ext = this.bunch_of_extensions.get_extension(this.showing_ext_id);
39 | // if (ext) {
40 | // ext.is_being_viewed = false;
41 | // }
42 | // this.showing_ext_id = '';
43 | if (ext) {
44 | detach_show_extension(ext);
45 | } else {
46 | this.showing_ext_node_controller = undefined;
47 | this.showing_ext_id = '';
48 | }
49 | })
50 |
51 | if (this.showing_ext_node_controller) {
52 | NodeContainer(this.showing_ext_node_controller)
53 | .width(this.tablet_mode ? '60%' : undefined)
54 | .height('70%')
55 | .animation(animation_default())
56 | .margin(12)
57 | .clip(true)
58 | .borderRadius(this.effects ? 5 : 0)
59 | .onAppear(() => {
60 | console.log(`[meowExtensionView][${this.my_window_id}] Hello NodeContainer.`);
61 | })
62 | } else {
63 | Row().onAppear(() => {
64 | console.log(`[meowExtensionView][${this.my_window_id}] Goodbye NodeContainer.`);
65 | })
66 | }
67 |
68 | }
69 | .alignContent(Alignment.TopEnd)
70 | .visibility((this.showing_ext_id != '') ? Visibility.Visible : Visibility.Hidden)
71 | .animation(animation_default())
72 | .width('100%')
73 | .height('100%')
74 | .hitTestBehavior((this.showing_ext_id != '') ? HitTestMode.Default : HitTestMode.Transparent)
75 | }
76 | // }
77 | }
78 |
79 | export default meowExtensionView;
--------------------------------------------------------------------------------
/home/src/main/ets/objects/HistoryDataSource.ets:
--------------------------------------------------------------------------------
1 | /**
2 | * Adapted from https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-rendering-control-lazyforeach
3 | * */
4 | import { history_record } from '../hosts/bunch_of_history';
5 |
6 | // BasicDataSource实现了IDataSource接口,用于管理listener监听,以及通知LazyForEach数据更新
7 | class BasicHistoryDataSource implements IDataSource {
8 | private listeners: DataChangeListener[] = [];
9 | private originDataArray: Array> = [];
10 |
11 | public totalCount(): number {
12 | return 0;
13 | }
14 |
15 | public getData(index: number): Array {
16 | return this.originDataArray[index];
17 | }
18 |
19 | // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
20 | registerDataChangeListener(listener: DataChangeListener): void {
21 | if (this.listeners.indexOf(listener) < 0) {
22 | // console.info('add listener');
23 | this.listeners.push(listener);
24 | }
25 | }
26 |
27 | // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
28 | unregisterDataChangeListener(listener: DataChangeListener): void {
29 | const pos = this.listeners.indexOf(listener);
30 | if (pos >= 0) {
31 | // console.info('remove listener');
32 | this.listeners.splice(pos, 1);
33 | }
34 | }
35 |
36 | // 通知LazyForEach组件需要重载所有子组件
37 | notifyDataReload(): void {
38 | this.listeners.forEach(listener => {
39 | listener.onDataReloaded();
40 | });
41 | }
42 |
43 | // 通知LazyForEach组件需要在index对应索引处添加子组件
44 | notifyDataAdd(index: number): void {
45 | this.listeners.forEach(listener => {
46 | listener.onDataAdd(index);
47 | // 写法2:listener.onDatasetChange([{type: DataOperationType.ADD, index: index}]);
48 | });
49 | }
50 |
51 | // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
52 | notifyDataChange(index: number): void {
53 | this.listeners.forEach(listener => {
54 | listener.onDataChange(index);
55 | // 写法2:listener.onDatasetChange([{type: DataOperationType.CHANGE, index: index}]);
56 | });
57 | }
58 |
59 | // 通知LazyForEach组件需要在index对应索引处删除该子组件
60 | notifyDataDelete(index: number): void {
61 | this.listeners.forEach(listener => {
62 | listener.onDataDelete(index);
63 | // 写法2:listener.onDatasetChange([{type: DataOperationType.DELETE, index: index}]);
64 | });
65 | }
66 |
67 | // 通知LazyForEach组件将from索引和to索引处的子组件进行交换
68 | notifyDataMove(from: number, to: number): void {
69 | this.listeners.forEach(listener => {
70 | listener.onDataMove(from, to);
71 | // 写法2:listener.onDatasetChange(
72 | // [{type: DataOperationType.EXCHANGE, index: {start: from, end: to}}]);
73 | });
74 | }
75 |
76 | notifyDatasetChange(operations: DataOperation[]): void {
77 | this.listeners.forEach(listener => {
78 | listener.onDatasetChange(operations);
79 | });
80 | }
81 | }
82 |
83 | export default class HistoryDataSource extends BasicHistoryDataSource {
84 | private dataArray: Array> = [];
85 |
86 | constructor(fromData: Array>) {
87 | super();
88 | this.dataArray = fromData;
89 | }
90 |
91 | public setData(data: Array>): void {
92 | this.dataArray = data;
93 | this.notifyDataReload();
94 | }
95 |
96 | public totalCount(): number {
97 | return this.dataArray.length;
98 | }
99 |
100 | public getData(index: number): Array {
101 | return this.dataArray[index];
102 | }
103 |
104 | public pushData(data: Array): void {
105 | this.dataArray.push(data);
106 | this.notifyDataAdd(this.dataArray.length - 1);
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/home/src/main/ets/dialogs/prompts/woofWantProtectedResources.ets:
--------------------------------------------------------------------------------
1 | import linysConfirmDenyButtons from '../../components/buttons/linysConfirmDenyButtons';
2 | import linysText from '../../components/texts/linysText';
3 | import linysTextTitle from '../../components/texts/linysTextTitle';
4 | import { click_effect_default } from '../../hosts/bunch_of_defaults';
5 | import { desc_for_ProtectedResourceTypes, permissions_for_ProtectedResourceTypes, request_user_permission } from '../../utils/permission_tools';
6 | import { common } from '@kit.AbilityKit';
7 |
8 | @CustomDialog
9 | struct woofWantResources {
10 | controller: CustomDialogController;
11 | @Prop protected_resource_types: string[] = [];
12 | @Prop source: string = 'source';
13 | onPermit?: () => void;
14 | onDeny?: () => void;
15 | // Colors
16 | @StorageLink('effects') effects: boolean = false;
17 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
18 | @StorageLink('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
19 | // Settings / Accessibility
20 | @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
21 | // UI display
22 | @LocalStorageLink('screen_height') screen_height: number = 0;
23 | // Count
24 | @LocalStorageLink('opened_dialog_controllers') opened_dialog_controllers: CustomDialogController[] = [];
25 |
26 | aboutToAppear(): void {
27 | this.opened_dialog_controllers.push(this.controller);
28 | console.log(`[Dialog][Open] Now dialog count: ${this.opened_dialog_controllers.length}!`);
29 | }
30 |
31 | aboutToDisappear(): void {
32 | this.opened_dialog_controllers.splice(this.opened_dialog_controllers.indexOf(this.controller), 1);
33 | console.log(`[Dialog][Close] Now dialog count: ${this.opened_dialog_controllers.length}!`);
34 | }
35 |
36 | build() {
37 | Column({ space: 15 }) {
38 | linysTextTitle({ text: $r('app.string.Permission_Manager_webpage_asks_for_permission'), max_lines: 3 })// This page is asking for a jump
39 | .width("100%")
40 | linysText({ text: this.source })
41 |
42 | Column({ space: 10 }) {
43 | ForEach(desc_for_ProtectedResourceTypes(this.protected_resource_types), ((desc: ResourceStr) => {
44 | linysText({
45 | text: desc,
46 | max_lines: 5
47 | })
48 | .padding(10)
49 | .borderRadius(13.5)
50 | .clickEffect(click_effect_default())
51 | .backgroundColor(this.color_current_secondary)
52 | }))
53 | }
54 | .alignItems(HorizontalAlign.Start)
55 |
56 | linysText({ text: $r('app.string.Permission_Manager_grant_permission') })// allow?
57 | .width("100%")
58 |
59 | linysConfirmDenyButtons({
60 | onConfirm: () => {
61 | request_user_permission(this.getUIContext().getHostContext() as common.UIAbilityContext,
62 | permissions_for_ProtectedResourceTypes(this.protected_resource_types));
63 | if (this.onPermit) {
64 | this.onPermit();
65 | }
66 | if (this.controller) {
67 | this.controller.close();
68 | }
69 | },
70 | onDeny: () => {
71 | if (this.onDeny) {
72 | this.onDeny();
73 | }
74 | if (this.controller) {
75 | this.controller.close();
76 | }
77 | }
78 | }) // Buttons
79 | }
80 | .padding(20)
81 | .alignItems(HorizontalAlign.Start)
82 | .justifyContent(FlexAlign.Center)
83 | .width("100%")
84 | .backgroundColor(this.effects ? undefined : this.color_current_primary)
85 | .useEffect(this.effects, EffectType.WINDOW_EFFECT)
86 | }
87 | }
88 |
89 | export default woofWantResources;
--------------------------------------------------------------------------------
/home/src/main/ets/hosts/bunch_of_user_agents.ets:
--------------------------------------------------------------------------------
1 | import { bunch_of_settings } from "./bunch_of_settings";
2 |
3 | export class bunch_of_user_agents {
4 | static list_of_user_agents: user_agent[] = [];
5 |
6 | /**
7 | * Update last accessed time. This would trigger some refresh on UI layer.
8 | * */
9 | static update_last_accessed() {
10 | AppStorage.setOrCreate('bunch_of_user_agents_update', Date.now());
11 | }
12 |
13 | /**
14 | * Sets the global UA.
15 | * @param idx The index of UA.
16 | * */
17 | static set_global_UA(idx: number) {
18 | if (idx == -99) {
19 | // Setting index to -99 then the real value
20 | // Can force refresh @Watch
21 | // And the index -99 has no meaning
22 | return;
23 | }
24 | AppStorage.setOrCreate('user_agent_selected', idx);
25 | if (idx <= -1) {
26 | // If set back to default
27 | AppStorage.setOrCreate('universal_global_custom_ua_gateway', '');
28 | } else {
29 | AppStorage.setOrCreate('universal_global_custom_ua_gateway', bunch_of_user_agents.list_of_user_agents[idx].user_agent_content);
30 | }
31 | bunch_of_settings.set('custom_user_agents_selected_index', idx);
32 | }
33 |
34 | /**
35 | * Add a new user agent to the current user agent list.
36 | * @param se A user_agent object, the user agent to be added.
37 | * */
38 | static add_user_agent(ua: user_agent) {
39 | bunch_of_user_agents.list_of_user_agents.push(ua);
40 | bunch_of_user_agents.update_last_accessed();
41 | }
42 |
43 | /**
44 | * Remove a user agent from the current user agent list.
45 | * @param index A number, the index of the user agent to be removed in the list.
46 | * */
47 | static del_user_agent(index: number) {
48 | bunch_of_user_agents.list_of_user_agents.splice(index, 1);
49 | bunch_of_user_agents.update_last_accessed();
50 | }
51 |
52 | /**
53 | * Export user agents in a specific plain text format.
54 | * @returns '\n' connected string of user agents in the format of:
55 | * @example 'Firefox Windows 132\nMozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0'
56 | * */
57 | static export_string() {
58 | let export_list: string[] = []
59 | for (let index = 0; index < bunch_of_user_agents.list_of_user_agents.length; index++) {
60 | let ua: user_agent = bunch_of_user_agents.list_of_user_agents[index];
61 | export_list.push(ua.label);
62 | export_list.push(ua.user_agent_content);
63 | }
64 | return export_list.join("\n");
65 | }
66 |
67 | /**
68 | * Import user agents in a specific plain text format.
69 | *
70 | * In default overwrites whatever was in this list_of_user_agents.
71 | * @param imp The string in the correct format:
72 | * @example 'Firefox Windows 132\nMozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0'
73 | * */
74 | static import_string(imp: string) {
75 | // Clear
76 | bunch_of_user_agents.list_of_user_agents = [];
77 | if (imp == "") {
78 | return;
79 | }
80 | // Import
81 | let import_list: string[] = imp.split("\n");
82 | for (let index = 0; index < import_list.length; index += 2) {
83 | if (import_list[index] != "") {
84 | bunch_of_user_agents.add_user_agent(new user_agent(import_list[index], import_list[index+1]));
85 | }
86 | }
87 | }
88 | }
89 |
90 | export class user_agent {
91 | label: string;
92 | user_agent_content: string;
93 |
94 | /**
95 | * A user_agent object, which consists of a label and the user agent text content.
96 | * @param label The name of the user agent.
97 | * @param user_agent_content The content of the user agent.
98 | */
99 | constructor(label: string, content: string) {
100 | this.label = label;
101 | this.user_agent_content = content;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/home/src/main/ets/components/linysPathTree.ets:
--------------------------------------------------------------------------------
1 | import { animation_default, click_effect_default } from '../hosts/bunch_of_defaults';
2 | import linysText from './texts/linysText';
3 |
4 | @Component
5 | struct linysPathTree {
6 | @Link @Watch('on_current_viewing_path_change') current_viewing_path: string;
7 | @LocalStorageProp('screen_height') screen_height: number = 100;
8 | @Prop max_height_screen_percentage: number = 0.4;
9 | @State path_stack: string[] = [];
10 | @State label_stack: string[] = [];
11 | scroller: Scroller = new Scroller();
12 | // Settings / Accessibility
13 | @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
14 | // Colors
15 | @StorageLink('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
16 | @State isHov: boolean = false;
17 |
18 | build() {
19 | Scroll(this.scroller) {
20 | Column({ space: 5 }) {
21 | Row() {
22 | linysText({ text: " ", font_weight: FontWeight.Bold });
23 | }
24 | .brightness(this.isHov ? 1.1 : 1)
25 | .backgroundColor(this.color_current_secondary)
26 | .animation(animation_default())
27 | .borderRadius(10)
28 | .padding(10)
29 | .clickEffect(click_effect_default())
30 | .onClick(() => {
31 | this.current_viewing_path = "";
32 | })
33 | .onHover((isHv) => {
34 | this.isHov = isHv;
35 | })
36 |
37 | ForEach(this.label_stack, (item: string, index: number) => {
38 | linysPathButton({
39 | current_viewing_path: this.current_viewing_path,
40 | label: item,
41 | my_directory: this.path_stack[index],
42 | })
43 | })
44 | }
45 | .alignItems(this.preferred_hand_left_or_right == 'right' ? HorizontalAlign.End : HorizontalAlign.Start)
46 | .width("100%")
47 | .animation(animation_default())
48 | }
49 | .padding({ top: 5 })
50 | .scrollBar(BarState.Off)
51 | .height(5 + 42 + Math.min(this.screen_height * this.max_height_screen_percentage, 45 * this.label_stack.length))
52 | .animation(animation_default())
53 | .edgeEffect(EdgeEffect.Spring)
54 | }
55 |
56 | on_current_viewing_path_change() {
57 | this.decompose_path(this.current_viewing_path);
58 | }
59 |
60 | decompose_path(path: string) {
61 | let split_labels = path.split("/");
62 | let result: string[] = [];
63 | let meow_path = "";
64 | for (let index = 0; index < split_labels.length; index++) {
65 | let label = split_labels[index];
66 | if (meow_path == '') {
67 | meow_path = label;
68 | result.push(label);
69 | } else {
70 | meow_path = meow_path + "/" + label;
71 | result.push(meow_path);
72 | }
73 | }
74 | this.path_stack = result;
75 | this.label_stack = split_labels;
76 | if (this.label_stack.join() == "") {
77 | this.label_stack = [];
78 | }
79 | }
80 | }
81 |
82 | export default linysPathTree;
83 |
84 | @Component
85 | struct linysPathButton {
86 | @Prop label: string = "label";
87 | @Prop my_directory: string = "label";
88 | @Link current_viewing_path: string;
89 | // Colors
90 | @StorageLink('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
91 | @State isHov: boolean = false;
92 |
93 | build() {
94 | Row() {
95 | linysText({ text: this.label });
96 | }
97 | .brightness(this.isHov ? 1.1 : 1)
98 | .backgroundColor(this.color_current_secondary)
99 | .animation(animation_default())
100 | .borderRadius(10)
101 | .padding(10)
102 | .clickEffect(click_effect_default())
103 | .onClick(() => {
104 | this.current_viewing_path = this.my_directory;
105 | })
106 | .onHover((isHv) => {
107 | this.isHov = isHv;
108 | })
109 | }
110 | }
--------------------------------------------------------------------------------
/home/src/main/ets/workers/History_index_saver.ets:
--------------------------------------------------------------------------------
1 | import { collections, ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
2 | import { bunch_of_history_index } from '../hosts/bunch_of_history_index';
3 | import { sandbox_save } from '../utils/storage_tools';
4 | import { fileIo } from '@kit.CoreFileKit';
5 | import { common } from '@kit.AbilityKit';
6 |
7 | const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
8 | let map: collections.Map>;
9 | let file_name: string;
10 | let filesDir: string;
11 | let clear: boolean;
12 | /**
13 | * Defines the event handler to be called when the worker thread receives a message sent by the host thread.
14 | * The event handler is executed in the worker thread.
15 | *
16 | * @param event message data
17 | */
18 | workerPort.onmessage = (event: MessageEvents) => {
19 | if (typeof event.data == "string") {
20 | // receive information from main thread.
21 | const data = event.data.split(":");
22 | const header = data[0];
23 | const content = data[1];
24 | // set options
25 | if (header == 'filesDir') {
26 | filesDir = content;
27 | } else if (header == 'file_name') {
28 | file_name = content;
29 | } else if (header == 'clear') {
30 | clear = content == 'true' ? true : false;
31 | }
32 | } else {
33 | // Receives the map. LETS GOO!~
34 | map = event.data;
35 |
36 | // Save to disk
37 | save_index_ext(file_name, map, clear, filesDir);
38 |
39 | // Report finish
40 | workerPort.postMessage("done");
41 | }
42 | };
43 |
44 | /**
45 | * Defines the event handler to be called when the worker receives a message that cannot be deserialized.
46 | * The event handler is executed in the worker thread.
47 | *
48 | * @param event message data
49 | */
50 | workerPort.onmessageerror = (event: MessageEvents) => {
51 | };
52 |
53 | /**
54 | * Defines the event handler to be called when an exception occurs during worker execution.
55 | * The event handler is executed in the worker thread.
56 | *
57 | * @param event error message
58 | */
59 | workerPort.onerror = (event: ErrorEvent) => {
60 | };
61 |
62 | /**
63 | * Saves index set to sandbox
64 | * @param map
65 | * @param context_filesDir
66 | * */
67 | function save_index_ext(file_name: string, map: collections.Map>, clear: boolean, context_filesDir: string) {
68 | let filesDir: string = context_filesDir;
69 | let result: string[] = [];
70 | let index = 0;
71 |
72 | if (clear) {
73 | // DONE: Update this worker to adapt to the monthly division style of maps.
74 | // Clear all previous index
75 | try {
76 | fileIo.rmdirSync(context_filesDir + "/history-index");
77 | fileIo.mkdirSync(context_filesDir + "/history-index", true);
78 | } catch (e) {
79 |
80 | }
81 | }
82 |
83 | // Timer
84 | let s = Date.now();
85 | console.log(bunch_of_history_index.log_head_worker() + '[Save] Start to save map "' + file_name + '"! Map size: ' + map.size.toString() + ".")
86 |
87 | map.forEach((value, key) => {
88 | result.push(key)
89 | result.push(value.join("_"));
90 | // Report status
91 | if (index % 1000 == 0) {
92 | const progress = index / map.size * 100;
93 | workerPort.postMessage((Date.now() - s).toString() + " ms, " + (progress.toFixed(2) + "%"));
94 | }
95 | index += 1;
96 | })
97 |
98 | console.log(bunch_of_history_index.log_head_worker() + '[Save] Finished forEach traversal! Time used: ' + (Date.now() - s).toString() + " ms.")
99 |
100 | // console.log("qwq " + result.toString())
101 | sandbox_save('history-index/' + file_name, result.join('\n'), filesDir);
102 | workerPort.postMessage((Date.now() - s).toString() + " ms, 100%");
103 |
104 | // Timer end
105 | console.log(bunch_of_history_index.log_head_worker() + "[Save] Finished saving to disk in " + (Date.now() - s).toString() + " ms.")
106 | }
--------------------------------------------------------------------------------
/home/src/main/ets/hosts/bunch_of_search_engines.ets:
--------------------------------------------------------------------------------
1 | import { default_search_engine } from './bunch_of_defaults';
2 | import { bunch_of_settings } from './bunch_of_settings';
3 |
4 | export class bunch_of_search_engines {
5 | static list_of_search_engines: search_engine[] = [];
6 |
7 | /**
8 | * Update last accessed time. This would trigger some refresh on UI layer.
9 | * */
10 | static update_last_accessed() {
11 | AppStorage.setOrCreate('bunch_of_search_engines_update', Date.now());
12 | }
13 |
14 | /**
15 | * Sets the global SE.
16 | * @param idx The index of SE.
17 | * */
18 | static set_global_SE(idx: number) {
19 | if (idx == -99) {
20 | // Setting index to -99 then the real value
21 | // Can force refresh @Watch
22 | // And the index -99 has no meaning
23 | return;
24 | }
25 | AppStorage.setOrCreate('search_engine_selected', idx);
26 | if (idx <= -1) {
27 | // If set back to default
28 | AppStorage.setOrCreate('search_engine', default_search_engine());
29 | } else {
30 | AppStorage.setOrCreate('search_engine', bunch_of_search_engines.list_of_search_engines[idx].url);
31 | }
32 | bunch_of_settings.set('custom_search_engines_selected_index', idx);
33 | }
34 |
35 | /**
36 | * Add a new search engine to the current search engine list.
37 | * @param se A search_engine object, the search engine to be added.
38 | * */
39 | static add_search_engine(se: search_engine) {
40 | bunch_of_search_engines.list_of_search_engines.push(se);
41 | bunch_of_search_engines.update_last_accessed();
42 | }
43 |
44 | /**
45 | * Remove a search engine from the current search engine list.
46 | * @param index A number, the index of the search engine to be removed in the list.
47 | * */
48 | static del_search_engine(index: number) {
49 | bunch_of_search_engines.list_of_search_engines.splice(index, 1);
50 | bunch_of_search_engines.update_last_accessed();
51 | }
52 |
53 | /**
54 | * Export search engines in a specific plain text format.
55 | * @returns '\n' connected string of search engines in the format of:
56 | * @example 'Bing\nhttps://www.cn.bing.com/search?q=%s\nGoogle\nhttps://www.google.com/search?q=%s'
57 | * */
58 | static export_string() {
59 | let export_list: string[] = []
60 | for (let index = 0; index < bunch_of_search_engines.list_of_search_engines.length; index++) {
61 | let ua: search_engine = bunch_of_search_engines.list_of_search_engines[index];
62 | export_list.push(ua.label);
63 | export_list.push(ua.url);
64 | }
65 | return export_list.join("\n");
66 | }
67 |
68 | /**
69 | * Import search engines in a specific plain text format.
70 | * In default overwrites whatever was in this list_of_search_engines.
71 | * @param imp The string in the correct format:
72 | * @example 'Bing\nhttps://www.cn.bing.com/search?q=%s\nGoogle\nhttps://www.google.com/search?q=%s'
73 | * */
74 | static import_string(imp: string) {
75 | // Clear
76 | bunch_of_search_engines.list_of_search_engines = [];
77 | if (imp == "") {
78 | return;
79 | }
80 | // Import
81 | let import_list: string[] = imp.split("\n");
82 | for (let index = 0; index < import_list.length; index += 2) {
83 | if (import_list[index] != "") {
84 | bunch_of_search_engines.add_search_engine(new search_engine(import_list[index], import_list[index+1]));
85 | }
86 | }
87 | }
88 | }
89 |
90 | export class search_engine {
91 | label: string;
92 | url: string;
93 |
94 | /**
95 | * A search_engine object, which consists of a label and the search engine link.
96 | * @param label The name of the search engine.
97 | * @param link The link of the search engine, '%s' in which will be replaced with real search keywords.
98 | */
99 | constructor(label: string, content: string) {
100 | this.label = label;
101 | this.url = content;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/home/src/main/ets/dialogs/quicks/woofQuickSE.ets:
--------------------------------------------------------------------------------
1 | import { animation_default, click_effect_default, default_search_engine } from '../../hosts/bunch_of_defaults';
2 | import { bunch_of_search_engines, search_engine } from '../../hosts/bunch_of_search_engines';
3 | import lazy woofControlFrame from '../woofControlFrame';
4 | import meowSEManager from '../../blocks/panels/meowSEManager';
5 | import linysText from '../../components/texts/linysText';
6 |
7 | @CustomDialog
8 | struct woofQuickSE {
9 | controller: CustomDialogController;
10 | @Prop keyword: string = ''
11 | @Prop default_new_se: search_engine = new search_engine(new Date().toLocaleString(), default_search_engine());
12 | // Colors
13 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
14 | @StorageLink('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
15 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
16 | // Settings - Accessibility
17 | @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
18 | // Animation UI Statuses
19 | @State adding_SE: boolean = false;
20 | @State height_of_add_panel: number = 0;
21 | button_height_default: number = 42;
22 | // Gateways
23 | @LocalStorageLink('universal_load_url_gateway') load_url_gateway: string = "";
24 | // Count
25 | @LocalStorageLink('opened_dialog_controllers') opened_dialog_controllers: CustomDialogController[] = [];
26 |
27 | aboutToAppear(): void {
28 | this.opened_dialog_controllers.push(this.controller);
29 | console.log(`[Dialog][Open] Now dialog count: ${this.opened_dialog_controllers.length}!`);
30 | }
31 |
32 | aboutToDisappear(): void {
33 | this.opened_dialog_controllers.splice(this.opened_dialog_controllers.indexOf(this.controller), 1);
34 | console.log(`[Dialog][Close] Now dialog count: ${this.opened_dialog_controllers.length}!`);
35 | }
36 |
37 | build() {
38 | woofControlFrame({
39 | title: $r('app.string.AddSE_search_again_on'),
40 | controller: this.controller
41 | }) {
42 | Scroll() {
43 | Column({ space: 10 }) {
44 | linysText({
45 | text: ' ' + this.keyword + ' '
46 | })
47 | .padding(10)
48 | .borderRadius(13.5)
49 | .clickEffect(click_effect_default())
50 | .backgroundColor(this.color_current_secondary)
51 |
52 | meowSEManager({
53 | default_new_se: this.default_new_se,
54 | on_default_execution: () => {
55 | this.search_with_se(this.keyword);
56 | },
57 | on_normal_execution: (idx, _se) => {
58 | this.search_with_se(this.keyword, idx);
59 | }
60 | })
61 | }
62 | .alignItems(HorizontalAlign.Start)
63 | }
64 | .borderRadius(13.5)
65 | .layoutWeight(1)
66 | .scrollBar(BarState.Off)
67 | .edgeEffect(EdgeEffect.Spring)
68 | .constraintSize({
69 | maxHeight: 45 + 48.5 * bunch_of_search_engines.list_of_search_engines.length + 100 + (this.adding_SE ? 20 + this.height_of_add_panel : 0)
70 | })
71 | .animation(animation_default())
72 | .onAppear(() => {
73 | console.log('[woofQuickSE] About to search: "' + this.keyword + '" on another engine.')
74 | })
75 | }
76 | }
77 |
78 | search_with_se(key: string, clicked_index?: number) {
79 | // Choose to search on another engine
80 | if (clicked_index !== undefined) {
81 | this.load_url_gateway = bunch_of_search_engines.list_of_search_engines[clicked_index].url.replaceAll('%s', key);
82 | } else {
83 | this.load_url_gateway = default_search_engine().replaceAll('%s', key);
84 | }
85 | console.log('[woofQuickSE] Search: "' + this.keyword + '" => ' + this.load_url_gateway);
86 | if (this.controller) {
87 | this.controller.close();
88 | }
89 | }
90 | }
91 |
92 | export default woofQuickSE;
--------------------------------------------------------------------------------
/home/src/main/ets/blocks/panels/meowHomepageManager.ets:
--------------------------------------------------------------------------------
1 | import lazy {
2 | arrayBuffer_2_pixelMap_sync,
3 | image_document_pick_to_ArrayBuffer,
4 | image_gallery_pick_to_ArrayBuffer,
5 | sandbox_save,
6 | sandbox_unlink
7 | } from '../../utils/storage_tools';
8 | import linysCapsuleButtonWithText from '../../components/buttons/linysCapsuleButtonWithText';
9 | import linysTimeoutButtonWithText from '../../components/buttons/linysTimeoutButtonWithText';
10 |
11 | @Component
12 | struct meowHomepageManager {
13 | // Gateways
14 | @LocalStorageLink('universal_fail_prompt_desc_gateway') uni_fail_prompt_gateway: ResourceStr = "";
15 | // Environments
16 | @StorageLink('homepage_background') homepage_background: PixelMap | undefined = undefined;
17 | // Colors
18 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
19 | @StorageLink('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
20 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
21 | // Settings / Accessibility
22 | @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
23 |
24 | build() {
25 | Column({ space: 10 }) {
26 | linysCapsuleButtonWithText({
27 | desc_text: $r('app.string.Homepage_background_select_storage'),
28 | button_text: " ",
29 | onExecution: () => {
30 | this.select_set_background_image_storage();
31 | }
32 | }) // Homepage background select from files
33 |
34 | linysCapsuleButtonWithText({
35 | desc_text: $r('app.string.Homepage_background_select_gallery'),
36 | button_text: " ",
37 | onExecution: () => {
38 | this.select_set_background_image_gallery();
39 | }
40 | }) // Homepage background select from files
41 |
42 | linysTimeoutButtonWithText({
43 | desc_text: $r('app.string.Homepage_background_delete'),
44 | button_text: " ",
45 | onExecution: () => {
46 | this.delete_homepage_background();
47 | }
48 | }) // Homepage background delete
49 | }
50 | .alignItems(this.preferred_hand_left_or_right == 'right' ? HorizontalAlign.End : HorizontalAlign.Start)
51 | }
52 |
53 | /**
54 | * Selects an image from storage.
55 | * */
56 | select_set_background_image_storage() {
57 | image_document_pick_to_ArrayBuffer().then(result => {
58 | if (result !== undefined) {
59 | let buf = result as ArrayBuffer;
60 | // Set image for homepage
61 | this.homepage_background = arrayBuffer_2_pixelMap_sync(buf);
62 | // Save this to sandbox
63 | sandbox_save('homepage_background_arrayBuffer', buf);
64 | } else {
65 | console.log('[meowAppSettings] Select homepage background failed. Received undefined?')
66 | this.uni_fail_prompt_gateway = $r('app.string.Fail_select_bad_image');
67 | }
68 | });
69 | }
70 |
71 | /**
72 | * Selects an image from gallery.
73 | * */
74 | select_set_background_image_gallery() {
75 | image_gallery_pick_to_ArrayBuffer().then(result => {
76 | if (result !== undefined) {
77 | let buf = result as ArrayBuffer;
78 | // Set image for homepage
79 | this.homepage_background = arrayBuffer_2_pixelMap_sync(buf);
80 | // Save this to sandbox
81 | sandbox_save('homepage_background_arrayBuffer', buf);
82 | } else {
83 | console.log('[meowAppSettings] Select homepage background failed. Received undefined?')
84 | this.uni_fail_prompt_gateway = $r('app.string.Fail_select_bad_image');
85 | }
86 | });
87 | }
88 |
89 | delete_homepage_background() {
90 | // Delete homepage background image
91 | this.homepage_background = undefined;
92 | sandbox_unlink('homepage_background_arrayBuffer');
93 | console.log("[meowAppSettings] Deleted homepage background!")
94 | }
95 | }
96 |
97 | export default meowHomepageManager;
--------------------------------------------------------------------------------
/home/src/main/resources/rawfile/cathrome/i18n.js:
--------------------------------------------------------------------------------
1 | /**
2 | * StandaloneI18n Class
3 | * * This class provides a basic implementation of the Chrome i18n API
4 | * (chrome.i18n) for use in environments where the native API is unavailable,
5 | * such as non-extension web pages or testing frameworks.
6 | * * NOTE: This is a structural and functional mimic. It does not handle
7 | * real-world locale file loading (_locales/xx/messages.json) or advanced
8 | * Chrome features like Bidi-UI.
9 | */
10 | class cathrome_i18n {
11 | constructor() {
12 | // Log initialization success
13 | console.log(`[i18n] StandaloneI18n initialized!`);
14 | }
15 |
16 | /**
17 | * Gets the localized string for the specified message name.
18 | * Substitutions using $1, $2, etc., are supported, mimicking Chrome's behavior.
19 | * * @param {string} messageName - The name of the message to retrieve.
20 | * @param {string|string[]|null} substitutions - A single string or an array of substitution strings.
21 | * @returns {string} The localized message, or an error message if not found.
22 | */
23 | getMessage(messageName, substitutions = [], options = {}) {
24 | let message = CatsBridge.chrome_i18n_getMessage(messageName);
25 |
26 | // 1. Handle substitutions
27 | const subsArray = Array.isArray(substitutions) ? substitutions : (substitutions ? [substitutions] : []);
28 |
29 | // Iterate through substitutions (up to $99)
30 | for (let i = 0; i < subsArray.length; i++) {
31 | // Replace $1, $2, etc. with the substitution string
32 | const placeholder = new RegExp('\\$' + (i + 1), 'g');
33 | message = message.replace(placeholder, subsArray[i]);
34 | }
35 |
36 | // 2. Handle escape sequences: replace '$$' with '$'
37 | message = message.replace(/\$\$/g, '$');
38 |
39 | const escapeLt = options.escapeLt;
40 | if (escapeLt) {
41 | // ?
42 | }
43 |
44 | return message;
45 | }
46 |
47 | /**
48 | * Gets the browser's UI language. In this standalone version, it returns the
49 | * locale provided during initialization.
50 | * * @returns {string} The application's UI language.
51 | */
52 | getUILanguage() {
53 | return CatsBridge.chrome_i18n_getUILanguage();
54 | }
55 |
56 | /**
57 | * Gets the language list that the browser supports.
58 | * This is an asynchronous method in the Chrome API, so we mimic the structure.
59 | * * NOTE: In a real implementation, you'd fetch this from the environment
60 | * (e.g., navigator.languages) or return it directly if synchronous is acceptable.
61 | * * @param {function(string[]): void} callback - A callback function that accepts the language array.
62 | */
63 | getAcceptLanguages(callback) {
64 | // Mocking the asynchronous call with a hardcoded list and a setTimeout.
65 | const mockLanguages = CatsBridge.chrome_i18n_getAcceptLanguages();
66 |
67 | // Use setTimeout to mimic the asynchronous nature of a Chrome API call
68 | setTimeout(() => {
69 | if (typeof callback === 'function') {
70 | callback(mockLanguages);
71 | }
72 | }, 0);
73 | }
74 |
75 | /**
76 | * Detects the language of the provided text.
77 | * This method mimics the asynchronous signature and return structure of the Chrome API.
78 | * * @param {string} text - The text to analyze for language.
79 | * @param {function(Object): void} callback - A callback function that accepts the detection result object.
80 | */
81 | detectLanguage(text, callback) {
82 | // Simple, mock language detection logic based on keywords
83 | const normalizedText = text.toLowerCase();
84 |
85 | // Construct the result object as per Chrome API specification
86 | const result = {
87 | isReliable: isReliable,
88 | languages: [{
89 | language: 'und',
90 | percentage: 49 // Mock percentage
91 | }]
92 | };
93 |
94 | // Use setTimeout to mimic the asynchronous nature of a Chrome API call
95 | setTimeout(() => {
96 | if (typeof callback === 'function') {
97 | callback(result);
98 | }
99 | }, 0);
100 | }
101 | }
--------------------------------------------------------------------------------
/home/src/main/ets/dialogs/web/woofHttpAuthPrompt.ets:
--------------------------------------------------------------------------------
1 | import linysConfirmDenyButtons from '../../components/buttons/linysConfirmDenyButtons';
2 | import linysText from '../../components/texts/linysText';
3 | import linysTextTitle from '../../components/texts/linysTextTitle';
4 | import { capsule_bar_height } from '../../hosts/bunch_of_defaults';
5 |
6 | @CustomDialog
7 | struct woofHttpAuthPrompt {
8 | @Link e: OnHttpAuthRequestEvent | undefined;
9 | username: string = 'Username Meow';
10 | password: string = 'Password Meow';
11 | controller: CustomDialogController;
12 | @StorageLink('woofHttpAuthPrompt_result') woofHttpAuthPrompt_result: boolean = false;
13 | // Colors
14 | @StorageLink('effects') effects: boolean = false;
15 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
16 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
17 | // UI display
18 | @LocalStorageLink('screen_height') screen_height: number = 0;
19 | // Count
20 | @LocalStorageLink('opened_dialog_controllers') opened_dialog_controllers: CustomDialogController[] = [];
21 |
22 | aboutToAppear(): void {
23 | this.opened_dialog_controllers.push(this.controller);
24 | console.log(`[Dialog][Open] Now dialog count: ${this.opened_dialog_controllers.length}!`);
25 | }
26 |
27 | aboutToDisappear(): void {
28 | this.cancel();
29 | this.opened_dialog_controllers.splice(this.opened_dialog_controllers.indexOf(this.controller), 1);
30 | console.log(`[Dialog][Close] Now dialog count: ${this.opened_dialog_controllers.length}!`);
31 | }
32 |
33 | build() {
34 | Column({ space: 15 }) {
35 | linysTextTitle({ text: this.e?.host + ' [' + this.e?.realm + ']', max_lines: 2 }) // Page url
36 | .width("100%")
37 |
38 | linysText({ text: $r('app.string.http_auth_desc'), max_lines: 2 }) // Prompt message
39 | .width("100%")
40 |
41 | TextInput({ text: '', placeholder: $r('app.string.username') }) // Username
42 | .onChange((value) => {
43 | this.username = value;
44 | })
45 | .fontWeight(FontWeight.Regular)
46 | .fontColor(this.color_current_font)
47 | .caretColor(this.color_current_font)
48 | .selectedBackgroundColor(this.color_current_font)
49 | .id('woofHttpAuthPrompt_username')
50 | .onSubmit(() => {
51 | focusControl.requestFocus('woofHttpAuthPrompt_password');
52 | })
53 | .height(capsule_bar_height())
54 | .onAppear(() => {
55 | focusControl.requestFocus('woofHttpAuthPrompt_username');
56 | })
57 |
58 | TextInput({ text: '', placeholder: $r('app.string.password') }) // Password
59 | .onChange((value) => {
60 | this.password = value;
61 | })
62 | .fontWeight(FontWeight.Regular)
63 | .fontColor(this.color_current_font)
64 | .caretColor(this.color_current_font)
65 | .selectedBackgroundColor(this.color_current_font)
66 | .id('woofHttpAuthPrompt_password')
67 | .onSubmit(() => {
68 | // Submit
69 | this.submit();
70 | if (this.controller) {
71 | this.controller.close();
72 | }
73 | })
74 | .height(capsule_bar_height())
75 |
76 | linysConfirmDenyButtons({
77 | onConfirm: () => {
78 | this.submit();
79 | if (this.controller) {
80 | this.controller.close();
81 | }
82 | },
83 | onDeny: () => {
84 | this.cancel();
85 | if (this.controller) {
86 | this.controller.close();
87 | }
88 | },
89 | deny_double_confirm: true,
90 | confirm_double_confirm: true,
91 | }) // Buttons
92 | }
93 | .padding(20)
94 | .alignItems(HorizontalAlign.Start)
95 | .justifyContent(FlexAlign.Center)
96 | .width("100%")
97 | .backgroundColor(this.effects ? undefined : this.color_current_primary)
98 | .useEffect(this.effects, EffectType.WINDOW_EFFECT)
99 | }
100 |
101 | submit() {
102 | this.woofHttpAuthPrompt_result = true;
103 | this.e?.handler.confirm(this.username, this.password);
104 | }
105 |
106 | cancel() {
107 | this.woofHttpAuthPrompt_result = false;
108 | this.e?.handler.cancel();
109 | }
110 | }
111 |
112 | export default woofHttpAuthPrompt;
--------------------------------------------------------------------------------
/home/src/main/ets/workers/History_index_loader.ets:
--------------------------------------------------------------------------------
1 | import { collections, ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
2 | import { bunch_of_history_index } from '../hosts/bunch_of_history_index';
3 | import { fileIo } from '@kit.CoreFileKit';
4 | import { bunch_of_history_index_lite as month_host } from '../hosts/bunch_of_history_index_lite';
5 |
6 | const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
7 | let threshold: number = 0;
8 |
9 | /**
10 | * Defines the event handler to be called when the worker thread receives a message sent by the host thread.
11 | * The event handler is executed in the worker thread.
12 | *
13 | * @param event message data
14 | */
15 | workerPort.onmessage = (event: MessageEvents) => {
16 | // receive getContext().filesDir from main thread
17 | if (typeof event.data == "number") {
18 | // threshold of load index files amount
19 | threshold = event.data as number;
20 | }
21 | if (typeof event.data == "string") {
22 | // Receive the filesDir
23 | let filesDir = event.data as string;
24 | // Execution
25 | sandbox_load_index_sync(filesDir, threshold);
26 | // Send back information
27 | let back = new collections.Array>>();
28 | back.push(bunch_of_history_index.index_map);
29 | back.push(month_host.index_map);
30 | workerPort.postMessageWithSharedSendable(back);
31 | }
32 | };
33 |
34 | /**
35 | * Defines the event handler to be called when the worker receives a message that cannot be deserialized.
36 | * The event handler is executed in the worker thread.
37 | *
38 | * @param event message data
39 | */
40 | workerPort.onmessageerror = (event: MessageEvents) => {
41 | };
42 |
43 | /**
44 | * Defines the event handler to be called when an exception occurs during worker execution.
45 | * The event handler is executed in the worker thread.
46 | *
47 | * @param event error message
48 | */
49 | workerPort.onerror = (event: ErrorEvent) => {
50 | };
51 |
52 | /**
53 | * Loads built index from disk.
54 | * */
55 | function sandbox_load_index_sync(context_filesDir: string, load_threshold: number = 0) {
56 | let s = Date.now();
57 | console.log(bunch_of_history_index.log_head_worker() + '[Load] Start loading from sandbox! Start: ' + new Date(s).toString() + '.')
58 |
59 | let all_index_files = fileIo.listFileSync(context_filesDir + '/history-index', { recursion: false }).sort();
60 |
61 | console.log(bunch_of_history_index.log_head_worker() + '[Load][listFileSync] all files: ' + all_index_files.join(", "));
62 |
63 | if (all_index_files.length == 0) {
64 | console.log(bunch_of_history_index.log_head_worker() + ' No index found. ')
65 | // console.log(bunch_of_history_index.log_head_worker() + ' Rebuilding... ')
66 | // history_index_full_rebuild_worker();
67 | return;
68 | }
69 |
70 | // Limit history index months
71 | // let load_threshold = AppStorage.get('history_index_load_quantity') as number;
72 | if (load_threshold != 0 && all_index_files.length > load_threshold) {
73 | all_index_files = all_index_files.slice(all_index_files.length - load_threshold, all_index_files.length);
74 | }
75 |
76 | workerPort.postMessage((Date.now() - s).toString() + " ms, 0.01%");
77 |
78 | // clear
79 | bunch_of_history_index.clear();
80 |
81 | // Read every file under history-index
82 | for (let index = 0; index < all_index_files.length; index++) {
83 | const name = all_index_files[index];
84 |
85 | bunch_of_history_index.index_from_index_file('history-index/' + name, context_filesDir);
86 |
87 | workerPort.postMessage((Date.now() - s).toString() + " ms, " + (index / all_index_files.length * 90).toFixed(2) + "%");
88 | // console.log(bunch_of_history_index.log_head_worker() + '[Load] Finish loading: ' + name + '.');
89 | }
90 |
91 | // Load this month to a separate host
92 | month_host.clear();
93 | const current_year: number = new Date().getUTCFullYear();
94 | const current_month: number = new Date().getUTCMonth() + 1;
95 | month_host.index_from_index_month_file(current_year, current_month, context_filesDir);
96 |
97 | // End report
98 | console.log(bunch_of_history_index.log_head_worker() + '[Load] Finish loading from sandbox! Time used: ' + (Date.now() - s).toString() + ' ms. ' +
99 | 'Map size: ' + bunch_of_history_index.index_map.size.toString() + '.');
100 |
101 | workerPort.postMessage((Date.now() - s).toString() + " ms, 100%");
102 | }
--------------------------------------------------------------------------------
/home/src/main/ets/dialogs/web/woofPrompt.ets:
--------------------------------------------------------------------------------
1 | import linysConfirmDenyButtons from '../../components/buttons/linysConfirmDenyButtons';
2 | import linysText from '../../components/texts/linysText';
3 | import linysTextArea from '../../components/texts/linysTextArea';
4 | import linysTextTitle from '../../components/texts/linysTextTitle';
5 | import { capsule_bar_height } from '../../hosts/bunch_of_defaults';
6 |
7 | @CustomDialog
8 | struct woofPrompt {
9 | @State title: ResourceStr = 'Title Meow';
10 | @State message: ResourceStr = 'Message Meow';
11 | @State value: string = 'Value Meow';
12 | @Link promptResult: string;
13 | single_line: boolean = false;
14 | controller: CustomDialogController;
15 | // Colors
16 | @StorageLink('effects') effects: boolean = false;
17 | @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
18 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
19 | // UI display
20 | @LocalStorageLink('screen_height') screen_height: number = 0;
21 | // Actions
22 | onConfirm?: () => void;
23 | onDeny?: () => void;
24 | // Count
25 | @LocalStorageLink('opened_dialog_controllers') opened_dialog_controllers: CustomDialogController[] = [];
26 | // Focus
27 | my_id: string = 'woofHistory_search_input_' + Date.now().toString();
28 |
29 | aboutToAppear(): void {
30 | this.opened_dialog_controllers.push(this.controller);
31 | console.log(`[Dialog][Open] Now dialog count: ${this.opened_dialog_controllers.length}!`);
32 | }
33 |
34 | aboutToDisappear(): void {
35 | if (this.onDeny) {
36 | this.onDeny();
37 | }
38 | this.opened_dialog_controllers.splice(this.opened_dialog_controllers.indexOf(this.controller), 1);
39 | console.log(`[Dialog][Close] Now dialog count: ${this.opened_dialog_controllers.length}!`);
40 | }
41 |
42 | build() {
43 | Column({ space: 15 }) {
44 | linysTextTitle({ text: this.title, max_lines: 2 }) // Page url
45 | .width("100%")
46 |
47 | linysText({ text: this.message, max_lines: 999 }) // Prompt message
48 | .width("100%")
49 |
50 | if (this.single_line) {
51 | TextInput({ text: this.value })
52 | .onChange((value) => {
53 | this.promptResult = value;
54 | })
55 | .fontWeight(FontWeight.Regular)
56 | .fontColor(this.color_current_font)
57 | .caretColor(this.color_current_font)
58 | .selectedBackgroundColor(this.color_current_font)
59 | .onSubmit(() => {
60 | if (this.controller) {
61 | if (this.onConfirm) {
62 | this.onConfirm();
63 | }
64 | this.controller.close();
65 | }
66 | })
67 | .height(capsule_bar_height())
68 | .id(this.my_id)
69 | .onAppear(() => {
70 | // Auto focus on history
71 | if (AppStorage.get('free_window_mode') as boolean) {
72 | this.focus_input();
73 | }
74 | })
75 | } else {
76 | linysTextArea({ init_text: this.value, text: this.promptResult }) // Edit
77 | .constraintSize({ maxHeight: Math.max(0, this.screen_height - 160) })
78 | .id(this.my_id)
79 | .onAppear(() => {
80 | // Auto focus on history
81 | if (AppStorage.get('free_window_mode') as boolean) {
82 | this.focus_input();
83 | }
84 | })
85 | }
86 |
87 | linysConfirmDenyButtons({
88 | onConfirm: () => {
89 | if (this.controller) {
90 | if (this.onConfirm) {
91 | this.onConfirm();
92 | }
93 | this.controller.close();
94 | }
95 | },
96 | onDeny: () => {
97 | if (this.controller) {
98 | if (this.onDeny) {
99 | this.onDeny();
100 | }
101 | this.controller.close();
102 | }
103 | },
104 | deny_double_confirm: true,
105 | confirm_double_confirm: true,
106 | }) // Buttons
107 | }
108 | .padding(20)
109 | .alignItems(HorizontalAlign.Start)
110 | .justifyContent(FlexAlign.Center)
111 | .width("100%")
112 | .backgroundColor(this.effects ? undefined : this.color_current_primary)
113 | .useEffect(this.effects, EffectType.WINDOW_EFFECT)
114 | }
115 |
116 | /**
117 | * Focus on the search input.
118 | * */
119 | focus_input() {
120 | focusControl.requestFocus(this.my_id);
121 | }
122 | }
123 |
124 | export default woofPrompt;
--------------------------------------------------------------------------------
/home/src/main/ets/hosts/bunch_of_defaults.ets:
--------------------------------------------------------------------------------
1 | import lazy Curves from '@ohos.curves';
2 | import lazy { resource_to_string } from '../utils/resource_tools';
3 |
4 | /**
5 | * Default fontSize_Extra for texts.
6 | *
7 | * Usually used on titles.
8 | * @returns 26
9 | * */
10 | export function fontSize_Extra() {
11 | return 24;
12 | }
13 |
14 | /**
15 | * Default fontSize_Large for texts.
16 | *
17 | * Usually used on sub titles.
18 | * @returns 20
19 | * */
20 | export function fontSize_Large() {
21 | return 20;
22 | }
23 |
24 | /**
25 | * Default fontSize_Normal for texts.
26 | *
27 | * Usually used on body parts.
28 | * @returns 16
29 | * */
30 | export function fontSize_Normal() {
31 | return 16;
32 | }
33 |
34 | /**
35 | * Default fontSize_Icon_Button for HarmonyOS Symbols.
36 | *
37 | * Usually used on buttons, like linysSymbol.
38 | * @returns 25
39 | * */
40 | export function fontSize_Icon_Button() {
41 | return 25;
42 | }
43 |
44 | /**
45 | * Default capsule bar height for capsule-shape components, like a capsule button.
46 | * @returns 35
47 | * */
48 | export function capsule_bar_height() {
49 | return 35;
50 | }
51 |
52 | /**
53 | * Default minimum width for cards in panels, like those in settings and downloads panel.
54 | * @returns 480
55 | * */
56 | export function minimum_card_width() {
57 | return 480;
58 | }
59 |
60 | /**
61 | * Default animation params.
62 | * @returns An AnimateParam, reading the arguments from app settings.
63 | * */
64 | export function animation_default() {
65 | let animation_damping_coefficient = AppStorage.get('animation_damping_coefficient') as number;
66 | let animation_response = AppStorage.get('animation_response') as number;
67 | let spring = Curves.springMotion(animation_response / 100, (100 - animation_damping_coefficient) / 100);
68 | let ap: AnimateParam = { curve: spring };
69 | return ap;
70 | }
71 |
72 | /**
73 | * Standard popup notice duration for intervals of 1 millisecond, 2 seconds.
74 | * @returns 2000
75 | * */
76 | export function animation_popup_duration() {
77 | return 1000;
78 | }
79 |
80 | /**
81 | * Default Click effect.
82 | * @returns ClickEffect = { level: ClickEffectLevel.LIGHT }
83 | * */
84 | export function click_effect_default() {
85 | let ce: ClickEffect = { level: ClickEffectLevel.LIGHT };
86 | return ce;
87 | }
88 |
89 | /**
90 | * Default link of blank page.
91 | * @returns 'meow://home'
92 | * */
93 | export function url_default_blank() {
94 | return 'meow://home';
95 | }
96 |
97 | /**
98 | * Preset search engines in a specified string format.
99 | * @returns Baidu and Google.
100 | * @example 'Bing\nhttps://www.cn.bing.com/search?q=%s'
101 | * */
102 | export function preset_search_engines() {
103 | let result = [
104 | "Baidu\nhttps://www.baidu.com/s?wd=%s",
105 | "Google\nhttps://www.google.com/search?q=%s",
106 | ];
107 | return result.join("\n");
108 | }
109 |
110 | /**
111 | * The single default search engine.
112 | * @returns 'https://bing.com/search?q=%s'
113 | * */
114 | export function default_search_engine() {
115 | return 'https://bing.com/search?q=%s';
116 | }
117 |
118 | /**
119 | * Default user agents in a specified string format.
120 | * @returns Firefox, Chrome and Edge.
121 | * @example 'Firefox Windows 132\nMozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0'
122 | * */
123 | export function default_user_agents() {
124 | let result = [
125 | "Huawei Browser 132\nMozilla/5.0 (PC; OpenHarmony 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 ArkWeb/6.0.0.107 HuaweiBrowser/233.1.9.311",
126 | "Firefox Windows 132\nMozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0",
127 | "Edge Windows 132\nMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/130.0.0.0",
128 | "Chrome Windows 132\nMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
129 | "Chrome Android 132\nMozilla/5.0 (Linux; Android 12; ALN-AL80 Build/5.0.0.107) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Mobile Safari/537.36",
130 | ];
131 | return result.join("\n");
132 | }
133 |
134 | /**
135 | * The default name of a window.
136 | * @returns 'Foxy Meowy Dodgy! Welcome to a new window!'
137 | * */
138 | export function default_new_window_name(): string {
139 | return resource_to_string($r('app.string.Settings_appearance_windows_default_name'));
140 | }
141 |
142 | /**
143 | * Obtains the default window_decor_height.
144 | * @returns A number, in vp.
145 | * */
146 | export function default_window_decor_height(): number {
147 | return (AppStorage.get('showing_my_window_alias') as boolean) ? 45 : 37;
148 | }
--------------------------------------------------------------------------------
/home/src/main/ets/objects/ExtWebNode.ets:
--------------------------------------------------------------------------------
1 | import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI';
2 | import { on_new_window } from '../processes/web_actions';
3 | import lazy { hilog } from '@kit.PerformanceAnalysisKit';
4 | import { extension } from '../hosts/bunch_of_extensions';
5 | import { CatsBridgeMethods } from './CatstensionBridgeObj';
6 | import { text_from_rawfile } from '../utils/storage_tools';
7 |
8 | @Builder
9 | function WebBuilder(ext: extension) {
10 | Web({ controller: ext.controller, src: $rawfile('home.html') })
11 | .mediaPlayGestureAccess(false)
12 | .databaseAccess(true)
13 | .fileAccess(true)
14 | .domStorageAccess(true)
15 | .multiWindowAccess(true)
16 | .mixedMode(MixedMode.All)
17 | .allowWindowOpenMethod(true)
18 | .mediaOptions({ audioExclusive: false })
19 | .darkMode(WebDarkMode.Auto) // Dark mode!
20 | .javaScriptAccess(true)
21 | .imageAccess(true)
22 | .onlineImageAccess(true)
23 | // .layoutMode(WebLayoutMode.FIT_CONTENT)
24 | .onWindowNew((event) => {
25 | if (ext.storage) {
26 | on_new_window(ext.storage, event);
27 | } else {
28 | event.handler.setWebController(null);
29 | }
30 | })
31 | .onPageEnd((e) => {
32 | console.log(`[Extension][${ext.name}][${ext.id}] Web Loaded! url=${e.url}`);
33 | })
34 | .javaScriptProxy({
35 | object: ext.bridge,
36 | name: 'CatsBridge',
37 | methodList: CatsBridgeMethods(),
38 | controller: ext.controller
39 | })
40 | .runJavaScriptOnDocumentStart([
41 | { script: text_from_rawfile('cathrome/eventListener.js'), scriptRules: ["*"] },
42 | { script: text_from_rawfile('cathrome/liny.js'), scriptRules: ["*"] },
43 | { script: text_from_rawfile('cathrome/alarms.js'), scriptRules: ["*"] },
44 | { script: text_from_rawfile('cathrome/action.js'), scriptRules: ["*"] },
45 | { script: text_from_rawfile('cathrome/bookmarks.js'), scriptRules: ["*"] },
46 | { script: text_from_rawfile('cathrome/browsingData.js'), scriptRules: ["*"] },
47 | { script: text_from_rawfile('cathrome/commands.js'), scriptRules: ["*"] },
48 | { script: text_from_rawfile('cathrome/contentSettings.js'), scriptRules: ["*"] },
49 | { script: text_from_rawfile('cathrome/contextMenus.js'), scriptRules: ["*"] },
50 | { script: text_from_rawfile('cathrome/declarativeNetRequest.js'), scriptRules: ["*"] },
51 | { script: text_from_rawfile('cathrome/i18n.js'), scriptRules: ["*"] },
52 | { script: text_from_rawfile('cathrome/cathrome.js'), scriptRules: ["*"] },
53 | ])
54 | .onConsole((e) => {
55 | let info = `[Extension][${ext.name}][${ext.id}]: ` + e.message.getMessage();
56 | if (e.message.getMessageLevel() == MessageLevel.Error) {
57 | console.error(info);
58 | } else if (e.message.getMessageLevel() == MessageLevel.Warn) {
59 | console.warn(info);
60 | } else {
61 | console.log(info);
62 | }
63 | return true;
64 | })
65 | }
66 |
67 | export class ExtNodeController extends NodeController {
68 | private builderNode: BuilderNode<[extension]> | null | undefined = null;
69 | private rootNode: FrameNode | null = null;
70 |
71 | constructor(builderNode: BuilderNode<[extension]> | undefined) {
72 | super();
73 | this.builderNode = builderNode;
74 | }
75 |
76 | makeNode(uiContext: UIContext): FrameNode | null {
77 | return this.rootNode;
78 | }
79 |
80 | attachWeb(): void {
81 | if (this.builderNode) {
82 | let frameNode: FrameNode | null = this.builderNode.getFrameNode();
83 | if (frameNode?.getParent() != null) {
84 | hilog.error(0x0000, 'testTag', '%{public}s', 'The frameNode is already attached');
85 | return;
86 | }
87 | this.rootNode = this.builderNode.getFrameNode();
88 | }
89 | }
90 |
91 | detachWeb(): void {
92 | this.rootNode = null;
93 | }
94 |
95 | // Update?
96 | update(ext: extension) {
97 | if (this.builderNode) {
98 | try {
99 | this.builderNode.update(ext);
100 | console.log(`[WebNode][update] ext.storage.get('my_window_id')=${ext.storage?.get('my_window_id') as string}`);
101 | } catch (e) {
102 | console.error(`[WebNode][update] Failed: ${e}!`);
103 | }
104 | }
105 | }
106 |
107 | dispose() {
108 | if (this.builderNode) {
109 | this.builderNode.dispose();
110 | console.log(`[WebNode][dispose] OK!`);
111 | } else {
112 | console.warn(`[WebNode][dispose] Failed!`);
113 | }
114 | }
115 | }
116 |
117 | export const createEWeb = (uiContext: UIContext, tab: extension) => {
118 | let builderNode: BuilderNode<[extension]> = new BuilderNode(uiContext);
119 | builderNode.build(wrapBuilder<[extension]>(WebBuilder), tab);
120 | return builderNode;
121 | }
--------------------------------------------------------------------------------
/home/src/main/ets/hosts/bunch_of_extensions.ets:
--------------------------------------------------------------------------------
1 | import { locale_messages_of_extension, manifest_of_extension, message_of_manifest_key } from '../processes/extension_actions';
2 | import { webview } from '@kit.ArkWeb';
3 | import { CatsBridge } from '../objects/CatstensionBridgeObj';
4 | import { ExtNodeController } from '../objects/ExtWebNode';
5 |
6 | export class bunch_of_extensions {
7 | extensions: extension[] = [];
8 | last_update: number = 0;
9 | NodeControllers: Array = [];
10 |
11 | update_timestamp() {
12 | this.last_update = Date.now();
13 | }
14 |
15 | /**
16 | * Returns the extension object of a specific ID.
17 | * @param id The id.
18 | * @returns The extension object if found, undefined otherwise.
19 | * */
20 | get_extension(id: string) {
21 | for (let index = 0; index < this.extensions.length; index++) {
22 | if (this.extensions[index].id == id) {
23 | return this.extensions[index];
24 | }
25 | }
26 | return undefined;
27 | }
28 |
29 | index_of_id(id: string) {
30 | for (let index = 0; index < this.extensions.length; index++) {
31 | if (this.extensions[index].id == id) {
32 | return index;
33 | }
34 | }
35 | return undefined;
36 | }
37 | }
38 |
39 | export class extension {
40 | id: string;
41 | path: string;
42 | path_full: string;
43 | // Web
44 | controller: WebviewController = new webview.WebviewController();
45 | bridge: CatsBridge;
46 | storage: LocalStorage | undefined;
47 | is_being_viewed: boolean = false;
48 | // Properties
49 | name: string;
50 | manifest_version: number;
51 | version: string;
52 | description: string | undefined;
53 | default_locale: string | undefined;
54 | homepage_url: string | undefined;
55 | // icon
56 | icon_path: string | undefined;
57 | // action
58 | action_default_icon_path: string | undefined;
59 | action_default_title: string | undefined;
60 | action_default_popup: string | undefined;
61 | // Objects
62 | manifest: object | undefined;
63 | messages: object | undefined;
64 |
65 | constructor(id: string, path: string, path_full: string) {
66 | this.id = id;
67 | this.path = path;
68 | this.path_full = path_full;
69 | this.manifest = manifest_of_extension(path);
70 | this.manifest_version = this.manifest?.['manifest_version'];
71 | this.name = this.manifest?.['name'];
72 | this.version = this.manifest?.['version'];
73 | this.description = this.manifest?.['description'];
74 |
75 | // icon
76 | let icons: object | undefined = this.manifest?.['icons'];
77 | this.icon_path = getMaxKeyPath(icons);
78 |
79 | // action
80 | let action_json_obj: object | undefined = this.manifest?.['action'];
81 | if (action_json_obj) {
82 | // icon
83 | let default_icon: object | undefined = action_json_obj?.['default_icon'];
84 | if (default_icon) {
85 | let best_path = getMaxKeyPath(default_icon);
86 | this.action_default_icon_path = best_path;
87 | }
88 | // default_title
89 | this.action_default_title = action_json_obj?.['default_title'];
90 | this.action_default_popup = action_json_obj?.['default_popup'];
91 | }
92 |
93 | // homepage_url
94 | this.homepage_url = this.manifest?.['homepage_url'];
95 |
96 | // i18n
97 | this.default_locale = this.manifest?.['default_locale'];
98 | if (this.default_locale) {
99 | // Do i18n
100 | this.messages = locale_messages_of_extension(path, this.default_locale);
101 | if (this.messages) {
102 | // Update manifest information
103 | this.name = message_of_manifest_key(this.messages, this.name);
104 | if (this.description) {
105 | this.description = message_of_manifest_key(this.messages, this.description);
106 | }
107 | if (this.action_default_title) {
108 | this.action_default_title = message_of_manifest_key(this.messages, this.action_default_title);
109 | }
110 | }
111 | }
112 |
113 | this.bridge = new CatsBridge(this);
114 |
115 | console.log(`[extension] Created! path=${path}, path_full=${path_full}`);
116 | }
117 | }
118 |
119 | /**
120 | * Finds the icon with largest size.
121 | * @param obj The default_icon object.
122 | * @returns A string, path of that icon.
123 | * */
124 | function getMaxKeyPath(obj: object | undefined): string | undefined {
125 | if (!obj) {
126 | return;
127 | }
128 |
129 | if (typeof obj === 'string') {
130 | return obj as string;
131 | }
132 |
133 | const keys = Object.keys(obj);
134 | if (keys.length === 0) {
135 | return undefined;
136 | }
137 | console.log(`[getMaxKeyPath] keys=${keys},`)
138 |
139 | let maxKey = -Infinity;
140 | for (let i = 0; i < keys.length; i++) {
141 | const keyNum = parseInt(keys[i], 10);
142 | if (keyNum > maxKey) {
143 | maxKey = keyNum;
144 | }
145 | }
146 |
147 | return obj?.[maxKey.toString()];
148 | }
--------------------------------------------------------------------------------
/home/src/main/ets/utils/drag_drop_tools.ets:
--------------------------------------------------------------------------------
1 | import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData';
2 | import lazy { copy_from_uri_to_sandbox_temp_with_timestamp } from './storage_tools';
3 | import lazy { storage_of_index, window_index_of_id } from './ui_tools';
4 |
5 | /**
6 | * Pass a DragEvent and some of the contents of correct type would be added to meowScratchingBoard
7 | * @param e DragEvent
8 | * */
9 | export function drop_to_scratching_board(e: DragEvent, storage: LocalStorage, openLinks: boolean) {
10 | let drop_ok = false;
11 | try {
12 | let drop_data_records = e.getData().getRecords();
13 | let result_list_string: string[] = [];
14 | let last_link: string = ''; // For comparision and avoid opening duplicate links!~
15 | for (let i = 0; i < drop_data_records.length; i++) {
16 | let record = drop_data_records[i];
17 | console.log('[Meow][drop_to_scratching_board][record.getType()][' + i + '] ' + record.getType());
18 |
19 | if (record.getType() == uniformTypeDescriptor.UniformDataType.HYPERLINK) {
20 | let desc = (record as unifiedDataChannel.Hyperlink).description;
21 | let url = (record as unifiedDataChannel.Hyperlink).url;
22 |
23 | if (openLinks) {
24 | // Directly open
25 | if (url != last_link) {
26 | storage.set('universal_new_tab_gateway', [url, false]);
27 | console.log("[drop_to_scratching_board] Hyperlink url: [" + url + "] Opened!");
28 | } else {
29 | console.log("[drop_to_scratching_board] Hyperlink url: [" + url + "] Duplication Avoided!");
30 | }
31 | last_link = url;
32 | } else {
33 | // Push Hyperlink
34 | if (desc && !result_list_string.includes(desc)) {
35 | // Push link description
36 | result_list_string.push(desc);
37 | drop_ok = true;
38 | }
39 | if (!result_list_string.includes(url)) {
40 | // Push link
41 | result_list_string.push(url);
42 | drop_ok = true;
43 | }
44 | console.log("[drop_to_scratching_board] Hyperlink url: [" + url + "]");
45 | }
46 | } else if (record.getType() == uniformTypeDescriptor.UniformDataType.PLAIN_TEXT) {
47 | // Push text
48 | let textContent = (record as unifiedDataChannel.PlainText).textContent;
49 | if (!result_list_string.includes(textContent)) {
50 | result_list_string.push(textContent);
51 | drop_ok = true;
52 | }
53 | console.log("[drop_to_scratching_board] Plain Text: [" + textContent + "]");
54 |
55 | } else if (record.getType() == uniformTypeDescriptor.UniformDataType.IMAGE) {
56 | // Push Image
57 | let image_uri = (record as unifiedDataChannel.Image).imageUri;
58 | console.log("[drop_to_scratching_board] image uri: [" + image_uri + "]");
59 | try {
60 | result_list_string.push(copy_from_uri_to_sandbox_temp_with_timestamp(image_uri, 'web-drag-image-cache'));
61 | drop_ok = true;
62 | } catch (e) {
63 | console.error('[drop_to_scratching_board] Plan A of Image drop failed: ' + e);
64 | }
65 |
66 | } else if (record.getType() == 'ApplicationDefinedType') {
67 | let re = record as unifiedDataChannel.ApplicationDefinedRecord;
68 | if (re.applicationDefinedType == 'ApplicationDefinedTabDrag') {
69 | try {
70 | let data = Array.from(re.rawData);
71 | let from_window = data[0];
72 | let from_tab = data[1];
73 | let from_storage = storage_of_index(data[0]);
74 | let to_window = window_index_of_id(storage.get('my_window_id') as string);
75 | console.log('[drop_to_scratching_board] Got a tab! window_index = ' + from_window + ', tab_index = ' + from_tab + ', to_window_index = ' + to_window + '.');
76 | // Move tab, notify from window.
77 | if (from_window == to_window) {
78 | // Moving a tab in back into its home window. This is meaningless so DO NOTHING.
79 | } else {
80 | from_storage.set('universal_move_tab_gateway_target', to_window);
81 | from_storage.set('universal_move_tab_gateway', data[1]);
82 | }
83 | } catch (e) {
84 | console.error('[drop_to_scratching_board] Tab drop failed: ' + e);
85 | }
86 | }
87 | } else {
88 | console.log(`[drop_to_scratching_board] Unrecognized data type!`);
89 | }
90 | }
91 | // Append to start of original scratching board content
92 | let original_drop_result_strings = storage.get('drop_result_strings') as string[];
93 | storage.set('drop_result_strings', result_list_string.concat(original_drop_result_strings));
94 | } catch (e) {
95 | console.error('[drop_to_scratching_board] Failed: ' + e);
96 | }
97 | e.setResult(DragResult.DRAG_SUCCESSFUL);
98 | return drop_ok;
99 | }
100 |
--------------------------------------------------------------------------------
/home/src/main/ets/dialogs/contents/woofRecentFaultLogs.ets:
--------------------------------------------------------------------------------
1 | import { FaultLogger } from '@kit.PerformanceAnalysisKit';
2 | // TODO: switch to https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hiappevent-watcher-crash-events-arkts
3 | import { BusinessError } from '@kit.BasicServicesKit';
4 | import lazy woofControlFrame from '../woofControlFrame';
5 | import { copy } from '../../utils/clipboard_tools';
6 | import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
7 | import linysTextTitle from '../../components/texts/linysTextTitle';
8 | import { animation_default } from '../../hosts/bunch_of_defaults';
9 | import linysTextArea from '../../components/texts/linysTextArea';
10 |
11 | @CustomDialog
12 | struct woofRecentFaultLogs {
13 | controller: CustomDialogController;
14 | @State faultLogs: string[] = [];
15 | @LocalStorageLink('opened_dialog_controllers') opened_dialog_controllers: CustomDialogController[] = [];
16 |
17 | aboutToAppear(): void {
18 | this.opened_dialog_controllers.push(this.controller);
19 | console.log(`[Dialog][Open] Now dialog count: ${this.opened_dialog_controllers.length}!`);
20 | }
21 |
22 | aboutToDisappear(): void {
23 | this.opened_dialog_controllers.splice(this.opened_dialog_controllers.indexOf(this.controller), 1);
24 | console.log(`[Dialog][Close] Now dialog count: ${this.opened_dialog_controllers.length}!`);
25 | }
26 |
27 | build() {
28 | woofControlFrame({
29 | title: $r('app.string.Fault_logs'),
30 | controller: this.controller,
31 | space_column: 5
32 | }) {
33 | Scroll() {
34 | Column({ space: 10 }) {
35 | if (this.faultLogs.length == 0) {
36 | linysTextTitle({
37 | text: $r('app.string.Fault_logs_empty')
38 | })
39 | linysTextTitle({
40 | text: ' ¯\\_(ツ)_/¯ '
41 | })
42 | .opacity(0.7)
43 | } else {
44 | ForEach(this.faultLogs, (fault: string, index: number) => {
45 | faultItem({
46 | fault: fault,
47 | idx: index
48 | })
49 | })
50 | }
51 | }
52 | .alignItems(HorizontalAlign.Center)
53 | }
54 | .borderRadius(13.5)
55 | .layoutWeight(1)
56 | .scrollable(ScrollDirection.Vertical)
57 | .edgeEffect(EdgeEffect.Spring)
58 | .onAppear(() => {
59 | this.get_fault_logs();
60 | })
61 | }
62 | }
63 |
64 | get_fault_logs() {
65 | this.faultLogs = [];
66 | try {
67 | FaultLogger.query(FaultLogger.FaultType.JS_CRASH).then(value => {
68 | if (value) {
69 | console.info("FaultLog length is " + value.length);
70 | let len: number = value.length;
71 | for (let i = 0; i < len; i++) {
72 | let this_fault: string[] = [];
73 | // this_fault.push("log: " + i)
74 | this_fault.push("Log pid: " + value[i].pid)
75 | this_fault.push("Log uid: " + value[i].uid)
76 | this_fault.push("Log type: " + value[i].type)
77 | this_fault.push("Log timestamp: " + value[i].timestamp +
78 | " (" + new Date(value[i].timestamp).toLocaleString() + ")")
79 | this_fault.push("Log reason: " + value[i].reason)
80 | this_fault.push("Log module: " + value[i].module)
81 | // this_fault.push("Log summary: " + value[i].summary)
82 | this_fault.push("Log text: " + value[i].fullLog)
83 | this.faultLogs.push(this_fault.join("\n"));
84 | }
85 | }
86 | });
87 | } catch (err) {
88 | console.error(`code: ${(err as BusinessError).code}, message: ${(err as BusinessError).message}`);
89 | }
90 | }
91 | }
92 |
93 | export default woofRecentFaultLogs;
94 |
95 | @Component
96 | struct faultItem {
97 | @Prop fault: string = 'meow meow meow wait a second meow!';
98 | @Prop idx: number;
99 | // Colors
100 | @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
101 | // Edit
102 | @State copied: boolean = false;
103 | // Settings / Accessibility
104 | @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
105 |
106 | build() {
107 | Column({ space: 10 }) {
108 | Row({ space: 10 }) {
109 | Row() {
110 | linysTextTitle({ text: (this.idx + 1).toString() + "." })
111 | }
112 | .layoutWeight(this.preferred_hand_left_or_right == 'right' ? 1 : undefined)
113 | .animation(animation_default())
114 |
115 | linysCapsuleButton({
116 | text: this.copied ? " " : " "
117 | })
118 | .onClick(() => {
119 | this.copy_this();
120 | })
121 | }
122 | .alignItems(VerticalAlign.Bottom)
123 | .width('100%')
124 |
125 | linysTextArea({ text: this.fault, init_text: this.fault })
126 | }
127 | }
128 |
129 | copy_this() {
130 | this.copied = true;
131 | copy(this.fault);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/home/src/main/ets/utils/color_tools.ets:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts a hsv color into rgb style.
3 | * @param h A number, the hue.
4 | * @param s A number, the saturation.
5 | * @param v A number, the value (of brightness).
6 | * @returns A number[] array, [r, g, b].
7 | * */
8 | export function hsv2rgb(h: number, s: number, v: number): number[] {
9 | h = Math.max(0, Math.min(360, h));
10 | s = Math.max(0, Math.min(100, s)) / 100;
11 | v = Math.max(0, Math.min(100, v)) / 100;
12 |
13 | const i = Math.floor(h / 60);
14 | const f = (h / 60) - i;
15 | const p = v * (1 - s);
16 | const q = v * (1 - f * s);
17 | const t = v * (1 - (1 - f) * s);
18 |
19 | let r: number = 0, g: number = 0, b: number = 0;
20 |
21 | switch (i % 6) {
22 | case 0:
23 | r = v;
24 | g = t;
25 | b = p;
26 | break;
27 | case 1:
28 | r = q;
29 | g = v;
30 | b = p;
31 | break;
32 | case 2:
33 | r = p;
34 | g = v;
35 | b = t;
36 | break;
37 | case 3:
38 | r = p;
39 | g = q;
40 | b = v;
41 | break;
42 | case 4:
43 | r = t;
44 | g = p;
45 | b = v;
46 | break;
47 | case 5:
48 | r = v;
49 | g = p;
50 | b = q;
51 | break;
52 | }
53 |
54 | r = Math.round(r * 255);
55 | g = Math.round(g * 255);
56 | b = Math.round(b * 255);
57 |
58 | return [r, g, b];
59 | }
60 |
61 | /**
62 | * Converts an rgb style color into hex format.
63 | * @param r A number, standing for the red value of the original rgb color.
64 | * @param g A number, standing for the green value of the original rgb color.
65 | * @param b A number, standing for the blue value of the original rgb color.
66 | * @returns A string, the hex color. Like #rrggbb.
67 | * */
68 | export function rgb2hex(r: number, g: number, b: number) {
69 | let hex = ("#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)).toUpperCase();
70 | return hex;
71 | }
72 |
73 | /**
74 | * Converts a hsv number style color into hex format.
75 | * @param hsv A number[] array, [hue, saturation, value] standing for the hsv color in number style.
76 | * @returns A string, the hex color. Like #rrggbb.
77 | * */
78 | export function hsv2hex(hsv: number[]) {
79 | let rgb: number[] = hsv2rgb(hsv[0], hsv[1], hsv[2]);
80 | let hex: string = rgb2hex(rgb[0], rgb[1], rgb[2]);
81 | return hex;
82 | }
83 |
84 | /**
85 | * Converts a hex string color to rgb format.
86 | * @param hex A string, in the format of #______.
87 | * @returns [r, g, b] if success.
88 | * */
89 | export function hex2rgb(hex: ResourceColor) {
90 | hex = (hex as string).toLowerCase();
91 | let rgb: number[] = [];
92 | for (let i = 1; i < 7; i += 2) {
93 | rgb.push(parseInt('0x' + hex.slice(i, i + 2)));
94 | }
95 | return rgb;
96 | }
97 |
98 | /**
99 | * Converts an rgb format color to hsv values.
100 | * @param r A number, standing for the red value of the original rgb color.
101 | * @param g A number, standing for the green value of the original rgb color.
102 | * @param b A number, standing for the blue value of the original rgb color.
103 | * @returns A number[] array, [h, s, v].
104 | * */
105 | export function rgb2hsv(r: number, g: number, b: number) {
106 | r = Math.max(0, Math.min(255, r));
107 | g = Math.max(0, Math.min(255, g));
108 | b = Math.max(0, Math.min(255, b));
109 |
110 | const rPrime = r / 255;
111 | const gPrime = g / 255;
112 | const bPrime = b / 255;
113 |
114 | const max = Math.max(rPrime, gPrime, bPrime);
115 | const min = Math.min(rPrime, gPrime, bPrime);
116 |
117 | const v = max;
118 |
119 | let s: number;
120 | if (max !== 0) {
121 | s = (max - min) / max;
122 | } else {
123 | s = 0;
124 | }
125 |
126 | let h: number;
127 | const delta = max - min;
128 | if (delta !== 0) {
129 | if (rPrime === max) {
130 | h = (gPrime - bPrime) / delta;
131 | } else if (gPrime === max) {
132 | h = 2 + (bPrime - rPrime) / delta;
133 | } else {
134 | // bPrime === max
135 | h = 4 + (rPrime - gPrime) / delta;
136 | }
137 |
138 | h = Math.round(h * 60);
139 |
140 | if (h < 0) {
141 | h += 360;
142 | }
143 | } else {
144 | h = 0;
145 | }
146 |
147 | return [h, Math.round(s * 100), Math.round(v * 100)];
148 | }
149 |
150 | /**
151 | * Converts a hex string color to hsv values.
152 | * @param hex A string, the hex color.
153 | * @returns A number[] array, [h, s, v].
154 | * */
155 | export function hex2hsv(hex: ResourceColor) {
156 | let rgb: number[] = hex2rgb(hex as string);
157 | let hsv = rgb2hsv(rgb[0], rgb[1], rgb[2]);
158 | return hsv;
159 | }
160 |
161 | /**
162 | * Examines a string and check if it is a legal #______ hex color.
163 | * @param hex A string.
164 | * */
165 | export function is_legal_hex(hex: ResourceColor) {
166 | let digits: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
167 | hex = (hex as string).toUpperCase();
168 | if (hex[0] != "#") {
169 | return false;
170 | }
171 | if (hex.length != 7) {
172 | return false;
173 | }
174 | for (let index = 1; index < hex.length; index++) {
175 | if (!digits.includes(hex[index])) {
176 | return false;
177 | }
178 | }
179 | return true;
180 | }
181 |
182 | /**
183 | * Adds transparency (0-255) to a ResourceColor
184 | * */
185 | export function add_transparency(color: ResourceColor, transparency: number) {
186 | let transparency_hex = ((255 - transparency) & 0xff).toString(16).padStart(2, '0');
187 | return '#' + transparency_hex + (color as string).substring(1);
188 | }
189 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Liny 的冲浪喵!
2 |
3 | [中文](README.md) | [English](README_EN.md)
4 |
5 | ## 一些前话
6 |
7 | 这个项目的诞生起初的目的是为 Liny 自己升级 HarmonyOS NEXT 之后创造一个容身之处,
8 | 但是发现似乎大家都需要这么一个玩意,于是就开源出来给大家用了。
9 |
10 | 但是由于 Liny 自己事务安排的缘故,查看 Issue、更新可能都不太及时,
11 | 很多事情有时候都比较滞后,还请各位见谅。
12 |
13 | 如果你见到上一个 Commit 是在好几个星期、甚至几个月前,
14 | 也请不要轻易地认为这个项目死掉了,
15 | 因为 Liny 可能正好在这段时间被迫沉迷写论文(bushi)而无暇关注这边的进度。
16 | 并且现在 DevEco Studio 并没有推出原生 HarmonyOS 版本,
17 | 由于卡到飞起的 Windows 模拟器 x86 转译,这个项目的更新可能更加缓慢了(:O)
18 |
19 | 然后,解决拖更问题的最好办法其实是给这个项目 **交一个 Issue**,
20 | 这样 Liny 就会感到十分焦虑,就会决定先来更新了。
21 |
22 | 让我们一起说:谢谢 Issue!
23 |
24 | :3
25 |
26 | ## 快速上手
27 |
28 | 在 [build_auto](build_auto) 目录下有最新测试版本的 build 产品,
29 | 这些产品可能存在某些未发现的不稳定因素,不建议长期使用
30 | ——用了的话,记得常来检查更新!
31 |
32 | ~~尽管很多时候开发版比稳定版稳定……~~
33 |
34 | 在界面下方的输入框里输入 URL 或关键词,并且回车或点击小放大镜以提交。
35 |
36 | 点击四个小圆点(2x2)组成的按钮以查看页面详情和更多功能,再点一次以关闭面板。
37 |
38 | ## 启发
39 |
40 | Liny 的浏览器为 HarmonyOS NEXT 而构建,旨在为各种性能水平的设备提供一个浏览器的轻量之选。
41 |
42 | 这个项目受 tuyafeng 大佬传奇之作 [Via](https://viayoo.com/)、
43 | 来自 Mozilla 的最可爱赛博小狐狸 [Firefox](https://firefox.com/) 之启发,
44 | 以及来自开源鸿蒙的 [浏览器](https://gitee.com/openharmony/applications_app_samples/tree/master/code/BasicFeature/Web/Browser)
45 | 及其定制增强版分支 [🌐 浏览器CE(社区版)](https://gitee.com/westinyang/browser-ce)
46 | 开放代码的帮助(解惑),最终得以呈现。感谢你们!!(* ̄3 ̄)╭
47 |
48 | ## 预览
49 |
50 | 手机:
51 |
52 | | 首页(浅) | 设置(浅) |
53 | |:------------------------------------------:|:--------------------------------------------:|
54 | |  |  |
55 |
56 | | 首页(深) | 设置(深) |
57 | |:------------------------------------------:|:------------------------------------------:|
58 | |  |  |
59 |
60 | 折叠屏:
61 |
62 | | 首页(浅) | 设置(浅) |
63 | |:------------------------------------------:|:------------------------------------------:|
64 | |  |  |
65 |
66 | | 首页(深) | 设置(深) |
67 | |:------------------------------------------:|:------------------------------------------:|
68 | |  |  |
69 |
70 | 平板:
71 |
72 | | 首页(浅) | 设置(浅) |
73 | |:--------------------------------------------:|:--------------------------------------------:|
74 | |  |  |
75 |
76 | | 首页(深) | 设置(深) |
77 | |:------------------------------------------:|:------------------------------------------:|
78 | |  |  |
79 |
80 | 注意!颜色和背景需要自己设定!:P
81 |
82 | ## 相关权限
83 |
84 | [ohos.permission.INTERNET](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/permission-list.md#ohospermissioninternet)
85 |
86 | ## 计划和开发
87 |
88 | 可能需要很多时间,也可能有些东西会永远被搁置。(ง •_•)ง
89 |
90 | ### 功能
91 |
92 | - [x] 多窗口/多实例模式
93 | - [x] 在窗口间移动标签页
94 | - [x] Cookies 管理与清理
95 | - [x] 缓存管理与清理
96 | - [x] 广告过滤+EasyList
97 | - [x] 分享/复制网址
98 | - [ ] 分布式能力同步数据(历史记录、书签)
99 | - [ ] 历史记录要求生物识别访问
100 | - [ ] 书签页面要求生物识别访问
101 | - [x] 打开 HTML 文件
102 | - [x] 历史记录搜索
103 | - [x] 自定义首页背景
104 | - [x] 自定义首页网址快捷方式
105 | - [x] 下载选项自定义:每次弹窗询问
106 | - [x] 直接下载到系统下载目录
107 | - [x] 页面内搜索
108 | - [x] 禁用 JavaScript
109 | - [x] 无图模式
110 | - [ ] 隐私模式
111 | - [ ] 网页链接右键/长按菜单(预览、操作)
112 | - [ ] 阅读模式
113 | - [ ] 搜索建议
114 | - [ ] 插件/脚本
115 | - [ ] DRM 内容支持
116 | - [x] 响应网页的新标签页打开要求
117 | - [x] 网页内下载功能
118 | - [x] UA 切换与自定义(UA 相关设置)
119 | - [x] 启动时恢复上次浏览的进度
120 | - [x] 多任务平行浏览
121 |
122 | ### 体验
123 |
124 | - [x] 自定义主题(配色)
125 | - [x] 标题栏自定义上下
126 | - [x] 标签页栏自定义横竖
127 | - [x] 接入系统返回
128 | - [x] 客制化动画曲线
129 | - [ ] 更高级的鸿蒙风格视觉效果
130 |
131 | ## 约束和限制
132 |
133 | ### 道阻且长。
134 |
135 | 1. 由于 Liny 不成体系的编程思想和低陋的代码水平,这个应用可能存在一些性能问题。
136 | 如果出现错误,还请各路前辈高手斧正,感激不尽!
137 | 2. 对于某些工作区 / 恢复的标签页,有时会发生 cppcrash。(调查中)
138 | 3. Cloudflare 人机验证可能无法通过。(使用 Android UA 就可以了!)
139 | 4. 由于神秘的问题和暂时却不得不的妥协,
140 | 有时候启动应用后广告屏蔽白名单可能生效不及时,导致网页上的广告还是被屏蔽了。
141 | (正在想办法解决)
142 | 5. 由于神秘的问题和暂时却不得不的妥协,
143 | 有时候启动应用后广告屏蔽启用可能生效不及时,导致网页上的广告没有被屏蔽。
144 | (正在想办法解决)
145 | 6. HarmonyOS UA 不被很多网站所理解,并且常常会导致不正确的网站呈现(但是可以自定义 UA 了)。
146 | 7. 这个项目要求 HarmonyOS NEXT API20 以运行。
147 | 8. OHOS 没有 Share Kit 怎么办(?)在线等,急。
148 |
149 | ### 似乎解决了?
150 |
151 | 1. 也许是因为 Windows x86-64 模拟器的性能问题,快速打开大量新标签页会导致错误 17100001
152 | (Init error. The WebviewController must be associated with a Web component)
153 | 并且使应用崩溃。
154 | (似乎解决了)
155 | 2. 快速关闭很多标签页似乎会导致所有的标签页按钮消失。
156 | 这是因为在某些未知的特定条件下,标签页按钮会尝试关闭 最后+1 个标签页面。
157 | 一个判断语句已经被添加,用于检测请求关掉的页面是否合法。
158 | (似乎解决了)
159 | 3. 神秘的问题导致长期不用之后启动会闪退 →
160 | Issue [#40](https://github.com/awaLiny2333/LinysBrowser_NEXT/issues/40)
161 | (似乎解决了)。
162 | 4. 神秘的问题导致快捷键修改之后没有生效?
163 | (似乎解决了,但是需要重启才能把原来的解绑掉)
164 | 5. 神秘问题导致应用启动后第一次打开设置页面会发现某些分段按钮不显示已选择的项。
165 | (似乎消失了)
166 | 6. 在某些情况下打开上次关闭时的标签页功能会失败,于是什么标签页都没有显示出来。
167 | (似乎解决了)
168 |
169 | ## 提交信息的说明
170 |
171 | 这个项目诞生以来的提交信息过于混乱,于是 Liny 决定作出一些自我约束……
172 |
173 | 1. fix:修复更新,用于标注对一个问题的修复;
174 | 2. feat:功能更新,一般用于标注推出了什么新功能;
175 | 3. improve:改进更新,一般用于标注已有的某些东西的变化,如逻辑、界面、文案等;
176 | 4. code:代码更新,用于标注代码优化,如代码整理、逻辑整理等;
177 | 5. version:版本号变更,用于标注版本号的变化;
178 | 6. api:API 级别变更,用于标注需要的 API 级别的变化;
179 | 7. other: 未归类的其它提交。
--------------------------------------------------------------------------------
/home/src/main/ets/workers/History_indexer.ets:
--------------------------------------------------------------------------------
1 | import { collections, ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
2 | import { sandbox_save } from '../utils/storage_tools';
3 | import { fileIo as fs } from '@kit.CoreFileKit';
4 | import { bunch_of_history_index_lite as host } from '../hosts/bunch_of_history_index_lite';
5 |
6 | const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
7 |
8 | /**
9 | * Defines the event handler to be called when the worker thread receives a message sent by the host thread.
10 | * The event handler is executed in the worker thread.
11 | *
12 | * @param event message data
13 | */
14 | workerPort.onmessage = (event: MessageEvents) => {
15 | // receive getContext().filesDir from main thread
16 | // history_index_host = event.data;
17 | if (typeof event.data == "string") {
18 | let filesDir = event.data as string;
19 | // Execution
20 | rebuild_full_history_index_sync(filesDir);
21 | // Report done
22 | workerPort.postMessage('done');
23 | }
24 | };
25 |
26 | /**
27 | * Defines the event handler to be called when the worker receives a message that cannot be deserialized.
28 | * The event handler is executed in the worker thread.
29 | *
30 | * @param event message data
31 | */
32 | workerPort.onmessageerror = (event: MessageEvents) => {
33 | };
34 |
35 | /**
36 | * Defines the event handler to be called when an exception occurs during worker execution.
37 | * The event handler is executed in the worker thread.
38 | *
39 | * @param event error message
40 | */
41 | workerPort.onerror = (event: ErrorEvent) => {
42 | };
43 |
44 |
45 | /**
46 | * Builds index for all history and saves (overwrites) the index file onto disk.
47 | *
48 | * THIS IS A SYNC FUNCTION
49 | * */
50 | function rebuild_full_history_index_sync(context_filesDir: string) {
51 | let s = Date.now();
52 | console.log(host.log_head_worker() + ' Start rebuilding full index! Start: ' + new Date(s).toString() + '.')
53 |
54 | // Clear all previous index
55 | try {
56 | fs.rmdirSync(context_filesDir + "/history-index");
57 | fs.mkdirSync(context_filesDir + "/history-index", true);
58 | } catch (e) {
59 |
60 | }
61 |
62 | // Get all months
63 | let months = get_history_months_no_create_this_month(context_filesDir);
64 |
65 | // Read month by month
66 | for (let index = 0; index < months.length; index++) {
67 | host.clear();
68 |
69 | // Index
70 | const year_month = months[index];
71 | // bunch_of_history_index.add_index_history_file(file_path, context_filesDir);
72 | host.index_from_history_month_file(year_month[0], year_month[1], context_filesDir);
73 |
74 | // Save to disk
75 | console.log(host.log_head_worker() + ' Indexing month: ' + year_month.toString() + '. Map size: ' + host.index_map.size.toString() + '.');
76 |
77 | let month_field: string = year_month[1].toString();
78 | if (year_month[1].toString().length == 1) {
79 | // is a single digit
80 | // This works until human have over 99 months a year, perhaps.
81 | month_field = "0" + year_month[1].toString();
82 | }
83 | save_index_ext("index_" + year_month[0] + "_" + month_field + "_00_00_00_00_000.txt", host.index_map, context_filesDir);
84 |
85 | // Report status
86 | const progress = index / months.length * 100;
87 | workerPort.postMessage((Date.now() - s).toString() + " ms, " + progress.toFixed(2) + "%");
88 | // console.log("[Meow][bunch_of_history_index] Index progress: " + progress.toFixed(2) + "%");
89 | }
90 |
91 | // save indexer version for future use
92 | // perhaps?
93 | sandbox_save('last_indexer_version.txt', "1", context_filesDir);
94 |
95 | // Timer & logs
96 | console.log(host.log_head_worker() + ' Finish full index! Time used: ' + ((Date.now() - s) / 1000).toString() + "s.")
97 | workerPort.postMessage((Date.now() - s).toString() + " ms, 100%");
98 | }
99 |
100 | // Functions borrowed from bunch_of_history
101 | // to avoid error when bunch_of_history is not even initialized
102 | // TODO: Find a more elegant way of doing this
103 |
104 | /**
105 | * Lists all the months in which ther are history records made, in ascending order.
106 | * @returns A number[][] array, e.g. [[2023, 2], [2023, 3], [2023, 4], ...].
107 | * */
108 | function get_history_months_no_create_this_month(context_filesDir: string) {
109 | let filesDir: string = context_filesDir;
110 | let result: number[][] = [];
111 | let history_files = fs.listFileSync(filesDir + '/history', { recursion: false });
112 | for (let index = 0; index < history_files.length; index++) {
113 | let f_name = history_files[index].split('.')[0];
114 | // 'history_2024_02.txt'
115 | let f_split = f_name.split('_');
116 | let year = parseInt(f_split[1]);
117 | let month = parseInt(f_split[2]);
118 | result.push([year, month]);
119 | }
120 | result = result.sort((a, b) => (a[0] * 12 + a[1]) - (b[0] * 12 + b[1]));
121 | console.log(host.log_head_worker() + ' Got_available_months. Result: ' + result.join(' ') + '.')
122 | return result;
123 | }
124 |
125 | /**
126 | * Saves index set to sandbox
127 | * @param index_name the file name of this index slice. For example, 'index_2023_02_01_23_55_11_020.txt'.
128 | * @param set
129 | * @param context_filesDir
130 | * */
131 | function save_index_ext(index_name: string, set: collections.Map>, context_filesDir: string) {
132 | let filesDir: string = context_filesDir;
133 | let result: string[] = [];
134 | set.forEach((value, key) => {
135 | result.push(key)
136 | result.push(value.join("_"));
137 | })
138 | // console.log("qwq " + result.toString())
139 | sandbox_save('history-index/' + index_name, result.join('\n'), filesDir);
140 | }
--------------------------------------------------------------------------------
/home/src/main/ets/utils/kv_store_tools.ets:
--------------------------------------------------------------------------------
1 | import { distributedKVStore } from '@kit.ArkData';
2 | import { BusinessError } from '@kit.BasicServicesKit';
3 | import { meowContext } from './environment_tools';
4 |
5 | /**
6 | * THEORETICALLY THIS FILE IS LEGACY.
7 | *
8 | * AS WE HAVE MOVED TOTALLY AWAY FROM KVSTORE SINCE SEP 30 2025.
9 | *
10 | * BUT MIGRATION IN bunch_of_settings.ets AND BOOKMARKS (init.ets) STILL NEEDS THIS, SO WE KEPT THIS FILE INTACT.
11 | * */
12 |
13 | let kvs: distributedKVStore.DeviceKVStore | undefined = undefined;
14 |
15 | export async function kv_store_get(key_name: string) {
16 | if (kvs == undefined) {
17 | await get_kv_store_into_app_storage();
18 | // console.log("[kv_store_tools][GET] kvStore not found from AppStorage! Created & Stored a new one.");
19 | } else {
20 | // console.log('[kv_store_tools][GET] Using an existing KVStore.');
21 | }
22 |
23 | if (kvs) {
24 | // If kvStore already got
25 | let get_result = "";
26 | await kvs.get(key_name).then((value) => {
27 | // console.log("[kv_store_tools] Succeeded in getting data, key: " + key_name + ".")
28 | get_result = value as string;
29 | }).catch(() => {
30 | get_result = "undefined"
31 | })
32 | return get_result;
33 | } else {
34 | let get_result = "error"
35 | console.error("[kv_store_tools][GET][ERROR] Undefined KVStore! ")
36 | return get_result;
37 | }
38 | }
39 |
40 | // export async function kv_store_put(key_name: string, put_content: string) {
41 | // if (kvs == undefined) {
42 | // await get_kv_store_into_app_storage();
43 | // // console.log("[kv_store_tools][GET] kvStore not found from AppStorage! Created & Stored a new one.");
44 | // } else {
45 | // // console.log('[kv_store_tools][GET] Using an existing KVStore.');
46 | // }
47 | //
48 | // if (kvs) {
49 | // kvs.put(key_name, put_content, (err) => {
50 | // if (err !== undefined) {
51 | // console.error(`[kv_store_tools][ERROR] Failed to put data. Code: ${err.code},message: ${err.message}`);
52 | // } else {
53 | // console.log("[kv_store_tools] Succeeded in putting data, key: " + key_name + ", content:\n\t" + put_content);
54 | // }
55 | // });
56 | // }
57 | //
58 | // }
59 | //
60 | // export async function kv_store_delete(key_name: string) {
61 | // if (kvs == undefined) {
62 | // console.log("[kv_store_tools][GET] Launch of app, now getting kvStore")
63 | // await get_kv_store_into_app_storage();
64 | // }
65 | // if (kvs) {
66 | // kvs.delete(key_name, (err) => {
67 | // if (err !== undefined) {
68 | // console.error(`[kv_store_tools][ERROR] Failed to delete data. Code: ${err.code},message: ${err.message}`);
69 | // } else {
70 | // console.info("[kv_store_tools] Succeeded in deleting data, key: " + key_name + ".");
71 | // }
72 | // });
73 | // }
74 | // }
75 |
76 | async function get_kv_store_into_app_storage() {
77 | console.log('[kv_store_tools] Getting a KVStore into AppStorage...')
78 | let t0 = Date.now();
79 |
80 | const kvManagerConfig: distributedKVStore.KVManagerConfig = {
81 | context: meowContext(),
82 | bundleName: 'meow.liny.browser.cat.uwu',
83 | }
84 |
85 | try {
86 | let kvManager = distributedKVStore.createKVManager(kvManagerConfig);
87 | console.info("[kv_store_tools] Succeeded in creating KVManager!");
88 | // Get kvManager
89 |
90 | const options: distributedKVStore.Options = {
91 | createIfMissing: true,
92 | encrypt: false,
93 | backup: false,
94 | autoSync: false,
95 | kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
96 | securityLevel: distributedKVStore.SecurityLevel.S2,
97 | };
98 |
99 | await kvManager.getKVStore('storeId', options)
100 | .then((store: distributedKVStore.DeviceKVStore) => {
101 | kvs = store;
102 | let t = Date.now() - t0;
103 | console.info("[kv_store_tools] Succeeded in getting KVStore! (" + t + ' ms)');
104 | // Got kvStore
105 | }).catch((err: BusinessError) => {
106 | console.error(`[kv_store_tools][ERROR] Failed to get KVStore. Code is ${err.code},message is ${err.message}.`);
107 | });
108 | // Get kvStore
109 |
110 | } catch (e) {
111 | let error = e as BusinessError;
112 | console.error(`[kv_store_tools][ERROR] Failed to create KVManager. Code is ${error.code},message is ${error.message}.`);
113 | }
114 | }
115 |
116 | // /**
117 | // * The only items stored in kv_store are the browsing statuses.
118 | // * @returns A json format string.
119 | // * */
120 | // export async function export_json_kv_store() {
121 | // let result_string: string[] = [];
122 | // result_string.push(' "continue_tabs_count": "' + await kv_store_get('continue_tabs_count') + '"');
123 | // result_string.push(' "continue_tabs_main_on": "' + await kv_store_get('continue_tabs_main_on') + '"');
124 | // result_string.push(' "continue_tabs_sub_on": "' + await kv_store_get('continue_tabs_sub_on') + '"');
125 | // result_string.sort();
126 | // return '{\n' + result_string.join(',\n') + '\n}';
127 | // }
128 | //
129 | // /**
130 | // * Imports settings from a json text.
131 | // * @param text The json text.
132 | // * @returns True if succeeded.
133 | // * */
134 | // export async function import_json_kv_store(text: string) {
135 | // let jsonObject: Record | null = null;
136 | // if (text.length < 2) {
137 | // return false;
138 | // }
139 | //
140 | // try {
141 | // jsonObject = JSON.parse(text);
142 | // } catch (e) {
143 | // console.log('[Meow][kv_store_tools] Import kv_store failed! Json parse failed!');
144 | // return false;
145 | // }
146 | //
147 | // Object.entries(jsonObject as Record).forEach(async (item) => {
148 | // const key = item[0];
149 | // const val = item[1];
150 | // kv_store_put(key, val);
151 | // })
152 | // console.log('[Meow][kv_store_tools] Import kv_store:\n' + text);
153 | // return true;
154 | // }
--------------------------------------------------------------------------------
/home/src/main/module.json5:
--------------------------------------------------------------------------------
1 | {
2 | "module": {
3 | "name": "home",
4 | "type": "entry",
5 | "srcEntry": "./ets/homeabilitystage/HomeAbilityStage.ets",
6 | "description": "$string:module_desc",
7 | "mainElement": "HomeAbility",
8 | "deviceTypes": [
9 | "default",
10 | "tablet",
11 | "2in1",
12 | // "wearable"
13 | ],
14 | "deliveryWithInstall": true,
15 | "installationFree": false,
16 | "pages": "$profile:main_pages",
17 | "abilities": [
18 | {
19 | "name": "HomeAbility",
20 | "srcEntry": "./ets/homeability/HomeAbility.ets",
21 | "description": "$string:HomeAbility_desc",
22 | "icon": "$media:layered_image",
23 | "launchType": "specified",
24 | "label": "$string:HomeAbility_label",
25 | "startWindowIcon": "$media:startIcon",
26 | "startWindowBackground": "$color:start_window_background",
27 | "exported": true,
28 | "removeMissionAfterTerminate": true,
29 | "orientation": "auto_rotation_restricted",
30 | "continuable": true,
31 | "preferMultiWindowOrientation": "landscape_auto",
32 | "skills": [
33 | {
34 | "entities": [
35 | "entity.system.home",
36 | ],
37 | "actions": [
38 | "action.system.home",
39 | ],
40 | "uris": [
41 | {
42 | "type": "general.hyperlink",
43 | }
44 | ]
45 | },
46 | {
47 | "entities": [
48 | "entity.system.browsable"
49 | ],
50 | "actions": [
51 | "ohos.want.action.viewData"
52 | ],
53 | "uris": [
54 | {
55 | "scheme": "file",
56 | // Accept All Files
57 | "linkFeature": "FileOpen"
58 | },
59 | {
60 | "scheme": "file",
61 | "type": "text/html",
62 | // Accept html
63 | "linkFeature": "FileOpen"
64 | },
65 | {
66 | "scheme": "file",
67 | "type": "application/xhtml+xml",
68 | // Accept xhtml
69 | "linkFeature": "FileOpen"
70 | },
71 | {
72 | "scheme": "file",
73 | "type": "message/rfc822",
74 | // Accept mht
75 | "linkFeature": "FileOpen"
76 | },
77 | {
78 | "scheme": "https"
79 | },
80 | {
81 | "scheme": "http"
82 | }
83 | ]
84 | }
85 | ],
86 | "minWindowHeight": 260,
87 | "minWindowWidth": 360
88 | }
89 | ],
90 | "extensionAbilities": [
91 | {
92 | "name": "HomeBackupAbility",
93 | "srcEntry": "./ets/homebackupability/HomeBackupAbility.ets",
94 | "type": "backup",
95 | "exported": false,
96 | "metadata": [
97 | {
98 | "name": "ohos.extension.backup",
99 | "resource": "$profile:backup_config"
100 | }
101 | ]
102 | }
103 | ],
104 | "requestPermissions": [
105 | {
106 | "name": "ohos.permission.INTERNET",
107 | "reason": "$string:permission_reason_INTERNET",
108 | "usedScene": {
109 | "when": "inuse",
110 | "abilities": [
111 | "HomeAbility"
112 | ]
113 | }
114 | },
115 | {
116 | "name": "ohos.permission.GET_NETWORK_INFO",
117 | "reason": "$string:permission_reason_INTERNET",
118 | "usedScene": {
119 | "when": "inuse",
120 | "abilities": [
121 | "HomeAbility"
122 | ]
123 | }
124 | },
125 | {
126 | "name": "ohos.permission.PRINT",
127 | "reason": "$string:permission_reason_PRINT",
128 | "usedScene": {
129 | "when": "inuse",
130 | "abilities": [
131 | "HomeAbility"
132 | ]
133 | }
134 | },
135 | {
136 | "name": "ohos.permission.MICROPHONE",
137 | "reason": "$string:permission_reason_MICROPHONE",
138 | "usedScene": {
139 | "when": "inuse",
140 | "abilities": [
141 | "HomeAbility"
142 | ]
143 | }
144 | },
145 | {
146 | "name": "ohos.permission.CAMERA",
147 | "reason": "$string:permission_reason_CAMERA",
148 | "usedScene": {
149 | "when": "inuse",
150 | "abilities": [
151 | "HomeAbility"
152 | ]
153 | }
154 | },
155 | {
156 | "name": "ohos.permission.WINDOW_TOPMOST",
157 | "reason": "$string:permission_reason_WINDOW_TOPMOST",
158 | "usedScene": {
159 | "when": "inuse",
160 | "abilities": [
161 | "HomeAbility"
162 | ]
163 | }
164 | },
165 | {
166 | "name": "ohos.permission.DETECT_GESTURE",
167 | "reason": "$string:permission_reason_DETECT_GESTURES",
168 | "usedScene": {
169 | "when": "inuse",
170 | "abilities": [
171 | "HomeAbility"
172 | ]
173 | }
174 | },
175 | {
176 | "name": "ohos.permission.ACCESS_FIDO2_ONLINEAUTH",
177 | "reason": "$string:permission_reason_ACCESS_FIDO2_ONLINEAUTH",
178 | "usedScene": {
179 | "when": "inuse",
180 | "abilities": [
181 | "HomeAbility"
182 | ]
183 | }
184 | },
185 | // {
186 | // "name" : "ohos.permission.READ_PASTEBOARD",
187 | // "reason": "$string:permission_reason_READ_PASTEPOARD",
188 | // "usedScene": {
189 | // "when": "inuse",
190 | // "abilities": [
191 | // "HomeAbility"
192 | // ]
193 | // }
194 | // }
195 | ]
196 | }
197 | }
--------------------------------------------------------------------------------