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: () {
keyProvider.removeKey(widget.pubkey);
},
child: Container(
margin: const EdgeInsets.only(
left: Base.BASE_PADDING_HALF, left: Base.BASE_PADDING_HALF,
// right: Base.BASE_PADDING_HALF,
), ),
child: Icon(Icons.logout), child: const 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 = [];
var pubkeys = _keyProvider.pubkeys;
var length = pubkeys.length;
for (var i = 0; i < length; i++) {
var pubkey = pubkeys[i];
list.add(Container( list.add(Container(
margin: margin, margin: margin,
child: GestureDetector(
onTap: () {
keyProvider.setDefault(pubkey);
},
child: KeysItemComponent( child: KeysItemComponent(
"29320975df855fe34a7b45ada2421e2c741c37c0136901fe477133a91eb18b07", pubkey,
isDefault: true, isDefault: i == 0,
), ),
));
for (var i = 0; i < 5; i++) {
list.add(Container(
margin: margin,
child: KeysItemComponent(
"8fb140b4e8ddef97ce4b821d247278a1a4353362623f64021484b372f948000c",
), ),
)); ));
} }

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>>(
builder: (context, npubList, child) {
if (npubList.isNotEmpty) {
var pubkey = npubList.first;
return UserNameComponent(pubkey);
}
return GestureDetector(
onTap: () { onTap: () {
KeysRouter.addKey(context); KeysRouter.addKey(context);
}, },
child: Text("Click and Login"), child: Text("Click and Login"),
);
},
selector: (context, _provider) {
return _provider.pubkeys;
},
), ),
)); ));
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,8 +73,14 @@ class _MeRouter extends State<MeRouter> {
), ),
); );
var memberListWidget = Selector<KeyProvider, List<String>>(
builder: (context, npubList, child) {
if (npubList.isEmpty) {
return Container();
}
List<Widget> memberList = []; List<Widget> memberList = [];
for (var i = 0; i < 3; i++) { for (var pubkey in npubList) {
memberList.add(Container( memberList.add(Container(
margin: EdgeInsets.only(left: Base.BASE_PADDING_HALF), margin: EdgeInsets.only(left: Base.BASE_PADDING_HALF),
child: UserPicComponent(width: 30), child: UserPicComponent(width: 30),
@@ -69,7 +90,8 @@ class _MeRouter extends State<MeRouter> {
memberList.add(GestureDetector( memberList.add(GestureDetector(
child: Icon(Icons.chevron_right), child: Icon(Icons.chevron_right),
)); ));
var memberListWidget = Container(
return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: themeData.cardColor, color: themeData.cardColor,
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
@@ -88,6 +110,11 @@ class _MeRouter extends State<MeRouter> {
), ),
), ),
); );
},
selector: (context, _provider) {
return _provider.pubkeys;
},
);
List<Widget> webItemList = []; List<Widget> webItemList = [];
webItemList.add(MeRouterWebItemComponent( webItemList.add(MeRouterWebItemComponent(
@@ -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(),