From b77ca7513bccb726430430cd8db2a80f3cb94008 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 04:05:01 +0000 Subject: [PATCH] feat: Pivot project to Soulstice and implement Phase 2 Data Architecture This commit transitions the project from EduPlay to Soulstice and implements the core data architecture requirements for Phase 2. Key changes: - Updated branding in main.dart with earth-toned palette and custom typography (Playfair Display, Inter). - Defined data models for Task, Project, Habit, Resource, and Client. - Implemented SQLite persistence layer using sqflite with a clean DAL (repositories). - Added PreferencesService for local settings using shared_preferences. - Added ExportService for JSON-based data export/import. - Cleaned up legacy EduPlay files and updated tests to match new app structure. Co-authored-by: iQodeIT <162815903+iQodeIT@users.noreply.github.com> --- edu_play/lib/main.dart | 88 +++++++-- edu_play/lib/models/client_model.dart | 35 ++++ edu_play/lib/models/habit_model.dart | 37 ++++ edu_play/lib/models/lesson_model.dart | 12 -- edu_play/lib/models/project_model.dart | 35 ++++ edu_play/lib/models/quiz_model.dart | 14 -- edu_play/lib/models/resource_model.dart | 35 ++++ edu_play/lib/models/reward_model.dart | 14 -- edu_play/lib/models/task_model.dart | 55 ++++++ edu_play/lib/models/user_model.dart | 13 -- .../lib/screens/admin_dashboard_screen.dart | 15 -- edu_play/lib/screens/onboarding_screen.dart | 14 -- .../lib/screens/parent_dashboard_screen.dart | 15 -- .../lib/screens/student_dashboard_screen.dart | 15 -- .../lib/screens/teacher_dashboard_screen.dart | 15 -- .../lib/services/dal/client_repository.dart | 37 ++++ .../lib/services/dal/habit_repository.dart | 37 ++++ .../lib/services/dal/project_repository.dart | 50 +++++ .../lib/services/dal/resource_repository.dart | 41 +++++ .../lib/services/dal/task_repository.dart | 57 ++++++ edu_play/lib/services/database_helper.dart | 89 +++++++++ edu_play/lib/services/export_service.dart | 40 ++++ .../lib/services/preferences_service.dart | 37 ++++ .../Flutter/GeneratedPluginRegistrant.swift | 4 + edu_play/pubspec.lock | 174 +++++++++++++----- edu_play/pubspec.yaml | 91 ++------- edu_play/test/widget_test.dart | 29 +-- 27 files changed, 801 insertions(+), 297 deletions(-) create mode 100644 edu_play/lib/models/client_model.dart create mode 100644 edu_play/lib/models/habit_model.dart delete mode 100644 edu_play/lib/models/lesson_model.dart create mode 100644 edu_play/lib/models/project_model.dart delete mode 100644 edu_play/lib/models/quiz_model.dart create mode 100644 edu_play/lib/models/resource_model.dart delete mode 100644 edu_play/lib/models/reward_model.dart create mode 100644 edu_play/lib/models/task_model.dart delete mode 100644 edu_play/lib/models/user_model.dart delete mode 100644 edu_play/lib/screens/admin_dashboard_screen.dart delete mode 100644 edu_play/lib/screens/onboarding_screen.dart delete mode 100644 edu_play/lib/screens/parent_dashboard_screen.dart delete mode 100644 edu_play/lib/screens/student_dashboard_screen.dart delete mode 100644 edu_play/lib/screens/teacher_dashboard_screen.dart create mode 100644 edu_play/lib/services/dal/client_repository.dart create mode 100644 edu_play/lib/services/dal/habit_repository.dart create mode 100644 edu_play/lib/services/dal/project_repository.dart create mode 100644 edu_play/lib/services/dal/resource_repository.dart create mode 100644 edu_play/lib/services/dal/task_repository.dart create mode 100644 edu_play/lib/services/database_helper.dart create mode 100644 edu_play/lib/services/export_service.dart create mode 100644 edu_play/lib/services/preferences_service.dart diff --git a/edu_play/lib/main.dart b/edu_play/lib/main.dart index 5b51a56..d89e1cd 100644 --- a/edu_play/lib/main.dart +++ b/edu_play/lib/main.dart @@ -1,39 +1,87 @@ -import 'package:edu_play/screens/onboarding_screen.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; void main() { - runApp(const MyApp()); + runApp(const SoulsticeApp()); } -class MyApp extends StatelessWidget { - const MyApp({super.key}); +class SoulsticeApp extends StatelessWidget { + const SoulsticeApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( - title: 'EduPlay', + title: 'Soulstice', theme: ThemeData( - // Define the default brightness and colors. + useMaterial3: true, colorScheme: ColorScheme.fromSeed( - seedColor: const Color(0xFF3B82F6), // Primary: Sky Blue - primary: const Color(0xFF3B82F6), - secondary: const Color(0xFFFACC15), // Accent: Warm Yellow - error: const Color(0xFFF87171), // Secondary: Coral Red - // Other colors can be defined here as needed + seedColor: const Color(0xFF8A9A5B), // Sage Green + primary: const Color(0xFF8A9A5B), + secondary: const Color(0xFFC07A50), // Clay Orange + tertiary: const Color(0xFF483C32), // Taupe + surface: const Color(0xFFFDFBF7), // Neutral/Cream ), - - // Define the default font family. - textTheme: GoogleFonts.nunitoTextTheme( + textTheme: GoogleFonts.interTextTheme( Theme.of(context).textTheme, + ).copyWith( + displayLarge: GoogleFonts.playfairDisplay( + textStyle: Theme.of(context).textTheme.displayLarge, + fontWeight: FontWeight.bold, + ), + displayMedium: GoogleFonts.playfairDisplay( + textStyle: Theme.of(context).textTheme.displayMedium, + fontWeight: FontWeight.bold, + ), + displaySmall: GoogleFonts.playfairDisplay( + textStyle: Theme.of(context).textTheme.displaySmall, + fontWeight: FontWeight.bold, + ), + headlineLarge: GoogleFonts.playfairDisplay( + textStyle: Theme.of(context).textTheme.headlineLarge, + fontWeight: FontWeight.bold, + ), + headlineMedium: GoogleFonts.playfairDisplay( + textStyle: Theme.of(context).textTheme.headlineMedium, + fontWeight: FontWeight.bold, + ), + headlineSmall: GoogleFonts.playfairDisplay( + textStyle: Theme.of(context).textTheme.headlineSmall, + fontWeight: FontWeight.bold, + ), ), - - // Use Material 3 design. - useMaterial3: true, ), - home: const OnboardingScreen(), + home: const PlaceholderHomeScreen(), debugShowCheckedModeBanner: false, ); } -} \ No newline at end of file +} + +class PlaceholderHomeScreen extends StatelessWidget { + const PlaceholderHomeScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + 'Soulstice', + style: Theme.of(context).textTheme.headlineMedium, + ), + centerTitle: true, + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Welcome to Soulstice', + style: Theme.of(context).textTheme.headlineLarge, + ), + const SizedBox(height: 20), + const Text('Core Data Architecture Foundation is being built...'), + ], + ), + ), + ); + } +} diff --git a/edu_play/lib/models/client_model.dart b/edu_play/lib/models/client_model.dart new file mode 100644 index 0000000..2baeb30 --- /dev/null +++ b/edu_play/lib/models/client_model.dart @@ -0,0 +1,35 @@ +class Client { + final int? id; + final String name; + final String email; + final String phone; + final String company; + + Client({ + this.id, + required this.name, + this.email = '', + this.phone = '', + this.company = '', + }); + + Map toMap() { + return { + 'id': id, + 'name': name, + 'email': email, + 'phone': phone, + 'company': company, + }; + } + + factory Client.fromMap(Map map) { + return Client( + id: map['id'], + name: map['name'], + email: map['email'] ?? '', + phone: map['phone'] ?? '', + company: map['company'] ?? '', + ); + } +} diff --git a/edu_play/lib/models/habit_model.dart b/edu_play/lib/models/habit_model.dart new file mode 100644 index 0000000..4b0fcdf --- /dev/null +++ b/edu_play/lib/models/habit_model.dart @@ -0,0 +1,37 @@ +class Habit { + final int? id; + final String name; + final String frequency; // 'Daily', 'Weekly' + final int streak; + final DateTime? lastCompleted; + + Habit({ + this.id, + required this.name, + this.frequency = 'Daily', + this.streak = 0, + this.lastCompleted, + }); + + Map toMap() { + return { + 'id': id, + 'name': name, + 'frequency': frequency, + 'streak': streak, + 'lastCompleted': lastCompleted?.toIso8601String(), + }; + } + + factory Habit.fromMap(Map map) { + return Habit( + id: map['id'], + name: map['name'], + frequency: map['frequency'] ?? 'Daily', + streak: map['streak'] ?? 0, + lastCompleted: map['lastCompleted'] != null + ? DateTime.parse(map['lastCompleted']) + : null, + ); + } +} diff --git a/edu_play/lib/models/lesson_model.dart b/edu_play/lib/models/lesson_model.dart deleted file mode 100644 index c11f567..0000000 --- a/edu_play/lib/models/lesson_model.dart +++ /dev/null @@ -1,12 +0,0 @@ -// Represents a single lesson in a topic. -class Lesson { - final String id; - final String title; - final String content; - - Lesson({ - required this.id, - required this.title, - required this.content, - }); -} \ No newline at end of file diff --git a/edu_play/lib/models/project_model.dart b/edu_play/lib/models/project_model.dart new file mode 100644 index 0000000..a9a7ebe --- /dev/null +++ b/edu_play/lib/models/project_model.dart @@ -0,0 +1,35 @@ +class Project { + final int? id; + final String name; + final String description; + final double progress; + final String status; + + Project({ + this.id, + required this.name, + this.description = '', + this.progress = 0.0, + this.status = 'Active', + }); + + Map toMap() { + return { + 'id': id, + 'name': name, + 'description': description, + 'progress': progress, + 'status': status, + }; + } + + factory Project.fromMap(Map map) { + return Project( + id: map['id'], + name: map['name'], + description: map['description'] ?? '', + progress: (map['progress'] ?? 0.0).toDouble(), + status: map['status'] ?? 'Active', + ); + } +} diff --git a/edu_play/lib/models/quiz_model.dart b/edu_play/lib/models/quiz_model.dart deleted file mode 100644 index 7d89eb2..0000000 --- a/edu_play/lib/models/quiz_model.dart +++ /dev/null @@ -1,14 +0,0 @@ -// Represents a single quiz question. -class QuizItem { - final String id; - final String question; - final List options; - final String correctAnswer; - - QuizItem({ - required this.id, - required this.question, - required this.options, - required this.correctAnswer, - }); -} \ No newline at end of file diff --git a/edu_play/lib/models/resource_model.dart b/edu_play/lib/models/resource_model.dart new file mode 100644 index 0000000..77a2c85 --- /dev/null +++ b/edu_play/lib/models/resource_model.dart @@ -0,0 +1,35 @@ +class Resource { + final int? id; + final String title; + final String content; + final String type; // 'Note', 'Link', 'Document' + final int? projectId; + + Resource({ + this.id, + required this.title, + this.content = '', + this.type = 'Note', + this.projectId, + }); + + Map toMap() { + return { + 'id': id, + 'title': title, + 'content': content, + 'type': type, + 'projectId': projectId, + }; + } + + factory Resource.fromMap(Map map) { + return Resource( + id: map['id'], + title: map['title'], + content: map['content'] ?? '', + type: map['type'] ?? 'Note', + projectId: map['projectId'], + ); + } +} diff --git a/edu_play/lib/models/reward_model.dart b/edu_play/lib/models/reward_model.dart deleted file mode 100644 index 7fc5de8..0000000 --- a/edu_play/lib/models/reward_model.dart +++ /dev/null @@ -1,14 +0,0 @@ -// Represents a reward, such as a badge or avatar item. -class Reward { - final String id; - final String name; - final String description; - final String imageUrl; - - Reward({ - required this.id, - required this.name, - required this.description, - required this.imageUrl, - }); -} \ No newline at end of file diff --git a/edu_play/lib/models/task_model.dart b/edu_play/lib/models/task_model.dart new file mode 100644 index 0000000..28c90bc --- /dev/null +++ b/edu_play/lib/models/task_model.dart @@ -0,0 +1,55 @@ +class Task { + final int? id; + final String title; + final String description; + final bool isCompleted; + final DateTime? dueDate; + final String energyLevel; // 'High', 'Low' + final String category; // 'Today', 'Active', 'Incubator', 'Leads' + final int? projectId; + final int? clientId; + final DateTime? updatedAt; + + Task({ + this.id, + required this.title, + this.description = '', + this.isCompleted = false, + this.dueDate, + this.energyLevel = 'High', + this.category = 'Active', + this.projectId, + this.clientId, + this.updatedAt, + }); + + Map toMap() { + return { + 'id': id, + 'title': title, + 'description': description, + 'isCompleted': isCompleted ? 1 : 0, + 'dueDate': dueDate?.toIso8601String(), + 'energyLevel': energyLevel, + 'category': category, + 'projectId': projectId, + 'clientId': clientId, + 'updatedAt': updatedAt?.toIso8601String() ?? DateTime.now().toIso8601String(), + }; + } + + factory Task.fromMap(Map map) { + return Task( + id: map['id'], + title: map['title'], + description: map['description'] ?? '', + isCompleted: map['isCompleted'] == 1, + dueDate: map['dueDate'] != null ? DateTime.parse(map['dueDate']) : null, + energyLevel: map['energyLevel'] ?? 'High', + category: map['category'] ?? 'Active', + projectId: map['projectId'], + clientId: map['clientId'], + updatedAt: map['updatedAt'] != null ? DateTime.parse(map['updatedAt']) : null, + ); + } +} diff --git a/edu_play/lib/models/user_model.dart b/edu_play/lib/models/user_model.dart deleted file mode 100644 index 94a697a..0000000 --- a/edu_play/lib/models/user_model.dart +++ /dev/null @@ -1,13 +0,0 @@ -// Represents a user in the EduPlay app. -// This will be expanded to include properties for each user role. -class User { - final String id; - final String email; - final String role; // "Student", "Parent", "Teacher", "Admin" - - User({ - required this.id, - required this.email, - required this.role, - }); -} \ No newline at end of file diff --git a/edu_play/lib/screens/admin_dashboard_screen.dart b/edu_play/lib/screens/admin_dashboard_screen.dart deleted file mode 100644 index 657cf8f..0000000 --- a/edu_play/lib/screens/admin_dashboard_screen.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/material.dart'; - -class AdminDashboardScreen extends StatelessWidget { - const AdminDashboardScreen({super.key}); - - @override - Widget build(BuildContext context) { - return const Scaffold( - appBar: AppBar(title: Text('Admin Dashboard')), - body: Center( - child: Text('Admin Dashboard'), - ), - ); - } -} \ No newline at end of file diff --git a/edu_play/lib/screens/onboarding_screen.dart b/edu_play/lib/screens/onboarding_screen.dart deleted file mode 100644 index 02ded90..0000000 --- a/edu_play/lib/screens/onboarding_screen.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:flutter/material.dart'; - -class OnboardingScreen extends StatelessWidget { - const OnboardingScreen({super.key}); - - @override - Widget build(BuildContext context) { - return const Scaffold( - body: Center( - child: Text('Onboarding Screen'), - ), - ); - } -} \ No newline at end of file diff --git a/edu_play/lib/screens/parent_dashboard_screen.dart b/edu_play/lib/screens/parent_dashboard_screen.dart deleted file mode 100644 index 74e9ad9..0000000 --- a/edu_play/lib/screens/parent_dashboard_screen.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/material.dart'; - -class ParentDashboardScreen extends StatelessWidget { - const ParentDashboardScreen({super.key}); - - @override - Widget build(BuildContext context) { - return const Scaffold( - appBar: AppBar(title: Text('Parent Dashboard')), - body: Center( - child: Text('Parent Dashboard'), - ), - ); - } -} \ No newline at end of file diff --git a/edu_play/lib/screens/student_dashboard_screen.dart b/edu_play/lib/screens/student_dashboard_screen.dart deleted file mode 100644 index afc4bba..0000000 --- a/edu_play/lib/screens/student_dashboard_screen.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/material.dart'; - -class StudentDashboardScreen extends StatelessWidget { - const StudentDashboardScreen({super.key}); - - @override - Widget build(BuildContext context) { - return const Scaffold( - appBar: AppBar(title: Text('Student Dashboard')), - body: Center( - child: Text('Student Dashboard'), - ), - ); - } -} \ No newline at end of file diff --git a/edu_play/lib/screens/teacher_dashboard_screen.dart b/edu_play/lib/screens/teacher_dashboard_screen.dart deleted file mode 100644 index b80d396..0000000 --- a/edu_play/lib/screens/teacher_dashboard_screen.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/material.dart'; - -class TeacherDashboardScreen extends StatelessWidget { - const TeacherDashboardScreen({super.key}); - - @override - Widget build(BuildContext context) { - return const Scaffold( - appBar: AppBar(title: Text('Teacher Dashboard')), - body: Center( - child: Text('Teacher Dashboard'), - ), - ); - } -} \ No newline at end of file diff --git a/edu_play/lib/services/dal/client_repository.dart b/edu_play/lib/services/dal/client_repository.dart new file mode 100644 index 0000000..dbfdabe --- /dev/null +++ b/edu_play/lib/services/dal/client_repository.dart @@ -0,0 +1,37 @@ +import 'package:edu_play/models/client_model.dart'; +import 'package:edu_play/services/database_helper.dart'; +import 'package:sqflite/sqflite.dart'; + +class ClientRepository { + final DatabaseHelper _dbHelper = DatabaseHelper(); + + Future insert(Client client) async { + Database db = await _dbHelper.database; + return await db.insert('clients', client.toMap()); + } + + Future> getAll() async { + Database db = await _dbHelper.database; + List> maps = await db.query('clients'); + return maps.map((map) => Client.fromMap(map)).toList(); + } + + Future update(Client client) async { + Database db = await _dbHelper.database; + return await db.update( + 'clients', + client.toMap(), + where: 'id = ?', + whereArgs: [client.id], + ); + } + + Future delete(int id) async { + Database db = await _dbHelper.database; + return await db.delete( + 'clients', + where: 'id = ?', + whereArgs: [id], + ); + } +} diff --git a/edu_play/lib/services/dal/habit_repository.dart b/edu_play/lib/services/dal/habit_repository.dart new file mode 100644 index 0000000..f6c1d3a --- /dev/null +++ b/edu_play/lib/services/dal/habit_repository.dart @@ -0,0 +1,37 @@ +import 'package:edu_play/models/habit_model.dart'; +import 'package:edu_play/services/database_helper.dart'; +import 'package:sqflite/sqflite.dart'; + +class HabitRepository { + final DatabaseHelper _dbHelper = DatabaseHelper(); + + Future insert(Habit habit) async { + Database db = await _dbHelper.database; + return await db.insert('habits', habit.toMap()); + } + + Future> getAll() async { + Database db = await _dbHelper.database; + List> maps = await db.query('habits'); + return maps.map((map) => Habit.fromMap(map)).toList(); + } + + Future update(Habit habit) async { + Database db = await _dbHelper.database; + return await db.update( + 'habits', + habit.toMap(), + where: 'id = ?', + whereArgs: [habit.id], + ); + } + + Future delete(int id) async { + Database db = await _dbHelper.database; + return await db.delete( + 'habits', + where: 'id = ?', + whereArgs: [id], + ); + } +} diff --git a/edu_play/lib/services/dal/project_repository.dart b/edu_play/lib/services/dal/project_repository.dart new file mode 100644 index 0000000..b71a9af --- /dev/null +++ b/edu_play/lib/services/dal/project_repository.dart @@ -0,0 +1,50 @@ +import 'package:edu_play/models/project_model.dart'; +import 'package:edu_play/services/database_helper.dart'; +import 'package:sqflite/sqflite.dart'; + +class ProjectRepository { + final DatabaseHelper _dbHelper = DatabaseHelper(); + + Future insert(Project project) async { + Database db = await _dbHelper.database; + return await db.insert('projects', project.toMap()); + } + + Future> getAll() async { + Database db = await _dbHelper.database; + List> maps = await db.query('projects'); + return maps.map((map) => Project.fromMap(map)).toList(); + } + + Future getById(int id) async { + Database db = await _dbHelper.database; + List> maps = await db.query( + 'projects', + where: 'id = ?', + whereArgs: [id], + ); + if (maps.isNotEmpty) { + return Project.fromMap(maps.first); + } + return null; + } + + Future update(Project project) async { + Database db = await _dbHelper.database; + return await db.update( + 'projects', + project.toMap(), + where: 'id = ?', + whereArgs: [project.id], + ); + } + + Future delete(int id) async { + Database db = await _dbHelper.database; + return await db.delete( + 'projects', + where: 'id = ?', + whereArgs: [id], + ); + } +} diff --git a/edu_play/lib/services/dal/resource_repository.dart b/edu_play/lib/services/dal/resource_repository.dart new file mode 100644 index 0000000..c6e90d4 --- /dev/null +++ b/edu_play/lib/services/dal/resource_repository.dart @@ -0,0 +1,41 @@ +import 'package:edu_play/models/resource_model.dart'; +import 'package:edu_play/services/database_helper.dart'; +import 'package:sqflite/sqflite.dart'; + +class ResourceRepository { + final DatabaseHelper _dbHelper = DatabaseHelper(); + + Future insert(Resource resource) async { + Database db = await _dbHelper.database; + return await db.insert('resources', resource.toMap()); + } + + Future> getByProject(int projectId) async { + Database db = await _dbHelper.database; + List> maps = await db.query( + 'resources', + where: 'projectId = ?', + whereArgs: [projectId], + ); + return maps.map((map) => Resource.fromMap(map)).toList(); + } + + Future update(Resource resource) async { + Database db = await _dbHelper.database; + return await db.update( + 'resources', + resource.toMap(), + where: 'id = ?', + whereArgs: [resource.id], + ); + } + + Future delete(int id) async { + Database db = await _dbHelper.database; + return await db.delete( + 'resources', + where: 'id = ?', + whereArgs: [id], + ); + } +} diff --git a/edu_play/lib/services/dal/task_repository.dart b/edu_play/lib/services/dal/task_repository.dart new file mode 100644 index 0000000..fb26a22 --- /dev/null +++ b/edu_play/lib/services/dal/task_repository.dart @@ -0,0 +1,57 @@ +import 'package:edu_play/models/task_model.dart'; +import 'package:edu_play/services/database_helper.dart'; +import 'package:sqflite/sqflite.dart'; + +class TaskRepository { + final DatabaseHelper _dbHelper = DatabaseHelper(); + + Future insert(Task task) async { + Database db = await _dbHelper.database; + return await db.insert('tasks', task.toMap()); + } + + Future> getAll() async { + Database db = await _dbHelper.database; + List> maps = await db.query('tasks'); + return maps.map((map) => Task.fromMap(map)).toList(); + } + + Future> getByCategory(String category) async { + Database db = await _dbHelper.database; + List> maps = await db.query( + 'tasks', + where: 'category = ?', + whereArgs: [category], + ); + return maps.map((map) => Task.fromMap(map)).toList(); + } + + Future> getByProject(int projectId) async { + Database db = await _dbHelper.database; + List> maps = await db.query( + 'tasks', + where: 'projectId = ?', + whereArgs: [projectId], + ); + return maps.map((map) => Task.fromMap(map)).toList(); + } + + Future update(Task task) async { + Database db = await _dbHelper.database; + return await db.update( + 'tasks', + task.toMap(), + where: 'id = ?', + whereArgs: [task.id], + ); + } + + Future delete(int id) async { + Database db = await _dbHelper.database; + return await db.delete( + 'tasks', + where: 'id = ?', + whereArgs: [id], + ); + } +} diff --git a/edu_play/lib/services/database_helper.dart b/edu_play/lib/services/database_helper.dart new file mode 100644 index 0000000..a00113f --- /dev/null +++ b/edu_play/lib/services/database_helper.dart @@ -0,0 +1,89 @@ +import 'dart:async'; +import 'package:path/path.dart'; +import 'package:sqflite/sqflite.dart'; + +class DatabaseHelper { + static final DatabaseHelper _instance = DatabaseHelper._internal(); + static Database? _database; + + factory DatabaseHelper() => _instance; + + DatabaseHelper._internal(); + + Future get database async { + if (_database != null) return _database!; + _database = await _initDatabase(); + return _database!; + } + + Future _initDatabase() async { + String path = join(await getDatabasesPath(), 'soulstice.db'); + return await openDatabase( + path, + version: 1, + onCreate: _onCreate, + ); + } + + Future _onCreate(Database db, int version) async { + await db.execute(''' + CREATE TABLE projects ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + description TEXT, + progress REAL, + status TEXT + ) + '''); + + await db.execute(''' + CREATE TABLE clients ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + email TEXT, + phone TEXT, + company TEXT + ) + '''); + + await db.execute(''' + CREATE TABLE tasks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + description TEXT, + isCompleted INTEGER, + dueDate TEXT, + energyLevel TEXT, + category TEXT, + projectId INTEGER, + clientId INTEGER, + updatedAt TEXT, + FOREIGN KEY (projectId) REFERENCES projects (id) ON DELETE SET NULL, + FOREIGN KEY (clientId) REFERENCES clients (id) ON DELETE SET NULL + ) + '''); + + await db.execute(''' + CREATE TABLE habits ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + frequency TEXT, + streak INTEGER, + lastCompleted TEXT + ) + '''); + + await db.execute(''' + CREATE TABLE resources ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + content TEXT, + type TEXT, + projectId INTEGER, + FOREIGN KEY (projectId) REFERENCES projects (id) ON DELETE CASCADE + ) + '''); + } + + // Generic CRUD operations can be added here or in separate DAL classes +} diff --git a/edu_play/lib/services/export_service.dart b/edu_play/lib/services/export_service.dart new file mode 100644 index 0000000..326ab64 --- /dev/null +++ b/edu_play/lib/services/export_service.dart @@ -0,0 +1,40 @@ +import 'dart:convert'; +import 'package:edu_play/services/database_helper.dart'; +import 'package:sqflite/sqflite.dart'; + +class ExportService { + final DatabaseHelper _dbHelper = DatabaseHelper(); + + Future exportToJson() async { + Database db = await _dbHelper.database; + + Map>> allData = {}; + + List tables = ['projects', 'clients', 'tasks', 'habits', 'resources']; + + for (String table in tables) { + allData[table] = await db.query(table); + } + + return jsonEncode(allData); + } + + // Import would involve parsing JSON and inserting into DB + Future importFromJson(String jsonString) async { + Database db = await _dbHelper.database; + Map data = jsonDecode(jsonString); + + await db.transaction((txn) async { + for (String table in data.keys) { + List rows = data[table]; + for (var row in rows) { + await txn.insert( + table, + row as Map, + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + } + }); + } +} diff --git a/edu_play/lib/services/preferences_service.dart b/edu_play/lib/services/preferences_service.dart new file mode 100644 index 0000000..a3190f3 --- /dev/null +++ b/edu_play/lib/services/preferences_service.dart @@ -0,0 +1,37 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +class PreferencesService { + static const String _keyEnergyLevel = 'energy_level'; + static const String _keyThemeMode = 'theme_mode'; + static const String _keyOnboardingComplete = 'onboarding_complete'; + + Future setEnergyLevel(String level) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_keyEnergyLevel, level); + } + + Future getEnergyLevel() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString(_keyEnergyLevel) ?? 'High'; + } + + Future setThemeMode(String mode) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_keyThemeMode, mode); + } + + Future getThemeMode() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString(_keyThemeMode) ?? 'light'; + } + + Future setOnboardingComplete(bool complete) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(_keyOnboardingComplete, complete); + } + + Future isOnboardingComplete() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getBool(_keyOnboardingComplete) ?? false; + } +} diff --git a/edu_play/macos/Flutter/GeneratedPluginRegistrant.swift b/edu_play/macos/Flutter/GeneratedPluginRegistrant.swift index f9c2b8a..21c996b 100644 --- a/edu_play/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/edu_play/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,8 +7,12 @@ import Foundation import firebase_core import path_provider_foundation +import shared_preferences_foundation +import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/edu_play/pubspec.lock b/edu_play/pubspec.lock index fa6ef73..379af7f 100644 --- a/edu_play/pubspec.lock +++ b/edu_play/pubspec.lock @@ -17,14 +17,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" - change_app_package_name: - dependency: "direct dev" - description: - name: change_app_package_name - sha256: "8e43b754fe960426904d77ed4c62fa8c9834deaf6e293ae40963fa447482c4c5" - url: "https://pub.dev" - source: hosted - version: "1.5.0" characters: dependency: transitive description: @@ -97,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" firebase_core: dependency: "direct main" description: @@ -152,22 +152,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.3.2" - hive: - dependency: "direct main" - description: - name: hive - sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" - url: "https://pub.dev" - source: hosted - version: "2.2.3" - hive_flutter: - dependency: "direct main" - description: - name: hive_flutter - sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc - url: "https://pub.dev" - source: hosted - version: "1.1.0" http: dependency: transitive description: @@ -184,6 +168,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" leak_tracker: dependency: transitive description: @@ -236,10 +228,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -256,16 +248,8 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - onesignal_flutter: - dependency: "direct main" - description: - name: onesignal_flutter - sha256: b5bb43bf496ddb5e3975ba54c6477cc2d1fcd18fb3698f195d2e0bfd376ddafd - url: "https://pub.dev" - source: hosted - version: "5.3.4" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" @@ -273,7 +257,7 @@ packages: source: hosted version: "1.9.1" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" @@ -320,14 +304,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" - paystack_flutter_sdk: - dependency: "direct main" - description: - name: paystack_flutter_sdk - sha256: a8386bee7bed218517c580017a2f3112da1c0e298f3c559f4a3d3f1f7e233221 - url: "https://pub.dev" - source: hosted - version: "0.0.1-alpha.2" platform: dependency: transitive description: @@ -352,6 +328,62 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5+1" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f + url: "https://pub.dev" + source: hosted + version: "2.4.20" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter @@ -365,6 +397,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sqflite: + dependency: "direct main" + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 + url: "https://pub.dev" + source: hosted + version: "2.4.2+2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: @@ -389,6 +461,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" term_glyph: dependency: transitive description: @@ -401,10 +481,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" typed_data: dependency: transitive description: @@ -447,4 +527,4 @@ packages: version: "1.1.0" sdks: dart: ">=3.9.2 <4.0.0" - flutter: ">=3.29.0" + flutter: ">=3.35.0" diff --git a/edu_play/pubspec.yaml b/edu_play/pubspec.yaml index 9688a19..a5c0233 100644 --- a/edu_play/pubspec.yaml +++ b/edu_play/pubspec.yaml @@ -1,104 +1,35 @@ name: edu_play -description: "A new Flutter project." -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. +description: "Soulstice - A Productivity App" +publish_to: 'none' version: 1.0.0+1 environment: sdk: ^3.9.2 -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter # Core - firebase_core: ^3.3.0 # For Firebase integration - provider: ^6.1.2 # For state management - dio: ^5.5.0+1 # For network requests - google_fonts: ^6.2.1 # For custom fonts like Poppins & Nunito + firebase_core: ^3.3.0 + provider: ^6.1.2 + dio: ^5.5.0+1 + google_fonts: ^6.2.1 + intl: ^0.19.0 # Offline Storage - hive: ^2.2.3 - hive_flutter: ^1.1.0 - - # Payments & Notifications - paystack_flutter_sdk: ^0.0.1-alpha.2 # For Paystack payments - onesignal_flutter: ^5.2.2 # For push notifications + sqflite: ^2.3.3+1 + path_provider: ^2.1.3 + path: ^1.9.0 + shared_preferences: ^2.2.3 # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 dev_dependencies: flutter_test: sdk: flutter - change_app_package_name: ^1.1.0 - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. flutter_lints: ^5.0.0 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package diff --git a/edu_play/test/widget_test.dart b/edu_play/test/widget_test.dart index 635f718..d32eeae 100644 --- a/edu_play/test/widget_test.dart +++ b/edu_play/test/widget_test.dart @@ -1,30 +1,13 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - import 'package:edu_play/main.dart'; +import 'package:flutter_test/flutter_test.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { + testWidgets('Soulstice welcome message test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); + await tester.pumpWidget(const SoulsticeApp()); - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + // Verify that our welcome message is displayed. + expect(find.text('Welcome to Soulstice'), findsOneWidget); + expect(find.text('Core Data Architecture Foundation is being built...'), findsOneWidget); }); }