move dropdown up; remove unused view
This commit is contained in:
parent
8f95b644d7
commit
bba42586f3
|
|
@ -1,10 +1,12 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'src/app.dart';
|
import 'src/app.dart';
|
||||||
import 'src/settings/settings_controller.dart';
|
import 'src/settings/settings_controller.dart';
|
||||||
import 'src/settings/settings_service.dart';
|
import 'src/settings/settings_service.dart';
|
||||||
|
|
||||||
void main() async {
|
void main(List<String> args) async {
|
||||||
// Set up the SettingsController, which will glue user settings to multiple
|
// Set up the SettingsController, which will glue user settings to multiple
|
||||||
// Flutter Widgets.
|
// Flutter Widgets.
|
||||||
final settingsController = SettingsController(SettingsService());
|
final settingsController = SettingsController(SettingsService());
|
||||||
|
|
@ -13,8 +15,13 @@ void main() async {
|
||||||
// This prevents a sudden theme change when the app is first displayed.
|
// This prevents a sudden theme change when the app is first displayed.
|
||||||
await settingsController.loadSettings();
|
await settingsController.loadSettings();
|
||||||
|
|
||||||
|
File? file;
|
||||||
|
if (args.isNotEmpty) {
|
||||||
|
file = File(args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
// Run the app and pass in the SettingsController. The app listens to the
|
// Run the app and pass in the SettingsController. The app listens to the
|
||||||
// SettingsController for changes, then passes it further down to the
|
// SettingsController for changes, then passes it further down to the
|
||||||
// SettingsView.
|
// SettingsView.
|
||||||
runApp(LogViewerApp(settingsController: settingsController));
|
runApp(LogViewerApp(settingsController: settingsController, file: file));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
136
lib/src/app.dart
136
lib/src/app.dart
|
|
@ -6,16 +6,24 @@ import 'package:desktop_drop/desktop_drop.dart';
|
||||||
|
|
||||||
class LogViewerApp extends StatefulWidget {
|
class LogViewerApp extends StatefulWidget {
|
||||||
final SettingsController settingsController;
|
final SettingsController settingsController;
|
||||||
const LogViewerApp({super.key, required this.settingsController});
|
final File? file;
|
||||||
|
const LogViewerApp(
|
||||||
|
{super.key, required this.settingsController, required this.file});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
LogViewerAppState createState() =>
|
LogViewerAppState createState() => LogViewerAppState();
|
||||||
LogViewerAppState(settingsController: settingsController);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogViewerAppState extends State<LogViewerApp> {
|
class LogViewerAppState extends State<LogViewerApp> {
|
||||||
final SettingsController settingsController;
|
late SettingsController settingsController;
|
||||||
LogViewerAppState({required this.settingsController});
|
late File? file;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
settingsController = widget.settingsController;
|
||||||
|
file = widget.file;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -23,6 +31,7 @@ class LogViewerAppState extends State<LogViewerApp> {
|
||||||
listenable: settingsController,
|
listenable: settingsController,
|
||||||
builder: (BuildContext context, Widget? child) {
|
builder: (BuildContext context, Widget? child) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
title: 'Fast Log Viewer',
|
title: 'Fast Log Viewer',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
primarySwatch: Colors.blue,
|
primarySwatch: Colors.blue,
|
||||||
|
|
@ -34,6 +43,7 @@ class LogViewerAppState extends State<LogViewerApp> {
|
||||||
),
|
),
|
||||||
themeMode: settingsController.themeMode,
|
themeMode: settingsController.themeMode,
|
||||||
home: LogViewerScreen(
|
home: LogViewerScreen(
|
||||||
|
file: file,
|
||||||
onThemeChanged: (ThemeMode mode) async {
|
onThemeChanged: (ThemeMode mode) async {
|
||||||
await settingsController.updateThemeMode(mode);
|
await settingsController.updateThemeMode(mode);
|
||||||
},
|
},
|
||||||
|
|
@ -47,13 +57,16 @@ class LogViewerAppState extends State<LogViewerApp> {
|
||||||
class LogViewerScreen extends StatefulWidget {
|
class LogViewerScreen extends StatefulWidget {
|
||||||
final Function(ThemeMode) onThemeChanged;
|
final Function(ThemeMode) onThemeChanged;
|
||||||
final SettingsController settingsController;
|
final SettingsController settingsController;
|
||||||
|
final File? file;
|
||||||
|
|
||||||
const LogViewerScreen(
|
const LogViewerScreen(
|
||||||
{required this.onThemeChanged, required this.settingsController});
|
{super.key,
|
||||||
|
required this.onThemeChanged,
|
||||||
|
required this.settingsController,
|
||||||
|
required this.file});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
LogViewerScreenState createState() =>
|
LogViewerScreenState createState() => LogViewerScreenState();
|
||||||
LogViewerScreenState(settingsController: settingsController);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogViewerScreenState extends State<LogViewerScreen> {
|
class LogViewerScreenState extends State<LogViewerScreen> {
|
||||||
|
|
@ -62,24 +75,51 @@ class LogViewerScreenState extends State<LogViewerScreen> {
|
||||||
String search = '';
|
String search = '';
|
||||||
LogLevel selectedLogLevel = LogLevel.info;
|
LogLevel selectedLogLevel = LogLevel.info;
|
||||||
bool _isDragging = false;
|
bool _isDragging = false;
|
||||||
int? selectedLogIndex;
|
|
||||||
|
|
||||||
final SettingsController settingsController;
|
late SettingsController settingsController;
|
||||||
LogViewerScreenState({required this.settingsController});
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.file != null) {
|
||||||
|
widget.file?.readAsString().then((String fileContent) {
|
||||||
|
logs = _parseLogFile(fileContent);
|
||||||
|
_filterLogs();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
settingsController = widget.settingsController;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return Scaffold(
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
selectedLogIndex = null;
|
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Scaffold(
|
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Log Viewer'),
|
title: const Text('Fast Log Viewer'),
|
||||||
actions: [
|
actions: [
|
||||||
|
DropdownButton<ThemeMode>(
|
||||||
|
value: settingsController.themeMode,
|
||||||
|
onChanged: (ThemeMode? newValue) async {
|
||||||
|
if (newValue != null) {
|
||||||
|
await settingsController.updateThemeMode(newValue);
|
||||||
|
setState(() {
|
||||||
|
widget.onThemeChanged(newValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
items: const [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: ThemeMode.light,
|
||||||
|
child: Text('Light'),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: ThemeMode.dark,
|
||||||
|
child: Text('Dark'),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: ThemeMode.system,
|
||||||
|
child: Text('System'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.folder_open),
|
icon: const Icon(Icons.folder_open),
|
||||||
onPressed: _openFile,
|
onPressed: _openFile,
|
||||||
|
|
@ -151,58 +191,26 @@ class LogViewerScreenState extends State<LogViewerScreen> {
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8.0),
|
|
||||||
DropdownButton<ThemeMode>(
|
|
||||||
value: settingsController.themeMode,
|
|
||||||
onChanged: (ThemeMode? newValue) async {
|
|
||||||
if (newValue != null) {
|
|
||||||
await settingsController.updateThemeMode(newValue);
|
|
||||||
setState(() {
|
|
||||||
widget.onThemeChanged(newValue);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
items: const [
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: ThemeMode.light,
|
|
||||||
child: Text('Light'),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: ThemeMode.dark,
|
|
||||||
child: Text('Dark'),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: ThemeMode.system,
|
|
||||||
child: Text('System'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
child: SelectionArea(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: visibleLogs.length,
|
itemCount: visibleLogs.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
selectedLogIndex = index;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: LogEntryWidget(
|
child: LogEntryWidget(
|
||||||
log: visibleLogs[index],
|
log: visibleLogs[index],
|
||||||
isSelected: selectedLogIndex == index,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -334,45 +342,33 @@ class LogEntry {
|
||||||
|
|
||||||
class LogEntryWidget extends StatelessWidget {
|
class LogEntryWidget extends StatelessWidget {
|
||||||
final LogEntry log;
|
final LogEntry log;
|
||||||
final bool isSelected;
|
|
||||||
|
|
||||||
const LogEntryWidget(
|
const LogEntryWidget({super.key, required this.log});
|
||||||
{super.key, required this.log, required this.isSelected});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
color: isSelected ? Colors.blue[100] : null,
|
|
||||||
margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
|
margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SelectableText(
|
Text(
|
||||||
'${log.timestamp} - [${log.level.name}] - ${log.category} - ${log.threadId}',
|
'${log.timestamp} - [${log.level.name}] - ${log.category} - ${log.threadId}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: _getLevelColor(log.level),
|
color: _getLevelColor(log.level),
|
||||||
),
|
),
|
||||||
onTap: () {
|
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
SelectableText(
|
Text(
|
||||||
'Component: ${log.component.name}@${log.component.address}',
|
'Component: ${log.component.name}@${log.component.address}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontStyle: FontStyle.italic,
|
fontStyle: FontStyle.italic,
|
||||||
),
|
),
|
||||||
onTap: () {
|
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
SelectableText(
|
Text(
|
||||||
log.message.join('\n'),
|
log.message.join('\n'),
|
||||||
onTap: () {
|
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
/// A placeholder class that represents an entity or model.
|
|
||||||
class SampleItem {
|
|
||||||
const SampleItem(this.id);
|
|
||||||
|
|
||||||
final int id;
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Displays detailed information about a SampleItem.
|
|
||||||
class SampleItemDetailsView extends StatelessWidget {
|
|
||||||
const SampleItemDetailsView({super.key});
|
|
||||||
|
|
||||||
static const routeName = '/sample_item';
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Item Details'),
|
|
||||||
),
|
|
||||||
body: const Center(
|
|
||||||
child: Text('More Information Here'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../settings/settings_view.dart';
|
|
||||||
import 'sample_item.dart';
|
|
||||||
import 'sample_item_details_view.dart';
|
|
||||||
|
|
||||||
/// Displays a list of SampleItems.
|
|
||||||
class SampleItemListView extends StatelessWidget {
|
|
||||||
const SampleItemListView({
|
|
||||||
super.key,
|
|
||||||
this.items = const [SampleItem(1), SampleItem(2), SampleItem(3)],
|
|
||||||
});
|
|
||||||
|
|
||||||
static const routeName = '/';
|
|
||||||
|
|
||||||
final List<SampleItem> items;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Sample Items'),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.settings),
|
|
||||||
onPressed: () {
|
|
||||||
// Navigate to the settings page. If the user leaves and returns
|
|
||||||
// to the app after it has been killed while running in the
|
|
||||||
// background, the navigation stack is restored.
|
|
||||||
Navigator.restorablePushNamed(context, SettingsView.routeName);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
// To work with lists that may contain a large number of items, it’s best
|
|
||||||
// to use the ListView.builder constructor.
|
|
||||||
//
|
|
||||||
// In contrast to the default ListView constructor, which requires
|
|
||||||
// building all Widgets up front, the ListView.builder constructor lazily
|
|
||||||
// builds Widgets as they’re scrolled into view.
|
|
||||||
body: ListView.builder(
|
|
||||||
// Providing a restorationId allows the ListView to restore the
|
|
||||||
// scroll position when a user leaves and returns to the app after it
|
|
||||||
// has been killed while running in the background.
|
|
||||||
restorationId: 'sampleItemListView',
|
|
||||||
itemCount: items.length,
|
|
||||||
itemBuilder: (BuildContext context, int index) {
|
|
||||||
final item = items[index];
|
|
||||||
|
|
||||||
return ListTile(
|
|
||||||
title: Text('SampleItem ${item.id}'),
|
|
||||||
leading: const CircleAvatar(
|
|
||||||
// Display the Flutter Logo image asset.
|
|
||||||
foregroundImage: AssetImage('assets/images/flutter_logo.png'),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
// Navigate to the details page. If the user leaves and returns to
|
|
||||||
// the app after it has been killed while running in the
|
|
||||||
// background, the navigation stack is restored.
|
|
||||||
Navigator.restorablePushNamed(
|
|
||||||
context,
|
|
||||||
SampleItemDetailsView.routeName,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue