diff --git a/lib/main.dart b/lib/main.dart index f45ffe2..59f9f36 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,18 +1,23 @@ import 'package:flutter/material.dart'; +import 'package:grassh_renew/src/config/global_config.dart'; import 'package:grassh_renew/src/ui/main.dart'; import 'package:grassh_renew/src/util/platform.dart'; +import 'package:grassh_renew/src/util/screen.dart'; import 'package:window_manager/window_manager.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); + await GlobalConfig.init(); + // Initialize PC Window if (PlatformUtil.isPC) { await windowManager.ensureInitialized(); - WindowOptions windowOptions = const WindowOptions( - size: Size(1000, 800), - minimumSize: Size(1000, 800), + WindowOptions windowOptions = WindowOptions( + size: Size(1000, ScreenUtil.height < 800 ? ScreenUtil.height : 800), + minimumSize: + Size(1000, ScreenUtil.height < 800 ? ScreenUtil.height : 800), center: true, backgroundColor: Colors.transparent, skipTaskbar: false, diff --git a/lib/src/config/config.dart b/lib/src/config/config.dart new file mode 100644 index 0000000..ec05164 --- /dev/null +++ b/lib/src/config/config.dart @@ -0,0 +1,56 @@ +import 'dart:io'; + +import 'package:toml/toml.dart'; + +class Config { + late Map conf; + String path; + + Config({ + required this.path, + required Map defaultConfig, + }) { + conf = defaultConfig; + init(path, defaultConfig); + } + + init(String path, Map defaultConfig) { + var f = File(path); + _configFileCheck(f); + read(); + } + + save() { + var f = File(path); + _configFileCheck(f); + try { + var config = TomlDocument.fromMap(conf).toString(); + f.writeAsStringSync(config); + } catch (e) { + rethrow; + } + } + + read() { + var f = File(path); + _configFileCheck(f); + var ctx = f.readAsStringSync(); + try { + conf = TomlDocument.parse(ctx).toMap(); + } catch (e) { + rethrow; + } + } + + _configFileCheck(File f) { + if (!f.existsSync()) { + f.createSync(); + try { + var config = TomlDocument.fromMap(conf).toString(); + f.writeAsStringSync(config); + } catch (e) { + rethrow; + } + } + } +} diff --git a/lib/src/config/global_config.dart b/lib/src/config/global_config.dart new file mode 100644 index 0000000..13f2436 --- /dev/null +++ b/lib/src/config/global_config.dart @@ -0,0 +1,21 @@ +import 'package:grassh_renew/src/config/config.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class GlobalConfig { + static late PackageInfo packageInfo; + static late Config config; + + static init() async { + packageInfo = await PackageInfo.fromPlatform(); + + // config = Config( + // path: "./config.toml", + // defaultConfig: { + // "version": 1, + // "global": { + // "language": "zh-CN", + // }, + // }, + // ); + } +} diff --git a/lib/src/ui/component/appbar/action_button.dart b/lib/src/ui/component/appbar/action_button.dart index 3298192..25fd079 100644 --- a/lib/src/ui/component/appbar/action_button.dart +++ b/lib/src/ui/component/appbar/action_button.dart @@ -17,16 +17,6 @@ class _ActionButtonState extends State { Widget build(BuildContext context) { return Row( children: [ - Button( - icon: - isSidebarLeftOpen ? Codicon.sidebarLeft : Codicon.sidebarLeftOff, - callback: () { - setState(() { - isSidebarLeftOpen = !isSidebarLeftOpen; - }); - }, - ), - const SizedBox(width: 5), Button( icon: isSidebarRightOpen ? Codicon.sidebarRight diff --git a/lib/src/ui/component/sidebar/left.dart b/lib/src/ui/component/sidebar/left.dart new file mode 100644 index 0000000..2eaaac1 --- /dev/null +++ b/lib/src/ui/component/sidebar/left.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:grassh_renew/src/ui/component/sidebar/main.dart'; +import 'package:grassh_renew/src/ui/component/sidebar/tab.dart'; +import 'package:grassh_renew/src/ui/icons/codicon.dart'; + +class LeftSideBar extends StatefulWidget { + final LeftSideBarController controller; + const LeftSideBar({super.key, required this.controller}); + + @override + State createState() => _LeftSideBarState(); +} + +class _LeftSideBarState extends State { + @override + Widget build(BuildContext context) { + return SideBar( + width: widget.controller.isOpen ? 150 : 40, + child: Column( + children: [ + SideBarTab( + icon: Codicon.menu, + text: "Menu", + callback: () { + widget.controller.switchSidebar(); + setState(() {}); + }, + ), + // TODO: Terminal Tabs + // Expanded( + // child: ListView.builder( + // itemCount: 0, + // itemBuilder: (ctx, index) { + // return Tab(); + // }, + // ), + // ), + Expanded(child: Container()), + SideBarTab( + icon: Codicon.add, + text: "Add Terminal", + callback: () {}, + ), + ], + ), + ); + } +} + +class LeftSideBarController { + final void Function()? callback; + + LeftSideBarController({this.callback}); + + bool _open = false; + bool get isOpen => _open; + + open() { + _open = true; + callback?.call(); + } + + close() { + _open = false; + callback?.call(); + } + + switchSidebar() { + _open ? close() : open(); + } +} diff --git a/lib/src/ui/component/sidebar/main.dart b/lib/src/ui/component/sidebar/main.dart new file mode 100644 index 0000000..c30cb11 --- /dev/null +++ b/lib/src/ui/component/sidebar/main.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +class SideBar extends StatelessWidget { + final Widget? child; + final double width; + const SideBar({ + super.key, + required this.width, + this.child, + }); + + @override + Widget build(BuildContext context) { + return AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.fastEaseInToSlowEaseOut, + width: width, + child: Center( + child: Container( + color: Theme.of(context).cardColor, + child: child, + ), + ), + ); + } +} diff --git a/lib/src/ui/component/sidebar/tab.dart b/lib/src/ui/component/sidebar/tab.dart new file mode 100644 index 0000000..629ac3d --- /dev/null +++ b/lib/src/ui/component/sidebar/tab.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; + +class SideBarTab extends StatefulWidget { + final void Function()? callback; + final IconData? icon; + final String? text; + + const SideBarTab({super.key, this.callback, this.icon, this.text}); + + @override + State createState() => _SideBarTabState(); +} + +class _SideBarTabState extends State { + bool _isHover = false; + + @override + Widget build(BuildContext context) { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 5), + child: GestureDetector( + onTap: () { + widget.callback?.call(); + }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (e) { + _isHover = true; + setState(() {}); + }, + onExit: (e) { + _isHover = false; + setState(() {}); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + width: MediaQuery.of(context).size.width, + height: 30, + decoration: BoxDecoration( + color: _isHover ? Theme.of(context).highlightColor : null, + borderRadius: BorderRadius.circular(5), + ), + child: Padding( + padding: const EdgeInsets.all(8), + child: ClipRect( + child: Stack( + alignment: Alignment.centerLeft, + children: [ + Icon( + widget.icon, + size: 14, + ), + Positioned( + left: 20, + child: Container( + constraints: const BoxConstraints(maxWidth: 104), + child: Text( + widget.text ?? "", + softWrap: false, + style: const TextStyle( + fontSize: 12, + overflow: TextOverflow.fade, + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/ui/icons/codicon.dart b/lib/src/ui/icons/codicon.dart index 62dd279..d12195f 100644 --- a/lib/src/ui/icons/codicon.dart +++ b/lib/src/ui/icons/codicon.dart @@ -48,4 +48,16 @@ class Codicon { fontFamily: "Codicon", matchTextDirection: true, ); + + static const IconData menu = IconData( + 0xeb94, + fontFamily: "Codicon", + matchTextDirection: true, + ); + + static const IconData add = IconData( + 0xea60, + fontFamily: "Codicon", + matchTextDirection: true, + ); } diff --git a/lib/src/ui/main.dart b/lib/src/ui/main.dart index a5e1173..6c248ae 100644 --- a/lib/src/ui/main.dart +++ b/lib/src/ui/main.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:grassh_renew/src/config/global_config.dart'; +import 'package:grassh_renew/src/ui/component/sidebar/left.dart'; import 'component/appbar/main.dart' as bar; class MainPage extends StatefulWidget { @@ -11,9 +13,39 @@ class MainPage extends StatefulWidget { class _MainPageState extends State { @override Widget build(BuildContext context) { - return const Scaffold( - appBar: bar.AppBar(), - body: Row(), + return Scaffold( + appBar: const bar.AppBar(), + body: SizedBox( + width: MediaQuery.of(context).size.width, + child: Stack( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LeftSideBar( + controller: LeftSideBarController(), + ), + Expanded( + child: Container(), + ) + ], + ), + GlobalConfig.packageInfo.buildNumber != "" + ? Positioned( + right: 0, + bottom: 0, + child: Text( + "GrassH v${GlobalConfig.packageInfo.version}\nBuild ${GlobalConfig.packageInfo.buildNumber}. Not intended for external distribution.", + textAlign: TextAlign.end, + style: TextStyle( + fontSize: 10, + color: Theme.of(context).primaryColorLight, + ), + ), + ) + : const SizedBox(), + ], + )), ); } } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 913ac71..2e081d6 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + flutter_pty ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index b622947..23ec071 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,10 +5,12 @@ import FlutterMacOS import Foundation +import package_info_plus import screen_retriever import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 847bee4..a14e040 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -49,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" flutter: dependency: "direct main" description: flutter @@ -62,11 +86,40 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + flutter_pty: + dependency: "direct main" + description: + name: flutter_pty + sha256: "08b6f37a4f394159e3a6adb6f07295e6fb6acb71270c87a4d0be187db34535cd" + url: "https://pub.dev" + source: hosted + version: "0.4.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + url: "https://pub.dev" + source: hosted + version: "1.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" leak_tracker: dependency: transitive description: @@ -123,6 +176,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 + url: "https://pub.dev" + source: hosted + version: "8.0.2" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + url: "https://pub.dev" + source: hosted + version: "3.0.1" path: dependency: transitive description: @@ -131,6 +200,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + quiver: + dependency: transitive + description: + name: quiver + sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 + url: "https://pub.dev" + source: hosted + version: "3.2.1" screen_retriever: dependency: transitive description: @@ -192,6 +285,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + toml: + dependency: "direct main" + description: + name: toml + sha256: d968d149c8bd06dc14e09ea3a140f90a3f2ba71949e7a91df4a46f3107400e71 + url: "https://pub.dev" + source: hosted + version: "0.16.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" vector_math: dependency: transitive description: @@ -208,6 +317,22 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.1" + web: + dependency: transitive + description: + name: web + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + win32: + dependency: transitive + description: + name: win32 + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" + url: "https://pub.dev" + source: hosted + version: "5.5.4" window_manager: dependency: "direct main" description: @@ -216,6 +341,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.0" + xterm: + dependency: "direct main" + description: + name: xterm + sha256: "168dfedca77cba33fdb6f52e2cd001e9fde216e398e89335c19b524bb22da3a2" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + zmodem: + dependency: transitive + description: + name: zmodem + sha256: "3b7e5b29f3a7d8aee472029b05165a68438eff2f3f7766edf13daba1e297adbf" + url: "https://pub.dev" + source: hosted + version: "0.0.6" sdks: dart: ">=3.4.4 <4.0.0" flutter: ">=3.22.3" diff --git a/pubspec.yaml b/pubspec.yaml index 1336336..0438a04 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,11 @@ environment: dependencies: flutter: sdk: flutter + flutter_pty: ^0.4.0 + package_info_plus: ^8.0.2 + toml: ^0.16.0 window_manager: ^0.4.0 + xterm: ^4.0.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index bfa52f4..07950b2 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + flutter_pty ) set(PLUGIN_BUNDLED_LIBRARIES)