Skip to content

Commit

Permalink
Add graphical editor (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
paul019 authored Sep 10, 2024
1 parent 60435c0 commit e64cbac
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 56 deletions.
5 changes: 4 additions & 1 deletion lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,8 @@
"notSpecifiedYet": "Noch nicht festgelegt",
"text": "Text",
"video": "Video",
"yes": "Ja"
"yes": "Ja",
"clickBoxToEdit": "(Box anklicken zum Bearbeiten)",
"editTournamentTreeEntry": "Eintrag bearbeiten",
"save": "Speichern"
}
5 changes: 4 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,8 @@
"notSpecifiedYet": "Not specified yet",
"text": "Text",
"video": "Video",
"yes": "Yes"
"yes": "Yes",
"clickBoxToEdit": "(Click box to edit)",
"editTournamentTreeEntry": "Edit entry",
"save": "Save"
}
26 changes: 26 additions & 0 deletions lib/models/tournament_tree_model/tournament_tree_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,30 @@ class TournamentTreeModel {

bool get isEmpty => stages.isEmpty;
int get numOfStages => stages.length;

TournamentTreeEntry? getEntryAtCoordinate(int i, int j) {
if (numOfStages > i) {
final stage = stages[i];
if (stage.entries.length > j) {
return stage.entries[j];
}
}

return null;
}

TournamentTreeModel replaceEntryAtCoordinate(int i, int j, TournamentTreeEntry entry) {
final List<TournamentTreeStage> newStages = List.of(stages);

if (i < numOfStages) {
final stage = stages[i];
if (j < stage.entries.length) {
final List<TournamentTreeEntry> newEntries = List.of(stage.entries);
newEntries[j] = entry;
newStages[i] = TournamentTreeStage(entries: newEntries);
}
}

return TournamentTreeModel(stages: newStages);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class TournamentTreeView extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
for (var stage in tournamentTreeModel.stages.reversed)
for (var stage in tournamentTreeModel.stages)
Flexible(
flex: 1,
child: Row(
Expand Down
157 changes: 104 additions & 53 deletions lib/screens/settings_page/tournament_tree_card.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:integration_bee_helper/models/settings_model/settings_model.dart';
import 'package:integration_bee_helper/models/tournament_tree_model/tournament_tree_entry.dart';
import 'package:integration_bee_helper/models/tournament_tree_model/tournament_tree_model.dart';
import 'package:integration_bee_helper/models/tournament_tree_model/tournament_tree_stage.dart';
import 'package:integration_bee_helper/screens/settings_page/tournament_tree_entry_editor.dart';
import 'package:integration_bee_helper/services/basic_services/intl_service.dart';
import 'package:integration_bee_helper/services/settings_service/settings_service.dart';
import 'package:integration_bee_helper/widgets/cancel_save_buttons.dart';

class TournamentTreeCard extends StatefulWidget {
final SettingsModel settings;
Expand All @@ -15,29 +19,7 @@ class TournamentTreeCard extends StatefulWidget {
}

class _TournamentTreeCardState extends State<TournamentTreeCard> {
bool hasChanged = false;

late String tournamentTreeString;
late TextEditingController tournamentTreeStringController;

@override
void initState() {
tournamentTreeString = widget.settings.tournamentTree.encode();
tournamentTreeStringController =
TextEditingController(text: tournamentTreeString);

super.initState();
}

@override
void didUpdateWidget(covariant TournamentTreeCard oldWidget) {
tournamentTreeString = widget.settings.tournamentTree.encode();
tournamentTreeStringController.text = tournamentTreeString;

hasChanged = false;

super.didUpdateWidget(oldWidget);
}
static const maxNumberOfStages = 4;

@override
Widget build(BuildContext context) {
Expand All @@ -50,47 +32,116 @@ class _TournamentTreeCardState extends State<TournamentTreeCard> {
children: [
Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
padding: const EdgeInsets.only(top: 8.0),
child: Text(
MyIntl.of(context).tournamentTree,
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
),
const Divider(),
TextField(
decoration: InputDecoration(
border: InputBorder.none,
hintText: MyIntl.of(context).tournamentTree,
Center(
child: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
MyIntl.of(context).clickBoxToEdit,
),
),
controller: tournamentTreeStringController,
onChanged: (v) => setState(() {
tournamentTreeString = v;
hasChanged = true;
}),
maxLines: 8,
),
if (hasChanged)
CancelSaveButtons(
onCancel: () {
setState(() {
hasChanged = false;
tournamentTreeString =
widget.settings.tournamentTree.encode();
});
},
onSave: () async {
await SettingsService().edit(
tournamentTree:
TournamentTreeModel.decode(tournamentTreeString),
);
setState(() => hasChanged = false);
},
),
const Divider(),
Column(
children: [
for (var i = 0; i < maxNumberOfStages; i++)
Row(
children: [
for (var j = 0; j < pow(2, i); j++)
_buildEntry(context, i, j),
],
),
],
),
],
),
),
),
);
}

TournamentTreeModel get tournamentTree {
final initialTournamentTree = widget.settings.tournamentTree;
final List<TournamentTreeStage> stages = [];

for (int i = 0; i < maxNumberOfStages; i++) {
final List<TournamentTreeEntry> entries = [];

for (int j = 0; j < pow(2, i); j++) {
TournamentTreeEntry? initialEntry =
initialTournamentTree.getEntryAtCoordinate(i, j);

entries.add(TournamentTreeEntry(
title: initialEntry?.title ?? '',
subtitle: initialEntry?.subtitle ?? '',
flex: 1,
));
}

stages.add(TournamentTreeStage(entries: entries));
}

return TournamentTreeModel(stages: stages);
}

Widget _buildEntry(BuildContext context, int i, int j) {
TournamentTreeEntry? entry = tournamentTree.getEntryAtCoordinate(i, j);
bool isEmpty = entry == null || entry.isEmpty;

return Flexible(
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
color: isEmpty ? null : Colors.white,
child: InkWell(
borderRadius: BorderRadius.circular(8.0),
onTap: () {
TournamentTreeEntryEditor.show(
context: context,
entry: entry,
onSave: (title, subtitle) {
final entry = TournamentTreeEntry(
title: title,
subtitle: subtitle,
flex: 1,
);

final newTournamentTree =
tournamentTree.replaceEntryAtCoordinate(i, j, entry);

SettingsService().edit(tournamentTree: newTournamentTree);
},
);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
entry?.title ?? '',
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
Text(
entry?.subtitle ?? '',
textAlign: TextAlign.center,
),
],
),
),
),
),
);
}
}
105 changes: 105 additions & 0 deletions lib/screens/settings_page/tournament_tree_entry_editor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'package:integration_bee_helper/models/tournament_tree_model/tournament_tree_entry.dart';
import 'package:integration_bee_helper/services/basic_services/intl_service.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';

class TournamentTreeEntryEditor extends StatefulWidget {
final TournamentTreeEntry? entry;
final void Function(String, String) onSave;

const TournamentTreeEntryEditor({
super.key,
required this.entry,
required this.onSave,
});

static void show({
required BuildContext context,
required TournamentTreeEntry? entry,
required void Function(String, String) onSave,
}) {
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return TournamentTreeEntryEditor(
entry: entry,
onSave: onSave,
);
},
);
}

@override
State<TournamentTreeEntryEditor> createState() =>
_TournamentTreeEntryEditorState();
}

class _TournamentTreeEntryEditorState extends State<TournamentTreeEntryEditor> {
late String title;
late String subtitle;

late TextEditingController titleController;
late TextEditingController subtitleController;

@override
void initState() {
title = widget.entry?.title ?? '';
subtitle = widget.entry?.subtitle ?? '';

titleController = TextEditingController(text: title);
subtitleController = TextEditingController(text: subtitle);

super.initState();
}

@override
Widget build(BuildContext context) {
return PointerInterceptor(
child: AlertDialog(
title: Text(MyIntl.of(context).editTournamentTreeEntry),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
decoration: InputDecoration(
border: InputBorder.none,
hintText: MyIntl.of(context).title,
),
controller: titleController,
onChanged: (v) => setState(() {
title = v;
}),
maxLines: 1,
),
TextField(
decoration: InputDecoration(
border: InputBorder.none,
hintText: MyIntl.of(context).subtitle,
),
controller: subtitleController,
onChanged: (v) => setState(() {
subtitle = v;
}),
maxLines: 1,
),
],
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(MyIntl.of(context).cancel),
),
TextButton(
onPressed: () {
widget.onSave(title, subtitle);
Navigator.of(context).pop();
},
child: Text(MyIntl.of(context).save),
),
],
),
);
}
}

0 comments on commit e64cbac

Please sign in to comment.