persist theme

This commit is contained in:
hardliner66 2024-10-06 02:51:15 +02:00
parent 41821a71d4
commit 8f95b644d7
6 changed files with 317 additions and 68 deletions

View File

@ -16,6 +16,5 @@ void main() async {
// 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());
runApp(LogViewerApp(settingsController: settingsController));
}

View File

@ -1,97 +1,209 @@
import 'package:fast_log_viewer/src/settings/settings_controller.dart';
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:desktop_drop/desktop_drop.dart';
class LogViewerApp extends StatelessWidget {
class LogViewerApp extends StatefulWidget {
final SettingsController settingsController;
const LogViewerApp({super.key, required this.settingsController});
@override
LogViewerAppState createState() =>
LogViewerAppState(settingsController: settingsController);
}
class LogViewerAppState extends State<LogViewerApp> {
final SettingsController settingsController;
LogViewerAppState({required this.settingsController});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Log Viewer',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: LogViewerScreen(),
);
return ListenableBuilder(
listenable: settingsController,
builder: (BuildContext context, Widget? child) {
return MaterialApp(
title: 'Fast Log Viewer',
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
),
darkTheme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.dark,
),
themeMode: settingsController.themeMode,
home: LogViewerScreen(
onThemeChanged: (ThemeMode mode) async {
await settingsController.updateThemeMode(mode);
},
settingsController: settingsController,
),
);
});
}
}
class LogViewerScreen extends StatefulWidget {
final Function(ThemeMode) onThemeChanged;
final SettingsController settingsController;
const LogViewerScreen(
{required this.onThemeChanged, required this.settingsController});
@override
_LogViewerScreenState createState() => _LogViewerScreenState();
LogViewerScreenState createState() =>
LogViewerScreenState(settingsController: settingsController);
}
class _LogViewerScreenState extends State<LogViewerScreen> {
class LogViewerScreenState extends State<LogViewerScreen> {
List<LogEntry> logs = [];
List<LogEntry> visibleLogs = [];
String search = '';
LogLevel selectedLogLevel = LogLevel.info;
bool _isDragging = false;
int? selectedLogIndex;
final SettingsController settingsController;
LogViewerScreenState({required this.settingsController});
@override
Widget build(BuildContext context) {
return Scaffold(
return GestureDetector(
onTap: () {
setState(() {
selectedLogIndex = null;
FocusScope.of(context).unfocus();
});
},
child: Scaffold(
appBar: AppBar(
title: const Text('Log Viewer'),
actions: [
IconButton(
icon: Icon(Icons.folder_open),
icon: const Icon(Icons.folder_open),
onPressed: _openFile,
),
],
),
body: DropTarget(
onDragEntered: (details) {
setState(() {
_isDragging = true;
});
},
onDragExited: (details) {
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);
visibleLogs = logs.toList();
_isDragging = false;
});
}
},
child: Container(
color: _isDragging ? Colors.grey[200] : Colors.white,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: const InputDecoration(
labelText: 'Search Logs',
border: OutlineInputBorder(),
}
},
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();
});
},
),
),
onChanged: (value) {
setState(() {
search = value;
visibleLogs =
logs.where((log) => log.contains(value)).toList();
});
},
),
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 LogEntryWidget(log: visibleLogs[index]);
},
),
),
Expanded(
child: ListView.builder(
itemCount: visibleLogs.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
setState(() {
selectedLogIndex = index;
});
},
child: LogEntryWidget(
log: visibleLogs[index],
isSelected: selectedLogIndex == index,
),
);
},
),
],
),
)));
),
],
),
),
),
),
);
}
Future<void> _openFile() async {
@ -101,11 +213,20 @@ class _LogViewerScreenState extends State<LogViewerScreen> {
String fileContent = await file.readAsString();
setState(() {
logs = _parseLogFile(fileContent);
visibleLogs = logs.where((log) => log.contains(search)).toList();
_filterLogs();
});
}
}
void _filterLogs() {
setState(() {
visibleLogs = logs
.where((log) =>
log.level.index >= selectedLogLevel.index && log.contains(search))
.toList();
});
}
List<LogEntry> _parseLogFile(String content) {
List<LogEntry> parsedLogs = [];
List<String> lines = content.split('\n');
@ -213,32 +334,46 @@ class LogEntry {
class LogEntryWidget extends StatelessWidget {
final LogEntry log;
final bool isSelected;
const LogEntryWidget({Key? key, required this.log}) : super(key: key);
const LogEntryWidget(
{super.key, required this.log, required this.isSelected});
@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: [
Text(
SelectableText(
'${log.timestamp} - [${log.level.name}] - ${log.category} - ${log.threadId}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: _getLevelColor(log.level),
),
onTap: () {
FocusScope.of(context).unfocus();
},
),
Text(
SelectableText(
'Component: ${log.component.name}@${log.component.address}',
style: const TextStyle(
fontStyle: FontStyle.italic,
),
onTap: () {
FocusScope.of(context).unfocus();
},
),
SelectableText(
log.message.join('\n'),
onTap: () {
FocusScope.of(context).unfocus();
},
),
Text(log.message.join('\n')),
],
),
),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// A service that stores and retrieves user settings.
///
@ -7,11 +8,18 @@ import 'package:flutter/material.dart';
/// you'd like to store settings on a web server, use the http package.
class SettingsService {
/// Loads the User's preferred ThemeMode from local or remote storage.
Future<ThemeMode> themeMode() async => ThemeMode.system;
Future<ThemeMode> themeMode() async {
var prefs = await SharedPreferences.getInstance();
var index = prefs.getInt("theme");
if (index != null) {
return ThemeMode.values[index];
}
return ThemeMode.system;
}
/// Persists the user's preferred ThemeMode to local or remote storage.
Future<void> updateThemeMode(ThemeMode theme) async {
// Use the shared_preferences package to persist settings locally or the
// http package to persist settings over the network.
var prefs = await SharedPreferences.getInstance();
prefs.setInt("theme", theme.index);
}
}

View File

@ -6,7 +6,9 @@ import FlutterMacOS
import Foundation
import desktop_drop
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}

View File

@ -73,6 +73,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.3"
file:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
file_picker:
dependency: "direct main"
description:
@ -189,6 +197,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
plugin_platform_interface:
dependency: transitive
description:
@ -197,6 +237,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev"
source: hosted
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
@ -282,6 +378,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.5.5"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
sdks:
dart: ">=3.5.3 <4.0.0"
flutter: ">=3.24.0"

View File

@ -16,6 +16,7 @@ dependencies:
sdk: flutter
file_picker:
desktop_drop:
shared_preferences:
dev_dependencies:
flutter_test: