From f4ad8da8a1a2cbeb6dce2015072a42f990f9e204 Mon Sep 17 00:00:00 2001 From: DASHU <385321165@qq.com> Date: Thu, 24 Jul 2025 20:09:55 +0800 Subject: [PATCH] add web apps --- lib/component/webview/web_home_component.dart | 9 + lib/const/base.dart | 2 + lib/const/router_path.dart | 1 + lib/generated/intl/messages_en.dart | 10 + lib/generated/intl/messages_zh.dart | 10 + lib/generated/l10n.dart | 100 ++++++ lib/l10n/intl_en.arb | 12 +- lib/l10n/intl_zh.arb | 12 +- lib/main.dart | 2 + lib/router/web_apps/web_app_item.dart | 23 ++ .../web_apps/web_app_item_component.dart | 98 ++++++ lib/router/web_apps/web_app_types.dart | 10 + lib/router/web_apps/web_apps_router.dart | 296 ++++++++++++++++++ 13 files changed, 583 insertions(+), 2 deletions(-) create mode 100644 lib/router/web_apps/web_app_item.dart create mode 100644 lib/router/web_apps/web_app_item_component.dart create mode 100644 lib/router/web_apps/web_app_types.dart create mode 100644 lib/router/web_apps/web_apps_router.dart diff --git a/lib/component/webview/web_home_component.dart b/lib/component/webview/web_home_component.dart index 24cfc74..d105a3e 100644 --- a/lib/component/webview/web_home_component.dart +++ b/lib/component/webview/web_home_component.dart @@ -196,6 +196,15 @@ class _WebHomeComponent extends State { main = WebHomeBtnComponent(bookmark!); } else { if (bookmarks.length == index) { + main = GestureDetector( + onTap: () { + RouterUtil.router(context, RouterPath.WEB_APPS); + }, + child: Container( + child: Icon(Icons.apps), + ), + ); + } else if (bookmarks.length + 1 == index) { main = GestureDetector( onTap: () { BookmarkEditDialog.show( diff --git a/lib/const/base.dart b/lib/const/base.dart index 6e7b584..507a08a 100644 --- a/lib/const/base.dart +++ b/lib/const/base.dart @@ -13,4 +13,6 @@ class Base { static String USER_AGENT = "${Base.APP_NAME} ${PlatformUtil.getPlatformName()} ${Base.VERSION_NAME}"; + + static String WEB_APPS = "https://nowser.nostrmo.com/jsons/webapps.json"; } diff --git a/lib/const/router_path.dart b/lib/const/router_path.dart index c8d5789..de2fb79 100644 --- a/lib/const/router_path.dart +++ b/lib/const/router_path.dart @@ -5,6 +5,7 @@ class RouterPath { static const String ME = "/me"; static const String KEYS = "/keys"; static const String APPS = "/apps"; + static const String WEB_APPS = "/webApps"; static const String ADD_REMOTE_APP = "/addRemoteApp"; static const String APP_DETAIL = "/appDetail"; static const String HISTORY = "/history"; diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index d191553..3685070 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -30,6 +30,7 @@ class MessageLookup extends MessageLookupByLibrary { "Add_to_index": MessageLookupByLibrary.simpleMessage("Add to index"), "Add_to_quick_action": MessageLookupByLibrary.simpleMessage("Add to quick action"), + "All": MessageLookupByLibrary.simpleMessage("All"), "Allow": MessageLookupByLibrary.simpleMessage("Allow"), "Alway_reject": MessageLookupByLibrary.simpleMessage("Alway reject"), "Always": MessageLookupByLibrary.simpleMessage("Always"), @@ -89,6 +90,7 @@ class MessageLookup extends MessageLookupByLibrary { "Get_Public_Key": MessageLookupByLibrary.simpleMessage("Get Public Key"), "Get_Relays": MessageLookupByLibrary.simpleMessage("Get Relays"), + "Group_Chat": MessageLookupByLibrary.simpleMessage("Group Chat"), "Historys": MessageLookupByLibrary.simpleMessage("Historys"), "Incognito": MessageLookupByLibrary.simpleMessage("Incognito"), "Input_can_not_be_null": @@ -103,7 +105,10 @@ class MessageLookup extends MessageLookupByLibrary { "Login_fail": MessageLookupByLibrary.simpleMessage("Login_fail"), "Login_with_Nesigner": MessageLookupByLibrary.simpleMessage("Login with Nesigner"), + "Long_Form": MessageLookupByLibrary.simpleMessage("Long Form"), + "Marketplaces": MessageLookupByLibrary.simpleMessage("Marketplaces"), "Name": MessageLookupByLibrary.simpleMessage("Name"), + "Notes": MessageLookupByLibrary.simpleMessage("Notes"), "Open_backgroundly": MessageLookupByLibrary.simpleMessage("Open backgroundly"), "Open_image_in_a_New_Tab": @@ -114,6 +119,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Open with Incognito Mode"), "Pendding_connect_remote_apps": MessageLookupByLibrary.simpleMessage( "Pendding connect remote apps"), + "Photos": MessageLookupByLibrary.simpleMessage("Photos"), "Privacy": MessageLookupByLibrary.simpleMessage("Privacy"), "Pubkey": MessageLookupByLibrary.simpleMessage("Pubkey"), "Reasonable": MessageLookupByLibrary.simpleMessage("Reasonable"), @@ -134,9 +140,13 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Show more logs"), "Sign_Event": MessageLookupByLibrary.simpleMessage("Sign Event"), "Stars": MessageLookupByLibrary.simpleMessage("Stars"), + "Streaming": MessageLookupByLibrary.simpleMessage("Streaming"), "ThemeStyle": MessageLookupByLibrary.simpleMessage("ThemeStyle"), + "Tools": MessageLookupByLibrary.simpleMessage("Tools"), "Url": MessageLookupByLibrary.simpleMessage("Url"), "WEB": MessageLookupByLibrary.simpleMessage("WEB"), + "Web_APPs": MessageLookupByLibrary.simpleMessage("Web APPs"), + "Zaps": MessageLookupByLibrary.simpleMessage("Zaps"), "a": MessageLookupByLibrary.simpleMessage("a"), "auto": MessageLookupByLibrary.simpleMessage("auto"), "detail": MessageLookupByLibrary.simpleMessage("detail"), diff --git a/lib/generated/intl/messages_zh.dart b/lib/generated/intl/messages_zh.dart index 08ea624..e448b0a 100644 --- a/lib/generated/intl/messages_zh.dart +++ b/lib/generated/intl/messages_zh.dart @@ -28,6 +28,7 @@ class MessageLookup extends MessageLookupByLibrary { "Add_bookmark": MessageLookupByLibrary.simpleMessage("添加书签"), "Add_to_index": MessageLookupByLibrary.simpleMessage("添加到首页"), "Add_to_quick_action": MessageLookupByLibrary.simpleMessage("添加到快捷方式"), + "All": MessageLookupByLibrary.simpleMessage("全部"), "Allow": MessageLookupByLibrary.simpleMessage("允许"), "Alway_reject": MessageLookupByLibrary.simpleMessage("总是拒绝"), "Always": MessageLookupByLibrary.simpleMessage("总是"), @@ -77,6 +78,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("生成一个秘钥"), "Get_Public_Key": MessageLookupByLibrary.simpleMessage("获取公钥"), "Get_Relays": MessageLookupByLibrary.simpleMessage("获取中继"), + "Group_Chat": MessageLookupByLibrary.simpleMessage("群组聊天"), "Historys": MessageLookupByLibrary.simpleMessage("历史"), "Incognito": MessageLookupByLibrary.simpleMessage("无痕"), "Input_can_not_be_null": MessageLookupByLibrary.simpleMessage("输入不能为空"), @@ -89,7 +91,10 @@ class MessageLookup extends MessageLookupByLibrary { "Login_fail": MessageLookupByLibrary.simpleMessage("登录失败"), "Login_with_Nesigner": MessageLookupByLibrary.simpleMessage("使用 Nesigner 登录"), + "Long_Form": MessageLookupByLibrary.simpleMessage("长文本"), + "Marketplaces": MessageLookupByLibrary.simpleMessage("市场"), "Name": MessageLookupByLibrary.simpleMessage("名称"), + "Notes": MessageLookupByLibrary.simpleMessage("笔记"), "Open_backgroundly": MessageLookupByLibrary.simpleMessage("后台打开"), "Open_image_in_a_New_Tab": MessageLookupByLibrary.simpleMessage("新标签打开图片"), @@ -98,6 +103,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("无痕模式打开"), "Pendding_connect_remote_apps": MessageLookupByLibrary.simpleMessage("待链接的远程应用"), + "Photos": MessageLookupByLibrary.simpleMessage("照片"), "Privacy": MessageLookupByLibrary.simpleMessage("隐私"), "Pubkey": MessageLookupByLibrary.simpleMessage("公钥"), "Reasonable": MessageLookupByLibrary.simpleMessage("合理的授权"), @@ -114,9 +120,13 @@ class MessageLookup extends MessageLookupByLibrary { "Show_more_logs": MessageLookupByLibrary.simpleMessage("显示更多日志"), "Sign_Event": MessageLookupByLibrary.simpleMessage("签名事件"), "Stars": MessageLookupByLibrary.simpleMessage("保存书签"), + "Streaming": MessageLookupByLibrary.simpleMessage("直播"), "ThemeStyle": MessageLookupByLibrary.simpleMessage("主题类型"), + "Tools": MessageLookupByLibrary.simpleMessage("工具"), "Url": MessageLookupByLibrary.simpleMessage("链接"), "WEB": MessageLookupByLibrary.simpleMessage("网页"), + "Web_APPs": MessageLookupByLibrary.simpleMessage("网页应用"), + "Zaps": MessageLookupByLibrary.simpleMessage("Zaps"), "a": MessageLookupByLibrary.simpleMessage("一个"), "auto": MessageLookupByLibrary.simpleMessage("自动"), "detail": MessageLookupByLibrary.simpleMessage("详情"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index d351858..8672719 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -1019,6 +1019,106 @@ class S { args: [], ); } + + /// `Notes` + String get Notes { + return Intl.message( + 'Notes', + name: 'Notes', + desc: '', + args: [], + ); + } + + /// `Long Form` + String get Long_Form { + return Intl.message( + 'Long Form', + name: 'Long_Form', + desc: '', + args: [], + ); + } + + /// `Group Chat` + String get Group_Chat { + return Intl.message( + 'Group Chat', + name: 'Group_Chat', + desc: '', + args: [], + ); + } + + /// `Tools` + String get Tools { + return Intl.message( + 'Tools', + name: 'Tools', + desc: '', + args: [], + ); + } + + /// `Photos` + String get Photos { + return Intl.message( + 'Photos', + name: 'Photos', + desc: '', + args: [], + ); + } + + /// `Streaming` + String get Streaming { + return Intl.message( + 'Streaming', + name: 'Streaming', + desc: '', + args: [], + ); + } + + /// `Zaps` + String get Zaps { + return Intl.message( + 'Zaps', + name: 'Zaps', + desc: '', + args: [], + ); + } + + /// `Marketplaces` + String get Marketplaces { + return Intl.message( + 'Marketplaces', + name: 'Marketplaces', + desc: '', + args: [], + ); + } + + /// `All` + String get All { + return Intl.message( + 'All', + name: 'All', + desc: '', + args: [], + ); + } + + /// `Web APPs` + String get Web_APPs { + return Intl.message( + 'Web APPs', + name: 'Web_APPs', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index d35a4f4..cd2439e 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -95,5 +95,15 @@ "or": "or", "Login_with_Nesigner": "Login with Nesigner", "Login_fail": "Login_fail", - "Delete": "Delete" + "Delete": "Delete", + "Notes": "Notes", + "Long_Form": "Long Form", + "Group_Chat": "Group Chat", + "Tools": "Tools", + "Photos": "Photos", + "Streaming": "Streaming", + "Zaps": "Zaps", + "Marketplaces": "Marketplaces", + "All": "All", + "Web_APPs": "Web APPs" } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 355a650..5d741d0 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -95,5 +95,15 @@ "or": "或者", "Login_with_Nesigner": "使用 Nesigner 登录", "Login_fail": "登录失败", - "Delete": "删除" + "Delete": "删除", + "Notes": "笔记", + "Long_Form": "长文本", + "Group_Chat": "群组聊天", + "Tools": "工具", + "Photos": "照片", + "Streaming": "直播", + "Zaps": "Zaps", + "Marketplaces": "市场", + "All": "全部", + "Web_APPs": "网页应用" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 8a23f94..5b73cc0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -36,6 +36,7 @@ import 'package:nowser/router/index/index_router.dart'; import 'package:nowser/router/keys/keys_router.dart'; import 'package:nowser/router/me/me_router.dart'; import 'package:nowser/router/setting/setting_router.dart'; +import 'package:nowser/router/web_apps/web_apps_router.dart'; import 'package:nowser/router/web_tabs_select/web_tabs_select_router.dart'; import 'package:nowser/router/web_url_input/web_url_input_router.dart'; import 'package:provider/provider.dart'; @@ -220,6 +221,7 @@ class _MyApp extends State { RouterPath.SETTING: (context) => SettingRouter(indexReload: reload), RouterPath.ABOUT_ME: (context) => AboutMeRouter(), RouterPath.DOWNLOADS: (context) => DownloadsRouter(), + RouterPath.WEB_APPS: (context) => WebAppsRouter(), }; SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( diff --git a/lib/router/web_apps/web_app_item.dart b/lib/router/web_apps/web_app_item.dart new file mode 100644 index 0000000..b51b153 --- /dev/null +++ b/lib/router/web_apps/web_app_item.dart @@ -0,0 +1,23 @@ +class WebAppItem { + String link; + + String name; + + String desc; + + String? image; + + List types; + + WebAppItem(this.link, this.name, this.desc, this.types, {this.image}); + + Map toJson() { + return { + "link": link, + "name": name, + "desc": desc, + "image": image, + "types": types, + }; + } +} diff --git a/lib/router/web_apps/web_app_item_component.dart b/lib/router/web_apps/web_app_item_component.dart new file mode 100644 index 0000000..e63fe9e --- /dev/null +++ b/lib/router/web_apps/web_app_item_component.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:nowser/router/web_apps/web_app_item.dart'; + +import '../../const/base.dart'; + +class WebAppItemComponent extends StatefulWidget { + WebAppItem item; + + Function(WebAppItem item)? onTap; + + WebAppItemComponent( + this.item, { + this.onTap, + }); + + @override + State createState() { + return _WebAppItemComponent(); + } +} + +class _WebAppItemComponent extends State { + double IMAGE_WIDTH = 74; + + @override + Widget build(BuildContext context) { + var themeData = Theme.of(context); + var titleFontSize = themeData.textTheme.bodyLarge!.fontSize; + + return GestureDetector( + onTap: () { + if (widget.onTap != null) { + widget.onTap!(widget.item); + } + }, + child: Container( + padding: const EdgeInsets.only( + left: Base.BASE_PADDING, + right: Base.BASE_PADDING, + bottom: Base.BASE_PADDING, + ), + child: Row( + children: [ + Container( + height: IMAGE_WIDTH, + width: IMAGE_WIDTH, + clipBehavior: Clip.hardEdge, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + ), + child: widget.item.image != null + ? Image.network( + widget.item.image!, + height: IMAGE_WIDTH, + width: IMAGE_WIDTH, + ) + : Icon( + Icons.public, + size: 70, + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(left: Base.BASE_PADDING), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text( + widget.item.name, + style: TextStyle( + fontSize: titleFontSize, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + child: Text( + widget.item.desc, + style: TextStyle( + color: themeData.hintColor, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/router/web_apps/web_app_types.dart b/lib/router/web_apps/web_app_types.dart new file mode 100644 index 0000000..5643d9b --- /dev/null +++ b/lib/router/web_apps/web_app_types.dart @@ -0,0 +1,10 @@ +class WebAppTypes { + static String NOTES = "notes"; + static String LONG_FORM = "longForm"; + static String GROUP_CHAT = "groupChat"; + static String TOOLS = "tools"; + static String PHOTOS = "photos"; + static String STREAMING = "streaming"; + static String ZAPS = "zaps"; + static String Marketplaces = "marketplaces"; +} diff --git a/lib/router/web_apps/web_apps_router.dart b/lib/router/web_apps/web_apps_router.dart new file mode 100644 index 0000000..b557b7a --- /dev/null +++ b/lib/router/web_apps/web_apps_router.dart @@ -0,0 +1,296 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:nostr_sdk/utils/platform_util.dart'; +import 'package:nostr_sdk/utils/string_util.dart'; +import 'package:nowser/component/cust_state.dart'; +import 'package:nowser/const/base.dart'; +import 'package:nowser/const/base_consts.dart'; +import 'package:nowser/main.dart'; +import 'package:nowser/router/web_apps/web_app_item_component.dart'; +import 'package:nowser/util/router_util.dart'; + +import '../../component/appbar_back_btn_component.dart'; +import '../../generated/l10n.dart'; +import '../../util/dio_util.dart'; +import 'web_app_item.dart'; +import 'web_app_types.dart'; + +class WebAppsRouter extends StatefulWidget { + @override + State createState() { + return WebAppsRouterState(); + } +} + +class WebAppsRouterState extends CustState { + late S s; + + List items = []; + + Map selectedMap = {}; + + List typeEnums = []; + + @override + Widget doBuild(BuildContext context) { + s = S.of(context); + var themeData = Theme.of(context); + + if (typeEnums.isEmpty) { + typeEnums.add(EnumObj(WebAppTypes.NOTES, s.Notes)); + typeEnums.add(EnumObj(WebAppTypes.LONG_FORM, s.Long_Form)); + typeEnums.add(EnumObj(WebAppTypes.GROUP_CHAT, s.Group_Chat)); + typeEnums.add(EnumObj(WebAppTypes.TOOLS, s.Tools)); + typeEnums.add(EnumObj(WebAppTypes.PHOTOS, s.Photos)); + typeEnums.add(EnumObj(WebAppTypes.STREAMING, s.Streaming)); + typeEnums.add(EnumObj(WebAppTypes.ZAPS, s.Zaps)); + typeEnums.add(EnumObj(WebAppTypes.Marketplaces, s.Marketplaces)); + } + + List list = []; + + List typeWidgetList = []; + typeWidgetList + .add(buildTypeWidget(EnumObj("all", s.All), selectedMap.isEmpty, () { + if (selectedMap.isNotEmpty) { + setState(() { + selectedMap.clear(); + }); + } + })); + for (var typeEnum in typeEnums) { + var selected = selectedMap[typeEnum.value] != null; + typeWidgetList.add(buildTypeWidget(typeEnum, selected, () { + if (selected) { + setState(() { + selectedMap.remove(typeEnum.value); + }); + } else { + setState(() { + selectedMap[typeEnum.value] = 1; + }); + } + })); + } + list.add(Container( + margin: const EdgeInsets.only( + top: Base.BASE_PADDING, + bottom: Base.BASE_PADDING, + ), + child: Wrap( + children: typeWidgetList, + ), + )); + + List showItems = []; + if (selectedMap.isNotEmpty) { + for (var item in items) { + for (var typeValue in item.types) { + if (selectedMap[typeValue] != null) { + showItems.add(item); + break; + } + } + } + } else { + showItems.addAll(items); + } + + List itemWidgetList = []; + if (PlatformUtil.isPC()) { + for (var i = 0; i < showItems.length; i += 2) { + var item = showItems[i]; + if (i + 1 < showItems.length) { + var item1 = showItems[i + 1]; + itemWidgetList.add(Container( + child: Row( + children: [ + Expanded(child: WebAppItemComponent(item, onTap: onTap)), + Expanded(child: WebAppItemComponent(item1, onTap: onTap)), + ], + ), + )); + } else { + itemWidgetList.add(Container( + child: Row( + children: [ + Expanded(child: WebAppItemComponent(item, onTap: onTap)), + Expanded(child: Container()), + ], + ), + )); + } + } + } else { + for (var item in showItems) { + itemWidgetList.add(WebAppItemComponent(item)); + } + } + + list.add(Expanded( + child: Container( + child: SingleChildScrollView( + child: Column( + children: itemWidgetList, + ), + ), + ), + )); + + return Scaffold( + appBar: AppBar( + leading: AppbarBackBtnComponent(), + title: Text( + "Web APPs", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: themeData.textTheme.bodyLarge!.fontSize, + ), + ), + ), + body: Column( + children: list, + ), + ); + } + + void onTap(WebAppItem item) { + RouterUtil.back(context); + webProvider.addTab(url: item.link); + } + + @override + Future onReady(BuildContext context) async { + load(); + } + + Future load() async { + var str = await DioUtil.getStr(Base.WEB_APPS); + if (StringUtil.isNotBlank(str)) { + var jsonList = jsonDecode(str!); + if (jsonList is List) { + items.clear(); + + for (var jsonObj in jsonList) { + var link = jsonObj["link"]; + var name = jsonObj["name"]; + var desc = jsonObj["desc"]; + var types = jsonObj["types"]; + var image = jsonObj["image"]; + + // print(link); + // print(name); + // print(desc); + // print(types); + // print(types! is List); + // print(image); + + if (StringUtil.isBlank(link) || + StringUtil.isBlank(name) || + StringUtil.isBlank(desc) || + types is! List) { + continue; + } + + items.add(WebAppItem( + link, name, desc, types.map((item) => item.toString()).toList(), + image: image)); + } + } + } + + setState(() {}); + } + + Widget buildTypeWidget(EnumObj enumObj, bool selected, Function onTap) { + return Container( + child: GestureDetector( + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: Checkbox( + value: selected, + onChanged: (_) { + onTap(); + }, + ), + ), + Container( + child: Text(enumObj.name), + ), + ], + ), + ), + ); + } + + @override + void initState() { + super.initState(); + + if (items.isEmpty) { + items.add(WebAppItem( + "https://app.flotilla.social/", + "Flotilla", + "Relay chat client", + [WebAppTypes.GROUP_CHAT], + image: "https://nowser.nostrmo.com/images/apps/flotilla.png", + )); + items.add(WebAppItem( + "https://www.zapplepay.com/", + "Zapplepay", + "Zap from any client 🖕", + [WebAppTypes.TOOLS], + image: "https://nowser.nostrmo.com/images/apps/zapplepay.png", + )); + items.add(WebAppItem( + "https://habla.news/", + "Habla", + "A long form content client for nostr notes", + [WebAppTypes.LONG_FORM], + image: "https://nowser.nostrmo.com/images/apps/habla.png", + )); + items.add(WebAppItem( + "https://listr.lol/", + "Listr", + "Create nostr lists", + [WebAppTypes.TOOLS], + image: "https://nowser.nostrmo.com/images/apps/listr.png", + )); + items.add(WebAppItem( + "https://groups.nip29.com/", + "Groups", + "A relay-based NIP-29 group chat client", + [WebAppTypes.GROUP_CHAT], + image: "https://nowser.nostrmo.com/images/apps/groups.png", + )); + items.add(WebAppItem( + "https://lumilumi.app/", + "lumilumi", + "Switch between full and low-data modes — a flexible Nostr web client", + [WebAppTypes.NOTES], + image: "https://nowser.nostrmo.com/images/apps/lumilumi.ico", + )); + items.add(WebAppItem( + "https://iris.to/", + "Iris", + "Simple and fast web client", + [WebAppTypes.NOTES], + image: "https://nowser.nostrmo.com/images/apps/iris.png", + )); + + // List jsonList = []; + // for (var item in items) { + // jsonList.add(item.toJson()); + // } + // log(jsonEncode(jsonList)); + + setState(() {}); + } + } +}