From 4d2307c98f31e75eec7e3fb904318c5d893a2446 Mon Sep 17 00:00:00 2001 From: saraayy <128136969+saraayy@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:38:45 +0200 Subject: [PATCH 1/5] Send calendar context when recording starts --- android/app/src/main/AndroidManifest.xml | 1 + lib/screens/landing_screen.dart | 26 ++++- lib/services/calendar_service.dart | 119 +++++++++++++++++++++++ lib/services/websocket_service.dart | 6 ++ pubspec.lock | 24 +++++ pubspec.yaml | 1 + 6 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 lib/services/calendar_service.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index af13917..445a5d6 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + { late final Lc3Decoder _decoder; late final WebsocketService _ws; late final AudioPipeline _audioPipeline; + late final CalendarService _calendarService; @override void initState() { @@ -39,6 +41,7 @@ class _LandingScreenState extends State { _manager = widget.manager ?? G1Manager(); _decoder = widget.decoder ?? Lc3Decoder(); _ws = widget.ws ?? WebsocketService(); + _calendarService = CalendarService(); _audioPipeline = widget.audioPipeline ?? AudioPipeline( _manager, @@ -106,10 +109,16 @@ class _LandingScreenState extends State { children: [ Row( children: [ - IconButton( - onPressed: () {}, - icon: const Icon(Icons.menu, color: Color(0xFF00239D)), - ), + SizedBox( + width: 96, + child: Align( + alignment: Alignment.centerLeft, + child: IconButton( + onPressed: () {}, + icon: const Icon(Icons.menu, color: Color(0xFF00239D)), + ), + ), + ), const Spacer(), Image.asset( 'assets/images/Elisa_logo_blue_RGB.png', @@ -166,6 +175,15 @@ class _LandingScreenState extends State { manager: _manager, onRecordToggle: () async { if (!_manager.transcription.isActive.value) { + final granted = await _calendarService.requestPermission(); + if (granted) { + final events = await _calendarService.getUpcomingEvents(); + final activeEvent = _calendarService.selectActiveContext(events); + if (activeEvent != null) { + final payload = _calendarService.buildCalendarPayload(activeEvent); + _ws.sendCalendarContext(payload); + } + } await _startTranscription(); } else { await _stopTranscription(); diff --git a/lib/services/calendar_service.dart b/lib/services/calendar_service.dart new file mode 100644 index 0000000..9a3aff7 --- /dev/null +++ b/lib/services/calendar_service.dart @@ -0,0 +1,119 @@ +import 'package:device_calendar/device_calendar.dart'; + + + + +class CalendarService { + final DeviceCalendarPlugin _calendarPlugin = DeviceCalendarPlugin(); + + //Requests calendar permission + Future requestPermission () async { + var permissionsGranted = await _calendarPlugin.requestPermissions(); + if (permissionsGranted.isSuccess && permissionsGranted.data == true) { + return true; + // Permission granted, you can now access the calendar + } else { + return false; + // Permission denied, handle accordingly + } + } + + //Searches for upcoming events in the next 7 days + Future > getUpcomingEvents() async { + var calendarResult = await _calendarPlugin.retrieveCalendars(); + if (calendarResult.isSuccess && calendarResult.data != null) { + List calendars = calendarResult.data!; + List events = []; + + DateTime startDate = DateTime.now(); + DateTime endDate = startDate.add(const Duration(days: 7)); + + + for (var calendar in calendars) { + var eventResult = await _calendarPlugin.retrieveEvents( + calendar.id!, + RetrieveEventsParams(startDate: startDate, endDate: endDate), + ); + + if (eventResult.isSuccess && eventResult.data != null) { + List calendarEvents = eventResult.data!; + for (var event in calendarEvents) { + if (event.start != null && event.end != null) { + events.add( + CalendarEventModel( + title: event.title ?? 'No Title', + description: event.description, + start: event.start!, + end: event.end!, + ), + ); + } + } + } + } + events.sort((a, b) => a.start.compareTo(b.start)); + return events; + } + return []; + } + + //Selects the active or upcoming event + CalendarEventModel? selectActiveContext(List events) { + DateTime now = DateTime.now(); + //Event is happening now + for (var event in events) { + if (event.start.isBefore(now) && event.end.isAfter(now) ) { + return event; + } + } + //Upcoming event + for (var event in events) { + if (event.start.isAfter(now)) { + return event; + } + } + //No active or upcoming events + return null; + } + + //Builds the payload to send to backend + Map buildCalendarPayload(CalendarEventModel? event) { + if (event == null) { + return { + "type": "calendar_context", + "data": { + "title": "General conversation", + "description": null, + "start": null, + "end": null + } + }; + } + return { + "type": "calendar_context", + "data": { + "title": event.title, + "description": event.description, + "start": event.start.toIso8601String(), + "end": event.end.toIso8601String() + } + }; + } + + +} + +// Model to represent calendar events in a simplified way for our application +class CalendarEventModel { + final String title; + final String? description; + final DateTime start; + final DateTime end; + + CalendarEventModel({ + required this.title, + required this.description, + required this.start, + required this.end, + }); +} \ No newline at end of file diff --git a/lib/services/websocket_service.dart b/lib/services/websocket_service.dart index a80017e..c9038f4 100644 --- a/lib/services/websocket_service.dart +++ b/lib/services/websocket_service.dart @@ -106,6 +106,12 @@ class WebsocketService { } } + void sendCalendarContext(Map payload) { + if (connected.value) { + _audioChannel?.sink.add(jsonEncode(payload)); + } + } + /// Tell the backend to stop expecting audio data. Future stopAudioStream() async { if (connected.value) { diff --git a/pubspec.lock b/pubspec.lock index 58e2349..ab477ec 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -89,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.11" + device_calendar: + dependency: "direct main" + description: + name: device_calendar + sha256: "683fb93ec302b6a65c0ce57df40ff9dcc2404f59c67a2f8b93e59318c8a0a225" + url: "https://pub.dev" + source: hosted + version: "4.3.3" even_realities_g1: dependency: "direct main" description: @@ -292,6 +300,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -332,6 +348,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.7" + timezone: + dependency: transitive + description: + name: timezone + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" + url: "https://pub.dev" + source: hosted + version: "0.9.4" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 930cc3a..3ba24f0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,6 +33,7 @@ dependencies: cupertino_icons: ^1.0.8 # Local path dependency + device_calendar: ^4.3.3 even_realities_g1: path: packages/even_realities_g1 flutter: From 6406bf2383f12c12b75caaf78e191ad0f086d276 Mon Sep 17 00:00:00 2001 From: saraayy <128136969+saraayy@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:49:46 +0200 Subject: [PATCH 2/5] Configure device calendar plugin and Cocoapods integration for ios --- ios/Flutter/Debug.xcconfig | 1 + ios/Flutter/Release.xcconfig | 1 + ios/Podfile | 43 +++++++ ios/Podfile.lock | 29 +++++ ios/Runner.xcodeproj/project.pbxproj | 121 +++++++++++++++++- .../contents.xcworkspacedata | 3 + ios/Runner/Info.plist | 10 +- 7 files changed, 201 insertions(+), 7 deletions(-) create mode 100644 ios/Podfile create mode 100644 ios/Podfile.lock diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..23eab87 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '14.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..fbeca2d --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,29 @@ +PODS: + - device_calendar (0.0.1): + - Flutter + - Flutter (1.0.0) + - flutter_blue_plus_darwin (0.0.2): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - device_calendar (from `.symlinks/plugins/device_calendar/ios`) + - Flutter (from `Flutter`) + - flutter_blue_plus_darwin (from `.symlinks/plugins/flutter_blue_plus_darwin/darwin`) + +EXTERNAL SOURCES: + device_calendar: + :path: ".symlinks/plugins/device_calendar/ios" + Flutter: + :path: Flutter + flutter_blue_plus_darwin: + :path: ".symlinks/plugins/flutter_blue_plus_darwin/darwin" + +SPEC CHECKSUMS: + device_calendar: b55b2c5406cfba45c95a59f9059156daee1f74ed + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3 + +PODFILE CHECKSUM: 8c4d30c2258325612f2b7276ac7aac1f617fcf63 + +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 1d1b77b..6413778 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 99F5015DBD6F6F67074A1B9D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9B72C4D29009EF8D1D1EEA7 /* Pods_Runner.framework */; }; + F8A02A1E8E6684C1D8450DF1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9C6A6BD5AE93A3E540561BF /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -45,9 +47,12 @@ 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 61CA3FFAF8421699CD486D19 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 807E81FEBDA1B3C207D2F02D /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 96D8DD3C040343A039F310FE /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -55,13 +60,27 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A66E4728E7B9DA7D3BF9212D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + A9B72C4D29009EF8D1D1EEA7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E805B90C1540E73EE0890817 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + E9C6A6BD5AE93A3E540561BF /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F06CDC837EF05FDD4CA99461 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 7A60E1A3672AB68E0E2A42D1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F8A02A1E8E6684C1D8450DF1 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 99F5015DBD6F6F67074A1B9D /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -94,6 +113,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + F0CFB2D7AEEBFE525929A1A9 /* Pods */, + CAAD90FF3B44AD28E7C94A7C /* Frameworks */, ); sourceTree = ""; }; @@ -121,6 +142,29 @@ path = Runner; sourceTree = ""; }; + CAAD90FF3B44AD28E7C94A7C /* Frameworks */ = { + isa = PBXGroup; + children = ( + A9B72C4D29009EF8D1D1EEA7 /* Pods_Runner.framework */, + E9C6A6BD5AE93A3E540561BF /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + F0CFB2D7AEEBFE525929A1A9 /* Pods */ = { + isa = PBXGroup; + children = ( + F06CDC837EF05FDD4CA99461 /* Pods-Runner.debug.xcconfig */, + A66E4728E7B9DA7D3BF9212D /* Pods-Runner.release.xcconfig */, + 61CA3FFAF8421699CD486D19 /* Pods-Runner.profile.xcconfig */, + 807E81FEBDA1B3C207D2F02D /* Pods-RunnerTests.debug.xcconfig */, + E805B90C1540E73EE0890817 /* Pods-RunnerTests.release.xcconfig */, + 96D8DD3C040343A039F310FE /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -128,8 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + FEF6B413DA9FC937656B660D /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + 7A60E1A3672AB68E0E2A42D1 /* Frameworks */, ); buildRules = ( ); @@ -145,12 +191,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + BEC52B047FD349E6B05F8651 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 62220B443F82F6667CD7939F /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -238,6 +286,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 62220B443F82F6667CD7939F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -253,6 +318,50 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + BEC52B047FD349E6B05F8651 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + FEF6B413DA9FC937656B660D /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -362,13 +471,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = F2VMBPUNG6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.front; + PRODUCT_BUNDLE_IDENTIFIER = "com.example.front-ai-smarties"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -378,6 +488,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 807E81FEBDA1B3C207D2F02D /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -395,6 +506,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = E805B90C1540E73EE0890817 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -410,6 +522,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 96D8DD3C040343A039F310FE /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -541,13 +654,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = F2VMBPUNG6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.front; + PRODUCT_BUNDLE_IDENTIFIER = "com.example.front-ai-smarties"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -563,13 +677,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = F2VMBPUNG6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.front; + PRODUCT_BUNDLE_IDENTIFIER = "com.example.front-ai-smarties"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 7788352..010caf8 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -24,6 +26,10 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + NSCalendarsFullAccessUsageDescription + Access most functions for calendar viewing. + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,9 +47,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - From d7cbd6eb4157fc5ac2d3f5eff9b739eea84650ba Mon Sep 17 00:00:00 2001 From: saraayy <128136969+saraayy@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:51:11 +0200 Subject: [PATCH 3/5] Add CalendarService unit tests --- test/services/calendar_service_test.dart | 83 ++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 test/services/calendar_service_test.dart diff --git a/test/services/calendar_service_test.dart b/test/services/calendar_service_test.dart new file mode 100644 index 0000000..6aec032 --- /dev/null +++ b/test/services/calendar_service_test.dart @@ -0,0 +1,83 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:front/services/calendar_service.dart'; + +void main() { + group('CalendarService.selectActiveContext', () { + final service = CalendarService(); + + test('returns null when event list is empty', () { + final result = service.selectActiveContext([]); + + expect(result, isNull); + }); + test('return active event when one is happening now', () { + final now = DateTime.now(); + final events = [CalendarEventModel( + title: 'Business meeting', + description: 'Discussing quarterly results', + start: now.subtract(const Duration(minutes: 30)), + end: now.add(const Duration(minutes: 30)) + ), + ]; + final result = service.selectActiveContext(events); + + expect(result, isNotNull); + expect(result!.title, 'Business meeting'); + }); + test('return upcoming event when no event is active', () { + final now = DateTime.now(); + final events = [ + CalendarEventModel( + title: 'Project deadline', + description: 'Submit final report', + start: now.add(const Duration(hours: 1)), + end: now.add(const Duration(hours: 2)) + ), + CalendarEventModel( + title: 'Later meeting', + description: 'Retrospective', + start: now.add(const Duration(hours: 3)), + end: now.add(const Duration(hours: 4)) + ), + ]; + final result = service.selectActiveContext(events); + + expect(result, isNotNull); + expect(result!.title, 'Project deadline'); + }); + }); + + group('CalendarService.buildCalendarPayload', () { + final service = CalendarService(); + + test('Build payload from active event', () { + final event = CalendarEventModel( + title: 'Business meeting', + description: 'Discussing quarterly results', + start: DateTime.parse('2025-06-01T10:00:00Z'), + end: DateTime.parse('2025-06-01T11:00:00Z') + ); + final payload = service.buildCalendarPayload(event); + + expect(payload['type'], 'calendar_context'); + expect(payload['data']['title'], 'Business meeting'); + expect(payload['data']['description'], 'Discussing quarterly results'); + expect(payload['data']['start'],event.start.toIso8601String()); + expect(payload['data']['end'], event.end.toIso8601String()); + }); + test('Build payload with null description', () { + final event = CalendarEventModel( + title: 'Quick meeting', + description: null, + start: DateTime.parse('2025-06-01T12:00:00Z'), + end: DateTime.parse('2025-06-01T12:30:00Z') + ); + final payload = service.buildCalendarPayload(event); + + expect(payload['data']['title'], 'Quick meeting'); + expect(payload['data']['description'], isNull); + }); + }); +} + + From f58f7f6e5874a8373d191b5daa4bd270f6bf1942 Mon Sep 17 00:00:00 2001 From: saraayy <128136969+saraayy@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:13:33 +0200 Subject: [PATCH 4/5] Apply dart format --- lib/screens/landing_screen.dart | 16 ++++-- lib/services/calendar_service.dart | 22 +++----- test/services/calendar_service_test.dart | 72 +++++++++++------------- 3 files changed, 51 insertions(+), 59 deletions(-) diff --git a/lib/screens/landing_screen.dart b/lib/screens/landing_screen.dart index 021af72..4435a7b 100644 --- a/lib/screens/landing_screen.dart +++ b/lib/screens/landing_screen.dart @@ -109,7 +109,7 @@ class _LandingScreenState extends State { children: [ Row( children: [ - SizedBox( + SizedBox( width: 96, child: Align( alignment: Alignment.centerLeft, @@ -118,7 +118,7 @@ class _LandingScreenState extends State { icon: const Icon(Icons.menu, color: Color(0xFF00239D)), ), ), - ), + ), const Spacer(), Image.asset( 'assets/images/Elisa_logo_blue_RGB.png', @@ -175,12 +175,16 @@ class _LandingScreenState extends State { manager: _manager, onRecordToggle: () async { if (!_manager.transcription.isActive.value) { - final granted = await _calendarService.requestPermission(); + final granted = + await _calendarService.requestPermission(); if (granted) { - final events = await _calendarService.getUpcomingEvents(); - final activeEvent = _calendarService.selectActiveContext(events); + final events = + await _calendarService.getUpcomingEvents(); + final activeEvent = + _calendarService.selectActiveContext(events); if (activeEvent != null) { - final payload = _calendarService.buildCalendarPayload(activeEvent); + final payload = _calendarService + .buildCalendarPayload(activeEvent); _ws.sendCalendarContext(payload); } } diff --git a/lib/services/calendar_service.dart b/lib/services/calendar_service.dart index 9a3aff7..2eabc15 100644 --- a/lib/services/calendar_service.dart +++ b/lib/services/calendar_service.dart @@ -1,13 +1,10 @@ import 'package:device_calendar/device_calendar.dart'; - - - class CalendarService { final DeviceCalendarPlugin _calendarPlugin = DeviceCalendarPlugin(); //Requests calendar permission - Future requestPermission () async { + Future requestPermission() async { var permissionsGranted = await _calendarPlugin.requestPermissions(); if (permissionsGranted.isSuccess && permissionsGranted.data == true) { return true; @@ -18,17 +15,16 @@ class CalendarService { } } - //Searches for upcoming events in the next 7 days - Future > getUpcomingEvents() async { + //Searches for upcoming events in the next 7 days + Future> getUpcomingEvents() async { var calendarResult = await _calendarPlugin.retrieveCalendars(); if (calendarResult.isSuccess && calendarResult.data != null) { List calendars = calendarResult.data!; List events = []; - + DateTime startDate = DateTime.now(); DateTime endDate = startDate.add(const Duration(days: 7)); - for (var calendar in calendars) { var eventResult = await _calendarPlugin.retrieveEvents( calendar.id!, @@ -48,13 +44,13 @@ class CalendarService { ), ); } - } + } } } events.sort((a, b) => a.start.compareTo(b.start)); return events; } - return []; + return []; } //Selects the active or upcoming event @@ -62,7 +58,7 @@ class CalendarService { DateTime now = DateTime.now(); //Event is happening now for (var event in events) { - if (event.start.isBefore(now) && event.end.isAfter(now) ) { + if (event.start.isBefore(now) && event.end.isAfter(now)) { return event; } } @@ -99,8 +95,6 @@ class CalendarService { } }; } - - } // Model to represent calendar events in a simplified way for our application @@ -116,4 +110,4 @@ class CalendarEventModel { required this.start, required this.end, }); -} \ No newline at end of file +} diff --git a/test/services/calendar_service_test.dart b/test/services/calendar_service_test.dart index 6aec032..3b6252f 100644 --- a/test/services/calendar_service_test.dart +++ b/test/services/calendar_service_test.dart @@ -12,38 +12,36 @@ void main() { }); test('return active event when one is happening now', () { final now = DateTime.now(); - final events = [CalendarEventModel( - title: 'Business meeting', - description: 'Discussing quarterly results', - start: now.subtract(const Duration(minutes: 30)), - end: now.add(const Duration(minutes: 30)) - ), - ]; - final result = service.selectActiveContext(events); + final events = [ + CalendarEventModel( + title: 'Business meeting', + description: 'Discussing quarterly results', + start: now.subtract(const Duration(minutes: 30)), + end: now.add(const Duration(minutes: 30))), + ]; + final result = service.selectActiveContext(events); - expect(result, isNotNull); - expect(result!.title, 'Business meeting'); + expect(result, isNotNull); + expect(result!.title, 'Business meeting'); }); test('return upcoming event when no event is active', () { final now = DateTime.now(); final events = [ - CalendarEventModel( - title: 'Project deadline', - description: 'Submit final report', - start: now.add(const Duration(hours: 1)), - end: now.add(const Duration(hours: 2)) - ), - CalendarEventModel( - title: 'Later meeting', - description: 'Retrospective', - start: now.add(const Duration(hours: 3)), - end: now.add(const Duration(hours: 4)) - ), - ]; - final result = service.selectActiveContext(events); + CalendarEventModel( + title: 'Project deadline', + description: 'Submit final report', + start: now.add(const Duration(hours: 1)), + end: now.add(const Duration(hours: 2))), + CalendarEventModel( + title: 'Later meeting', + description: 'Retrospective', + start: now.add(const Duration(hours: 3)), + end: now.add(const Duration(hours: 4))), + ]; + final result = service.selectActiveContext(events); - expect(result, isNotNull); - expect(result!.title, 'Project deadline'); + expect(result, isNotNull); + expect(result!.title, 'Project deadline'); }); }); @@ -52,26 +50,24 @@ void main() { test('Build payload from active event', () { final event = CalendarEventModel( - title: 'Business meeting', - description: 'Discussing quarterly results', - start: DateTime.parse('2025-06-01T10:00:00Z'), - end: DateTime.parse('2025-06-01T11:00:00Z') - ); + title: 'Business meeting', + description: 'Discussing quarterly results', + start: DateTime.parse('2025-06-01T10:00:00Z'), + end: DateTime.parse('2025-06-01T11:00:00Z')); final payload = service.buildCalendarPayload(event); expect(payload['type'], 'calendar_context'); expect(payload['data']['title'], 'Business meeting'); expect(payload['data']['description'], 'Discussing quarterly results'); - expect(payload['data']['start'],event.start.toIso8601String()); + expect(payload['data']['start'], event.start.toIso8601String()); expect(payload['data']['end'], event.end.toIso8601String()); }); test('Build payload with null description', () { final event = CalendarEventModel( - title: 'Quick meeting', - description: null, - start: DateTime.parse('2025-06-01T12:00:00Z'), - end: DateTime.parse('2025-06-01T12:30:00Z') - ); + title: 'Quick meeting', + description: null, + start: DateTime.parse('2025-06-01T12:00:00Z'), + end: DateTime.parse('2025-06-01T12:30:00Z')); final payload = service.buildCalendarPayload(event); expect(payload['data']['title'], 'Quick meeting'); @@ -79,5 +75,3 @@ void main() { }); }); } - - From 9d66271023f5a9026d69bea75b256560be5506c6 Mon Sep 17 00:00:00 2001 From: saraayy <128136969+saraayy@users.noreply.github.com> Date: Fri, 13 Mar 2026 11:03:31 +0200 Subject: [PATCH 5/5] Apply dart format --- lib/screens/landing_screen.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/screens/landing_screen.dart b/lib/screens/landing_screen.dart index 1b22dff..86b3ded 100644 --- a/lib/screens/landing_screen.dart +++ b/lib/screens/landing_screen.dart @@ -283,10 +283,10 @@ class _LandingScreenState extends State { final granted = await _calendarService.requestPermission(); if (granted) { - final events = - await _calendarService.getUpcomingEvents(); - final activeEvent = - _calendarService.selectActiveContext(events); + final events = await _calendarService + .getUpcomingEvents(); + final activeEvent = _calendarService + .selectActiveContext(events); if (activeEvent != null) { final payload = _calendarService .buildCalendarPayload(activeEvent);