Compare commits
6 Commits
61f2246042
...
07b9e44350
| Author | SHA1 | Date | |
|---|---|---|---|
| 07b9e44350 | |||
| 8f6ed5504b | |||
| 05d1bb2428 | |||
| 0363e915ee | |||
| 2bc54f1cd7 | |||
| 51f6a91bbf |
@@ -0,0 +1,92 @@
|
||||
# AGENTS.md
|
||||
|
||||
This file is the working guide for people and agents changing MoonWell
|
||||
Launcher. Keep it current when architecture, commands, or repository rules
|
||||
change.
|
||||
|
||||
## Project Shape
|
||||
|
||||
MoonWell Launcher is a Flutter desktop app for a private World of Warcraft:
|
||||
Wrath of the Lich King server. It logs players in, downloads and synchronizes
|
||||
client files, displays launcher news, and starts `Wow.exe`.
|
||||
|
||||
Important areas:
|
||||
|
||||
- `lib/main.dart` initializes Flutter, the desktop window, dependency
|
||||
injection, and `MoonWellApp`.
|
||||
- `lib/app` contains UI screens, BLoC state, widgets, and theme code.
|
||||
- `lib/app/widgets` contains shared app-level UI such as the custom desktop
|
||||
title bar.
|
||||
- `lib/features/launcher/domain` contains launcher entities such as sessions,
|
||||
manifests, news items, sync status, and exceptions.
|
||||
- `lib/features/launcher/application` contains use cases for session restore
|
||||
and client synchronization.
|
||||
- `lib/features/launcher/data` contains API, installation, and log services.
|
||||
- `lib/features/preferences` stores selected install directory and launcher
|
||||
session data. Launcher sessions must use secure storage; non-sensitive
|
||||
preferences may use `shared_preferences`.
|
||||
- `lib/service_container.dart` and `lib/service_container.config.dart` provide
|
||||
`get_it`/`injectable` dependency wiring.
|
||||
- `docs/launcher_web_api_spec.md` documents the current launcher Web API and
|
||||
sync behavior.
|
||||
|
||||
## Flutter SDK Policy
|
||||
|
||||
MoonWell Launcher supports the latest stable Flutter SDK only. Do not introduce
|
||||
or rely on FVM-pinned workflows.
|
||||
|
||||
If `flutter` is not available after installing or updating Flutter, restart the
|
||||
shell and verify PATH with `flutter --version`.
|
||||
|
||||
## Common Commands
|
||||
|
||||
```powershell
|
||||
flutter pub get
|
||||
dart run build_runner build --delete-conflicting-outputs
|
||||
dart format --set-exit-if-changed .
|
||||
flutter analyze
|
||||
flutter test
|
||||
flutter run -d windows --dart-define=MOONWELL_API_BASE_URL=https://host
|
||||
flutter build windows --dart-define=MOONWELL_API_BASE_URL=https://host
|
||||
```
|
||||
|
||||
See `docs/linting.md` for the required format, analyze, test, and Lefthook
|
||||
pre-commit workflow.
|
||||
|
||||
## Configuration
|
||||
|
||||
The launcher API base URL is compile-time configuration:
|
||||
|
||||
```powershell
|
||||
--dart-define=MOONWELL_API_BASE_URL=https://host
|
||||
```
|
||||
|
||||
Do not commit private credentials, production secrets, temporary bearer tokens,
|
||||
or local-only API URLs.
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
- All backend calls should go through `LauncherApiClient`.
|
||||
- Launcher bearer tokens must be persisted through `flutter_secure_storage`.
|
||||
Do not store sessions or other secrets in `shared_preferences`.
|
||||
- File sync behavior should stay in `ClientSyncUseCase` and
|
||||
`GameInstallationService`.
|
||||
- The server manifest is the source of truth for client files.
|
||||
- Downloads must write to `*.moonwell.part`, verify size and SHA-256, then
|
||||
replace the destination file only after successful verification.
|
||||
- Local scans should ignore volatile client directories and launcher metadata:
|
||||
`Cache`, `Errors`, `Logs`, `Screenshots`, `WTF`, and `.moonwell_launcher`.
|
||||
- Path normalization and traversal checks are safety-critical. Do not bypass
|
||||
`GameInstallationService.resolveClientPath` for manifest-relative paths.
|
||||
- `lib/service_container.config.dart` is generated by injectable. Do not
|
||||
hand-edit it unless you are intentionally repairing generated output; prefer
|
||||
regenerating it with build_runner.
|
||||
|
||||
## Repository Rules
|
||||
|
||||
- Preserve unrelated work in a dirty tree. This repository currently may have
|
||||
user edits in dependency files.
|
||||
- Keep changes scoped to the requested behavior.
|
||||
- Always keep `README.md` and files under `docs/` up-to-date with any
|
||||
architecture, setup, API, sync, build, or operational change.
|
||||
- Keep documentation links current when files move.
|
||||
@@ -1,16 +1,101 @@
|
||||
# moonwell_launcher
|
||||
# MoonWell Launcher
|
||||
|
||||
A new Flutter project.
|
||||
MoonWell Launcher is the desktop launcher for MoonWell, a private World of
|
||||
Warcraft: Wrath of the Lich King server focused on a player-friendly experience
|
||||
without excessive grind and with stronger social play.
|
||||
|
||||
## Getting Started
|
||||
The launcher is responsible for:
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
- logging in to a player's MoonWell account
|
||||
- downloading and synchronizing client files
|
||||
- displaying launcher news
|
||||
- launching the game client
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
## Stack
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
- Flutter desktop
|
||||
- Dart
|
||||
- `flutter_bloc` for launcher UI state
|
||||
- `get_it` and `injectable` for dependency injection
|
||||
- `dio` for Web API and file download requests
|
||||
- `flutter_secure_storage` for launcher sessions
|
||||
- `shared_preferences` for non-sensitive launcher preferences
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
## Prerequisites
|
||||
|
||||
Use the latest stable Flutter SDK available from the official Flutter release
|
||||
channel. This project does not support FVM-pinned SDK workflows.
|
||||
|
||||
```powershell
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
If `flutter` is unavailable, restart the shell after installing Flutter or
|
||||
fix PATH so `flutter --version` works.
|
||||
|
||||
## Configuration
|
||||
|
||||
Set the launcher Web API base URL at compile time:
|
||||
|
||||
```powershell
|
||||
--dart-define=MOONWELL_API_BASE_URL=https://host
|
||||
```
|
||||
|
||||
Example run command:
|
||||
|
||||
```powershell
|
||||
flutter run -d windows --dart-define=MOONWELL_API_BASE_URL=https://host
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Regenerate dependency injection after changing injectable services:
|
||||
|
||||
```powershell
|
||||
dart run build_runner build --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
Run static analysis:
|
||||
|
||||
```powershell
|
||||
flutter analyze
|
||||
```
|
||||
|
||||
See `docs/linting.md` for the full format, analyze, test, and Lefthook
|
||||
pre-commit workflow.
|
||||
|
||||
Build the Windows launcher:
|
||||
|
||||
```powershell
|
||||
flutter build windows --dart-define=MOONWELL_API_BASE_URL=https://host
|
||||
```
|
||||
|
||||
The Inno Setup installer script lives at `installer/moonwell_launcher.iss`.
|
||||
|
||||
## Architecture
|
||||
|
||||
The UI lives in `lib/app`, with shared UI widgets under `lib/app/widgets`.
|
||||
Launcher domain models, sync use cases, API clients, installation services, and
|
||||
preferences live under `lib/features`.
|
||||
|
||||
The launcher sync flow is manifest-based:
|
||||
|
||||
1. authenticate against the MoonWell Web API
|
||||
2. fetch the client manifest
|
||||
3. scan the selected installation directory
|
||||
4. compare local files to the manifest
|
||||
5. remove stale files outside ignored directories
|
||||
6. download missing or changed files to temporary part files
|
||||
7. verify size and SHA-256 before replacing client files
|
||||
8. launch `Wow.exe` from the selected client directory
|
||||
|
||||
See `docs/launcher_web_api_spec.md` for the API and sync contract.
|
||||
|
||||
## Documentation
|
||||
|
||||
- `AGENTS.md`: architecture, commands, and repository rules for contributors
|
||||
- `TODO.md`: remaining verification and maintenance tasks
|
||||
- `docs/linting.md`: format, analyze, test, and Lefthook workflow
|
||||
- `docs/project_review.md`: project review with strengths, weaknesses, and
|
||||
upgrade feasibility notes
|
||||
- `docs/launcher_web_api_spec.md`: current launcher Web API and sync behavior
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# TODO
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] Run a Windows build smoke test:
|
||||
`flutter build windows --dart-define=MOONWELL_API_BASE_URL=https://host`.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Linting And Test Flow
|
||||
|
||||
MoonWell Launcher supports the latest stable Flutter SDK only. Do not use FVM
|
||||
for this project.
|
||||
|
||||
## Required Checks
|
||||
|
||||
Run these commands before committing:
|
||||
|
||||
```powershell
|
||||
dart format --set-exit-if-changed .
|
||||
flutter analyze
|
||||
flutter test
|
||||
```
|
||||
|
||||
Use `dart format .` to apply formatting fixes when the format check fails.
|
||||
|
||||
## Lefthook
|
||||
|
||||
This repository includes `lefthook.yml` with a `pre-commit` hook that runs the
|
||||
same format, analyze, and test flow.
|
||||
|
||||
Install Lefthook once on your machine, then install the Git hook from the repo
|
||||
root:
|
||||
|
||||
```powershell
|
||||
lefthook install
|
||||
```
|
||||
|
||||
Run the pre-commit flow manually with:
|
||||
|
||||
```powershell
|
||||
lefthook run pre-commit
|
||||
```
|
||||
|
||||
If `lefthook` is not available, install it with your preferred package manager
|
||||
from the official Lefthook distribution, then rerun `lefthook install`.
|
||||
|
||||
## Windows Troubleshooting
|
||||
|
||||
If Flutter reports that building with plugins requires symlink support, enable
|
||||
Windows Developer Mode and rerun the command.
|
||||
@@ -0,0 +1,99 @@
|
||||
# MoonWell Launcher Project Review
|
||||
|
||||
Reviewed on 2026-06-18.
|
||||
|
||||
## Overview
|
||||
|
||||
MoonWell Launcher is a Flutter desktop application for a private World of
|
||||
Warcraft: Wrath of the Lich King server. Its core responsibilities are account
|
||||
login, client installation/synchronization, news display, and launching
|
||||
`Wow.exe`.
|
||||
|
||||
The project is already more than a Flutter template: it has a clear launcher
|
||||
API contract, domain models for manifests and sessions, a sync use case,
|
||||
installation scanning, logging, dependency injection, and tests around the
|
||||
most important file verification behavior.
|
||||
|
||||
## Strong Points
|
||||
|
||||
- The project uses a readable layered structure: UI under `lib/app`, feature
|
||||
code under `lib/features`, and shared primitives under `lib/core`.
|
||||
- Client sync is manifest driven and verifies downloaded files by size and
|
||||
SHA-256 before replacing local files.
|
||||
- Local path handling normalizes manifest paths and rejects absolute paths or
|
||||
traversal attempts before resolving files under the selected installation
|
||||
directory.
|
||||
- Player-generated or volatile client directories are excluded from
|
||||
verification: `Cache`, `Errors`, `Logs`, `Screenshots`, `WTF`, and launcher
|
||||
metadata.
|
||||
- Hash caching is implemented as an optimization while keeping the server
|
||||
manifest as the source of truth.
|
||||
- Sync diagnostics are written to `.moonwell_launcher/launcher.log`, and failed
|
||||
downloads can be preserved for inspection.
|
||||
- The Web API integration is documented in `docs/launcher_web_api_spec.md`,
|
||||
including login, manifest, presigned download, news, pause/resume, and launch
|
||||
behavior.
|
||||
- Tests cover deterministic build hash behavior, ignored directories, hash
|
||||
cache behavior, path traversal rejection, sync success, stale file removal,
|
||||
download verification failure, session restore, preferences, and news loading.
|
||||
|
||||
## Weak Points And Risks
|
||||
|
||||
- `README.md` was still the generated Flutter starter text before this review,
|
||||
so new contributors had no project-specific setup or operation guide.
|
||||
- There was no `AGENTS.md`, so future automated or human contributors had to
|
||||
infer architecture, commands, and safety rules from source code.
|
||||
- At initial review, `lib/main.dart` contained a large inactive demo
|
||||
`LauncherHome` implementation. This has since been removed from the active
|
||||
entrypoint.
|
||||
- At initial review, several Russian UI strings appeared mojibake-encoded in
|
||||
the active login and home flow. These active strings have since been
|
||||
normalized to UTF-8.
|
||||
- At initial review, custom title-bar widgets were duplicated between login and
|
||||
home screens. They have since been extracted into a shared widget.
|
||||
- Session tokens were originally persisted through `shared_preferences`. They
|
||||
now use `flutter_secure_storage`, while `shared_preferences` remains limited
|
||||
to non-sensitive launcher preferences.
|
||||
- The dependency injection output file `lib/service_container.config.dart` is
|
||||
committed, but the regeneration command was not previously documented.
|
||||
- UI/widget/integration coverage is still light compared with the risk in login,
|
||||
bootstrap, directory selection, news failure handling, sync controls, and
|
||||
launch behavior.
|
||||
- There is no visible CI configuration in the repository.
|
||||
- The current working tree already had modified `pubspec.yaml` and
|
||||
`pubspec.lock` before this documentation pass. Those changes were not part of
|
||||
this review and should be preserved.
|
||||
|
||||
## Flutter 3.44.2 Upgrade Feasibility
|
||||
|
||||
Assumption: `3.44.2` means Flutter SDK `3.44.2`.
|
||||
|
||||
Current local indicators:
|
||||
|
||||
- `.fvmrc` is absent.
|
||||
- `pubspec.yaml` declares Dart SDK `^3.9.0`.
|
||||
- `pubspec.lock` declares Flutter SDK `>=3.38.0`.
|
||||
- The project policy is latest stable Flutter SDK only; FVM-pinned workflows are
|
||||
not supported.
|
||||
- The local shell now finds Flutter `3.44.2` on the stable channel.
|
||||
|
||||
Flutter `3.44.2` has been validated for dependency resolution, formatting,
|
||||
analysis, and tests. A production-like Windows build smoke test remains open.
|
||||
|
||||
Recommended upgrade path:
|
||||
|
||||
1. Keep the latest stable Flutter SDK on PATH.
|
||||
2. Run `flutter pub get`.
|
||||
3. Regenerate dependency injection only if dependency resolution or generator
|
||||
output requires it:
|
||||
`dart run build_runner build --delete-conflicting-outputs`.
|
||||
4. Run `flutter analyze`.
|
||||
5. Run a Windows build smoke test:
|
||||
`flutter build windows --dart-define=MOONWELL_API_BASE_URL=https://host`.
|
||||
|
||||
## Recommended Refactoring Direction
|
||||
|
||||
- Keep secure session persistence covered by tests when login or session
|
||||
restore behavior changes.
|
||||
- Add CI or a documented local verification workflow that runs analysis and a
|
||||
Windows build smoke test with the latest stable Flutter SDK.
|
||||
@@ -0,0 +1,8 @@
|
||||
pre-commit:
|
||||
commands:
|
||||
format:
|
||||
run: dart format --set-exit-if-changed .
|
||||
analyze:
|
||||
run: flutter analyze
|
||||
test:
|
||||
run: flutter test
|
||||
@@ -165,7 +165,7 @@ class HomeScreenBloc extends Bloc<HomeScreenEvent, HomeScreenState> {
|
||||
return;
|
||||
}
|
||||
|
||||
final directory = await FilePicker.platform.getDirectoryPath(
|
||||
final directory = await FilePicker.getDirectoryPath(
|
||||
dialogTitle: 'Выберите папку установки Moonwell',
|
||||
);
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:moonwell_launcher/app/home_screen/bloc/home_screen_bloc.dart';
|
||||
import 'package:moonwell_launcher/app/home_screen/home_screen_content.dart';
|
||||
import 'package:moonwell_launcher/app/login_screen/login_screen.dart';
|
||||
import 'package:moonwell_launcher/app/theme/mw_theme.dart';
|
||||
import 'package:moonwell_launcher/app/widgets/window_title_bar.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/application/client_sync_use_case.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/data/game_installation_service.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/data/launcher_api_client.dart';
|
||||
@@ -13,7 +12,6 @@ import 'package:moonwell_launcher/features/launcher/domain/entities/client_manif
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_session.dart';
|
||||
import 'package:moonwell_launcher/features/preferences/domain/repositories/preferences_repository.dart';
|
||||
import 'package:moonwell_launcher/service_container.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class HomeScreen extends StatelessWidget {
|
||||
const HomeScreen({super.key, required this.session, required this.manifest});
|
||||
@@ -38,8 +36,8 @@ class HomeScreen extends StatelessWidget {
|
||||
listener: (context, state) async {
|
||||
await Navigator.of(context).pushReplacement(
|
||||
PageRouteBuilder(
|
||||
pageBuilder: (_, __, ___) => const LoginScreen(),
|
||||
transitionsBuilder: (_, animation, __, child) {
|
||||
pageBuilder: (_, _, _) => const LoginScreen(),
|
||||
transitionsBuilder: (_, animation, _, child) {
|
||||
return FadeTransition(opacity: animation, child: child);
|
||||
},
|
||||
transitionDuration: const Duration(milliseconds: 250),
|
||||
@@ -49,11 +47,9 @@ class HomeScreen extends StatelessWidget {
|
||||
child: Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
// Background image
|
||||
Positioned.fill(
|
||||
child: Image.asset('assets/background.png', fit: BoxFit.cover),
|
||||
),
|
||||
// Gradient overlay
|
||||
Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
@@ -70,10 +66,13 @@ class HomeScreen extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
// Main content
|
||||
const Positioned.fill(child: HomeScreenContent()),
|
||||
// Custom title bar
|
||||
const Positioned(top: 0, left: 0, right: 0, child: _TitleBar()),
|
||||
const Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: WindowTitleBar(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -81,71 +80,3 @@ class HomeScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TitleBar extends StatelessWidget {
|
||||
const _TitleBar();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
|
||||
return GestureDetector(
|
||||
onPanStart: (_) => windowManager.startDragging(),
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
_TitleBarButton(
|
||||
icon: Icons.remove,
|
||||
onTap: () => windowManager.minimize(),
|
||||
color: cs.onSurface,
|
||||
),
|
||||
_TitleBarButton(
|
||||
icon: Icons.crop_square,
|
||||
onTap: () async {
|
||||
if (await windowManager.isMaximized()) {
|
||||
await windowManager.unmaximize();
|
||||
} else {
|
||||
await windowManager.maximize();
|
||||
}
|
||||
},
|
||||
color: cs.onSurface,
|
||||
),
|
||||
_TitleBarButton(
|
||||
icon: Icons.close,
|
||||
onTap: () {
|
||||
unawaited(windowManager.destroy());
|
||||
},
|
||||
color: MWColors.error,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TitleBarButton extends StatelessWidget {
|
||||
const _TitleBarButton({
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
required this.color,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final VoidCallback onTap;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: SizedBox(
|
||||
width: 40,
|
||||
height: 36,
|
||||
child: Icon(icon, size: 16, color: color.withAlpha(180)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ class _NewsPanel extends StatelessWidget {
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.only(right: 16, bottom: 24),
|
||||
itemCount: model.newsItems.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 10),
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 10),
|
||||
itemBuilder: (context, index) {
|
||||
return _NewsCard(item: model.newsItems[index]);
|
||||
},
|
||||
@@ -239,7 +239,7 @@ class _NewsCard extends StatelessWidget {
|
||||
child: Image.network(
|
||||
item.imageUrl!,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => Container(
|
||||
errorBuilder: (_, _, _) => Container(
|
||||
color: cs.surface.withAlpha(120),
|
||||
alignment: Alignment.center,
|
||||
child: Icon(
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:moonwell_launcher/app/home_screen/home_screen.dart';
|
||||
import 'package:moonwell_launcher/app/theme/mw_theme.dart';
|
||||
import 'package:moonwell_launcher/app/widgets/window_title_bar.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/data/launcher_api_client.dart';
|
||||
import 'package:moonwell_launcher/features/preferences/domain/repositories/preferences_repository.dart';
|
||||
import 'package:moonwell_launcher/service_container.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({super.key});
|
||||
@@ -53,9 +51,9 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
|
||||
await Navigator.of(context).pushReplacement(
|
||||
PageRouteBuilder(
|
||||
pageBuilder: (_, __, ___) =>
|
||||
pageBuilder: (_, _, _) =>
|
||||
HomeScreen(session: session, manifest: manifest),
|
||||
transitionsBuilder: (_, animation, __, child) {
|
||||
transitionsBuilder: (_, animation, _, child) {
|
||||
return FadeTransition(opacity: animation, child: child);
|
||||
},
|
||||
transitionDuration: const Duration(milliseconds: 400),
|
||||
@@ -83,7 +81,6 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
// Background
|
||||
Positioned.fill(
|
||||
child: Image.asset('assets/background.png', fit: BoxFit.cover),
|
||||
),
|
||||
@@ -101,23 +98,19 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
// Title bar
|
||||
Positioned(top: 0, left: 0, right: 0, child: _TitleBar(cs: cs)),
|
||||
// Login form — centered
|
||||
const Positioned(top: 0, left: 0, right: 0, child: WindowTitleBar()),
|
||||
Center(
|
||||
child: SizedBox(
|
||||
width: 340,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Logo
|
||||
Image.asset(
|
||||
'assets/logo.png',
|
||||
height: 160,
|
||||
filterQuality: FilterQuality.high,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
// Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(28),
|
||||
decoration: BoxDecoration(
|
||||
@@ -194,70 +187,3 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TitleBar extends StatelessWidget {
|
||||
const _TitleBar({required this.cs});
|
||||
final ColorScheme cs;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onPanStart: (_) => windowManager.startDragging(),
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
_TitleBarButton(
|
||||
icon: Icons.remove,
|
||||
onTap: () => windowManager.minimize(),
|
||||
color: cs.onSurface,
|
||||
),
|
||||
_TitleBarButton(
|
||||
icon: Icons.crop_square,
|
||||
onTap: () async {
|
||||
if (await windowManager.isMaximized()) {
|
||||
await windowManager.unmaximize();
|
||||
} else {
|
||||
await windowManager.maximize();
|
||||
}
|
||||
},
|
||||
color: cs.onSurface,
|
||||
),
|
||||
_TitleBarButton(
|
||||
icon: Icons.close,
|
||||
onTap: () {
|
||||
unawaited(windowManager.destroy());
|
||||
},
|
||||
color: MWColors.error,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TitleBarButton extends StatelessWidget {
|
||||
const _TitleBarButton({
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
required this.color,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final VoidCallback onTap;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: SizedBox(
|
||||
width: 40,
|
||||
height: 36,
|
||||
child: Icon(icon, size: 16, color: color.withAlpha(180)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:moonwell_launcher/app/theme/mw_theme.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class WindowTitleBar extends StatelessWidget {
|
||||
const WindowTitleBar({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
|
||||
return GestureDetector(
|
||||
onPanStart: (_) => windowManager.startDragging(),
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
_TitleBarButton(
|
||||
icon: Icons.remove,
|
||||
onTap: () => windowManager.minimize(),
|
||||
color: cs.onSurface,
|
||||
),
|
||||
_TitleBarButton(
|
||||
icon: Icons.crop_square,
|
||||
onTap: () {
|
||||
unawaited(_toggleMaximized());
|
||||
},
|
||||
color: cs.onSurface,
|
||||
),
|
||||
_TitleBarButton(
|
||||
icon: Icons.close,
|
||||
onTap: () {
|
||||
unawaited(windowManager.destroy());
|
||||
},
|
||||
color: MWColors.error,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _toggleMaximized() async {
|
||||
if (await windowManager.isMaximized()) {
|
||||
await windowManager.unmaximize();
|
||||
} else {
|
||||
await windowManager.maximize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _TitleBarButton extends StatelessWidget {
|
||||
const _TitleBarButton({
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
required this.color,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final VoidCallback onTap;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: SizedBox(
|
||||
width: 40,
|
||||
height: 36,
|
||||
child: Icon(icon, size: 16, color: color.withAlpha(180)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:moonwell_launcher/config.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
@lazySingleton
|
||||
class LauncherLogService {
|
||||
static Future<void> _pendingWrite = Future<void>.value();
|
||||
|
||||
|
||||
+29
-15
@@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_session.dart';
|
||||
import 'package:moonwell_launcher/features/preferences/domain/repositories/preferences_repository.dart';
|
||||
@@ -9,12 +10,15 @@ const _outputDirKey = 'output_dir';
|
||||
const _launcherSessionKey = 'launcher_session';
|
||||
|
||||
@LazySingleton(as: PreferencesRepository, env: ['flutter'])
|
||||
class SharedPrefsPreferencesRepository implements PreferencesRepository {
|
||||
class LocalPreferencesRepository implements PreferencesRepository {
|
||||
final SharedPreferences _preferences;
|
||||
final FlutterSecureStorage _secureStorage;
|
||||
|
||||
const SharedPrefsPreferencesRepository({
|
||||
const LocalPreferencesRepository({
|
||||
required SharedPreferences preferences,
|
||||
}) : _preferences = preferences;
|
||||
required FlutterSecureStorage secureStorage,
|
||||
}) : _preferences = preferences,
|
||||
_secureStorage = secureStorage;
|
||||
|
||||
@override
|
||||
Future<Uri?> getOutputDir() {
|
||||
@@ -33,35 +37,45 @@ class SharedPrefsPreferencesRepository implements PreferencesRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LauncherSession?> getLauncherSession() {
|
||||
final rawSession = _preferences.getString(_launcherSessionKey);
|
||||
Future<LauncherSession?> getLauncherSession() async {
|
||||
await _maybeClearLegacyLauncherSession();
|
||||
|
||||
final rawSession = await _secureStorage.read(key: _launcherSessionKey);
|
||||
if (rawSession == null || rawSession.isEmpty) {
|
||||
return Future.value(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final decoded = jsonDecode(rawSession);
|
||||
if (decoded is! Map) {
|
||||
return Future.value(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
final json = decoded.map((key, value) => MapEntry(key.toString(), value));
|
||||
return Future.value(LauncherSession.fromJson(json));
|
||||
return LauncherSession.fromJson(json);
|
||||
} catch (_) {
|
||||
return Future.value(null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setLauncherSession(LauncherSession session) {
|
||||
return _preferences.setString(
|
||||
_launcherSessionKey,
|
||||
jsonEncode(session.toJson()),
|
||||
Future<void> setLauncherSession(LauncherSession session) async {
|
||||
await _secureStorage.write(
|
||||
key: _launcherSessionKey,
|
||||
value: jsonEncode(session.toJson()),
|
||||
);
|
||||
await _maybeClearLegacyLauncherSession();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clearLauncherSession() {
|
||||
return _preferences.remove(_launcherSessionKey);
|
||||
Future<void> clearLauncherSession() async {
|
||||
await _secureStorage.delete(key: _launcherSessionKey);
|
||||
await _maybeClearLegacyLauncherSession();
|
||||
}
|
||||
|
||||
Future<void> _maybeClearLegacyLauncherSession() async {
|
||||
if (_preferences.containsKey(_launcherSessionKey)) {
|
||||
await _preferences.remove(_launcherSessionKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-440
@@ -1,11 +1,8 @@
|
||||
// main.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, TargetPlatform;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:moonwell_launcher/app/mw_app.dart';
|
||||
import 'package:moonwell_launcher/app/theme/mw_theme.dart';
|
||||
import 'package:moonwell_launcher/service_container.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
@@ -32,438 +29,3 @@ Future<void> main() async {
|
||||
|
||||
runApp(const MoonWellApp());
|
||||
}
|
||||
|
||||
bool get _isDesktop => const {
|
||||
TargetPlatform.macOS,
|
||||
TargetPlatform.linux,
|
||||
TargetPlatform.windows,
|
||||
}.contains(defaultTargetPlatform);
|
||||
|
||||
class LauncherHome extends StatefulWidget {
|
||||
const LauncherHome({super.key});
|
||||
|
||||
@override
|
||||
State<LauncherHome> createState() => _LauncherHomeState();
|
||||
}
|
||||
|
||||
class _LauncherHomeState extends State<LauncherHome> {
|
||||
double _progress = 0.0; // 0..1
|
||||
bool _isDownloading = false;
|
||||
double _kbps = 0; // fake speed
|
||||
Duration _eta = Duration.zero;
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _startDownload() {
|
||||
if (_isDownloading || _progress >= 1.0) return;
|
||||
setState(() => _isDownloading = true);
|
||||
|
||||
_timer = Timer.periodic(const Duration(milliseconds: 160), (t) {
|
||||
// Fake speed & progress for demo
|
||||
final chunk = 0.002 + math.Random().nextDouble() * 0.004;
|
||||
_kbps = 800 + math.Random().nextDouble() * 2200; // 0.8–3.0 MB/s
|
||||
_progress = (_progress + chunk).clamp(0.0, 1.0);
|
||||
final remaining = (1 - _progress);
|
||||
// naive ETA based on current "speed"
|
||||
_eta = Duration(seconds: math.max(1, (remaining * 120).round()));
|
||||
|
||||
if (_progress >= 1.0) {
|
||||
_isDownloading = false;
|
||||
t.cancel();
|
||||
}
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
void _pause() {
|
||||
_timer?.cancel();
|
||||
setState(() => _isDownloading = false);
|
||||
}
|
||||
|
||||
void _reset() {
|
||||
_timer?.cancel();
|
||||
setState(() {
|
||||
_progress = 0;
|
||||
_isDownloading = false;
|
||||
_kbps = 0;
|
||||
_eta = Duration.zero;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final deco = Theme.of(context).extension<MoonWellDecorations>()!;
|
||||
final title = ShaderMask(
|
||||
shaderCallback: (rect) =>
|
||||
(deco.goldBevel as LinearGradient).createShader(rect),
|
||||
child: Text(
|
||||
'MOONWELL',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.displaySmall?.copyWith(shadows: [deco.textGlow]),
|
||||
),
|
||||
);
|
||||
|
||||
if (!_isDesktop) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Text(
|
||||
'Desktop only. Please run on Windows, macOS, or Linux.',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: const BoxDecoration(
|
||||
// subtle vignette for “night” vibe
|
||||
gradient: RadialGradient(
|
||||
center: Alignment(0, -0.5),
|
||||
radius: 1.2,
|
||||
colors: [Color(0xFF0F1522), Color(0xFF0B101A)],
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 1100, maxHeight: 760),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Row(
|
||||
children: [
|
||||
// LEFT: Game title + download controls
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: _LeftPane(
|
||||
title: title,
|
||||
progress: _progress,
|
||||
isDownloading: _isDownloading,
|
||||
kbps: _kbps,
|
||||
eta: _eta,
|
||||
onStart: _startDownload,
|
||||
onPause: _pause,
|
||||
onReset: _reset,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
// RIGHT: Patch notes
|
||||
Expanded(flex: 4, child: _PatchNotesPane(color: cs.surface)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LeftPane extends StatelessWidget {
|
||||
const _LeftPane({
|
||||
required this.title,
|
||||
required this.progress,
|
||||
required this.isDownloading,
|
||||
required this.kbps,
|
||||
required this.eta,
|
||||
required this.onStart,
|
||||
required this.onPause,
|
||||
required this.onReset,
|
||||
});
|
||||
|
||||
final Widget title;
|
||||
final double progress;
|
||||
final bool isDownloading;
|
||||
final double kbps;
|
||||
final Duration eta;
|
||||
final VoidCallback onStart;
|
||||
final VoidCallback onPause;
|
||||
final VoidCallback onReset;
|
||||
|
||||
String get _speedLabel {
|
||||
if (kbps <= 0) return '—';
|
||||
if (kbps >= 1024) {
|
||||
return '${(kbps / 1024).toStringAsFixed(2)} MB/s';
|
||||
}
|
||||
return '${kbps.toStringAsFixed(0)} kB/s';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final deco = Theme.of(context).extension<MoonWellDecorations>()!;
|
||||
final percent = (progress * 100).clamp(0, 100).toStringAsFixed(1);
|
||||
|
||||
final canPlay = progress >= 1.0 && !isDownloading;
|
||||
|
||||
return Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
title,
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Classic MMO Launcher',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: cs.onSurface.withValues(alpha: 0.85),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
|
||||
// Progress bar
|
||||
_GildedProgressBar(value: progress),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'$percent%',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const Spacer(),
|
||||
Icon(Icons.speed, size: 18, color: cs.onSurfaceVariant),
|
||||
const SizedBox(width: 6),
|
||||
Text(_speedLabel, style: TextStyle(color: cs.onSurfaceVariant)),
|
||||
const SizedBox(width: 16),
|
||||
Icon(
|
||||
Icons.timer_outlined,
|
||||
size: 18,
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
eta == Duration.zero
|
||||
? '—'
|
||||
: '${eta.inMinutes}:${(eta.inSeconds % 60).toString().padLeft(2, '0')}',
|
||||
style: TextStyle(color: cs.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Controls row
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
icon: Icon(
|
||||
canPlay ? Icons.play_arrow_rounded : Icons.download,
|
||||
),
|
||||
label: Text(
|
||||
canPlay
|
||||
? 'Play'
|
||||
: (isDownloading
|
||||
? 'Downloading…'
|
||||
: (progress == 0 ? 'Install' : 'Resume')),
|
||||
),
|
||||
onPressed: canPlay
|
||||
? () {}
|
||||
: (isDownloading ? null : onStart),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
OutlinedButton.icon(
|
||||
icon: Icon(
|
||||
isDownloading ? Icons.pause_rounded : Icons.replay,
|
||||
),
|
||||
label: Text(isDownloading ? 'Pause' : 'Reset'),
|
||||
onPressed: isDownloading
|
||||
? onPause
|
||||
: (progress > 0 ? onReset : null),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
// Disk path / build info (placeholders)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: cs.surfaceContainerHighest.withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: cs.outline),
|
||||
boxShadow: (deco.cardGlow),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.folder_open),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Install path: C:/Games/MoonWell',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Icon(Icons.info_outline, size: 18),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'Build: 1.2.0 (live)',
|
||||
style: TextStyle(color: cs.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GildedProgressBar extends StatelessWidget {
|
||||
const _GildedProgressBar({required this.value});
|
||||
final double value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final deco = Theme.of(context).extension<MoonWellDecorations>()!;
|
||||
return Container(
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: cs.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: cs.outline),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Stack(
|
||||
children: [
|
||||
// Fill with gold bevel
|
||||
FractionallySizedBox(
|
||||
widthFactor: value.clamp(0.0, 1.0),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: (deco.goldBevel as LinearGradient),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Subtle top highlight
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
height: 6,
|
||||
color: Colors.white.withValues(alpha: 0.08),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PatchNotesPane extends StatelessWidget {
|
||||
const _PatchNotesPane({required this.color});
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
|
||||
// Example static notes — wire your real source here.
|
||||
final notes = [
|
||||
(
|
||||
'1.2.0 — “Moonlit March”',
|
||||
[
|
||||
'New dungeon: *Hall of Tides*.',
|
||||
'Launcher supports delta updates with blockmaps.',
|
||||
'Improved shader warmup (faster first launch).',
|
||||
'Fixed login race condition on slow networks.',
|
||||
],
|
||||
),
|
||||
(
|
||||
'1.1.5',
|
||||
[
|
||||
'Balance pass on Ranger & Warlock.',
|
||||
'Memory usage reduced ~12% in large towns.',
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
return Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
),
|
||||
border: Border(bottom: BorderSide(color: cs.outline)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.article_outlined),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
'Patch Notes',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton.icon(
|
||||
onPressed: () {}, // hook to "Open full changelog"
|
||||
icon: const Icon(Icons.open_in_new),
|
||||
label: const Text('Full changelog'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Body
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 20),
|
||||
itemCount: notes.length,
|
||||
separatorBuilder: (_, __) => const Divider(),
|
||||
itemBuilder: (context, i) {
|
||||
final (title, items) = notes[i];
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...items.map(
|
||||
(s) => Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('• '),
|
||||
Expanded(
|
||||
child: Text(
|
||||
s,
|
||||
style: TextStyle(
|
||||
color: cs.onSurface.withValues(alpha: 0.9),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// dart format width=80
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// dart format width=80
|
||||
|
||||
// **************************************************************************
|
||||
// InjectableConfigGenerator
|
||||
@@ -9,6 +9,7 @@
|
||||
// coverage:ignore-file
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart' as _i558;
|
||||
import 'package:get_it/get_it.dart' as _i174;
|
||||
import 'package:injectable/injectable.dart' as _i526;
|
||||
import 'package:shared_preferences/shared_preferences.dart' as _i460;
|
||||
@@ -16,10 +17,12 @@ import 'package:shared_preferences/shared_preferences.dart' as _i460;
|
||||
import 'features/launcher/application/client_sync_use_case.dart' as _i757;
|
||||
import 'features/launcher/data/game_installation_service.dart' as _i315;
|
||||
import 'features/launcher/data/launcher_api_client.dart' as _i703;
|
||||
import 'features/preferences/data/repositories/shared_prefs_preferences_repository.dart'
|
||||
as _i662;
|
||||
import 'features/launcher/data/launcher_log_service.dart' as _i43;
|
||||
import 'features/preferences/data/repositories/local_preferences_repository.dart'
|
||||
as _i658;
|
||||
import 'features/preferences/domain/repositories/preferences_repository.dart'
|
||||
as _i44;
|
||||
import 'third_party/flutter_secure_storage.dart' as _i782;
|
||||
import 'third_party/shared_preferences.dart' as _i1006;
|
||||
|
||||
const String _flutter = 'flutter';
|
||||
@@ -32,6 +35,7 @@ extension GetItInjectableX on _i174.GetIt {
|
||||
}) async {
|
||||
final gh = _i526.GetItHelper(this, environment, environmentFilter);
|
||||
final sharedPreferencesModule = _$SharedPreferencesModule();
|
||||
final flutterSecureStorageModule = _$FlutterSecureStorageModule();
|
||||
await gh.factoryAsync<_i460.SharedPreferences>(
|
||||
() => sharedPreferencesModule.prefs,
|
||||
preResolve: true,
|
||||
@@ -39,21 +43,33 @@ extension GetItInjectableX on _i174.GetIt {
|
||||
gh.lazySingleton<_i315.GameInstallationService>(
|
||||
() => _i315.GameInstallationService(),
|
||||
);
|
||||
gh.lazySingleton<_i703.LauncherApiClient>(() => _i703.LauncherApiClient());
|
||||
gh.lazySingleton<_i43.LauncherLogService>(() => _i43.LauncherLogService());
|
||||
gh.lazySingleton<_i558.FlutterSecureStorage>(
|
||||
() => flutterSecureStorageModule.storage,
|
||||
);
|
||||
gh.lazySingleton<_i703.LauncherApiClient>(
|
||||
() => _i703.LauncherApiClient(
|
||||
launcherLogService: gh<_i43.LauncherLogService>(),
|
||||
),
|
||||
);
|
||||
gh.lazySingleton<_i44.PreferencesRepository>(
|
||||
() => _i658.LocalPreferencesRepository(
|
||||
preferences: gh<_i460.SharedPreferences>(),
|
||||
secureStorage: gh<_i558.FlutterSecureStorage>(),
|
||||
),
|
||||
registerFor: {_flutter},
|
||||
);
|
||||
gh.lazySingleton<_i757.ClientSyncUseCase>(
|
||||
() => _i757.ClientSyncUseCase(
|
||||
launcherApiClient: gh<_i703.LauncherApiClient>(),
|
||||
installationService: gh<_i315.GameInstallationService>(),
|
||||
launcherLogService: gh<_i43.LauncherLogService>(),
|
||||
),
|
||||
);
|
||||
gh.lazySingleton<_i44.PreferencesRepository>(
|
||||
() => _i662.SharedPrefsPreferencesRepository(
|
||||
preferences: gh<_i460.SharedPreferences>(),
|
||||
),
|
||||
registerFor: {_flutter},
|
||||
);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class _$SharedPreferencesModule extends _i1006.SharedPreferencesModule {}
|
||||
|
||||
class _$FlutterSecureStorageModule extends _i782.FlutterSecureStorageModule {}
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
@module
|
||||
abstract class FlutterSecureStorageModule {
|
||||
@lazySingleton
|
||||
FlutterSecureStorage get storage => const FlutterSecureStorage();
|
||||
}
|
||||
+252
-124
@@ -5,18 +5,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
|
||||
sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "85.0.0"
|
||||
version: "93.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d"
|
||||
sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.7.1"
|
||||
version: "10.0.1"
|
||||
ansicolor:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ansicolor
|
||||
sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -29,18 +37,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
version: "2.13.1"
|
||||
bloc:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bloc
|
||||
sha256: "52c10575f4445c61dd9e0cafcc6356fdd827c4c64dd7945ef3c4105f6b6ac189"
|
||||
sha256: e03b235924e4f509c27b5d6b2f949200e0a91149a9818b4f65eeb56662b75413
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.0.0"
|
||||
version: "9.2.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -53,50 +61,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "6439a9c71a4e6eca8d9490c1b380a25b02675aa688137dfbe66d2062884a23ac"
|
||||
sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "4.0.6"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
|
||||
sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.3.0"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
|
||||
sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.4"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: "2b21a125d66a86b9511cc3fb6c668c42e9a1185083922bf60e46d483a81a9712"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "4.1.1"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: fd3c09f4bbff7fa6e8d8ef688a0b2e8a6384e6483a25af0dac75fef362bcfe6f
|
||||
sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: ab27e46c8aa233e610cf6084ee6d8a22c6f873a0a9929241d8855b7a72978ae7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.3.0"
|
||||
version: "2.15.0"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -109,18 +101,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: ba95c961bafcd8686d1cf63be864eb59447e795e124d98d6a27d91fcd13602fb
|
||||
sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.11.1"
|
||||
version: "8.12.6"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.4.1"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -137,14 +129,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
code_assets:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_assets
|
||||
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
|
||||
sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.10.1"
|
||||
version: "4.11.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -165,58 +165,58 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
||||
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.4+2"
|
||||
version: "0.3.5+2"
|
||||
crypto:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
version: "3.0.7"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
version: "1.0.9"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb"
|
||||
sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
version: "3.1.7"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dbus
|
||||
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
|
||||
sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
version: "0.7.14"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
||||
sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.9.0"
|
||||
version: "5.9.2"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dio_web_adapter
|
||||
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
|
||||
sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.2"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -229,10 +229,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.2.0"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -245,10 +245,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: e7e16c9d15c36330b94ca0e2ad8cb61f93cd5282d0158c09805aed13b5452f22
|
||||
sha256: f13a03000d942e476bc1ff0a736d2e9de711d2f89a95cd4c1d88f861c3348387
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.3.2"
|
||||
version: "11.0.2"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -274,18 +274,66 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
version: "6.0.0"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31
|
||||
sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.30"
|
||||
version: "2.0.35"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_secure_storage
|
||||
sha256: "7686b1d6a29985dcbb808c59518226e603e3bfa7c0ddfd1a0d00e4cda77c868e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.3.1"
|
||||
flutter_secure_storage_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_darwin
|
||||
sha256: "82329fa5cdf343773b1b6897dea959105a29f092454259edff92f9f6637e8149"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.2"
|
||||
flutter_secure_storage_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_linux
|
||||
sha256: a5f35ddab43cf5c8215d2feb4ce1957851f28c5c37e6f04335066a0602087bf5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
flutter_secure_storage_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_platform_interface
|
||||
sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
flutter_secure_storage_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_web
|
||||
sha256: "073a62b3aeb866ab4ce795f960413948e51e5a42a9b0c8333b6daf5bb3208a1c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
flutter_secure_storage_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_windows
|
||||
sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -308,10 +356,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: get_it
|
||||
sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b
|
||||
sha256: "568d62f0e68666fb5d95519743b3c24a34c7f19d834b0658c46e26d778461f66"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.2.0"
|
||||
version: "9.2.1"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -328,6 +376,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
hooks:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hooks
|
||||
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
hotreloader:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hotreloader
|
||||
sha256: "66871df468fc24eee81f1a0a7cb98acc104716f9b7376d355437b48d633c4ebf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.4.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -348,18 +412,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: injectable
|
||||
sha256: "1b86fab6a98c11a97e5c718afb00e628d47d183c2a2256392e995a4c561141c1"
|
||||
sha256: "1da6399509e44ddb0d8a4a35291d7122c4efb5c625a9d733bf5e48d4a72e379e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
version: "3.0.0"
|
||||
injectable_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: injectable_generator
|
||||
sha256: "09c55dba52b53d17411b90134a6751270b8930abd2529e2637d700fc99b0d5b5"
|
||||
sha256: "433ad4d94837ff187803b2bf04c6707db6fad8d245f1d693e2e34053bc07ec28"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.8.1"
|
||||
version: "3.0.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -368,22 +432,38 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
jni:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jni
|
||||
sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
jni_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jni_flutter
|
||||
sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
version: "4.12.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.1"
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -400,14 +480,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lean_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lean_builder
|
||||
sha256: c16e95ddf7b2d49dd551357b7212fe2ce9f13ec7ad1b1e660c157184031e96c0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.10"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
version: "6.1.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -420,26 +508,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
version: "1.18.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -456,6 +544,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.4.1"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -472,6 +568,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.6"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -484,10 +604,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -500,10 +620,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
|
||||
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
version: "7.0.2"
|
||||
pixelarticons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -532,10 +652,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
version: "1.5.2"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -568,70 +688,78 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
record_use:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_use
|
||||
sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
screen_retriever:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: screen_retriever
|
||||
sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c"
|
||||
sha256: "42cc3b402a0f67d2455a0d067553d0f13453f6a008d98eababf8b63958d506bd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.2.1"
|
||||
screen_retriever_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: screen_retriever_linux
|
||||
sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18
|
||||
sha256: "2a476f1a5538065bc5badf376cfdc83d6ecf07d77eb2391b9c2bff5a76970048"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.2.1"
|
||||
screen_retriever_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: screen_retriever_macos
|
||||
sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149"
|
||||
sha256: b5abb900fcb86614ff10b738b34e37b9e1d03b0447280668e2bc8a98bdc7bd59
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.2.1"
|
||||
screen_retriever_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: screen_retriever_platform_interface
|
||||
sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0
|
||||
sha256: "3af22d926bedf20c2caa308eea376776451a3af125919ce072e56525fded8901"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.2.1"
|
||||
screen_retriever_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: screen_retriever_windows
|
||||
sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13"
|
||||
sha256: c44b38a4c4bab34af259180a70a4eee1e29384e7b82e627c9faa68afcdab2e73
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.2.1"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
|
||||
sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.3"
|
||||
version: "2.5.5"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74
|
||||
sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.12"
|
||||
version: "2.4.23"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
|
||||
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4"
|
||||
version: "2.5.6"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -644,10 +772,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.2"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -689,18 +817,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3"
|
||||
sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "4.2.3"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
version: "1.10.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -745,18 +873,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.7"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
version: "0.7.11"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -777,18 +897,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.2"
|
||||
version: "15.2.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c"
|
||||
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
version: "1.2.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -817,10 +937,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
|
||||
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.14.0"
|
||||
version: "5.15.0"
|
||||
window_manager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -845,6 +965,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.6.1"
|
||||
xxh3:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xxh3
|
||||
sha256: "399a0438f5d426785723c99da6b16e136f4953fb1e9db0bf270bd41dd4619916"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -854,5 +982,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.29.0"
|
||||
dart: ">=3.10.3 <4.0.0"
|
||||
flutter: ">=3.38.4"
|
||||
|
||||
+6
-5
@@ -36,12 +36,13 @@ dependencies:
|
||||
cupertino_icons: ^1.0.8
|
||||
dio: ^5.9.0
|
||||
path: ^1.9.1
|
||||
injectable: ^2.5.1
|
||||
injectable: ^3.0.0
|
||||
crypto: ^3.0.6
|
||||
get_it: ^8.2.0
|
||||
get_it: ^9.2.1
|
||||
flutter_bloc: ^9.1.1
|
||||
file_picker: ^10.3.2
|
||||
file_picker: ^11.0.2
|
||||
pixelarticons: ^0.4.0
|
||||
flutter_secure_storage: ^10.3.1
|
||||
shared_preferences: ^2.5.3
|
||||
window_manager: ^0.5.1
|
||||
|
||||
@@ -54,8 +55,8 @@ dev_dependencies:
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^5.0.0
|
||||
injectable_generator: ^2.8.1
|
||||
flutter_lints: ^6.0.0
|
||||
injectable_generator: ^3.0.2
|
||||
build_runner: ^2.7.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_session.dart';
|
||||
import 'package:moonwell_launcher/features/preferences/data/repositories/local_preferences_repository.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
void main() {
|
||||
group('LocalPreferencesRepository', () {
|
||||
setUp(() {
|
||||
SharedPreferences.setMockInitialValues(<String, Object>{});
|
||||
FlutterSecureStorage.setMockInitialValues(<String, String>{});
|
||||
});
|
||||
|
||||
Future<LocalPreferencesRepository> createRepository() async {
|
||||
final preferences = await SharedPreferences.getInstance();
|
||||
return LocalPreferencesRepository(
|
||||
preferences: preferences,
|
||||
secureStorage: const FlutterSecureStorage(),
|
||||
);
|
||||
}
|
||||
|
||||
test('persists output directory through shared preferences', () async {
|
||||
final repository = await createRepository();
|
||||
final uri = Uri.file(r'C:\Games\MoonWell');
|
||||
|
||||
await repository.setOutputDir(uri);
|
||||
|
||||
expect(await repository.getOutputDir(), uri);
|
||||
});
|
||||
|
||||
test('persists launcher session through secure storage', () async {
|
||||
final repository = await createRepository();
|
||||
final session = LauncherSession(
|
||||
accessToken: 'token',
|
||||
tokenType: 'Bearer',
|
||||
expiresAt: DateTime.parse('2030-01-01T00:00:00Z'),
|
||||
);
|
||||
|
||||
await repository.setLauncherSession(session);
|
||||
final restoredSession = await repository.getLauncherSession();
|
||||
|
||||
expect(restoredSession?.accessToken, 'token');
|
||||
expect(restoredSession?.tokenType, 'Bearer');
|
||||
expect(
|
||||
restoredSession?.expiresAt?.toUtc(),
|
||||
DateTime.parse('2030-01-01T00:00:00Z'),
|
||||
);
|
||||
});
|
||||
|
||||
test('clears persisted launcher session', () async {
|
||||
final repository = await createRepository();
|
||||
|
||||
await repository.setLauncherSession(
|
||||
const LauncherSession(
|
||||
accessToken: 'token',
|
||||
tokenType: 'Bearer',
|
||||
expiresAt: null,
|
||||
),
|
||||
);
|
||||
await repository.clearLauncherSession();
|
||||
|
||||
expect(await repository.getLauncherSession(), isNull);
|
||||
});
|
||||
|
||||
test(
|
||||
'removes legacy shared preferences session without restoring it',
|
||||
() async {
|
||||
final legacySession = LauncherSession(
|
||||
accessToken: 'legacy-token',
|
||||
tokenType: 'Bearer',
|
||||
expiresAt: DateTime.parse('2030-01-01T00:00:00Z'),
|
||||
);
|
||||
SharedPreferences.setMockInitialValues(<String, Object>{
|
||||
'launcher_session': jsonEncode(legacySession.toJson()),
|
||||
});
|
||||
final preferences = await SharedPreferences.getInstance();
|
||||
final repository = LocalPreferencesRepository(
|
||||
preferences: preferences,
|
||||
secureStorage: const FlutterSecureStorage(),
|
||||
);
|
||||
|
||||
expect(await repository.getLauncherSession(), isNull);
|
||||
expect(preferences.containsKey('launcher_session'), isFalse);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_session.dart';
|
||||
import 'package:moonwell_launcher/features/preferences/data/repositories/shared_prefs_preferences_repository.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
void main() {
|
||||
group('SharedPrefsPreferencesRepository', () {
|
||||
setUp(() {
|
||||
SharedPreferences.setMockInitialValues(<String, Object>{});
|
||||
});
|
||||
|
||||
test('persists launcher session between reads', () async {
|
||||
final preferences = await SharedPreferences.getInstance();
|
||||
final repository = SharedPrefsPreferencesRepository(
|
||||
preferences: preferences,
|
||||
);
|
||||
final session = LauncherSession(
|
||||
accessToken: 'token',
|
||||
tokenType: 'Bearer',
|
||||
expiresAt: DateTime.parse('2030-01-01T00:00:00Z'),
|
||||
);
|
||||
|
||||
await repository.setLauncherSession(session);
|
||||
final restoredSession = await repository.getLauncherSession();
|
||||
|
||||
expect(restoredSession?.accessToken, 'token');
|
||||
expect(restoredSession?.tokenType, 'Bearer');
|
||||
expect(
|
||||
restoredSession?.expiresAt?.toUtc(),
|
||||
DateTime.parse('2030-01-01T00:00:00Z'),
|
||||
);
|
||||
});
|
||||
|
||||
test('clears persisted launcher session', () async {
|
||||
final preferences = await SharedPreferences.getInstance();
|
||||
final repository = SharedPrefsPreferencesRepository(
|
||||
preferences: preferences,
|
||||
);
|
||||
|
||||
await repository.setLauncherSession(
|
||||
LauncherSession(
|
||||
accessToken: 'token',
|
||||
tokenType: 'Bearer',
|
||||
expiresAt: null,
|
||||
),
|
||||
);
|
||||
await repository.clearLauncherSession();
|
||||
|
||||
expect(await repository.getLauncherSession(), isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -6,10 +6,13 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
|
||||
WindowManagerPluginRegisterWithRegistrar(
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_secure_storage_windows
|
||||
screen_retriever_windows
|
||||
window_manager
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
jni
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
Reference in New Issue
Block a user