persist theme
This commit is contained in:
parent
41821a71d4
commit
8f95b644d7
|
|
@ -16,6 +16,5 @@ void main() async {
|
||||||
// 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));
|
||||||
runApp(LogViewerApp());
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
261
lib/src/app.dart
261
lib/src/app.dart
|
|
@ -1,97 +1,209 @@
|
||||||
|
import 'package:fast_log_viewer/src/settings/settings_controller.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:desktop_drop/desktop_drop.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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return ListenableBuilder(
|
||||||
title: 'Log Viewer',
|
listenable: settingsController,
|
||||||
theme: ThemeData(
|
builder: (BuildContext context, Widget? child) {
|
||||||
primarySwatch: Colors.blue,
|
return MaterialApp(
|
||||||
),
|
title: 'Fast Log Viewer',
|
||||||
home: LogViewerScreen(),
|
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 {
|
class LogViewerScreen extends StatefulWidget {
|
||||||
|
final Function(ThemeMode) onThemeChanged;
|
||||||
|
final SettingsController settingsController;
|
||||||
|
|
||||||
|
const LogViewerScreen(
|
||||||
|
{required this.onThemeChanged, required this.settingsController});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_LogViewerScreenState createState() => _LogViewerScreenState();
|
LogViewerScreenState createState() =>
|
||||||
|
LogViewerScreenState(settingsController: settingsController);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LogViewerScreenState extends State<LogViewerScreen> {
|
class LogViewerScreenState extends State<LogViewerScreen> {
|
||||||
List<LogEntry> logs = [];
|
List<LogEntry> logs = [];
|
||||||
List<LogEntry> visibleLogs = [];
|
List<LogEntry> visibleLogs = [];
|
||||||
String search = '';
|
String search = '';
|
||||||
|
LogLevel selectedLogLevel = LogLevel.info;
|
||||||
bool _isDragging = false;
|
bool _isDragging = false;
|
||||||
|
int? selectedLogIndex;
|
||||||
|
|
||||||
|
final SettingsController settingsController;
|
||||||
|
LogViewerScreenState({required this.settingsController});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
selectedLogIndex = null;
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Log Viewer'),
|
title: const Text('Log Viewer'),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.folder_open),
|
icon: const Icon(Icons.folder_open),
|
||||||
onPressed: _openFile,
|
onPressed: _openFile,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: DropTarget(
|
body: DropTarget(
|
||||||
onDragEntered: (details) {
|
onDragEntered: (details) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isDragging = true;
|
_isDragging = true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onDragExited: (details) {
|
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(
|
||||||
visibleLogs = logs.toList();
|
children: [
|
||||||
_isDragging = false;
|
Padding(
|
||||||
});
|
padding: const EdgeInsets.all(8.0),
|
||||||
}
|
child: Row(
|
||||||
},
|
children: [
|
||||||
child: Container(
|
Expanded(
|
||||||
color: _isDragging ? Colors.grey[200] : Colors.white,
|
child: TextField(
|
||||||
child: Column(
|
decoration: const InputDecoration(
|
||||||
children: [
|
labelText: 'Search Logs',
|
||||||
Padding(
|
border: OutlineInputBorder(),
|
||||||
padding: const EdgeInsets.all(8.0),
|
),
|
||||||
child: TextField(
|
onChanged: (value) {
|
||||||
decoration: const InputDecoration(
|
setState(() {
|
||||||
labelText: 'Search Logs',
|
search = value;
|
||||||
border: OutlineInputBorder(),
|
_filterLogs();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
const SizedBox(width: 8.0),
|
||||||
setState(() {
|
DropdownButton<LogLevel>(
|
||||||
search = value;
|
value: selectedLogLevel,
|
||||||
visibleLogs =
|
onChanged: (LogLevel? newValue) {
|
||||||
logs.where((log) => log.contains(value)).toList();
|
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(
|
Expanded(
|
||||||
itemCount: visibleLogs.length,
|
child: ListView.builder(
|
||||||
itemBuilder: (context, index) {
|
itemCount: visibleLogs.length,
|
||||||
return LogEntryWidget(log: visibleLogs[index]);
|
itemBuilder: (context, index) {
|
||||||
},
|
return GestureDetector(
|
||||||
),
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
selectedLogIndex = index;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: LogEntryWidget(
|
||||||
|
log: visibleLogs[index],
|
||||||
|
isSelected: selectedLogIndex == index,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
)));
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openFile() async {
|
Future<void> _openFile() async {
|
||||||
|
|
@ -101,11 +213,20 @@ class _LogViewerScreenState extends State<LogViewerScreen> {
|
||||||
String fileContent = await file.readAsString();
|
String fileContent = await file.readAsString();
|
||||||
setState(() {
|
setState(() {
|
||||||
logs = _parseLogFile(fileContent);
|
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> _parseLogFile(String content) {
|
||||||
List<LogEntry> parsedLogs = [];
|
List<LogEntry> parsedLogs = [];
|
||||||
List<String> lines = content.split('\n');
|
List<String> lines = content.split('\n');
|
||||||
|
|
@ -213,32 +334,46 @@ class LogEntry {
|
||||||
|
|
||||||
class LogEntryWidget extends StatelessWidget {
|
class LogEntryWidget extends StatelessWidget {
|
||||||
final LogEntry log;
|
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
|
@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: [
|
||||||
Text(
|
SelectableText(
|
||||||
'${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();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Text(
|
SelectableText(
|
||||||
'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(
|
||||||
|
log.message.join('\n'),
|
||||||
|
onTap: () {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Text(log.message.join('\n')),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
/// A service that stores and retrieves user settings.
|
/// 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.
|
/// you'd like to store settings on a web server, use the http package.
|
||||||
class SettingsService {
|
class SettingsService {
|
||||||
/// Loads the User's preferred ThemeMode from local or remote storage.
|
/// 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.
|
/// Persists the user's preferred ThemeMode to local or remote storage.
|
||||||
Future<void> updateThemeMode(ThemeMode theme) async {
|
Future<void> updateThemeMode(ThemeMode theme) async {
|
||||||
// Use the shared_preferences package to persist settings locally or the
|
var prefs = await SharedPreferences.getInstance();
|
||||||
// http package to persist settings over the network.
|
prefs.setInt("theme", theme.index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import desktop_drop
|
import desktop_drop
|
||||||
|
import shared_preferences_foundation
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
|
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
|
||||||
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
104
pubspec.lock
104
pubspec.lock
|
|
@ -73,6 +73,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.3"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
file_picker:
|
file_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -189,6 +197,38 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
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:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -197,6 +237,62 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
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:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
@ -282,6 +378,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.5.5"
|
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:
|
sdks:
|
||||||
dart: ">=3.5.3 <4.0.0"
|
dart: ">=3.5.3 <4.0.0"
|
||||||
flutter: ">=3.24.0"
|
flutter: ">=3.24.0"
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ dependencies:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
file_picker:
|
file_picker:
|
||||||
desktop_drop:
|
desktop_drop:
|
||||||
|
shared_preferences:
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue