feat: triggers

This commit is contained in:
2023-09-24 15:45:27 +03:00
parent d65d707646
commit 4c1404ca07
17 changed files with 435 additions and 191 deletions

View File

@@ -1,52 +1,15 @@
import 'package:flutter/foundation.dart';
import 'package:mudblock/core/consts.dart';
import 'parser/parser.dart';
class ColorUtils {
static stripColor(String text) {
return text
// esc
// .replaceAll(esc, '')
// .replaceAll(r'^[', '')
// color
.replaceAll(RegExp(esc + colorPatternRaw), '')
// color
// .replaceAll(RegExp(r'\[\d+m'), '')
// esc
// .replaceAll(String.fromCharCode(0xff), '');
//
;
return split(text).map((e) => e.text).join();
}
static Iterable<ColoredText> split(String line) {
// return line.split(RegExp(esc + colorPatternRaw)).map(
// (raw) => ColoredText(
// text: stripColor(raw),
// color: 0,
// raw: raw,
// ),
// );
try {
final result = <ColoredText>[];
// final tokenizer = Tokenizer(StringReader(line));
// var lexer = Lexer(tokenizer);
// final tokens = lexer.lex();
// for (var i = 0; i < tokens.length; i++) {
// final token = tokens[i];
// result.add(
// ColoredText(
// text: token.text,
// fgColor: token.fgColor,
// bgColor: token.bgColor,
// raw: token.text,
// ),
// );
// }
final tokens = ColorParser(line).parse();
for (final token in tokens) {
@@ -91,7 +54,7 @@ class ColoredText extends ColorToken {
return xterm256 ? (xtermColorMap[fgColor] ?? xtermColorMap[15]!) : (ansiFgColorMap[fgColor] ?? ansiFgColorMap[97]!);
}
int get themedBgColor => ansiBgColorMap[bgColor] ?? ansiBgColorMap[40]!;
int get themedBgColor => ansiBgColorMap[bgColor] ?? 0x00000000;
}
/// map of ansi colors to flutter color ints

View File

@@ -1,3 +1,5 @@
import 'package:flutter/widgets.dart';
const newline = '\n';
const cr = '\r';
const lf = '\n';
@@ -9,3 +11,4 @@ const colorPatternRaw = r'\[\d*m';
const boldByte = 1;
const italicByte = 3;
const underlineByte = 4;
final homeKey = GlobalKey(debugLabel: 'Home');

View File

@@ -0,0 +1,45 @@
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
import '../store.dart';
enum MUDActionTarget {
world,
execute,
script,
input,
}
class MUDAction {
final String content;
final MUDActionTarget sendTo;
const MUDAction(this.content, {this.sendTo = MUDActionTarget.world});
void invoke(BuildContext context, List<String> matches) {
final store = Provider.of<GameStore>(context, listen: false);
debugPrint('MUDAction.invoke: ${this.content}, $matches');
var content = this.content;
for (var i = 0; i < matches.length; i++) {
content = content.replaceAll('%$i', matches[i]);
}
debugPrint('MUDAction.invoking: $content');
switch (sendTo) {
case MUDActionTarget.world:
debugPrint('ActionSendTo.world: $content');
store.send(content);
break;
case MUDActionTarget.execute:
debugPrint('ActionSendTo.execute: $content');
break;
case MUDActionTarget.script:
debugPrint('ActionSendTo.script: $content');
break;
case MUDActionTarget.input:
debugPrint('ActionSendTo.input: $content');
store.setInput(content);
break;
}
}
}

View File

@@ -0,0 +1,14 @@
class MUDProfile {
String id;
String name;
String host;
int port;
MUDProfile({
required this.id,
required this.name,
required this.host,
required this.port,
});
}

View File

@@ -0,0 +1,68 @@
import 'package:flutter/widgets.dart';
import 'action.dart';
class Trigger {
String id;
String pattern;
bool isRegex;
bool isCaseSensitive;
// bool isMultiLine;
bool isRemovedFromBuffer;
bool isTemporary;
int invokeCount = 0;
MUDAction action;
Trigger({
required this.id,
required this.pattern,
required this.action,
this.isRegex = false,
this.isCaseSensitive = false,
// required this.isMultiLine,
this.isRemovedFromBuffer = false,
this.isTemporary = false,
});
bool matches(String line) {
if (isRegex) {
final regex = RegExp(pattern, caseSensitive: isCaseSensitive);
return regex.hasMatch(line);
} else {
if (isCaseSensitive) {
return line.toLowerCase().contains(pattern.toLowerCase());
}
return line.contains(pattern);
}
}
void invokeEffect(BuildContext context, String line) {
invokeCount++;
action.invoke(context, allMatches(line));
}
List<String> allMatches(str) {
if (!matches(str)) {
return [];
}
if (isRegex) {
final regex = RegExp(pattern, caseSensitive: isCaseSensitive);
debugPrint('allMatches: ${regex.allMatches(str).map((m) => m.group(1)!).toList()}');
return regex.allMatches(str).map((m) => m.group(0)!).toList();
} else {
final input = isCaseSensitive ? str.toLowerCase() : str;
final compare = isCaseSensitive ? pattern.toLowerCase() : pattern;
final matches = <String>[str];
for (var i = 0; i < input.length; i++) {
final index = input.indexOf(compare, i);
if (index == -1) {
break;
}
matches.add(str.substring(index, index + compare.length));
i = index + compare.length;
}
return matches;
}
}
}

View File

@@ -0,0 +1,7 @@
import 'package:shared_preferences/shared_preferences.dart';
late final SharedPreferences prefs;
Future<SharedPreferences> getPrefs() async {
return prefs = await SharedPreferences.getInstance();
}

View File

@@ -1,3 +0,0 @@
export './storage_macos.dart' if (dart.library.io) './storage_io.dart' if (dart.library.html) './storage_web.dart';

View File

@@ -1,9 +0,0 @@
abstract class IFileStorage {
Future<String> read(String path);
Future<void> write(String path, String value);
Future<void> delete(String path);
Future<bool> exists(String path);
Future<List<String>> list(String path);
Future<void> createDirectory(String path);
Future<void> deleteDirectory(String path);
}

View File

@@ -1,37 +0,0 @@
import 'dart:io';
import 'package:mudblock/core/storage/storage_base.dart';
class FileStorage implements IFileStorage {
@override
Future<void> createDirectory(String path) => Directory(path).create(recursive: true);
@override
Future<void> delete(String path) => File(path).delete();
@override
Future<void> deleteDirectory(String path) => Directory(path).delete(recursive: true);
@override
Future<bool> exists(String path) async {
final type = await FileSystemEntity.type(path);
return type != FileSystemEntityType.notFound;
}
@override
Future<List<String>> list(String path) {
return Directory(path)
.list()
.map((e) => e.path.substring(
e.path.lastIndexOf(Platform.pathSeparator) + 1,
))
.toList();
}
@override
Future<String> read(String path) => File(path).readAsString();
@override
Future<void> write(String path, String value) => File(path).writeAsString(value);
}

View File

@@ -1,46 +0,0 @@
import 'package:mudblock/core/storage/storage_base.dart';
class FileStorage implements IFileStorage {
@override
Future<void> createDirectory(String path) {
// TODO: implement createDirectory
throw UnimplementedError();
}
@override
Future<void> delete(String path) {
// TODO: implement delete
throw UnimplementedError();
}
@override
Future<void> deleteDirectory(String path) {
// TODO: implement deleteDirectory
throw UnimplementedError();
}
@override
Future<bool> exists(String path) {
// TODO: implement exists
throw UnimplementedError();
}
@override
Future<List<String>> list(String path) {
// TODO: implement list
throw UnimplementedError();
}
@override
Future<String> read(String path) {
// TODO: implement read
throw UnimplementedError();
}
@override
Future<void> write(String path, String value) {
// TODO: implement write
throw UnimplementedError();
}
}

View File

@@ -1,13 +1,17 @@
import 'dart:convert';
import 'dart:math';
import 'dart:io';
import 'dart:typed_data';
import 'dart:math';
import 'package:ctelnet/ctelnet.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
import '../pages/home_page.dart';
import 'color_utils.dart';
import 'consts.dart';
import 'features/action.dart';
import 'features/profile.dart';
import 'features/trigger.dart';
const maxLines = 2000;
@@ -20,24 +24,68 @@ class GameStore extends ChangeNotifier {
final FocusNode inputFocus = FocusNode();
bool isCompressed = false;
final ZLibDecoder decoder = ZLibDecoder();
final List<Trigger> triggers = [];
MUDProfile? _currentProfile;
MUDProfile get currentProfile => _currentProfile!;
GameStore init() {
debugPrint('GameStore.init');
addLine('Connecting...');
final profiles = [
MUDProfile(id: 'local', name: 'Local', host: 'localhost', port: 4000),
MUDProfile(id: 'smud', name: 'SimpleMUD', host: 'smud.ourmmo.com', port: 3000),
MUDProfile(id: 'aardwolf', name: 'Aardwolf', host: 'aardmud.org', port: 23),
MUDProfile(id: 'batmud', name: 'BatMUD', host: 'batmud.bat.org', port: 23),
MUDProfile(id: 'dune', name: 'Dune', host: 'dune.servint.com', port: 6789),
];
_currentProfile = profiles[1];
_client = CTelnetClient(
// host: 'aardmud.org',
// port: 23,
host: 'smud.ourmmo.com',
port: 3000,
host: currentProfile.host,
port: currentProfile.port,
onConnect: _onConnect,
onDisconnect: onDisconnect,
onData: onData,
onError: onError,
);
scrollController = ScrollController();
loadTriggers();
_client.connect();
return this;
}
void loadTriggers() {
triggers.clear();
triggers.add(
Trigger(
id: 'test',
pattern: r'^You are in the ([^.]+)\. This is the ([^.]+)\.',
action: const MUDAction('Hello, %1, the %2!', sendTo: MUDActionTarget.world),
isRegex: true,
),
);
debugPrint('triggers: ${triggers.length}');
}
bool processTriggers(BuildContext context, String line) {
bool showLine = true;
final str = ColorUtils.stripColor(line);
debugPrint('Processing triggers for: $str');
for (final trigger in triggers) {
debugPrint('trigger: ${trigger.pattern}');
if (trigger.matches(str)) {
debugPrint('trigger matches: ${trigger.pattern}');
trigger.invokeEffect(context, str);
if (trigger.isRemovedFromBuffer) {
debugPrint('line is removed from buffer');
showLine = false;
}
}
}
debugPrint('');
return showLine;
}
void _onConnect() {
addLine('Connected');
}
@@ -53,7 +101,8 @@ class GameStore extends ChangeNotifier {
void onData(Message data) {
try {
debugPrint('text: ${data.text}');
final home = homeKey.currentState as HomePageState;
debugPrint('text: ${data.text}');
debugPrint('subnegotiations: ${data.data.subnegotiations}');
if (mccpEnabled && isCompressed) {
@@ -65,7 +114,7 @@ class GameStore extends ChangeNotifier {
final pattern = RegExp("($cr$lf)|($lf$cr)|$cr|$lf");
for (final line in data.text.trimRight().split(pattern)) {
onLine(line);
onLine(home.context, line);
}
} catch (e, stack) {
debugPrint('error: $e$newline$stack');
@@ -116,8 +165,11 @@ class GameStore extends ChangeNotifier {
onError('Error: $error');
}
void onLine(String line) {
addLine(line);
void onLine(BuildContext context, String line) {
var showLine = processTriggers(context, line);
if (showLine) {
addLine(line);
}
}
List<String> get lines => _lines.sublist(max(0, _lines.length - maxLines), _lines.length);
@@ -162,6 +214,10 @@ class GameStore extends ChangeNotifier {
});
}
void unselectInput() {
input.selection = const TextSelection.collapsed(offset: -1);
}
void selectInput() {
input.selection = TextSelection(
baseOffset: 0,
@@ -170,6 +226,11 @@ class GameStore extends ChangeNotifier {
inputFocus.previousFocus();
inputFocus.requestFocus();
}
void setInput(String content) {
input.text = content;
selectInput();
}
}
mixin GameStoreMixin<T extends StatefulWidget> on State<T> {

View File

@@ -1,17 +1,23 @@
import 'package:flutter/material.dart';
import 'package:mudblock/core/consts.dart';
import 'package:mudblock/core/store.dart';
import 'package:provider/provider.dart';
import 'package:window_manager/window_manager.dart';
import 'core/storage/shared_prefs.dart';
import 'pages/home_page.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await getPrefs();
await windowManager.ensureInitialized();
WindowOptions windowOptions = const WindowOptions(
size: Size(1000, 900),
center: true,
final w = prefs.getInt('windowWidth') ?? 1000;
final h = prefs.getInt('windowHeight') ?? 900;
final size = Size(w.toDouble(), h.toDouble());
WindowOptions windowOptions = WindowOptions(
size: size,
backgroundColor: Colors.transparent,
skipTaskbar: false,
titleBarStyle: TitleBarStyle.hidden,
@@ -42,7 +48,7 @@ class MyApp extends StatelessWidget {
body: ChangeNotifierProvider(
create: (_) => GameStore().init(),
builder: (context, snapshot) {
return const HomePage();
return HomePage(key: homeKey);
},
),
),

View File

@@ -1,7 +1,9 @@
import 'dart:ui';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.dart';
import 'package:mudblock/core/color_utils.dart';
import 'package:mudblock/core/storage/shared_prefs.dart';
import 'package:provider/provider.dart';
import 'package:window_manager/window_manager.dart';
@@ -12,10 +14,10 @@ class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
State<HomePage> createState() => HomePageState();
}
class _HomePageState extends State<HomePage> with GameStoreMixin, WindowListener {
class HomePageState extends State<HomePage> with GameStoreMixin, WindowListener {
@override
void initState() {
super.initState();
@@ -34,7 +36,6 @@ class _HomePageState extends State<HomePage> with GameStoreMixin, WindowListener
color: Colors.white,
fontFamily: 'Menlo',
fontSize: 16,
fontWeight: FontWeight.w500,
height: 1,
);
final inputStyle = consoleStyle.copyWith(color: Colors.grey);
@@ -56,35 +57,38 @@ class _HomePageState extends State<HomePage> with GameStoreMixin, WindowListener
selectionHandleColor: Colors.white,
),
),
child: SingleChildScrollView(
controller: store.scrollController,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SelectableText.rich(
TextSpan(
children: [
for (final line in lines) ...[
for (final segment in ColorUtils.split(line))
TextSpan(
text: segment.text,
style: consoleStyle.copyWith(
color: Color(segment.themedFgColor),
backgroundColor: Color(segment.themedBgColor),
fontWeight: segment.bold ? FontWeight.w800 : null,
fontStyle: segment.italic ? FontStyle.italic : null,
decoration: segment.underline ? TextDecoration.underline : null,
child: GestureDetector(
onTap: store.selectInput,
child: SingleChildScrollView(
controller: store.scrollController,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SelectableText.rich(
TextSpan(
children: [
for (final line in lines) ...[
for (final segment in ColorUtils.split(line))
TextSpan(
text: segment.text,
style: consoleStyle.copyWith(
color: Color(segment.themedFgColor),
backgroundColor: Color(segment.themedBgColor),
fontWeight: segment.bold ? FontWeight.w800 : null,
fontStyle: segment.italic ? FontStyle.italic : null,
decoration: segment.underline ? TextDecoration.underline : null,
),
),
const TextSpan(
text: newline,
style: consoleStyle, // .copyWith(fontSize: 1),
),
TextSpan(
text: newline,
style: consoleStyle.copyWith(fontSize: 1),
),
],
],
],
),
enableInteractiveSelection: true,
selectionWidthStyle: BoxWidthStyle.tight,
selectionHeightStyle: BoxHeightStyle.max,
),
enableInteractiveSelection: true,
selectionWidthStyle: BoxWidthStyle.tight,
selectionHeightStyle: BoxHeightStyle.max,
),
),
),
@@ -95,30 +99,54 @@ class _HomePageState extends State<HomePage> with GameStoreMixin, WindowListener
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
autofocus: true,
focusNode: store.inputFocus,
controller: store.input,
onSubmitted: (text) {
store.submitInput(text);
},
onTap: store.selectInput,
style: consoleStyle.copyWith(color: Colors.black),
decoration: InputDecoration(
hintText: 'Enter command',
border: const OutlineInputBorder(),
hintStyle: inputStyle,
),
child: Row(
children: [
Expanded(
child: TextField(
autofocus: true,
focusNode: store.inputFocus,
controller: store.input,
onSubmitted: store.submitInput,
onTap: store.selectInput,
style: consoleStyle.copyWith(color: Colors.black),
decoration: InputDecoration(
hintText: 'Enter command',
border: const OutlineInputBorder(),
hintStyle: inputStyle,
),
),
),
IconButton(
icon: const Icon(Icons.bug_report),
onPressed: store.loadTriggers,
),
],
),
),
],
);
}
@override
onWindowBlur() {
debugPrint("Window blurred");
store.unselectInput();
}
@override
void onWindowFocus() {
debugPrint("Window focused");
store.selectInput();
}
@override
void onWindowResize() async {
EasyDebounce.debounce('windowResize', const Duration(milliseconds: 500), () async {
final size = await windowManager.getSize();
debugPrint("Window resized to $size");
prefs.setInt('windowWidth', size.width.toInt());
prefs.setInt('windowHeight', size.height.toInt());
});
}
}

View File

@@ -6,9 +6,11 @@ import FlutterMacOS
import Foundation
import screen_retriever
import shared_preferences_foundation
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
}

View File

@@ -2,12 +2,16 @@ PODS:
- FlutterMacOS (1.0.0)
- screen_retriever (0.0.1):
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- window_manager (0.2.0):
- FlutterMacOS
DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral`)
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
EXTERNAL SOURCES:
@@ -15,12 +19,15 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral
screen_retriever:
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
window_manager:
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
SPEC CHECKSUMS:
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367

View File

@@ -80,6 +80,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.6"
easy_debounce:
dependency: "direct main"
description:
name: easy_debounce
sha256: f082609cfb8f37defb9e37fc28bc978c6712dedf08d4c5a26f820fa10165a236
url: "https://pub.dev"
source: hosted
version: "2.0.3"
fake_async:
dependency: transitive
description:
@@ -88,6 +96,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
file:
dependency: transitive
description:
@@ -114,6 +130,11 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
lints:
dependency: transitive
description:
@@ -162,6 +183,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.8.3"
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: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
platform:
dependency: transitive
description:
name: platform
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102
url: "https://pub.dev"
source: hosted
version: "3.1.2"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
url: "https://pub.dev"
source: hosted
version: "2.1.6"
provider:
dependency: "direct main"
description:
@@ -178,6 +239,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.9"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: b7f41bad7e521d205998772545de63ff4e6c97714775902c199353f8bf1511ac
url: "https://pub.dev"
source: hosted
version: "2.2.1"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7"
url: "https://pub.dev"
source: hosted
version: "2.3.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: c2eb5bf57a2fe9ad6988121609e47d3e07bb3bdca5b6f8444e4cf302428a128a
url: "https://pub.dev"
source: hosted
version: "2.3.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
url: "https://pub.dev"
source: hosted
version: "2.3.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf
url: "https://pub.dev"
source: hosted
version: "2.2.1"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: f763a101313bd3be87edffe0560037500967de9c394a714cd598d945517f694f
url: "https://pub.dev"
source: hosted
version: "2.3.1"
sky_engine:
dependency: transitive
description: flutter
@@ -247,6 +364,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
win32:
dependency: transitive
description:
name: win32
sha256: c97defd418eef4ec88c0d1652cdce84b9f7b63dd7198e266d06ac1710d527067
url: "https://pub.dev"
source: hosted
version: "5.0.8"
window_manager:
dependency: "direct main"
description:
@@ -255,6 +380,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.6"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
sdks:
dart: ">=3.1.2 <4.0.0"
flutter: ">=3.3.0"
flutter: ">=3.7.0"

View File

@@ -38,6 +38,8 @@ dependencies:
cupertino_icons: ^1.0.2
provider: ^6.0.5
window_manager: ^0.3.6
shared_preferences: ^2.2.1
easy_debounce: ^2.0.3
dev_dependencies:
flutter_test: