Skip to content

Commit

Permalink
feat: Add seed phrase to settings
Browse files Browse the repository at this point in the history
  • Loading branch information
holzeis committed Jan 23, 2024
1 parent 9e50080 commit 87d1298
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 14 deletions.
90 changes: 90 additions & 0 deletions webapp/frontend/lib/settings/seed_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:get_10101/settings/seed_words.dart';
import 'package:get_10101/settings/settings_service.dart';
import 'package:provider/provider.dart';

class SeedScreen extends StatefulWidget {
const SeedScreen({super.key});

@override
State<SeedScreen> createState() => _SeedScreenState();
}

class _SeedScreenState extends State<SeedScreen> {
bool checked = false;
bool visibility = false;

List<String>? phrase;

@override
void initState() {
context.read<SettingsService>().getSeedPhrase().then((value) => setState(() => phrase = value));
super.initState();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.only(top: 20, left: 10, right: 10),
child: Column(
children: [
Container(
margin: const EdgeInsets.all(10),
child: Center(
child: RichText(
text: const TextSpan(
style: TextStyle(color: Colors.black, fontSize: 18),
children: [
TextSpan(
text:
"The recovery phrase (sometimes called a seed), is a list of 12 English words. It allows you to recover full access to your funds if needed\n\n"),
TextSpan(
text: "Do not share this seed with anyone. ",
style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(
text:
"Beware of phising. The developers of 10101 will never ask for your seed.\n\n"),
TextSpan(
text: "Do not lose this seed. ",
style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(
text:
"Save it somewhere safe. If you lose your seed your lose your funds."),
])),
),
),
const SizedBox(height: 25),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
buildSeedWordsWidget(phrase!, visibility),
const SizedBox(height: 20),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
icon: visibility
? const Icon(Icons.visibility)
: const Icon(Icons.visibility_off),
onPressed: () {
setState(() {
visibility = !visibility;
});
},
tooltip: visibility ? 'Hide Seed' : 'Show Seed'),
Text(visibility ? 'Hide Seed' : 'Show Seed')
],
),
),
],
),
],
),
),
);
}
}
64 changes: 64 additions & 0 deletions webapp/frontend/lib/settings/seed_words.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import 'package:flutter/material.dart';

Row buildSeedWordsWidget(List<String> phrase, bool visible) {
final firstColumn = phrase
.getRange(0, 6)
.toList()
.asMap()
.entries
.map((entry) => SeedWord(entry.value, entry.key + 1, visible))
.toList();
final secondColumn = phrase
.getRange(6, 12)
.toList()
.asMap()
.entries
.map((entry) => SeedWord(entry.value, entry.key + 7, visible))
.toList();

return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(crossAxisAlignment: CrossAxisAlignment.start, children: firstColumn),
const SizedBox(width: 30),
Column(crossAxisAlignment: CrossAxisAlignment.start, children: secondColumn)
],
);
}

class SeedWord extends StatelessWidget {
final String? word;
final int? index;
final bool visibility;

const SeedWord(this.word, this.index, this.visibility, {super.key});

@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.fromLTRB(0, 5, 0, 5),
child: Row(
crossAxisAlignment: visibility ? CrossAxisAlignment.baseline : CrossAxisAlignment.end,
textBaseline: TextBaseline.alphabetic,
children: [
SizedBox(
width: 25.0,
child: Text(
'#$index',
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
),
),
const SizedBox(width: 5),
visibility
? SizedBox(
width: 100,
child: Text(
word!,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
)
: Container(width: 100, height: 24, color: Colors.grey[300]),
]));
}
}
3 changes: 2 additions & 1 deletion webapp/frontend/lib/settings/settings_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get_10101/common/color.dart';
import 'package:get_10101/settings/app_info_screen.dart';
import 'package:get_10101/settings/seed_screen.dart';

class SettingsScreen extends StatefulWidget {
static const route = "/settings";
Expand Down Expand Up @@ -63,7 +64,7 @@ class _SettingsScreenState extends State<SettingsScreen> with SingleTickerProvid
Expanded(
child: TabBarView(
controller: _tabController,
children: const [AppInfoScreen(), Text("Channel"), Text("Backup")],
children: const [AppInfoScreen(), Text("Channel"), SeedScreen()],
),
),
],
Expand Down
31 changes: 18 additions & 13 deletions webapp/frontend/lib/settings/settings_service.dart
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import 'package:http/http.dart' as http;
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:get_10101/common/http_client.dart';

class SettingsService {
const SettingsService();

Future<String> getNodeId() async {
// TODO(holzeis): this should come from the config
const port = "3001";
const host = "localhost";
final response = await HttpClientManager.instance.get(Uri(path: '/api/node'));

if (response.statusCode == 200) {
return response.body;
} else {
throw FlutterError("Failed to fetch node id");
}
}

try {
final response = await http.get(Uri.http('$host:$port', '/api/node'));
Future<List<String>> getSeedPhrase() async {
final response = await HttpClientManager.instance.get(Uri(path: '/api/seed'));

if (response.statusCode == 200) {
return response.body;
} else {
return "unknown";
}
} catch (e) {
return "unknown";
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw FlutterError("Failed to fetch seed phrase");
}
}
}
4 changes: 4 additions & 0 deletions webapp/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ pub async fn get_node_id() -> impl IntoResponse {
ln_dlc::get_node_pubkey().to_string()
}

pub async fn get_seed_phrase() -> Json<Vec<String>> {
Json(ln_dlc::get_seed_phrase())
}

#[derive(Serialize)]
pub struct OrderId {
id: Uuid,
Expand Down
2 changes: 2 additions & 0 deletions webapp/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::api::get_balance;
use crate::api::get_node_id;
use crate::api::get_onchain_payment_history;
use crate::api::get_positions;
use crate::api::get_seed_phrase;
use crate::api::get_unused_address;
use crate::api::post_new_order;
use crate::api::send_payment;
Expand Down Expand Up @@ -101,6 +102,7 @@ fn using_serve_dir(subscribers: Arc<AppSubscribers>, network: Network) -> Router
.route("/api/orders", post(post_new_order))
.route("/api/positions", get(get_positions))
.route("/api/node", get(get_node_id))
.route("/api/seed", get(get_seed_phrase))
.route("/main.dart.js", get(main_dart_handler))
.route("/flutter.js", get(flutter_js))
.route("/index.html", get(index_handler))
Expand Down

0 comments on commit 87d1298

Please sign in to comment.