login and keys manage

This commit is contained in:
DASHU
2024-08-31 02:53:49 +08:00
parent 7c109f15c4
commit 47f02c72b0
8 changed files with 298 additions and 63 deletions

View File

@@ -0,0 +1,129 @@
import 'package:flutter/material.dart';
import 'package:nostr_sdk/client_utils/keys.dart';
import 'package:nostr_sdk/nip19/nip19.dart';
import 'package:nostr_sdk/utils/string_util.dart';
import 'package:nowser/main.dart';
import 'package:nowser/util/router_util.dart';
import '../../const/base.dart';
class UserLoginDialog extends StatefulWidget {
static Future<void> show(BuildContext context) async {
await showDialog<String>(
context: context,
builder: (_context) {
return UserLoginDialog();
});
}
@override
State<StatefulWidget> createState() {
return _UserLoginDialog();
}
}
class _UserLoginDialog extends State<UserLoginDialog> {
bool obscureText = true;
TextEditingController controller = TextEditingController();
@override
Widget build(BuildContext context) {
var themeData = Theme.of(context);
List<Widget> list = [];
list.add(Container(
margin: EdgeInsets.only(bottom: Base.BASE_PADDING * 2),
child: Text(
"Login",
style: TextStyle(
fontSize: themeData.textTheme.bodyLarge!.fontSize,
fontWeight: FontWeight.bold,
),
),
));
var prefixIcon = GestureDetector(
onTap: () {},
child: Icon(Icons.qr_code),
);
var suffixIcon = GestureDetector(
onTap: () {
setState(() {
obscureText = !obscureText;
});
},
child: Icon(obscureText ? Icons.visibility_off : Icons.visibility),
);
list.add(Container(
margin: const EdgeInsets.only(bottom: Base.BASE_PADDING),
child: TextField(
controller: controller,
obscureText: obscureText,
autofocus: true,
decoration: InputDecoration(
hintText: "nsec / hex private key",
prefixIcon: prefixIcon,
suffixIcon: suffixIcon,
),
),
));
list.add(Container(
margin: EdgeInsets.only(bottom: Base.BASE_PADDING * 2),
width: double.infinity,
child: FilledButton(onPressed: confirm, child: Text("Confirm")),
));
list.add(GestureDetector(
onTap: () {
var pk = generatePrivateKey();
controller.text = pk;
},
child: Container(
child: Text(
"Generate a private key",
style: TextStyle(
decoration: TextDecoration.underline,
),
),
),
));
var main = Column(
mainAxisSize: MainAxisSize.min,
children: list,
);
return Dialog(
child: Container(
padding: EdgeInsets.all(Base.BASE_PADDING * 2),
child: main,
),
);
}
void confirm() {
var pk = controller.text;
try {
if (Nip19.isPrivateKey(pk)) {
pk = Nip19.decode(pk);
}
var pubkey = getPublicKey(pk);
if (StringUtil.isBlank(pubkey)) {
keyCheckFail();
return;
}
keyProvider.addKey(pk);
RouterUtil.back(context);
} catch (e) {
keyCheckFail();
}
}
void keyCheckFail() {}
}

View File

@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:nostr_sdk/nip19/nip19.dart';
class UserNameComponent extends StatefulWidget {
String pubkey;
bool fullNpubName;
UserNameComponent(this.pubkey, {this.fullNpubName = false});
@override
State<StatefulWidget> createState() {
return _UserNameComponent();
}
}
class _UserNameComponent extends State<UserNameComponent> {
@override
Widget build(BuildContext context) {
var npub = Nip19.encodePubKey(widget.pubkey);
if (!widget.fullNpubName) {
npub = Nip19.encodeSimplePubKey(widget.pubkey);
}
return Text(
npub,
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
}
}

View File

@@ -86,6 +86,9 @@ class _MyApp extends State<MyApp> {
ListenableProvider<WebProvider>.value( ListenableProvider<WebProvider>.value(
value: webProvider, value: webProvider,
), ),
ListenableProvider<KeyProvider>.value(
value: keyProvider,
),
], ],
child: MaterialApp( child: MaterialApp(
builder: BotToastInit(), builder: BotToastInit(),

View File

@@ -20,8 +20,8 @@ class KeyProvider extends ChangeNotifier {
Future<void> reload() async { Future<void> reload() async {
keys.clear(); keys.clear();
pubkeys.clear(); keysMap = {};
keysMap.clear(); pubkeys = [];
final storage = FlutterSecureStorage(); final storage = FlutterSecureStorage();
var strs = await storage.read(key: KEY_NAME); var strs = await storage.read(key: KEY_NAME);
@@ -40,6 +40,34 @@ class KeyProvider extends ChangeNotifier {
} }
} }
void _regenMemKeys() {
keys = [...keys];
keysMap = {};
pubkeys = [];
for (var key in keys) {
var pubkey = getPublicKey(key);
keysMap[pubkey] = key;
pubkeys.add(pubkey);
}
}
void setDefault(String pubkey) {
var key = keysMap[pubkey];
if (StringUtil.isNotBlank(key)) {
keys.remove(key);
List<String> newKeys = [key!];
newKeys.addAll(keys);
keys = newKeys;
_saveKey();
_regenMemKeys();
notifyListeners();
}
}
Future<void> _saveKey() async { Future<void> _saveKey() async {
var jsonStr = jsonEncode(keys); var jsonStr = jsonEncode(keys);
final storage = FlutterSecureStorage(); final storage = FlutterSecureStorage();
@@ -57,6 +85,7 @@ class KeyProvider extends ChangeNotifier {
pubkeys.add(pubkey); pubkeys.add(pubkey);
_saveKey(); _saveKey();
_regenMemKeys();
notifyListeners(); notifyListeners();
} }
@@ -68,6 +97,7 @@ class KeyProvider extends ChangeNotifier {
} }
_saveKey(); _saveKey();
_regenMemKeys();
notifyListeners(); notifyListeners();
} }

View File

@@ -1,8 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:nostr_sdk/nip19/nip19.dart'; import 'package:nostr_sdk/nip19/nip19.dart';
import 'package:nowser/const/base.dart'; import 'package:nowser/const/base.dart';
import 'package:nowser/main.dart';
import '../../component/user_pic_component.dart'; import '../../component/user/user_name_component.dart';
import '../../component/user/user_pic_component.dart';
class KeysItemComponent extends StatefulWidget { class KeysItemComponent extends StatefulWidget {
bool isDefault; bool isDefault;
@@ -33,17 +35,20 @@ class _KeysItemComponent extends State<KeysItemComponent> {
), ),
)); ));
list.add(Expanded( list.add(Expanded(
child: Text( child: UserNameComponent(
Nip19.encodePubKey(widget.pubkey), widget.pubkey,
maxLines: 1, fullNpubName: true,
overflow: TextOverflow.ellipsis,
))); )));
list.add(Container( list.add(GestureDetector(
margin: EdgeInsets.only( onTap: () {
left: Base.BASE_PADDING_HALF, keyProvider.removeKey(widget.pubkey);
// right: Base.BASE_PADDING_HALF, },
child: Container(
margin: const EdgeInsets.only(
left: Base.BASE_PADDING_HALF,
),
child: const Icon(Icons.logout),
), ),
child: Icon(Icons.logout),
)); ));
return Container( return Container(

View File

@@ -1,8 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:nowser/component/appbar_back_btn_component.dart'; import 'package:nowser/component/appbar_back_btn_component.dart';
import 'package:nowser/component/text_input/text_input_dialog.dart';
import 'package:nowser/const/base.dart'; import 'package:nowser/const/base.dart';
import 'package:nowser/main.dart';
import 'package:nowser/provider/key_provider.dart';
import 'package:nowser/router/keys/keys_item_component.dart'; import 'package:nowser/router/keys/keys_item_component.dart';
import 'package:provider/provider.dart';
import '../../component/user/user_login_dialog.dart';
class KeysRouter extends StatefulWidget { class KeysRouter extends StatefulWidget {
@override @override
@@ -11,7 +15,7 @@ class KeysRouter extends StatefulWidget {
} }
static void addKey(BuildContext context) { static void addKey(BuildContext context) {
TextInputDialog.show(context, "Please input private key"); UserLoginDialog.show(context);
} }
} }
@@ -25,19 +29,23 @@ class _KeysRouter extends State<KeysRouter> {
top: Base.BASE_PADDING, top: Base.BASE_PADDING,
); );
var _keyProvider = Provider.of<KeyProvider>(context);
List<Widget> list = []; List<Widget> list = [];
list.add(Container( var pubkeys = _keyProvider.pubkeys;
margin: margin, var length = pubkeys.length;
child: KeysItemComponent( for (var i = 0; i < length; i++) {
"29320975df855fe34a7b45ada2421e2c741c37c0136901fe477133a91eb18b07", var pubkey = pubkeys[i];
isDefault: true,
),
));
for (var i = 0; i < 5; i++) {
list.add(Container( list.add(Container(
margin: margin, margin: margin,
child: KeysItemComponent( child: GestureDetector(
"8fb140b4e8ddef97ce4b821d247278a1a4353362623f64021484b372f948000c", onTap: () {
keyProvider.setDefault(pubkey);
},
child: KeysItemComponent(
pubkey,
isDefault: i == 0,
),
), ),
)); ));
} }

View File

@@ -1,11 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:nowser/component/text_input/text_input_dialog.dart'; import 'package:nowser/component/text_input/text_input_dialog.dart';
import 'package:nowser/component/user_pic_component.dart'; import 'package:nowser/component/user/user_name_component.dart';
import 'package:nowser/component/user/user_pic_component.dart';
import 'package:nowser/const/base.dart'; import 'package:nowser/const/base.dart';
import 'package:nowser/const/router_path.dart'; import 'package:nowser/const/router_path.dart';
import 'package:nowser/provider/key_provider.dart';
import 'package:nowser/router/me/me_router_log_item_component.dart'; import 'package:nowser/router/me/me_router_log_item_component.dart';
import 'package:nowser/router/me/me_router_web_item_component.dart'; import 'package:nowser/router/me/me_router_web_item_component.dart';
import 'package:nowser/util/router_util.dart'; import 'package:nowser/util/router_util.dart';
import 'package:provider/provider.dart';
import '../keys/keys_router.dart'; import '../keys/keys_router.dart';
import 'me_router_app_item_component.dart'; import 'me_router_app_item_component.dart';
@@ -23,31 +26,43 @@ class _MeRouter extends State<MeRouter> {
var mediaQueryData = MediaQuery.of(context); var mediaQueryData = MediaQuery.of(context);
var themeData = Theme.of(context); var themeData = Theme.of(context);
var listWidgetMargin = EdgeInsets.only( var listWidgetMargin = const EdgeInsets.only(
top: Base.BASE_PADDING, top: Base.BASE_PADDING,
bottom: Base.BASE_PADDING, bottom: Base.BASE_PADDING,
); );
List<Widget> defaultUserWidgets = []; List<Widget> defaultUserWidgets = [];
defaultUserWidgets.add(Container( defaultUserWidgets.add(Container(
margin: EdgeInsets.only( margin: const EdgeInsets.only(
left: Base.BASE_PADDING, left: Base.BASE_PADDING,
), ),
child: UserPicComponent(width: 50), child: UserPicComponent(width: 50),
)); ));
defaultUserWidgets.add(Container( defaultUserWidgets.add(Container(
margin: EdgeInsets.only(left: Base.BASE_PADDING), margin: const EdgeInsets.only(left: Base.BASE_PADDING),
child: GestureDetector( child: Selector<KeyProvider, List<String>>(
onTap: () { builder: (context, npubList, child) {
KeysRouter.addKey(context); if (npubList.isNotEmpty) {
var pubkey = npubList.first;
return UserNameComponent(pubkey);
}
return GestureDetector(
onTap: () {
KeysRouter.addKey(context);
},
child: Text("Click and Login"),
);
},
selector: (context, _provider) {
return _provider.pubkeys;
}, },
child: Text("Click and Login"),
), ),
)); ));
defaultUserWidgets.add(Expanded(child: Container())); defaultUserWidgets.add(Expanded(child: Container()));
defaultUserWidgets.add(Container( defaultUserWidgets.add(Container(
margin: EdgeInsets.only(right: Base.BASE_PADDING), margin: const EdgeInsets.only(right: Base.BASE_PADDING),
child: Icon(Icons.settings), child: const Icon(Icons.settings),
)); ));
var defaultUserWidget = Container( var defaultUserWidget = Container(
margin: const EdgeInsets.only( margin: const EdgeInsets.only(
@@ -58,35 +73,47 @@ class _MeRouter extends State<MeRouter> {
), ),
); );
List<Widget> memberList = []; var memberListWidget = Selector<KeyProvider, List<String>>(
for (var i = 0; i < 3; i++) { builder: (context, npubList, child) {
memberList.add(Container( if (npubList.isEmpty) {
margin: EdgeInsets.only(left: Base.BASE_PADDING_HALF), return Container();
child: UserPicComponent(width: 30), }
));
} List<Widget> memberList = [];
memberList.add(Expanded(child: Container())); for (var pubkey in npubList) {
memberList.add(GestureDetector( memberList.add(Container(
child: Icon(Icons.chevron_right), margin: EdgeInsets.only(left: Base.BASE_PADDING_HALF),
)); child: UserPicComponent(width: 30),
var memberListWidget = Container( ));
decoration: BoxDecoration( }
color: themeData.cardColor, memberList.add(Expanded(child: Container()));
borderRadius: BorderRadius.circular( memberList.add(GestureDetector(
Base.BASE_PADDING, child: Icon(Icons.chevron_right),
), ));
),
margin: listWidgetMargin, return Container(
padding: EdgeInsets.all(Base.BASE_PADDING), decoration: BoxDecoration(
child: GestureDetector( color: themeData.cardColor,
onTap: () { borderRadius: BorderRadius.circular(
RouterUtil.router(context, RouterPath.KEYS); Base.BASE_PADDING,
}, ),
behavior: HitTestBehavior.translucent, ),
child: Row( margin: listWidgetMargin,
children: memberList, padding: EdgeInsets.all(Base.BASE_PADDING),
), child: GestureDetector(
), onTap: () {
RouterUtil.router(context, RouterPath.KEYS);
},
behavior: HitTestBehavior.translucent,
child: Row(
children: memberList,
),
),
);
},
selector: (context, _provider) {
return _provider.pubkeys;
},
); );
List<Widget> webItemList = []; List<Widget> webItemList = [];
@@ -155,6 +182,8 @@ class _MeRouter extends State<MeRouter> {
), ),
); );
// TODO add zap send list here!
List<Widget> logList = []; List<Widget> logList = [];
logList.add(Container( logList.add(Container(
child: MeRouterLogItemComponent(), child: MeRouterLogItemComponent(),