mirror of
https://github.com/haorendashu/nowser.git
synced 2025-12-17 18:04:18 +01:00
add web apps
This commit is contained in:
23
lib/router/web_apps/web_app_item.dart
Normal file
23
lib/router/web_apps/web_app_item.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
class WebAppItem {
|
||||
String link;
|
||||
|
||||
String name;
|
||||
|
||||
String desc;
|
||||
|
||||
String? image;
|
||||
|
||||
List<String> types;
|
||||
|
||||
WebAppItem(this.link, this.name, this.desc, this.types, {this.image});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
"link": link,
|
||||
"name": name,
|
||||
"desc": desc,
|
||||
"image": image,
|
||||
"types": types,
|
||||
};
|
||||
}
|
||||
}
|
||||
98
lib/router/web_apps/web_app_item_component.dart
Normal file
98
lib/router/web_apps/web_app_item_component.dart
Normal file
@@ -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<StatefulWidget> createState() {
|
||||
return _WebAppItemComponent();
|
||||
}
|
||||
}
|
||||
|
||||
class _WebAppItemComponent extends State<WebAppItemComponent> {
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
10
lib/router/web_apps/web_app_types.dart
Normal file
10
lib/router/web_apps/web_app_types.dart
Normal file
@@ -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";
|
||||
}
|
||||
296
lib/router/web_apps/web_apps_router.dart
Normal file
296
lib/router/web_apps/web_apps_router.dart
Normal file
@@ -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<StatefulWidget> createState() {
|
||||
return WebAppsRouterState();
|
||||
}
|
||||
}
|
||||
|
||||
class WebAppsRouterState extends CustState<WebAppsRouter> {
|
||||
late S s;
|
||||
|
||||
List<WebAppItem> items = [];
|
||||
|
||||
Map<String, int> selectedMap = {};
|
||||
|
||||
List<EnumObj> 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<Widget> list = [];
|
||||
|
||||
List<Widget> 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<WebAppItem> 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<Widget> 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<void> onReady(BuildContext context) async {
|
||||
load();
|
||||
}
|
||||
|
||||
Future<void> 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<Map> jsonList = [];
|
||||
// for (var item in items) {
|
||||
// jsonList.add(item.toJson());
|
||||
// }
|
||||
// log(jsonEncode(jsonList));
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user