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 'src/app.dart';
import 'src/settings/settings_controller.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
// Flutter Widgets.
final settingsController = SettingsController(SettingsService());
@ -13,8 +15,13 @@ void main() async {
// This prevents a sudden theme change when the app is first displayed.
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
// SettingsController for changes, then passes it further down to the
// 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 {
final SettingsController settingsController;
const LogViewerApp({super.key, required this.settingsController});
final File? file;
const LogViewerApp(
{super.key, required this.settingsController, required this.file});
@override
LogViewerAppState createState() =>
LogViewerAppState(settingsController: settingsController);
LogViewerAppState createState() => LogViewerAppState();
}
class LogViewerAppState extends State<LogViewerApp> {
final SettingsController settingsController;
LogViewerAppState({required this.settingsController});
late SettingsController settingsController;
late File? file;
@override
void initState() {
super.initState();
settingsController = widget.settingsController;
file = widget.file;
}
@override
Widget build(BuildContext context) {
@ -23,6 +31,7 @@ class LogViewerAppState extends State<LogViewerApp> {
listenable: settingsController,
builder: (BuildContext context, Widget? child) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Fast Log Viewer',
theme: ThemeData(
primarySwatch: Colors.blue,
@ -34,6 +43,7 @@ class LogViewerAppState extends State<LogViewerApp> {
),
themeMode: settingsController.themeMode,
home: LogViewerScreen(
file: file,
onThemeChanged: (ThemeMode mode) async {
await settingsController.updateThemeMode(mode);
},
@ -47,13 +57,16 @@ class LogViewerAppState extends State<LogViewerApp> {
class LogViewerScreen extends StatefulWidget {
final Function(ThemeMode) onThemeChanged;
final SettingsController settingsController;
final File? file;
const LogViewerScreen(
{required this.onThemeChanged, required this.settingsController});
{super.key,
required this.onThemeChanged,
required this.settingsController,
required this.file});
@override
LogViewerScreenState createState() =>
LogViewerScreenState(settingsController: settingsController);
LogViewerScreenState createState() => LogViewerScreenState();
}
class LogViewerScreenState extends State<LogViewerScreen> {
@ -62,144 +75,139 @@ class LogViewerScreenState extends State<LogViewerScreen> {
String search = '';
LogLevel selectedLogLevel = LogLevel.info;
bool _isDragging = false;
int? selectedLogIndex;
final SettingsController settingsController;
LogViewerScreenState({required this.settingsController});
late SettingsController settingsController;
@override
void initState() {
super.initState();
if (widget.file != null) {
widget.file?.readAsString().then((String fileContent) {
logs = _parseLogFile(fileContent);
_filterLogs();
});
}
settingsController = widget.settingsController;
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
selectedLogIndex = null;
FocusScope.of(context).unfocus();
});
},
child: Scaffold(
appBar: AppBar(
title: const Text('Log Viewer'),
actions: [
IconButton(
icon: const Icon(Icons.folder_open),
onPressed: _openFile,
),
],
),
body: DropTarget(
onDragEntered: (details) {
setState(() {
_isDragging = true;
});
},
onDragExited: (details) {
return Scaffold(
appBar: AppBar(
title: const Text('Fast Log Viewer'),
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(
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(() {
logs = _parseLogFile(fileContent);
_filterLogs();
_isDragging = false;
});
},
onDragDone: (details) async {
if (details.files.isNotEmpty) {
File file = File(details.files.first.path);
String fileContent = await file.readAsString();
setState(() {
logs = _parseLogFile(fileContent);
_filterLogs();
_isDragging = false;
});
}
},
child: Container(
color: _isDragging
? Colors.grey[200]
: Theme.of(context).scaffoldBackgroundColor,
child: Column(
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();
});
},
}
},
child: Container(
color: _isDragging
? Colors.grey[200]
: Theme.of(context).scaffoldBackgroundColor,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
decoration: const InputDecoration(
labelText: 'Search Logs',
border: OutlineInputBorder(),
),
),
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: () {
onChanged: (value) {
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 {
final LogEntry log;
final bool isSelected;
const LogEntryWidget(
{super.key, required this.log, required this.isSelected});
const LogEntryWidget({super.key, required this.log});
@override
Widget build(BuildContext context) {
return Card(
color: isSelected ? Colors.blue[100] : null,
margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(
Text(
'${log.timestamp} - [${log.level.name}] - ${log.category} - ${log.threadId}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: _getLevelColor(log.level),
),
onTap: () {
FocusScope.of(context).unfocus();
},
),
SelectableText(
Text(
'Component: ${log.component.name}@${log.component.address}',
style: const TextStyle(
fontStyle: FontStyle.italic,
),
onTap: () {
FocusScope.of(context).unfocus();
},
),
SelectableText(
Text(
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,
);
}
);
},
),
);
}
}