move dropdown up; remove unused view

This commit is contained in:
hardliner66 2024-10-06 03:21:25 +02:00
parent 8f95b644d7
commit bba42586f3
5 changed files with 155 additions and 249 deletions

View File

@ -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));
} }

View File

@ -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,144 +75,139 @@ 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: () { appBar: AppBar(
setState(() { title: const Text('Fast Log Viewer'),
selectedLogIndex = null; actions: [
FocusScope.of(context).unfocus(); DropdownButton<ThemeMode>(
}); value: settingsController.themeMode,
}, onChanged: (ThemeMode? newValue) async {
child: Scaffold( if (newValue != null) {
appBar: AppBar( await settingsController.updateThemeMode(newValue);
title: const Text('Log Viewer'), setState(() {
actions: [ widget.onThemeChanged(newValue);
IconButton( });
icon: const Icon(Icons.folder_open), }
onPressed: _openFile, },
), items: const [
], DropdownMenuItem(
), value: ThemeMode.light,
body: DropTarget( child: Text('Light'),
onDragEntered: (details) { ),
setState(() { DropdownMenuItem(
_isDragging = true; value: ThemeMode.dark,
}); child: Text('Dark'),
}, ),
onDragExited: (details) { DropdownMenuItem(
value: ThemeMode.system,
child: Text('System'),
),
],
),
IconButton(
icon: const Icon(Icons.folder_open),
onPressed: _openFile,
),
],
),
body: DropTarget(
onDragEntered: (details) {
setState(() {
_isDragging = true;
});
},
onDragExited: (details) {
setState(() {
_isDragging = false;
});
},
onDragDone: (details) async {
if (details.files.isNotEmpty) {
File file = File(details.files.first.path);
String fileContent = await file.readAsString();
setState(() { setState(() {
logs = _parseLogFile(fileContent);
_filterLogs();
_isDragging = false; _isDragging = false;
}); });
}, }
onDragDone: (details) async { },
if (details.files.isNotEmpty) { child: Container(
File file = File(details.files.first.path); color: _isDragging
String fileContent = await file.readAsString(); ? Colors.grey[200]
setState(() { : Theme.of(context).scaffoldBackgroundColor,
logs = _parseLogFile(fileContent); child: Column(
_filterLogs(); children: [
_isDragging = false; Padding(
}); padding: const EdgeInsets.all(8.0),
} child: Row(
}, children: [
child: Container( Expanded(
color: _isDragging child: TextField(
? Colors.grey[200] decoration: const InputDecoration(
: Theme.of(context).scaffoldBackgroundColor, labelText: 'Search Logs',
child: Column( border: OutlineInputBorder(),
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
decoration: const InputDecoration(
labelText: 'Search Logs',
border: OutlineInputBorder(),
),
onChanged: (value) {
setState(() {
search = value;
_filterLogs();
});
},
), ),
), onChanged: (value) {
const SizedBox(width: 8.0),
DropdownButton<LogLevel>(
value: selectedLogLevel,
onChanged: (LogLevel? newValue) {
if (newValue != null) {
setState(() {
selectedLogLevel = newValue;
_filterLogs();
});
}
},
items: LogLevel.values
.map<DropdownMenuItem<LogLevel>>((LogLevel level) {
return DropdownMenuItem<LogLevel>(
value: level,
child: Text(level.name),
);
}).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(
child: ListView.builder(
itemCount: visibleLogs.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
setState(() { setState(() {
selectedLogIndex = index; search = value;
_filterLogs();
}); });
}, },
child: LogEntryWidget( ),
log: visibleLogs[index], ),
isSelected: selectedLogIndex == index, const SizedBox(width: 8.0),
), DropdownButton<LogLevel>(
); value: selectedLogLevel,
}, onChanged: (LogLevel? newValue) {
), if (newValue != null) {
setState(() {
selectedLogLevel = newValue;
_filterLogs();
});
}
},
items: LogLevel.values
.map<DropdownMenuItem<LogLevel>>((LogLevel level) {
return DropdownMenuItem<LogLevel>(
value: level,
child: Text(level.name),
);
}).toList(),
),
],
), ),
], ),
), Expanded(
child: SelectionArea(
child: ListView.builder(
itemCount: visibleLogs.length,
itemBuilder: (context, index) {
return GestureDetector(
child: LogEntryWidget(
log: visibleLogs[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();
},
), ),
], ],
), ),

View File

@ -1,6 +0,0 @@
/// A placeholder class that represents an entity or model.
class SampleItem {
const SampleItem(this.id);
final int id;
}

View File

@ -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'),
),
);
}
}

View File

@ -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, its 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 theyre 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,
);
}
);
},
),
);
}
}