frida-ui-runtime 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +181 -0
  2. package/lib/android-listener.d.ts +20 -0
  3. package/lib/android-listener.js +92 -0
  4. package/lib/android-runtime.d.ts +25 -0
  5. package/lib/android-runtime.js +310 -0
  6. package/lib/api.d.ts +1 -0
  7. package/lib/api.js +156 -0
  8. package/lib/component/button.d.ts +11 -0
  9. package/lib/component/button.js +61 -0
  10. package/lib/component/category.d.ts +9 -0
  11. package/lib/component/category.js +57 -0
  12. package/lib/component/checkBox.d.ts +33 -0
  13. package/lib/component/checkBox.js +276 -0
  14. package/lib/component/collapsible.d.ts +19 -0
  15. package/lib/component/collapsible.js +197 -0
  16. package/lib/component/image.d.ts +18 -0
  17. package/lib/component/image.js +85 -0
  18. package/lib/component/infoCardBox.d.ts +13 -0
  19. package/lib/component/infoCardBox.js +68 -0
  20. package/lib/component/input.d.ts +41 -0
  21. package/lib/component/input.js +342 -0
  22. package/lib/component/selector.d.ts +31 -0
  23. package/lib/component/selector.js +246 -0
  24. package/lib/component/slider.d.ts +18 -0
  25. package/lib/component/slider.js +156 -0
  26. package/lib/component/style/style.d.ts +4 -0
  27. package/lib/component/style/style.js +325 -0
  28. package/lib/component/style/theme.d.ts +33 -0
  29. package/lib/component/style/theme.js +24 -0
  30. package/lib/component/switch.d.ts +12 -0
  31. package/lib/component/switch.js +117 -0
  32. package/lib/component/text.d.ts +9 -0
  33. package/lib/component/text.js +36 -0
  34. package/lib/component/ui-components.d.ts +34 -0
  35. package/lib/component/ui-components.js +108 -0
  36. package/lib/component/views/header-view.d.ts +20 -0
  37. package/lib/component/views/header-view.js +198 -0
  38. package/lib/component/views/log-view.d.ts +41 -0
  39. package/lib/component/views/log-view.js +511 -0
  40. package/lib/component/views/tabs-view.d.ts +41 -0
  41. package/lib/component/views/tabs-view.js +334 -0
  42. package/lib/constant-config.d.ts +5 -0
  43. package/lib/constant-config.js +3 -0
  44. package/lib/event-emitter.d.ts +10 -0
  45. package/lib/event-emitter.js +52 -0
  46. package/lib/float-menu.d.ts +103 -0
  47. package/lib/float-menu.js +648 -0
  48. package/lib/index.d.ts +17 -0
  49. package/lib/index.js +16 -0
  50. package/lib/java-runtime.d.ts +2 -0
  51. package/lib/java-runtime.js +2 -0
  52. package/lib/logger.d.ts +43 -0
  53. package/lib/logger.js +200 -0
  54. package/lib/runtime/component-registry.d.ts +24 -0
  55. package/lib/runtime/component-registry.js +48 -0
  56. package/lib/runtime/launch-sequence.d.ts +5 -0
  57. package/lib/runtime/launch-sequence.js +20 -0
  58. package/lib/runtime/lifecycle-controller.d.ts +9 -0
  59. package/lib/runtime/lifecycle-controller.js +31 -0
  60. package/lib/runtime/listener-registry.d.ts +7 -0
  61. package/lib/runtime/listener-registry.js +40 -0
  62. package/lib/runtime/overlay-options.d.ts +9 -0
  63. package/lib/runtime/overlay-options.js +42 -0
  64. package/lib/runtime/safe-runtime.d.ts +3 -0
  65. package/lib/runtime/safe-runtime.js +20 -0
  66. package/lib/utils.d.ts +8 -0
  67. package/lib/utils.js +13 -0
  68. package/package.json +43 -0
package/lib/api.js ADDED
@@ -0,0 +1,156 @@
1
+ import Java from "./java-runtime.js";
2
+ export const API = {
3
+ get ImageView() {
4
+ return Java.use("android.widget.ImageView");
5
+ },
6
+ get ImageViewScaleType() {
7
+ return Java.use("android.widget.ImageView$ScaleType");
8
+ },
9
+ get FrameLayoutParams() {
10
+ return Java.use("android.widget.FrameLayout$LayoutParams");
11
+ },
12
+ get PopupWindow() {
13
+ return Java.use("android.widget.PopupWindow");
14
+ },
15
+ get PopupWindowOnDismissListener() {
16
+ return Java.use("android.widget.PopupWindow$OnDismissListener");
17
+ },
18
+ get Gravity() {
19
+ return Java.use("android.view.Gravity");
20
+ },
21
+ get DialogMultiChoiceListener() {
22
+ return Java.use("android.content.DialogInterface$OnMultiChoiceClickListener");
23
+ },
24
+ get DialogClickListener() {
25
+ return Java.use("android.content.DialogInterface$OnClickListener");
26
+ },
27
+ get HorizontalScrollView() {
28
+ return Java.use("android.widget.HorizontalScrollView");
29
+ },
30
+ get Context() {
31
+ return Java.use("android.content.Context");
32
+ },
33
+ get GradientDrawable() {
34
+ return Java.use("android.graphics.drawable.GradientDrawable");
35
+ },
36
+ get BitmapFactory() {
37
+ return Java.use("android.graphics.BitmapFactory");
38
+ },
39
+ get Base64() {
40
+ return Java.use("android.util.Base64");
41
+ },
42
+ get Button() {
43
+ return Java.use("android.widget.Button");
44
+ },
45
+ get Color() {
46
+ return Java.use("android.graphics.Color");
47
+ },
48
+ get ViewManager() {
49
+ return Java.use("android.view.ViewManager");
50
+ },
51
+ get JString() {
52
+ return Java.use("java.lang.String");
53
+ },
54
+ get View() {
55
+ return Java.use("android.view.View");
56
+ },
57
+ get GridLayout() {
58
+ return Java.use("android.widget.GridLayout");
59
+ },
60
+ get CheckBox() {
61
+ return Java.use("android.widget.CheckBox");
62
+ },
63
+ get SeekBar() {
64
+ return Java.use("android.widget.SeekBar");
65
+ },
66
+ get Html() {
67
+ return Java.use("android.text.Html");
68
+ },
69
+ get SeekBarOnSeekBarChangeListener() {
70
+ return Java.use("android.widget.SeekBar$OnSeekBarChangeListener");
71
+ },
72
+ get AdapterViewOnItemSelectedListener() {
73
+ return Java.use("android.widget.AdapterView$OnItemSelectedListener");
74
+ },
75
+ get Spinner() {
76
+ return Java.use("android.widget.Spinner");
77
+ },
78
+ get ArrayAdapter() {
79
+ return Java.use("android.widget.ArrayAdapter");
80
+ },
81
+ get R_layout() {
82
+ return Java.use("android.R$layout");
83
+ },
84
+ get DialogInterfaceOnClickListener() {
85
+ return Java.use("android.content.DialogInterface$OnClickListener");
86
+ },
87
+ get DialogInterfaceOnDismissListener() {
88
+ return Java.use("android.content.DialogInterface$OnDismissListener");
89
+ },
90
+ get AlertDialogBuilder() {
91
+ return Java.use("android.app.AlertDialog$Builder");
92
+ },
93
+ get AlertDialog() {
94
+ return Java.use("android.app.AlertDialog");
95
+ },
96
+ get OnTouchListener() {
97
+ return Java.use("android.view.View$OnTouchListener");
98
+ },
99
+ get MotionEvent() {
100
+ return Java.use("android.view.MotionEvent");
101
+ },
102
+ get EditText() {
103
+ return Java.use("android.widget.EditText");
104
+ },
105
+ get Point() {
106
+ return Java.use("android.graphics.Point");
107
+ },
108
+ get DisplayMetrics() {
109
+ return Java.use("android.util.DisplayMetrics");
110
+ },
111
+ get TextViewBufferType() {
112
+ return Java.use("android.widget.TextView$BufferType");
113
+ },
114
+ get InputType() {
115
+ return Java.use("android.text.InputType");
116
+ },
117
+ get GridLayoutParams() {
118
+ return Java.use("android.widget.GridLayout$LayoutParams");
119
+ },
120
+ get Switch() {
121
+ return Java.use("android.widget.Switch");
122
+ },
123
+ get OnCheckedChangeListener() {
124
+ return Java.use("android.widget.CompoundButton$OnCheckedChangeListener");
125
+ },
126
+ get CompoundButtonOnCheckedChangeListener() {
127
+ return Java.use("android.widget.CompoundButton$OnCheckedChangeListener");
128
+ },
129
+ get TextView() {
130
+ return Java.use("android.widget.TextView");
131
+ },
132
+ get OnClickListener() {
133
+ return Java.use("android.view.View$OnClickListener");
134
+ },
135
+ get FrameLayout() {
136
+ return Java.use("android.widget.FrameLayout");
137
+ },
138
+ get ViewGroupLayoutParams() {
139
+ return Java.use("android.view.ViewGroup$LayoutParams");
140
+ },
141
+ get LayoutParams() {
142
+ return Java.use("android.view.WindowManager$LayoutParams");
143
+ },
144
+ get BuildVERSION() {
145
+ return Java.use("android.os.Build$VERSION");
146
+ },
147
+ get LinearLayout() {
148
+ return Java.use("android.widget.LinearLayout");
149
+ },
150
+ get LinearLayoutParams() {
151
+ return Java.use("android.widget.LinearLayout$LayoutParams");
152
+ },
153
+ get ScrollView() {
154
+ return Java.use("android.widget.ScrollView");
155
+ },
156
+ };
@@ -0,0 +1,11 @@
1
+ import { UIComponent } from "./ui-components.js";
2
+ export declare class Button extends UIComponent {
3
+ private label;
4
+ private handler?;
5
+ private kind;
6
+ constructor(id: string, label: string, kind?: "primary" | "danger", handler?: () => void);
7
+ protected createView(context: any): void;
8
+ protected updateView(): void;
9
+ setLabel(label: string): void;
10
+ onClick(handler: () => void): void;
11
+ }
@@ -0,0 +1,61 @@
1
+ import Java from "../java-runtime.js";
2
+ import { API } from "../api.js";
3
+ import { Logger } from "../logger.js";
4
+ import { deferSafe } from "../runtime/safe-runtime.js";
5
+ import { createJavaListener } from "../android-listener.js";
6
+ import { applyStyle } from "./style/style.js";
7
+ import { UIComponent } from "./ui-components.js";
8
+ export class Button extends UIComponent {
9
+ constructor(id, label, kind = "primary", handler) {
10
+ super(id);
11
+ this.kind = "primary";
12
+ this.label = label;
13
+ this.kind = kind;
14
+ this.handler = handler;
15
+ this.value = null;
16
+ }
17
+ createView(context) {
18
+ const Button = API.Button;
19
+ const String = API.JString;
20
+ const Gravity = API.Gravity;
21
+ this.view = Button.$new(context);
22
+ this.view.setText.overload('java.lang.CharSequence').call(this.view, this.label);
23
+ applyStyle(this.view, this.kind === "danger" ? "dangerButton" : "primaryButton", this.menu.options.theme);
24
+ try {
25
+ this.view.setGravity(Gravity.CENTER.value);
26
+ }
27
+ catch (_e) { }
28
+ const OnClickListener = API.OnClickListener;
29
+ const self = this;
30
+ const clickListener = createJavaListener({
31
+ key: "view-click",
32
+ interfaceClass: OnClickListener,
33
+ callbacks: {
34
+ onClick: (_v) => {
35
+ self.emit("click");
36
+ if (self.handler) {
37
+ deferSafe(`Button:${self.id}:click`, self.handler);
38
+ }
39
+ },
40
+ },
41
+ });
42
+ this.own(() => clickListener.dispose());
43
+ this.view.setOnClickListener(clickListener.instance);
44
+ }
45
+ updateView() {
46
+ }
47
+ setLabel(label) {
48
+ this.label = label;
49
+ if (!this.view) {
50
+ Logger.instance.warn(`[Button:${this.id}] Cannot set label - view not initialized`);
51
+ return;
52
+ }
53
+ Java.scheduleOnMainThread(() => {
54
+ const String = API.JString;
55
+ this.view.setText.overload('java.lang.CharSequence').call(this.view, label);
56
+ });
57
+ }
58
+ onClick(handler) {
59
+ this.handler = handler;
60
+ }
61
+ }
@@ -0,0 +1,9 @@
1
+ import { UIComponent } from "./ui-components.js";
2
+ export declare class Category extends UIComponent {
3
+ private label;
4
+ private labelView;
5
+ constructor(id: string, label: string);
6
+ protected createView(context: any): void;
7
+ protected updateView(): void;
8
+ setLabel(label: string): void;
9
+ }
@@ -0,0 +1,57 @@
1
+ import Java from "../java-runtime.js";
2
+ import { API } from "../api.js";
3
+ import { applyStyle, dp } from "./style/style.js";
4
+ import { UIComponent } from "./ui-components.js";
5
+ export class Category extends UIComponent {
6
+ constructor(id, label) {
7
+ super(id);
8
+ this.label = label;
9
+ this.value = label;
10
+ }
11
+ createView(context) {
12
+ const LinearLayout = API.LinearLayout;
13
+ const LinearLayoutParams = API.LinearLayoutParams;
14
+ const ViewGroupLayoutParams = API.ViewGroupLayoutParams;
15
+ const TextView = API.TextView;
16
+ const View = API.View;
17
+ const Gravity = API.Gravity;
18
+ const String = API.JString;
19
+ const theme = this.menu.options.theme;
20
+ const root = LinearLayout.$new(context);
21
+ root.setOrientation(LinearLayout.HORIZONTAL.value);
22
+ root.setGravity(Gravity.CENTER_VERTICAL.value);
23
+ root.setLayoutParams(LinearLayoutParams.$new(ViewGroupLayoutParams.MATCH_PARENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value));
24
+ root.setPadding(dp(context, 2), dp(context, 10), dp(context, 2), dp(context, 8));
25
+ const indicator = View.$new(context);
26
+ const indicatorLp = LinearLayoutParams.$new(dp(context, 3), dp(context, 14));
27
+ indicatorLp.setMargins(0, 0, dp(context, 10), 0);
28
+ indicator.setLayoutParams(indicatorLp);
29
+ const GradientDrawable = API.GradientDrawable;
30
+ const indicatorBg = GradientDrawable.$new();
31
+ indicatorBg.setColor(theme.colors.accent | 0);
32
+ indicatorBg.setCornerRadius(dp(context, 2));
33
+ indicator.setBackground(indicatorBg);
34
+ const labelView = TextView.$new(context);
35
+ labelView.setText.overload('java.lang.CharSequence').call(labelView, this.label);
36
+ applyStyle(labelView, "category", theme);
37
+ const textLp = LinearLayoutParams.$new(0, ViewGroupLayoutParams.WRAP_CONTENT.value, 1.0);
38
+ labelView.setLayoutParams(textLp);
39
+ root.addView(indicator);
40
+ root.addView(labelView);
41
+ this.labelView = labelView;
42
+ this.view = root;
43
+ }
44
+ updateView() {
45
+ if (!this.labelView)
46
+ return;
47
+ Java.scheduleOnMainThread(() => {
48
+ const String = API.JString;
49
+ this.labelView.setText.overload('java.lang.CharSequence').call(this.labelView, this.value);
50
+ });
51
+ }
52
+ setLabel(label) {
53
+ this.label = label;
54
+ this.value = label;
55
+ this.updateView();
56
+ }
57
+ }
@@ -0,0 +1,33 @@
1
+ import { UIComponent } from "./ui-components.js";
2
+ export interface CheckBoxOption {
3
+ id: string;
4
+ label: string;
5
+ [key: string]: any;
6
+ }
7
+ export declare class CheckBoxGroup extends UIComponent {
8
+ private optionsMap;
9
+ private title;
10
+ private changeHandler?;
11
+ private valueChangeHandler?;
12
+ private maxDisplayCount;
13
+ private titleView;
14
+ private valueView;
15
+ private arrowView;
16
+ private triggerRow;
17
+ constructor(id: string, title: string, options: CheckBoxOption[], initialChecked?: string[]);
18
+ onChangeHandler(handler: (value: CheckBoxOption[], item?: {
19
+ id: string;
20
+ checked: boolean;
21
+ }) => void): void;
22
+ onValueChangeHandler(handler: (value: CheckBoxOption[]) => void): void;
23
+ setMaxDisplayCount(n: number): void;
24
+ protected createView(context: any): void;
25
+ private openMultiSelectDialog;
26
+ protected updateView(): void;
27
+ private getDisplayText;
28
+ getCheckedValues(): CheckBoxOption[];
29
+ setChecked(id: string, checked: boolean): void;
30
+ setCheckedValues(checkedIds: string[]): void;
31
+ getOptions(): CheckBoxOption[];
32
+ setTitle(title: string): void;
33
+ }
@@ -0,0 +1,276 @@
1
+ import Java from "../java-runtime.js";
2
+ import { API } from "../api.js";
3
+ import { Logger } from "../logger.js";
4
+ import { applyStyle, dp } from "./style/style.js";
5
+ import { UIComponent } from "./ui-components.js";
6
+ import { deferSafe } from "../runtime/safe-runtime.js";
7
+ import { getOverlayWindowType } from "../android-runtime.js";
8
+ import { createJavaListener, disposeJavaListenerOwner, } from "../android-listener.js";
9
+ export class CheckBoxGroup extends UIComponent {
10
+ constructor(id, title, options, initialChecked = []) {
11
+ super(id);
12
+ this.optionsMap = new Map();
13
+ this.maxDisplayCount = 3;
14
+ this.title = title;
15
+ for (const opt of options) {
16
+ const checked = initialChecked.includes(opt.id);
17
+ this.optionsMap.set(opt.id, { ...opt, checked });
18
+ }
19
+ this.value = this.getCheckedValues();
20
+ }
21
+ onChangeHandler(handler) {
22
+ this.changeHandler = handler;
23
+ }
24
+ onValueChangeHandler(handler) {
25
+ this.valueChangeHandler = handler;
26
+ }
27
+ setMaxDisplayCount(n) {
28
+ this.maxDisplayCount = Math.max(1, n);
29
+ this.updateView();
30
+ }
31
+ createView(context) {
32
+ const LinearLayout = API.LinearLayout;
33
+ const LinearLayoutParams = API.LinearLayoutParams;
34
+ const ViewGroupLayoutParams = API.ViewGroupLayoutParams;
35
+ const TextView = API.TextView;
36
+ const Gravity = API.Gravity;
37
+ const String = API.JString;
38
+ const OnClickListener = API.OnClickListener;
39
+ const theme = this.menu.options.theme;
40
+ const root = LinearLayout.$new(context);
41
+ root.setOrientation(LinearLayout.VERTICAL.value);
42
+ root.setLayoutParams(LinearLayoutParams.$new(ViewGroupLayoutParams.MATCH_PARENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value));
43
+ const titleView = TextView.$new(context);
44
+ titleView.setText.overload('java.lang.CharSequence').call(titleView, this.title);
45
+ applyStyle(titleView, "caption", theme);
46
+ try {
47
+ titleView.setTypeface(null, 1);
48
+ }
49
+ catch (_e) { }
50
+ try {
51
+ titleView.setAllCaps(true);
52
+ }
53
+ catch (_e) { }
54
+ try {
55
+ titleView.setLetterSpacing(0.04);
56
+ }
57
+ catch (_e) { }
58
+ const titleLp = LinearLayoutParams.$new(ViewGroupLayoutParams.MATCH_PARENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value);
59
+ titleLp.setMargins(0, 0, 0, dp(context, 8));
60
+ titleView.setLayoutParams(titleLp);
61
+ const triggerRow = LinearLayout.$new(context);
62
+ triggerRow.setOrientation(LinearLayout.HORIZONTAL.value);
63
+ triggerRow.setGravity(Gravity.CENTER_VERTICAL.value);
64
+ applyStyle(triggerRow, "row", theme);
65
+ triggerRow.setPadding(dp(context, 14), dp(context, 14), dp(context, 14), dp(context, 14));
66
+ const valueView = TextView.$new(context);
67
+ valueView.setText.overload('java.lang.CharSequence').call(valueView, this.getDisplayText());
68
+ applyStyle(valueView, "text", theme);
69
+ valueView.setTextSize(2, 14);
70
+ try {
71
+ valueView.setTypeface(null, 1);
72
+ }
73
+ catch (_e) { }
74
+ valueView.setSingleLine(true);
75
+ valueView.setGravity(Gravity.LEFT.value | Gravity.CENTER_VERTICAL.value);
76
+ valueView.setLayoutParams(LinearLayoutParams.$new(0, ViewGroupLayoutParams.WRAP_CONTENT.value, 1.0));
77
+ const arrowView = TextView.$new(context);
78
+ arrowView.setText.overload('java.lang.CharSequence').call(arrowView, "⌄");
79
+ arrowView.setTextColor(theme.colors.accent);
80
+ arrowView.setTextSize(2, 18);
81
+ try {
82
+ arrowView.setTypeface(null, 1);
83
+ }
84
+ catch (_e) { }
85
+ arrowView.setGravity(Gravity.CENTER.value);
86
+ const arrowLp = LinearLayoutParams.$new(ViewGroupLayoutParams.WRAP_CONTENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value);
87
+ arrowLp.leftMargin = dp(context, 12);
88
+ arrowView.setLayoutParams(arrowLp);
89
+ triggerRow.addView(valueView);
90
+ triggerRow.addView(arrowView);
91
+ const self = this;
92
+ const clickListener = createJavaListener({
93
+ key: "view-click",
94
+ interfaceClass: OnClickListener,
95
+ callbacks: {
96
+ onClick: () => {
97
+ self.openMultiSelectDialog(context);
98
+ },
99
+ },
100
+ });
101
+ this.own(() => clickListener.dispose());
102
+ triggerRow.setClickable(true);
103
+ triggerRow.setOnClickListener(clickListener.$new());
104
+ root.addView(titleView);
105
+ root.addView(triggerRow);
106
+ this.view = root;
107
+ this.titleView = titleView;
108
+ this.valueView = valueView;
109
+ this.arrowView = arrowView;
110
+ this.triggerRow = triggerRow;
111
+ }
112
+ openMultiSelectDialog(context) {
113
+ Java.scheduleOnMainThread(() => {
114
+ let releaseDialogListeners = () => { };
115
+ try {
116
+ const AlertDialogBuilder = API.AlertDialogBuilder;
117
+ const String = API.JString;
118
+ const opts = Array.from(this.optionsMap.values());
119
+ const labels = opts.map((o) => o.label);
120
+ const checkedArr = opts.map((o) => !!o.checked);
121
+ const csArray = Java.array("java.lang.CharSequence", labels.map((s) => String.$new(s)));
122
+ const boolArray = Java.array("boolean", checkedArr);
123
+ const DialogMultiChoiceListener = API.DialogMultiChoiceListener;
124
+ const DialogClickListener = API.DialogClickListener;
125
+ const self = this;
126
+ const dialogListenerOwner = `CheckBoxGroup:${this.id}:dialog:${Date.now()}:${Math.random()}`;
127
+ releaseDialogListeners = this.own(() => disposeJavaListenerOwner(dialogListenerOwner));
128
+ const multiListener = createJavaListener({
129
+ key: "dialog-multi-choice",
130
+ owner: dialogListenerOwner,
131
+ interfaceClass: DialogMultiChoiceListener,
132
+ callbacks: {
133
+ onClick: (_dialog, which, isChecked) => {
134
+ const opt = opts[which];
135
+ self.optionsMap.set(opt.id, { ...opt, checked: isChecked });
136
+ self.value = self.getCheckedValues();
137
+ self.emit("change", self.value, { id: opt.id, checked: isChecked });
138
+ if (self.changeHandler) {
139
+ deferSafe(`CheckBoxGroup:${self.id}:change`, () => self.changeHandler(self.value, {
140
+ id: opt.id,
141
+ checked: isChecked,
142
+ }));
143
+ }
144
+ },
145
+ },
146
+ });
147
+ const okListener = createJavaListener({
148
+ key: "dialog-click",
149
+ owner: dialogListenerOwner,
150
+ interfaceClass: DialogClickListener,
151
+ callbacks: {
152
+ onClick: (dialog, _which) => {
153
+ self.value = self.getCheckedValues();
154
+ self.updateView();
155
+ self.emit("valueChanged", self.value);
156
+ if (self.valueChangeHandler) {
157
+ deferSafe(`CheckBoxGroup:${self.id}:valueChanged`, () => self.valueChangeHandler(self.value));
158
+ }
159
+ dialog.dismiss();
160
+ },
161
+ },
162
+ });
163
+ const cancelListener = createJavaListener({
164
+ key: "dialog-click",
165
+ owner: dialogListenerOwner,
166
+ interfaceClass: DialogClickListener,
167
+ callbacks: {
168
+ onClick: (dialog, _which) => {
169
+ dialog.dismiss();
170
+ },
171
+ },
172
+ });
173
+ const builder = AlertDialogBuilder.$new(context);
174
+ builder.setTitle(String.$new(this.title || "请选择"));
175
+ builder.setMultiChoiceItems(csArray, boolArray, multiListener.$new());
176
+ builder.setPositiveButton(String.$new("确定"), okListener.$new());
177
+ builder.setNegativeButton(String.$new("取消"), cancelListener.$new());
178
+ const dialog = builder.create();
179
+ const dismissListener = createJavaListener({
180
+ key: "dialog-dismiss",
181
+ owner: dialogListenerOwner,
182
+ interfaceClass: API.DialogInterfaceOnDismissListener,
183
+ callbacks: { onDismiss: releaseDialogListeners },
184
+ });
185
+ dialog.setOnDismissListener(dismissListener.instance);
186
+ const WindowManagerLP = API.LayoutParams;
187
+ const win = dialog.getWindow();
188
+ if (win) {
189
+ win.setType(getOverlayWindowType(WindowManagerLP));
190
+ win.addFlags(WindowManagerLP.FLAG_NOT_TOUCH_MODAL.value);
191
+ }
192
+ dialog.show();
193
+ }
194
+ catch (error) {
195
+ releaseDialogListeners();
196
+ Logger.instance.error(`[CheckBoxGroup:${this.id}] Failed to show dialog: ${error}`);
197
+ }
198
+ });
199
+ }
200
+ updateView() {
201
+ if (!this.valueView)
202
+ return;
203
+ Java.scheduleOnMainThread(() => {
204
+ const String = API.JString;
205
+ this.valueView.setText.overload('java.lang.CharSequence').call(this.valueView, this.getDisplayText());
206
+ });
207
+ }
208
+ getDisplayText() {
209
+ const selected = this.getCheckedValues();
210
+ if (selected.length === 0) {
211
+ return `请选择`;
212
+ }
213
+ const labels = selected.map((x) => x.label);
214
+ if (labels.length <= this.maxDisplayCount) {
215
+ return `${labels.join("、")}`;
216
+ }
217
+ const shown = labels.slice(0, this.maxDisplayCount).join("、");
218
+ const rest = labels.length - this.maxDisplayCount;
219
+ return `${shown} +${rest}`;
220
+ }
221
+ getCheckedValues() {
222
+ return Array.from(this.optionsMap.values()).filter((op) => op.checked);
223
+ }
224
+ setChecked(id, checked) {
225
+ if (!this.optionsMap.has(id)) {
226
+ Logger.instance.warn(`[CheckBoxGroup:${this.id}] Option with id "${id}" not found`);
227
+ return;
228
+ }
229
+ const opt = this.optionsMap.get(id);
230
+ this.optionsMap.set(id, { ...opt, checked });
231
+ this.value = this.getCheckedValues();
232
+ this.updateView();
233
+ this.emit("change", this.value, { id, checked });
234
+ this.emit("valueChanged", this.value);
235
+ if (this.changeHandler) {
236
+ deferSafe(`CheckBoxGroup:${this.id}:change`, () => this.changeHandler(this.value, { id: opt.id, checked }));
237
+ }
238
+ if (this.valueChangeHandler) {
239
+ deferSafe(`CheckBoxGroup:${this.id}:valueChanged`, () => this.valueChangeHandler(this.value));
240
+ }
241
+ }
242
+ setCheckedValues(checkedIds) {
243
+ for (const [id, opt] of this.optionsMap.entries()) {
244
+ this.optionsMap.set(id, { ...opt, checked: false });
245
+ }
246
+ for (const id of checkedIds) {
247
+ if (this.optionsMap.has(id)) {
248
+ const opt = this.optionsMap.get(id);
249
+ this.optionsMap.set(id, { ...opt, checked: true });
250
+ }
251
+ }
252
+ this.value = this.getCheckedValues();
253
+ this.updateView();
254
+ this.emit("change", this.value);
255
+ this.emit("valueChanged", this.value);
256
+ if (this.changeHandler) {
257
+ deferSafe(`CheckBoxGroup:${this.id}:change`, () => this.changeHandler(this.value));
258
+ }
259
+ if (this.valueChangeHandler) {
260
+ deferSafe(`CheckBoxGroup:${this.id}:valueChanged`, () => this.valueChangeHandler(this.value));
261
+ }
262
+ }
263
+ getOptions() {
264
+ return Array.from(this.optionsMap.values()).slice();
265
+ }
266
+ setTitle(title) {
267
+ this.title = title;
268
+ if (!this.titleView)
269
+ return;
270
+ Java.scheduleOnMainThread(() => {
271
+ const String = API.JString;
272
+ this.titleView.setText.overload('java.lang.CharSequence').call(this.titleView, title);
273
+ this.updateView();
274
+ });
275
+ }
276
+ }
@@ -0,0 +1,19 @@
1
+ import { UIComponent } from "./ui-components.js";
2
+ export declare class Collapsible extends UIComponent {
3
+ private title;
4
+ private expanded;
5
+ private contentContainer;
6
+ private arrowView;
7
+ pendingChildren: UIComponent[];
8
+ constructor(id: string, title: string, expanded?: boolean);
9
+ protected createView(context: any): void;
10
+ protected updateView(): void;
11
+ toggle(): void;
12
+ expand(): void;
13
+ collapse(): void;
14
+ setTitle(title: string): void;
15
+ addChild(component: UIComponent): void;
16
+ addChildren(components: UIComponent[]): void;
17
+ removeChildView(view: any): void;
18
+ clearChildren(): void;
19
+ }