diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..2826270 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,37 @@ +name: Build + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - lib/** + - .github/workflows/** + - native/** + pull_request: + +jobs: + build-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + - name: Install Rust & Prepare Complie + run: | + rustup update stable + cargo install rinf + rinf message + dart fix --apply + - name: Build + run: flutter build windows --release + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: windows-x64 + path: build/windows/x64/runner/Release/* + + + diff --git a/.gitignore b/.gitignore index 6677a43..37913bf 100644 --- a/.gitignore +++ b/.gitignore @@ -42,11 +42,11 @@ app.*.map.json /android/app/profile /android/app/release -app.config.json - # Rust related .cargo/ target/ # Generated messages */**/messages/ + +app.config.json diff --git a/assets/i18n/en_US/fields.yaml b/assets/i18n/en_US/fields.yaml index 6a7c131..4e7093d 100644 --- a/assets/i18n/en_US/fields.yaml +++ b/assets/i18n/en_US/fields.yaml @@ -1,4 +1,11 @@ home: Home install: Install +settings: Settings install.prepare: Prepare -install.auto_install: Auto Install +install.auto_install: Auto Install (Admin) +open: Open +configure: Configure +optional_features: Optional Features +language: Language +font: Font +need_admin_permission: Need Admin Permission (Exit and right click exe and "Run as administrator") diff --git a/assets/i18n/en_US/optional_features.md b/assets/i18n/en_US/optional_features.md new file mode 100644 index 0000000..c968241 --- /dev/null +++ b/assets/i18n/en_US/optional_features.md @@ -0,0 +1,9 @@ +# Mannual Install + +Find and check the features you're missing in "Turn Windows features on or off". + +If you can't find them, you can click the "Optional Features" Button. + +Then scroll to bottom and click "More Windows features". + +After checking, restart to complete the installation. diff --git a/assets/i18n/languages.yaml b/assets/i18n/languages.yaml index f726f65..4fea8f4 100644 --- a/assets/i18n/languages.yaml +++ b/assets/i18n/languages.yaml @@ -1,2 +1,2 @@ -English: en_US.yaml -简体中文: zh_CN.yaml +English: en_US +简体中文: zh_CN \ No newline at end of file diff --git a/assets/i18n/zh_CN/fields.yaml b/assets/i18n/zh_CN/fields.yaml index 8eddc17..fb76c84 100644 --- a/assets/i18n/zh_CN/fields.yaml +++ b/assets/i18n/zh_CN/fields.yaml @@ -1,4 +1,11 @@ home: 主页 install: 安装 install.prepare: 准备 -install.auto_install: 自动安装 \ No newline at end of file +install.auto_install: 自动安装 (管理员) +open: 打开 +configure: 配置 +optional_features: 可选功能 +settings: 设置 +language: 语言 +font: 字体 +need_admin_permission: 需要管理员权限 (退出并右键程序 "以管理员身份运行") \ No newline at end of file diff --git a/assets/i18n/zh_CN/optional_features.md b/assets/i18n/zh_CN/optional_features.md new file mode 100644 index 0000000..1eae737 --- /dev/null +++ b/assets/i18n/zh_CN/optional_features.md @@ -0,0 +1,9 @@ +# 手动安装 + +在"启用或关闭 Windows 功能"中找到并勾选你缺少的功能。 + +如果你找不到他们,你可以点击"可选功能"按钮。 + +然后滑到底部点击"更多Windows功能"。 + +勾选后重新启动即可完成安装。 \ No newline at end of file diff --git a/lib/i18n/constants.dart b/lib/i18n/constants.dart index f54e119..1446de8 100644 --- a/lib/i18n/constants.dart +++ b/lib/i18n/constants.dart @@ -2,4 +2,6 @@ const i18nFolder = "assets/i18n"; const i18nLanguages = "$i18nFolder/languages.yaml"; String i18nLanguage(String locale) => "$i18nFolder/$locale"; -String i18nLanguageFields(String locale) => "${i18nLanguage(locale)}/fields.yaml"; + +String i18nLanguageFile(String locale, String fileName) => + "${i18nLanguage(locale)}/$fileName"; diff --git a/lib/i18n/i18n.dart b/lib/i18n/i18n.dart index 6d2ee36..fc5b546 100644 --- a/lib/i18n/i18n.dart +++ b/lib/i18n/i18n.dart @@ -3,6 +3,8 @@ import 'dart:async'; import 'package:arche/arche.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:url_launcher/url_launcher_string.dart'; import 'package:wslconfigurer/i18n/constants.dart'; import 'package:yaml/yaml.dart'; @@ -19,8 +21,13 @@ class I18n { this.locale = locale; } - _fields = - _load(await rootBundle.loadString(i18nLanguageFields(this.locale))); + _fields = _load(await rootBundle.loadString(fileName("fields.yaml"))); + } + + String fileName(String fileName) => i18nLanguageFile(locale, fileName); + + Future loadString(String fileName) async { + return await rootBundle.loadString(this.fileName(fileName)); } String getOrKey(String translateKey) { @@ -47,4 +54,22 @@ extension I18nEx on BuildContext { T i18nBuilder(String translateKey, T Function(String text) builder) { return builder(ArcheBus.bus.of().getOrKey(translateKey)); } + + Widget i18nMarkdown(String fileName, [bool shrinkWrap = true]) { + return FutureBuilder( + future: ArcheBus.bus.of().loadString(fileName), + builder: (context, snapshot) { + var data = snapshot.data; + if (data == null) { + return const CircularProgressIndicator(); + } + + return MarkdownBody( + data: data, + shrinkWrap: shrinkWrap, + onTapLink: (text, href, title) => launchUrlString(href.toString()), + ); + }, + ); + } } diff --git a/lib/main.dart b/lib/main.dart index 4c3bacd..3db9009 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,26 +4,37 @@ import 'package:arche/arche.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; +import 'package:superuser/superuser.dart'; +import 'package:system_fonts/system_fonts.dart'; import 'package:wslconfigurer/i18n/i18n.dart'; import 'package:wslconfigurer/models/config.dart'; +import 'package:wslconfigurer/models/key.dart'; +import 'package:wslconfigurer/views/pages/settings.dart'; import 'package:wslconfigurer/views/widgets/basic.dart'; import 'package:wslconfigurer/views/pages/install.dart'; import 'package:rinf/rinf.dart'; import './messages/generated.dart'; void main() async { - await initializeRust(assignRustSignal); WidgetsFlutterBinding.ensureInitialized(); + await initializeRust(assignRustSignal); // Init Config var config = ArcheBus.bus .provideof(instance: AppConfigs(ArcheConfig.path("app.config.json"))); + if (config.font.has()) { + var font = await SystemFonts().loadFont(config.font.get()); + if (font == null) { + config.font.delete(); + } + } + // Init I18n var i18n = I18n(); await i18n.init(config.locale.tryGet()); ArcheBus.bus.provide(i18n); // Start Launch App appWindow.size = const Size(750, 550); - runApp(const MyApp()); + runApp(MyApp(key: appKey)); appWindow.show(); doWhenWindowReady(() { final win = appWindow; @@ -31,31 +42,46 @@ void main() async { win.minSize = initialSize; win.size = initialSize; win.alignment = Alignment.center; - win.title = "WSL Configurer"; + if (Superuser.isSuperuser) { + win.title = "WSL Configurer (Admin)"; + } else { + win.title = "WSL Configurer (User)"; + } + win.show(); }); } -class MyApp extends StatelessWidget { +class MyApp extends StatefulWidget { const MyApp({super.key}); + @override + State createState() => MyAppState(); +} + +class MyAppState extends State with RefreshMountedStateMixin { @override Widget build(BuildContext context) { + var configs = ArcheBus.bus.of(); return DynamicColorBuilder( builder: (lightDynamic, darkDynamic) => MaterialApp( theme: ThemeData( useMaterial3: true, colorScheme: lightDynamic, typography: Typography.material2021(), + fontFamily: configs.font.tryGet(), ), darkTheme: ThemeData( brightness: Brightness.dark, useMaterial3: true, colorScheme: darkDynamic, typography: Typography.material2021(), + fontFamily: configs.font.tryGet(), ), home: WindowTitleBarBox( - child: const HomePage(), + child: HomePage( + key: rootKey, + ), ), debugShowCheckedModeBanner: false, themeMode: ThemeMode.system, @@ -68,10 +94,10 @@ class HomePage extends StatefulWidget { const HomePage({super.key}); @override - State createState() => _HomePageState(); + State createState() => HomePageState(); } -class _HomePageState extends State { +class HomePageState extends State with RefreshMountedStateMixin { late AppLifecycleListener _lifecycleListener; @override @@ -134,6 +160,7 @@ class _HomePageState extends State { ).toItem(icon: const Icon(Icons.install_desktop)), 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 5c2cf68..1aafef1 100644 --- a/lib/models/config.dart +++ b/lib/models/config.dart @@ -6,4 +6,5 @@ class AppConfigs { : _generator = ConfigEntry.withConfig(config, generateMap: true); ConfigEntry get locale => _generator("locale"); + ConfigEntry get font => _generator("font"); } diff --git a/lib/models/key.dart b/lib/models/key.dart new file mode 100644 index 0000000..8f7ae9b --- /dev/null +++ b/lib/models/key.dart @@ -0,0 +1,5 @@ +import 'package:flutter/material.dart'; +import 'package:wslconfigurer/main.dart'; + +GlobalKey rootKey = GlobalKey(); +GlobalKey appKey = GlobalKey(); diff --git a/lib/views/pages/install.dart b/lib/views/pages/install.dart index 1ac2ce3..466dccd 100644 --- a/lib/views/pages/install.dart +++ b/lib/views/pages/install.dart @@ -2,7 +2,8 @@ import 'package:arche/arche.dart'; import 'package:arche/extensions/dialogs.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:wslconfigurer/controllers/ms_open.dart'; +import 'package:superuser/superuser.dart'; +import 'package:wslconfigurer/windows/ms_open.dart'; import 'package:wslconfigurer/i18n/i18n.dart'; import 'package:wslconfigurer/messages/windows.pb.dart'; import 'package:wslconfigurer/views/widgets/basic.dart'; @@ -45,15 +46,15 @@ class _InstallPageState extends State { ComplexDialog.instance.text( context: context, content: Wrap( + spacing: 8, direction: Axis.vertical, children: [ - const Text( - "Optional Features -> WSL/VirtualMachinePlatform"), + context.i18nMarkdown("optional_features.md", true), FilledButton( onPressed: () { openMSSetting("optionalfeatures"); }, - child: Text("Open"), + child: context.i18nText("optional_features"), ) ], ), @@ -63,11 +64,14 @@ class _InstallPageState extends State { ), title: Row( children: [ - context.i18nText("Open Windows Features"), + Text( + "${context.i18n.getOrKey("configure")} ${context.i18n.getOrKey("optional_features")}"), ], ), trailing: IconButton( - onPressed: () => setState(() {}), + onPressed: () => setState(() { + QueryOptionalFeature().sendSignalToRust(); + }), icon: const Icon(Icons.refresh), ), ) @@ -97,7 +101,9 @@ class _InstallPageState extends State { alignment: Alignment.centerRight, child: FilledButton( onPressed: () { - //TODO + ComplexDialog.instance.text( + context: context, + content: Text(Superuser.isSuperuser.toString())); }, child: context.i18nText("install.auto_install")), ), @@ -111,30 +117,6 @@ class _InstallPageState extends State { return ScrollableContainer( children: [ - ListTile( - leading: const Icon(FontAwesomeIcons.section), - title: context.i18nText("install.prepare"), - ), - Padding( - padding: const EdgeInsets.all(8), - child: Card.filled( - child: Column( - children: [ - ListTile( - title: const Text("HyperV"), - trailing: IconButton( - onPressed: () {}, - icon: const Icon(Icons.arrow_right_rounded)), - ), - const ListTile( - title: Text("WSL"), - trailing: Icon(Icons.check), - ) - ], - ), - ), - ), - divider8, ListTile( leading: const Icon(FontAwesomeIcons.section), title: context.i18nText("Install Linux Distribution"), diff --git a/lib/views/pages/settings.dart b/lib/views/pages/settings.dart index e69de29..4a5a513 100644 --- a/lib/views/pages/settings.dart +++ b/lib/views/pages/settings.dart @@ -0,0 +1,58 @@ +import 'package:arche/arche.dart'; +import 'package:flutter/material.dart'; +import 'package:system_fonts/system_fonts.dart'; +import 'package:wslconfigurer/i18n/i18n.dart'; +import 'package:wslconfigurer/models/config.dart'; +import 'package:wslconfigurer/models/key.dart'; +import 'package:wslconfigurer/views/widgets/basic.dart'; +import 'package:wslconfigurer/views/widgets/divider.dart'; + +class SettingsPage extends StatefulWidget { + const SettingsPage({super.key}); + + @override + State createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + @override + Widget build(BuildContext context) { + var configs = ArcheBus.bus.of(); + return ScrollableContainer( + children: [ + ListTile( + title: context.i18nText("language"), + trailing: PopupMenuButton( + initialValue: configs.locale.getOr("en_US"), + onSelected: (value) async { + configs.locale.write(value); + var i18n = I18n(); + await i18n.init(value); + ArcheBus.bus.replace(i18n); + rootKey.currentState?.setState(() {}); + setState(() {}); + }, + itemBuilder: (context) => context.i18n.avaiableLanguages.entries + .map((entry) => PopupMenuItem( + value: entry.value, + child: Text(entry.key), + )) + .toList(), + ), + ), + divider8, + ListTile( + title: context.i18nText("font"), + trailing: SystemFontSelector( + initial: configs.font.tryGet(), + onFontSelected: (value) { + configs.font.write(value); + setState(() {}); + appKey.currentState?.refreshMounted(); + }, + ), + ) + ], + ); + } +} diff --git a/lib/controllers/ms_open.dart b/lib/windows/ms_open.dart similarity index 100% rename from lib/controllers/ms_open.dart rename to lib/windows/ms_open.dart diff --git a/lib/windows/sh.dart b/lib/windows/sh.dart new file mode 100644 index 0000000..c6749cf --- /dev/null +++ b/lib/windows/sh.dart @@ -0,0 +1,19 @@ +import 'dart:io'; + +import 'package:arche/arche.dart'; +import 'package:arche/extensions/dialogs.dart'; +import 'package:flutter/material.dart'; +import 'package:superuser/superuser.dart'; +import 'package:wslconfigurer/i18n/i18n.dart'; + +void su( + BuildContext context, + Function() run, +) { + if (Superuser.isSuperuser) { + run(); + } else { + ComplexDialog.instance.text( + context: context, content: context.i18nText("need_admin_permission")); + } +} diff --git a/pubspec.lock b/pubspec.lock index 6d94d1e..1ad0a11 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,18 @@ packages: dependency: "direct main" description: name: arche - sha256: dc4ce5c40538a65dd1eb372c1b6bb13f6abe023ec22f2b7a09821c6d6808a507 + sha256: b5e42ed6b4f49c6d23cad1b6cc4515370bb6f25ac2acfd64791836b6f8c75861 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" async: dependency: transitive description: @@ -158,6 +166,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + flutter_markdown: + dependency: "direct main" + description: + name: flutter_markdown + sha256: "2e8a801b1ded5ea001a4529c97b1f213dcb11c6b20668e081cafb23468593514" + url: "https://pub.dev" + source: hosted + version: "0.7.3" flutter_test: dependency: "direct dev" description: flutter @@ -224,6 +240,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + markdown: + dependency: transitive + description: + name: markdown + sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051 + url: "https://pub.dev" + source: hosted + version: "7.2.2" matcher: dependency: transitive description: @@ -357,6 +381,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + system_fonts: + dependency: "direct main" + description: + name: system_fonts + sha256: c220ca03cba3b510c7fa6cd8aa56f9b77c1649909cec6b99648f5201117e8914 + url: "https://pub.dev" + source: hosted + version: "1.0.1" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 649ea9a..3bea42e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: cupertino_icons: ^1.0.6 dio: ^5.5.0+1 dynamic_color: ^1.7.0 - arche: ^1.0.0 + arche: ^1.0.1 bitsdojo_window: ^0.1.6 url_launcher: ^6.3.0 yaml: ^3.1.2 @@ -44,6 +44,8 @@ dependencies: superuser: ^1.0.1 rinf: ^6.14.2 protobuf: ^3.1.0 + system_fonts: ^1.0.1 + flutter_markdown: ^0.7.3 dev_dependencies: flutter_test: @@ -73,7 +75,8 @@ flutter: assets: - assets/i18n/ - assets/i18n/en_US/ - + - assets/i18n/zh_CN/ + # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware