Files
trainhub-flutter/lib/data/services/grading_service.dart
Kazimierz Ciołek ebb7426c67
Some checks failed
Build Linux App / build (push) Failing after 1m12s
Poprawki
2026-07-07 22:03:36 +02:00

253 lines
7.1 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' show Color;
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
/// One belt level with its ITF grading requirements.
class BeltLevel {
const BeltLevel({
required this.id,
required this.gup,
required this.name,
required this.baseColor,
this.stripeColor,
required this.requirements,
});
final String id;
final String gup;
final String name;
final Color baseColor;
final Color? stripeColor;
final List<String> requirements;
}
/// Tracks grading preparation: which requirements are checked off per belt,
/// and which belt the user is currently preparing for. Persisted as JSON.
class GradingService extends ChangeNotifier {
static const _fileName = 'trainhub_grading.json';
String? _currentBeltId;
Map<String, Set<int>> _checked = {};
bool _loaded = false;
String? get currentBeltId => _currentBeltId;
bool isChecked(String beltId, int index) =>
_checked[beltId]?.contains(index) ?? false;
int checkedCount(String beltId) => _checked[beltId]?.length ?? 0;
double progress(BeltLevel belt) => belt.requirements.isEmpty
? 0
: checkedCount(belt.id) / belt.requirements.length;
Future<File> _file() async {
final dir = await getApplicationDocumentsDirectory();
return File(p.join(dir.path, _fileName));
}
Future<void> ensureLoaded() async {
if (_loaded) return;
_loaded = true;
try {
final file = await _file();
if (!file.existsSync()) return;
final json = jsonDecode(await file.readAsString());
_currentBeltId = json['current'] as String?;
_checked = {
for (final entry in (json['checked'] as Map? ?? {}).entries)
entry.key as String: {
for (final i in entry.value as List) (i as num).toInt(),
},
};
notifyListeners();
} catch (e) {
if (kDebugMode) debugPrint('Failed to load grading state: $e');
}
}
void toggle(String beltId, int index) {
final set = _checked.putIfAbsent(beltId, () => {});
set.contains(index) ? set.remove(index) : set.add(index);
notifyListeners();
_persist();
}
void setCurrentBelt(String? beltId) {
_currentBeltId = beltId;
notifyListeners();
_persist();
}
Future<void> _persist() async {
try {
final file = await _file();
await file.writeAsString(
jsonEncode({
'current': _currentBeltId,
'checked': {
for (final entry in _checked.entries)
entry.key: entry.value.toList(),
},
}),
);
} catch (e) {
if (kDebugMode) debugPrint('Failed to save grading state: $e');
}
}
}
// ---------------------------------------------------------------------------
// ITF colored-belt syllabus (10 gup → 1 gup)
// ---------------------------------------------------------------------------
const _white = Color(0xFFF4F4F5);
const _yellow = Color(0xFFFACC15);
const _green = Color(0xFF22C55E);
const _blue = Color(0xFF3B82F6);
const _red = Color(0xFFFF2B2B);
const _black = Color(0xFF18181B);
const kBeltSyllabus = <BeltLevel>[
BeltLevel(
id: '10gup',
gup: '10 GUP',
name: 'White → Yellow Stripe',
baseColor: _white,
stripeColor: _yellow,
requirements: [
'Saju Jirugi (4-direction punch)',
'Saju Makgi (4-direction block)',
'Stances: charyot, narani, annun, gunnun sogi',
'Ap Chagi (front snap kick) — both legs',
'Theory: meaning of Taekwon-Do, belt colors, dojang etiquette',
],
),
BeltLevel(
id: '9gup',
gup: '9 GUP',
name: 'Yellow Stripe → Yellow',
baseColor: _white,
stripeColor: _yellow,
requirements: [
'Tul: Chon-Ji (19 movements)',
'Niunja sogi (L-stance) + palmok daebi makgi',
'Ap Chagi and Yop Chagi from walking stance',
'3-step sparring (Sambo Matsogi) #12',
'Theory: meaning of Chon-Ji, counting in Korean 110',
],
),
BeltLevel(
id: '8gup',
gup: '8 GUP',
name: 'Yellow',
baseColor: _yellow,
requirements: [
'Tul: Dan-Gun (21 movements)',
'Sonkal daebi makgi (knife-hand guarding block)',
'Dollyo Chagi (turning kick) — both legs',
'3-step sparring #14',
'Theory: meaning of Dan-Gun, yellow belt symbolism',
],
),
BeltLevel(
id: '7gup',
gup: '7 GUP',
name: 'Yellow → Green Stripe',
baseColor: _yellow,
stripeColor: _green,
requirements: [
'Tul: Do-San (24 movements)',
'Bakat palmok makgi + sonkal taerigi combinations',
'Yopcha Jirugi (side piercing kick) — height improvement',
'3-step sparring full set',
'Theory: meaning of Do-San (Ahn Chang-Ho)',
],
),
BeltLevel(
id: '6gup',
gup: '6 GUP',
name: 'Green',
baseColor: _green,
requirements: [
'Tul: Won-Hyo (28 movements)',
'Gojung sogi (fixed stance) + punch',
'Goro Chagi (hooking kick), Naeryo Chagi (downward kick)',
'2-step sparring (Ibo Matsogi) #13',
'Theory: meaning of Won-Hyo, green belt symbolism',
],
),
BeltLevel(
id: '5gup',
gup: '5 GUP',
name: 'Green → Blue Stripe',
baseColor: _green,
stripeColor: _blue,
requirements: [
'Tul: Yul-Gok (38 movements)',
'Palkup taerigi (elbow strike), dwijibo jirugi',
'Dwit Chagi (back piercing kick) — both legs',
'2-step sparring full set + free sparring intro',
'Theory: meaning of Yul-Gok (Yi I, "Confucius of Korea")',
],
),
BeltLevel(
id: '4gup',
gup: '4 GUP',
name: 'Blue',
baseColor: _blue,
requirements: [
'Tul: Joong-Gun (32 movements)',
'Kyocha sogi (X-stance), sang palmok makgi',
'Bandae Dollyo Chagi (reverse turning kick)',
'1-step sparring (Ilbo Matsogi) #13',
'Theory: meaning of Joong-Gun (Ahn Joong-Gun)',
],
),
BeltLevel(
id: '3gup',
gup: '3 GUP',
name: 'Blue → Red Stripe',
baseColor: _blue,
stripeColor: _red,
requirements: [
'Tul: Toi-Gye (37 movements)',
'W-shape block (san makgi) in annun sogi',
'Twimyo Yop Chagi (flying side kick)',
'1-step sparring full set + free sparring 2×2 min',
'Theory: meaning of Toi-Gye (Yi Hwang)',
],
),
BeltLevel(
id: '2gup',
gup: '2 GUP',
name: 'Red',
baseColor: _red,
requirements: [
'Tul: Hwa-Rang (29 movements)',
'Combination kicking: dollyo + bandae dollyo chagi',
'Twimyo Dollyo Chagi (flying turning kick)',
'Free sparring 2×2 min + self-defence (hosinsul) basics',
'Theory: meaning of Hwa-Rang, red belt symbolism',
],
),
BeltLevel(
id: '1gup',
gup: '1 GUP',
name: 'Red → Black Stripe',
baseColor: _red,
stripeColor: _black,
requirements: [
'Tul: Choong-Moo (30 movements)',
'All previous patterns on demand',
'Twimyo Bandae Dollyo Chagi (flying reverse turning kick)',
'Free sparring vs two opponents + breaking (kyokpa)',
'Theory: meaning of Choong-Moo (Admiral Yi Sun-Sin), full terminology',
],
),
];