diff --git a/analysis_options.yaml b/analysis_options.yaml index 1afcb62..fb2dac0 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,6 +2,7 @@ include: package:flutter_lints/flutter.yaml linter: rules: + use_build_context_synchronously: false analyzer: exclude: diff --git a/lib/view/home.dart b/lib/view/home.dart index 098a0fd..0d5b4c1 100644 --- a/lib/view/home.dart +++ b/lib/view/home.dart @@ -2,19 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; -import 'package:jenkins_board/api/jenkins_api.dart'; import 'package:jenkins_board/provider/jobs_provider.dart'; -import 'package:jenkins_board/storage/hive_box.dart'; -import 'package:jenkins_board/utils/extensions.dart'; import 'package:jenkins_board/view/settings/build_detail.dart'; import 'package:jenkins_board/view/settings/build_task.dart'; import 'package:jenkins_board/view/settings/choose_jobs.dart'; import 'package:jenkins_board/view/settings/settings.dart'; -import 'package:jenkins_board/widgets/build_task_button.dart'; -import 'package:jenkins_board/widgets/custom_button.dart'; import 'package:jenkins_board/widgets/job_panel.dart'; -import 'package:line_icons/line_icons.dart'; +import 'package:jenkins_board/widgets/topbar.dart'; import 'package:reorderables/reorderables.dart'; enum SettingType { choose_jobs, settings, build_detail, build_tasks, undefined } @@ -67,95 +61,11 @@ class HomeView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final jobs = ref.watch(jobsProvider); - final username = HiveBox.getUsername(); return ListView( children: [ + const Topbar(), Padding( - padding: const EdgeInsets.fromLTRB(30, 30, 30, 20), - child: Row( - children: [ - Text('Hi $username', style: context.headline4), - const SizedBox( - width: 20, - ), - CustomButton( - onPressed: () { - context.go('/home/choose_jobs'); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Row( - children: [ - Text(context.S.addJob), - const Icon( - LineIcons.plusCircle, - size: 25, - ), - ], - ), - ), - ), - const Spacer(), - const BuildTasksButton(), - const SizedBox( - width: 20, - ), - Container( - decoration: BoxDecoration( - color: context.primaryColorLight, - borderRadius: BorderRadius.circular(100), - ), - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Row( - children: [ - IconButton( - onPressed: ref.read(jobsProvider.notifier).refresh, - splashRadius: 20, - icon: Icon( - LineIcons.alternateRedo, - color: context.primaryColorDark, - size: 20, - ), - tooltip: context.S.refresh, - ), - Container( - width: 2, - height: 20, - color: context.primaryColorDark, - ), - IconButton( - onPressed: () { - context.go('/home/settings'); - }, - splashRadius: 20, - icon: Icon( - LineIcons.cog, - color: context.primaryColorDark, - ), - tooltip: context.S.settings, - ), - Container( - width: 2, - height: 20, - color: context.primaryColorDark, - ), - IconButton( - onPressed: () async => await _logout(context), - splashRadius: 20, - icon: Icon( - LineIcons.shareSquare, - color: context.primaryColorDark, - ), - tooltip: context.S.logout, - ), - ], - ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(15, 15, 30, 15), + padding: const EdgeInsets.fromLTRB(15, 15, 0, 15), child: ReorderableWrap( onReorder: ref.read(jobsProvider.notifier).reOrder, spacing: 20, @@ -166,9 +76,4 @@ class HomeView extends ConsumerWidget { ], ); } - - Future _logout(BuildContext context) async { - await JenkinsApi.logout(); - context.push('/'); - } } diff --git a/lib/widgets/custom_button.dart b/lib/widgets/custom_button.dart index 9d1354f..e58f11c 100644 --- a/lib/widgets/custom_button.dart +++ b/lib/widgets/custom_button.dart @@ -18,7 +18,6 @@ class CustomButton extends StatelessWidget { Widget build(BuildContext context) { return ElevatedButton( style: ButtonStyle( - enableFeedback: false, elevation: MaterialStateProperty.resolveWith((states) { return 0; }), @@ -45,8 +44,8 @@ class CustomButton extends StatelessWidget { }, ), animationDuration: Duration.zero, - shape: MaterialStateProperty.all( - RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + shape: MaterialStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), ), onPressed: onPressed, @@ -67,7 +66,7 @@ class CustomButton extends StatelessWidget { ), ), ), - ) + ), ], ), ); diff --git a/lib/widgets/job_panel.dart b/lib/widgets/job_panel.dart index e28a48d..8911618 100644 --- a/lib/widgets/job_panel.dart +++ b/lib/widgets/job_panel.dart @@ -79,31 +79,18 @@ class JobPanel extends ConsumerWidget { } } -class _JobItem extends ConsumerStatefulWidget { +class _JobItem extends StatelessWidget { const _JobItem(this.branch, {required this.jobName, Key? key}) : super(key: key); final Branch branch; final String jobName; - @override - ConsumerState createState() => _JobItemState(); -} - -class _JobItemState extends ConsumerState<_JobItem> { - late bool _loadBuilding; - - @override - initState() { - _loadBuilding = false; - super.initState(); - } - @override Widget build(BuildContext context) { return Row( children: [ Text( - widget.branch.name, + branch.name, style: context.headline6, ), const SizedBox( @@ -112,7 +99,7 @@ class _JobItemState extends ConsumerState<_JobItem> { InkWell( onTap: () async { context.go('/home/build_detail'); - final url = await JenkinsApi.recentBuildUrl(widget.branch.url); + final url = await JenkinsApi.recentBuildUrl(branch.url); if (url != null) { context.go('/home/build_detail', extra: url); } else { @@ -121,41 +108,64 @@ class _JobItemState extends ConsumerState<_JobItem> { icon: LineIcons.infoCircle); } }, - child: (widget.branch.isRunning) + child: (branch.isRunning) ? const RunningWidget() : CircleAvatar( radius: 6, - backgroundColor: widget.branch.statusColor, + backgroundColor: branch.statusColor, ), ), const Spacer(), Material( color: Colors.transparent, - child: Builder( - builder: (context) { - if (_loadBuilding) { - return const Padding( - padding: EdgeInsets.all(10), - child: SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator( - strokeWidth: 1, - ), - ), - ); - } - return IconButton( - splashRadius: 20, - icon: Icon( - LineIcons.running, - color: context.accentColor, - ), - onPressed: () => _onBuild(context), - ); - }, + child: _RunningButton(branch: branch, jobName: jobName), + ), + ], + ); + } +} + +class _RunningButton extends ConsumerStatefulWidget { + final Branch branch; + final String jobName; + const _RunningButton({required this.branch, required this.jobName, Key? key}) + : super(key: key); + + @override + ConsumerState createState() => __RunningButtonState(); +} + +class __RunningButtonState extends ConsumerState<_RunningButton> { + late bool _loadBuilding; + @override + void initState() { + _loadBuilding = false; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return IndexedStack( + index: _loadBuilding ? 0 : 1, + children: [ + const Padding( + padding: EdgeInsets.all(10), + child: SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + strokeWidth: 1, + ), ), ), + IconButton( + splashRadius: 20, + icon: Icon( + LineIcons.running, + color: context.accentColor, + ), + onPressed: () => _onBuild(context), + ) ], ); } @@ -172,6 +182,7 @@ class _JobItemState extends ConsumerState<_JobItem> { } else { showPopover( context: context, + direction: PopoverDirection.top, barrierColor: Colors.transparent, transitionDuration: const Duration(milliseconds: 150), bodyBuilder: (context) { @@ -181,16 +192,8 @@ class _JobItemState extends ConsumerState<_JobItem> { _RunBuildWidget(widget.branch, buildParams, widget.jobName), ); }, - shadow: const [ - BoxShadow( - color: Colors.black12, - spreadRadius: 2, - blurRadius: 5, - offset: Offset(-1, 1)) - ], onPop: () {}, - direction: PopoverDirection.top, - arrowHeight: 15, + arrowHeight: 0, arrowWidth: 30, ); } @@ -220,20 +223,20 @@ class __RunBuildState extends ConsumerState<_RunBuildWidget> { final _buildParam = {}; @override void initState() { - for (var param in widget.buildParams) { + super.initState(); + for (final param in widget.buildParams) { _buildParam[param.name] = param.defautlValue; } - super.initState(); } @override Widget build(BuildContext context) { return SizedBox( width: 100, - child: Column( - mainAxisSize: MainAxisSize.min, + child: ListView( + shrinkWrap: true, children: [ - ...[for (var p in widget.buildParams) _paramWidget(p)], + for (var p in widget.buildParams) _paramWidget(p), const SizedBox( height: 20, ), @@ -314,7 +317,7 @@ Future _newBuild( final url = await JenkinsApi.newBuild(branch, params: buildParam); final buildNumber = await JenkinsApi.getNextBuildNumber(branch.url); final task = BuildTask( - name: jobName + '-' + branch.name, + name: '$jobName-${branch.name}', branchUrl: branch.url, buildNumber: buildNumber, buildUrl: url, diff --git a/lib/widgets/topbar.dart b/lib/widgets/topbar.dart new file mode 100644 index 0000000..8c7da1b --- /dev/null +++ b/lib/widgets/topbar.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:jenkins_board/api/jenkins_api.dart'; +import 'package:jenkins_board/provider/jobs_provider.dart'; +import 'package:jenkins_board/storage/hive_box.dart'; +import 'package:jenkins_board/utils/extensions.dart'; +import 'package:jenkins_board/widgets/build_task_button.dart'; +import 'package:jenkins_board/widgets/custom_button.dart'; +import 'package:line_icons/line_icons.dart'; + +class Topbar extends ConsumerWidget { + const Topbar({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Padding( + padding: const EdgeInsets.fromLTRB(30, 30, 30, 20), + child: LayoutBuilder(builder: (context, constraints) { + if (constraints.maxWidth < 660) { + return Column( + children: const [ + _LeftPanel(), + SizedBox( + height: 10, + ), + _RightPanel() + ], + ); + } + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: const [_LeftPanel(), _RightPanel()], + ); + }), + ); + } +} + +class _LeftPanel extends StatelessWidget { + const _LeftPanel({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final username = HiveBox.getUsername(); + return Row( + children: [ + Text('Hi $username', style: context.headline4), + const SizedBox( + width: 20, + ), + CustomButton( + onPressed: () { + context.go('/home/choose_jobs'); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Row( + children: [ + Text(context.S.addJob), + const Icon( + LineIcons.plusCircle, + size: 25, + ), + ], + ), + ), + ), + ], + ); + } +} + +class _RightPanel extends ConsumerWidget { + const _RightPanel({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Row( + children: [ + const BuildTasksButton(), + const SizedBox( + width: 20, + ), + Container( + decoration: BoxDecoration( + color: context.primaryColorLight, + borderRadius: BorderRadius.circular(100), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + children: [ + IconButton( + onPressed: ref.read(jobsProvider.notifier).refresh, + splashRadius: 20, + icon: Icon( + LineIcons.alternateRedo, + color: context.primaryColorDark, + size: 20, + ), + tooltip: context.S.refresh, + ), + Container( + width: 2, + height: 20, + color: context.primaryColorDark, + ), + IconButton( + onPressed: () { + context.go('/home/settings'); + }, + splashRadius: 20, + icon: Icon( + LineIcons.cog, + color: context.primaryColorDark, + ), + tooltip: context.S.settings, + ), + Container( + width: 2, + height: 20, + color: context.primaryColorDark, + ), + IconButton( + onPressed: () async => await _logout(context), + splashRadius: 20, + icon: Icon( + LineIcons.shareSquare, + color: context.primaryColorDark, + ), + tooltip: context.S.logout, + ), + ], + ), + ), + ], + ); + } + + Future _logout(BuildContext context) async { + await JenkinsApi.logout(); + context.push('/'); + } +} diff --git a/pubspec.lock b/pubspec.lock index c303114..ac06b81 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -162,6 +162,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.5" + custom_pop_up_menu: + dependency: "direct main" + description: + name: custom_pop_up_menu + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.4" dart_style: dependency: transitive description: @@ -229,7 +236,7 @@ packages: name: flutter_lints url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.4" + version: "2.0.1" flutter_localizations: dependency: "direct dev" description: flutter @@ -314,7 +321,7 @@ packages: name: hive url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.1" + version: "2.2.2" hive_flutter: dependency: "direct main" description: @@ -398,7 +405,7 @@ packages: name: lints url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.1" + version: "2.0.0" local_notifier: dependency: "direct main" description: @@ -559,7 +566,7 @@ packages: name: reorderables url: "https://pub.flutter-io.cn" source: hosted - version: "0.4.4" + version: "0.5.0" riverpod: dependency: transitive description: @@ -676,7 +683,7 @@ packages: name: url_launcher url: "https://pub.flutter-io.cn" source: hosted - version: "6.1.2" + version: "6.1.3" url_launcher_android: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e295348..fd2c15c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,22 +13,23 @@ dependencies: cupertino_icons: ^1.0.5 flutter_riverpod: ^1.0.3 go_router: ^3.1.1 - state_notifier: ^0.7.1 + state_notifier: ^0.7.2 freezed_annotation: ^2.0.3 - hive: ^2.0.5 + hive: ^2.2.2 hive_flutter: ^1.1.0 dio: ^4.0.4 - url_launcher: ^6.1.2 + url_launcher: ^6.1.3 line_icons: ^2.0.1 fluttertoast: ^8.0.8 equatable: ^2.0.3 intl: ^0.17.0 google_fonts: ^3.0.1 local_notifier: ^0.1.5 - reorderables: ^0.4.2 + reorderables: ^0.5.0 popover: ^0.2.6+3 cookie_jar: ^3.0.1 dio_cookie_manager: ^2.0.0 + custom_pop_up_menu: ^1.2.4 dev_dependencies: freezed: ^2.0.3 @@ -38,8 +39,8 @@ dev_dependencies: sdk: flutter flutter_localizations: sdk: flutter - flutter_lints: ^1.0.0 - hive_generator: ^1.1.1 + flutter_lints: ^2.0.1 + hive_generator: ^1.1.3 flutter: uses-material-design: true