mobile_nebula/lib/screens/SiteLogsScreen.dart
Caleb Jasik 2b844d27dd
Add Flutter lint (#253)
* Enable `flutter_lints` linting

* Fix unmarked deps, we aren't on web so we don't need a URL strategy

* ` dart fix --apply --code=use_super_parameters`

* `dart fix --apply --code=use_key_in_widget_constructors`

* `dart fix --apply --code=use_function_type_syntax_for_parameters`

* Ignore code-generated `lib/services/theme.dart` file

* `dart fix --apply --code=unnecessary_this`

* `dart fix --apply --code=unnecessary_null_in_if_null_operators`

* `dart fix --apply --code=unnecessary_new`

* `dart fix --apply --code=sort_child_properties_last`

* `dart fix --apply --code=sized_box_for_whitespace`

* `dart fix --apply --code=prefer_typing_uninitialized_variables`

* `dart fix --apply --code=prefer_is_empty`

* `dart fix --apply --code=prefer_interpolation_to_compose_strings`

* `dart fix --apply --code=prefer_final_fields`

* `dart fix --apply --code=prefer_const_constructors_in_immutables`

* `dart fix --apply --code=prefer_collection_literals`

* `dart fix --apply --code=no_leading_underscores_for_local_identifiers`

* `dart fix --apply --code=curly_braces_in_flow_control_structures`

* `dart fix --apply --code=avoid_function_literals_in_foreach_calls`

* `dart fix --apply --code=annotate_overrides`

* Add CI for dart linting

* `dart format lib/`

* Re-enable the `usePathUrlStrategy` call, with proper deps

https://docs.flutter.dev/ui/navigation/url-strategies#configuring-the-url-strategy
2025-03-04 11:29:23 -06:00

168 lines
5.7 KiB
Dart

import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:mobile_nebula/components/SimplePage.dart';
import 'package:mobile_nebula/models/Site.dart';
import 'package:mobile_nebula/services/logs.dart';
import 'package:mobile_nebula/services/result.dart';
import 'package:mobile_nebula/services/settings.dart';
import 'package:mobile_nebula/services/share.dart';
import 'package:mobile_nebula/services/utils.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../components/SiteTitle.dart';
class SiteLogsScreen extends StatefulWidget {
const SiteLogsScreen({super.key, required this.site});
final Site site;
@override
_SiteLogsScreenState createState() => _SiteLogsScreenState();
}
class _SiteLogsScreenState extends State<SiteLogsScreen> {
final ScrollController controller = ScrollController();
final RefreshController refreshController = RefreshController(initialRefresh: false);
final LogsNotifier logsNotifier = LogsNotifier();
var settings = Settings();
@override
void initState() {
logsNotifier.loadLogs(logFile: widget.site.logFile);
super.initState();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final title = SiteTitle(site: widget.site);
return SimplePage(
title: title,
trailingActions: [Padding(padding: const EdgeInsets.only(right: 8), child: _buildTextWrapToggle())],
scrollable: SimpleScrollable.both,
scrollController: controller,
onRefresh: () async {
await logsNotifier.loadLogs(logFile: widget.site.logFile);
refreshController.refreshCompleted();
},
onLoading: () async {
await logsNotifier.loadLogs(logFile: widget.site.logFile);
refreshController.loadComplete();
},
refreshController: refreshController,
bottomBar: _buildBottomBar(),
child: Container(
padding: EdgeInsets.all(5),
constraints: logBoxConstraints(context),
child: ListenableBuilder(
listenable: logsNotifier,
builder:
(context, child) => SelectableText(switch (logsNotifier.logsResult) {
Ok<String>(:var value) => value.trim(),
Error<String>(:var error) =>
error is LogsNotFoundException
? error.error()
: Utils.popError(context, "Error while reading logs.", error.toString()),
null => "",
}, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)),
),
),
);
}
Widget _buildTextWrapToggle() {
return Platform.isIOS
? Tooltip(
message: "Turn ${settings.logWrap ? "off" : "on"} text wrapping",
child: CupertinoButton.tinted(
// Use the default tint when enabled, match the background when not.
color: settings.logWrap ? null : CupertinoColors.systemBackground,
sizeStyle: CupertinoButtonSize.small,
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: const Icon(Icons.wrap_text),
onPressed:
() => {
setState(() {
settings.logWrap = !settings.logWrap;
}),
},
),
)
: IconButton.filledTonal(
isSelected: settings.logWrap,
tooltip: "Turn ${settings.logWrap ? "off" : "on"} text wrapping",
// The variants of wrap_text seem to be the same, but this seems most correct.
selectedIcon: const Icon(Icons.wrap_text_outlined),
icon: const Icon(Icons.wrap_text),
onPressed:
() => {
setState(() {
settings.logWrap = !settings.logWrap;
}),
},
);
}
Widget _buildBottomBar() {
var borderSide = BorderSide(color: CupertinoColors.separator, style: BorderStyle.solid, width: 0.0);
var padding = Platform.isAndroid ? EdgeInsets.fromLTRB(0, 20, 0, 30) : EdgeInsets.all(10);
return PlatformWidgetBuilder(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
spacing: 8,
children: <Widget>[
Tooltip(
message: "Share logs",
child: PlatformIconButton(
icon: Icon(context.platformIcons.share),
onPressed: () {
Share.shareFile(
context,
title: '${widget.site.name} logs',
filePath: widget.site.logFile,
filename: '${widget.site.name}.log',
);
},
),
),
Tooltip(
message: 'Go to latest',
child: PlatformIconButton(
icon: Icon(context.platformIcons.downArrow),
onPressed: () async {
controller.animateTo(
controller.position.maxScrollExtent,
duration: const Duration(milliseconds: 500),
curve: Curves.linearToEaseOut,
);
},
),
),
],
),
cupertino:
(context, child, platform) =>
Container(decoration: BoxDecoration(border: Border(top: borderSide)), padding: padding, child: child),
material: (context, child, platform) => BottomAppBar(child: child),
);
}
logBoxConstraints(BuildContext context) {
if (settings.logWrap) {
return BoxConstraints(maxWidth: MediaQuery.of(context).size.width);
} else {
return BoxConstraints(minWidth: MediaQuery.of(context).size.width);
}
}
}