diff --git a/android/app/build.gradle b/android/app/build.gradle index 31930d5a..c59123a3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -44,7 +44,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "tech.lolli.toolbox" - minSdkVersion 17 + minSdkVersion 21 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/android/build.gradle b/android/build.gradle index ed45c658..18261f3a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:7.0.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index bc6a58af..b8793d3c 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip diff --git a/assets/app_icon.jpg b/assets/app_icon.jpg new file mode 100644 index 00000000..3b59a76d Binary files /dev/null and b/assets/app_icon.jpg differ diff --git a/lib/app.dart b/lib/app.dart index 9d2970a5..3d074598 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:toolbox/page/home.dart'; +import 'package:toolbox/view/page/home.dart'; class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); diff --git a/lib/data/store/server.dart b/lib/data/store/server.dart new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/lib/data/store/server.dart @@ -0,0 +1 @@ + diff --git a/lib/page/convert.dart b/lib/view/page/convert.dart similarity index 87% rename from lib/page/convert.dart rename to lib/view/page/convert.dart index 0af7a8a4..6e13bda3 100644 --- a/lib/page/convert.dart +++ b/lib/view/page/convert.dart @@ -3,14 +3,14 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; -class EncodePage extends StatefulWidget { - const EncodePage({Key? key}) : super(key: key); +class ConvertPage extends StatefulWidget { + const ConvertPage({Key? key}) : super(key: key); @override - _EncodePageState createState() => _EncodePageState(); + _ConvertPageState createState() => _ConvertPageState(); } -class _EncodePageState extends State +class _ConvertPageState extends State with AutomaticKeepAliveClientMixin { late TextEditingController _textEditingController; late TextEditingController _textEditingControllerResult; @@ -106,11 +106,8 @@ class _EncodePageState extends State child: SizedBox( width: _media.size.width * 0.2, child: Row( - children: const [ - Icon(Icons.change_circle), - Text('上下交换') - ], - ), + children: const [Icon(Icons.change_circle), Text('上下交换')], + ), ), onPressed: () { final temp = _textEditingController.text; @@ -120,14 +117,9 @@ class _EncodePageState extends State ), title: SizedBox( width: _media.size.width * 0.4, - child: Row( - children: [ - const VerticalDivider(width: 2, thickness: 2, indent: 2, endIndent: 2,), - Text( - _typeOption[_typeOptionIndex], - style: const TextStyle(fontSize: 16.0, fontWeight: FontWeight.w500), - ) - ], + child: Text( + _typeOption[_typeOptionIndex], + style: const TextStyle(fontSize: 16.0, fontWeight: FontWeight.w500), ), ), children: _typeOption diff --git a/lib/page/debug.dart b/lib/view/page/debug.dart similarity index 100% rename from lib/page/debug.dart rename to lib/view/page/debug.dart diff --git a/lib/page/home.dart b/lib/view/page/home.dart similarity index 76% rename from lib/page/home.dart rename to lib/view/page/home.dart index 525aedb8..1809accc 100644 --- a/lib/page/home.dart +++ b/lib/view/page/home.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:toolbox/core/route.dart'; import 'package:toolbox/data/res/build_data.dart'; -import 'package:toolbox/page/convert.dart'; -import 'package:toolbox/page/debug.dart'; +import 'package:toolbox/view/page/convert.dart'; +import 'package:toolbox/view/page/debug.dart'; +import 'package:toolbox/view/page/server.dart'; class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); @@ -14,7 +15,7 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin { - final List _tabs = ['编码', 'Ping', '1', '2', '3']; + final List _tabs = ['服务器', '编/解码', '1', '2', '3']; late final TabController _tabController; @override @@ -40,11 +41,11 @@ class _MyHomePageState extends State ), drawer: _buildDrawer(), body: TabBarView(controller: _tabController, children: const [ - EncodePage(), - EncodePage(), - EncodePage(), - EncodePage(), - EncodePage() + ServerPage(), + ConvertPage(), + ConvertPage(), + ConvertPage(), + ConvertPage() ]), ); } @@ -57,6 +58,7 @@ class _MyHomePageState extends State UserAccountsDrawerHeader( accountName: const Text('ToolBox'), accountEmail: Text(_buildVersionStr()), + currentAccountPicture: _buildIcon(const Color(0x00083963)), ), const ListTile( leading: Icon(Icons.settings), @@ -67,8 +69,9 @@ class _MyHomePageState extends State child: const Text('开源证书'), applicationName: BuildData.name, applicationVersion: _buildVersionStr(), + applicationIcon: _buildIcon(Colors.transparent), aboutBoxChildren: const [ - Text('''\nMade with ❤️ by Toast Studio . + Text('''\nMade with Love. \nAll rights reserved.'''), ], ), @@ -77,6 +80,13 @@ class _MyHomePageState extends State ); } + Widget _buildIcon(Color c) { + return CircleAvatar( + child: Image.asset('assets/app_icon.jpg'), + backgroundColor: c, + ); + } + String _buildVersionStr() { return 'Ver: 1.0.${BuildData.build}'; } diff --git a/lib/view/page/server.dart b/lib/view/page/server.dart new file mode 100644 index 00000000..abb70744 --- /dev/null +++ b/lib/view/page/server.dart @@ -0,0 +1,175 @@ +import 'package:charts_flutter/flutter.dart' as chart; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:ssh2/ssh2.dart'; +import 'package:toolbox/core/utils.dart'; +import 'package:toolbox/view/widget/circle_pie.dart'; + +class ServerPage extends StatefulWidget { + const ServerPage({Key? key}) : super(key: key); + + @override + _ServerPageState createState() => _ServerPageState(); +} + +class _ServerPageState extends State + with AutomaticKeepAliveClientMixin { + late MediaQueryData _media; + late ThemeData _theme; + + @override + void initState() { + super.initState(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _media = MediaQuery.of(context); + _theme = Theme.of(context); + } + + @override + Widget build(BuildContext context) { + super.build(context); + return Scaffold( + body: GestureDetector( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 7), + child: AnimationLimiter( + child: Column( + children: AnimationConfiguration.toStaggeredList( + duration: const Duration(milliseconds: 377), + childAnimationBuilder: (widget) => SlideAnimation( + verticalOffset: 50.0, + child: FadeInAnimation( + child: widget, + ), + ), + children: [const SizedBox(height: 13), ..._buildServerCards()], + ))), + ), + onTap: () => FocusScope.of(context).requestFocus(FocusNode()), + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + showSnackBar(context, const Text('')); + }, + tooltip: 'add a server', + child: const Icon(Icons.add), + ), + ); + } + + Future>? _getData() async { + final client = SSHClient( + host: '', + port: 0, + username: '', + passwordOrKey: '', + ); + await client.connect(); + final cpu = await client.execute( + "top -bn1 | grep load | awk '{printf \"%.2f\", \$(NF-2)}'") ?? + 'failed'; + final mem = await client + .execute("free -m | awk 'NR==2{printf \"%s/%sMB\", \$3,\$2}'") ?? + 'failed'; + return [cpu.trim(), mem.trim()]; + } + + Widget _buildEachServerCard() { + return FutureBuilder>( + future: _getData(), + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasError) { + return Text("Error: ${snapshot.error}"); + } else { + return _buildEachCardContent(snapshot); + } + } else { + return const CircularProgressIndicator(); + } + }, + ); + } + + Widget _buildEachCardContent(AsyncSnapshot snapshot) { + final cpuPercent = double.parse(snapshot.data![0]) * 100; + final memSplit = snapshot.data![1].replaceFirst('MB', '').split('/'); + final memPercent = int.parse(memSplit[0]) / int.parse(memSplit[1]) * 100; + final cpuData = [ + IndexPercent(0, cpuPercent.toInt()), + ]; + final memData = [ + IndexPercent(0, memPercent.toInt()), + ]; + return Card( + child: Padding(padding:const EdgeInsets.all(13) , child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(' Jilin', style: TextStyle(fontWeight: FontWeight.bold),), + const SizedBox(height: 7,), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildPercentCircle(cpuPercent, 'CPU', [ + chart.Series( + id: 'CPU', + domainFn: (IndexPercent cpu, _) => cpu.id, + measureFn: (IndexPercent cpu, _) => cpu.percent, + data: cpuData, + ) + ]), + _buildPercentCircle(memPercent, 'MEM', [ + chart.Series( + id: 'MEM', + domainFn: (IndexPercent sales, _) => sales.id, + measureFn: (IndexPercent sales, _) => sales.percent, + data: memData, + ) + ]) + ], + ) + ], + ),), + ); + } + + Widget _buildPercentCircle( + double percent, String title, List> series) { + return SizedBox( + width: _media.size.width * 0.2, + height: _media.size.height * 0.1, + child: Stack( + children: [ + DonutPieChart.withRandomData(), + Positioned( + child: Text( + '${percent.toStringAsFixed(1)}%', + textAlign: TextAlign.center, + ), + left: 0, + right: 0, + top: 0, + bottom: 0 + ), + Positioned( + child: Text(title, textAlign: TextAlign.center), + bottom: 0, + left: 0, + right: 0) + ], + ), + ); + } + + List _buildServerCards() { + return [_buildEachServerCard()]; + } + + @override + bool get wantKeepAlive => true; +} diff --git a/lib/view/widget/circle_pie.dart b/lib/view/widget/circle_pie.dart new file mode 100644 index 00000000..a591b75f --- /dev/null +++ b/lib/view/widget/circle_pie.dart @@ -0,0 +1,65 @@ +import 'dart:math'; +// EXCLUDE_FROM_GALLERY_DOCS_END +import 'package:charts_flutter/flutter.dart' as charts; +import 'package:flutter/material.dart'; + +class DonutPieChart extends StatelessWidget { + final List> seriesList; + final bool animate; + + const DonutPieChart(this.seriesList, {Key? key, this.animate = true}) : super(key: key); + + factory DonutPieChart.withRandomData() { + return DonutPieChart(_createRandomData()); + } + + /// Create random data. + static List> _createRandomData() { + final random = Random(); + + final data = [ + IndexPercent(0, random.nextInt(100)), + IndexPercent(1, random.nextInt(100)), + IndexPercent(2, random.nextInt(100)), + IndexPercent(3, random.nextInt(100)), + ]; + + return [ + charts.Series( + id: 'Sales', + domainFn: (IndexPercent sales, _) => sales.id, + measureFn: (IndexPercent sales, _) => sales.percent, + data: data, + ) + ]; + } + // EXCLUDE_FROM_GALLERY_DOCS_END + + @override + Widget build(BuildContext context) { + return charts.PieChart(seriesList, + animate: animate, + layoutConfig: charts.LayoutConfig( + leftMarginSpec: charts.MarginSpec.fixedPixel(1), + topMarginSpec: charts.MarginSpec.fixedPixel(1), + rightMarginSpec: charts.MarginSpec.fixedPixel(1), + bottomMarginSpec: charts.MarginSpec.fixedPixel(17) + ), + // Configure the width of the pie slices to 60px. The remaining space in + // the chart will be left as a hole in the center. + defaultRenderer: charts.ArcRendererConfig( + arcWidth: 6, + minHoleWidthForCenterContent: 60, + arcRatio: 0.2, + ) + ); + } +} + +/// Sample linear data type. +class IndexPercent { + final int id; + final int percent; + + IndexPercent(this.id, this.percent); +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 7fcd91f1..b43347df 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -36,6 +36,20 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.1" + charts_common: + dependency: transitive + description: + name: charts_common + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.11.0" + charts_flutter: + dependency: "direct main" + description: + name: charts_flutter + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.11.0" clock: dependency: transitive description: @@ -186,6 +200,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.17.0" js: dependency: transitive description: @@ -200,6 +221,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" matcher: dependency: transitive description: @@ -310,6 +338,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.8.1" + ssh2: + dependency: "direct main" + description: + name: ssh2 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.2" stack_trace: dependency: transitive description: @@ -394,6 +429,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.0.2" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.4" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0702dbea..c1c13c77 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,8 @@ dependencies: git: url: https://github.com/Countly/countly-sdk-flutter-bridge.git ref: master + ssh2: ^2.2.2 + charts_flutter: ^0.11.0 dev_dependencies: @@ -68,7 +70,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: + assets: + - assets/ # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg