From 81c8557bd5c0567e7ed7db6a6c546f09c0a9cecc Mon Sep 17 00:00:00 2001 From: H2Sxxa Date: Tue, 23 Jul 2024 16:57:08 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20Clean=20dependency=20and=20WSL?= =?UTF-8?q?=20Distribution=20Fetch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/i18n/en_US/fields.yaml | 6 +- assets/i18n/zh_CN/fields.yaml | 4 + lib/main.dart | 91 ++++++------ lib/models/config.dart | 5 +- lib/models/distribution.dart | 45 ++++++ lib/views/pages/doctor.dart | 0 lib/views/pages/install.dart | 264 ++++++++++++++------------------- lib/views/pages/settings.dart | 38 ++++- lib/views/widgets/optfeat.dart | 128 ++++++++++++++++ pubspec.lock | 44 ++---- pubspec.yaml | 4 +- 11 files changed, 393 insertions(+), 236 deletions(-) create mode 100644 lib/models/distribution.dart create mode 100644 lib/views/pages/doctor.dart create mode 100644 lib/views/widgets/optfeat.dart diff --git a/assets/i18n/en_US/fields.yaml b/assets/i18n/en_US/fields.yaml index 1808bd2..c569a09 100644 --- a/assets/i18n/en_US/fields.yaml +++ b/assets/i18n/en_US/fields.yaml @@ -12,4 +12,8 @@ need_admin_permission: Need Admin Permission (Exit and right click exe and "Run success: Susscess error: Error output: Output -doctor: Doctor \ No newline at end of file +doctor: Doctor +install.install_linux_distro: Install Linux Distribution +microsoft_store: MicroSoft Store +recommand: Recommand +distro_info_url: Linux Distribution Info Url \ No newline at end of file diff --git a/assets/i18n/zh_CN/fields.yaml b/assets/i18n/zh_CN/fields.yaml index dd30347..1165bf9 100644 --- a/assets/i18n/zh_CN/fields.yaml +++ b/assets/i18n/zh_CN/fields.yaml @@ -13,3 +13,7 @@ success: 成功 error: 错误 output: 输出 doctor: 诊断 +install.install_linux_distro: 安装 Linux 发行版 +microsoft_store: 微软商店 +recommand: 推荐 +distro_info_url: Linux 发行版信息地址 \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index d63a96b..dc62abb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -22,6 +22,9 @@ void main() async { // Init Config var config = ArcheBus.bus .provideof(instance: AppConfigs(ArcheConfig.path("app.config.json"))); + + config.distroInfoUrl.getOrWrite(AppConfigs.defaultDistroInfoUrl); + if (config.font.has()) { var font = await SystemFonts().loadFont(config.font.get()); if (font == null) { @@ -120,53 +123,55 @@ class HomePageState extends State with RefreshMountedStateMixin { @override Widget build(BuildContext context) { - return NavigationView( - builder: (context, vertical, horizontal, state) => Column( - children: [ - Container( - color: Theme.of(context).colorScheme.surfaceContainer, - child: WindowTitleBarBox( + return Scaffold( + body: NavigationView( + builder: (context, vertical, horizontal, state) => Column( + children: [ + Container( + color: Theme.of(context).colorScheme.surfaceContainer, + child: WindowTitleBarBox( + child: Row( + children: [ + Expanded(child: MoveWindow()), + const WindowButtons() + ], + ), + ), + ), + Expanded( child: Row( children: [ - Expanded(child: MoveWindow()), - const WindowButtons() + GestureDetector( + behavior: HitTestBehavior.translucent, + child: horizontal(), + onPanStart: (details) { + appWindow.startDragging(); + }, + ), + state.content ], ), - ), - ), - Expanded( - child: Row( - children: [ - GestureDetector( - behavior: HitTestBehavior.translucent, - child: horizontal(), - onPanStart: (details) { - appWindow.startDragging(); - }, - ), - state.content - ], - ), - ) - ], - ), - backgroundColor: Theme.of(context).colorScheme.surfaceContainer, - items: [ - PageContainer( - title: context.i18n.getOrKey("home"), - ).toItem(icon: const Icon(Icons.home)), - PageContainer( - title: context.i18n.getOrKey("install"), - child: const InstallPage(), - ).toItem(icon: const Icon(Icons.install_desktop)), - PageContainer( - title: context.i18n.getOrKey("doctor"), - ).toItem(icon: const Icon(FontAwesomeIcons.userDoctor)), - PageContainer( - title: context.i18n.getOrKey("settings"), - child: const SettingsPage(), - ).toItem(icon: const Icon(Icons.settings)), - ]); + ) + ], + ), + backgroundColor: Theme.of(context).colorScheme.surfaceContainer, + items: [ + PageContainer( + title: context.i18n.getOrKey("home"), + ).toItem(icon: const Icon(Icons.home)), + PageContainer( + title: context.i18n.getOrKey("install"), + child: const InstallPage(), + ).toItem(icon: const Icon(Icons.install_desktop)), + PageContainer( + title: context.i18n.getOrKey("doctor"), + ).toItem(icon: const Icon(FontAwesomeIcons.userDoctor)), + PageContainer( + title: context.i18n.getOrKey("settings"), + child: const SettingsPage(), + ).toItem(icon: const Icon(Icons.settings)), + ]), + ); } } diff --git a/lib/models/config.dart b/lib/models/config.dart index 1aafef1..21f1956 100644 --- a/lib/models/config.dart +++ b/lib/models/config.dart @@ -1,10 +1,13 @@ import 'package:arche/arche.dart'; class AppConfigs { - final ConfigEntry Function(String configKey) _generator; + final ConfigEntry Function(String key) _generator; AppConfigs(ArcheConfig config) : _generator = ConfigEntry.withConfig(config, generateMap: true); ConfigEntry get locale => _generator("locale"); ConfigEntry get font => _generator("font"); + ConfigEntry get distroInfoUrl => _generator("distro_info_url"); + static const defaultDistroInfoUrl = + "https://raw.githubusercontent.com/microsoft/WSL/master/distributions/DistributionInfo.json"; } diff --git a/lib/models/distribution.dart b/lib/models/distribution.dart new file mode 100644 index 0000000..4659e83 --- /dev/null +++ b/lib/models/distribution.dart @@ -0,0 +1,45 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:arche/arche.dart'; +import 'package:wslconfigurer/models/config.dart'; + +class LinuxDistribution { + late final String name; + late final String friendlyName; + late final String storeAppId; + late final bool amd64; + late final bool arm64; + late final String? amd64PackageUrl; + late final String? arm64PackageUrl; + late final String packageFamilyName; + static Future> fetch() async { + var url = ArcheBus() + .of() + .distroInfoUrl + .getOr(AppConfigs.defaultDistroInfoUrl); + var request = await HttpClient().getUrl(Uri.parse(url)); + var response = await request.close(); + Iterable json = jsonDecode( + await response.transform(utf8.decoder).join())["Distributions"]; + + return json + .map( + (distro) => LinuxDistribution() + ..name = distro["Name"] + ..friendlyName = distro["FriendlyName"] + ..storeAppId = distro["StoreAppId"] + ..amd64 = distro["Amd64"] + ..arm64 = distro["Arm64"] + ..amd64PackageUrl = distro["Amd64PackageUrl"] + ..arm64PackageUrl = distro["Arm64PackageUrl"] + ..packageFamilyName = distro["PackageFamilyName"], + ) + .toList(); + } + + @override + String toString() { + return "LinuxDistribution: $name"; + } +} diff --git a/lib/views/pages/doctor.dart b/lib/views/pages/doctor.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/views/pages/install.dart b/lib/views/pages/install.dart index 066d660..48ce985 100644 --- a/lib/views/pages/install.dart +++ b/lib/views/pages/install.dart @@ -1,15 +1,15 @@ import 'package:arche/arche.dart'; import 'package:arche/extensions/dialogs.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:rinf/rinf.dart'; -import 'package:wslconfigurer/views/widgets/process.dart'; -import 'package:wslconfigurer/windows/ms_open.dart'; import 'package:wslconfigurer/i18n/i18n.dart'; -import 'package:wslconfigurer/messages/windows.pb.dart'; +import 'package:wslconfigurer/models/distribution.dart'; import 'package:wslconfigurer/views/widgets/basic.dart'; import 'package:wslconfigurer/views/widgets/divider.dart'; -import 'package:wslconfigurer/windows/sh.dart'; +import 'package:wslconfigurer/views/widgets/optfeat.dart'; +import 'package:wslconfigurer/windows/ms_open.dart'; class InstallPage extends StatefulWidget { const InstallPage({super.key}); @@ -20,166 +20,122 @@ class InstallPage extends StatefulWidget { class _InstallPageState extends State { @override - void initState() { - super.initState(); - - QueryOptionalFeature().sendSignalToRust(); - } - - Widget buildWidget(BuildContext context, - AsyncSnapshot> snapshot) { - var message = snapshot.data; - if (message == null) { - return const Center( - child: CircularProgressIndicator(), - ); - } - - var features = message.message.features; - - if (features.fold(0, (state, feat) => state + feat.installState) != 2) { - return ScrollableContainer( - key: const ValueKey("prepare"), + Widget build(BuildContext context) { + return CheckOptionalFeatureWidget( + nextWidget: ScrollableContainer( + key: const ValueKey(true), children: [ ListTile( - leading: IconButton( - onPressed: () { - ComplexDialog.instance.text( - context: context, - content: Wrap( - spacing: 8, - direction: Axis.vertical, - children: [ - context.i18nMarkdown("optional_features.md", true), - FilledButton( - onPressed: () { - openMSSetting("optionalfeatures"); - }, - child: context.i18nText("optional_features"), - ) - ], - ), - ); - }, - icon: const Icon(Icons.help), - ), - title: Row( - children: [ - Text( - "${context.i18n.getOrKey("configure")} ${context.i18n.getOrKey("optional_features")}"), - ], - ), - trailing: IconButton( - onPressed: () => setState(() { - QueryOptionalFeature().sendSignalToRust(); - }), - icon: const Icon(Icons.refresh), - ), + leading: const Icon(FontAwesomeIcons.section), + title: context.i18nText("install.install_linux_distro"), ), - ...features.map( - (feat) => ListTile( - title: Text(feat.caption), - subtitle: Text(feat.name), - trailing: feat.installState == 1 - ? const Icon( - Icons.check, - color: Colors.green, - ) - : IconButton( - icon: const Icon(Icons.download), - onPressed: () { - su( - context, - () => enableFeature(feat.name).then( - (process) => ComplexDialog.instance - .text( - context: context, - title: context.i18nText("output"), - content: SingleChildScrollView( - child: ProcessText( - process: process, - ), - ), - ) - .then( - (_) => setState(() { - QueryOptionalFeature().sendSignalToRust(); - }), - ), - ), - ); - }, - ), - ), - ) - ], - ); - } - - return ScrollableContainer( - key: const ValueKey("install"), - children: [ - ListTile( - leading: const Icon(FontAwesomeIcons.section), - title: context.i18nText("Install Linux Distribution"), - ), - Padding( - padding: const EdgeInsets.all(8), - child: Card.filled( + Padding( + padding: const EdgeInsets.all(8), child: Column( children: [ - ListTile( - title: const Text("Ubuntu"), - trailing: IconButton( - onPressed: () {}, icon: const Icon(Icons.open_in_new)), + FutureBuilder( + future: LinuxDistribution.fetch(), + builder: (context, snapshot) { + var data = snapshot.data; + if (data == null) { + return const CircularProgressIndicator(); + } + + return Card.filled( + child: AnimationLimiter( + child: Column( + children: AnimationConfiguration.toStaggeredList( + duration: const Duration(milliseconds: 375), + childAnimationBuilder: (widget) => SlideAnimation( + horizontalOffset: 50.0, + child: FadeInAnimation( + child: widget, + ), + ), + children: data + .map( + (distro) => ListTile( + title: Text(distro.friendlyName), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () { + var messager = + ScaffoldMessenger.of(context); + messager.clearSnackBars(); + messager.showSnackBar( + const SnackBar( + content: Text("Copyied!"), + ), + ); + + Clipboard.setData(ClipboardData( + text: + "wsl.exe --install -d ${distro.name}")); + }, + icon: const Icon(Icons.copy), + ), + IconButton( + onPressed: () { + ComplexDialog.instance.text( + context: context, + ); + }, + icon: const Icon(Icons.terminal), + ), + IconButton( + onPressed: () => openMSStoreProduct( + distro.storeAppId), + icon: const Icon(Icons.store), + ), + ], + )), + ) + .toList(), + ), + ), + ), + ); + }, ), - ListTile( - title: const Text("ArchLinux"), - trailing: IconButton( - onPressed: () {}, icon: const Icon(Icons.open_in_new)), - ) - ], - ), - ), - ), - divider8, - ListTile( - leading: const Icon(FontAwesomeIcons.section), - title: context.i18nText("Install Essential Packages(Optional)"), - ), - Padding( - padding: const EdgeInsets.all(8), - child: Card.filled( - child: Column( - children: [ - ListTile( - title: const Text("C (GCC)"), - trailing: IconButton( - onPressed: () {}, icon: const Icon(Icons.copy)), + const ListTile( + title: Text("Install from File"), + trailing: Text("TODO"), ), - ListTile( - title: const Text("C (Clang)"), - trailing: IconButton( - onPressed: () {}, icon: const Icon(Icons.copy)), + const ListTile( + title: Text("Upgrade to WSL2"), + trailing: Text("TODO"), ), ], ), ), - ), - ], - ); - } - - @override - Widget build(BuildContext context) { - return StreamBuilder( - stream: OptionFeatures.rustSignalStream, - builder: (context, snapshot) { - return AnimatedSwitcher( - duration: Durations.medium4, - child: buildWidget(context, snapshot), - ); - }, + divider8, + ListTile( + leading: const Icon(FontAwesomeIcons.section), + title: context.i18nText("Install Essential Packages(Optional)"), + ), + Padding( + padding: const EdgeInsets.all(8), + child: Card.filled( + child: Column( + children: [ + ListTile( + title: const Text("C (GCC)"), + trailing: IconButton( + onPressed: () {}, icon: const Icon(Icons.copy)), + ), + ListTile( + title: const Text("C (Clang)"), + trailing: IconButton( + onPressed: () {}, icon: const Icon(Icons.copy)), + ), + ], + ), + ), + ), + ], + ), ); } } diff --git a/lib/views/pages/settings.dart b/lib/views/pages/settings.dart index b2e0b15..d917e4c 100644 --- a/lib/views/pages/settings.dart +++ b/lib/views/pages/settings.dart @@ -1,4 +1,5 @@ import 'package:arche/arche.dart'; +import 'package:arche/extensions/dialogs.dart'; import 'package:flutter/material.dart'; import 'package:system_fonts/system_fonts.dart'; import 'package:wslconfigurer/i18n/i18n.dart'; @@ -81,7 +82,42 @@ class _SettingsPageState extends State { .toList(); }, ), - ) + ), + divider8, + ListTile( + title: context.i18nText("distro_info_url"), + subtitle: Text( + configs.distroInfoUrl.getOr(AppConfigs.defaultDistroInfoUrl)), + trailing: IconButton( + onPressed: () { + ComplexDialog.instance + .input( + controller: TextEditingController( + text: configs.distroInfoUrl + .getOr(AppConfigs.defaultDistroInfoUrl)), + context: context, + title: Row( + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: IconButton( + onPressed: Navigator.of(context).pop, + icon: const Icon(Icons.restore), + ), + ), + const Text("Input") + ], + ), + ) + .then( + (url) => setState(() { + configs.distroInfoUrl + .write(url ?? AppConfigs.defaultDistroInfoUrl); + }), + ); + }, + icon: const Icon(Icons.edit)), + ), ], ); } diff --git a/lib/views/widgets/optfeat.dart b/lib/views/widgets/optfeat.dart new file mode 100644 index 0000000..b711ccd --- /dev/null +++ b/lib/views/widgets/optfeat.dart @@ -0,0 +1,128 @@ +import 'package:arche/arche.dart'; +import 'package:arche/extensions/dialogs.dart'; +import 'package:flutter/material.dart'; +import 'package:rinf/rinf.dart'; +import 'package:wslconfigurer/i18n/i18n.dart'; +import 'package:wslconfigurer/messages/windows.pb.dart'; +import 'package:wslconfigurer/views/widgets/basic.dart'; +import 'package:wslconfigurer/views/widgets/process.dart'; +import 'package:wslconfigurer/windows/ms_open.dart'; +import 'package:wslconfigurer/windows/sh.dart'; + +class CheckOptionalFeatureWidget extends StatefulWidget { + final Widget? nextWidget; + + const CheckOptionalFeatureWidget({super.key, this.nextWidget}); + + @override + State createState() => _CheckOptionalFeatureWidgetState(); +} + +class _CheckOptionalFeatureWidgetState + extends State { + @override + void initState() { + super.initState(); + QueryOptionalFeature().sendSignalToRust(); + } + + Widget buildWidget(BuildContext context, + AsyncSnapshot> snapshot) { + var signal = snapshot.data; + if (signal == null) { + return const CircularProgressIndicator(); + } + + var feats = signal.message.features; + var featscount = feats.fold(0, (count, feat) => count + feat.installState); + + if (featscount == 2 && widget.nextWidget != null) { + return widget.nextWidget!; + } + + return ScrollableContainer( + key: const ValueKey(false), + children: [ + ListTile( + leading: IconButton( + onPressed: () { + ComplexDialog.instance.text( + context: context, + content: Wrap( + spacing: 8, + direction: Axis.vertical, + children: [ + context.i18nMarkdown("optional_features.md", true), + FilledButton( + onPressed: () => openMSSetting("optionalfeatures"), + child: context.i18nText("optional_features"), + ) + ], + ), + ); + }, + icon: const Icon(Icons.help), + ), + title: Row( + children: [ + Text( + "${context.i18n.getOrKey("configure")} ${context.i18n.getOrKey("optional_features")}"), + ], + ), + trailing: IconButton( + onPressed: QueryOptionalFeature().sendSignalToRust, + icon: const Icon(Icons.refresh), + ), + ), + ...feats.map( + (feat) => ListTile( + title: Text(feat.caption), + subtitle: Text(feat.name), + trailing: feat.installState == 1 + ? const Icon( + Icons.check, + color: Colors.green, + ) + : IconButton( + icon: const Icon(Icons.download), + onPressed: () { + su( + context, + () => enableFeature(feat.name).then( + (process) => ComplexDialog.instance + .text( + context: context, + title: context.i18nText("output"), + content: SingleChildScrollView( + child: ProcessText( + process: process, + ), + ), + ) + .then( + (_) => + QueryOptionalFeature().sendSignalToRust(), + ), + ), + ); + }, + ), + ), + ) + ], + ); + } + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: OptionFeatures.rustSignalStream, + builder: (context, snapshot) { + return AnimatedSwitcher( + duration: Durations.medium4, + child: buildWidget(context, snapshot), + ); + }, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 1ad0a11..a1306d7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: "direct main" description: name: arche - sha256: b5e42ed6b4f49c6d23cad1b6cc4515370bb6f25ac2acfd64791836b6f8c75861 + sha256: b11a130d4ef9bbcf5c3651113b6e57ca0d35da60f8cc7a1ca495764015e0165d url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.1" args: dependency: transitive description: @@ -105,22 +105,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" - dio: - dependency: "direct main" - description: - name: dio - sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714 - url: "https://pub.dev" - source: hosted - version: "5.5.0+1" - dio_web_adapter: - dependency: transitive - description: - name: dio_web_adapter - sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac" - url: "https://pub.dev" - source: hosted - version: "1.0.1" dynamic_color: dependency: "direct main" description: @@ -174,6 +158,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.3" + flutter_staggered_animations: + dependency: "direct main" + description: + name: flutter_staggered_animations + sha256: "81d3c816c9bb0dca9e8a5d5454610e21ffb068aedb2bde49d2f8d04f75538351" + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter_test: dependency: "direct dev" description: flutter @@ -192,14 +184,6 @@ packages: url: "https://pub.dev" source: hosted version: "10.7.0" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.dev" - source: hosted - version: "4.0.2" internet_connection_checker: dependency: transitive description: @@ -405,14 +389,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.dev" - source: hosted - version: "1.3.2" url_launcher: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 3bea42e..741646e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,9 +34,8 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.6 - dio: ^5.5.0+1 dynamic_color: ^1.7.0 - arche: ^1.0.1 + arche: ^1.1.1 bitsdojo_window: ^0.1.6 url_launcher: ^6.3.0 yaml: ^3.1.2 @@ -46,6 +45,7 @@ dependencies: protobuf: ^3.1.0 system_fonts: ^1.0.1 flutter_markdown: ^0.7.3 + flutter_staggered_animations: ^1.1.1 dev_dependencies: flutter_test: