// main.dart import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:convert'; import 'package:uuid/uuid.dart'; import 'package:intl/intl.dart'; // For date formatting import 'dart:math' as math; // Add these dependencies to your pubspec.yaml: // dependencies: // flutter: // sdk: flutter // shared_preferences: ^2.2.0 # Use the latest version // uuid: ^4.0.0 # Use the latest version // intl: ^0.18.0 # Use the latest version void main() { runApp(const SoloLevelingApp()); } // SECTION: Global Variables and Data Structures // File path for saving user data (simulates localStorage) const String USER_DATA_KEY = 'soloLevelingUserData'; // Task Database for AI quest generation final Map>> taskDatabase = { "physical": [ {"task": "Do 20 minutes of cardio exercise", "xp": 25, "desc": "Great for boosting energy and health stats"}, {"task": "Perform 3 sets of bodyweight exercises", "xp": 30, "desc": "Improves both physical and mental discipline"}, {"task": "Take a 30-minute walk outside", "xp": 20, "desc": "Fresh air and movement helps clear your mind"} ], "mental": [ {"task": "Read a book for 30 minutes", "xp": 25, "desc": "Expands knowledge and improves focus"}, {"task": "Solve 3 brain teasers or puzzles", "xp": 30, "desc": "Sharpens problem-solving skills"}, {"task": "Learn something new for 20 minutes", "xp": 25, "desc": "Continuous learning boosts knowledge stat"} ], "creative": [ {"task": "Work on a creative project for 30 minutes", "xp": 30, "desc": "Creativity is essential for well-rounded growth"}, {"task": "Journal your thoughts and ideas", "xp": 25, "desc": "Reflection improves self-awareness"}, {"task": "Brainstorm 10 new ideas in your area of interest", "xp": 35, "desc": "Idea generation exercises creativity"} ], "productive": [ {"task": "Organize your workspace for 15 minutes", "xp": 20, "desc": "Clean environment improves focus"}, {"task": "Plan your next day in detail", "xp": 25, "desc": "Planning boosts discipline stat"}, {"task": "Complete one procrastinated task", "xp": 40, "desc": "Overcoming procrastination is valuable"} ], "social": [ {"task": "Reach out to a friend or family member", "xp": 20, "desc": "Social connections are important"}, {"task": "Give someone a genuine compliment", "xp": 15, "desc": "Positive interactions boost mood"}, {"task": "Practice active listening in a conversation", "xp": 25, "desc": "Improves communication skills"} ], "rest": [ {"task": "Take a 20-minute power nap", "xp": 15, "desc": "Rest is crucial for productivity"}, {"task": "Practice 10 minutes of meditation", "xp": 25, "desc": "Improves focus and reduces stress"}, {"task": "Do nothing for 15 minutes (no screens)", "xp": 20, "desc": "Mental rest is important too"} ] }; // AI Responses final Map aiResponses = { "greetings": [ "Hello! Ready to tackle some quests today?", "Greetings! What's your focus for today?", "Hi there! Let's make progress on your goals." ], "task_suggestions": [ "Based on your current stats, I recommend this quest:", "Here's a quest that would help you improve:", "This activity would be beneficial for your growth:" ], "encouragement": [ "You're making great progress!", "Consistency is key - keep it up!", "Each completed quest brings you closer to your goals.", "Your dedication is impressive!", "Success is built one quest at a time." ], "advice": { "focus": [ "Try the Pomodoro technique: 25 minutes work, 5 minutes break.", "Minimize distractions by turning off notifications during work sessions.", "Practice mindfulness to improve your concentration." ], "discipline": [ "Create a daily routine and stick to it consistently.", "Start with small habits and gradually increase the difficulty.", "Use implementation intentions: 'When X happens, I will do Y'." ], "knowledge": [ "Use active recall and spaced repetition for better learning.", "Teach what you learn to someone else to reinforce understanding.", "Connect new information to what you already know." ] } }; // Default user data structure final Map defaultUserData = { "username": "Player", "email": null, "stats": { "level": 1, "xp": 0, "focus": 70, "discipline": 60, "knowledge": 50, "health": 100, "energy": 100, "class": "Beginner" }, "inventory": ["Notebook", "Online Course Subscription", "Focus Timer App"], "skills": ["Time Management", "Problem Solving", "Creativity"], "goals": [ {"id": const Uuid().v4(), "text": "Learn Flutter", "completed": false, "xp": 30, "completion_date": null}, {"id": const Uuid().v4(), "text": "Practice Data Structures", "completed": false, "xp": 50, "completion_date": null}, {"id": const Uuid().v4(), "text": "Exercise for 30 minutes", "completed": false, "xp": 20, "completion_date": null} ], "task_history": [], // Stores objects {"date": ISOString, "xp": number, "type": string, "description": string} "activity_log": {}, // Format: {"YYYY-MM-DD": count} "achievements": [ {"id": 'first-quest', "name": "First Quest", "icon": "🏆", "earned": false}, {"id": 'first-book', "name": "First Book Read", "icon": "📚", "earned": false}, {"id": 'five-books', "name": "Bibliophile Initiate (5 Books)", "icon": "📖", "earned": false}, {"id": 'ten-books', "name": "Avid Reader (10 Books)", "icon": "🧠", "earned": false}, {"id": 'first-habit', "name": "First Habit Completed", "icon": "✅", "earned": false}, {"id": 'seven-day-streak', "name": "7-Day Habit Streak", "icon": "🔥", "earned": false}, {"id": 'first-training', "name": "First Training Logged", "icon": "🏋️", "earned": false}, ], "habits": [], // {"id": uuid, "name": 'Meditate', "streak": 0, "total_days_completed": 0, "last_completed": null, "start_date": 'YYYY-MM-DD', "completions": {'YYYY-M-D': true}} "books": [], // {"id": uuid, "title": 'Book Title', "author": 'Author Name', "completed": false, "completion_date": null} "training_log": [] // {"id": uuid, "date": ISOString, "exercise": 'Squats', "reps": 10, "sets": 3, "xp_gained": 30} }; // SECTION: SoloLevelingApp - Main Application Widget class SoloLevelingApp extends StatelessWidget { const SoloLevelingApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Solo Leveling System', debugShowCheckedModeBanner: false, theme: ThemeData( brightness: Brightness.dark, primaryColor: const Color(0xFF00BFFF), // Primary Blue canvasColor: const Color(0xFF0A0F1C), // Background Dark scaffoldBackgroundColor: const Color(0xFF0A0F1C), appBarTheme: const AppBarTheme( backgroundColor: Color(0xFF0A0F1C), foregroundColor: Color(0xFFE0E0E0), ), textTheme: const TextTheme( bodyLarge: TextStyle(color: Color(0xFFE0E0E0)), bodyMedium: TextStyle(color: Color(0xFFE0E0E0)), labelLarge: TextStyle(color: Colors.white), headlineSmall: TextStyle(color: Color(0xFF00BFFF), fontSize: 20, fontWeight: FontWeight.bold), titleMedium: TextStyle(color: Color(0xFFE0E0E0), fontSize: 16, fontWeight: FontWeight.bold), ), cardColor: const Color(0xFF001428), // Darker background for panels/cards dividerColor: const Color(0xFF00BFFF), // Primary blue for dividers elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF00BFFF), // Primary blue for buttons foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( foregroundColor: const Color(0xFF00BFFF), // Primary blue for text buttons ), ), inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: const Color(0xFF08111A), // Darker background for inputs border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: Color(0xFF00BFFF)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: Color(0xFF00BFFF)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: Color(0xFF00BFFF), width: 2), ), labelStyle: const TextStyle(color: Color(0xFFE0E0E0)), hintStyle: const TextStyle(color: Color(0xFF808080)), ), checkboxTheme: CheckboxThemeData( fillColor: MaterialStateProperty.resolveWith((states) { if (states.contains(MaterialState.selected)) { return const Color(0xFF00BFFF); } return const Color(0xFF08111A); }), checkColor: MaterialStateProperty.all(Colors.white), side: const BorderSide(color: Color(0xFF00BFFF), width: 1.5), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), ), home: const SoloLevelingHomePage(), ); } } // SECTION: SoloLevelingHomePage - Stateful Widget for Main App Logic class SoloLevelingHomePage extends StatefulWidget { const SoloLevelingHomePage({super.key}); @override State createState() => _SoloLevelingHomePageState(); } class _SoloLevelingHomePageState extends State with SingleTickerProviderStateMixin { late Map userData; late TabController _tabController; final ScrollController _chatScrollController = ScrollController(); final TextEditingController _chatInputController = TextEditingController(); final TextEditingController _newGoalController = TextEditingController(); final TextEditingController _newHabitNameController = TextEditingController(); final TextEditingController _newHabitStartDateController = TextEditingController(); final TextEditingController _newBookTitleController = TextEditingController(); final TextEditingController _newBookAuthorController = TextEditingController(); final TextEditingController _trainingExerciseController = TextEditingController(); final TextEditingController _trainingRepsController = TextEditingController(); final TextEditingController _trainingSetsController = TextEditingController(); List> chatHistory = []; String? _messageBarText; Color _messageBarColor = Colors.cyan; bool _isTyping = false; @override void initState() { super.initState(); _tabController = TabController(length: 7, vsync: this); _tabController.addListener(_handleTabSelection); _loadUserData(); _newHabitStartDateController.text = DateFormat('yyyy-MM-dd').format(DateTime.now()); } @override void dispose() { _tabController.removeListener(_handleTabSelection); _tabController.dispose(); _chatScrollController.dispose(); _chatInputController.dispose(); _newGoalController.dispose(); _newHabitNameController.dispose(); _newHabitStartDateController.dispose(); _newBookTitleController.dispose(); _newBookAuthorController.dispose(); _trainingExerciseController.dispose(); _trainingRepsController.dispose(); _trainingSetsController.dispose(); super.dispose(); } void _handleTabSelection() { if (!_tabController.indexIsChanging) { // Refresh the UI when tab changes to ensure latest data is displayed setState(() { // This setState will trigger all relevant build methods to re-render. }); } } // SECTION: Data Management Functions (Loading, Saving, Logout) Future _loadUserData() async { _showMessage("Loading player data...", Colors.cyan); final prefs = await SharedPreferences.getInstance(); String? storedData = prefs.getString(USER_DATA_KEY); setState(() { if (storedData != null) { userData = jsonDecode(storedData); // Merge new default achievements if they were added in a new version of the app for (var defaultAch in defaultUserData["achievements"]) { if (!userData["achievements"].any((ach) => ach["id"] == defaultAch["id"])) { userData["achievements"].add(defaultAch); } } // Ensure properties critical for functionality exist and are of correct type userData["goals"] = (userData["goals"] as List? ?? []).map((e) => Map.from(e)).toList(); userData["goals"].forEach((goal) { goal["completion_date"] ??= null; // Ensure completion_date exists }); userData["task_history"] = (userData["task_history"] as List? ?? []).map((e) => Map.from(e)).toList(); userData["stats"]["xp"] = (userData["stats"]["xp"] as num? ?? 0).toInt(); // Ensure XP is int // Re-calculate streaks and totalDaysCompleted for all habits on load to ensure accuracy userData["habits"] = (userData["habits"] as List? ?? []).map((e) => Map.from(e)).toList(); for (var habit in userData["habits"]) { habit["completions"] ??= {}; // Ensure completions object exists habit["streak"] = _calculateCurrentStreakForHabit(habit); habit["total_days_completed"] = _calculateTotalDaysCompleted(habit); } userData["books"] = (userData["books"] as List? ?? []).map((e) => Map.from(e)).toList(); userData["training_log"] = (userData["training_log"] as List? ?? []).map((e) => Map.from(e)).toList(); userData["achievements"] = (userData["achievements"] as List? ?? []).map((e) => Map.from(e)).toList(); userData["activity_log"] = (userData["activity_log"] as Map? ?? {}).map((k, v) => MapEntry(k as String, v as int)); } else { userData = Map.from(defaultUserData); // Deep copy } _showMessage("Data loaded!", Colors.green); _updateClass(); chatHistory = [ {"sender": "ai", "message": "System: Hello! I'm your AI assistant. How can I help you today? You can ask me for:
  • Personalized quests
  • Skill development advice
  • Productivity tips
  • Progress analysis
What would you like to focus on?"} ]; }); } Future _saveUserData() async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(USER_DATA_KEY, jsonEncode(userData)); _showMessage("Data saved!", Colors.green); } void _logout() { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text("Logout"), content: const Text("Are you sure you want to logout? All local data will be cleared."), actions: [ TextButton( child: const Text("Cancel"), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( child: const Text("Logout"), onPressed: () async { final prefs = await SharedPreferences.getInstance(); await prefs.remove(USER_DATA_KEY); Navigator.of(context).pop(); setState(() { userData = Map.from(defaultUserData); // Reset to default chatHistory = [ {"sender": "ai", "message": "System: Hello! I'm your AI assistant. How can I help you today? You can ask me for:
  • Personalized quests
  • Skill development advice
  • Productivity tips
  • Progress analysis
What would you like to focus on?"} ]; }); _showMessage("Logged out successfully.", Colors.blue); }, ), ], ); }, ); } // SECTION: Core System UI & Logic Functions void _updateClass() { String playerClass = "Beginner"; if (userData["stats"]["level"] >= 10) { playerClass = "Expert"; } else if (userData["stats"]["level"] >= 5) { playerClass = "Intermediate"; } setState(() { userData["stats"]["class"] = playerClass; }); _saveUserData(); } void _gainXP(int amount) { setState(() { userData["stats"]["xp"] = (userData["stats"]["xp"] as int) + amount; _showMessage("+ $amount XP!", const Color(0xFF00FF88)); // Success green const int maxXP = 100; while (userData["stats"]["xp"] >= maxXP) { userData["stats"]["xp"] -= maxXP; userData["stats"]["level"] = (userData["stats"]["level"] as int) + 1; // Improve stats on level up userData["stats"]["focus"] = (userData["stats"]["focus"] as int) + (3 + math.Random().nextInt(3)); userData["stats"]["focus"] = userData["stats"]["focus"] > 100 ? 100 : userData["stats"]["focus"]; userData["stats"]["discipline"] = (userData["stats"]["discipline"] as int) + (4 + math.Random().nextInt(3)); userData["stats"]["discipline"] = userData["stats"]["discipline"] > 100 ? 100 : userData["stats"]["discipline"]; userData["stats"]["knowledge"] = (userData["stats"]["knowledge"] as int) + (5 + math.Random().nextInt(3)); userData["stats"]["knowledge"] = userData["stats"]["knowledge"] > 100 ? 100 : userData["stats"]["knowledge"]; _updateClass(); _addAIMessage("System: Congratulations on reaching level ${userData['stats']['level']}! Your stats have improved. Keep up the good work!"); } }); _saveUserData(); } // SECTION: Quests & Goals Management Functions void _addCustomGoal() { final text = _newGoalController.text.trim(); if (text.isEmpty) { _showMessage("Quest cannot be empty!", Colors.orange); return; } setState(() { final xp = text.length > 50 ? 50 : (text.length < 20 ? 20 : text.length); userData["goals"].add({ "id": const Uuid().v4(), "text": text, "completed": false, "xp": xp, "completion_date": null, }); _newGoalController.clear(); _showMessage("New quest added!", const Color(0xFF00FF88)); _addAIMessage("New quest added: $text (worth $xp XP). Would you like me to suggest steps to achieve this?"); }); _saveUserData(); } void _completeGoal(String goalId) { setState(() { final goalIndex = userData["goals"].indexWhere((g) => g["id"] == goalId && !g["completed"]); if (goalIndex != -1) { final goal = userData["goals"][goalIndex]; goal["completed"] = true; goal["completion_date"] = DateTime.now().toIso8601String(); userData["task_history"].add({ "date": DateTime.now().toIso8601String(), "xp": goal["xp"], "type": 'quest', "description": goal['text'], }); _gainXP(goal["xp"] as int); _addAIMessage("Quest completed: ${goal['text']}! You earned ${goal['xp']} XP."); // Check for 'first-quest' achievement final firstQuestAch = userData["achievements"].firstWhere((a) => a["id"] == 'first-quest', orElse: () => null); if (firstQuestAch != null && !firstQuestAch["earned"]) { firstQuestAch["earned"] = true; _showMessage("Achievement Unlocked: First Quest! 🏆", const Color(0xFF00FF88)); } } else { _showMessage("Quest not found or already completed.", Colors.orange); } }); _saveUserData(); } void _removeGoal(String goalId) { setState(() { final initialLength = userData["goals"].length; userData["goals"].removeWhere((g) => g["id"] == goalId); if (userData["goals"].length < initialLength) { _showMessage("Quest removed.", Colors.blue); } else { _showMessage("Quest not found.", Colors.orange); } }); _saveUserData(); } // SECTION: AI Assistant Functions void _sendChatMessage() { final message = _chatInputController.text.trim(); if (message.isEmpty) return; setState(() { chatHistory.add({"sender": "user", "message": message}); _chatInputController.clear(); _isTyping = true; }); _processUserMessage(message); _scrollToBottom(); } void _processUserMessage(String message) { Future.delayed(const Duration(seconds: 1), () { setState(() { _isTyping = false; final lowerMsg = message.toLowerCase(); String response = ""; if (lowerMsg.contains('hello') || lowerMsg.contains('hi')) { response = "System: ${_getRandomResponse(aiResponses['greetings'])} What would you like to focus on today?"; } else if (lowerMsg.contains('task') || lowerMsg.contains('quest') || lowerMsg.contains('mission')) { _generateSmartTask(); return; // Exit as smart task handles its own message } else if (lowerMsg.contains('goal') || lowerMsg.contains('target') || lowerMsg.contains('objective')) { final activeGoals = (userData["goals"] as List).where((g) => !g["completed"]).toList(); if (activeGoals.isEmpty) { response = "System: You don't have any active quests. Would you like to add some?"; } else { final goalTexts = activeGoals.map((g) => "
  • ${g['text']} (${g['xp']} XP)
  • ").join(''); response = "System: Your current quests:
      $goalTexts
    "; response += "
    Would you like to:
    "; response += " "; response += ""; } } else if (lowerMsg.contains('stat') || lowerMsg.contains('level') || lowerMsg.contains('progress')) { response = "System: Your current stats:
    " "Level: ${userData['stats']['level']}
    " "Focus: ${userData['stats']['focus']}/100
    " "Discipline: ${userData['stats']['discipline']}/100
    " "Knowledge: ${userData['stats']['knowledge']}/100

    "; if (userData["stats"]["focus"] < 60) response += "Your focus could use improvement. Try meditation or reducing distractions.
    "; if (userData["stats"]["discipline"] < 60) response += "Your discipline needs work. Setting clear routines can help.
    "; if (userData["stats"]["knowledge"] < 60) response += "Your knowledge stat is low. Consider more learning activities.
    "; response += "
    Would you like quests to improve specific stats?
    "; response += " "; response += " "; response += ""; } else if (lowerMsg.contains('advice') || lowerMsg.contains('tip') || lowerMsg.contains('help')) { if (lowerMsg.contains('focus')) { response = "System: ${_getRandomResponse(aiResponses['advice']['focus'])}"; } else if (lowerMsg.contains('discipline')) { response = "System: ${_getRandomResponse(aiResponses['advice']['discipline'])}"; } else if (lowerMsg.contains('knowledge')) { response = "System: ${_getRandomResponse(aiResponses['advice']['knowledge'])}"; } else { response = "System: I can offer advice on improving your focus, discipline, or knowledge. Which would you like help with?"; } } else if (lowerMsg.contains('thank')) { response = "System: You're welcome, ${userData['username']}! Remember I'm here to help you grow."; } else { response = "System: ${_getRandomResponse([ 'That\'s interesting. How does this relate to your personal growth goals?', 'I\'m here to help you improve. What\'s on your mind?', 'Let me know if you\'d like quests, advice, or to review your progress.', 'I can help you stay motivated and productive. What\'s on your mind?', 'Would you like me to suggest an activity based on that?' ])}"; } _addAIMessage(response, withButtons: true); }); _scrollToBottom(); }); } void _generateSmartTask() { setState(() { _isTyping = true; }); Future.delayed(const Duration(seconds: 1), () { setState(() { _isTyping = false; final user = userData; final List statAreas = []; if ((user["stats"]["focus"] as int) < 70) statAreas.add('focus'); if ((user["stats"]["discipline"] as int) < 70) statAreas.add('discipline'); if ((user["stats"]["knowledge"] as int) < 70) statAreas.add('knowledge'); String focusArea = statAreas.isNotEmpty ? statAreas[math.Random().nextInt(statAreas.length)] : ['physical', 'mental', 'creative', 'productive', 'social', 'rest'][math.Random().nextInt(6)]; String taskCategory; switch (focusArea) { case 'focus': taskCategory = math.Random().nextBool() ? 'mental' : 'rest'; break; case 'discipline': taskCategory = math.Random().nextBool() ? 'productive' : 'physical'; break; case 'knowledge': taskCategory = 'mental'; break; default: taskCategory = focusArea; } final tasks = taskDatabase[taskCategory]!; Map selectedTask = Map.from(tasks[math.Random().nextInt(tasks.length)]); // Ensure modifiable map int xpReward = selectedTask["xp"] as int; if (focusArea == 'focus' && (user["stats"]["focus"] as int) < 60) xpReward += 10; if (focusArea == 'discipline' && (user["stats"]["discipline"] as int) < 60) xpReward += 10; if (focusArea == 'knowledge' && (user["stats"]["knowledge"] as int) < 60) xpReward += 10; // Avoid recently given tasks (simplified for Flutter) final recentTasksDesc = (user["task_history"] as List).sublist( (user["task_history"] as List).length > 5 ? (user["task_history"] as List).length - 5 : 0 ).map((task) => (task["description"] as String?)?.toLowerCase() ?? '').toList(); if (recentTasksDesc.contains((selectedTask["task"] as String).toLowerCase()) && tasks.length > 1) { final otherTasks = tasks.where((t) => (t["task"] as String).toLowerCase() != (selectedTask["task"] as String).toLowerCase()).toList(); if (otherTasks.isNotEmpty) { selectedTask = Map.from(otherTasks[math.Random().nextInt(otherTasks.length)]); xpReward = selectedTask["xp"] as int; if (focusArea == 'focus' && (user["stats"]["focus"] as int) < 60) xpReward += 10; if (focusArea == 'discipline' && (user["stats"]["discipline"] as int) < 60) xpReward += 10; if (focusArea == 'knowledge' && (user["stats"]["knowledge"] as int) < 60) xpReward += 10; } } final aiResponse = "System: ${_getRandomResponse(aiResponses['task_suggestions'])}" "

    ${selectedTask['task']}" "

    ${selectedTask['desc']}" "

    Complete it to earn $xpReward XP."; chatHistory.add({"sender": "ai", "message": aiResponse}); chatHistory.add({ "sender": "ai_action", "command": "complete_generated_quest", "args": {"xp": xpReward, "description": selectedTask['task'], "task_id": const Uuid().v4()} // Add unique ID for the task }); }); _scrollToBottom(); }); } void _generateStatTask(String stat) { setState(() { _isTyping = true; }); Future.delayed(const Duration(seconds: 1), () { setState(() { _isTyping = false; final user = userData; String? taskCategory; String? taskDesc; int xpReward = 0; switch (stat) { case 'focus': taskCategory = math.Random().nextBool() ? 'mental' : 'rest'; xpReward = 25 + math.Random().nextInt(10); taskDesc = _getRandomResponse(aiResponses['advice']['focus']); break; case 'discipline': taskCategory = math.Random().nextBool() ? 'productive' : 'physical'; xpReward = 30 + math.Random().nextInt(10); taskDesc = _getRandomResponse(aiResponses['advice']['discipline']); break; case 'knowledge': taskCategory = 'mental'; xpReward = 25 + math.Random().nextInt(10); taskDesc = _getRandomResponse(aiResponses['advice']['knowledge']); break; } if (taskCategory == null) { _addAIMessage("System: I cannot generate a task for that stat category right now."); return; } final tasks = taskDatabase[taskCategory]!; final selectedTask = Map.from(tasks[math.Random().nextInt(tasks.length)]); final aiResponse = "System: To improve your $stat, try this quest:" "

    ${selectedTask['task']}" "

    $taskDesc" "

    Complete it to earn $xpReward XP."; chatHistory.add({"sender": "ai", "message": aiResponse}); chatHistory.add({ "sender": "ai_action", "command": "complete_generated_quest", "args": {"xp": xpReward, "description": selectedTask['task'], "task_id": const Uuid().v4()} }); }); _scrollToBottom(); }); } void _scrollToBottom() { WidgetsBinding.instance.addPostFrameCallback((_) { if (_chatScrollController.hasClients) { _chatScrollController.animateTo( _chatScrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } }); } // SECTION: Calendar Functions void _logActivityToday() { setState(() { final todayKey = DateFormat('yyyy-MM-dd').format(DateTime.now()); userData["activity_log"] ??= {}; userData["activity_log"][todayKey] = (userData["activity_log"][todayKey] as int? ?? 0) + 1; _showMessage("Activity logged for today!", const Color(0xFF00FF88)); }); _saveUserData(); } // SECTION: Habit Tracker Functions void _addHabit() { final name = _newHabitNameController.text.trim(); final startDateStr = _newHabitStartDateController.text.trim(); if (name.isEmpty || startDateStr.isEmpty) { _showMessage("Habit name and start date are required.", Colors.orange); return; } try { DateFormat('yyyy-MM-dd').parseStrict(startDateStr); } catch (_) { _showMessage("Invalid date format. Use YYYY-MM-DD.", Colors.orange); return; } setState(() { userData["habits"].add({ "id": const Uuid().v4(), "name": name, "streak": 0, "total_days_completed": 0, "last_completed": null, "start_date": startDateStr, "completions": {}, }); _newHabitNameController.clear(); _newHabitStartDateController.text = DateFormat('yyyy-MM-dd').format(DateTime.now()); _showMessage("New habit '$name' added!", const Color(0xFF00FF88)); }); _saveUserData(); } void _completeHabit(String habitId) { setState(() { final habitIndex = userData["habits"].indexWhere((h) => h["id"] == habitId); if (habitIndex != -1) { final habit = userData["habits"][habitIndex]; final todayKey = DateFormat('yyyy-MM-dd').format(DateTime.now()); if (habit["completions"][todayKey] == true) { _showMessage("You've already completed this habit today!", Colors.blue); return; } habit["completions"][todayKey] = true; habit["last_completed"] = todayKey; // Recalculate streak and total days habit["total_days_completed"] = _calculateTotalDaysCompleted(habit); habit["streak"] = _calculateCurrentStreakForHabit(habit); final xpBonus = _calculateStreakXP(habit["streak"] as int); userData["task_history"].add({ "date": DateTime.now().toIso8601String(), "xp": xpBonus, "type": 'habit', "description": "Completed habit: ${habit['name']}", }); _gainXP(xpBonus); _addAIMessage("You completed '${habit['name']}'! Earned $xpBonus XP (Current Streak: ${habit['streak']} days)."); // Check for achievements final firstHabitAch = userData["achievements"].firstWhere((a) => a["id"] == 'first-habit', orElse: () => null); if (firstHabitAch != null && !firstHabitAch["earned"]) { firstHabitAch["earned"] = true; _showMessage("Achievement Unlocked: First Habit Completed! ✅", const Color(0xFF00FF88)); } final sevenDayStreakAch = userData["achievements"].firstWhere((a) => a["id"] == 'seven-day-streak', orElse: () => null); if ((habit["streak"] as int) >= 7 && sevenDayStreakAch != null && !sevenDayStreakAch["earned"]) { sevenDayStreakAch["earned"] = true; _showMessage("Achievement Unlocked: 7-Day Habit Streak! 🔥", const Color(0xFF00FF88)); } } else { _showMessage("Habit not found.", Colors.orange); } }); _saveUserData(); } void _removeHabit(String habitId) { setState(() { final initialLength = userData["habits"].length; userData["habits"].removeWhere((h) => h["id"] == habitId); if (userData["habits"].length < initialLength) { _showMessage("Habit removed.", Colors.blue); } else { _showMessage("Habit not found.", Colors.orange); } }); _saveUserData(); } int _calculateCurrentStreakForHabit(Map habit) { if (habit["completions"] == null || (habit["completions"] as Map).isEmpty) { return 0; } int currentStreak = 0; DateTime today = DateTime.now(); today = DateTime(today.year, today.month, today.day); // Normalize to start of day DateTime checkingDate = today; String todayKey = DateFormat('yyyy-MM-dd').format(today); String yesterdayKey = DateFormat('yyyy-MM-dd').format(today.subtract(const Duration(days: 1))); if (habit["completions"][todayKey] == true) { currentStreak = 1; checkingDate = checkingDate.subtract(const Duration(days: 1)); } else if (habit["completions"][yesterdayKey] == true) { // If not completed today but completed yesterday, streak is broken return 0; } else { // Not completed today or yesterday, streak is 0 return 0; } while (true) { String dateKey = DateFormat('yyyy-MM-dd').format(checkingDate); if (habit["completions"][dateKey] == true) { currentStreak++; checkingDate = checkingDate.subtract(const Duration(days: 1)); } else { break; // Streak broken } DateTime habitStartDate = DateFormat('yyyy-MM-dd').parseStrict(habit["start_date"]); habitStartDate = DateTime(habitStartDate.year, habitStartDate.month, habitStartDate.day); if (checkingDate.isBefore(habitStartDate)) { break; // Passed habit start date } } return currentStreak; } int _calculateTotalDaysCompleted(Map habit) { return (habit["completions"] as Map? ?? {}).keys.length; } int _calculateStreakXP(int streak) { const int baseXP = 5; if (streak <= 0) return 0; return baseXP + (streak - 1) * 2; } // SECTION: Library / Books Functions void _addBook() { final title = _newBookTitleController.text.trim(); final author = _newBookAuthorController.text.trim(); if (title.isEmpty) { _showMessage("Book title cannot be empty!", Colors.orange); return; } setState(() { userData["books"].add({ "id": const Uuid().v4(), "title": title, "author": author.isEmpty ? null : author, "completed": false, "completion_date": null, }); _newBookTitleController.clear(); _newBookAuthorController.clear(); _showMessage("Book '$title' added to your library!", const Color(0xFF00FF88)); }); _saveUserData(); } void _markBookCompleted(String bookId) { setState(() { final bookIndex = userData["books"].indexWhere((b) => b["id"] == bookId); if (bookIndex != -1) { final book = userData["books"][bookIndex]; if (!book["completed"]) { book["completed"] = true; book["completion_date"] = DateTime.now().toIso8601String(); userData["task_history"].add({ "date": DateTime.now().toIso8601String(), "xp": 20, "type": 'book', "description": "Completed book: ${book['title']}", }); _gainXP(20); _addAIMessage("You completed reading '${book['title']}'! Earned 20 XP."); _checkReadingAchievements(); } else { _showMessage("This book is already marked as completed.", Colors.blue); } } else { _showMessage("Book not found.", Colors.orange); } }); _saveUserData(); } void _removeBook(String bookId) { setState(() { final initialLength = userData["books"].length; userData["books"].removeWhere((b) => b["id"] == bookId); if (userData["books"].length < initialLength) { _showMessage("Book removed.", Colors.blue); _checkReadingAchievements(); // Re-check achievements in case a completed book was removed } else { _showMessage("Book not found.", Colors.orange); } }); _saveUserData(); } void _checkReadingAchievements() { final completedBooksCount = (userData["books"] as List).where((b) => b["completed"]).length; // Helper to ensure achievement exists in user data void ensureAchievement(String id, String name, String icon) { if (!userData["achievements"].any((a) => a["id"] == id)) { userData["achievements"].add({"id": id, "name": name, "icon": icon, "earned": false}); } } ensureAchievement('first-book', 'First Book Read', '📚'); ensureAchievement('five-books', 'Bibliophile Initiate (5 Books)', '📖'); ensureAchievement('ten-books', 'Avid Reader (10 Books)', '🧠'); final firstBookAch = userData["achievements"].firstWhere((a) => a["id"] == 'first-book', orElse: () => null); if (firstBookAch != null && completedBooksCount >= 1 && !firstBookAch["earned"]) { firstBookAch["earned"] = true; _showMessage("Achievement Unlocked: First Book Read! 📚", const Color(0xFF00FF88)); } final fiveBooksAch = userData["achievements"].firstWhere((a) => a["id"] == 'five-books', orElse: () => null); if (fiveBooksAch != null && completedBooksCount >= 5 && !fiveBooksAch["earned"]) { fiveBooksAch["earned"] = true; _showMessage("Achievement Unlocked: Bibliophile Initiate! 📖", const Color(0xFF00FF88)); } final tenBooksAch = userData["achievements"].firstWhere((a) => a["id"] == 'ten-books', orElse: () => null); if (tenBooksAch != null && completedBooksCount >= 10 && !tenBooksAch["earned"]) { tenBooksAch["earned"] = true; _showMessage("Achievement Unlocked: Avid Reader! 🧠", const Color(0xFF00FF88)); } setState(() {}); // Re-render achievements section } // SECTION: Training Log Functions void _logTrainingSession() { final exercise = _trainingExerciseController.text.trim(); final repsStr = _trainingRepsController.text.trim(); final setsStr = _trainingSetsController.text.trim(); if (exercise.isEmpty || repsStr.isEmpty || setsStr.isEmpty) { _showMessage("Please fill all training fields.", Colors.orange); return; } int reps, sets; try { reps = int.parse(repsStr); sets = int.parse(setsStr); if (reps <= 0 || sets <= 0) { throw const FormatException("Values must be positive."); } } catch (_) { _showMessage("Reps and Sets must be valid positive numbers.", Colors.orange); return; } setState(() { final totalReps = reps * sets; final xpGained = _calculateTrainingXP(exercise, totalReps); userData["training_log"].add({ "id": const Uuid().v4(), "date": DateTime.now().toIso8601String(), "exercise": exercise, "reps": reps, "sets": sets, "xp_gained": xpGained, }); userData["task_history"].add({ "date": DateTime.now().toIso8601String(), "xp": xpGained, "type": 'training', "description": "Logged training: $exercise", }); _gainXP(xpGained); _addAIMessage("Logged $totalReps $exercise reps! You gained $xpGained XP."); // Check for 'first-training' achievement final firstTrainingAch = userData["achievements"].firstWhere((a) => a["id"] == 'first-training', orElse: () => null); if (firstTrainingAch != null && !firstTrainingAch["earned"]) { firstTrainingAch["earned"] = true; _showMessage("Achievement Unlocked: First Training Logged! 🏋️", const Color(0xFF00FF88)); } _trainingExerciseController.clear(); _trainingRepsController.clear(); _trainingSetsController.clear(); }); _saveUserData(); } void _removeTrainingSession(String sessionId) { setState(() { final initialLength = userData["training_log"].length; userData["training_log"].removeWhere((s) => s["id"] == sessionId); if (userData["training_log"].length < initialLength) { _showMessage("Training session removed.", Colors.blue); } else { _showMessage("Training session not found.", Colors.orange); } }); _saveUserData(); } int _calculateTrainingXP(String exercise, int totalReps) { double xpPerRep = 1; switch (exercise) { case 'Squats': xpPerRep = 1.2; break; case 'Push-ups': xpPerRep = 0.8; break; case 'Pull-ups': xpPerRep = 2; break; case 'Plank': xpPerRep = 0.1; // XP per second case 'Running': xpPerRep = 0.5; // XP per minute default: xpPerRep = 1.0; break; } return (totalReps * xpPerRep).round(); } // SECTION: Utility Functions String _getRandomResponse(List responses) { return responses[math.Random().nextInt(responses.length)]; } void _showMessage(String text, Color color) { setState(() { _messageBarText = text; _messageBarColor = color; }); Future.delayed(const Duration(seconds: 5), () { if (mounted) { setState(() { _messageBarText = null; }); } }); } void _addAIMessage(String message, {bool withButtons = false}) { setState(() { chatHistory.add({"sender": "ai", "message": message, "with_buttons": withButtons}); }); _scrollToBottom(); } // SECTION: UI Widgets @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Solo Leveling System - ${userData['username']}"), actions: [ IconButton( icon: const Icon(Icons.logout), onPressed: _logout, tooltip: "Logout", ), ], bottom: TabBar( controller: _tabController, isScrollable: true, labelColor: Colors.white, unselectedLabelColor: const Color(0xFFE0E0E0), indicatorColor: const Color(0xFF00BFFF), tabs: const [ Tab(text: "Quests"), Tab(text: "AI Assistant"), Tab(text: "Calendar"), Tab(text: "Habits"), Tab(text: "Library"), Tab(text: "Training"), Tab(text: "Analytics"), ], ), ), body: Column( children: [ Expanded( child: TabBarView( controller: _tabController, children: [ _buildQuestsTab(), _buildAIAssistantTab(), _buildCalendarTab(), _buildHabitsTab(), _buildLibraryTab(), _buildTrainingTab(), _buildAnalyticsTab(), ], ), ), if (_messageBarText != null) Container( padding: const EdgeInsets.all(8), color: _messageBarColor.withOpacity(0.2), alignment: Alignment.center, child: Text( _messageBarText!, style: TextStyle(color: _messageBarColor, fontWeight: FontWeight.bold), ), ), ], ), ); } Widget _buildStatCard(String title, String value, {double? progressValue}) { return Card( margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), child: Padding( padding: const EdgeInsets.all(8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle(fontSize: 14, color: Color(0xFFE0E0E0)), ), const SizedBox(height: 4), Text( value, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF00BFFF)), ), if (progressValue != null) Padding( padding: const EdgeInsets.only(top: 4.0), child: LinearProgressIndicator( value: progressValue / 100, backgroundColor: const Color(0xFF08111A), color: const Color(0xFF00BFFF), minHeight: 8, borderRadius: BorderRadius.circular(4), ), ), ], ), ), ); } Widget _buildQuestsTab() { final activeGoals = (userData["goals"] as List).where((g) => !g["completed"]).toList(); return Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Current Quests", style: Theme.of(context).textTheme.headlineSmall), Expanded( child: activeGoals.isEmpty ? const Center(child: Text("No active quests.", style: TextStyle(color: Colors.grey))) : ListView.builder( itemCount: activeGoals.length, itemBuilder: (context, index) { final goal = activeGoals[index]; return Card( margin: const EdgeInsets.symmetric(vertical: 4), child: ListTile( tileColor: Theme.of(context).cardColor, leading: Checkbox( value: false, // Always false as we only show incomplete onChanged: (bool? checked) { if (checked == true) { _completeGoal(goal["id"] as String); } }, checkColor: Colors.white, activeColor: const Color(0xFF00BFFF), ), title: Text(goal["text"] as String, style: const TextStyle(color: Color(0xFFE0E0E0))), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ Text("+${goal['xp']} XP", style: const TextStyle(color: Color(0xFF00BFFF), fontWeight: FontWeight.bold)), IconButton( icon: const Icon(Icons.delete_outline, color: Colors.redAccent), onPressed: () => _removeGoal(goal["id"] as String), tooltip: "Remove Quest", ), ], ), ), ); }, ), ), const SizedBox(height: 16), Text("Add New Quest", style: Theme.of(context).textTheme.headlineSmall), const SizedBox(height: 8), Row( children: [ Expanded( child: TextField( controller: _newGoalController, decoration: const InputDecoration( hintText: "Enter new quest...", ), ), ), const SizedBox(width: 8), ElevatedButton( onPressed: _addCustomGoal, child: const Text("Add"), ), ], ), const SizedBox(height: 16), ElevatedButton( onPressed: _generateSmartTask, child: const Text("Generate AI Quest"), ), ], ), ); } Widget _buildAIAssistantTab() { return Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ Expanded( child: ListView.builder( controller: _chatScrollController, itemCount: chatHistory.length, itemBuilder: (context, index) { final messageData = chatHistory[index]; if (messageData["sender"] == "ai_action") { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Align( alignment: Alignment.centerLeft, child: ElevatedButton( onPressed: () { if (messageData["command"] == "complete_generated_quest") { _gainXP(messageData["args"]["xp"] as int); userData["task_history"].add({ "date": DateTime.now().toIso8601String(), "xp": messageData["args"]["xp"], "type": 'generated_quest', "description": messageData['args']['description'], "id": messageData['args']['task_id'] }); _addAIMessage("System: Excellent work! You've earned ${messageData['args']['xp']} XP. ${_getRandomResponse(aiResponses['encouragement'])}"); // Remove the action button from chat history by creating a new list without it setState(() { chatHistory.removeAt(index); // Remove the action entry }); } }, child: Text(messageData["text"] as String? ?? "Mark Complete"), ), ), ); } final isUser = messageData["sender"] == "user"; return Align( alignment: isUser ? Alignment.centerRight : Alignment.centerLeft, child: Container( margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: isUser ? const Color(0xFF00C8FF).withOpacity(0.2) : const Color(0xFF0064C8).withOpacity(0.2), borderRadius: BorderRadius.circular(12), border: Border.all( color: isUser ? const Color(0xFF00FFFF) : const Color(0xFF00BFFF), width: 1, ), ), constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.7), child: _parseRichText(messageData["message"] as String), ), ); }, ), ), if (_isTyping) const Padding( padding: EdgeInsets.all(8.0), child: Align( alignment: Alignment.centerLeft, child: Text("AI is thinking...", style: TextStyle(fontStyle: FontStyle.italic, color: Color(0xFF00BFFF))), ), ), const SizedBox(height: 8), Row( children: [ Expanded( child: TextField( controller: _chatInputController, decoration: const InputDecoration( hintText: "Type your message...", ), onSubmitted: (_) => _sendChatMessage(), ), ), const SizedBox(width: 8), ElevatedButton( onPressed: _sendChatMessage, child: const Text("Send"), ), ], ), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 4, children: [ ElevatedButton(onPressed: () => _processUserMessage("Get Quest"), child: const Text("Get Quest")), ElevatedButton(onPressed: () => _processUserMessage("I need some advice"), child: const Text("Get Advice")), ElevatedButton(onPressed: () => _processUserMessage("How are my stats looking?"), child: const Text("Check Stats")), ElevatedButton(onPressed: () => _processUserMessage("Review my current quests"), child: const Text("Review Goals")), ], ), ], ), ); } // A simple HTML-like text parser for AI messages Widget _parseRichText(String text) { List spans = []; RegExp exp = RegExp( r'((.*?))|' r'((.*?))|' r'(
      (.*?)
    )|' r'(
  • (.*?)
  • )|' r'()' ); int lastMatchEnd = 0; for (Match m in exp.allMatches(text)) { if (m.start > lastMatchEnd) { spans.add(TextSpan(text: text.substring(lastMatchEnd, m.start), style: const TextStyle(color: Color(0xFFE0E0E0)))); } if (m.group(1) != null) { // Strong tag spans.add(TextSpan(text: m.group(2), style: const TextStyle(fontWeight: FontWeight.bold, color: Color(0xFFE0E0E0)))); } else if (m.group(3) != null) { // Em tag spans.add(TextSpan(text: m.group(4), style: const TextStyle(fontStyle: FontStyle.italic, color: Color(0xFFE0E0E0)))); } else if (m.group(5) != null) { // Ul tag (nested text) String listContent = m.group(6)!; RegExp liExp = RegExp(r'
  • (.*?)<\/li>'); for (Match liMatch in liExp.allMatches(listContent)) { spans.add(const TextSpan(text: '\n • ', style: TextStyle(color: Color(0xFFE0E0E0)))); spans.add(TextSpan(text: liMatch.group(1), style: const TextStyle(color: Color(0xFFE0E0E0)))); } } else if (m.group(7) != null) { // Li tag (standalone, less likely with ul parsing) spans.add(TextSpan(text: ' • ${m.group(8)}\n', style: const TextStyle(color: Color(0xFFE0E0E0)))); } else if (m.group(9) != null) { // AI Button tag String command = m.group(10)!; String buttonText = m.group(11)!; spans.add(WidgetSpan( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0), child: ElevatedButton( onPressed: () { _handleAIChatButtonCommand(command, buttonText); // After command executed, remove this specific button from the chat history setState(() { chatHistory.removeWhere((msg) => msg["sender"] == "ai" && (msg["message"] as String).contains("System: Which quest would you like advice on?"); } else if (command == 'focus_stat') { _generateStatTask('focus'); } else if (command == 'discipline_stat') { _generateStatTask('discipline'); } else if (command == 'knowledge_stat') { _generateStatTask('knowledge'); } } Widget _buildCalendarTab() { final now = DateTime.now(); final firstDayOfMonth = DateTime(now.year, now.month, 1); final daysInMonth = DateTime(now.year, now.month + 1, 0).day; final firstWeekday = firstDayOfMonth.weekday % 7; // Sunday = 0, Monday = 1... final userActivityLog = userData["activity_log"] as Map; return Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ Text("Activity Calendar - ${DateFormat('MMMM yyyy').format(now)}", style: Theme.of(context).textTheme.headlineSmall), const SizedBox(height: 16), Table( border: TableBorder.all(color: const Color(0xFF00BFFF).withOpacity(0.3)), children: [ TableRow( children: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] .map((day) => TableCell(child: Center(child: Text(day, style: const TextStyle(fontWeight: FontWeight.bold, color: Color(0xFF00BFFF)))))) .toList(), ), ...List.generate((firstWeekday + daysInMonth + 6) ~/ 7, (weekIndex) { return TableRow( children: List.generate(7, (dayIndex) { final day = (weekIndex * 7) + dayIndex - firstWeekday + 1; bool isCurrentMonthDay = day > 0 && day <= daysInMonth; bool isToday = isCurrentMonthDay && day == now.day && now.month == firstDayOfMonth.month && now.year == firstDayOfMonth.year; String dateKey = ''; bool hasActivity = false; if (isCurrentMonthDay) { final currentDayDate = DateTime(now.year, now.month, day); dateKey = DateFormat('yyyy-MM-dd').format(currentDayDate); hasActivity = (userActivityLog[dateKey] as int? ?? 0) > 0; } return TableCell( child: Container( alignment: Alignment.center, padding: const EdgeInsets.all(8.0), color: isToday ? const Color(0xFF00BFFF).withOpacity(0.2) : Colors.transparent, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( isCurrentMonthDay ? day.toString() : '', style: TextStyle( color: isCurrentMonthDay ? (hasActivity ? const Color(0xFF00FF88) : const Color(0xFFE0E0E0)) : Colors.grey.withOpacity(0.5), fontWeight: isToday ? FontWeight.bold : FontWeight.normal, ), ), if (hasActivity) Container( width: 6, height: 6, decoration: BoxDecoration( color: const Color(0xFF00BFFF), borderRadius: BorderRadius.circular(3), ), ) ], ), ), ); }), ); }), ], ), const SizedBox(height: 16), ElevatedButton( onPressed: _logActivityToday, child: const Text("Log Activity for Today"), ), ], ), ); } Widget _buildHabitsTab() { final habits = userData["habits"] as List; return Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Habit Tracker", style: Theme.of(context).textTheme.headlineSmall), Expanded( child: habits.isEmpty ? const Center(child: Text("No habits added yet.", style: TextStyle(color: Colors.grey))) : ListView.builder( itemCount: habits.length, itemBuilder: (context, index) { final habit = habits[index]; final isCompletedToday = (habit["completions"] as Map? ?? {})[DateFormat('yyyy-MM-dd').format(DateTime.now())] == true; return Card( margin: const EdgeInsets.symmetric(vertical: 4), child: ListTile( tileColor: Theme.of(context).cardColor, leading: Checkbox( value: isCompletedToday, onChanged: isCompletedToday ? null : (bool? checked) { if (checked == true) { _completeHabit(habit["id"] as String); } }, checkColor: Colors.white, activeColor: const Color(0xFF00BFFF), ), title: Text( habit["name"] as String + (isCompletedToday ? " (Done!)" : ""), style: TextStyle( color: isCompletedToday ? const Color(0xFF00FF88) : const Color(0xFFE0E0E0), decoration: isCompletedToday ? TextDecoration.lineThrough : TextDecoration.none, ), ), subtitle: Text( "Started: ${DateFormat('MMM d, yyyy').format(DateFormat('yyyy-MM-dd').parse(habit["start_date"]))} | " "Current Streak: ${habit['streak']} day(s) | " "Total Completed: ${habit['total_days_completed']} day(s)", style: const TextStyle(color: Colors.grey), ), trailing: IconButton( icon: const Icon(Icons.delete_outline, color: Colors.redAccent), onPressed: () => _removeHabit(habit["id"] as String), tooltip: "Remove Habit", ), ), ); }, ), ), const SizedBox(height: 16), Text("Add New Habit", style: Theme.of(context).textTheme.headlineSmall), const SizedBox(height: 8), Row( children: [ Expanded( child: TextField( controller: _newHabitNameController, decoration: const InputDecoration( hintText: "Enter new habit...", ), ), ), const SizedBox(width: 8), SizedBox( width: 120, // Fixed width for date input child: TextField( controller: _newHabitStartDateController, readOnly: true, decoration: const InputDecoration( hintText: "YYYY-MM-DD", ), onTap: () async { DateTime? pickedDate = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2101), builder: (context, child) { return Theme( data: Theme.of(context).copyWith( colorScheme: const ColorScheme.dark( primary: Color(0xFF00BFFF), // Header background color onPrimary: Colors.white, // Header text color surface: Color(0xFF08111A), // Calendar background color onSurface: Color(0xFFE0E0E0), // Calendar text color ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( foregroundColor: const Color(0xFF00BFFF), // Button text color ), ), ), child: child!, ); }, ); if (pickedDate != null) { setState(() { _newHabitStartDateController.text = DateFormat('yyyy-MM-dd').format(pickedDate); }); } }, ), ), const SizedBox(width: 8), ElevatedButton( onPressed: _addHabit, child: const Text("Add Habit"), ), ], ), ], ), ); } Widget _buildLibraryTab() { final books = userData["books"] as List; final sortedBooks = List>.from(books); sortedBooks.sort((a, b) { if (a["completed"] == b["completed"]) { return (a["title"] as String).compareTo(b["title"] as String); } return a["completed"] ? 1 : -1; // Uncompleted books first }); return Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("My Library", style: Theme.of(context).textTheme.headlineSmall), Expanded( child: sortedBooks.isEmpty ? const Center(child: Text("No books in your library.", style: TextStyle(color: Colors.grey))) : ListView.builder( itemCount: sortedBooks.length, itemBuilder: (context, index) { final book = sortedBooks[index]; return Card( margin: const EdgeInsets.symmetric(vertical: 4), child: ListTile( tileColor: Theme.of(context).cardColor, leading: Checkbox( value: book["completed"] as bool, onChanged: book["completed"] ? null : (bool? checked) { if (checked == true) { _markBookCompleted(book["id"] as String); } }, checkColor: Colors.white, activeColor: const Color(0xFF00BFFF), ), title: Text( book["title"] as String, style: TextStyle( color: book["completed"] ? const Color(0xFF00FF88) : const Color(0xFFE0E0E0), decoration: book["completed"] ? TextDecoration.lineThrough : TextDecoration.none, ), ), subtitle: Text( book["author"] != null ? "by ${book['author']}${book['completed'] ? ' (Completed: ${DateFormat('MMM d, yyyy').format(DateTime.parse(book['completion_date']))})' : ''}" : (book['completed'] ? 'Completed: ${DateFormat('MMM d, yyyy').format(DateTime.parse(book['completion_date']))}' : ''), style: const TextStyle(color: Colors.grey), ), trailing: IconButton( icon: const Icon(Icons.delete_outline, color: Colors.redAccent), onPressed: () => _removeBook(book["id"] as String), tooltip: "Remove Book", ), ), ); }, ), ), const SizedBox(height: 16), Text("Add New Book", style: Theme.of(context).textTheme.headlineSmall), const SizedBox(height: 8), Row( children: [ Expanded( child: TextField( controller: _newBookTitleController, decoration: const InputDecoration(hintText: "Book Title"), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: _newBookAuthorController, decoration: const InputDecoration(hintText: "Author (optional)"), ), ), const SizedBox(width: 8), ElevatedButton( onPressed: _addBook, child: const Text("Add Book"), ), ], ), ], ), ); } Widget _buildTrainingTab() { final trainingLog = List>.from(userData["training_log"] as List? ?? []); trainingLog.sort((a, b) => (b["date"] as String).compareTo(a["date"] as String)); // Sort by date descending final List exerciseOptions = ["Squats", "Push-ups", "Pull-ups", "Plank", "Running", "Other"]; return Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Log New Training Session", style: Theme.of(context).textTheme.headlineSmall), const SizedBox(height: 8), DropdownButtonFormField( value: _trainingExerciseController.text.isEmpty || !exerciseOptions.contains(_trainingExerciseController.text) ? "Squats" : _trainingExerciseController.text, decoration: const InputDecoration(labelText: "Select Exercise"), items: exerciseOptions.map((String value) { return DropdownMenuItem( value: value, child: Text(value), ); }).toList(), onChanged: (String? newValue) { setState(() { _trainingExerciseController.text = newValue!; }); }, dropdownColor: Theme.of(context).cardColor, ), if (_trainingExerciseController.text == "Other") Padding( padding: const EdgeInsets.only(top: 8.0), child: TextField( controller: _trainingExerciseController, // Reuse controller for custom entry decoration: const InputDecoration(hintText: "Enter custom exercise"), ), ), const SizedBox(height: 8), Row( children: [ Expanded( child: TextField( controller: _trainingRepsController, keyboardType: TextInputType.number, decoration: const InputDecoration(hintText: "Reps"), ), ), const SizedBox(width: 8), Expanded( child: TextField( controller: _trainingSetsController, keyboardType: TextInputType.number, decoration: const InputDecoration(hintText: "Sets"), ), ), ], ), const SizedBox(height: 16), ElevatedButton( onPressed: _logTrainingSession, child: const Text("Log Training"), ), const SizedBox(height: 24), Text("Training History", style: Theme.of(context).textTheme.headlineSmall), Expanded( child: trainingLog.isEmpty ? const Center(child: Text("No training sessions logged.", style: TextStyle(color: Colors.grey))) : ListView.builder( itemCount: trainingLog.length, itemBuilder: (context, index) { final session = trainingLog[index]; return Card( margin: const EdgeInsets.symmetric(vertical: 4), child: ListTile( tileColor: Theme.of(context).cardColor, title: Text( "${session['exercise']} - ${session['reps']} Reps x ${session['sets']} Sets", style: const TextStyle(color: Color(0xFFE0E0E0)), ), subtitle: Text( DateFormat('MMM d, yyyy').format(DateTime.parse(session['date'])), style: const TextStyle(color: Colors.grey), ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ Text("+${session['xp_gained']} XP", style: const TextStyle(color: Color(0xFF00BFFF), fontWeight: FontWeight.bold)), IconButton( icon: const Icon(Icons.delete_outline, color: Colors.redAccent), onPressed: () => _removeTrainingSession(session["id"] as String), tooltip: "Remove Session", ), ], ), ), ); }, ), ), ], ), ); } Widget _buildAnalyticsTab() { final taskHistory = userData["task_history"] as List; final Map dailyXp = {}; for (var entry in taskHistory) { final dateKey = DateFormat('yyyy-MM-dd').format(DateTime.parse(entry["date"] as String)); dailyXp[dateKey] = (dailyXp[dateKey] ?? 0) + (entry["xp"] as int); } final sortedDates = dailyXp.keys.toList()..sort(); // Ensure all default achievements are present (important for app updates) for (var defaultAch in defaultUserData["achievements"]) { if (!userData["achievements"].any((a) => a["id"] == defaultAch["id"])) { userData["achievements"].add(defaultAch); } } final sortedAchievements = List>.from(userData["achievements"] as List); sortedAchievements.sort((a, b) { if (a["earned"] == b["earned"]) { return (a["name"] as String).compareTo(b["name"] as String); } return a["earned"] ? -1 : 1; // Earned first }); return Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Progress Analytics", style: Theme.of(context).textTheme.headlineSmall), const SizedBox(height: 8), Card( color: Theme.of(context).cardColor, margin: EdgeInsets.zero, child: Padding( padding: const EdgeInsets.all(12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("XP Gained Per Day (Simulated Chart Data):", style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 8), if (dailyXp.isEmpty) const Text("No XP data to display yet.", style: TextStyle(color: Colors.grey)) else ...sortedDates.map((date) => Text("$date: ${dailyXp[date]} XP")), const SizedBox(height: 8), const Text("(In a full UI, this would be a line graph.)", style: TextStyle(fontStyle: FontStyle.italic, color: Colors.grey)), ], ), ), ), const SizedBox(height: 24), Text("Achievements", style: Theme.of(context).textTheme.headlineSmall), const SizedBox(height: 8), Expanded( child: GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 8, mainAxisSpacing: 8, childAspectRatio: 0.8, // Adjust as needed for content ), itemCount: sortedAchievements.length, itemBuilder: (context, index) { final ach = sortedAchievements[index]; final isEarned = ach["earned"] as bool; return Card( color: Theme.of(context).cardColor.withOpacity(isEarned ? 1.0 : 0.5), child: Padding( padding: const EdgeInsets.all(8.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(ach["icon"] as String, style: const TextStyle(fontSize: 36)), const SizedBox(height: 4), Text( ach["name"] as String, textAlign: TextAlign.center, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: isEarned ? const Color(0xFFE0E0E0) : Colors.grey, ), ), const SizedBox(height: 4), Text( isEarned ? "Earned" : "Locked", style: TextStyle( fontSize: 10, fontStyle: FontStyle.italic, color: isEarned ? const Color(0xFF00FF88) : Colors.grey, ), ), ], ), ), ); }, ), ), ], ), ); } }