diff --git a/firka/ios/Runner.xcodeproj/project.pbxproj b/firka/ios/Runner.xcodeproj/project.pbxproj index 90c453a..e38aa9a 100644 --- a/firka/ios/Runner.xcodeproj/project.pbxproj +++ b/firka/ios/Runner.xcodeproj/project.pbxproj @@ -332,14 +332,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; @@ -390,14 +386,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; diff --git a/firka/lib/helpers/live_activity_service.dart b/firka/lib/helpers/live_activity_service.dart new file mode 100644 index 0000000..f3da347 --- /dev/null +++ b/firka/lib/helpers/live_activity_service.dart @@ -0,0 +1,186 @@ +import 'package:live_activities/live_activities.dart'; +import 'package:firka/helpers/api/model/timetable.dart'; +import 'package:firka/helpers/extensions.dart'; + +class LiveActivityService { + static const String _activityType = 'FirkaLessonActivity'; + static final LiveActivities _liveActivities = LiveActivities(); + + /// Initialize live activities + static Future initialize() async { + try { + await _liveActivities.init(); + } catch (e) { + print('Failed to initialize live activities: $e'); + } + } + + /// Create a live activity for the current/next lesson + static Future createLessonActivity({ + required Lesson currentLesson, + Lesson? nextLesson, + required DateTime now, + }) async { + try { + final activityId = await _liveActivities.createActivity( + _activityType, + _buildActivityData( + currentLesson: currentLesson, + nextLesson: nextLesson, + now: now, + ), + ); + + print('Created live activity with ID: $activityId'); + return activityId; + } catch (e) { + print('Failed to create live activity: $e'); + return null; + } + } + + /// Update an existing live activity + static Future updateLessonActivity({ + required String activityId, + required Lesson currentLesson, + Lesson? nextLesson, + required DateTime now, + }) async { + try { + await _liveActivities.updateActivity( + activityId, + _buildActivityData( + currentLesson: currentLesson, + nextLesson: nextLesson, + now: now, + ), + ); + + print('Updated live activity: $activityId'); + } catch (e) { + print('Failed to update live activity: $e'); + } + } + + /// End a live activity + static Future endActivity(String activityId) async { + try { + await _liveActivities.endActivity(activityId); + print('Ended live activity: $activityId'); + } catch (e) { + print('Failed to end live activity: $e'); + } + } + + /// Get all active live activities + static Future> getActiveActivities() async { + try { + return await _liveActivities.getAllActivitiesIds(); + } catch (e) { + print('Failed to get active activities: $e'); + return []; + } + } + + /// End all active activities + static Future endAllActivities() async { + try { + final activeIds = await getActiveActivities(); + for (final id in activeIds) { + await endActivity(id); + } + } catch (e) { + print('Failed to end all activities: $e'); + } + } + + /// Build activity data for the widget + static Map _buildActivityData({ + required Lesson currentLesson, + Lesson? nextLesson, + required DateTime now, + }) { + final currentProgress = _calculateLessonProgress(currentLesson, now); + final isLessonActive = now.isAfter(currentLesson.start) && now.isBefore(currentLesson.end); + + return { + 'currentLesson': { + 'subject': currentLesson.subject, + 'teacher': currentLesson.teacher, + 'classroom': currentLesson.classroom, + 'startTime': _formatTime(currentLesson.start), + 'endTime': _formatTime(currentLesson.end), + 'progress': currentProgress, + 'isActive': isLessonActive, + }, + if (nextLesson != null) + 'nextLesson': { + 'subject': nextLesson.subject, + 'teacher': nextLesson.teacher, + 'classroom': nextLesson.classroom, + 'startTime': _formatTime(nextLesson.start), + 'endTime': _formatTime(nextLesson.end), + }, + 'timestamp': now.millisecondsSinceEpoch, + }; + } + + /// Calculate lesson progress as a percentage (0.0 to 1.0) + static double _calculateLessonProgress(Lesson lesson, DateTime now) { + if (now.isBefore(lesson.start)) return 0.0; + if (now.isAfter(lesson.end)) return 1.0; + + final totalDuration = lesson.end.difference(lesson.start).inMilliseconds; + final elapsed = now.difference(lesson.start).inMilliseconds; + + return elapsed / totalDuration; + } + + /// Format time as HH:mm + static String _formatTime(DateTime dateTime) { + return '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}'; + } + + /// Check if live activities are available on this device + static Future areActivitiesEnabled() async { + try { + return await _liveActivities.areActivitiesEnabled(); + } catch (e) { + print('Failed to check if activities are enabled: $e'); + return false; + } + } + + /// Get the current lesson from a list of lessons + static Lesson? getCurrentLesson(List lessons, DateTime now) { + try { + return lessons.firstWhere( + (lesson) => now.isAfter(lesson.start) && now.isBefore(lesson.end), + ); + } catch (e) { + return null; + } + } + + /// Get the next lesson from a list of lessons + static Lesson? getNextLesson(List lessons, DateTime now) { + try { + final futureLessons = lessons + .where((lesson) => lesson.start.isAfter(now)) + .toList() + ..sort((a, b) => a.start.compareTo(b.start)); + + return futureLessons.isNotEmpty ? futureLessons.first : null; + } catch (e) { + return null; + } + } + + /// Get the upcoming lesson (current if active, otherwise next) + static Lesson? getUpcomingLesson(List lessons, DateTime now) { + final current = getCurrentLesson(lessons, now); + if (current != null) return current; + + return getNextLesson(lessons, now); + } +} diff --git a/firka/pubspec.yaml b/firka/pubspec.yaml index 117e608..ddff065 100644 --- a/firka/pubspec.yaml +++ b/firka/pubspec.yaml @@ -65,6 +65,7 @@ dependencies: flutter_staggered_grid_view: ^0.7.0 pull_to_refresh_flutter3: ^2.0.2 package_info_plus: ^8.3.1 + live_activities: ^2.4.1 dev_dependencies: flutter_test: