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 '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));
|
||||
}
|
||||
|
|
|
|||
296
lib/src/app.dart
296
lib/src/app.dart
|
|
@ -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();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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