-
Notifications
You must be signed in to change notification settings - Fork 348
Release v2 of Resonate App with all recent changes #537
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
fixed greet contributor workflow.
Added input length validation and handled overflows througout the app
Changes to Color API usage and removal of deprecated code
Fix Multiple Room Creation on Button Click (#445)
fix:profilePicturePlaceHolder inconsistency
Add tags for genres in explore screen
fixes #380 streamline flutter development
…ta with audiotags
…I makes it obsolete and unneccesary
…r that appeared on deleting a room
Added Meilisearch Integration for search
Add support for 16KB Page Size requirements in Android
chore: Bump app version to 2.0.0+1 for v2 release
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds multiple GitHub issue/workflow configs; migrates Android build to Kotlin DSL with AGP/Gradle upgrades; removes embedded Firebase configs; updates iOS project settings; introduces full i18n (en/hi) with ARB and localization scaffolding; adds friends, direct-call, and room chat features; broad controller refactors (DI, localization); theme UI revamp; new docs and policies. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant A as App (UI)
participant FC as FriendsController
participant DB as Appwrite Databases
participant FN as Appwrite Functions
participant FCM as Firebase Messaging
participant CK as CallKit (FlutterCallkitIncoming)
participant LK as LiveKit
User->>A: Start friend call
A->>FC: startCall(caller, receiver, roomId, tokens)
FC->>DB: Create call document (status=waiting)
FC->>FN: Invoke startFriendCall (notify receiver)
FN-->>FCM: Send FCM (data.type=incoming_call)
FCM-->>A: onBackgroundMessage (receiver device)
A->>CK: Show incoming call UI
alt Receiver answers
A->>FC: onAnswerCall(extra)
FC->>DB: Update call status=connected
FC->>LK: Join LiveKit room
A-->>User: Navigate to FriendCallScreen
else Receiver declines
A->>FC: onDeclinedCall(extra)
FC->>DB: Update call status=declined
A-->>User: Show declined state
end
User->>A: End call
A->>FC: onEndedCall(extra)
FC->>DB: Update call status=ended
FC->>LK: Cleanup session
A->>CK: End all calls
sequenceDiagram
autonumber
actor User
participant UI as Room UI
participant RC as RoomChatController
participant DB as Appwrite Databases
participant RT as Appwrite Realtime
participant FN as Appwrite Functions
participant LN as Local Notifications
User->>UI: Open chat
UI->>RC: init(roomId/upcomingRoom)
RC->>DB: loadMessages(order by index)
RC->>RT: subscribe(database.documents)
User->>RC: sendMessage(content, [replyTo])
RC->>DB: create message document
opt Upcoming room
RC->>FN: sendMessageNotification
end
RT-->>RC: event(document.create/update)
RC->>DB: fetch replyTo (if any)
RC-->>UI: update messages list
RC->>LN: show in-app notification
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements internationalization (i18n) for the Resonate app, replacing hardcoded English strings with localized alternatives. This is a comprehensive localization update that enables multi-language support across the entire application.
- Introduces AppLocalizations system to support multiple languages (currently English and Hindi)
- Replaces hardcoded strings throughout the application with localized equivalents
- Updates UI components, error messages, and user-facing text to use the new localization system
Reviewed Changes
Copilot reviewed 136 out of 212 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/main.dart | Adds localization delegates and supported locales configuration |
| lib/views/screens/*.dart | Updates all screen widgets to use AppLocalizations for text content |
| lib/utils/utils.dart | Modifies utility functions to support localized button text |
| lib/models/*.dart | Updates model classes with improved structure and formatting |
| lib/themes/*.dart | Enhances theme system with better organization and icon support |
| lib/utils/constants.dart | Adds environment variable support and new service endpoints |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| await exploreStoryController.searchStories(value); | ||
| await exploreStoryController.searchUsers(value); | ||
| exploreStoryController.isSearching.value = false; |
Copilot
AI
Sep 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line adds user search functionality but lacks error handling. Consider wrapping this async call in a try-catch block to handle potential exceptions gracefully, similar to the existing story search pattern.
| await exploreStoryController.searchStories(value); | |
| await exploreStoryController.searchUsers(value); | |
| exploreStoryController.isSearching.value = false; | |
| try { | |
| await exploreStoryController.searchStories(value); | |
| await exploreStoryController.searchUsers(value); | |
| } catch (e) { | |
| // Optionally log the error or show a user-friendly message | |
| debugPrint('Error during search: $e'); | |
| } finally { | |
| exploreStoryController.isSearching.value = false; | |
| } |
| // Category Dropdown | ||
| DropdownButtonFormField<StoryCategory>( | ||
| value: selectedCategory, | ||
| initialValue: selectedCategory, |
Copilot
AI
Sep 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using initialValue instead of value in DropdownButtonFormField can cause state management issues. The initialValue property only sets the initial state and won't update when selectedCategory changes. Use value: selectedCategory instead to ensure proper reactive updates.
| initialValue: selectedCategory, | |
| value: selectedCategory, |
| gradient: LinearGradient( | ||
| colors: [ | ||
| Theme.of(context).colorScheme.secondary.withAlpha((255 * 0.8).round()), | ||
| Theme.of(context).colorScheme.secondary.withValues(alpha: 0.8), |
Copilot
AI
Sep 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The withValues(alpha: 0.8) method is being used instead of the deprecated withAlpha(). However, ensure this is compatible with your minimum Flutter version requirements, as withValues was introduced in Flutter 3.22. Consider adding version constraints if targeting older Flutter versions.
| ), | ||
| BoxShadow( | ||
| color: Colors.white.withAlpha((255 * 0.3).round()), | ||
| color: Colors.white.withValues(alpha: 0.3), |
Copilot
AI
Sep 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The withValues(alpha: 0.8) method is being used instead of the deprecated withAlpha(). However, ensure this is compatible with your minimum Flutter version requirements, as withValues was introduced in Flutter 3.22. Consider adding version constraints if targeting older Flutter versions.
| color: Colors.white.withValues(alpha: 0.3), | |
| color: Colors.white.withOpacity(0.3), |
|
|
||
| @override | ||
| void dispose() { | ||
| Get.delete<ChapterPlayerController>(); |
Copilot
AI
Sep 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling Get.delete<ChapterPlayerController>() in dispose without checking if the controller exists could cause issues. Consider using Get.deleteIfExists<ChapterPlayerController>() or wrapping in a try-catch to handle cases where the controller might not exist.
| Get.delete<ChapterPlayerController>(); | |
| Get.deleteIfExists<ChapterPlayerController>(); |
| FilePickerResult? result = await FilePicker.platform.pickFiles( | ||
| type: FileType.custom, | ||
| allowedExtensions: [ | ||
| 'wav', | ||
| 'aiff', | ||
| 'alac', | ||
| 'flac', | ||
| 'mp3', | ||
| 'aac', | ||
| 'wma', | ||
| 'ogg', | ||
| ], |
Copilot
AI
Sep 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded list of audio file extensions should be moved to a constants file to improve maintainability and allow for easier updates. Consider defining this as static const List<String> supportedAudioFormats in a constants file.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 70
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (12)
README.md (1)
10-19: Logos may not render; use raw GitHub asset URLs and add rel attributes.The
/ tags reference GitHub “blob” pages, which won’t render as images in README. Also, external links with target="_blank" should include rel="noopener noreferrer".
-<a href="https://appwrite.io" target="_blank"> +<a href="https://appwrite.io" target="_blank" rel="noopener noreferrer"> <picture> - <source media="(prefers-color-scheme: dark)" align="center" srcset="https://github.com/appwrite/website/blob/main/static/images/logos/appwrite.svg" + <source media="(prefers-color-scheme: dark)" align="center" srcset="https://raw.githubusercontent.com/appwrite/website/main/static/images/logos/appwrite.svg" alt="Appwrite Logo" width="200"> - <img alt="Appwrite Logo" align="center" src="https://github.com/appwrite/website/blob/main/static/images/logos/appwrite-light.svg" alt="Appwrite Logo" - width="200"> + <img alt="Appwrite Logo" align="center" src="https://raw.githubusercontent.com/appwrite/website/main/static/images/logos/appwrite-light.svg" width="200"> </picture> </a> ... -<a href="https://livekit.io" target="_blank"> +<a href="https://livekit.io" target="_blank" rel="noopener noreferrer"> <picture> <source media="(prefers-color-scheme: dark)" align="center" srcset="https://github.com/AKASHANGADII/Resonate/assets/81625153/87bb173f-d5b0-4386-b9ca-6e69cd53578f" alt="Livekit Logo" width="140" height="30"> <img alt="Livekit Logo" align="center" src="https://github.com/AKASHANGADII/Resonate/assets/81625153/1466de84-d00e-4db7-9b51-a99f3c1997ff" width="140" height="30"> </picture> </a>Also applies to: 21-27
lib/controllers/email_verify_controller.dart (1)
69-96: Return actual success fromsendOTP().Method always returns
true, masking failures.- return true; + return res.responseBody == '{"message":"mail sent"}';lib/controllers/network_controller.dart (1)
17-29: Leak/UX: unsubscribe stream and prevent duplicate dialogs; null-safe dialog closing
- Stream subscription isn’t stored/canceled → leak.
- Dialog can be opened multiple times.
Get.isDialogOpen!can NPE; use== true.Apply:
+import 'dart:async'; ... class NetworkController extends GetxController { - final _connectivity = InternetConnection(); + final _connectivity = InternetConnection(); + late final StreamSubscription<InternetStatus> _connSub; ... - _connectivity.onStatusChange.listen((status) { - if (status == InternetStatus.disconnected) { - Get.dialog( + _connSub = _connectivity.onStatusChange.listen((status) { + if (status == InternetStatus.disconnected) { + if (Get.isDialogOpen != true) { + Get.dialog( barrierColor: Colors.transparent, NoConnectionDialog(), barrierDismissible: false, - ); + ); + } } else { - if (Get.isDialogOpen!) { + if (Get.isDialogOpen == true) { Get.back(); } } }); } + + @override + void onClose() { + _connSub.cancel(); + super.onClose(); + }lib/controllers/livekit_controller.dart (2)
47-66: Retry loop leaks Room/listener on failures; remove no-op reset.
Each failed attempt creates a new Room/listener without disposing the previous ones. Also, Line 49 resets nothing.Apply:
Future<bool> connectToRoom() async { - // Reset attempts for a fresh connection - if (reconnectAttempts == 0) reconnectAttempts = 0; + // Attempts are managed by caller (handleDisconnection) and here on failures. while (reconnectAttempts < maxAttempts) { try { liveKitRoom = Room(); listener = liveKitRoom.createListener(); await liveKitRoom.connect( @@ log('Connected to room successfully'); return true; - } catch (error) { + } catch (error) { + // Dispose partially-initialized resources before retry + try { await listener.dispose(); } catch (_) {} + try { await liveKitRoom.dispose(); } catch (_) {} reconnectAttempts++; log( 'Connection attempt $reconnectAttempts/$maxAttempts failed: $error', );Also applies to: 70-89
37-45: onClose(): unawaited async IIFE and potential late-init exceptions.
Call disposals directly with guards; avoid spawning an unawaited async closure.Apply:
- void onClose() async { - reconnectTimer?.cancel(); - (() async { - liveKitRoom.removeListener(onRoomDidUpdate); - await listener.dispose(); - await liveKitRoom.dispose(); - })(); - super.onClose(); - } + void onClose() async { + reconnectTimer?.cancel(); + try { liveKitRoom.removeListener(onRoomDidUpdate); } catch (_) {} + try { await listener.dispose(); } catch (_) {} + try { await liveKitRoom.dispose(); } catch (_) {} + super.onClose(); + }Additionally, consider making these nullable to avoid LateInitializationError if connection never succeeded:
// outside diff (field declarations) Room? liveKitRoom; EventsListener<RoomEvent>? listener;lib/controllers/tabview_controller.dart (1)
35-50: Subscription leak: cancel inonClose, not an unusedonDispose.You start a stream subscription here but the controller never overrides
onClose;onDisposeisn’t a GetxController lifecycle hook, so it won’t run. This leaks the listener.Apply this outside-range patch to add a proper override and remove
onDispose:@override void onClose() { _linkSubscription?.cancel(); super.onClose(); }android/app/src/main/AndroidManifest.xml (1)
18-18: Disable cleartext traffic for release.
usesCleartextTraffic="true"will trigger Play warnings and weakens security. Gate it by build type or a Network Security Config.Apply this diff for production safety:
- android:usesCleartextTraffic="true"> + android:usesCleartextTraffic="false">Or use
android:networkSecurityConfig="@xml/network_security_config"and limit cleartext to debug hosts.lib/routes/app_pages.dart (1)
80-88: Duplicate resetPassword route — remove the second entry.Two identical
AppRoutes.resetPasswordpages will cause ambiguity and binding mismatch risk.Apply this diff to drop the duplicate (keep the first one at Lines 80–83):
- GetPage( - name: AppRoutes.resetPassword, - page: () => const ResetPasswordScreen(), - binding: ProfileBinding(), - ),lib/controllers/single_room_controller.dart (1)
94-103: Guard index lookup before mutation.
indexWheremay return -1 leading toRangeError.- int toBeUpdatedIndex = participants.indexWhere( + final toBeUpdatedIndex = participants.indexWhere( (p) => p.value.uid == payload["uid"], ); + if (toBeUpdatedIndex < 0) { + return; // participant not in list; avoid RangeError + }lib/controllers/onboarding_controller.dart (1)
191-201: Availability check treats any error as “available”.Only 404 (not found) should imply “available”; network/other errors should not.
Apply this diff:
- Future<bool> isUsernameAvailable(String username) async { - try { - await databases.getDocument( - databaseId: userDatabaseID, - collectionId: usernameCollectionID, - documentId: username, - ); - return false; - } catch (e) { - log(e.toString()); - return true; - } - } + Future<bool> isUsernameAvailable(String username) async { + try { + await databases.getDocument( + databaseId: userDatabaseID, + collectionId: usernameCollectionID, + documentId: username, + ); + return false; // exists + } on AppwriteException catch (e) { + if (e.code == 404) return true; // not found => available + log('isUsernameAvailable failed: ${e.code} ${e.message}'); + return false; // be conservative on errors + } catch (e) { + log(e.toString()); + return false; + } + }lib/controllers/edit_profile_controller.dart (1)
95-99: Don’t blindly pop routes; guard dialog dismissal.Calling Get.back() in finally can pop a screen if no dialog is open.
Apply:
} finally { - // Close the loading dialog - Get.back(); + // Close the loading dialog if present + if (Get.isDialogOpen ?? false) { + Get.back(); + } }Repeat the same guard in pickImageFromGallery():
} finally { - // Close the loading dialog - Get.back(); + if (Get.isDialogOpen ?? false) { + Get.back(); + } }Also applies to: 131-134
lib/controllers/auth_state_controller.dart (1)
88-104: Fix crash when notification payload is null or missing.
payload!will throw for notifications shown without a payload (e.g., regex miss).- void onDidReceiveNotificationResponse( - NotificationResponse notificationResponse, - ) async { - String name = notificationResponse.payload!; + void onDidReceiveNotificationResponse( + NotificationResponse notificationResponse, + ) async { + final name = notificationResponse.payload; + if (name == null || name.isEmpty) return; UpcomingRoomsController upcomingRoomsController = Get.find<UpcomingRoomsController>(); - int index = upcomingRoomsController.upcomingRooms.indexWhere( + final index = upcomingRoomsController.upcomingRooms.indexWhere( (upcomingRoom) => upcomingRoom.name == name, ); + if (index < 0) return;
| build: | ||
| runs-on: ubuntu-latest | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gate runs on closed PRs to only execute when merged.
As written, closing an unmerged PR will still run both jobs. Add job-level guards.
jobs:
build:
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.merged == true }}
runs-on: ubuntu-latest
@@
release:
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.merged == true }}
runs-on: ubuntu-latestAlso applies to: 57-62
🤖 Prompt for AI Agents
.github/workflows/build_and_deploy.yml around lines 14-16 (and similarly lines
57-62): the workflow currently runs the jobs when a PR is closed even if not
merged; add job-level guards to execute only when the pull request was merged by
adding an if condition that checks github.event.pull_request.merged == true
(e.g., set if: github.event_name == 'pull_request' &&
github.event.pull_request.merged == true or include the equivalent check for
your event types) on each job so the jobs run only for merged PRs.
| name: Update Project Structure | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - github-action-project-structure | ||
| - dev | ||
|
|
||
| jobs: | ||
| generate-structure: | ||
| runs-on: ubuntu-latest | ||
| steps: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grant write permission for git push.
Without permissions: contents: write, git push may fail depending on repo settings.
name: Update Project Structure
on:
push:
branches:
- github-action-project-structure
- dev
+
+permissions:
+ contents: write📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| name: Update Project Structure | |
| on: | |
| push: | |
| branches: | |
| - github-action-project-structure | |
| - dev | |
| jobs: | |
| generate-structure: | |
| runs-on: ubuntu-latest | |
| steps: | |
| name: Update Project Structure | |
| on: | |
| push: | |
| branches: | |
| - github-action-project-structure | |
| - dev | |
| permissions: | |
| contents: write | |
| jobs: | |
| generate-structure: | |
| runs-on: ubuntu-latest | |
| steps: |
🤖 Prompt for AI Agents
.github/workflows/update-project-structure.yml lines 1-12: the workflow lacks
repository write permissions so downstream git push steps can fail; add a
top-level permissions block with "contents: write" (properly indented at root
level of the YAML) so the GITHUB_TOKEN has push rights, e.g. insert a
permissions: section directly under the workflow name or before jobs and ensure
any steps using git push use the default GITHUB_TOKEN or a token with
appropriate scopes.
| app/google-services.json | ||
|
|
||
| /app/.cxx/ | ||
| keys/ No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ignore rule for keys/ is scoped to android/; add root ignore too.
If CI writes secrets to a top‑level keys/, android/.gitignore won’t cover it. Keep this line, but add keys/ to the root .gitignore (see suggested diff there).
🤖 Prompt for AI Agents
In android/.gitignore around lines 14 to 17, the existing ignore rule "keys/"
only applies to the android/ directory; add a corresponding "keys/" entry to the
repository root .gitignore so top-level secrets written by CI are ignored. Keep
the current android/.gitignore rule as-is and open or create the root .gitignore
(at repository root) and add a single line "keys/" (followed by a newline) to
ensure the top-level keys/ directory is ignored by git.
| plugins { | ||
| id("com.android.application") | ||
| id("com.google.gms.google-services") | ||
| id("kotlin-android") | ||
| id("dev.flutter.flutter-gradle-plugin") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard Google Services plugin (build fails if google-services.json is not present)
If Firebase/FCM is optional (per ONBOARDING.md), applying com.google.gms.google-services unconditionally will fail CI/local builds without google-services.json.
Suggested change in this file:
plugins {
id("com.android.application")
- id("com.google.gms.google-services")
id("kotlin-android")
id("dev.flutter.flutter-gradle-plugin")
}Then conditionally apply at the bottom of this file:
if (file("google-services.json").exists()) {
apply(plugin = "com.google.gms.google-services")
}🤖 Prompt for AI Agents
In android/app/build.gradle.kts around lines 4 to 9, the
com.google.gms.google-services plugin is applied unconditionally which breaks
builds when google-services.json is absent; remove
id("com.google.gms.google-services") from the plugins { } block and instead add
a conditional plugin application at the end of this file that checks if the
google-services.json file exists in the app directory and only then applies the
google-services plugin (apply(plugin = "com.google.gms.google-services")), so
CI/local builds succeed when Firebase/FCM is optional.
| buildTypes { | ||
| signingConfigs { | ||
| create("release") { | ||
| keyAlias = keystoreProperties["keyAlias"] as String | ||
| keyPassword = keystoreProperties["keyPassword"] as String | ||
| storeFile = keystoreProperties["storeFile"]?.let { file(it) } | ||
| storePassword = keystoreProperties["storePassword"] as String | ||
| } | ||
| } | ||
| release { | ||
| // TODO: Add your own signing config for the release build. | ||
| // Signing with the debug keys for now, so `flutter run --release` works. | ||
| signingConfig = signingConfigs.getByName("debug") | ||
| signingConfig = signingConfigs.getByName("release") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix: signingConfigs is misplaced and can crash when key.properties is absent
- signingConfigs must be defined under android {}, not inside buildTypes {}.
- Casting nullable keystoreProperties values to String will crash when key.properties is missing.
- Duplicate assignment to signingConfig (debug then release).
Apply within this hunk (remove the block here):
- buildTypes {
- signingConfigs {
- create("release") {
- keyAlias = keystoreProperties["keyAlias"] as String
- keyPassword = keystoreProperties["keyPassword"] as String
- storeFile = keystoreProperties["storeFile"]?.let { file(it) }
- storePassword = keystoreProperties["storePassword"] as String
- }
- }
- release {
- // TODO: Add your own signing config for the release build.
- // Signing with the debug keys for now, so `flutter run --release` works.
- signingConfig = signingConfigs.getByName("debug")
- signingConfig = signingConfigs.getByName("release")
- }
- }Add this block just above buildTypes {} (outside this hunk):
signingConfigs {
if (keystorePropertiesFile.exists()
&& keystoreProperties["keyAlias"] != null
&& keystoreProperties["keyPassword"] != null
&& keystoreProperties["storeFile"] != null
&& keystoreProperties["storePassword"] != null
) {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}
}
}
buildTypes {
release {
signingConfig = if (signingConfigs.findByName("release") != null)
signingConfigs.getByName("release")
else
signingConfigs.getByName("debug")
isMinifyEnabled = true
isShrinkResources = true
}
}🤖 Prompt for AI Agents
In android/app/build.gradle.kts around lines 47-61, the signingConfigs block is
incorrectly placed inside buildTypes, nullable keystoreProperties values are
force-cast which will crash if key.properties is absent, and signingConfig is
assigned twice; remove the current signingConfigs/create("release") block from
inside buildTypes and instead add a top-level signingConfigs block just above
buildTypes that first checks keystorePropertiesFile.exists() and that each
property (keyAlias, keyPassword, storeFile, storePassword) is non-null before
creating the "release" config, use safe casts when reading values and file(...)
for storeFile, and then in the buildTypes.release set signingConfig to
signingConfigs.getByName("release") if it exists (use findByName or a null
check) otherwise fall back to the "debug" signingConfig; also consolidate the
release block to remove the duplicate signingConfig assignment and set
isMinifyEnabled/isShrinkResources as needed.
| Get.put( | ||
| LiveKitController(liveKitUri: livekitUri, roomToken: roomToken), | ||
| permanent: true, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid GetX “already registered” crashes when rejoining rooms.
Calling Get.put(LiveKitController…, permanent: true) repeatedly will throw if the controller is already registered.
static Future<void> joinLiveKitRoom(
String livekitUri,
String roomToken,
) async {
- Get.put(
+ if (Get.isRegistered<LiveKitController>()) {
+ Get.delete<LiveKitController>(force: true);
+ }
+ Get.put(
LiveKitController(liveKitUri: livekitUri, roomToken: roomToken),
permanent: true,
);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Get.put( | |
| LiveKitController(liveKitUri: livekitUri, roomToken: roomToken), | |
| permanent: true, | |
| ); | |
| if (Get.isRegistered<LiveKitController>()) { | |
| Get.delete<LiveKitController>(force: true); | |
| } | |
| Get.put( | |
| LiveKitController(liveKitUri: livekitUri, roomToken: roomToken), | |
| permanent: true, | |
| ); |
🤖 Prompt for AI Agents
In lib/services/room_service.dart around lines 18 to 21, calling Get.put(...,
permanent: true) unconditionally causes GetX to throw when the LiveKitController
is already registered; change this to first check
Get.isRegistered<LiveKitController>() and only call Get.put when not registered,
otherwise retrieve the existing controller with Get.find<LiveKitController>()
and update or call a method on it to apply the new liveKitUri/roomToken so you
reuse the existing instance instead of re-registering.
| // Increment the totalParticipants Attribute | ||
| int newParticipantCount = roomDoc.data["totalParticipants"] - | ||
| int newParticipantCount = | ||
| roomDoc.data["totalParticipants"] - | ||
| participantDocsRef.documents.length + | ||
| 1; | ||
| await roomsController.databases.updateDocument( | ||
| databaseId: masterDatabaseId, | ||
| collectionId: roomsCollectionId, | ||
| documentId: roomId, | ||
| data: {"totalParticipants": newParticipantCount}); | ||
| databaseId: masterDatabaseId, | ||
| collectionId: roomsCollectionId, | ||
| documentId: roomId, | ||
| data: {"totalParticipants": newParticipantCount}, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Participant count update is race-prone; clamp and centralize the increment.
The read-modify-write can lose updates under concurrency and may go negative if duplicates > totalParticipants.
- Short term: clamp locally.
- Better: move increment/decrement into a backend endpoint (single writer) or use a DB function/queue.
- int newParticipantCount =
- roomDoc.data["totalParticipants"] -
- participantDocsRef.documents.length +
- 1;
+ final int current =
+ (roomDoc.data["totalParticipants"] as int?) ?? 0;
+ final int newParticipantCount =
+ (current - participantDocsRef.documents.length + 1).clamp(0, 1 << 31);Committable suggestion skipped: line range outside the PR's diff.
| String? livekitToken = await storage.read(key: "createdRoomAdminToken"); | ||
| await apiService.deleteRoom(roomId, livekitToken); | ||
| await apiService.deleteRoom(roomId, livekitToken!); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t force-unwrap livekitToken.
If the token is absent, this throws and leaves Appwrite docs undeleted.
- String? livekitToken = await storage.read(key: "createdRoomAdminToken");
- await apiService.deleteRoom(roomId, livekitToken!);
+ final livekitToken = await storage.read(key: "createdRoomAdminToken");
+ if (livekitToken == null) {
+ // Log and decide: either abort or proceed with Appwrite cleanup only.
+ // e.g., throw StateError('Missing admin token for room $roomId');
+ } else {
+ await apiService.deleteRoom(roomId, livekitToken);
+ }🤖 Prompt for AI Agents
In lib/services/room_service.dart around lines 125-127, avoid force-unwrapping
livekitToken which can throw and abort subsequent cleanup; instead null-check
the value and handle both cases: if livekitToken != null call
apiService.deleteRoom(roomId, livekitToken), otherwise log a warning/notice
about the missing token and proceed with deleting Appwrite docs (or call
deleteRoom with a null-safe overload if available); ensure the function does not
crash on missing token so Appwrite cleanup always runs.
| final _themeKey = 'theme'; | ||
| final _favoritesKey = 'favoriteThemes'; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove dead fields and stray comment
clean up: _themeKey, _favoritesKey, and the joke comment.
- final _themeKey = 'theme';
- final _favoritesKey = 'favoriteThemes';
@@
- //hahahhaAlso applies to: 18-18
🤖 Prompt for AI Agents
In lib/themes/theme_controller.dart around lines 12-14 (and line 18), remove the
unused private fields _themeKey and _favoritesKey and delete the stray joke
comment at line 18; also search the file for any remaining references to those
constants and remove or replace them with the correct implementation if they are
referenced elsewhere, then run analyzer/tests to ensure no unresolved
identifiers remain.
| return Column( | ||
| spacing: 8, | ||
| crossAxisAlignment: CrossAxisAlignment.start, | ||
| children: [ | ||
| Text(themeName, style: TextStyle(fontSize: 18)), | ||
| const SizedBox(width: 8), | ||
| Row( | ||
| mainAxisSize: MainAxisSize.min, | ||
| children: [ | ||
| colorDots(theme.primaryColor), | ||
| const SizedBox(width: 8), | ||
| colorDots(theme.secondaryColor, isBorder: true), | ||
| const SizedBox(width: 8), | ||
| colorDots(theme.onPrimaryColor), | ||
| ], | ||
| ), | ||
| ], | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Column has no spacing; and width-sized box in a Column is incorrect.
This won’t compile on stable Flutter, and the SizedBox(width: 8) inside a Column won’t add vertical space.
Apply this diff:
- return Column(
- spacing: 8,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(themeName, style: TextStyle(fontSize: 18)),
- const SizedBox(width: 8),
- Row(
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const TextStyle(fontSize: 18) is TextStyle; // placeholder to enforce const below
+ Text(themeName, style: const TextStyle(fontSize: 18)),
+ const SizedBox(height: 8),
+ Row(
mainAxisSize: MainAxisSize.min,
children: [
colorDots(theme.primaryColor),
const SizedBox(width: 8),
colorDots(theme.secondaryColor, isBorder: true),
const SizedBox(width: 8),
colorDots(theme.onPrimaryColor),
],
),
],
);And make size constant above:
- final double size = 10;
+ const double size = 10;Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In lib/themes/theme_tile_title.dart around lines 33 to 50, the Column usage is
incorrect: Column does not accept a spacing parameter and the SizedBox(width: 8)
inside the Column attempts horizontal spacing instead of vertical. Remove the
spacing property from the Column, replace the SizedBox(width: 8) between the
Text and Row with SizedBox(height: 8) to provide vertical spacing, keep the
SizedBox(width: 8) instances inside the Row for horizontal spacing, and define
the color dot size as a constant (e.g., const double size = ...) above for
reuse.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review continued from previous batch...
| customSnackbar( | ||
| AppLocalizations.of(Get.context!)!.updateCheckFailed, | ||
| AppLocalizations.of(Get.context!)!.updateCheckFailedMessage, | ||
| LogType.error, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid Get.context! in controller catch; it can be null and crash
Guard localization lookup; fall back to plain strings.
- customSnackbar(
- AppLocalizations.of(Get.context!)!.updateCheckFailed,
- AppLocalizations.of(Get.context!)!.updateCheckFailedMessage,
- LogType.error,
- );
+ final ctx = Get.context;
+ final l10n = ctx != null ? AppLocalizations.of(ctx) : null;
+ customSnackbar(
+ l10n?.updateCheckFailed ?? 'Update Check Failed',
+ l10n?.updateCheckFailedMessage ??
+ 'Could not check for updates. Please try again later.',
+ LogType.error,
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| customSnackbar( | |
| AppLocalizations.of(Get.context!)!.updateCheckFailed, | |
| AppLocalizations.of(Get.context!)!.updateCheckFailedMessage, | |
| LogType.error, | |
| ); | |
| final ctx = Get.context; | |
| final l10n = ctx != null ? AppLocalizations.of(ctx) : null; | |
| customSnackbar( | |
| l10n?.updateCheckFailed ?? 'Update Check Failed', | |
| l10n?.updateCheckFailedMessage ?? | |
| 'Could not check for updates. Please try again later.', | |
| LogType.error, | |
| ); |
🤖 Prompt for AI Agents
In lib/controllers/about_app_screen_controller.dart around lines 49 to 53, the
controller catch block uses Get.context! which can be null and crash; replace
the forced context usage with a null-aware lookup and plain-string fallbacks
(e.g. use AppLocalizations.of(Get.context?)?.updateCheckFailed ?? 'Update check
failed' and AppLocalizations.of(Get.context?)?.updateCheckFailedMessage ??
'Could not check for updates') so the snackbar still shows even when Get.context
is null; ensure no forced unwrapping (!) is used and pass the resolved strings
to customSnackbar.
| if (clearSettings) { | ||
| Upgrader.clearSavedSettings(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Confirm Upgrader.clearSavedSettings() async semantics
If it returns a Future in your Upgrader version, await it to avoid TOCTOU on first-run prompts; if not, keep as-is. Please verify.
🌐 Web query:
Is `Upgrader.clearSavedSettings()` (from the `upgrader` Flutter package) asynchronous and should it be awaited?
💡 Result:
Yes — Upgrader.clearSavedSettings() returns a Future and is asynchronous, so you should await it (for example in main() before runApp). [1][2]
Sources:
- Upgrader API (clearSavedSettings → Future). [1]
- upgrader example showing await Upgrader.clearSavedSettings() in main(). [2]
Await Upgrader.clearSavedSettings() — it's async
Upgrader.clearSavedSettings() returns Future; await it to avoid TOCTOU on first‑run prompts. Make the call await Upgrader.clearSavedSettings() and mark the enclosing function async (or perform/await the call in main() before runApp). File: lib/controllers/about_app_screen_controller.dart lines 69–71
🤖 Prompt for AI Agents
In lib/controllers/about_app_screen_controller.dart around lines 69–71, the call
to Upgrader.clearSavedSettings() is asynchronous and must be awaited to avoid
TOCTOU on first-run prompts; change the call to await
Upgrader.clearSavedSettings() and make the enclosing function async (or
move/await this call in main() before runApp) so the Future completes before
proceeding.
| } else if (Platform.isIOS) { | ||
| // The App Store URL of app | ||
| storeUrl = ''; | ||
| } else { | ||
| return UpdateActionResult.error; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iOS storeUrl is empty; avoid parsing empty URI
Return a clear result until the App Store link is available.
- } else if (Platform.isIOS) {
- // The App Store URL of app
- storeUrl = '';
+ } else if (Platform.isIOS) {
+ // TODO: set App Store URL when available
+ return UpdateActionResult.failed;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } else if (Platform.isIOS) { | |
| // The App Store URL of app | |
| storeUrl = ''; | |
| } else { | |
| return UpdateActionResult.error; | |
| } else if (Platform.isIOS) { | |
| // TODO: set App Store URL when available | |
| return UpdateActionResult.failed; | |
| } else { | |
| return UpdateActionResult.error; |
🤖 Prompt for AI Agents
In lib/controllers/about_app_screen_controller.dart around lines 112 to 116, the
iOS branch sets storeUrl = '' which leads to attempts to parse an empty URI;
change this to either assign the real App Store URL when available or
immediately return a clear result (e.g. UpdateActionResult.unavailable) instead
of setting an empty string. Also add an early check before any URI parsing to
return UpdateActionResult.unavailable (or the chosen sentinel) when storeUrl is
null/empty to prevent parsing errors.
| ratingTotal = userDataDoc.data["ratingTotal"].toDouble() ?? 5; | ||
| ratingCount = userDataDoc.data["ratingCount"] ?? 1; | ||
| followerDocuments = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Null-safe numeric parsing for ratingTotal and ratingCount.
userDataDoc.data["ratingTotal"].toDouble() will throw if null.
- ratingTotal = userDataDoc.data["ratingTotal"].toDouble() ?? 5;
- ratingCount = userDataDoc.data["ratingCount"] ?? 1;
+ final rt = userDataDoc.data["ratingTotal"];
+ ratingTotal = (rt is num ? rt.toDouble() : 5.0);
+ ratingCount = (userDataDoc.data["ratingCount"] as int?) ?? 1;Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In lib/controllers/auth_state_controller.dart around lines 215 to 217, the code
calls userDataDoc.data["ratingTotal"].toDouble() and assumes the field is
non-null and a num, which will throw if it's null or a non-numeric type; change
to a null-safe parse: read the raw value into a local, check for null, then if
it's a num call toDouble(), otherwise attempt double.tryParse(raw.toString())
and fall back to the default (5 for ratingTotal); do the same for ratingCount
but coerce to an integer (use int.tryParse or (num).toInt()) and fall back to 1;
replace the current direct calls with these guarded conversions so nulls and
unexpected types won't throw.
| if (removeImage) { | ||
| imageController.text = userProfileImagePlaceholderUrl; | ||
| imageController.text = ""; | ||
|
|
||
| // Update user profile picture URL in Database | ||
| await databases.updateDocument( | ||
| databaseId: userDatabaseID, | ||
| collectionId: usersCollectionID, | ||
| documentId: authStateController.uid!, | ||
| data: { | ||
| "profileImageUrl": imageController.text, | ||
| }, | ||
| data: {"profileImageUrl": imageController.text}, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing profile picture leaves orphaned file and stale profileImageID.
On removeImage, you only clear profileImageUrl. Delete the stored file and clear both fields.
Apply:
if (removeImage) {
- imageController.text = "";
-
- // Update user profile picture URL in Database
- await databases.updateDocument(
- databaseId: userDatabaseID,
- collectionId: usersCollectionID,
- documentId: authStateController.uid!,
- data: {"profileImageUrl": imageController.text},
- );
+ // Delete previous file if present
+ final oldId = authStateController.profileImageID;
+ if (oldId != null && oldId.isNotEmpty) {
+ try {
+ await storage.deleteFile(
+ bucketId: userProfileImageBucketId,
+ fileId: oldId,
+ );
+ } catch (e) {
+ log(e.toString());
+ }
+ }
+ // Clear both URL and ID in DB
+ await databases.updateDocument(
+ databaseId: userDatabaseID,
+ collectionId: usersCollectionID,
+ documentId: authStateController.uid!,
+ data: {"profileImageUrl": null, "profileImageID": null},
+ );
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (removeImage) { | |
| imageController.text = userProfileImagePlaceholderUrl; | |
| imageController.text = ""; | |
| // Update user profile picture URL in Database | |
| await databases.updateDocument( | |
| databaseId: userDatabaseID, | |
| collectionId: usersCollectionID, | |
| documentId: authStateController.uid!, | |
| data: { | |
| "profileImageUrl": imageController.text, | |
| }, | |
| data: {"profileImageUrl": imageController.text}, | |
| ); | |
| if (removeImage) { | |
| // Delete previous file if present | |
| final oldId = authStateController.profileImageID; | |
| if (oldId != null && oldId.isNotEmpty) { | |
| try { | |
| await storage.deleteFile( | |
| bucketId: userProfileImageBucketId, | |
| fileId: oldId, | |
| ); | |
| } catch (e) { | |
| log(e.toString()); | |
| } | |
| } | |
| // Clear both URL and ID in DB | |
| await databases.updateDocument( | |
| databaseId: userDatabaseID, | |
| collectionId: usersCollectionID, | |
| documentId: authStateController.uid!, | |
| data: {"profileImageUrl": null, "profileImageID": null}, | |
| ); | |
| } |
🤖 Prompt for AI Agents
In lib/controllers/edit_profile_controller.dart around lines 248–257, when
removeImage is true you only clear profileImageUrl locally which leaves the
stored file and the profileImageID in the DB orphaned; modify the flow to (1)
read the current profileImageID (if any), call the storage deletion API to
remove that file, (2) clear both profileImageUrl and profileImageID locally
(controllers/state), and (3) update the user document via
databases.updateDocument setting both "profileImageUrl" and "profileImageID" to
empty/null; also add basic error handling around the storage delete so failure
to delete doesn’t skip clearing DB fields.
| @override | ||
| String get checkForUpdates => 'अपडेट चेक करें'; | ||
|
|
||
| @override | ||
| String get updateNow => 'अभी अपडेट करें'; | ||
|
|
||
| @override | ||
| String get updateLater => 'बाद में'; | ||
|
|
||
| @override | ||
| String get updateSuccessful => 'अपडेट सफल'; | ||
|
|
||
| @override | ||
| String get updateSuccessfulMessage => 'रेज़ोनेट सफलतापूर्वक अपडेट हो गया है!'; | ||
|
|
||
| @override | ||
| String get updateCancelled => 'अपडेट रद्द किया गया'; | ||
|
|
||
| @override | ||
| String get updateCancelledMessage => 'अपडेट यूज़र द्वारा रद्द किया गया'; | ||
|
|
||
| @override | ||
| String get updateFailed => 'अपडेट फेल'; | ||
|
|
||
| @override | ||
| String get updateFailedMessage => | ||
| 'अपडेट फेल हो गया। कृपया Play Store से मैन्युअली अपडेट करने का प्रयास करें।'; | ||
|
|
||
| @override | ||
| String get updateError => 'अपडेट एरर'; | ||
|
|
||
| @override | ||
| String get updateErrorMessage => | ||
| 'अपडेट करने में कोई समस्या आई। कृपया फिर से प्रयास करें।'; | ||
|
|
||
| @override | ||
| String get platformNotSupported => 'प्लेटफॉर्म सपोर्टेड नहीं'; | ||
|
|
||
| @override | ||
| String get platformNotSupportedMessage => | ||
| 'अपडेट चेक करना केवल Android डिवाइस पर उपलब्ध है'; | ||
|
|
||
| @override | ||
| String get updateCheckFailed => 'अपडेट चेक फेल'; | ||
|
|
||
| @override | ||
| String get updateCheckFailedMessage => | ||
| 'अपडेट चेक नहीं हो सका। कृपया बाद में प्रयास करें।'; | ||
|
|
||
| @override | ||
| String get upToDateTitle => 'आप अप टू डेट हैं!'; | ||
|
|
||
| @override | ||
| String get upToDateMessage => | ||
| 'आप रेज़ोनेट का लेटेस्ट वर्शन इस्तेमाल कर रहे हैं'; | ||
|
|
||
| @override | ||
| String get updateAvailableTitle => 'अपडेट उपलब्ध है!'; | ||
|
|
||
| @override | ||
| String get updateAvailableMessage => | ||
| 'रेज़ोनेट का नया वर्शन Play Store पर उपलब्ध है'; | ||
|
|
||
| @override | ||
| String get updateFeaturesImprovement => | ||
| 'नवीनतम सुविधाएं और सुधार प्राप्त करें!'; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate overrides — this class redefines the same getters twice.
The block starting here repeats keys previously overridden (e.g., checkForUpdates, updateNow, …). Remove duplicates; one override per abstract member.
- @override
- String get checkForUpdates => 'अपडेट चेक करें';
- ...
- String get updateFeaturesImprovement =>
- 'नवीनतम सुविधाएं और सुधार प्राप्त करें!';
+ // (duplicate overrides removed)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @override | |
| String get checkForUpdates => 'अपडेट चेक करें'; | |
| @override | |
| String get updateNow => 'अभी अपडेट करें'; | |
| @override | |
| String get updateLater => 'बाद में'; | |
| @override | |
| String get updateSuccessful => 'अपडेट सफल'; | |
| @override | |
| String get updateSuccessfulMessage => 'रेज़ोनेट सफलतापूर्वक अपडेट हो गया है!'; | |
| @override | |
| String get updateCancelled => 'अपडेट रद्द किया गया'; | |
| @override | |
| String get updateCancelledMessage => 'अपडेट यूज़र द्वारा रद्द किया गया'; | |
| @override | |
| String get updateFailed => 'अपडेट फेल'; | |
| @override | |
| String get updateFailedMessage => | |
| 'अपडेट फेल हो गया। कृपया Play Store से मैन्युअली अपडेट करने का प्रयास करें।'; | |
| @override | |
| String get updateError => 'अपडेट एरर'; | |
| @override | |
| String get updateErrorMessage => | |
| 'अपडेट करने में कोई समस्या आई। कृपया फिर से प्रयास करें।'; | |
| @override | |
| String get platformNotSupported => 'प्लेटफॉर्म सपोर्टेड नहीं'; | |
| @override | |
| String get platformNotSupportedMessage => | |
| 'अपडेट चेक करना केवल Android डिवाइस पर उपलब्ध है'; | |
| @override | |
| String get updateCheckFailed => 'अपडेट चेक फेल'; | |
| @override | |
| String get updateCheckFailedMessage => | |
| 'अपडेट चेक नहीं हो सका। कृपया बाद में प्रयास करें।'; | |
| @override | |
| String get upToDateTitle => 'आप अप टू डेट हैं!'; | |
| @override | |
| String get upToDateMessage => | |
| 'आप रेज़ोनेट का लेटेस्ट वर्शन इस्तेमाल कर रहे हैं'; | |
| @override | |
| String get updateAvailableTitle => 'अपडेट उपलब्ध है!'; | |
| @override | |
| String get updateAvailableMessage => | |
| 'रेज़ोनेट का नया वर्शन Play Store पर उपलब्ध है'; | |
| @override | |
| String get updateFeaturesImprovement => | |
| 'नवीनतम सुविधाएं और सुधार प्राप्त करें!'; | |
| } | |
| // (duplicate overrides removed) | |
| } |
🤖 Prompt for AI Agents
lib/l10n/app_localizations_hi.dart around lines 1202 to 1268: this block
duplicates earlier overrides for the same abstract getters (checkForUpdates,
updateNow, updateLater, updateSuccessful, updateSuccessfulMessage,
updateCancelled, updateCancelledMessage, updateFailed, updateFailedMessage,
updateError, updateErrorMessage, platformNotSupported,
platformNotSupportedMessage, updateCheckFailed, updateCheckFailedMessage,
upToDateTitle, upToDateMessage, updateAvailableTitle, updateAvailableMessage,
updateFeaturesImprovement); remove this duplicate block (or merge any differing
translations into the single original overrides) so each abstract member is
overridden exactly once and the class compiles without duplicate member
definitions.
| /// No description provided for @checkForUpdates. | ||
| /// | ||
| /// In en, this message translates to: | ||
| /// **'Check Updates'** | ||
| String get checkForUpdates; | ||
|
|
||
| /// No description provided for @updateNow. | ||
| /// | ||
| /// In en, this message translates to: | ||
| /// **'Update Now'** | ||
| String get updateNow; | ||
|
|
||
| /// No description provided for @updateLater. | ||
| /// | ||
| /// In en, this message translates to: | ||
| /// **'Later'** | ||
| String get updateLater; | ||
|
|
||
| /// No description provided for @updateSuccessful. | ||
| /// | ||
| /// In en, this message translates to: | ||
| /// **'Update Successful'** | ||
| String get updateSuccessful; | ||
|
|
||
| /// No description provided for @updateSuccessfulMessage. | ||
| /// | ||
| /// In en, this message translates to: | ||
| /// **'Resonate has been updated successfully!'** | ||
| String get updateSuccessfulMessage; | ||
|
|
||
| /// No description provided for @updateCancelled. | ||
| /// | ||
| /// In en, this message translates to: | ||
| /// **'Update Cancelled'** | ||
| String get updateCancelled; | ||
|
|
||
| /// No description provided for @updateCancelledMessage. | ||
| /// | ||
| /// In en, this message translates to: | ||
| /// **'Update was cancelled by user'** | ||
| String get updateCancelledMessage; | ||
|
|
||
| /// No description provided for @updateFailed. | ||
| /// | ||
| /// In en, this message translates to: | ||
| /// **'Update Failed'** | ||
| String get updateFailed; | ||
|
|
||
| /// No description provided for @updateFailedMessage. | ||
| /// | ||
| /// In en, this message translates to: | ||
| /// **'Failed to update. Please try updating from Play Store manually.'** | ||
| String get updateFailedMessage; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Duplicate getters in AppLocalizations — compile-time redeclarations.
Keys like checkForUpdates, updateNow, updateLater, updateSuccessful, … are defined twice in this abstract class. Dart will not compile with duplicate members.
Delete the duplicate block (second occurrence) and keep a single definition for each key. Prefer regenerating via flutter gen-l10n to avoid drift.
Minimal fix (remove the second block lines 2111–2329):
- /// No description provided for @checkForUpdates.
- /// ...
- String get updateFeaturesImprovement;
+ // (duplicates removed)Run to confirm duplicates across this file:
Also applies to: 2111-2329
🏁 Script executed:
#!/bin/bash
rg -nP "^\s*String\s+get\s+(\w+);" lib/l10n/app_localizations.dart | awk '{print $NF}' | sort | uniq -dLength of output: 393
Duplicate getters in AppLocalizations — remove second occurrence (compile-time redeclarations)
Delete the duplicate getter block in lib/l10n/app_localizations.dart — the following keys are declared twice: checkForUpdates, platformNotSupported, platformNotSupportedMessage, upToDateMessage, upToDateTitle, updateAvailableMessage, updateAvailableTitle, updateCancelled, updateCancelledMessage, updateCheckFailed, updateCheckFailedMessage, updateError, updateErrorMessage, updateFailed, updateFailedMessage, updateFeaturesImprovement, updateLater, updateNow, updateSuccessful, updateSuccessfulMessage. Remove the second block (lines 2111–2329) or regenerate the file with flutter gen-l10n. Dart will not compile with duplicate members.
🤖 Prompt for AI Agents
In lib/l10n/app_localizations.dart around lines 2001–2054 there are duplicate
getter declarations (and additional duplicates later between lines ~2111–2329)
causing compile-time redeclarations; remove the second/duplicate block of
getters (or regenerate the file using flutter gen-l10n) so each localization key
(e.g., checkForUpdates, updateNow, updateLater, updateSuccessful,
updateSuccessfulMessage, updateCancelled, updateCancelledMessage, updateFailed,
updateFailedMessage, etc.) is declared only once; ensure imports and class
structure remain intact and run dart/Flutter analyzer to confirm no duplicate
members remain.
| ResonateUser recieverToResonateUserForRequestsPage() { | ||
| return ResonateUser( | ||
| uid: recieverId, | ||
| userName: recieverName, | ||
| profileImageUrl: recieverProfileImgUrl, | ||
| name: recieverName, | ||
| docId: docId, | ||
|
|
||
| userRating: recieverRating!, | ||
| // Note: email, dateOfBirth are not available in FollowerUserModel | ||
| // so they will be null in the converted ResonateUser | ||
| email: null, | ||
| dateOfBirth: null, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong mapping: userName uses recieverName; rating force-unwrap
Map userName to recieverUsername and avoid null-assert on recieverRating.
ResonateUser recieverToResonateUserForRequestsPage() {
return ResonateUser(
uid: recieverId,
- userName: recieverName,
+ userName: recieverUsername,
profileImageUrl: recieverProfileImgUrl,
name: recieverName,
docId: docId,
-
- userRating: recieverRating!,
+ userRating: recieverRating ?? 0.0,
// Note: email, dateOfBirth are not available in FollowerUserModel
// so they will be null in the converted ResonateUser
email: null,
dateOfBirth: null,
);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ResonateUser recieverToResonateUserForRequestsPage() { | |
| return ResonateUser( | |
| uid: recieverId, | |
| userName: recieverName, | |
| profileImageUrl: recieverProfileImgUrl, | |
| name: recieverName, | |
| docId: docId, | |
| userRating: recieverRating!, | |
| // Note: email, dateOfBirth are not available in FollowerUserModel | |
| // so they will be null in the converted ResonateUser | |
| email: null, | |
| dateOfBirth: null, | |
| ); | |
| } | |
| ResonateUser recieverToResonateUserForRequestsPage() { | |
| return ResonateUser( | |
| uid: recieverId, | |
| userName: recieverUsername, | |
| profileImageUrl: recieverProfileImgUrl, | |
| name: recieverName, | |
| docId: docId, | |
| userRating: recieverRating ?? 0.0, | |
| // Note: email, dateOfBirth are not available in FollowerUserModel | |
| // so they will be null in the converted ResonateUser | |
| email: null, | |
| dateOfBirth: null, | |
| ); | |
| } |
🤖 Prompt for AI Agents
In lib/models/friends_model.dart around lines 33 to 47, the ResonateUser
construction incorrectly sets userName to recieverName and force-unwraps
recieverRating; change the userName mapping to recieverUsername and remove the
null-assert on recieverRating by assigning a safe value or nullable field (e.g.,
userRating: recieverRating ?? 0 or userRating: recieverRating) so the code
handles null ratings without throwing.
| ResonateUser senderToResonateUserForRequestsPage() { | ||
| return ResonateUser( | ||
| uid: senderId, | ||
| userName: senderName, | ||
| profileImageUrl: senderProfileImgUrl, | ||
| name: senderName, | ||
| docId: docId, | ||
|
|
||
| userRating: senderRating!, | ||
| // Note: email, dateOfBirth are not available in FollowerUserModel | ||
| // so they will be null in the converted ResonateUser | ||
| email: null, | ||
| dateOfBirth: null, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong mapping: userName uses senderName; rating force-unwrap
Map userName to senderUsername and avoid null-assert on senderRating.
ResonateUser senderToResonateUserForRequestsPage() {
return ResonateUser(
uid: senderId,
- userName: senderName,
+ userName: senderUsername,
profileImageUrl: senderProfileImgUrl,
name: senderName,
docId: docId,
-
- userRating: senderRating!,
+ userRating: senderRating ?? 0.0,
// Note: email, dateOfBirth are not available in FollowerUserModel
// so they will be null in the converted ResonateUser
email: null,
dateOfBirth: null,
);
}🤖 Prompt for AI Agents
In lib/models/friends_model.dart around lines 49 to 63, the mapping incorrectly
assigns userName to senderName and force-unwraps senderRating; change userName
to use senderUsername (or senderUserName field) instead of senderName, and
remove the null-assert on senderRating — propagate senderRating as a nullable
value or provide a safe default (e.g., 0 or null) so the constructor doesn't
crash when rating is null.
| String get userProfileImagePlaceholderUrl => | ||
| "http://$baseDomain/v1/storage/buckets/$userProfileImageBucketId/files/$currentThemePlaceHolder/view?project=resonate&mode=admin"; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Insecure URL and admin mode in client
Use https and don’t request admin mode from the client.
- String get userProfileImagePlaceholderUrl =>
- "http://$baseDomain/v1/storage/buckets/$userProfileImageBucketId/files/$currentThemePlaceHolder/view?project=resonate&mode=admin";
+ String get userProfileImagePlaceholderUrl =>
+ "https://$baseDomain/v1/storage/buckets/$userProfileImageBucketId/files/$currentThemePlaceHolder/view?project=resonate";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| String get userProfileImagePlaceholderUrl => | |
| "http://$baseDomain/v1/storage/buckets/$userProfileImageBucketId/files/$currentThemePlaceHolder/view?project=resonate&mode=admin"; | |
| String get userProfileImagePlaceholderUrl => | |
| "https://$baseDomain/v1/storage/buckets/$userProfileImageBucketId/files/$currentThemePlaceHolder/view?project=resonate"; |
🤖 Prompt for AI Agents
In lib/themes/theme_controller.dart around lines 29 to 31, the generated
userProfileImagePlaceholderUrl uses an insecure "http" scheme and includes
"mode=admin" which exposes admin access from the client; change the URL to use
"https" and remove the "mode=admin" query parameter (or replace it with a safe
non-admin mode if required by the API), and construct the URL safely (e.g., via
proper URL/URI builders) so the client no longer requests admin mode over an
insecure connection.
|
✅ PR Closed - Thank You, @M4dhav!
We appreciate your effort and look forward to more contributions from you! 🤝 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 74
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (15)
lib/controllers/network_controller.dart (2)
18-24: Fix Get.dialog call: positional widget must precede named args (current order won’t compile).Dart requires positional arguments before named ones. Also guard against duplicate dialogs and mark the widget const.
Apply this diff:
- if (status == InternetStatus.disconnected) { - Get.dialog( - barrierColor: Colors.transparent, - NoConnectionDialog(), - barrierDismissible: false, - ); - } else { + if (status == InternetStatus.disconnected) { + if (Get.isDialogOpen != true) { + Get.dialog( + const NoConnectionDialog(), + barrierColor: Colors.transparent, + barrierDismissible: false, + ); + } + } else {
25-27: Avoid null-assert on Get.isDialogOpen; use a safe check.
Get.isDialogOpen!can throw when null. Prefer== true.Apply this diff:
- if (Get.isDialogOpen!) { + if (Get.isDialogOpen == true) { Get.back(); }And:
- if (status == InternetStatus.connected) { - if (Get.isDialogOpen!) { + if (status == InternetStatus.connected) { + if (Get.isDialogOpen == true) { Get.back(); } }Also applies to: 39-41
android/app/src/main/AndroidManifest.xml (1)
14-19: usesCleartextTraffic is enabled; disable for Play release.Leaving cleartext enabled is a security risk and may fail review. Use HTTPS or a networkSecurityConfig for dev-only exceptions.
- <application + <application android:label="Resonate" android:icon="@mipmap/ic_launcher" android:theme="@style/Theme.AppCompat.Light.NoActionBar" - android:usesCleartextTraffic="true"> + android:usesCleartextTraffic="false">lib/services/appwrite_service.dart (1)
23-26: Critical: potential null client dereference in getAccount().
Account(_client!)will throw ifgetClient()wasn’t called first.static Account getAccount() { - _account ??= Account(_client!); + _account ??= Account(getClient()); return _account!; }lib/controllers/livekit_controller.dart (1)
11-19: Fix late-final reassign crash, re-bind listeners on reconnect, and dispose previous instances.
late finalfields are reassigned on retries, causingLateInitializationError. Also, listeners are attached only once and lost after creating a newRoom. Dispose prior instances before re-creating, make fields reassignable, and (re)bind listeners when a new room is created.-class LiveKitController extends GetxController { - late final Room liveKitRoom; - late final EventsListener<RoomEvent> listener; +class LiveKitController extends GetxController { + late Room liveKitRoom; + EventsListener<RoomEvent>? listener; @@ @override void onInit() async { - await connectToRoom(); // Initial connection with retries - liveKitRoom.addListener(onRoomDidUpdate); - setUpListeners(); + await connectToRoom(); // Initial connection with retries (binds listeners) super.onInit(); } @@ Future<bool> connectToRoom() async { - // Reset attempts for a fresh connection - if (reconnectAttempts == 0) reconnectAttempts = 0; + // Reset attempts for a fresh connection + reconnectAttempts = 0; while (reconnectAttempts < maxAttempts) { try { - liveKitRoom = Room(); - listener = liveKitRoom.createListener(); + // Dispose any previous instances safely + try { await listener?.dispose(); } catch (_) {} + try { await liveKitRoom.dispose(); } catch (_) {} + + liveKitRoom = Room(); + listener = liveKitRoom.createListener(); @@ - await liveKitRoom.connect( + await liveKitRoom.connect( liveKitUri, roomToken, roomOptions: const RoomOptions( adaptiveStream: false, dynacast: false, - defaultVideoPublishOptions: VideoPublishOptions(simulcast: false), + defaultVideoPublishOptions: VideoPublishOptions(simulcast: false), ), ); isConnected.value = true; reconnectAttempts = 0; // Reset on success + // (Re)bind listeners for the newly created room/listener + liveKitRoom.addListener(onRoomDidUpdate); + setUpListeners(); log('Connected to room successfully'); return true; } catch (error) { reconnectAttempts++; @@ - Get.snackbar( - AppLocalizations.of(Get.context!)!.connectionFailed, - AppLocalizations.of(Get.context!)!.unableToJoinRoom, - duration: const Duration(seconds: 5), - ); + final ctx = Get.context; + Get.snackbar( + ctx != null + ? AppLocalizations.of(ctx)!.connectionFailed + : 'Connection failed', + ctx != null + ? AppLocalizations.of(ctx)!.unableToJoinRoom + : 'Unable to join room', + duration: const Duration(seconds: 5), + ); return false; } } } return false; // Fallback return (shouldn’t hit this due to loop logic) } @@ - Get.snackbar( - AppLocalizations.of(Get.context!)!.connectionLost, - AppLocalizations.of(Get.context!)!.unableToReconnect, - duration: const Duration(seconds: 5), - ); + final ctx = Get.context; + Get.snackbar( + ctx != null + ? AppLocalizations.of(ctx)!.connectionLost + : 'Connection lost', + ctx != null + ? AppLocalizations.of(ctx)!.unableToReconnect + : 'Unable to reconnect', + duration: const Duration(seconds: 5), + ); } }); } }Also applies to: 28-35, 47-68, 70-76, 76-86, 92-117, 123-148
lib/controllers/tabview_controller.dart (1)
30-33: Use GetX lifecycle:onCloseinstead ofonDispose.
onDisposeis never called; subscription leak risk.-void onDispose() { - _linkSubscription?.cancel(); - super.dispose(); -} +@override +void onClose() { + _linkSubscription?.cancel(); + super.onClose(); +}lib/controllers/single_room_controller.dart (2)
161-169: Missing break in 'delete' case can cause compile error and unintended fall‑throughIn the 'delete' case, the
if (updatedUserId == me.value.uid)branch lacks a terminatingbreak/return. Dart requires each switch case to end withbreak,continue,return, orthrow. Add abreakafterGet.delete<SingleRoomController>().Apply:
case 'delete': { if (updatedUserId == me.value.uid) { - await Get.delete<SingleRoomController>(); + await Get.delete<SingleRoomController>(); + break; } else { removeParticipantDataFromList(data.payload["uid"]); break; } }
94-102: Guard against -1 index in participant update (race with realtime events)If the payload arrives before the participant is added,
indexWherereturns-1, causing a RangeError. Guard and no‑op (or log) when not found.- int toBeUpdatedIndex = participants.indexWhere( + final toBeUpdatedIndex = participants.indexWhere( (p) => p.value.uid == payload["uid"], ); + if (toBeUpdatedIndex < 0) { + log('Participant ${payload["uid"]} not found in list for update'); + return; + }lib/controllers/create_room_controller.dart (2)
26-33: GetxController won’t call dispose(); use onClose() to prevent leaks.GetX disposes controllers via onClose(), not dispose(). Your TextEditingController/TextfieldTagsController may leak.
Apply this diff:
- @override - void dispose() { - nameController.dispose(); - descriptionController.dispose(); - tagsController.dispose(); - super.dispose(); - } + @override + void onClose() { + nameController.dispose(); + descriptionController.dispose(); + tagsController.dispose(); + super.onClose(); + }
68-71: Guard Get.back() to avoid unintended pops.Unconditional Get.back() can pop the current route if no dialog is open.
Apply this diff:
- // Close the loading dialog - Get.back(); + // Close the loading dialog (if open) + if (Get.isDialogOpen == true) Get.back();And in catch:
- // Close the loading dialog - Get.back(); + // Close the loading dialog (if open) + if (Get.isDialogOpen == true) Get.back();Also applies to: 92-94
lib/controllers/authentication_controller.dart (1)
117-121: Reset‑password link points to https://localhost — this will break in production.Parameterize via a dart-define or config; don’t ship a localhost URL.
- url: - 'https://localhost/reset-password', // Replace with actual reset password URL + url: 'https://${const String.fromEnvironment('APPWRITE_BASE_DOMAIN')}/reset-password',If you prefer constants, read from a central config instead of dart-define.
lib/controllers/auth_state_controller.dart (2)
88-104: Crash on null notification payload and invalid index.
notificationResponse.payload!can be null (you sometimes show a notification without a payload), causing an exception; alsoindexcan be-1.-void onDidReceiveNotificationResponse( - NotificationResponse notificationResponse, -) async { - String name = notificationResponse.payload!; +void onDidReceiveNotificationResponse( + NotificationResponse notificationResponse, +) async { + final payload = notificationResponse.payload; + if (payload == null || payload.isEmpty) return; UpcomingRoomsController upcomingRoomsController = Get.find<UpcomingRoomsController>(); - int index = upcomingRoomsController.upcomingRooms.indexWhere( - (upcomingRoom) => upcomingRoom.name == name, + final index = upcomingRoomsController.upcomingRooms.indexWhere( + (upcomingRoom) => upcomingRoom.name == payload, ); + if (index < 0) return;
270-314: FCM token may be null; missed awaits; potential duplicates.
fcmToken!can be null; updates aren’t awaited; tokens may duplicate.- final fcmToken = await messaging.getToken(); + final fcmToken = await messaging.getToken(); + if (fcmToken == null) return; ... - registrationTokens.add(fcmToken!); - databases.updateDocument( + if (!registrationTokens.contains(fcmToken)) { + registrationTokens.add(fcmToken); + } + await databases.updateDocument( databaseId: upcomingRoomsDatabaseId, collectionId: subscribedUserCollectionId, documentId: subscription.$id, data: {"registrationTokens": registrationTokens}, ); ... - creatorFcmTokens.add(fcmToken!); - databases.updateDocument( + if (!creatorFcmTokens.contains(fcmToken)) { + creatorFcmTokens.add(fcmToken); + } + await databases.updateDocument( databaseId: upcomingRoomsDatabaseId, collectionId: upcomingRoomsCollectionId, documentId: upcomingRoom.$id, data: {"creator_fcm_tokens": creatorFcmTokens}, );lib/controllers/onboarding_controller.dart (1)
114-131: Initialize required fields on user creation.Downstream code expects
ratingTotal,ratingCount, and often list fields (followers,friends). Set safe defaults to prevent crashes.await databases.createDocument( databaseId: userDatabaseID, collectionId: usersCollectionID, documentId: authStateController.uid!, data: { "name": nameController.text.trim(), "username": usernameController.text.trim(), "profileImageUrl": imageController.text, "dob": dobController.text, "email": authStateController.email, "profileImageID": uniqueIdForProfileImage, + "ratingTotal": 5.0, + "ratingCount": 1, + "followers": [], + "friends": [], }, );lib/controllers/edit_profile_controller.dart (1)
95-98: GuardGet.back(); calling with no open route can throw.Wrap with a check to prevent navigator errors when no dialog is shown.
- // Close the loading dialog - Get.back(); + // Close the loading dialog if present + if (Get.isDialogOpen == true) { + Get.back(); + }Do this in both camera and gallery flows.
Also applies to: 129-133
| - type: textarea | ||
| attributes: | ||
| label: Motivation, pitch | ||
| description: > | ||
| Please provide the motivation for re-implementing this part of the code. | ||
| Why do you think the current implementation needs to be replaced or rewritten? | ||
| E.g., *"The current implementation is slow, hard to maintain, or outdated."* If you have any supporting context or examples, please include them here. | ||
| validations: | ||
| required: true | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Second textarea missing id
Same schema issue here; add an id.
- - type: textarea
- attributes:
+ - type: textarea
+ id: motivation
+ attributes:
label: Motivation, pitch📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - type: textarea | |
| attributes: | |
| label: Motivation, pitch | |
| description: > | |
| Please provide the motivation for re-implementing this part of the code. | |
| Why do you think the current implementation needs to be replaced or rewritten? | |
| E.g., *"The current implementation is slow, hard to maintain, or outdated."* If you have any supporting context or examples, please include them here. | |
| validations: | |
| required: true | |
| - type: textarea | |
| id: motivation | |
| attributes: | |
| label: Motivation, pitch | |
| description: > | |
| Please provide the motivation for re-implementing this part of the code. | |
| Why do you think the current implementation needs to be replaced or rewritten? | |
| E.g., *"The current implementation is slow, hard to maintain, or outdated."* If you have any supporting context or examples, please include them here. | |
| validations: | |
| required: true |
🤖 Prompt for AI Agents
In .github/ISSUE_TEMPLATE/re-implementation.yml around lines 15 to 24, the
second textarea field is missing an id which breaks the issue form schema; add a
unique id property under that textarea (matching the naming pattern used
elsewhere, e.g., a snake_case identifier like "motivation_pitch" or
"motivation_pitch_details") so the field has an "id:" entry and the validations
and attributes remain unchanged.
| - type: checkboxes | ||
| attributes: | ||
| label: Areas of Focus | ||
| options: | ||
| - "Performance" | ||
| - "Maintainability" | ||
| - "Code readability" | ||
| - "Functionality improvement" | ||
| - "Other" | ||
| validations: | ||
| required: false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checkboxes block missing id
Add an id for the checkboxes.
- - type: checkboxes
- attributes:
+ - type: checkboxes
+ id: focus-areas
+ attributes:
label: Areas of Focus📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - type: checkboxes | |
| attributes: | |
| label: Areas of Focus | |
| options: | |
| - "Performance" | |
| - "Maintainability" | |
| - "Code readability" | |
| - "Functionality improvement" | |
| - "Other" | |
| validations: | |
| required: false | |
| - type: checkboxes | |
| id: focus-areas | |
| attributes: | |
| label: Areas of Focus | |
| options: | |
| - "Performance" | |
| - "Maintainability" | |
| - "Code readability" | |
| - "Functionality improvement" | |
| - "Other" | |
| validations: | |
| required: false |
🤖 Prompt for AI Agents
.github/ISSUE_TEMPLATE/re-implementation.yml around lines 25 to 35: the
checkboxes block lacks a required id; add a unique id field under attributes
(e.g., id: areas_of_focus) so the block becomes identifiable and valid per
GitHub issue template schema, ensure the id is unique within the file and
follows a simple snake_case naming convention.
| - type: textarea | ||
| attributes: | ||
| label: Motivation, pitch | ||
| description: > | ||
| Please explain the motivation for this refactor. Why is this refactor needed? | ||
| What benefits will it bring to the codebase, such as improved performance, easier maintenance, or better readability? | ||
| Provide examples of how the code can be improved if possible. | ||
| validations: | ||
| required: true | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing id for the second textarea
Add a unique id to satisfy GitHub forms schema.
- - type: textarea
- attributes:
+ - type: textarea
+ id: motivation
+ attributes:
label: Motivation, pitch📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - type: textarea | |
| attributes: | |
| label: Motivation, pitch | |
| description: > | |
| Please explain the motivation for this refactor. Why is this refactor needed? | |
| What benefits will it bring to the codebase, such as improved performance, easier maintenance, or better readability? | |
| Provide examples of how the code can be improved if possible. | |
| validations: | |
| required: true | |
| - type: textarea | |
| id: motivation | |
| attributes: | |
| label: Motivation, pitch | |
| description: > | |
| Please explain the motivation for this refactor. Why is this refactor needed? | |
| What benefits will it bring to the codebase, such as improved performance, easier maintenance, or better readability? | |
| Provide examples of how the code can be improved if possible. | |
| validations: | |
| required: true |
🤖 Prompt for AI Agents
In .github/ISSUE_TEMPLATE/refactor-to-enhance.yml around lines 15 to 24 the
second textarea block is missing a unique `id` required by GitHub Forms schema;
add an `id` property to that textarea (for example `id: motivation_pitch` or
another unique identifier), ensuring it is a simple string and unique within the
form so the schema validates correctly.
| - type: checkboxes | ||
| attributes: | ||
| label: Refactoring Focus Areas | ||
| options: | ||
| - "Performance" | ||
| - "Code readability" | ||
| - "Maintainability" | ||
| - "Reducing complexity" | ||
| - "Other" | ||
| validations: | ||
| required: false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add id to checkboxes block
Without an id, the form can fail to render.
- - type: checkboxes
- attributes:
+ - type: checkboxes
+ id: focus-areas
+ attributes:
label: Refactoring Focus Areas🤖 Prompt for AI Agents
.github/ISSUE_TEMPLATE/refactor-to-enhance.yml lines 25-35: the checkboxes block
is missing an id which can cause the form to fail to render; add a unique id
field (e.g., id: refactoring_focus_areas) directly under the checkboxes key so
the block becomes identified, keep the existing attributes/options/validations
intact, and ensure the id is a valid YAML string without spaces.
| - type: textarea | ||
| attributes: | ||
| label: Motivation, pitch | ||
| description: > | ||
| Explain the motivation behind this security improvement. How does it protect against security risks, and what kind of | ||
| vulnerabilities does it address? Describe how it can prevent unauthorized access, data breaches, or other threats. | ||
| validations: | ||
| required: true | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing id for textarea breaks GitHub issue form
Each input needs a unique id. The second textarea lacks one, so the template won’t load.
Apply:
- - type: textarea
- attributes:
+ - type: textarea
+ id: motivation
+ attributes:
label: Motivation, pitch📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - type: textarea | |
| attributes: | |
| label: Motivation, pitch | |
| description: > | |
| Explain the motivation behind this security improvement. How does it protect against security risks, and what kind of | |
| vulnerabilities does it address? Describe how it can prevent unauthorized access, data breaches, or other threats. | |
| validations: | |
| required: true | |
| - type: textarea | |
| id: motivation | |
| attributes: | |
| label: Motivation, pitch | |
| description: > | |
| Explain the motivation behind this security improvement. How does it protect against security risks, and what kind of | |
| vulnerabilities does it address? Describe how it can prevent unauthorized access, data breaches, or other threats. | |
| validations: | |
| required: true |
🤖 Prompt for AI Agents
.github/ISSUE_TEMPLATE/security_improvement.yml lines 15-23: the second textarea
is missing a unique id which prevents the GitHub issue form from loading; add a
new unique id field (e.g., id: motivation_pitch or similar, using lowercase
letters/numbers/hyphens) under the textarea definition, ensure the id does not
collide with other inputs in the file, and keep the rest of the
attributes/validations unchanged so the form validates correctly.
| _client ??= Client() | ||
| .setEndpoint(appwriteEndpoint) | ||
| .setProject(appwriteProjectId) | ||
| .setSelfSigned( | ||
| status: | ||
| true); // For self signed certificates, only use for development | ||
| status: true, | ||
| ); // For self signed certificates, only use for development | ||
| return _client!; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t enable self‑signed certs in release builds.
Unconditionally calling setSelfSigned(status: true) weakens TLS in production. Gate it by kDebugMode.
+import 'package:flutter/foundation.dart';
@@
static Client getClient() {
- _client ??= Client()
- .setEndpoint(appwriteEndpoint)
- .setProject(appwriteProjectId)
- .setSelfSigned(
- status: true,
- ); // For self signed certificates, only use for development
+ if (_client != null) return _client!;
+ final client = Client()
+ .setEndpoint(appwriteEndpoint)
+ .setProject(appwriteProjectId);
+ if (kDebugMode) {
+ client.setSelfSigned(status: true); // dev only
+ }
+ _client = client;
return _client!;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| _client ??= Client() | |
| .setEndpoint(appwriteEndpoint) | |
| .setProject(appwriteProjectId) | |
| .setSelfSigned( | |
| status: | |
| true); // For self signed certificates, only use for development | |
| status: true, | |
| ); // For self signed certificates, only use for development | |
| return _client!; | |
| } | |
| import 'package:flutter/foundation.dart'; | |
| static Client getClient() { | |
| if (_client != null) return _client!; | |
| final client = Client() | |
| .setEndpoint(appwriteEndpoint) | |
| .setProject(appwriteProjectId); | |
| if (kDebugMode) { | |
| client.setSelfSigned(status: true); // dev only | |
| } | |
| _client = client; | |
| return _client!; | |
| } |
🤖 Prompt for AI Agents
In lib/services/appwrite_service.dart around lines 14 to 21, the client is
unconditionally configured with setSelfSigned(status: true) which weakens TLS in
production; update the code to only enable self-signed certs when running in
debug mode by importing flutter/foundation.dart and gating setSelfSigned(status:
true) with a kDebugMode check (otherwise omit the call or pass false), so
release builds never enable self‑signed certificates.
| String get userProfileImagePlaceholderUrl => | ||
| "http://$baseDomain/v1/storage/buckets/$userProfileImageBucketId/files/$currentThemePlaceHolder/view?project=resonate&mode=admin"; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use HTTPS and drop admin mode in client URL.
In-app “mode=admin” is inappropriate and HTTP is insecure. Use HTTPS and a public file URL only.
- String get userProfileImagePlaceholderUrl =>
- "http://$baseDomain/v1/storage/buckets/$userProfileImageBucketId/files/$currentThemePlaceHolder/view?project=resonate&mode=admin";
+ String get userProfileImagePlaceholderUrl =>
+ "https://$baseDomain/v1/storage/buckets/$userProfileImageBucketId/files/$currentThemePlaceHolder/view?project=resonate";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| String get userProfileImagePlaceholderUrl => | |
| "http://$baseDomain/v1/storage/buckets/$userProfileImageBucketId/files/$currentThemePlaceHolder/view?project=resonate&mode=admin"; | |
| String get userProfileImagePlaceholderUrl => | |
| "https://$baseDomain/v1/storage/buckets/$userProfileImageBucketId/files/$currentThemePlaceHolder/view?project=resonate"; |
🤖 Prompt for AI Agents
In lib/themes/theme_controller.dart around lines 29 to 31, the generated
placeholder URL uses plain HTTP and includes the inappropriate "mode=admin"
query; replace it with a secure HTTPS URL and remove the mode=admin parameter so
the client uses the public file endpoint (e.g., keep baseDomain and bucket/file
path but switch to "https://" and drop "&mode=admin" from the query string,
ensuring any required public project param is preserved or adjusted to the
public access pattern).
| return Column( | ||
| spacing: 8, | ||
| crossAxisAlignment: CrossAxisAlignment.start, | ||
| children: [ | ||
| Text(themeName, style: TextStyle(fontSize: 18)), | ||
| const SizedBox(width: 8), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compile error: Column has no “spacing”; SizedBox uses width in a vertical layout.
Replace spacing with an explicit vertical SizedBox(height: 8), and fix the SizedBox(width: 8) between title and row.
- return Column(
- spacing: 8,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(themeName, style: TextStyle(fontSize: 18)),
- const SizedBox(width: 8),
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(themeName, style: const TextStyle(fontSize: 18)),
+ const SizedBox(height: 8),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return Column( | |
| spacing: 8, | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Text(themeName, style: TextStyle(fontSize: 18)), | |
| const SizedBox(width: 8), | |
| return Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Text(themeName, style: const TextStyle(fontSize: 18)), | |
| const SizedBox(height: 8), |
🤖 Prompt for AI Agents
In lib/themes/theme_tile_title.dart around lines 33 to 38, the Column is using a
non-existent "spacing" property and the SizedBox inside a vertical Column
incorrectly uses width; remove the "spacing" property from the Column and
replace it by inserting a vertical spacer SizedBox(height: 8) between the title
Text and the following widget, and change the existing const SizedBox(width: 8)
to const SizedBox(height: 8) (or remove it if redundant) so spacing is applied
vertically.
| static const noConnectionImage = 'assets/images/no_connection.svg'; | ||
| static const noRoomImage = 'assets/images/no_room.svg'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Switch to SVG may break call sites using Image.asset — verify and migrate to SvgPicture.asset.
If any screen still does Image.asset(AppImages.noConnectionImage|noRoomImage) or AssetImage(...), this change will throw at runtime. Ensure flutter_svg is a dependency and callers use SvgPicture.asset(...).
Run this to find risky usages and confirm SVG setup:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "1) Check for Image/AssetImage usages with the changed constants"
rg -n -C2 -g '!**/build/**' --type dart -P \
'(Image\.asset|AssetImage)\s*\(\s*AppImages\.(noConnectionImage|noRoomImage)'
echo -e "\n2) Check that flutter_svg is declared and imported where needed"
rg -n 'flutter_svg' pubspec.yaml || echo "flutter_svg not found in pubspec.yaml"
rg -n -g '!**/build/**' --type dart 'import .+flutter_svg' || echo "No flutter_svg imports found"
echo -e "\n3) Verify the new SVG assets actually exist"
for f in assets/images/no_connection.svg assets/images/no_room.svg; do
[[ -f "$f" ]] && echo "OK: $f" || echo "MISSING: $f"
done
echo -e "\n4) Verify assets/images is listed in pubspec.yaml"
rg -n -C2 -P 'assets:\s*(\n\s*-\s*assets/images/?\s*$|\n\s*-\s*assets/)' pubspec.yaml || echo "assets/images not declared"Length of output: 212
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== debug: rg version ==="
if command -v rg >/dev/null 2>&1; then rg --version; else echo "rg not installed"; fi
echo "=== repo root ==="
pwd
echo "=== list root files (top 100) ==="
ls -la | sed -n '1,100p' || true
echo "=== count .dart files ==="
if command -v rg >/dev/null 2>&1; then
rg --files -g '!**/build/**' --hidden --glob '*.dart' | wc -l || true
else
find . -type f -name '*.dart' | wc -l || true
fi
echo
echo "=== 1) Search for AppImages.noConnectionImage / noRoomImage usages ==="
if command -v rg >/dev/null 2>&1; then
rg -n --hidden -S 'AppImages\.(noConnectionImage|noRoomImage)' -g '!**/build/**' || echo "No matches for AppImages constants"
else
grep -RIn --exclude-dir=build -E 'AppImages\.(noConnectionImage|noRoomImage)' || echo "No matches (grep)"
fi
echo
echo "=== 2) Search for Image.asset / AssetImage referencing assets/images/no_* ==="
if command -v rg >/dev/null 2>&1; then
rg -n --hidden -S '(Image\.asset|AssetImage)\s*\(\s*["'"'"'](assets/images/no_connection|assets/images/no_room|assets/images/no_)[^"'"'"']*["'"'"']' -g '!**/build/**' || echo "No Image.asset/AssetImage usages found"
else
grep -RIn --exclude-dir=build -E '(Image\.asset|AssetImage)\s*\(\s*["'"'"'](assets/images/no_connection|assets/images/no_room|assets/images/no_)[^"'"'"']*["'"'"']' || echo "No matches (grep)"
fi
echo
echo "=== 3) Check pubspec.yaml for flutter_svg and assets/images ==="
if [[ -f pubspec.yaml ]]; then
echo "pubspec.yaml found"
if command -v rg >/dev/null 2>&1; then
rg -n 'flutter_svg' pubspec.yaml || echo "flutter_svg not found in pubspec.yaml"
rg -n -C2 '^\s*assets:' pubspec.yaml || echo "assets: block not found in pubspec.yaml"
rg -n 'assets/images' pubspec.yaml || echo "assets/images not referenced in pubspec.yaml"
else
grep -n 'flutter_svg' pubspec.yaml || echo "flutter_svg not found (grep)"
grep -n -n '^\s*assets:' pubspec.yaml || echo "assets: block not found (grep)"
grep -n 'assets/images' pubspec.yaml || echo "assets/images not referenced (grep)"
fi
else
echo "pubspec.yaml not found"
fi
echo
echo "=== 4) Search for flutter_svg imports in dart files ==="
if command -v rg >/dev/null 2>&1; then
rg -n --hidden -S \"import\\s+['\\\"]package:flutter_svg/flutter_svg.dart['\\\"]\" -g '!**/build/**' || echo "No flutter_svg imports found"
else
grep -RIn --exclude-dir=build \"flutter_svg\" --include='*.dart' || echo "No matches (grep)"
fi
echo
echo "=== 5) Check asset files existence ==="
for f in assets/images/no_connection.svg assets/images/no_room.svg; do
if [[ -f \"$f\" ]]; then echo \"OK: $f\"; else echo \"MISSING: $f\"; fi
doneLength of output: 3534
Critical: lib/utils/app_images.dart references SVGs that are missing — restore assets or migrate callers to SvgPicture.asset
- Location: lib/utils/app_images.dart (lines 12–13) — constants now point to assets/images/no_connection.svg and assets/images/no_room.svg.
- Findings: pubspec.yaml includes flutter_svg: ^2.2.0 and declares assets (assets/images/ and assets/svg/), but the two files are MISSING: assets/images/no_connection.svg, assets/images/no_room.svg.
- Repo scan returned no matches for AppImages.noConnectionImage / noRoomImage but ripgrep reported "No files were searched" (search inconclusive) — verify all call sites.
- Action: add the missing SVG files to assets/images/ OR update the constants to existing assets; ensure any Image.asset(...) or AssetImage(...) usages are migrated to SvgPicture.asset(...).
🤖 Prompt for AI Agents
In lib/utils/app_images.dart around lines 12 to 13 the constants point to
assets/images/no_connection.svg and assets/images/no_room.svg which are missing
from the repo; either restore those two SVG files into assets/images/ (and
ensure pubspec.yaml assets include that path) or change the constants to point
to existing asset files (e.g., under assets/svg/) and then update all call
sites: replace any Image.asset(...) or AssetImage(...) usages with
SvgPicture.asset(...) from flutter_svg for SVGs; finally run a repository search
for AppImages.noConnectionImage and AppImages.noRoomImage to verify and update
every caller and run flutter pub get/build to confirm assets load.
| ### 2.3 Permissions We Request | ||
|
|
||
| Our App requires the following permissions to function properly: | ||
|
|
||
| - **Microphone Access**: Required for audio communication in voice rooms and pair chats | ||
| - **Camera Access**: Optional, for taking profile pictures | ||
| - **Photo Library Access**: Optional, for selecting profile pictures and story cover images | ||
| - **Internet Access**: Required for real-time communication and data synchronization | ||
| - **Network State Access**: To determine connection quality and optimize audio streaming | ||
| - **Audio Settings Modification**: To manage audio routing and microphone settings | ||
| - **Bluetooth Access**: For connecting to Bluetooth audio devices | ||
| - **Storage Access**: To temporarily store audio files and images | ||
| - **Notification Access**: To send room reminders and app notifications | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Align declared permissions with actual app permissions.
Policy lists Camera/Photo/Storage/Notification access, but AndroidManifest requests none of these. Either update the manifest (e.g., POST_NOTIFICATIONS for Android 13+) or revise the policy.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Repository root: $(pwd)"
echo "---- PRIVACY-POLICY.md snippet (lines 1-240) ----"
if [ -f "PRIVACY-POLICY.md" ]; then
sed -n '1,240p' PRIVACY-POLICY.md || true
else
echo "PRIVACY-POLICY.md not at repo root; searching..."
rg -n --hidden -S "Permissions We Request|PRIVACY-POLICY.md" -g '!*node_modules/**' -C3 || true
for f in $(rg -l --hidden -S "Permissions We Request" -g '!*node_modules/**' || true); do
echo "---- $f ----"
sed -n '1,240p' "$f" || true
done
fi
echo
echo "---- Finding AndroidManifest.xml files ----"
fd -HI -e xml AndroidManifest.xml || true
echo
echo "---- Searching manifests for permission declarations ----"
for m in $(fd -HI -e xml AndroidManifest.xml || true); do
echo "---- $m ----"
rg -n --hidden -S "uses-permission|android.permission.POST_NOTIFICATIONS|android.permission.CAMERA|android.permission.RECORD_AUDIO|android.permission.BLUETOOTH|android.permission.BLUETOOTH_CONNECT|android.permission.ACCESS_NETWORK_STATE|android.permission.INTERNET|READ_EXTERNAL_STORAGE|WRITE_EXTERNAL_STORAGE|READ_MEDIA_IMAGES|READ_MEDIA_AUDIO|READ_MEDIA_VIDEO" -n -C2 "$m" || true
echo "---- Full head (first 200 lines) ----"
sed -n '1,200p' "$m" || true
echo
done
echo
echo "---- Searching entire repo for android.permission occurrences ----"
rg -n --hidden -S "android.permission\\.|POST_NOTIFICATIONS|READ_EXTERNAL_STORAGE|WRITE_EXTERNAL_STORAGE|READ_MEDIA_IMAGES|READ_MEDIA_AUDIO|CAMERA|BLUETOOTH" -g '!*node_modules/**' -C2 || true
echo
echo "---- Searching for iOS Info.plist usage description keys ----"
rg -n --hidden -S "NSCameraUsageDescription|NSPhotoLibraryUsageDescription|NSPhotoLibraryAddUsageDescription|NSMicrophoneUsageDescription|NSBluetooth|NSPhotoLibraryUsage" -g '!*node_modules/**' -C2 || trueLength of output: 17509
Action: Align PRIVACY-POLICY.md with actual platform permissions (update manifest/Info.plist or revise policy)
PRIVACY-POLICY.md (repo root) lists Camera, Photo Library, Storage, and Notification access, but android/app/src/main/AndroidManifest.xml only declares: INTERNET, RECORD_AUDIO, ACCESS_NETWORK_STATE, CHANGE_NETWORK_STATE, MODIFY_AUDIO_SETTINGS, BLUETOOTH (maxSdkVersion=30), BLUETOOTH_CONNECT, BLUETOOTH_ADMIN (maxSdkVersion=30).
- If the app uses camera/photo/storage/notifications: add the corresponding Android permissions (android.permission.CAMERA; READ_MEDIA_IMAGES/READ_MEDIA_AUDIO/READ_MEDIA_VIDEO on Android 13+ or READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE for older SDKs; android.permission.POST_NOTIFICATIONS + runtime request on Android 13+), and add matching iOS Info.plist keys (NSCameraUsageDescription, NSPhotoLibraryUsageDescription / NSPhotoLibraryAddUsageDescription, NSMicrophoneUsageDescription as applicable).
- If the app does not use those features: remove Camera / Photo Library / Storage / Notification entries from PRIVACY-POLICY.md.
- Verify Bluetooth handling across SDK versions (manifest already includes Bluetooth perms with maxSdkVersion where applicable).
🤖 Prompt for AI Agents
In PRIVACY-POLICY.md around lines 30 to 43, the policy lists Camera, Photo
Library, Storage, and Notification permissions that are not declared in
android/app/src/main/AndroidManifest.xml; either update platform
manifests/Info.plist to reflect actual usage or remove those entries from the
policy. If the app uses those features: add the matching Android permissions
(CAMERA, runtime storage permissions or Android 13+ READ_MEDIA_* variants,
POST_NOTIFICATIONS with runtime request) to AndroidManifest.xml and add
corresponding iOS Info.plist keys (NSCameraUsageDescription,
NSPhotoLibraryUsageDescription/NSPhotoLibraryAddUsageDescription,
NSMicrophoneUsageDescription if needed); if the app does not use them: remove
Camera/Photo Library/Storage/Notification lines from PRIVACY-POLICY.md. Also
verify Bluetooth permissions remain correctly declared with appropriate
maxSdkVersion and include any Android 12+ Bluetooth runtime perms if required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review continued from previous batch...
| customSnackbar( | ||
| AppLocalizations.of(Get.context!)!.updateCheckFailed, | ||
| AppLocalizations.of(Get.context!)!.updateCheckFailedMessage, | ||
| LogType.error, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Get.context! can be null inside controllers; make it null-safe.
Avoid potential NPE when no active context.
Apply this diff:
- customSnackbar(
- AppLocalizations.of(Get.context!)!.updateCheckFailed,
- AppLocalizations.of(Get.context!)!.updateCheckFailedMessage,
- LogType.error,
- );
+ final ctx = Get.context;
+ if (ctx != null) {
+ customSnackbar(
+ AppLocalizations.of(ctx)!.updateCheckFailed,
+ AppLocalizations.of(ctx)!.updateCheckFailedMessage,
+ LogType.error,
+ );
+ } else {
+ log('updateCheckFailed (no context): $e');
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| customSnackbar( | |
| AppLocalizations.of(Get.context!)!.updateCheckFailed, | |
| AppLocalizations.of(Get.context!)!.updateCheckFailedMessage, | |
| LogType.error, | |
| ); | |
| final ctx = Get.context; | |
| if (ctx != null) { | |
| customSnackbar( | |
| AppLocalizations.of(ctx)!.updateCheckFailed, | |
| AppLocalizations.of(ctx)!.updateCheckFailedMessage, | |
| LogType.error, | |
| ); | |
| } else { | |
| log('updateCheckFailed (no context): $e'); | |
| } |
🤖 Prompt for AI Agents
In lib/controllers/about_app_screen_controller.dart around lines 49 to 53, the
code calls AppLocalizations.of(Get.context!) which can NPE when Get.context is
null; guard against null by capturing Get.context into a local variable, check
if it is null and handle that case (either return early or call customSnackbar
with a non-localized fallback message), otherwise call AppLocalizations.of(ctx)!
using the non-null ctx and proceed to show the localized snackbar; ensure no
forced unwrapped Get.context! remains.
| Future<UpdateActionResult> launchStoreForUpdate() async { | ||
| try { | ||
| String storeUrl; | ||
| if (Platform.isAndroid) { | ||
| storeUrl = | ||
| 'https://play.google.com/store/apps/details?id=com.resonate.resonate'; | ||
| } else if (Platform.isIOS) { | ||
| // The App Store URL of app | ||
| storeUrl = ''; | ||
| } else { | ||
| return UpdateActionResult.error; | ||
| } | ||
| final uri = Uri.parse(storeUrl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iOS store URL is empty.
Launching with '' will fail silently. Either provide the App Store URL or surface a user-visible failure.
Apply this diff:
- } else if (Platform.isIOS) {
- // The App Store URL of app
- storeUrl = '';
+ } else if (Platform.isIOS) {
+ // TODO: set real App Store URL; until then, fail fast
+ return UpdateActionResult.failed;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Future<UpdateActionResult> launchStoreForUpdate() async { | |
| try { | |
| String storeUrl; | |
| if (Platform.isAndroid) { | |
| storeUrl = | |
| 'https://play.google.com/store/apps/details?id=com.resonate.resonate'; | |
| } else if (Platform.isIOS) { | |
| // The App Store URL of app | |
| storeUrl = ''; | |
| } else { | |
| return UpdateActionResult.error; | |
| } | |
| final uri = Uri.parse(storeUrl); | |
| Future<UpdateActionResult> launchStoreForUpdate() async { | |
| try { | |
| String storeUrl; | |
| if (Platform.isAndroid) { | |
| storeUrl = | |
| 'https://play.google.com/store/apps/details?id=com.resonate.resonate'; | |
| } else if (Platform.isIOS) { | |
| // TODO: set real App Store URL; until then, fail fast | |
| return UpdateActionResult.failed; | |
| } else { | |
| return UpdateActionResult.error; | |
| } | |
| final uri = Uri.parse(storeUrl); |
🤖 Prompt for AI Agents
In lib/controllers/about_app_screen_controller.dart around lines 106 to 118, the
iOS storeUrl is set to an empty string which will cause launching to fail
silently; update the code to either populate storeUrl with the correct App Store
URL for the app or check for an empty/invalid URL and surface a visible failure
(e.g., log the error, show a user-facing message, and return
UpdateActionResult.error) before attempting to parse/launch the URI.
| FlutterCallkitIncoming.onEvent.listen((CallEvent? event) { | ||
| if (event!.event == Event.actionCallAccept) { | ||
| log(event.body['extra'].toString()); | ||
|
|
||
| friendCallingController.onAnswerCall( | ||
| Map<String, dynamic>.from(event.body['extra']), | ||
| ); | ||
| } | ||
| if (event.event == Event.actionCallDecline) { | ||
| friendCallingController.onDeclinedCall( | ||
| Map<String, dynamic>.from(event.body['extra']), | ||
| ); | ||
| FlutterCallkitIncoming.showMissCallNotification(CallKitParams()); | ||
| } | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Null-safety on CallKit events and extra payload.
event and event.body['extra'] can be null; current event! will crash.
-FlutterCallkitIncoming.onEvent.listen((CallEvent? event) {
- if (event!.event == Event.actionCallAccept) {
+FlutterCallkitIncoming.onEvent.listen((CallEvent? event) {
+ if (event == null) return;
+ if (event.event == Event.actionCallAccept) {
log(event.body['extra'].toString());
- friendCallingController.onAnswerCall(
- Map<String, dynamic>.from(event.body['extra']),
- );
+ final extra = event.body['extra'];
+ if (extra is Map) {
+ friendCallingController.onAnswerCall(
+ Map<String, dynamic>.from(extra as Map),
+ );
+ }
}
- if (event.event == Event.actionCallDecline) {
- friendCallingController.onDeclinedCall(
- Map<String, dynamic>.from(event.body['extra']),
- );
+ if (event.event == Event.actionCallDecline) {
+ final extra = event.body['extra'];
+ if (extra is Map) {
+ friendCallingController.onDeclinedCall(
+ Map<String, dynamic>.from(extra as Map),
+ );
+ }
FlutterCallkitIncoming.showMissCallNotification(CallKitParams());
}
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| FlutterCallkitIncoming.onEvent.listen((CallEvent? event) { | |
| if (event!.event == Event.actionCallAccept) { | |
| log(event.body['extra'].toString()); | |
| friendCallingController.onAnswerCall( | |
| Map<String, dynamic>.from(event.body['extra']), | |
| ); | |
| } | |
| if (event.event == Event.actionCallDecline) { | |
| friendCallingController.onDeclinedCall( | |
| Map<String, dynamic>.from(event.body['extra']), | |
| ); | |
| FlutterCallkitIncoming.showMissCallNotification(CallKitParams()); | |
| } | |
| }); | |
| } | |
| FlutterCallkitIncoming.onEvent.listen((CallEvent? event) { | |
| if (event == null) return; | |
| if (event.event == Event.actionCallAccept) { | |
| log(event.body['extra'].toString()); | |
| final extra = event.body['extra']; | |
| if (extra is Map) { | |
| friendCallingController.onAnswerCall( | |
| Map<String, dynamic>.from(extra as Map), | |
| ); | |
| } | |
| } | |
| if (event.event == Event.actionCallDecline) { | |
| final extra = event.body['extra']; | |
| if (extra is Map) { | |
| friendCallingController.onDeclinedCall( | |
| Map<String, dynamic>.from(extra as Map), | |
| ); | |
| } | |
| FlutterCallkitIncoming.showMissCallNotification(CallKitParams()); | |
| } | |
| }); | |
| } |
🤖 Prompt for AI Agents
In lib/controllers/auth_state_controller.dart around lines 169 to 184, the code
force-unwraps event and directly accesses event.body['extra'], which can be null
and crash; add null-safety guards: first check if event is null and return
early, then ensure event.body is non-null and contains a non-null 'extra' entry
of the expected Map type before calling Map.from; for missing/invalid extras,
log a warning and skip the call handling (or handle gracefully), and use
null-aware operators (?. and ??) and type checks (is Map) to avoid runtime
exceptions.
| Document userDataDoc = await databases.getDocument( | ||
| databaseId: userDatabaseID, | ||
| collectionId: usersCollectionID, | ||
| documentId: appwriteUser.$id); | ||
| databaseId: userDatabaseID, | ||
| collectionId: usersCollectionID, | ||
| documentId: appwriteUser.$id, | ||
| ); | ||
| profileImageUrl = userDataDoc.data["profileImageUrl"]; | ||
| profileImageID = userDataDoc.data["profileImageID"]; | ||
| userName = userDataDoc.data["username"] ?? "unavailable"; | ||
| ratingTotal = userDataDoc.data["ratingTotal"].toDouble() ?? 5; | ||
| ratingCount = userDataDoc.data["ratingCount"] ?? 1; | ||
| followerDocuments = | ||
| (userDataDoc.data["followers"] as List<dynamic>?)?.map((e) { | ||
| return FollowerUserModel.fromJson(e); | ||
| }).toList() ?? | ||
| []; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ratingTotal null/shape handling will throw.
userDataDoc.data["ratingTotal"].toDouble() calls .toDouble() on null/non-num. Also guard zero ratingCount.
- ratingTotal = userDataDoc.data["ratingTotal"].toDouble() ?? 5;
- ratingCount = userDataDoc.data["ratingCount"] ?? 1;
+ final rt = userDataDoc.data["ratingTotal"];
+ ratingTotal = (rt is num) ? rt.toDouble() : 5.0;
+ final rc = userDataDoc.data["ratingCount"];
+ ratingCount = (rc is int && rc > 0) ? rc : 1;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Document userDataDoc = await databases.getDocument( | |
| databaseId: userDatabaseID, | |
| collectionId: usersCollectionID, | |
| documentId: appwriteUser.$id); | |
| databaseId: userDatabaseID, | |
| collectionId: usersCollectionID, | |
| documentId: appwriteUser.$id, | |
| ); | |
| profileImageUrl = userDataDoc.data["profileImageUrl"]; | |
| profileImageID = userDataDoc.data["profileImageID"]; | |
| userName = userDataDoc.data["username"] ?? "unavailable"; | |
| ratingTotal = userDataDoc.data["ratingTotal"].toDouble() ?? 5; | |
| ratingCount = userDataDoc.data["ratingCount"] ?? 1; | |
| followerDocuments = | |
| (userDataDoc.data["followers"] as List<dynamic>?)?.map((e) { | |
| return FollowerUserModel.fromJson(e); | |
| }).toList() ?? | |
| []; | |
| } | |
| Document userDataDoc = await databases.getDocument( | |
| databaseId: userDatabaseID, | |
| collectionId: usersCollectionID, | |
| documentId: appwriteUser.$id, | |
| ); | |
| profileImageUrl = userDataDoc.data["profileImageUrl"]; | |
| profileImageID = userDataDoc.data["profileImageID"]; | |
| userName = userDataDoc.data["username"] ?? "unavailable"; | |
| final rt = userDataDoc.data["ratingTotal"]; | |
| ratingTotal = (rt is num) ? rt.toDouble() : 5.0; | |
| final rc = userDataDoc.data["ratingCount"]; | |
| ratingCount = (rc is int && rc > 0) ? rc : 1; | |
| followerDocuments = | |
| (userDataDoc.data["followers"] as List<dynamic>?)?.map((e) { | |
| return FollowerUserModel.fromJson(e); | |
| }).toList() ?? | |
| []; | |
| } |
🤖 Prompt for AI Agents
In lib/controllers/auth_state_controller.dart around lines 207 to 222, the code
directly calls .toDouble() on userDataDoc.data["ratingTotal"] and assumes
ratingCount is non-zero; update to defensively read these fields by checking for
null and type before converting: read ratingTotal into a local variable, if it's
a num use .toDouble() otherwise fall back to a sensible default (e.g. 5.0); read
ratingCount into a local variable, if it's an int/num use its integer value and
if null or <= 0 set it to 1 to avoid divide-by-zero or invalid state; replace
the existing assignments with these validated values and keep follower parsing
unchanged.
| Future<void> removeRegistrationTokenFromSubscribedUpcomingRooms() async { | ||
| final fcmToken = await messaging.getToken(); | ||
|
|
||
| //subscribed Upcoming Rooms | ||
| List<Document> subscribedUpcomingRooms = await databases.listDocuments( | ||
| databaseId: upcomingRoomsDatabaseId, | ||
| collectionId: subscribedUserCollectionId, | ||
| queries: [ | ||
| Query.equal("userID", [uid]) | ||
| ]).then((value) => value.documents); | ||
| List<Document> subscribedUpcomingRooms = await databases | ||
| .listDocuments( | ||
| databaseId: upcomingRoomsDatabaseId, | ||
| collectionId: subscribedUserCollectionId, | ||
| queries: [ | ||
| Query.equal("userID", [uid]), | ||
| ], | ||
| ) | ||
| .then((value) => value.documents); | ||
| for (var subscription in subscribedUpcomingRooms) { | ||
| List<dynamic> registrationTokens = | ||
| subscription.data['registrationTokens']; | ||
| registrationTokens.remove(fcmToken!); | ||
| databases.updateDocument( | ||
| databaseId: upcomingRoomsDatabaseId, | ||
| collectionId: subscribedUserCollectionId, | ||
| documentId: subscription.$id, | ||
| data: {"registrationTokens": registrationTokens}); | ||
| databaseId: upcomingRoomsDatabaseId, | ||
| collectionId: subscribedUserCollectionId, | ||
| documentId: subscription.$id, | ||
| data: {"registrationTokens": registrationTokens}, | ||
| ); | ||
| } | ||
|
|
||
|
|
||
| //created Upcoming Rooms | ||
| List<Document> createdUpcomingRooms = await databases.listDocuments( | ||
| databaseId: upcomingRoomsDatabaseId, | ||
| collectionId: upcomingRoomsCollectionId, | ||
| queries: [ | ||
| Query.equal("creatorUid", [uid]) | ||
| ]).then((value) => value.documents); | ||
| List<Document> createdUpcomingRooms = await databases | ||
| .listDocuments( | ||
| databaseId: upcomingRoomsDatabaseId, | ||
| collectionId: upcomingRoomsCollectionId, | ||
| queries: [ | ||
| Query.equal("creatorUid", [uid]), | ||
| ], | ||
| ) | ||
| .then((value) => value.documents); | ||
| for (var upcomingRoom in createdUpcomingRooms) { | ||
| List<dynamic> creatorFcmTokens = | ||
| upcomingRoom.data['creator_fcm_tokens']; | ||
| List<dynamic> creatorFcmTokens = upcomingRoom.data['creator_fcm_tokens']; | ||
| creatorFcmTokens.remove(fcmToken!); | ||
| databases.updateDocument( | ||
| databaseId: upcomingRoomsDatabaseId, | ||
| collectionId: upcomingRoomsCollectionId, | ||
| documentId: upcomingRoom.$id, | ||
| data: {"creator_fcm_tokens": creatorFcmTokens}); | ||
| databaseId: upcomingRoomsDatabaseId, | ||
| collectionId: upcomingRoomsCollectionId, | ||
| documentId: upcomingRoom.$id, | ||
| data: {"creator_fcm_tokens": creatorFcmTokens}, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issues on removal: null token and missed awaits.
- final fcmToken = await messaging.getToken();
+ final fcmToken = await messaging.getToken();
+ if (fcmToken == null) return;
...
- registrationTokens.remove(fcmToken!);
- databases.updateDocument(
+ registrationTokens.remove(fcmToken);
+ await databases.updateDocument(
databaseId: upcomingRoomsDatabaseId,
collectionId: subscribedUserCollectionId,
documentId: subscription.$id,
data: {"registrationTokens": registrationTokens},
);
...
- creatorFcmTokens.remove(fcmToken!);
- databases.updateDocument(
+ creatorFcmTokens.remove(fcmToken);
+ await databases.updateDocument(
databaseId: upcomingRoomsDatabaseId,
collectionId: upcomingRoomsCollectionId,
documentId: upcomingRoom.$id,
data: {"creator_fcm_tokens": creatorFcmTokens},
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Future<void> removeRegistrationTokenFromSubscribedUpcomingRooms() async { | |
| final fcmToken = await messaging.getToken(); | |
| //subscribed Upcoming Rooms | |
| List<Document> subscribedUpcomingRooms = await databases.listDocuments( | |
| databaseId: upcomingRoomsDatabaseId, | |
| collectionId: subscribedUserCollectionId, | |
| queries: [ | |
| Query.equal("userID", [uid]) | |
| ]).then((value) => value.documents); | |
| List<Document> subscribedUpcomingRooms = await databases | |
| .listDocuments( | |
| databaseId: upcomingRoomsDatabaseId, | |
| collectionId: subscribedUserCollectionId, | |
| queries: [ | |
| Query.equal("userID", [uid]), | |
| ], | |
| ) | |
| .then((value) => value.documents); | |
| for (var subscription in subscribedUpcomingRooms) { | |
| List<dynamic> registrationTokens = | |
| subscription.data['registrationTokens']; | |
| registrationTokens.remove(fcmToken!); | |
| databases.updateDocument( | |
| databaseId: upcomingRoomsDatabaseId, | |
| collectionId: subscribedUserCollectionId, | |
| documentId: subscription.$id, | |
| data: {"registrationTokens": registrationTokens}); | |
| databaseId: upcomingRoomsDatabaseId, | |
| collectionId: subscribedUserCollectionId, | |
| documentId: subscription.$id, | |
| data: {"registrationTokens": registrationTokens}, | |
| ); | |
| } | |
| //created Upcoming Rooms | |
| List<Document> createdUpcomingRooms = await databases.listDocuments( | |
| databaseId: upcomingRoomsDatabaseId, | |
| collectionId: upcomingRoomsCollectionId, | |
| queries: [ | |
| Query.equal("creatorUid", [uid]) | |
| ]).then((value) => value.documents); | |
| List<Document> createdUpcomingRooms = await databases | |
| .listDocuments( | |
| databaseId: upcomingRoomsDatabaseId, | |
| collectionId: upcomingRoomsCollectionId, | |
| queries: [ | |
| Query.equal("creatorUid", [uid]), | |
| ], | |
| ) | |
| .then((value) => value.documents); | |
| for (var upcomingRoom in createdUpcomingRooms) { | |
| List<dynamic> creatorFcmTokens = | |
| upcomingRoom.data['creator_fcm_tokens']; | |
| List<dynamic> creatorFcmTokens = upcomingRoom.data['creator_fcm_tokens']; | |
| creatorFcmTokens.remove(fcmToken!); | |
| databases.updateDocument( | |
| databaseId: upcomingRoomsDatabaseId, | |
| collectionId: upcomingRoomsCollectionId, | |
| documentId: upcomingRoom.$id, | |
| data: {"creator_fcm_tokens": creatorFcmTokens}); | |
| databaseId: upcomingRoomsDatabaseId, | |
| collectionId: upcomingRoomsCollectionId, | |
| documentId: upcomingRoom.$id, | |
| data: {"creator_fcm_tokens": creatorFcmTokens}, | |
| ); | |
| } | |
| Future<void> removeRegistrationTokenFromSubscribedUpcomingRooms() async { | |
| final fcmToken = await messaging.getToken(); | |
| if (fcmToken == null) return; | |
| //subscribed Upcoming Rooms | |
| List<Document> subscribedUpcomingRooms = await databases | |
| .listDocuments( | |
| databaseId: upcomingRoomsDatabaseId, | |
| collectionId: subscribedUserCollectionId, | |
| queries: [ | |
| Query.equal("userID", [uid]), | |
| ], | |
| ) | |
| .then((value) => value.documents); | |
| for (var subscription in subscribedUpcomingRooms) { | |
| List<dynamic> registrationTokens = | |
| subscription.data['registrationTokens']; | |
| registrationTokens.remove(fcmToken); | |
| await databases.updateDocument( | |
| databaseId: upcomingRoomsDatabaseId, | |
| collectionId: subscribedUserCollectionId, | |
| documentId: subscription.$id, | |
| data: {"registrationTokens": registrationTokens}, | |
| ); | |
| } | |
| //created Upcoming Rooms | |
| List<Document> createdUpcomingRooms = await databases | |
| .listDocuments( | |
| databaseId: upcomingRoomsDatabaseId, | |
| collectionId: upcomingRoomsCollectionId, | |
| queries: [ | |
| Query.equal("creatorUid", [uid]), | |
| ], | |
| ) | |
| .then((value) => value.documents); | |
| for (var upcomingRoom in createdUpcomingRooms) { | |
| List<dynamic> creatorFcmTokens = upcomingRoom.data['creator_fcm_tokens']; | |
| creatorFcmTokens.remove(fcmToken); | |
| await databases.updateDocument( | |
| databaseId: upcomingRoomsDatabaseId, | |
| collectionId: upcomingRoomsCollectionId, | |
| documentId: upcomingRoom.$id, | |
| data: {"creator_fcm_tokens": creatorFcmTokens}, | |
| ); | |
| } | |
| } |
🤖 Prompt for AI Agents
In lib/controllers/auth_state_controller.dart around lines 317 to 361, the code
can call messaging.getToken() which may return null and it also calls
databases.updateDocument without awaiting the futures; fix by first checking if
fcmToken is null and return early (or skip removals) to avoid null removal,
ensure registrationTokens/creator_fcm_tokens are non-null Lists before calling
remove (initialize to empty list if missing), perform removal only when token
and lists are valid, and await each databases.updateDocument call (or collect
futures and await Future.wait) so updates complete before the function returns.
| ResonateUser senderToResonateUserForRequestsPage() { | ||
| return ResonateUser( | ||
| uid: senderId, | ||
| userName: senderName, | ||
| profileImageUrl: senderProfileImgUrl, | ||
| name: senderName, | ||
| docId: docId, | ||
|
|
||
| userRating: senderRating!, | ||
| // Note: email, dateOfBirth are not available in FollowerUserModel | ||
| // so they will be null in the converted ResonateUser | ||
| email: null, | ||
| dateOfBirth: null, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mirror fixes for sender side.
Apply this diff:
ResonateUser senderToResonateUserForRequestsPage() {
return ResonateUser(
uid: senderId,
- userName: senderName,
+ userName: senderUsername,
profileImageUrl: senderProfileImgUrl,
name: senderName,
- docId: docId,
-
- userRating: senderRating!,
+ docId: senderId,
+ userRating: senderRating,
// Note: email, dateOfBirth are not available in FollowerUserModel
// so they will be null in the converted ResonateUser
email: null,
dateOfBirth: null,
);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ResonateUser senderToResonateUserForRequestsPage() { | |
| return ResonateUser( | |
| uid: senderId, | |
| userName: senderName, | |
| profileImageUrl: senderProfileImgUrl, | |
| name: senderName, | |
| docId: docId, | |
| userRating: senderRating!, | |
| // Note: email, dateOfBirth are not available in FollowerUserModel | |
| // so they will be null in the converted ResonateUser | |
| email: null, | |
| dateOfBirth: null, | |
| ); | |
| } | |
| ResonateUser senderToResonateUserForRequestsPage() { | |
| return ResonateUser( | |
| uid: senderId, | |
| userName: senderUsername, | |
| profileImageUrl: senderProfileImgUrl, | |
| name: senderName, | |
| docId: senderId, | |
| userRating: senderRating, | |
| // Note: email, dateOfBirth are not available in FollowerUserModel | |
| // so they will be null in the converted ResonateUser | |
| email: null, | |
| dateOfBirth: null, | |
| ); | |
| } |
🤖 Prompt for AI Agents
lib/models/friends_model.dart around lines 49 to 63: mirror the same fixes
applied to the receiver conversion by updating
senderToResonateUserForRequestsPage to use the exact field mappings and
null-safety used in the receiver-side method — map uid->senderId,
userName->senderName, profileImageUrl->senderProfileImgUrl, name->senderName,
docId->docId, set userRating using a null-safe expression (e.g. senderRating ??
default or nullable type rather than forcing with !), and explicitly set email
and dateOfBirth to null with a comment; make the function consistent with the
receiver conversion implementation.
| Message.fromJson(Map<String, dynamic> json) | ||
| : roomId = json['roomId'], | ||
| messageId = json['messageId'], | ||
| creatorId = json['creatorId'], | ||
| creatorUsername = json['creatorUsername'], | ||
| creatorName = json['creatorName'], | ||
| creatorImgUrl = json['creatorImgUrl'], | ||
| hasValidTag = json['hasValidTag'], | ||
| index = json['index'], | ||
| isEdited = json['isEdited'], | ||
| content = json['content'], | ||
| creationDateTime = json['creationDateTime'], | ||
| replyTo = json['replyTo'] != null ? ReplyTo.fromJson(json['replyTo']) : null; | ||
| : roomId = json['roomId'], | ||
| messageId = json['messageId'], | ||
| creatorId = json['creatorId'], | ||
| creatorUsername = json['creatorUsername'], | ||
| creatorName = json['creatorName'], | ||
| creatorImgUrl = json['creatorImgUrl'], | ||
| hasValidTag = json['hasValidTag'], | ||
| index = json['index'], | ||
| isEdited = json['isEdited'], | ||
| content = json['content'], | ||
| creationDateTime = DateTime.parse(json['creationDateTime']), | ||
| replyTo = json['replyTo'] != null | ||
| ? ReplyTo.fromJson(json['replyTo']) | ||
| : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Harden fromJson parsing and normalize to UTC.
Guard types and ensure consistent UTC to match serialization. Also coerce numeric types safely.
- Message.fromJson(Map<String, dynamic> json)
- : roomId = json['roomId'],
- messageId = json['messageId'],
- creatorId = json['creatorId'],
- creatorUsername = json['creatorUsername'],
- creatorName = json['creatorName'],
- creatorImgUrl = json['creatorImgUrl'],
- hasValidTag = json['hasValidTag'],
- index = json['index'],
- isEdited = json['isEdited'],
- content = json['content'],
- creationDateTime = DateTime.parse(json['creationDateTime']),
- replyTo = json['replyTo'] != null
- ? ReplyTo.fromJson(json['replyTo'])
- : null;
+ Message.fromJson(Map<String, dynamic> json)
+ : roomId = json['roomId'] as String,
+ messageId = json['messageId'] as String,
+ creatorId = json['creatorId'] as String,
+ creatorUsername = json['creatorUsername'] as String,
+ creatorName = json['creatorName'] as String,
+ creatorImgUrl = json['creatorImgUrl'] as String,
+ hasValidTag = (json['hasValidTag'] as bool?) ?? false,
+ index = (json['index'] is int)
+ ? json['index'] as int
+ : (json['index'] as num).toInt(),
+ isEdited = (json['isEdited'] as bool?) ?? false,
+ content = json['content'] as String,
+ creationDateTime =
+ DateTime.parse(json['creationDateTime'] as String).toUtc(),
+ replyTo = (json['replyTo'] != null && json['replyTo'] is Map)
+ ? ReplyTo.fromJson(json['replyTo'] as Map<String, dynamic>)
+ : null;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Message.fromJson(Map<String, dynamic> json) | |
| : roomId = json['roomId'], | |
| messageId = json['messageId'], | |
| creatorId = json['creatorId'], | |
| creatorUsername = json['creatorUsername'], | |
| creatorName = json['creatorName'], | |
| creatorImgUrl = json['creatorImgUrl'], | |
| hasValidTag = json['hasValidTag'], | |
| index = json['index'], | |
| isEdited = json['isEdited'], | |
| content = json['content'], | |
| creationDateTime = json['creationDateTime'], | |
| replyTo = json['replyTo'] != null ? ReplyTo.fromJson(json['replyTo']) : null; | |
| : roomId = json['roomId'], | |
| messageId = json['messageId'], | |
| creatorId = json['creatorId'], | |
| creatorUsername = json['creatorUsername'], | |
| creatorName = json['creatorName'], | |
| creatorImgUrl = json['creatorImgUrl'], | |
| hasValidTag = json['hasValidTag'], | |
| index = json['index'], | |
| isEdited = json['isEdited'], | |
| content = json['content'], | |
| creationDateTime = DateTime.parse(json['creationDateTime']), | |
| replyTo = json['replyTo'] != null | |
| ? ReplyTo.fromJson(json['replyTo']) | |
| : null; | |
| Message.fromJson(Map<String, dynamic> json) | |
| : roomId = json['roomId'] as String, | |
| messageId = json['messageId'] as String, | |
| creatorId = json['creatorId'] as String, | |
| creatorUsername = json['creatorUsername'] as String, | |
| creatorName = json['creatorName'] as String, | |
| creatorImgUrl = json['creatorImgUrl'] as String, | |
| hasValidTag = (json['hasValidTag'] as bool?) ?? false, | |
| index = (json['index'] is int) | |
| ? json['index'] as int | |
| : (json['index'] as num).toInt(), | |
| isEdited = (json['isEdited'] as bool?) ?? false, | |
| content = json['content'] as String, | |
| creationDateTime = | |
| DateTime.parse(json['creationDateTime'] as String).toUtc(), | |
| replyTo = (json['replyTo'] != null && json['replyTo'] is Map) | |
| ? ReplyTo.fromJson(json['replyTo'] as Map<String, dynamic>) | |
| : null; |
🤖 Prompt for AI Agents
In lib/models/message.dart around lines 65 to 79, the Message.fromJson
initializer assumes input types and local time for dates; update it to validate
and coerce each field (check json['roomId'], ['messageId'], ['creatorId'],
['creatorUsername'], ['creatorName'], ['creatorImgUrl'], and ['content'] are
Strings or fallback to empty/null as appropriate), ensure hasValidTag and
isEdited are booleans (convert numeric/strings to bool safely), coerce index to
an int (handle if it's double or string), parse creationDateTime defensively and
call toUtc() on the resulting DateTime (handle null/invalid by using
DateTime.now().toUtc() or null per model contract), and only call
ReplyTo.fromJson when replyTo is a Map<String, dynamic>; apply these guards so
deserialization is robust against wrong types.
| docId: json['docId'] as String?, | ||
| userRating: toDouble(json['userRating']), | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doc ID mapping likely wrong for Appwrite ($id vs docId). Update source model and regen.
If your backend provides $id, reading json['docId'] will be null. Adjust annotations in resonate_user.dart and re-generate.
Apply this change in resonate_user.dart (source), then run build_runner:
// in resonate_user.dart
@JsonSerializable()
class ResonateUser {
// ...
- final String? docId;
+ @JsonKey(name: r'$id', includeToJson: false)
+ final String? docId;
// ...
}Then re-run:
- flutter pub run build_runner build --delete-conflicting-outputs
Also applies to: 29-31
<details>
<summary>🤖 Prompt for AI Agents</summary>
In lib/models/resonate_user.g.dart around lines 17-19 (and similarly lines
29-31), the generated code is reading json['docId'] which will be null when the
backend provides $id; fix this by updating the source model resonate_user.dart
to annotate the docId field with @jsonkey(name: r'$id', includeToJson: false) so
the generator maps $id → docId, then re-run code generation with flutter pub run
build_runner build --delete-conflicting-outputs to regenerate
resonate_user.g.dart.
</details>
<!-- fingerprinting:phantom:medusa:chinchilla -->
<!-- This is an auto-generated comment by CodeRabbit -->
| GetPage(name: AppRoutes.loginScreen, page: () => const LoginScreen()), | ||
| GetPage(name: AppRoutes.homeScreen, page: () => const HomeScreen()), | ||
| GetPage(name: AppRoutes.themeScreen, page: () => ThemeScreen()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate Login route; later entry drops the binding.
Keep the earlier definition with AuthSplashBinding and remove the later one.
- GetPage(name: AppRoutes.loginScreen, page: () => const LoginScreen()),Also applies to: 60-63
🤖 Prompt for AI Agents
In lib/routes/app_pages.dart around lines 116-118 (and also apply the same
change to lines 60-63), there's a duplicate GetPage for the login route that
omits the AuthSplashBinding; remove the later duplicate GetPage entry(s) for
AppRoutes.loginScreen and keep the earlier definition which includes
AuthSplashBinding so the binding is preserved.
| // Delete room on livekit and roomdoc on appwrite | ||
| String? livekitToken = await storage.read(key: "createdRoomAdminToken"); | ||
| await apiService.deleteRoom(roomId, livekitToken); | ||
| await apiService.deleteRoom(roomId, livekitToken!); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential null crash on livekitToken!.
Token may be absent (e.g., storage cleared). Guard and handle gracefully.
String? livekitToken = await storage.read(key: "createdRoomAdminToken");
-await apiService.deleteRoom(roomId, livekitToken!);
+if (livekitToken == null) {
+ // Optionally log/return; or reauth to get a fresh admin token
+ return;
+}
+await apiService.deleteRoom(roomId, livekitToken);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Delete room on livekit and roomdoc on appwrite | |
| String? livekitToken = await storage.read(key: "createdRoomAdminToken"); | |
| await apiService.deleteRoom(roomId, livekitToken); | |
| await apiService.deleteRoom(roomId, livekitToken!); | |
| // Delete room on livekit and roomdoc on appwrite | |
| String? livekitToken = await storage.read(key: "createdRoomAdminToken"); | |
| if (livekitToken == null) { | |
| // Optionally log/return; or reauth to get a fresh admin token | |
| return; | |
| } | |
| await apiService.deleteRoom(roomId, livekitToken); |
🤖 Prompt for AI Agents
In lib/services/room_service.dart around lines 124 to 127, the code
force-unwraps livekitToken with livekitToken! which can crash if storage returns
null; instead check if livekitToken is null before calling apiService.deleteRoom
and handle it gracefully (e.g., if null, log a warning/error, return a failed
result or throw a controlled exception, or attempt to refresh/retrieve a valid
token), and only call apiService.deleteRoom when a non-null token is available
to avoid runtime exceptions.
Description
This PR merges all recent changes over the past few months into master to deploy v2 of Resonate App to the Play Store
Fixes #533
Type of change
Please delete options that are not relevant.
How Has This Been Tested?
All Software tests were run, app was thoroughly run and tested
Checklist:
Maintainer Checklist
Summary by CodeRabbit
New Features
Improvements
Documentation
Chores