Skip to content

Commit c35a568

Browse files
committed
feat: support user added icon
1 parent 2f00ed3 commit c35a568

File tree

6 files changed

+153
-23
lines changed

6 files changed

+153
-23
lines changed

AppScope/app.json5

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"app": {
33
"bundleName": "smartcityshenzhen.yylx.totptoken",
4-
"vendor": "example",
5-
"versionCode": 1000005,
6-
"versionName": "1.0.5",
4+
"vendor": "opensource",
5+
"versionCode": 1000006,
6+
"versionName": "1.0.6",
77
"icon": "$media:app_icon",
88
"label": "$string:app_name"
99
}

entry/src/main/ets/dialogs/SelectIconDialog.ets

Lines changed: 102 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { common } from '@kit.AbilityKit';
21
import { TokenIcon } from "../components/TokenIcon";
2+
import { showSelectFilePicker } from "../utils/FileUtils";
33
import { AegisIconPack, TokenIconPacks } from "../utils/TokenUtils";
4+
import { fileIo } from "@kit.CoreFileKit";
45

56
@Preview
67
@CustomDialog
@@ -13,12 +14,12 @@ export struct SelectIconDialog {
1314
@State selected_icon_path: string = '';
1415

1516
@State icon_matched_with_issuer: string[] = [''];
16-
@State default_icon_pack_visable: boolean = true;
17-
@State aegis_icon_pack_visable: boolean[] = [];
17+
@State default_icon_pack_visible: boolean = true;
18+
@State aegis_icon_pack_visible: boolean[] = [];
1819

1920
private default_icon_data: MyDataSource = new MyDataSource();
21+
private user_icon_data: MyDataSource = new MyDataSource();
2022
private aegis_icon_data: MyDataSource[] = [];
21-
private appCtx = AppStorage.get<common.UIAbilityContext>('appContext') as common.UIAbilityContext;
2223

2324
aboutToAppear(): void {
2425
// find issuer's icon
@@ -28,7 +29,7 @@ export struct SelectIconDialog {
2829
}
2930
TokenIconPacks.aegis_icon_packs.forEach((aegis_icon_pack) => {
3031
const aegis_icon = aegis_icon_pack.getIconPathByIssuer(this.issuer);
31-
this.aegis_icon_pack_visable.push(false);
32+
this.aegis_icon_pack_visible.push(false);
3233
if (aegis_icon !== '') {
3334
this.icon_matched_with_issuer.push(aegis_icon);
3435
}
@@ -38,6 +39,11 @@ export struct SelectIconDialog {
3839
TokenIconPacks.default_icon_pack.default_icon_pack_path.forEach((path) => {
3940
this.default_icon_data.pushData(path!);
4041
});
42+
43+
TokenIconPacks.user_added_icon_packs.icon_path.forEach((path) => {
44+
this.user_icon_data.pushData(path!);
45+
});
46+
4147
TokenIconPacks.aegis_icon_packs.forEach((aegis_icon_pack) => {
4248
let data_source = new MyDataSource();
4349
for (let icon of aegis_icon_pack.icons) {
@@ -58,25 +64,88 @@ export struct SelectIconDialog {
5864
}
5965

6066
@Builder
61-
ItemIcon(path: string, visable: boolean) {
67+
ItemIcon(path: string, visible: boolean, removable: boolean) {
6268
ListItem() {
63-
Stack() {
69+
Stack( { alignContent: Alignment.TopStart } ) {
6470
if (path === '') {
6571
Text($r('app.string.dialog_icon_default'))
72+
.margin({ top: 15 })
6673
} else {
6774
TokenIcon({ issuer: '', icon_path: path, bypassColorFilter: true })
6875
}
76+
77+
Button({ type: ButtonType.Circle }) {
78+
SymbolGlyph($r('sys.symbol.plus'))
79+
.fontSize(15)
80+
.fontColor([Color.White])
81+
.fontWeight(FontWeight.Medium)
82+
.rotate({ angle: 45 })
83+
}
84+
.backgroundColor(Color.Red)
85+
.width(20)
86+
.height(20)
87+
.margin({ left: 0, top: 0 })
88+
.visibility(removable ? Visibility.Visible : Visibility.Hidden)
89+
.onClick(() => {
90+
AlertDialog.show({
91+
message: $r('app.string.icon_alert_remove_confirm_msg'),
92+
autoCancel: true,
93+
alignment: DialogAlignment.Center,
94+
primaryButton: {
95+
defaultFocus: false,
96+
value: $r('app.string.dialog_btn_cancel'),
97+
action: () => {
98+
return
99+
}
100+
},
101+
secondaryButton: {
102+
value: $r('app.string.dialog_btn_confirm'),
103+
fontColor: Color.Red,
104+
action: () => {
105+
TokenIconPacks.user_added_icon_packs.removeIcon(path);
106+
this.user_icon_data.removeData(path);
107+
}
108+
}
109+
})
110+
})
111+
69112
Radio({ value: '', group: 'icon' })
70113
.width(20)
71114
.height(20)
72-
.margin({ left:30, top: 30 })
115+
.margin({ left: 20, top: 20 })
73116
.onChange((checked) => {
74117
if (checked) {
75118
this.selected_icon_path = path;
76119
}
77120
})
78121
}
79-
.visibility(visable ? Visibility.Visible : Visibility.Hidden)
122+
.visibility(visible ? Visibility.Visible : Visibility.Hidden)
123+
}
124+
.margin({ left: 10 })
125+
}
126+
127+
@Builder
128+
ItemIconAddButton() {
129+
ListItem() {
130+
Button({ type: ButtonType.Circle }) {
131+
SymbolGlyph($r('sys.symbol.plus'))
132+
.fontSize(30)
133+
.fontWeight(FontWeight.Bold)
134+
.fontColor([Color.Gray])
135+
}
136+
.backgroundColor(Color.Transparent)
137+
.shadow({ radius: 10, color: Color.Gray })
138+
.width(40)
139+
.height(40)
140+
.onClick(() => {
141+
showSelectFilePicker(1, ["PNG|.png", "JPEG|.jpg"]).then((uris) => {
142+
fileIo.open(uris[0]).then((file) => {
143+
TokenIconPacks.user_added_icon_packs.addIcon(file);
144+
this.user_icon_data.pushData(TokenIconPacks.user_added_icon_packs.latest_icon_path);
145+
});
146+
147+
});
148+
})
80149
}
81150
.margin({ left: 10 })
82151
}
@@ -86,25 +155,34 @@ export struct SelectIconDialog {
86155
List({ space: 10 }) {
87156
ListItemGroup({ header: this.itemHead($r('app.string.dialog_icon_recommend')) }) {
88157
ForEach(this.icon_matched_with_issuer, (icon_path: string) => {
89-
this.ItemIcon(icon_path, true)
158+
this.ItemIcon(icon_path, true, false)
159+
})
160+
}
161+
ListItemGroup({ header: this.itemHead(TokenIconPacks.user_added_icon_packs.name) }) {
162+
LazyForEach(this.user_icon_data, (path: string) => {
163+
this.ItemIcon(path, true, true)
90164
})
165+
this.ItemIconAddButton()
91166
}
167+
.onClick(() => {
168+
this.default_icon_pack_visible = !this.default_icon_pack_visible;
169+
})
92170
ListItemGroup({ header: this.itemHead(TokenIconPacks.default_icon_pack.name) }) {
93171
LazyForEach(this.default_icon_data, (path: string) => {
94-
this.ItemIcon(path, true)
172+
this.ItemIcon(path, true, false)
95173
})
96174
}
97175
.onClick(() => {
98-
this.default_icon_pack_visable = !this.default_icon_pack_visable;
176+
this.default_icon_pack_visible = !this.default_icon_pack_visible;
99177
})
100178
ForEach(TokenIconPacks.aegis_icon_packs, (aegis_icon_pack: AegisIconPack, index: number) => {
101179
ListItemGroup({ header: this.itemHead(aegis_icon_pack.name) }) {
102180
LazyForEach(this.aegis_icon_data[index], (path: string) => {
103-
this.ItemIcon(path, true)
181+
this.ItemIcon(path, true, false)
104182
})
105183
}
106184
.onClick(() => {
107-
this.aegis_icon_pack_visable[index] = !this.aegis_icon_pack_visable[index];
185+
this.aegis_icon_pack_visible[index] = !this.aegis_icon_pack_visible[index];
108186
})
109187
}, (aegis_icon_pack: AegisIconPack) => {
110188
return aegis_icon_pack.on_device_path;
@@ -221,7 +299,6 @@ class BasicDataSource implements IDataSource {
221299
}
222300
}
223301

224-
225302
class MyDataSource extends BasicDataSource {
226303
private dataArray: string[] = [];
227304

@@ -234,7 +311,15 @@ class MyDataSource extends BasicDataSource {
234311
}
235312

236313
public pushData(data: string): void {
237-
this.dataArray.push(data);
238-
this.notifyDataAdd(this.dataArray.length - 1);
314+
if (this.dataArray.findIndex(_ => _ === data) < 0) {
315+
this.dataArray.push(data);
316+
this.notifyDataAdd(this.dataArray.length - 1);
317+
}
318+
}
319+
320+
public removeData(data: string): void {
321+
const index = this.dataArray.findIndex(_ => _ === data);
322+
this.dataArray = this.dataArray.filter(_ => _ !== data);
323+
this.notifyDataDelete(index);
239324
}
240325
}

entry/src/main/ets/pages/Index.ets

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ struct Index {
4545
private appTopAvoidHeight = AppStorage.get("appTopAvoidHeight") as number;
4646

4747
private icon_pack_dir = this.appCtx.cacheDir + "/icon_packs";
48+
private user_icon_dir = this.appCtx.cacheDir + "/user_icon";
4849

4950
private dialog_uri_config?: CustomDialogController;
5051
private dialog_totp_config?: CustomDialogController;
@@ -221,6 +222,7 @@ struct Index {
221222
//endregion
222223

223224
refreshIconPack(): void {
225+
TokenIconPacks.addUserIconPack(this.user_icon_dir);
224226
fs.listFile(this.icon_pack_dir).then((paths) => {
225227
paths.forEach((path) => {
226228
TokenIconPacks.addAegisIconPack(this.icon_pack_dir + "/" + path);

entry/src/main/ets/utils/TokenUtils.ets

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@ import { TokenConfig } from './TokenConfig';
55
import { scanBarcode, scanCore } from '@kit.ScanKit';
66
import { pasteboard } from '@kit.BasicServicesKit';
77
import { promptAction } from '@kit.ArkUI';
8-
import { image } from '@kit.ImageKit';
8+
import { fileIo as fs } from '@kit.CoreFileKit';
99
import { readFileContent } from './FileUtils';
1010
import { fileIo } from '@kit.CoreFileKit';
11-
import { resourceManager } from '@kit.LocalizationKit';
1211
import { AppPreference } from './AppPreference';
13-
import { TokenIcon } from '../components/TokenIcon';
1412

1513
const RFC4648 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
1614
const RFC4648_HEX = '0123456789ABCDEFGHIJKLMNOPQRSTUV';
@@ -268,11 +266,48 @@ export class OTPDefaultIconPack {
268266
}
269267
}
270268

269+
export class UserAddedIconPack {
270+
public name: string = "User Added";
271+
public user_icon_path: string = '';
272+
public icon_path: string[] = [];
273+
public latest_icon_path: string = '';
274+
275+
public initIconPack(path: string): void {
276+
this.user_icon_path = path;
277+
if (fs.accessSync(path) === false) {
278+
fs.mkdirSync(path);
279+
}
280+
fs.listFile(path).then((files) => {
281+
files.forEach((file) => {
282+
this.icon_path.push("file://" + this.user_icon_path + "/" + file);
283+
})
284+
});
285+
}
286+
287+
public addIcon(file: fileIo.File): void {
288+
this.latest_icon_path = "file://" + this.user_icon_path + "/" + file.name;
289+
this.icon_path.push(this.latest_icon_path);
290+
fs.copyFile(file.fd, this.user_icon_path + "/" + file.name);
291+
}
292+
293+
public removeIcon(path: string): void {
294+
this.icon_path = this.icon_path.filter(_ => _ !== path);
295+
fs.unlink(path.slice(7));
296+
}
297+
298+
public constructor() {}
299+
}
300+
271301
export class TokenIconPacks {
272302
public static default_icon_pack: OTPDefaultIconPack = new OTPDefaultIconPack();
273303
public static aegis_icon_packs: AegisIconPack[] = [];
304+
public static user_added_icon_packs: UserAddedIconPack = new UserAddedIconPack();
274305
public static selected_icon_pack: number = AppPreference.getAppearance('app_appearance_icon_pack_selected') as number ?? 0;
275306

307+
public static async addUserIconPack(path: string): Promise<void> {
308+
TokenIconPacks.user_added_icon_packs.initIconPack(path);
309+
}
310+
276311
public static async addAegisIconPack(path: string): Promise<void> {
277312
const icon_path_json = path + "/pack.json";
278313
if (!fileIo.accessSync(icon_path_json)) {

entry/src/main/resources/base/element/string.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,10 @@
379379
{
380380
"name": "dialog_icon_default",
381381
"value": "Default"
382+
},
383+
{
384+
"name": "icon_alert_remove_confirm_msg",
385+
"value": "Delete this icon?"
382386
}
383387
]
384388
}

entry/src/main/resources/zh_CN/element/string.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,10 @@
379379
{
380380
"name": "dialog_icon_default",
381381
"value": "默认"
382+
},
383+
{
384+
"name": "icon_alert_remove_confirm_msg",
385+
"value": "是否删除这个图标?"
382386
}
383387
]
384388
}

0 commit comments

Comments
 (0)