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.
- package/README.md +181 -0
- package/lib/android-listener.d.ts +20 -0
- package/lib/android-listener.js +92 -0
- package/lib/android-runtime.d.ts +25 -0
- package/lib/android-runtime.js +310 -0
- package/lib/api.d.ts +1 -0
- package/lib/api.js +156 -0
- package/lib/component/button.d.ts +11 -0
- package/lib/component/button.js +61 -0
- package/lib/component/category.d.ts +9 -0
- package/lib/component/category.js +57 -0
- package/lib/component/checkBox.d.ts +33 -0
- package/lib/component/checkBox.js +276 -0
- package/lib/component/collapsible.d.ts +19 -0
- package/lib/component/collapsible.js +197 -0
- package/lib/component/image.d.ts +18 -0
- package/lib/component/image.js +85 -0
- package/lib/component/infoCardBox.d.ts +13 -0
- package/lib/component/infoCardBox.js +68 -0
- package/lib/component/input.d.ts +41 -0
- package/lib/component/input.js +342 -0
- package/lib/component/selector.d.ts +31 -0
- package/lib/component/selector.js +246 -0
- package/lib/component/slider.d.ts +18 -0
- package/lib/component/slider.js +156 -0
- package/lib/component/style/style.d.ts +4 -0
- package/lib/component/style/style.js +325 -0
- package/lib/component/style/theme.d.ts +33 -0
- package/lib/component/style/theme.js +24 -0
- package/lib/component/switch.d.ts +12 -0
- package/lib/component/switch.js +117 -0
- package/lib/component/text.d.ts +9 -0
- package/lib/component/text.js +36 -0
- package/lib/component/ui-components.d.ts +34 -0
- package/lib/component/ui-components.js +108 -0
- package/lib/component/views/header-view.d.ts +20 -0
- package/lib/component/views/header-view.js +198 -0
- package/lib/component/views/log-view.d.ts +41 -0
- package/lib/component/views/log-view.js +511 -0
- package/lib/component/views/tabs-view.d.ts +41 -0
- package/lib/component/views/tabs-view.js +334 -0
- package/lib/constant-config.d.ts +5 -0
- package/lib/constant-config.js +3 -0
- package/lib/event-emitter.d.ts +10 -0
- package/lib/event-emitter.js +52 -0
- package/lib/float-menu.d.ts +103 -0
- package/lib/float-menu.js +648 -0
- package/lib/index.d.ts +17 -0
- package/lib/index.js +16 -0
- package/lib/java-runtime.d.ts +2 -0
- package/lib/java-runtime.js +2 -0
- package/lib/logger.d.ts +43 -0
- package/lib/logger.js +200 -0
- package/lib/runtime/component-registry.d.ts +24 -0
- package/lib/runtime/component-registry.js +48 -0
- package/lib/runtime/launch-sequence.d.ts +5 -0
- package/lib/runtime/launch-sequence.js +20 -0
- package/lib/runtime/lifecycle-controller.d.ts +9 -0
- package/lib/runtime/lifecycle-controller.js +31 -0
- package/lib/runtime/listener-registry.d.ts +7 -0
- package/lib/runtime/listener-registry.js +40 -0
- package/lib/runtime/overlay-options.d.ts +9 -0
- package/lib/runtime/overlay-options.js +42 -0
- package/lib/runtime/safe-runtime.d.ts +3 -0
- package/lib/runtime/safe-runtime.js +20 -0
- package/lib/utils.d.ts +8 -0
- package/lib/utils.js +13 -0
- package/package.json +43 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import Java from "../java-runtime.js";
|
|
2
|
+
import { API } from "../api.js";
|
|
3
|
+
import { Logger } from "../logger.js";
|
|
4
|
+
import { UIComponent } from "./ui-components.js";
|
|
5
|
+
import { deferSafe } from "../runtime/safe-runtime.js";
|
|
6
|
+
import { createJavaListener } from "../android-listener.js";
|
|
7
|
+
export class Slider extends UIComponent {
|
|
8
|
+
constructor(id, label, min, max, initialValue = min, step = 1, handler) {
|
|
9
|
+
super(id);
|
|
10
|
+
this.label = label;
|
|
11
|
+
this.min = min;
|
|
12
|
+
this.max = max;
|
|
13
|
+
this.step = step;
|
|
14
|
+
this.handler = handler;
|
|
15
|
+
this.value = this.clampToStep(initialValue);
|
|
16
|
+
}
|
|
17
|
+
createView(context) {
|
|
18
|
+
const LinearLayout = API.LinearLayout;
|
|
19
|
+
const TextView = API.TextView;
|
|
20
|
+
const SeekBar = API.SeekBar;
|
|
21
|
+
const Color = API.Color;
|
|
22
|
+
const String = API.JString;
|
|
23
|
+
const ViewGroupLayoutParams = API.ViewGroupLayoutParams;
|
|
24
|
+
const LinearLayoutParams = API.LinearLayoutParams;
|
|
25
|
+
const container = LinearLayout.$new(context);
|
|
26
|
+
container.setOrientation(0);
|
|
27
|
+
container.setLayoutParams(LinearLayoutParams.$new(ViewGroupLayoutParams.MATCH_PARENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value));
|
|
28
|
+
container.setPadding(16, 8, 16, 8);
|
|
29
|
+
const labelView = TextView.$new(context);
|
|
30
|
+
labelView.setText.overload('java.lang.CharSequence').call(labelView, this.label);
|
|
31
|
+
labelView.setTextColor(Color.WHITE.value);
|
|
32
|
+
labelView.setTextSize(14);
|
|
33
|
+
labelView.setLayoutParams(LinearLayoutParams.$new(0, ViewGroupLayoutParams.WRAP_CONTENT.value, 1.0));
|
|
34
|
+
const valueView = TextView.$new(context);
|
|
35
|
+
valueView.setText.overload('java.lang.CharSequence').call(valueView, String.$new(this.value.toString()));
|
|
36
|
+
valueView.setTextColor(Color.WHITE.value);
|
|
37
|
+
valueView.setTextSize(14);
|
|
38
|
+
valueView.setLayoutParams(LinearLayoutParams.$new(ViewGroupLayoutParams.WRAP_CONTENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value));
|
|
39
|
+
container.addView(labelView);
|
|
40
|
+
container.addView(valueView);
|
|
41
|
+
const seekBar = SeekBar.$new(context);
|
|
42
|
+
seekBar.setMax(this.calculateSeekBarMax());
|
|
43
|
+
seekBar.setProgress(this.valueToProgress(this.value));
|
|
44
|
+
seekBar.setLayoutParams(LinearLayoutParams.$new(ViewGroupLayoutParams.MATCH_PARENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value));
|
|
45
|
+
const verticalLayout = LinearLayout.$new(context);
|
|
46
|
+
verticalLayout.setOrientation(1);
|
|
47
|
+
verticalLayout.setLayoutParams(LinearLayoutParams.$new(ViewGroupLayoutParams.MATCH_PARENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value));
|
|
48
|
+
verticalLayout.addView(container);
|
|
49
|
+
verticalLayout.addView(seekBar);
|
|
50
|
+
this.view = verticalLayout;
|
|
51
|
+
this.view.seekBar = seekBar;
|
|
52
|
+
this.view.valueView = valueView;
|
|
53
|
+
this.view.labelView = labelView;
|
|
54
|
+
this.view.container = container;
|
|
55
|
+
const SeekBarOnSeekBarChangeListener = API.SeekBarOnSeekBarChangeListener;
|
|
56
|
+
const self = this;
|
|
57
|
+
const changeListener = createJavaListener({
|
|
58
|
+
key: "seekbar-change",
|
|
59
|
+
interfaceClass: SeekBarOnSeekBarChangeListener,
|
|
60
|
+
callbacks: {
|
|
61
|
+
onProgressChanged: (seekBar, progress, fromUser) => {
|
|
62
|
+
if (fromUser) {
|
|
63
|
+
const newValue = self.progressToValue(progress);
|
|
64
|
+
self.value = newValue;
|
|
65
|
+
Java.scheduleOnMainThread(() => {
|
|
66
|
+
const valueView = self.view.valueView;
|
|
67
|
+
if (valueView) {
|
|
68
|
+
valueView.setText.overload('java.lang.CharSequence').call(valueView, String.$new(newValue.toString()));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
self.emit("valueChanged", newValue);
|
|
72
|
+
if (self.handler) {
|
|
73
|
+
deferSafe(`Slider:${self.id}:valueChanged`, () => self.handler(newValue));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
onStartTrackingTouch: (_seekBar) => {
|
|
78
|
+
},
|
|
79
|
+
onStopTrackingTouch: (_seekBar) => {
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
this.own(() => changeListener.dispose());
|
|
84
|
+
seekBar.setOnSeekBarChangeListener(changeListener.instance);
|
|
85
|
+
}
|
|
86
|
+
onValueChange(handler) {
|
|
87
|
+
this.handler = handler;
|
|
88
|
+
}
|
|
89
|
+
updateView() {
|
|
90
|
+
if (!this.view) {
|
|
91
|
+
Logger.instance.warn(`[Slider:${this.id}] Cannot update view - view not initialized`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
Java.scheduleOnMainThread(() => {
|
|
95
|
+
const seekBar = this.view.seekBar;
|
|
96
|
+
const valueView = this.view.valueView;
|
|
97
|
+
if (seekBar) {
|
|
98
|
+
seekBar.setProgress(this.valueToProgress(this.value));
|
|
99
|
+
}
|
|
100
|
+
if (valueView) {
|
|
101
|
+
const String = API.JString;
|
|
102
|
+
valueView.setText.overload('java.lang.CharSequence').call(valueView, String.$new(this.value.toString()));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
setLabel(label) {
|
|
107
|
+
this.label = label;
|
|
108
|
+
if (!this.view) {
|
|
109
|
+
Logger.instance.warn(`[Slider:${this.id}] Cannot set label - view not initialized`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
Java.scheduleOnMainThread(() => {
|
|
113
|
+
const labelView = this.view.labelView;
|
|
114
|
+
if (labelView) {
|
|
115
|
+
const String = API.JString;
|
|
116
|
+
labelView.setText.overload('java.lang.CharSequence').call(labelView, String.$new(label));
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
setRange(min, max, step = 1) {
|
|
121
|
+
this.min = min;
|
|
122
|
+
this.max = max;
|
|
123
|
+
this.step = step;
|
|
124
|
+
this.value = this.clampToStep(this.value);
|
|
125
|
+
if (!this.view) {
|
|
126
|
+
Logger.instance.warn(`[Slider:${this.id}] Cannot set range - view not initialized`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
Java.scheduleOnMainThread(() => {
|
|
130
|
+
const seekBar = this.view.seekBar;
|
|
131
|
+
if (seekBar) {
|
|
132
|
+
seekBar.setMax(this.calculateSeekBarMax());
|
|
133
|
+
seekBar.setProgress(this.valueToProgress(this.value));
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
this.updateView();
|
|
137
|
+
}
|
|
138
|
+
calculateSeekBarMax() {
|
|
139
|
+
return Math.round((this.max - this.min) / this.step);
|
|
140
|
+
}
|
|
141
|
+
valueToProgress(value) {
|
|
142
|
+
return Math.round((value - this.min) / this.step);
|
|
143
|
+
}
|
|
144
|
+
progressToValue(progress) {
|
|
145
|
+
const value = this.min + progress * this.step;
|
|
146
|
+
return this.clampToStep(value);
|
|
147
|
+
}
|
|
148
|
+
clampToStep(value) {
|
|
149
|
+
let clamped = Math.max(this.min, Math.min(this.max, value));
|
|
150
|
+
if (this.step > 0) {
|
|
151
|
+
const steps = Math.round((clamped - this.min) / this.step);
|
|
152
|
+
clamped = this.min + steps * this.step;
|
|
153
|
+
}
|
|
154
|
+
return clamped;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Theme } from "./theme.js";
|
|
2
|
+
export type StyleRole = "overlay" | "card" | "category" | "row" | "fieldLabel" | "selectValue" | "text" | "caption" | "noteText" | "infoCard" | "noteCard" | "inputButton" | "inputTrigger" | "primaryButton" | "dangerButton" | "headerBar" | "iconButton" | "textButton" | "dangerTextButton" | "divider" | "chip" | "dialog" | "inputField";
|
|
3
|
+
export declare function dp(ctx: any, v: number): number;
|
|
4
|
+
export declare function applyStyle(view: any, role: StyleRole, theme: Theme): void;
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import Java from "../../java-runtime.js";
|
|
2
|
+
import { API } from "../../api.js";
|
|
3
|
+
import { readDisplayDensity } from "../../android-runtime.js";
|
|
4
|
+
export function dp(ctx, v) {
|
|
5
|
+
return Math.floor(v * readDisplayDensity(ctx) + 0.5);
|
|
6
|
+
}
|
|
7
|
+
export function applyStyle(view, role, theme) {
|
|
8
|
+
const ctx = view.getContext();
|
|
9
|
+
const dpx = (v) => dp(ctx, v);
|
|
10
|
+
const withAlpha = (color, alpha) => {
|
|
11
|
+
const a = Math.max(0, Math.min(255, alpha)) & 0xff;
|
|
12
|
+
return (a << 24) | (color & 0x00ffffff) | 0;
|
|
13
|
+
};
|
|
14
|
+
const GradientDrawable = Java.use("android.graphics.drawable.GradientDrawable");
|
|
15
|
+
const TextView = Java.use("android.widget.TextView");
|
|
16
|
+
const rounded = (bg, rDp, stroke) => {
|
|
17
|
+
const d = GradientDrawable.$new();
|
|
18
|
+
d.setColor(bg | 0);
|
|
19
|
+
d.setCornerRadius(dp(ctx, rDp));
|
|
20
|
+
if (stroke)
|
|
21
|
+
d.setStroke(dp(ctx, stroke.wDp), stroke.c);
|
|
22
|
+
view.setBackground(d);
|
|
23
|
+
};
|
|
24
|
+
const asTextView = () => {
|
|
25
|
+
try {
|
|
26
|
+
return Java.cast(view, TextView);
|
|
27
|
+
}
|
|
28
|
+
catch (_e) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
switch (role) {
|
|
33
|
+
case "overlay": {
|
|
34
|
+
const bg = GradientDrawable.$new();
|
|
35
|
+
bg.setShape(GradientDrawable.RECTANGLE.value);
|
|
36
|
+
bg.setCornerRadius(dpx(theme.radiusDp.overlay));
|
|
37
|
+
bg.setColor(theme.colors.overlayBg | 0);
|
|
38
|
+
bg.setStroke(dpx(1), (theme.colors.accentStroke ?? theme.colors.controlStroke) | 0);
|
|
39
|
+
view.setBackgroundDrawable(bg);
|
|
40
|
+
view.setPadding(dpx(12), dpx(12), dpx(12), dpx(12));
|
|
41
|
+
try {
|
|
42
|
+
view.setElevation(dpx(12));
|
|
43
|
+
}
|
|
44
|
+
catch (_e) { }
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case "card":
|
|
48
|
+
rounded(theme.colors.cardBg, theme.radiusDp.card, {
|
|
49
|
+
c: theme.colors.divider,
|
|
50
|
+
wDp: 1,
|
|
51
|
+
});
|
|
52
|
+
view.setPadding(dpx(12), dpx(12), dpx(12), dpx(12));
|
|
53
|
+
view.setElevation(dpx(6));
|
|
54
|
+
break;
|
|
55
|
+
case "infoCard":
|
|
56
|
+
rounded(theme.colors.infoCardBg ?? withAlpha(theme.colors.cardBg, 0xdd), 12, {
|
|
57
|
+
c: withAlpha(theme.colors.accentStroke ?? theme.colors.controlStroke, 0x55),
|
|
58
|
+
wDp: 1,
|
|
59
|
+
});
|
|
60
|
+
view.setPadding(dpx(14), dpx(12), dpx(14), dpx(12));
|
|
61
|
+
break;
|
|
62
|
+
case "inputButton": {
|
|
63
|
+
const bg = theme.colors.rowBg ?? withAlpha(theme.colors.controlBg, 0x26);
|
|
64
|
+
const stroke = theme.colors.accentStroke ?? theme.colors.controlStroke;
|
|
65
|
+
rounded(bg, 14, { c: withAlpha(stroke, 0xaa), wDp: 1 });
|
|
66
|
+
view.setPadding(dpx(14), dpx(12), dpx(14), dpx(12));
|
|
67
|
+
view.setMinimumHeight(dp(ctx, 48));
|
|
68
|
+
const tv = asTextView();
|
|
69
|
+
if (tv) {
|
|
70
|
+
tv.setTextColor(theme.colors.text);
|
|
71
|
+
tv.setTextSize(2, 14);
|
|
72
|
+
tv.setAllCaps(false);
|
|
73
|
+
tv.setGravity(API.Gravity.LEFT.value | API.Gravity.CENTER_VERTICAL.value);
|
|
74
|
+
try {
|
|
75
|
+
tv.setTypeface(null, 1);
|
|
76
|
+
}
|
|
77
|
+
catch (_e) { }
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case "noteCard":
|
|
82
|
+
rounded(theme.colors.noteCardBg ?? withAlpha(theme.colors.accent, 0x18), 12, {
|
|
83
|
+
c: theme.colors.accentStroke ?? theme.colors.controlStroke,
|
|
84
|
+
wDp: 1,
|
|
85
|
+
});
|
|
86
|
+
view.setPadding(dpx(14), dpx(12), dpx(14), dpx(12));
|
|
87
|
+
break;
|
|
88
|
+
case "fieldLabel": {
|
|
89
|
+
const tv = asTextView();
|
|
90
|
+
if (tv) {
|
|
91
|
+
tv.setTextColor(theme.colors.subText);
|
|
92
|
+
tv.setTextSize(2, 11);
|
|
93
|
+
tv.setAllCaps(true);
|
|
94
|
+
try {
|
|
95
|
+
tv.setTypeface(null, 1);
|
|
96
|
+
}
|
|
97
|
+
catch (_e) { }
|
|
98
|
+
try {
|
|
99
|
+
tv.setLetterSpacing(0.04);
|
|
100
|
+
}
|
|
101
|
+
catch (_e) { }
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case "selectValue": {
|
|
106
|
+
const tv = asTextView();
|
|
107
|
+
if (tv) {
|
|
108
|
+
tv.setTextColor(theme.colors.text);
|
|
109
|
+
tv.setTextSize(2, 14);
|
|
110
|
+
try {
|
|
111
|
+
tv.setTypeface(null, 1);
|
|
112
|
+
}
|
|
113
|
+
catch (_e) { }
|
|
114
|
+
tv.setSingleLine(true);
|
|
115
|
+
tv.setGravity(API.Gravity.LEFT.value | API.Gravity.CENTER_VERTICAL.value);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case "category": {
|
|
120
|
+
const tv = asTextView();
|
|
121
|
+
if (tv) {
|
|
122
|
+
tv.setTextColor(theme.colors.sectionText ?? theme.colors.subText);
|
|
123
|
+
tv.setTextSize(2, 14);
|
|
124
|
+
tv.setAllCaps(true);
|
|
125
|
+
try {
|
|
126
|
+
tv.setLetterSpacing(0.06);
|
|
127
|
+
}
|
|
128
|
+
catch (_e) { }
|
|
129
|
+
try {
|
|
130
|
+
tv.setTypeface(null, 1);
|
|
131
|
+
}
|
|
132
|
+
catch (_e) { }
|
|
133
|
+
tv.setGravity(API.Gravity.LEFT.value | API.Gravity.CENTER_VERTICAL.value);
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case "row": {
|
|
138
|
+
const rowBg = theme.colors.rowBg ?? withAlpha(theme.colors.controlBg, 0x22);
|
|
139
|
+
const stroke = withAlpha(theme.colors.controlStroke, 0x33);
|
|
140
|
+
rounded(rowBg, theme.radiusDp.control, { c: stroke, wDp: 1 });
|
|
141
|
+
view.setPadding(dpx(12), dpx(10), dpx(12), dpx(10));
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case "text": {
|
|
145
|
+
const tv = asTextView();
|
|
146
|
+
if (tv) {
|
|
147
|
+
tv.setTextColor(0xffd8e1f0 | 0);
|
|
148
|
+
tv.setTextSize(2, 13);
|
|
149
|
+
tv.setGravity(API.Gravity.LEFT.value);
|
|
150
|
+
try {
|
|
151
|
+
tv.setLineSpacing(dpx(4), 1.0);
|
|
152
|
+
}
|
|
153
|
+
catch (_e) { }
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case "noteText": {
|
|
158
|
+
const tv = asTextView();
|
|
159
|
+
if (tv) {
|
|
160
|
+
tv.setTextColor(0xffc9d4e8 | 0);
|
|
161
|
+
tv.setTextSize(2, 13);
|
|
162
|
+
tv.setGravity(API.Gravity.LEFT.value);
|
|
163
|
+
try {
|
|
164
|
+
tv.setLineSpacing(dpx(4), 1.0);
|
|
165
|
+
}
|
|
166
|
+
catch (_e) { }
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case "caption": {
|
|
171
|
+
const tv = asTextView();
|
|
172
|
+
if (tv) {
|
|
173
|
+
tv.setTextColor(theme.colors.subText);
|
|
174
|
+
tv.setTextSize(2, theme.textSp.caption);
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
case "inputTrigger": {
|
|
179
|
+
const bg = theme.colors.accentSoft ?? theme.colors.controlBg;
|
|
180
|
+
const stroke = theme.colors.accentStroke ?? theme.colors.controlStroke;
|
|
181
|
+
rounded(bg, theme.radiusDp.control, { c: stroke, wDp: 2 });
|
|
182
|
+
view.setPadding(dpx(12), dpx(10), dpx(12), dpx(10));
|
|
183
|
+
view.setMinimumHeight(dpx(42));
|
|
184
|
+
const tv = asTextView();
|
|
185
|
+
if (tv) {
|
|
186
|
+
tv.setTextColor(theme.colors.text);
|
|
187
|
+
tv.setTextSize(2, theme.textSp.body);
|
|
188
|
+
tv.setAllCaps(false);
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
case "primaryButton": {
|
|
193
|
+
rounded(theme.colors.accent, 14);
|
|
194
|
+
view.setPadding(dpx(16), dpx(12), dpx(16), dpx(12));
|
|
195
|
+
view.setMinimumHeight(dpx(48));
|
|
196
|
+
const tv = asTextView();
|
|
197
|
+
if (tv) {
|
|
198
|
+
tv.setTextColor(theme.colors.buttonText ?? 0xffffffff | 0);
|
|
199
|
+
tv.setTextSize(2, 14);
|
|
200
|
+
tv.setAllCaps(true);
|
|
201
|
+
try {
|
|
202
|
+
tv.setTypeface(null, 1);
|
|
203
|
+
}
|
|
204
|
+
catch (_e) { }
|
|
205
|
+
tv.setGravity(API.Gravity.CENTER.value);
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case "dangerButton": {
|
|
210
|
+
rounded(theme.colors.danger, 14);
|
|
211
|
+
view.setPadding(dpx(16), dpx(12), dpx(16), dpx(12));
|
|
212
|
+
view.setMinimumHeight(dpx(48));
|
|
213
|
+
const tv = asTextView();
|
|
214
|
+
if (tv) {
|
|
215
|
+
tv.setTextColor(theme.colors.buttonText ?? 0xffffffff | 0);
|
|
216
|
+
tv.setTextSize(2, 14);
|
|
217
|
+
tv.setAllCaps(true);
|
|
218
|
+
try {
|
|
219
|
+
tv.setTypeface(null, 1);
|
|
220
|
+
}
|
|
221
|
+
catch (_e) { }
|
|
222
|
+
tv.setGravity(API.Gravity.CENTER.value);
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
case "headerBar": {
|
|
227
|
+
const bg = GradientDrawable.$new();
|
|
228
|
+
bg.setCornerRadius(dpx(14));
|
|
229
|
+
bg.setColor(theme.colors.cardBg);
|
|
230
|
+
bg.setStroke(dpx(1), theme.colors.accentStroke ?? theme.colors.controlStroke);
|
|
231
|
+
view.setBackgroundDrawable(bg);
|
|
232
|
+
view.setPadding(dpx(10), dpx(8), dpx(10), dpx(8));
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
case "iconButton": {
|
|
236
|
+
const bg = GradientDrawable.$new();
|
|
237
|
+
bg.setCornerRadius(dpx(10));
|
|
238
|
+
bg.setColor(0x00000000);
|
|
239
|
+
bg.setStroke(dpx(1), theme.colors.accentStroke ?? theme.colors.controlStroke);
|
|
240
|
+
view.setBackgroundDrawable(bg);
|
|
241
|
+
view.setPadding(dpx(6), dpx(6), dpx(6), dpx(6));
|
|
242
|
+
if (view.setAllCaps)
|
|
243
|
+
view.setAllCaps(false);
|
|
244
|
+
if (view.setTextColor)
|
|
245
|
+
view.setTextColor(theme.colors.text);
|
|
246
|
+
if (view.setTextSize)
|
|
247
|
+
view.setTextSize(2, theme.textSp.body);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
case "textButton": {
|
|
251
|
+
try {
|
|
252
|
+
view.setBackground(null);
|
|
253
|
+
}
|
|
254
|
+
catch (_e) { }
|
|
255
|
+
if (view.setAllCaps)
|
|
256
|
+
view.setAllCaps(false);
|
|
257
|
+
if (view.setTextColor)
|
|
258
|
+
view.setTextColor(theme.colors.accent);
|
|
259
|
+
if (view.setTextSize)
|
|
260
|
+
view.setTextSize(2, theme.textSp.body);
|
|
261
|
+
view.setPadding(dpx(8), dpx(6), dpx(8), dpx(6));
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
case "dangerTextButton": {
|
|
265
|
+
try {
|
|
266
|
+
view.setBackground(null);
|
|
267
|
+
}
|
|
268
|
+
catch (_e) { }
|
|
269
|
+
if (view.setAllCaps)
|
|
270
|
+
view.setAllCaps(false);
|
|
271
|
+
if (view.setTextColor)
|
|
272
|
+
view.setTextColor(theme.colors.danger ?? theme.colors.accent);
|
|
273
|
+
if (view.setTextSize)
|
|
274
|
+
view.setTextSize(2, theme.textSp.body);
|
|
275
|
+
view.setPadding(dpx(8), dpx(6), dpx(8), dpx(6));
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
case "divider": {
|
|
279
|
+
view.setBackgroundColor(theme.colors.divider);
|
|
280
|
+
const lp = view.getLayoutParams?.();
|
|
281
|
+
if (lp) {
|
|
282
|
+
lp.height = dpx(1);
|
|
283
|
+
view.setLayoutParams(lp);
|
|
284
|
+
}
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
case "chip": {
|
|
288
|
+
const bg = GradientDrawable.$new();
|
|
289
|
+
bg.setCornerRadius(dpx(999));
|
|
290
|
+
bg.setColor(theme.colors.chipBg ?? theme.colors.rowBg);
|
|
291
|
+
bg.setStroke(dpx(1), theme.colors.accentStroke ?? theme.colors.controlStroke);
|
|
292
|
+
view.setBackgroundDrawable(bg);
|
|
293
|
+
view.setPadding(dpx(10), dpx(4), dpx(10), dpx(4));
|
|
294
|
+
if (view.setTextColor)
|
|
295
|
+
view.setTextColor(theme.colors.subText);
|
|
296
|
+
if (view.setTextSize)
|
|
297
|
+
view.setTextSize(2, theme.textSp.caption ?? theme.textSp.body);
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
case "dialog": {
|
|
301
|
+
const bg = GradientDrawable.$new();
|
|
302
|
+
bg.setCornerRadius(dpx(14));
|
|
303
|
+
bg.setColor(theme.colors.cardBg);
|
|
304
|
+
bg.setStroke(dpx(1), theme.colors.accentStroke ?? theme.colors.controlStroke);
|
|
305
|
+
view.setBackgroundDrawable(bg);
|
|
306
|
+
view.setPadding(dpx(12), dpx(12), dpx(12), dpx(12));
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
case "inputField": {
|
|
310
|
+
const bg = GradientDrawable.$new();
|
|
311
|
+
bg.setCornerRadius(dpx(theme.radiusDp.control));
|
|
312
|
+
bg.setColor(theme.colors.inputBg ?? theme.colors.controlBg);
|
|
313
|
+
bg.setStroke(dpx(1), theme.colors.accentStroke ?? theme.colors.controlStroke);
|
|
314
|
+
view.setBackgroundDrawable(bg);
|
|
315
|
+
view.setPadding(dpx(12), dpx(10), dpx(12), dpx(10));
|
|
316
|
+
if (view.setTextColor)
|
|
317
|
+
view.setTextColor(theme.colors.text);
|
|
318
|
+
if (view.setHintTextColor)
|
|
319
|
+
view.setHintTextColor(theme.colors.subText ?? 0xff888888);
|
|
320
|
+
if (view.setTextSize)
|
|
321
|
+
view.setTextSize(2, theme.textSp.body);
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type Theme = {
|
|
2
|
+
colors: {
|
|
3
|
+
overlayBg: number;
|
|
4
|
+
cardBg: number;
|
|
5
|
+
text: number;
|
|
6
|
+
subText: number;
|
|
7
|
+
accent: number;
|
|
8
|
+
danger: number;
|
|
9
|
+
divider: number;
|
|
10
|
+
controlBg: number;
|
|
11
|
+
controlStroke: number;
|
|
12
|
+
accentSoft?: number;
|
|
13
|
+
accentStroke?: number;
|
|
14
|
+
rowBg?: number;
|
|
15
|
+
inputBg?: number;
|
|
16
|
+
chipBg?: number;
|
|
17
|
+
infoCardBg?: number;
|
|
18
|
+
noteCardBg?: number;
|
|
19
|
+
sectionText?: number;
|
|
20
|
+
buttonText?: number;
|
|
21
|
+
};
|
|
22
|
+
radiusDp: {
|
|
23
|
+
overlay: number;
|
|
24
|
+
card: number;
|
|
25
|
+
control: number;
|
|
26
|
+
};
|
|
27
|
+
textSp: {
|
|
28
|
+
title: number;
|
|
29
|
+
body: number;
|
|
30
|
+
caption: number;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export declare const DarkNeonTheme: Theme;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const DarkNeonTheme = {
|
|
2
|
+
colors: {
|
|
3
|
+
overlayBg: 0xE60a1426 | 0,
|
|
4
|
+
cardBg: 0xee101b31 | 0,
|
|
5
|
+
text: 0xfff3f6ff | 0,
|
|
6
|
+
subText: 0xff8fa5c7 | 0,
|
|
7
|
+
accent: 0xff0f6cff | 0,
|
|
8
|
+
danger: 0xffef4444 | 0,
|
|
9
|
+
divider: 0x263b5f93 | 0,
|
|
10
|
+
controlBg: 0x44111d33 | 0,
|
|
11
|
+
controlStroke: 0x2e4d79b8 | 0,
|
|
12
|
+
accentSoft: 0x1a0f6cff | 0,
|
|
13
|
+
accentStroke: 0xaa1f63ff | 0,
|
|
14
|
+
rowBg: 0x70111a2d | 0,
|
|
15
|
+
inputBg: 0x66101a2d | 0,
|
|
16
|
+
chipBg: 0x40111827 | 0,
|
|
17
|
+
infoCardBg: 0xcc102347 | 0,
|
|
18
|
+
noteCardBg: 0xd0122344 | 0,
|
|
19
|
+
sectionText: 0xffb4c3dc | 0,
|
|
20
|
+
buttonText: 0xffffffff | 0,
|
|
21
|
+
},
|
|
22
|
+
radiusDp: { overlay: 18, card: 14, control: 12 },
|
|
23
|
+
textSp: { title: 15, body: 13, caption: 12 },
|
|
24
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { UIComponent } from "./ui-components.js";
|
|
2
|
+
export declare class Switch extends UIComponent {
|
|
3
|
+
private label;
|
|
4
|
+
private handler?;
|
|
5
|
+
private labelView;
|
|
6
|
+
private switchView;
|
|
7
|
+
onValueChange(handler: (value: boolean) => void): void;
|
|
8
|
+
constructor(id: string, label: string, initialValue?: boolean, handler?: (value: boolean) => void);
|
|
9
|
+
protected createView(context: any): void;
|
|
10
|
+
protected updateView(): void;
|
|
11
|
+
setLabel(label: string): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
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
|
+
import { deferSafe } from "../runtime/safe-runtime.js";
|
|
6
|
+
import { createJavaListener } from "../android-listener.js";
|
|
7
|
+
export class Switch extends UIComponent {
|
|
8
|
+
onValueChange(handler) {
|
|
9
|
+
this.handler = handler;
|
|
10
|
+
}
|
|
11
|
+
constructor(id, label, initialValue = false, handler) {
|
|
12
|
+
super(id);
|
|
13
|
+
this.label = label;
|
|
14
|
+
this.value = initialValue;
|
|
15
|
+
this.handler = handler;
|
|
16
|
+
}
|
|
17
|
+
createView(context) {
|
|
18
|
+
const LinearLayout = API.LinearLayout;
|
|
19
|
+
const LinearLayoutParams = API.LinearLayoutParams;
|
|
20
|
+
const ViewGroupLayoutParams = API.ViewGroupLayoutParams;
|
|
21
|
+
const Gravity = API.Gravity;
|
|
22
|
+
const TextView = API.TextView;
|
|
23
|
+
const Switch = API.Switch;
|
|
24
|
+
const String = API.JString;
|
|
25
|
+
const OnClickListener = API.OnClickListener;
|
|
26
|
+
const OnCheckedChangeListener = API.OnCheckedChangeListener;
|
|
27
|
+
const theme = this.menu.options.theme;
|
|
28
|
+
const row = LinearLayout.$new(context);
|
|
29
|
+
row.setOrientation(LinearLayout.HORIZONTAL.value);
|
|
30
|
+
row.setGravity(Gravity.CENTER_VERTICAL.value);
|
|
31
|
+
applyStyle(row, "row", theme);
|
|
32
|
+
row.setPadding(dp(context, 14), dp(context, 12), dp(context, 14), dp(context, 12));
|
|
33
|
+
const label = TextView.$new(context);
|
|
34
|
+
label.setText.overload('java.lang.CharSequence').call(label, this.label);
|
|
35
|
+
applyStyle(label, "text", theme);
|
|
36
|
+
label.setTextSize(2, 14);
|
|
37
|
+
try {
|
|
38
|
+
label.setTypeface(null, 1);
|
|
39
|
+
}
|
|
40
|
+
catch (_e) { }
|
|
41
|
+
label.setSingleLine(true);
|
|
42
|
+
label.setGravity(Gravity.LEFT.value | Gravity.CENTER_VERTICAL.value);
|
|
43
|
+
label.setLayoutParams(LinearLayoutParams.$new(0, ViewGroupLayoutParams.WRAP_CONTENT.value, 1.0));
|
|
44
|
+
const sw = Switch.$new(context);
|
|
45
|
+
sw.setText.overload('java.lang.CharSequence').call(sw, "");
|
|
46
|
+
sw.setChecked(!!this.value);
|
|
47
|
+
sw.setShowText(false);
|
|
48
|
+
try {
|
|
49
|
+
sw.setMinWidth(dp(context, 50));
|
|
50
|
+
sw.setMinimumWidth(dp(context, 50));
|
|
51
|
+
}
|
|
52
|
+
catch (_e) { }
|
|
53
|
+
try {
|
|
54
|
+
sw.setSwitchMinWidth(dp(context, 50));
|
|
55
|
+
}
|
|
56
|
+
catch (_e) { }
|
|
57
|
+
try {
|
|
58
|
+
sw.setScaleX(1.05);
|
|
59
|
+
sw.setScaleY(1.05);
|
|
60
|
+
}
|
|
61
|
+
catch (_e) { }
|
|
62
|
+
const switchLp = LinearLayoutParams.$new(ViewGroupLayoutParams.WRAP_CONTENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value);
|
|
63
|
+
switchLp.leftMargin = dp(context, 12);
|
|
64
|
+
sw.setLayoutParams(switchLp);
|
|
65
|
+
const self = this;
|
|
66
|
+
const checkedListener = createJavaListener({
|
|
67
|
+
key: "compound-button-checked-change",
|
|
68
|
+
interfaceClass: OnCheckedChangeListener,
|
|
69
|
+
callbacks: {
|
|
70
|
+
onCheckedChanged: (_buttonView, isChecked) => {
|
|
71
|
+
self.value = !!isChecked;
|
|
72
|
+
self.emit("valueChanged", self.value);
|
|
73
|
+
if (self.handler) {
|
|
74
|
+
deferSafe(`Switch:${self.id}:valueChanged`, () => self.handler(self.value));
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
this.own(() => checkedListener.dispose());
|
|
80
|
+
sw.setOnCheckedChangeListener(checkedListener.instance);
|
|
81
|
+
const clickListener = createJavaListener({
|
|
82
|
+
key: "view-click",
|
|
83
|
+
interfaceClass: OnClickListener,
|
|
84
|
+
callbacks: {
|
|
85
|
+
onClick: (_v) => {
|
|
86
|
+
if (!self.switchView)
|
|
87
|
+
return;
|
|
88
|
+
self.switchView.setChecked(!self.switchView.isChecked());
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
this.own(() => clickListener.dispose());
|
|
93
|
+
row.setClickable(true);
|
|
94
|
+
row.setOnClickListener(clickListener.instance);
|
|
95
|
+
row.addView(label);
|
|
96
|
+
row.addView(sw);
|
|
97
|
+
this.labelView = label;
|
|
98
|
+
this.switchView = sw;
|
|
99
|
+
this.view = row;
|
|
100
|
+
}
|
|
101
|
+
updateView() {
|
|
102
|
+
if (!this.switchView)
|
|
103
|
+
return;
|
|
104
|
+
Java.scheduleOnMainThread(() => {
|
|
105
|
+
this.switchView.setChecked(!!this.value);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
setLabel(label) {
|
|
109
|
+
this.label = label;
|
|
110
|
+
if (!this.labelView)
|
|
111
|
+
return;
|
|
112
|
+
Java.scheduleOnMainThread(() => {
|
|
113
|
+
const String = API.JString;
|
|
114
|
+
this.labelView.setText.overload('java.lang.CharSequence').call(this.labelView, String.$new(label));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { UIComponent } from "./ui-components.js";
|
|
2
|
+
export declare class TextView extends UIComponent {
|
|
3
|
+
private size?;
|
|
4
|
+
private kind;
|
|
5
|
+
constructor(id: string, content: string, kind?: "normal" | "note", size?: number);
|
|
6
|
+
protected createView(context: any): void;
|
|
7
|
+
protected updateView(): void;
|
|
8
|
+
setText(content: string): void;
|
|
9
|
+
}
|