app edit page

This commit is contained in:
DASHU
2024-09-12 19:19:19 +08:00
parent 99d1c03d2a
commit 2c7629ab3f
7 changed files with 440 additions and 9 deletions

View File

@@ -6,4 +6,5 @@ class RouterPath {
static const String KEYS = "/keys";
static const String APPS = "/apps";
static const String ADD_REMOTE_APP = "/addRemoteApp";
static const String APP_DETAIL = "/appDetail";
}

View File

@@ -32,7 +32,7 @@ class AppDB {
static Future update(App o, {DatabaseExecutor? db}) async {
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 {

View File

@@ -13,6 +13,7 @@ import 'package:nowser/provider/app_provider.dart';
import 'package:nowser/provider/key_provider.dart';
import 'package:nowser/provider/permission_check_mixin.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/apps_router.dart';
import 'package:nowser/router/index/index_router.dart';
@@ -100,6 +101,7 @@ class _MyApp extends State<MyApp> {
RouterPath.KEYS: (context) => KeysRouter(),
RouterPath.APPS: (context) => AppsRouter(),
RouterPath.ADD_REMOTE_APP: (context) => AddRemoteAppRouter(),
RouterPath.APP_DETAIL: (context) => AppDetailRouter(),
};
return MultiProvider(

View File

@@ -39,6 +39,13 @@ class AppProvider extends ChangeNotifier {
notifyListeners();
}
Future<void> update(App app) async {
app.updatedAt = DateTime.now().millisecondsSinceEpoch ~/ 1000;
AppDB.update(app);
reload();
notifyListeners();
}
Future<void> add(App app) async {
if (await AppDB.insert(app) > 0) {
_list.add(app);

View File

@@ -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),
],
),
),
);
}
}

View 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);
}
}

View File

@@ -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/image_component.dart';
import 'package:nowser/const/app_type.dart';
import 'package:nowser/const/router_path.dart';
import 'package:nowser/data/app.dart';
import 'package:nowser/util/router_util.dart';
import '../../const/base.dart';
@@ -57,14 +59,20 @@ class _MeRouterAppItemComponent extends State<MeRouterAppItemComponent> {
child: Icon(Icons.chevron_right),
);
return Container(
child: Row(
children: [
imageWidget,
Expanded(child: titleWidget),
typeWidget,
rightIconWidget,
],
return GestureDetector(
onTap: () {
RouterUtil.router(context, RouterPath.APP_DETAIL, widget.app);
},
behavior: HitTestBehavior.translucent,
child: Container(
child: Row(
children: [
imageWidget,
Expanded(child: titleWidget),
typeWidget,
rightIconWidget,
],
),
),
);
}