From 81370d27a93e828a491b748623873004de2aed9f Mon Sep 17 00:00:00 2001 From: otsmr Date: Wed, 22 Apr 2026 17:15:45 +0200 Subject: [PATCH] improve faq --- dependencies | 2 +- .../visual/views/settings/help/faq.view.dart | 57 ++++++++++++------- .../settings/help/faq/faq_markdown.view.dart | 32 +++++++++++ .../user_discovery_enabled.comp.dart | 4 +- pubspec.lock | 15 +++++ pubspec.yaml | 3 + 6 files changed, 91 insertions(+), 22 deletions(-) create mode 100644 lib/src/visual/views/settings/help/faq/faq_markdown.view.dart diff --git a/dependencies b/dependencies index 24d048b4..4e6d3eb9 160000 --- a/dependencies +++ b/dependencies @@ -1 +1 @@ -Subproject commit 24d048b4abbe5c266b09965cc6f3ebdf83f97855 +Subproject commit 4e6d3eb9b7602030bf3d4b6dcb92ecc0f23de386 diff --git a/lib/src/visual/views/settings/help/faq.view.dart b/lib/src/visual/views/settings/help/faq.view.dart index c6e10da0..c746dbea 100644 --- a/lib/src/visual/views/settings/help/faq.view.dart +++ b/lib/src/visual/views/settings/help/faq.view.dart @@ -2,11 +2,13 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; +import 'package:twonly/globals.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:url_launcher/url_launcher.dart'; +import 'package:twonly/src/visual/views/settings/help/faq/faq_markdown.view.dart'; class FaqView extends StatefulWidget { const FaqView({super.key}); @@ -19,7 +21,7 @@ class _FaqViewState extends State { Map? _faqData; String? _locale; late String domain; - bool noInternet = false; + bool _noInternet = false; @override void initState() { @@ -29,30 +31,40 @@ class _FaqViewState extends State { } Future _fetchFAQData() async { + final cacheFile = File('${AppEnvironment.cacheDir}/faq.json'); try { final response = await http.get(Uri.parse('$domain/faq.json')); if (response.statusCode == 200) { + final jsonData = utf8.decode(response.bodyBytes); setState(() { - _faqData = - json.decode(utf8.decode(response.bodyBytes)) - as Map?; - noInternet = false; + _faqData = json.decode(jsonData) as Map?; + + _noInternet = false; }); + cacheFile.writeAsStringSync(jsonData); } else { Log.error('FAQ got ${response.statusCode}'); } } catch (e) { Log.error(e); setState(() { - noInternet = true; + _noInternet = true; + }); + } + if (_noInternet && cacheFile.existsSync()) { + final jsonData = cacheFile.readAsStringSync(); + setState(() { + _faqData = json.decode(jsonData) as Map?; + + _noInternet = false; }); } } @override Widget build(BuildContext context) { - if (noInternet) { + if (_noInternet) { return Scaffold( appBar: AppBar( title: Text(context.lang.settingsHelpFAQ), @@ -73,26 +85,39 @@ class _FaqViewState extends State { } final faq = _faqData![_locale ?? 'en'] as Map; + final sortedCategories = faq.entries.toList() + ..sort((a, b) { + final aPriority = (a.value['meta']['priority'] as num? ?? 0).toInt(); + final bPriority = (b.value['meta']['priority'] as num? ?? 0).toInt(); + return bPriority.compareTo(aPriority); + }); return Scaffold( appBar: AppBar( title: Text(context.lang.settingsHelpFAQ), ), body: ListView.builder( - itemCount: faq.keys.length, + itemCount: sortedCategories.length, itemBuilder: (context, index) { - final category = faq.keys.elementAt(index); - final categoryData = faq[category]; + final categoryData = sortedCategories[index].value; return Card( child: ExpansionTile( title: Text(categoryData['meta']['title'] as String), subtitle: Text(categoryData['meta']['desc'] as String), + shape: const RoundedRectangleBorder(), + backgroundColor: context.color.surfaceContainer, + collapsedShape: const RoundedRectangleBorder(), children: categoryData['questions'].map((question) { return ListTile( title: Text(question['title'] as String), - onTap: () => _launchURL(question['path'] as String), + onTap: () => context.navPush( + FaqMarkdownView( + markdown: question['body'] as String, + title: question['title'] as String, + ), + ), ); }).toList() as List, @@ -102,12 +127,4 @@ class _FaqViewState extends State { ), ); } - - Future _launchURL(String path) async { - try { - await launchUrl(Uri.parse('$domain$path')); - } catch (e) { - Log.error('Could not launch $e'); - } - } } diff --git a/lib/src/visual/views/settings/help/faq/faq_markdown.view.dart b/lib/src/visual/views/settings/help/faq/faq_markdown.view.dart new file mode 100644 index 00000000..f4876137 --- /dev/null +++ b/lib/src/visual/views/settings/help/faq/faq_markdown.view.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown_plus/flutter_markdown_plus.dart'; +import 'package:twonly/src/database/daos/contacts.dao.dart'; + +class FaqMarkdownView extends StatelessWidget { + const FaqMarkdownView({ + required this.markdown, + required this.title, + super.key, + }); + + final String title; + final String markdown; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(substringBy(title, 30)), + ), + body: Markdown( + data: markdown, + padding: const EdgeInsets.only( + top: 16, + left: 16, + right: 16, + bottom: 40, + ), + ), + ); + } +} diff --git a/lib/src/visual/views/settings/privacy/user_discovery/components/user_discovery_enabled.comp.dart b/lib/src/visual/views/settings/privacy/user_discovery/components/user_discovery_enabled.comp.dart index 06954bce..a8e63acb 100644 --- a/lib/src/visual/views/settings/privacy/user_discovery/components/user_discovery_enabled.comp.dart +++ b/lib/src/visual/views/settings/privacy/user_discovery/components/user_discovery_enabled.comp.dart @@ -3,7 +3,9 @@ import 'dart:async'; import 'package:drift/drift.dart' show Value; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:twonly/locator.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/user_discovery/types.pb.dart'; @@ -160,7 +162,7 @@ class _UserDiscoveryEnabledCompState extends State { subtitle: Text( context.lang.userDiscoveryEnabledFaq, ), - // onTap: _disableUserDiscovery, + onTap: () => context.push(Routes.settingsHelpFaq), ), const Divider(), ListTile( diff --git a/pubspec.lock b/pubspec.lock index 4689f5cf..46c4c678 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -760,6 +760,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_markdown_plus: + dependency: "direct main" + description: + path: "dependencies/flutter_markdown_plus" + relative: true + source: path + version: "1.0.7" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -1261,6 +1268,14 @@ packages: relative: true source: path version: "3.3.2" + markdown: + dependency: transitive + description: + name: markdown + sha256: ee85086ad7698b42522c6ad42fe195f1b9898e4d974a1af4576c1a3a176cada9 + url: "https://pub.dev" + source: hosted + version: "7.3.1" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 301ace8e..e07d5d5d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -85,6 +85,7 @@ dependencies: hand_signature: ^3.0.3 flutter_sharing_intent: ^2.0.4 no_screenshot: ^0.3.1 + flutter_markdown_plus: ^1.0.7 # With high download. (But should be checked nonetheless.) app_links: ^7.0.0 # 1.6 mio @@ -153,6 +154,8 @@ dependency_overrides: path: ./dependencies/qr_flutter no_screenshot: path: ./dependencies/no_screenshot + flutter_markdown_plus: + path: ./dependencies/flutter_markdown_plus camera_android_camerax: # path: ../flutter-packages/packages/camera/camera_android_camerax git: