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 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> _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() async { final dir = await getApplicationDocumentsDirectory(); return File(p.join(dir.path, _fileName)); } Future 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 _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( 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) #1–2', 'Theory: meaning of Chon-Ji, counting in Korean 1–10', ], ), 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 #1–4', '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) #1–3', '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) #1–3', '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', ], ), ];