mirror of
https://github.com/haorendashu/nowser.git
synced 2025-12-18 02:04:18 +01:00
app edit page
This commit is contained in:
@@ -6,4 +6,5 @@ class RouterPath {
|
|||||||
static const String KEYS = "/keys";
|
static const String KEYS = "/keys";
|
||||||
static const String APPS = "/apps";
|
static const String APPS = "/apps";
|
||||||
static const String ADD_REMOTE_APP = "/addRemoteApp";
|
static const String ADD_REMOTE_APP = "/addRemoteApp";
|
||||||
|
static const String APP_DETAIL = "/appDetail";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class AppDB {
|
|||||||
|
|
||||||
static Future update(App o, {DatabaseExecutor? db}) async {
|
static Future update(App o, {DatabaseExecutor? db}) async {
|
||||||
db = await DB.getDB(db);
|
db = await DB.getDB(db);
|
||||||
await db.update("app", o.toJson(), where: "id = ?", whereArgs: [o.pubkey]);
|
await db.update("app", o.toJson(), where: "id = ?", whereArgs: [o.id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> delete(int id, {DatabaseExecutor? db}) async {
|
static Future<void> delete(int id, {DatabaseExecutor? db}) async {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import 'package:nowser/provider/app_provider.dart';
|
|||||||
import 'package:nowser/provider/key_provider.dart';
|
import 'package:nowser/provider/key_provider.dart';
|
||||||
import 'package:nowser/provider/permission_check_mixin.dart';
|
import 'package:nowser/provider/permission_check_mixin.dart';
|
||||||
import 'package:nowser/provider/web_provider.dart';
|
import 'package:nowser/provider/web_provider.dart';
|
||||||
|
import 'package:nowser/router/app_detail/app_detail_router.dart';
|
||||||
import 'package:nowser/router/apps/add_remote_app_router.dart';
|
import 'package:nowser/router/apps/add_remote_app_router.dart';
|
||||||
import 'package:nowser/router/apps/apps_router.dart';
|
import 'package:nowser/router/apps/apps_router.dart';
|
||||||
import 'package:nowser/router/index/index_router.dart';
|
import 'package:nowser/router/index/index_router.dart';
|
||||||
@@ -100,6 +101,7 @@ class _MyApp extends State<MyApp> {
|
|||||||
RouterPath.KEYS: (context) => KeysRouter(),
|
RouterPath.KEYS: (context) => KeysRouter(),
|
||||||
RouterPath.APPS: (context) => AppsRouter(),
|
RouterPath.APPS: (context) => AppsRouter(),
|
||||||
RouterPath.ADD_REMOTE_APP: (context) => AddRemoteAppRouter(),
|
RouterPath.ADD_REMOTE_APP: (context) => AddRemoteAppRouter(),
|
||||||
|
RouterPath.APP_DETAIL: (context) => AppDetailRouter(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
|
|||||||
@@ -39,6 +39,13 @@ class AppProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> update(App app) async {
|
||||||
|
app.updatedAt = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
|
AppDB.update(app);
|
||||||
|
reload();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> add(App app) async {
|
Future<void> add(App app) async {
|
||||||
if (await AppDB.insert(app) > 0) {
|
if (await AppDB.insert(app) > 0) {
|
||||||
_list.add(app);
|
_list.add(app);
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:nowser/component/tag_component.dart';
|
||||||
|
import 'package:nowser/const/auth_type.dart';
|
||||||
|
|
||||||
|
class AppDetailPermissionItemComponent extends StatefulWidget {
|
||||||
|
bool allow;
|
||||||
|
|
||||||
|
int authType;
|
||||||
|
|
||||||
|
int? eventKind;
|
||||||
|
|
||||||
|
Function(bool, int, int?)? onDelete;
|
||||||
|
|
||||||
|
AppDetailPermissionItemComponent(this.allow, this.authType,
|
||||||
|
{this.eventKind, this.onDelete});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() {
|
||||||
|
return _AppDetailPermissionItemComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppDetailPermissionItemComponent
|
||||||
|
extends State<AppDetailPermissionItemComponent> {
|
||||||
|
bool tapFirst = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var permissionText = AuthType.getAuthName(context, widget.authType);
|
||||||
|
if (widget.eventKind != null) {
|
||||||
|
permissionText += " (EventKind ${widget.eventKind})";
|
||||||
|
}
|
||||||
|
var main = TagComponent(permissionText);
|
||||||
|
|
||||||
|
if (!tapFirst) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
tapFirst = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: main,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
if (widget.onDelete != null) {
|
||||||
|
widget.onDelete!(widget.allow, widget.authType, widget.eventKind);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
main,
|
||||||
|
Icon(Icons.delete),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
350
lib/router/app_detail/app_detail_router.dart
Normal file
350
lib/router/app_detail/app_detail_router.dart
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:nostr_sdk/nip19/nip19.dart';
|
||||||
|
import 'package:nostr_sdk/utils/string_util.dart';
|
||||||
|
import 'package:nowser/component/app/app_type_component.dart';
|
||||||
|
import 'package:nowser/component/image_component.dart';
|
||||||
|
import 'package:nowser/const/connect_type.dart';
|
||||||
|
import 'package:nowser/data/app.dart';
|
||||||
|
import 'package:nowser/main.dart';
|
||||||
|
import 'package:nowser/router/app_detail/app_detail_permission_item_component.dart';
|
||||||
|
import 'package:nowser/util/router_util.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../component/appbar_back_btn_component.dart';
|
||||||
|
import '../../const/base.dart';
|
||||||
|
import '../../provider/key_provider.dart';
|
||||||
|
|
||||||
|
class AppDetailRouter extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() {
|
||||||
|
return _AppDetailRouter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppDetailRouter extends State<AppDetailRouter> {
|
||||||
|
late TextEditingController nameController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
nameController = TextEditingController();
|
||||||
|
nameController.addListener(() {
|
||||||
|
if (app != null && !changed && nameController.text != app!.name) {
|
||||||
|
setState(() {
|
||||||
|
changed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
App? app;
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var themeData = Theme.of(context);
|
||||||
|
var arg = RouterUtil.routerArgs(context);
|
||||||
|
if (arg != null && arg is App) {
|
||||||
|
if (app == null || app!.id != arg.id) {
|
||||||
|
app = App.fromJson(arg.toJson());
|
||||||
|
print(app!.name);
|
||||||
|
nameController.text = app!.name ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app == null) {
|
||||||
|
RouterUtil.back(context);
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> list = [];
|
||||||
|
|
||||||
|
var baseMargin = EdgeInsets.only(bottom: Base.BASE_PADDING);
|
||||||
|
|
||||||
|
Widget imageWidget = Icon(
|
||||||
|
Icons.image,
|
||||||
|
size: 80,
|
||||||
|
);
|
||||||
|
if (StringUtil.isNotBlank(app!.image)) {
|
||||||
|
imageWidget = ImageComponent(
|
||||||
|
imageUrl: app!.image!,
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
list.add(Container(
|
||||||
|
margin: baseMargin,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.only(right: Base.BASE_PADDING * 2),
|
||||||
|
child: Container(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
child: imageWidget,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AppTypeComponent(app!.appType!),
|
||||||
|
Text(
|
||||||
|
app!.code!,
|
||||||
|
style: TextStyle(color: themeData.hintColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
list.add(Container(
|
||||||
|
margin: baseMargin,
|
||||||
|
child: TextField(
|
||||||
|
controller: nameController,
|
||||||
|
decoration: InputDecoration(hintText: "Name"),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
var keyWidget =
|
||||||
|
Selector<KeyProvider, List<String>>(builder: (context, pubkeys, child) {
|
||||||
|
List<DropdownMenuItem<String>> items = [];
|
||||||
|
for (var pubkey in pubkeys) {
|
||||||
|
items.add(DropdownMenuItem(
|
||||||
|
value: pubkey,
|
||||||
|
child: Text(
|
||||||
|
Nip19.encodePubKey(pubkey),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return DropdownButton<String>(
|
||||||
|
items: items,
|
||||||
|
isExpanded: true,
|
||||||
|
onChanged: null,
|
||||||
|
value: app!.pubkey,
|
||||||
|
);
|
||||||
|
}, selector: (context, provider) {
|
||||||
|
return provider.pubkeys;
|
||||||
|
});
|
||||||
|
list.add(Container(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.only(right: Base.BASE_PADDING),
|
||||||
|
child: Text("Pubkey:"),
|
||||||
|
),
|
||||||
|
Expanded(child: keyWidget),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
List<DropdownMenuItem<int>> connectTypeItems = [];
|
||||||
|
connectTypeItems.add(DropdownMenuItem(
|
||||||
|
child: Text("Fully trust"), value: ConnectType.FULLY_TRUST));
|
||||||
|
connectTypeItems.add(DropdownMenuItem(
|
||||||
|
child: Text("Reasonable"), value: ConnectType.REASONABLE));
|
||||||
|
connectTypeItems.add(DropdownMenuItem(
|
||||||
|
child: Text("Alway reject"), value: ConnectType.ALWAY_REJECT));
|
||||||
|
list.add(Container(
|
||||||
|
margin: baseMargin,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.only(right: Base.BASE_PADDING),
|
||||||
|
child: Text("ConnectType:"),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: DropdownButton<int>(
|
||||||
|
items: connectTypeItems,
|
||||||
|
isExpanded: true,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
setState(() {
|
||||||
|
changed = true;
|
||||||
|
app!.connectType = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value: app!.connectType,
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
if (app!.connectType == ConnectType.REASONABLE) {
|
||||||
|
if (StringUtil.isNotBlank(app!.alwaysAllow)) {
|
||||||
|
var permissionItems =
|
||||||
|
getPermissionItems(context, app!.alwaysAllow!, true);
|
||||||
|
|
||||||
|
if (permissionItems.isNotEmpty) {
|
||||||
|
list.add(Container(
|
||||||
|
margin: baseMargin,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
"Always Allow:",
|
||||||
|
),
|
||||||
|
));
|
||||||
|
list.add(Container(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Wrap(
|
||||||
|
spacing: Base.BASE_PADDING,
|
||||||
|
runSpacing: Base.BASE_PADDING,
|
||||||
|
children: permissionItems,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtil.isNotBlank(app!.alwaysReject)) {
|
||||||
|
var permissionItems =
|
||||||
|
getPermissionItems(context, app!.alwaysReject!, false);
|
||||||
|
|
||||||
|
if (permissionItems.isNotEmpty) {
|
||||||
|
list.add(Container(
|
||||||
|
margin: baseMargin,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
"Always Reject:",
|
||||||
|
),
|
||||||
|
));
|
||||||
|
list.add(Container(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Wrap(
|
||||||
|
spacing: Base.BASE_PADDING,
|
||||||
|
runSpacing: Base.BASE_PADDING,
|
||||||
|
children: permissionItems,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> actions = [];
|
||||||
|
if (changed == true) {
|
||||||
|
actions.add(GestureDetector(
|
||||||
|
onTap: appUpdate,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(Base.BASE_PADDING),
|
||||||
|
child: Icon(Icons.done),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: AppbarBackBtnComponent(),
|
||||||
|
title: Text(
|
||||||
|
"App Detail",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: themeData.textTheme.bodyLarge!.fontSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: actions,
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(Base.BASE_PADDING),
|
||||||
|
child: Column(
|
||||||
|
children: list,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> getPermissionItems(
|
||||||
|
BuildContext context, String permissionText, bool allow) {
|
||||||
|
var permissionTexts = permissionText.split(";");
|
||||||
|
List<Widget> permissionItems = [];
|
||||||
|
for (var permissionText in permissionTexts) {
|
||||||
|
var strs = permissionText.split("-");
|
||||||
|
var authType = int.tryParse(strs[0]);
|
||||||
|
if (authType != null) {
|
||||||
|
if (strs.length > 1) {
|
||||||
|
var eventKindStrs = strs[1].split(",");
|
||||||
|
for (var eventKindStr in eventKindStrs) {
|
||||||
|
var eventKind = int.tryParse(eventKindStr);
|
||||||
|
if (eventKind != null) {
|
||||||
|
permissionItems.add(AppDetailPermissionItemComponent(
|
||||||
|
allow,
|
||||||
|
authType,
|
||||||
|
eventKind: eventKind,
|
||||||
|
onDelete: onPermissionDelete,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
permissionItems.add(AppDetailPermissionItemComponent(
|
||||||
|
allow,
|
||||||
|
authType,
|
||||||
|
onDelete: onPermissionDelete,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissionItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
onPermissionDelete(bool allow, int authType, int? eventKind) {
|
||||||
|
var sourceText = app!.alwaysAllow;
|
||||||
|
if (!allow) {
|
||||||
|
sourceText = app!.alwaysReject;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> permissions = [];
|
||||||
|
if (StringUtil.isNotBlank(sourceText)) {
|
||||||
|
var permissionTexts = sourceText!.split(";");
|
||||||
|
for (var permissionText in permissionTexts) {
|
||||||
|
var strs = permissionText.split("-");
|
||||||
|
var _authType = int.tryParse(strs[0]);
|
||||||
|
if (_authType != authType) {
|
||||||
|
permissions.add(permissionText);
|
||||||
|
} else {
|
||||||
|
// authType same, check eventKind
|
||||||
|
if (eventKind == null || strs.length <= 1) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// should check eventKind
|
||||||
|
List<String> checkedEventKindStrs = [];
|
||||||
|
if (strs.length > 1) {
|
||||||
|
var eventKindStrs = strs[1].split(",");
|
||||||
|
for (var eventKindStr in eventKindStrs) {
|
||||||
|
if (eventKindStr == "$eventKind") {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
checkedEventKindStrs.add(eventKindStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkedEventKindStrs.isNotEmpty) {
|
||||||
|
permissions.add("$_authType-${checkedEventKindStrs.join(",")}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allow) {
|
||||||
|
app!.alwaysAllow = permissions.join(";");
|
||||||
|
} else {
|
||||||
|
app!.alwaysReject = permissions.join(";");
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void appUpdate() {
|
||||||
|
app!.name = nameController.text;
|
||||||
|
appProvider.update(app!);
|
||||||
|
RouterUtil.back(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,9 @@ import 'package:nostr_sdk/utils/string_util.dart';
|
|||||||
import 'package:nowser/component/app/app_type_component.dart';
|
import 'package:nowser/component/app/app_type_component.dart';
|
||||||
import 'package:nowser/component/image_component.dart';
|
import 'package:nowser/component/image_component.dart';
|
||||||
import 'package:nowser/const/app_type.dart';
|
import 'package:nowser/const/app_type.dart';
|
||||||
|
import 'package:nowser/const/router_path.dart';
|
||||||
import 'package:nowser/data/app.dart';
|
import 'package:nowser/data/app.dart';
|
||||||
|
import 'package:nowser/util/router_util.dart';
|
||||||
|
|
||||||
import '../../const/base.dart';
|
import '../../const/base.dart';
|
||||||
|
|
||||||
@@ -57,14 +59,20 @@ class _MeRouterAppItemComponent extends State<MeRouterAppItemComponent> {
|
|||||||
child: Icon(Icons.chevron_right),
|
child: Icon(Icons.chevron_right),
|
||||||
);
|
);
|
||||||
|
|
||||||
return Container(
|
return GestureDetector(
|
||||||
child: Row(
|
onTap: () {
|
||||||
children: [
|
RouterUtil.router(context, RouterPath.APP_DETAIL, widget.app);
|
||||||
imageWidget,
|
},
|
||||||
Expanded(child: titleWidget),
|
behavior: HitTestBehavior.translucent,
|
||||||
typeWidget,
|
child: Container(
|
||||||
rightIconWidget,
|
child: Row(
|
||||||
],
|
children: [
|
||||||
|
imageWidget,
|
||||||
|
Expanded(child: titleWidget),
|
||||||
|
typeWidget,
|
||||||
|
rightIconWidget,
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user