This commit is contained in:
Alessandro Autiero
2024-06-15 17:57:17 +02:00
parent 2bf084d120
commit e24f4e97b3
144 changed files with 442 additions and 278 deletions

View File

@@ -29,6 +29,8 @@ import 'package:reboot_launcher/src/widget/profile_tile.dart';
import 'package:reboot_launcher/src/widget/title_bar.dart';
import 'package:window_manager/window_manager.dart';
import 'info_page.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@@ -221,6 +223,7 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
super.build(context);
_settingsController.language.value;
loadTranslations(context);
// InfoPage.initInfoTiles();
return Obx(() {
return NavigationPaneTheme(
data: NavigationPaneThemeData(
@@ -394,7 +397,7 @@ class _PaneBody extends StatefulWidget {
class _PaneBodyState extends State<_PaneBody> with AutomaticKeepAliveClientMixin {
final SettingsController _settingsController = Get.find<SettingsController>();
final PageController _pageController = PageController(keepPage: true);
final PageController _pageController = PageController(keepPage: true, initialPage: pageIndex.value);
@override
bool get wantKeepAlive => true;
@@ -402,7 +405,15 @@ class _PaneBodyState extends State<_PaneBody> with AutomaticKeepAliveClientMixin
@override
void initState() {
super.initState();
pageIndex.listen((index) => _pageController.jumpToPage(index));
var lastPage = pageIndex.value;
pageIndex.listen((index) {
if(index == lastPage) {
return;
}
lastPage = index;
_pageController.jumpToPage(index);
});
}
@override
@@ -437,6 +448,10 @@ class _PaneBodyState extends State<_PaneBody> with AutomaticKeepAliveClientMixin
elements.add(TextSpan(
text: pages[pageIndex.value].name,
recognizer: pageStack.isNotEmpty ? (TapGestureRecognizer()..onTap = () {
if(inDialog) {
return;
}
for(var i = 0; i < pageStack.length; i++) {
Navigator.of(pageKey.currentContext!).pop();
final element = pageStack.removeLast();
@@ -461,6 +476,10 @@ class _PaneBodyState extends State<_PaneBody> with AutomaticKeepAliveClientMixin
elements.add(TextSpan(
text: innerPage,
recognizer: i == pageStack.length - 1 ? null : (TapGestureRecognizer()..onTap = () {
if(inDialog) {
return;
}
for(var j = 0; j < i - 1; j++) {
Navigator.of(pageKey.currentContext!).pop();
final element = pageStack.removeLast();

View File

@@ -1,12 +1,11 @@
import 'dart:async';
import 'dart:collection';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
import 'package:reboot_launcher/src/page/abstract/page.dart';
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
import 'package:reboot_launcher/src/page/pages.dart';
@@ -15,12 +14,14 @@ import 'package:path/path.dart' as path;
import 'package:reboot_launcher/src/widget/info_tile.dart';
class InfoPage extends RebootPage {
static late final List<InfoTile> _infoTiles;
static late List<InfoTile> _infoTiles;
static late List<_QuizEntry> _quizEntries;
static Object? initInfoTiles() {
try {
final directory = Directory("${assetsDirectory.path}\\info\\$currentLocale");
final map = SplayTreeMap<int, InfoTile>();
for(final entry in directory.listSync()) {
final faqDirectory = Directory("${assetsDirectory.path}\\info\\$currentLocale\\faq");
final infoTiles = SplayTreeMap<int, InfoTile>();
for(final entry in faqDirectory.listSync()) {
if(entry is File) {
final name = Uri.decodeQueryComponent(path.basename(entry.path));
final splitter = name.indexOf(".");
@@ -34,16 +35,42 @@ class InfoPage extends RebootPage {
}
final questionName = Uri.decodeQueryComponent(name.substring(splitter + 2));
map[index] = InfoTile(
infoTiles[index] = InfoTile(
title: Text(questionName),
content: Text(entry.readAsStringSync())
);
}
}
_infoTiles = map.values.toList(growable: false);
_infoTiles = infoTiles.values.toList(growable: false);
final questionsDirectory = Directory("${assetsDirectory.path}\\info\\$currentLocale\\questions");
final questions = SplayTreeMap<int, _QuizEntry>();
for(final entry in questionsDirectory.listSync()) {
if(entry is File) {
final name = Uri.decodeQueryComponent(path.basename(entry.path));
final splitter = name.indexOf(".");
if(splitter == -1) {
continue;
}
final index = int.tryParse(name.substring(0, splitter));
if(index == null) {
continue;
}
final questionName = Uri.decodeQueryComponent(name.substring(splitter + 2));
questions[index] = _QuizEntry(
question: questionName,
options: entry.readAsStringSync().split("\n")
);
}
}
_quizEntries = questions.values.toList(growable: false);
return null;
}catch(error) {
_infoTiles = [];
_quizEntries = [];
return error;
}
}
@@ -60,7 +87,7 @@ class InfoPage extends RebootPage {
String get iconAsset => "assets/images/info.png";
@override
bool hasButton(String? routeName) => false;
bool hasButton(String? pageName) => Get.find<SettingsController>().firstRun.value && pageName != null;
@override
RebootPageType get type => RebootPageType.info;
@@ -68,22 +95,14 @@ class InfoPage extends RebootPage {
class _InfoPageState extends RebootPageState<InfoPage> {
final SettingsController _settingsController = Get.find<SettingsController>();
RxInt _counter = RxInt(kDebugMode ? 0 : 180);
late bool _showButton;
late final Rxn<Widget> _quizPage;
@override
void initState() {
_showButton = _settingsController.firstRun.value;
if(_settingsController.firstRun.value) {
Timer.periodic(const Duration(seconds: 1), (timer) {
if (_counter.value <= 0) {
_settingsController.firstRun.value = false;
timer.cancel();
} else {
_counter.value = _counter.value - 1;
}
});
}
_quizPage = Rxn(_settingsController.firstRun.value ? _QuizRoute(
entries: InfoPage._quizEntries,
onSuccess: () => _quizPage.value = null
) : null);
super.initState();
}
@@ -92,28 +111,183 @@ class _InfoPageState extends RebootPageState<InfoPage> {
@override
Widget? get button {
if(!_showButton) {
return const SizedBox.shrink();
if(_quizPage.value == null) {
return null;
}
return Obx(() {
final totalSecondsLeft = _counter.value;
final minutesLeft = totalSecondsLeft ~/ 60;
final secondsLeft = totalSecondsLeft % 60;
final page = _quizPage.value;
if(page == null) {
return const SizedBox.shrink();
}
return SizedBox(
width: double.infinity,
height: 48,
child: Button(
onPressed: totalSecondsLeft <= 0 ? () {
_showButton = false;
pageIndex.value = RebootPageType.play.index;
} : null,
onPressed: () => Navigator.of(context).push(PageRouteBuilder(
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
settings: RouteSettings(
name: translations.quiz
),
pageBuilder: (context, incoming, outgoing) => page
)),
child: Text(
totalSecondsLeft <= 0 ? "I have read the instructions"
: "Read the instructions for at least ${secondsLeft == 0 ? '$minutesLeft minute${minutesLeft > 1 ? 's' : ''}' : minutesLeft == 0 ? '$secondsLeft second${secondsLeft > 1 ? 's' : ''}' : '$minutesLeft minute${minutesLeft > 1 ? 's' : ''} and $secondsLeft second${secondsLeft > 1 ? 's' : ''}'}"
translations.startQuiz
),
)
);
});
}
}
class _QuizRoute extends StatefulWidget {
final List<_QuizEntry> entries;
final void Function() onSuccess;
const _QuizRoute({
required this.entries,
required this.onSuccess
});
@override
State<_QuizRoute> createState() => _QuizRouteState();
}
class _QuizRouteState extends State<_QuizRoute> with AutomaticKeepAliveClientMixin {
final SettingsController _settingsController = Get.find<SettingsController>();
late final List<RxInt> _selectedIndexes = List.generate(widget.entries.length, (_) => RxInt(-1));
int _triesLeft = 3;
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return Column(
children: [
Expanded(
child: ListView(
children: widget.entries.indexed.expand((entry) {
final selectedIndex = _selectedIndexes[entry.$1];
return [
Text(
"${entry.$1 + 1}. ${entry.$2.question}",
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w600
),
),
const SizedBox(height: 12.0),
...entry.$2.options.indexed.map<Widget>((value) => Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: Obx(() => RadioButton(
checked: value.$1 == selectedIndex.value,
content: Text(value.$2, textAlign: TextAlign.center),
onChanged: (_) => selectedIndex.value = value.$1
)),
)),
const SizedBox(height: 12.0)
];
}).toList()
),
),
const SizedBox(
height: 8.0,
),
SizedBox(
width: double.infinity,
height: 48,
child: Obx(() {
var clickable = true;
for(final index in _selectedIndexes) {
if(index.value == -1) {
clickable = false;
break;
}
}
return Button(
onPressed: clickable ? () async {
if(_triesLeft <= 0) {
return;
}
var right = 0;
final total = widget.entries.length;
for(var i = 0; i < total; i++) {
final selectedIndex = _selectedIndexes[i].value;
final correctIndex = widget.entries[i].correctIndex;
if(selectedIndex == correctIndex) {
right++;
}
}
if(right == total) {
widget.onSuccess();
showInfoBar(
translations.quizSuccess,
severity: InfoBarSeverity.success
);
_settingsController.firstRun.value = false;
Navigator.of(context).pop();
pageIndex.value = RebootPageType.play.index;
return;
}
switch(--_triesLeft) {
case 0:
showInfoBar(
translations.quizFailed(
right,
total,
translations.quizZeroTriesLeft
),
severity: InfoBarSeverity.error
);
await Future.delayed(const Duration(seconds: 1));
exit(0);
case 1:
showInfoBar(
translations.quizFailed(
right,
total,
translations.quizOneTryLeft
),
severity: InfoBarSeverity.error
);
break;
case 2:
showInfoBar(
translations.quizFailed(
right,
total,
translations.quizTwoTriesLeft
),
severity: InfoBarSeverity.error
);
break;
}
} : null,
child: Text(translations.checkQuiz),
);
},
),
)
],
);
}
}
class _QuizEntry {
final String question;
final List<String> options;
late final int correctIndex;
_QuizEntry({required this.question, required this.options}) {
final correct = options.first;
options.shuffle();
correctIndex = options.indexOf(correct);
}
}

View File

@@ -29,7 +29,7 @@ class HostPage extends RebootPage {
const HostPage({Key? key}) : super(key: key);
@override
String get name => "Host";
String get name => translations.hostName;
@override
String get iconAsset => "assets/images/host.png";
@@ -289,51 +289,23 @@ class _HostingPageState extends RebootPageState<HostPage> {
title: Text(translations.settingsServerOptionsName),
subtitle: Text(translations.settingsServerOptionsSubtitle),
children: [
Obx(() => SettingTile(
SettingTile(
icon: Icon(
FluentIcons.window_console_20_regular
),
title: Text(translations.hostHeadlessName),
subtitle: Text(translations.hostHeadlessDescription),
contentWidth: null,
content: Row(
children: [
Text(
_hostingController.headless.value ? translations.on : translations.off
),
const SizedBox(
width: 16.0
),
ToggleSwitch(
checked: _hostingController.headless.value,
onChanged: (value) => _hostingController.headless.value = value
),
],
),
)),
Obx(() => SettingTile(
icon: Icon(
FluentIcons.desktop_edit_24_regular
),
title: Text(translations.hostVirtualDesktopName),
subtitle: Text(translations.hostVirtualDesktopDescription),
contentWidth: null,
content: Row(
children: [
Text(
_hostingController.virtualDesktop.value ? translations.on : translations.off
),
const SizedBox(
width: 16.0
),
ToggleSwitch(
checked: _hostingController.virtualDesktop.value,
onChanged: (value) => _hostingController.virtualDesktop.value = value
),
],
),
)),
Obx(() => SettingTile(
title: Text(translations.gameServerTypeName),
subtitle: Text(translations.gameServerTypeDescription),
content: Obx(() => DropDownButton(
onOpen: () => inDialog = true,
onClose: () => inDialog = false,
leading: Text(_hostingController.type.value.translatedName),
items: GameServerType.values.map((entry) => MenuFlyoutItem(
text: Text(entry.translatedName),
onPressed: () => _hostingController.type.value = entry
)).toList()
)),
),
SettingTile(
icon: Icon(
FluentIcons.arrow_reset_24_regular
),
@@ -348,13 +320,13 @@ class _HostingPageState extends RebootPageState<HostPage> {
const SizedBox(
width: 16.0
),
ToggleSwitch(
Obx(() => ToggleSwitch(
checked: _hostingController.autoRestart.value,
onChanged: (value) => _hostingController.autoRestart.value = value
),
)),
],
),
)),
),
SettingTile(
icon: Icon(
fluentUi.FluentIcons.number_field