Skip to content

Commit

Permalink
feat: manage pf2e sources (#39)
Browse files Browse the repository at this point in the history
* chore: add bestiari list to pf2e settings model

* chore: add bestiary source as a Pf2eBestiaryService dependency

* feat: add dialog to select the pf2e bestiary sources

* ci: update env variables in CI workflows

* style: format
  • Loading branch information
VytorCalixto authored Nov 12, 2024
1 parent 55eba6f commit 7a3fbf9
Show file tree
Hide file tree
Showing 13 changed files with 260 additions and 33 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ SENTRY_DSN="<URI>"
PLAUSIBLE_SERVER="<URI>"
PLAUSIBLE_DOMAIN="<DOMAIN>"
WIREDASH_PROJECT="<PROJECT>"
WIREDASH_SECRET="<SECRET>"
WIREDASH_SECRET="<SECRET>"
PF2E_URI="https://raw.githubusercontent.com/VytorCalixto/pf2e-fvtt-bestiary/refs/heads/main"
3 changes: 3 additions & 0 deletions .github/workflows/minor-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ jobs:
echo "SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env
echo "PLAUSIBLE_SERVER=${{ secrets.PLAUSIBLE_SERVER }}" >> .env
echo "PLAUSIBLE_DOMAIN=${{ secrets.PLAUSIBLE_DOMAIN }}" >> .env
echo "WIREDASH_PROJECT=${{ secrets.WIREDASH_PROJECT }}" >> .env
echo "WIREDASH_SECRET=${{ secrets.WIREDASH_SECRET }}" >> .env
echo "PF2E_URI=${{ secrets.PF2E_URI }}" >> .env
- name: Build APK
run: flutter build apk --release --dart-define-from-file=.env
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/nightly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ jobs:
echo "SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env
echo "PLAUSIBLE_SERVER=${{ secrets.PLAUSIBLE_SERVER }}" >> .env
echo "PLAUSIBLE_DOMAIN=${{ secrets.PLAUSIBLE_DOMAIN }}" >> .env
echo "WIREDASH_PROJECT=${{ secrets.WIREDASH_PROJECT }}" >> .env
echo "WIREDASH_SECRET=${{ secrets.WIREDASH_SECRET }}" >> .env
echo "PF2E_URI=${{ secrets.PF2E_URI }}" >> .env
- name: Build APK
run: flutter build apk --release --dart-define-from-file=.env
Expand Down
27 changes: 8 additions & 19 deletions lib/api/services/pf2e_bestiary_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,17 @@ import '../../features/combatant/models/combatant.dart';
import '../../features/combatant/models/pf2e_combatant_data.dart';
import 'bestiary_service.dart';

const _baseUrl =
'https://raw.githubusercontent.com/VytorCalixto/pf2e-fvtt-bestiary/refs/heads/main';
const _baseUrl = String.fromEnvironment("PF2E_URI");

class Pf2eBestiaryService extends BestiaryService {
Pf2eBestiaryService()
: super(
final Set<String> bestiarySources;

Pf2eBestiaryService({
this.bestiarySources = const {},
}) : super(
initialData: [],
baseUrl: _baseUrl,
) {
fetchData();
}

final List<String> _defaultSources = [
"book-of-the-dead",
"npc-gallery",
"pathfinder-bestiary-2",
"pathfinder-bestiary-3",
"pathfinder-dark-archive",
"pathfinder-monster-core",
"pathfinder",
"rage-of-elements",
];
);

final Map<String, String> _availableSources = {};

Expand Down Expand Up @@ -58,7 +47,7 @@ class Pf2eBestiaryService extends BestiaryService {
}

await _getAvailableSources();
for (final source in _defaultSources) {
for (final source in bestiarySources) {
logger.d('Fetching bestiary data for $source');
final sourceUri = _availableSources[source];
if (sourceUri == null) {
Expand Down
6 changes: 6 additions & 0 deletions lib/extensions/string_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
extension CapitalizeString on String? {
String capitalize() {
if (this == null) return '';
return '${this![0].toUpperCase()}${this!.substring(1)}';
}
}
10 changes: 9 additions & 1 deletion lib/features/settings/models/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,15 @@ class Settings {
@JsonSerializable()
class PF2eSettings {
final bool enabled;
final Set<String> bestiaries;

const PF2eSettings({
this.enabled = true,
this.enabled = false,
this.bestiaries = const {
"npc-gallery",
"pathfinder-monster-core",
"rage-of-elements",
},
});

factory PF2eSettings.fromJson(Map<String, dynamic> json) =>
Expand All @@ -57,9 +63,11 @@ class PF2eSettings {

PF2eSettings copyWith({
bool? enabled,
Set<String>? bestiaries,
}) {
return PF2eSettings(
enabled: enabled ?? this.enabled,
bestiaries: bestiaries ?? this.bestiaries,
);
}
}
Expand Down
7 changes: 6 additions & 1 deletion lib/features/settings/models/settings.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions lib/features/settings/providers/pf2e_bestiary_source.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'dart:convert';

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';

enum Pf2eBestiarySourceStatus {
loading,
loaded,
error,
}

class Pf2eBestiarySource extends ChangeNotifier {
Pf2eBestiarySourceStatus _status = Pf2eBestiarySourceStatus.loading;
Set<String> _bestiaries = {};

Pf2eBestiarySource() {
load();
}

Pf2eBestiarySourceStatus get status => _status;

Set<String> get bestiaries => _bestiaries;

Future<void> load() async {
_status = Pf2eBestiarySourceStatus.loading;
notifyListeners();
final dio =
Dio(BaseOptions(baseUrl: const String.fromEnvironment('PF2E_URI')));
final response = await dio.get('/bestiaries/index.json');
final sources =
(jsonDecode(response.data) as Map).keys.cast<String>().toSet();
_bestiaries = sources;
_status = Pf2eBestiarySourceStatus.loaded;
notifyListeners();
}
}
90 changes: 90 additions & 0 deletions lib/features/settings/widgets/pf2e_bestiary_dialog.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import 'package:battlemaster/extensions/string_extension.dart';
import 'package:battlemaster/features/settings/providers/pf2e_bestiary_source.dart';
import 'package:battlemaster/features/settings/providers/system_settings_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';

class Pf2eBestiaryDialog extends StatelessWidget {
const Pf2eBestiaryDialog({super.key});

@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Pf2eBestiarySource>(
create: (_) => Pf2eBestiarySource(),
child: const _Dialog(),
);
}
}

class _Dialog extends StatefulWidget {
const _Dialog();

@override
State<_Dialog> createState() => _DialogState();
}

class _DialogState extends State<_Dialog> {
final _selected = <String>{};

@override
void initState() {
super.initState();
final selectedBestiaries =
context.read<SystemSettingsProvider>().pf2eSettings.bestiaries;
_selected.addAll(selectedBestiaries);
}

@override
Widget build(BuildContext context) {
final localization = AppLocalizations.of(context)!;
final state = context.watch<Pf2eBestiarySource>();
return AlertDialog(
title: Text(localization.select_bestiaries_title),
content: SizedBox(
width: 500,
child: state.status == Pf2eBestiarySourceStatus.loading
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
child: Column(
children: [
for (final bestiary in state.bestiaries.toList()..sort())
CheckboxListTile(
value: _selected.contains(bestiary),
onChanged: (value) {
setState(() {
if (value!) {
_selected.add(bestiary);
} else {
_selected.remove(bestiary);
}
});
},
title: Text(
bestiary
.split('-')
.map((s) => s.capitalize())
.join(' '),
),
),
],
),
),
),
actions: [
OutlinedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(localization.cancel_button),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(_selected);
},
child: Text(localization.save_button),
),
],
);
}
}
69 changes: 61 additions & 8 deletions lib/features/settings/widgets/pf2e_settings.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import 'package:battlemaster/extensions/string_extension.dart';
import 'package:battlemaster/features/analytics/analytics_service.dart';
import 'package:battlemaster/features/settings/models/settings.dart';
import 'package:battlemaster/features/settings/widgets/pf2e_bestiary_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:icons_plus/icons_plus.dart';
import 'package:provider/provider.dart';

import '../providers/system_settings_provider.dart';
Expand All @@ -17,14 +21,7 @@ class Pf2eSettingsWidget extends StatelessWidget {
(state) => state.pf2eSettings);
return Column(
children: [
ListTile(
title: Text(
localization.pf2e_settings_title,
style: Theme.of(context).textTheme.headlineSmall,
),
),
SwitchListTile.adaptive(
secondary: Icon(Icons.library_books),
value: gameSettings.enabled,
onChanged: (value) async {
await systemSettings.setPF2eSettings(
Expand All @@ -36,7 +33,63 @@ class Pf2eSettingsWidget extends StatelessWidget {
props: {'enabled': value.toString()},
);
},
title: Text(localization.pf2e_settings_bestiary_toggle),
title: Text(
localization.pf2e_settings_title,
style: Theme.of(context).textTheme.headlineSmall,
),
subtitle: Text(localization.pf2e_settings_bestiary_toggle),
),
AnimatedSwitcher(
duration: 300.ms,
switchInCurve: Curves.easeInOutCubic,
switchOutCurve: Curves.easeInOutCubic,
child:
gameSettings.enabled ? const _Pf2eSettings() : SizedBox.shrink(),
),
],
);
}
}

class _Pf2eSettings extends StatelessWidget {
const _Pf2eSettings();

@override
Widget build(BuildContext context) {
final localization = AppLocalizations.of(context)!;
final gameSettings = context.select<SystemSettingsProvider, PF2eSettings>(
(state) => state.pf2eSettings);
final selectedBestiaries = gameSettings.bestiaries
.map((b) => b.split('-').map((s) => s.capitalize()).join(' '))
.toList()
..sort();
final bestiariesString = selectedBestiaries.join(', ');
final subtitle =
"${localization.bestiary_settings_description}\n${localization.selected_bestiaries} $bestiariesString";
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(MingCute.book_3_fill),
title: Text(localization.bestiary_settings_title),
subtitle: Text(subtitle),
isThreeLine: true,
trailing: Icon(MingCute.right_fill),
onTap: () async {
final selected = await showDialog(
context: context,
builder: (context) => const Pf2eBestiaryDialog(),
);

if (selected == null) {
return;
}

// ignore: use_build_context_synchronously
await context.read<SystemSettingsProvider>().setPF2eSettings(
gameSettings.copyWith(bestiaries: selected),
);
},
),
],
);
Expand Down
16 changes: 16 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -441,5 +441,21 @@
"skip_dead_behavior_none": "None",
"@skip_dead_behavior_none": {
"description": "The behavior to skip no dead combatants."
},
"bestiary_settings_title": "Bestiaries",
"@bestiary_settings_title": {
"description": "The title of the bestiaries section."
},
"bestiary_settings_description": "Select the bestiaries you want to enable.",
"@bestiary_settings_description": {
"description": "The description of the bestiaries section."
},
"select_bestiaries_title": "Select Bestiaries",
"@select_bestiaries_title": {
"description": "The title of the select bestiaries dialog."
},
"selected_bestiaries": "Selected bestiaries:",
"@selected_bestiaries": {
"description": "The label of the selected bestiaries."
}
}
6 changes: 5 additions & 1 deletion lib/l10n/app_pt.arb
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,9 @@
"skip_dead_description": "Pular combatentes mortos automaticamente ao avançar a rodada",
"skip_dead_behavior_all": "Todos",
"skip_dead_behavior_allButPlayers": "Todos menos jogadores",
"skip_dead_behavior_none": "Nenhum"
"skip_dead_behavior_none": "Nenhum",
"bestiary_settings_title": "Bestiários",
"bestiary_settings_description": "Ative bestiários para adicionar monstros rapidamente aos combates",
"select_bestiaries_title": "Selecionar bestiários",
"selected_bestiaries": "Bestiários selecionados:"
}
Loading

0 comments on commit 7a3fbf9

Please sign in to comment.