Skip to content

Conversation

@M4dhav
Copy link
Contributor

@M4dhav M4dhav commented Oct 12, 2025

Description

This PR merges previous changes into main and deploys v2.5 of Resonate which include the new features of Improved Host Moderation Controls and Live Chapters with automated transcriptions.

Type of change

Please delete options that are not relevant.

  • App Update

How Has This Been Tested?

Software tests are passing and app features are verified to be working

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules
  • I have checked my code and corrected any misspellings

Maintainer Checklist

  • Tag the PR with the appropriate labels

Summary by CodeRabbit

  • New Features

    • Live Chapters: start/join live sessions with attendee tracking, mic & recording controls, dedicated screen, and create chapters from live sessions.
    • Transcription: automated audio-to-LRC transcription for chapters.
    • Reporting: report participants via a Report dialog and a user-blocked notice screen.
  • Localization

    • Added Gujarati locale and expanded English/Hindi localization metadata.
  • Enhancements

    • Stories surface live chapters, improved sharing, and rooms hide participants you’ve reported.
  • Tests

    • Added assertion for user report count initialization.

Mayank4352 and others added 30 commits September 16, 2025 14:30
Feat: Added metadata to all keys and removed duplicate translations
fix: Updated the deprecated code and removed unused code
feat: updated the UX issue
- Added complete translation for Gujarati (gu)
- Added app_gu.arb and app_localizations_gu.dart
- Updated supportedLocales in main.dart
- Added Gujarati to iOS CFBundleLocalizations
feat: add Gujarati translation support
@M4dhav M4dhav self-assigned this Oct 12, 2025
@M4dhav M4dhav added gsoc Part of Google Summer of Code season app-update labels Oct 12, 2025
@github-actions
Copy link
Contributor

🎉 Welcome @M4dhav!
Thank you for your pull request! Our team will review it soon. 🔍

  • Please ensure your PR follows the contribution guidelines. ✅
  • All automated tests should pass before merging. 🔄
  • If this PR fixes an issue, link it in the description. 🔗

We appreciate your contribution! 🚀

@coderabbitai
Copy link

coderabbitai bot commented Oct 12, 2025

Walkthrough

Adds Live Chapters and reporting/moderation features, Gujarati localization, new models/controllers/widgets/screens, recording & transcription integrations, reportedUsers room filtering, API/RoomService extensions, dependency updates, and multiple i18n key changes.

Changes

Cohort / File(s) Summary
Live Chapters — Controllers & Services
lib/controllers/live_chapter_controller.dart, lib/controllers/livekit_controller.dart, lib/controllers/explore_story_controller.dart, lib/controllers/whisper_transcription_controller.dart, lib/services/room_service.dart, lib/services/api_service.dart
New LiveChapterController for lifecycle/attendees/media; LiveKitController gains recording; ExploreStoryController fetches live chapter; WhisperTranscriptionController adds mp4→wav→transcribe→LRC; RoomService/ApiService add create/join/delete live-chapter APIs.
Live Chapters — Models
lib/models/live_chapter_model.dart, lib/models/live_chapter_model.*.dart, lib/models/live_chapter_attendees_model.dart, lib/models/live_chapter_attendees_model.*.dart, lib/models/story.dart
Adds LiveChapterModel and LiveChapterAttendeesModel (Freezed + JSON); Story gains nullable liveChapter field; generated Freezed/g.dart parts included.
Live Chapters — UI
lib/views/screens/live_chapter_screen.dart, lib/views/widgets/live_chapter_attendee_block.dart, lib/views/widgets/live_chapter_header.dart, lib/views/widgets/live_chapter_list_tile.dart, lib/views/widgets/start_live_chapter_dialog.dart, lib/views/screens/verify_chapter_details_screen.dart, lib/views/screens/story_screen.dart
New LiveChapter screen, attendee/header/list-tile widgets, start dialog, verify-chapter flow, and story-screen integration to show/join live chapters and start live for creators.
Reporting / Moderation
lib/views/widgets/report_widget.dart, lib/controllers/single_room_controller.dart, lib/views/widgets/participant_block.dart, lib/models/user_report_model.dart, lib/models/user_report_model.*.dart, lib/utils/enums/report_type_enum.dart, lib/controllers/auth_state_controller.dart, lib/views/screens/user_blocked_screen.dart, lib/routes/app_pages.dart, lib/routes/app_routes.dart
Adds reporting UI (ReportWidget), UserReportModel, ReportTypeEnum, reportParticipant flow and room document update, reportedUsers tracking, AuthStateController.reportsCount, userBlocked route/screen and route registrations.
Rooms model and filtering
lib/models/appwrite_room.dart, lib/controllers/create_room_controller.dart, lib/controllers/rooms_controller.dart, lib/views/screens/home_screen.dart
AppwriteRoom adds reportedUsers field (required); room creation populates reportedUsers: []; RoomsController filters rooms where current user is reported; home screen sample data updated.
Localization & i18n
lib/l10n/app_gu.arb, lib/l10n/app_en.arb, lib/l10n/app_hi.arb, lib/l10n/app_localizations.dart, lib/l10n/app_localizations_en.dart, lib/l10n/app_localizations_hi.dart, lib/l10n/app_localizations_gu.dart, l10n.yaml, untranslated.txt
Adds Gujarati ARB and generated/localization class, wires Locale('gu') into supportedLocales and resolver, extensive new/renamed localization keys and @description metadata (e.g., pushNewChaptersnewChapters) and l10n.yaml supported-locales update.
UI copy and sharing updates
lib/views/screens/about_app_screen.dart, lib/views/screens/add_chapter_screen.dart, lib/views/screens/chapter_play_screen.dart, lib/views/screens/edit_profile_screen.dart, lib/views/widgets/live_room_tile.dart
Replaces Share.share with SharePlus.instance.share(ShareParams), updates localization key usages (about→title, pushNewChapters→newChapters, camera/gallery labels).
Misc controllers & screens
lib/controllers/about_app_screen_controller.dart, lib/views/screens/email_verification_screen.dart, lib/views/screens/tabview_screen.dart, lib/views/widgets/chapter_player.dart, lib/themes/theme_controller.dart
AboutAppScreenController triggers background update check on init; email verification keys updated (with a noted ternary color expression regression); tabview logging and color alpha tweaks; chapter player gradient uses withAlpha; theme controller removes unused private keys.
App setup & platform
lib/main.dart, ios/Runner/Info.plist, lib/utils/constants.dart, pubspec.yaml, l10n.yaml
Adds Locale('gu') to app, Info.plist CFBundleLocalizations adds gu, constants updated (baseDomain and new collection/function IDs), pubspec version bump and new dependencies: flutter_webrtc, path_provider, whisper_flutter_new, ffmpeg_kit_flutter_new.
Routing additions
lib/routes/app_pages.dart, lib/routes/app_routes.dart
Registers liveChapterScreen and userBlockedScreen routes and new route constants.
Tests & metadata
test/controllers/auth_state_controller_test.dart, test/controllers/friends_controller_test.dart, project_structure.txt
Test updated to assert reportsCount == 0; removed unused import in friends test; project structure updated to reflect new files.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Creator
  participant StoryScreen
  participant StartDialog as StartLiveChapterDialog
  participant LiveChapterCtrl as LiveChapterController
  participant Appwrite
  participant RoomService
  participant ApiService
  participant LiveKit

  Creator->>StartDialog: fill title/desc, start
  StartDialog->>LiveChapterCtrl: startLiveChapter(...)
  LiveChapterCtrl->>Appwrite: create liveChapter doc
  LiveChapterCtrl->>RoomService: createLiveChapterRoom(appwriteId, adminUid)
  RoomService->>ApiService: createLiveChapterRoom(...)
  ApiService-->>RoomService: return token,url
  RoomService->>LiveKit: joinLiveKitRoom(url, token, isLiveChapter: true)
  LiveChapterCtrl->>Appwrite: create attendees doc
  LiveChapterCtrl->>StoryScreen: navigate to LiveChapterScreen
Loading
sequenceDiagram
  autonumber
  actor Listener
  participant LiveTile as LiveChapterListTile
  participant LiveChapterCtrl as LiveChapterController
  participant Appwrite
  participant RoomService
  participant ApiService
  participant LiveKit

  Listener->>LiveTile: tap to join
  LiveTile->>LiveChapterCtrl: joinLiveChapter(liveChapterId)
  LiveChapterCtrl->>Appwrite: update attendees
  LiveChapterCtrl->>RoomService: joinLiveChapterRoom(roomId, userId)
  RoomService->>ApiService: get token,url
  ApiService-->>RoomService: token,url
  RoomService->>LiveKit: joinLiveKitRoom(url, token, isLiveChapter: true)
  LiveChapterCtrl->>LiveTile: navigate to LiveChapterScreen
Loading
sequenceDiagram
  autonumber
  actor User
  participant ParticipantMenu
  participant SingleRoomCtrl
  participant ReportWidget
  participant AuthCtrl
  participant Appwrite

  User->>ParticipantMenu: select "Report Participant"
  ParticipantMenu->>SingleRoomCtrl: reportParticipant(participant)
  SingleRoomCtrl->>ReportWidget: open dialog
  User->>ReportWidget: select type + submit
  ReportWidget->>AuthCtrl: build UserReportModel
  ReportWidget->>Appwrite: createDocument(userReportsCollection)
  Appwrite-->>ReportWidget: success
  ReportWidget-->>SingleRoomCtrl: result true
  SingleRoomCtrl->>Appwrite: update room.reportedUsers, kick participant
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • kmk142789

Poem

Hop hop, a chapter goes live tonight,
I twitch my ears to recording’s light.
Reports filed, Gujarātī words take flight,
I share a link, nibble code by moonlight. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the primary objective of this changeset as a version release of the app, clearly indicating the deployment of v2.5 without extraneous detail.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 21

🧹 Nitpick comments (19)
lib/views/widgets/chapter_player.dart (1)

25-30: LGTM! Consider simplifying the alpha calculation.

The conversion from withOpacity to withAlpha is correct and preserves the intended visual behavior. The alpha values are computed accurately (204 and 76 for progress < 0.75, 255 otherwise).

However, the ~/ 1 operation is redundant—it performs integer division by 1, which is effectively a floor operation. Consider simplifying:

             chapter.tintColor.withAlpha(
-              (progress < 0.75 ? 0.8 : 1) * 255 ~/ 1,
+              ((progress < 0.75 ? 0.8 : 1) * 255).toInt(),
             ),
             chapter.tintColor.withAlpha(
-              (progress < 0.75 ? 0.3 : 1) * 255 ~/ 1,
+              ((progress < 0.75 ? 0.3 : 1) * 255).toInt(),
             ),
lib/views/screens/tabview_screen.dart (1)

175-175: Consider structured logging for production error handling.

While replacing print with log is an improvement for debugging, consider using a structured logging framework (e.g., logger package) for production environments. This would provide better log levels, formatting, and filtering capabilities.

Example with the logger package:

final logger = Logger();
// In catch block:
logger.error('Room creation error', error: e, stackTrace: stackTrace);
lib/controllers/auth_state_controller.dart (1)

239-242: Consider making the user blocking threshold configurable.

The hard-coded threshold of 5 reports for blocking users is a magic number that could benefit from being a named constant or configuration value. This would improve maintainability and allow easier threshold adjustments without code changes.

Consider extracting to a constant:

static const int USER_BLOCK_THRESHOLD = 5;

// Later in isUserLoggedIn:
if (reportsCount > USER_BLOCK_THRESHOLD) {
  Get.offNamed(AppRoutes.userBlockedScreen);
  return;
}

Additionally, please verify:

  1. Is a threshold of 5 reports appropriate for your moderation policy?
  2. Should users receive warnings before being blocked?
  3. Is there an appeal or review process for blocked users?
lib/controllers/about_app_screen_controller.dart (1)

41-41: Leverage Upgrader caching and surface init-phase errors

  • checkForUpdate in onInit is asynchronous and runs only once via the singleton—no repeated init/performance impact.
  • Defaults to clearSettings = true, which resets Upgrader’s saved state and bypasses durationUntilAlertAgain; set clearSettings: false for init or manual checks to respect the retry interval and reduce network calls.
  • Init-phase failures are only logged; manual checks show a snackbar on checkFailed. Add UI feedback for errors during the automatic init check if needed.
lib/views/widgets/participant_block.dart (1)

84-89: Report participant action added consistently.

The "Report Participant" menu item is correctly added across all role scenarios (admin, moderator) with proper localization and controller integration.

Consider using a distinct icon for the report action (e.g., Icons.flag or Icons.report) instead of Icons.remove_circle_outline to better differentiate reporting from removal/kick actions. Currently, all menu items share the same icon via makeMenuItems.

Example refinement to makeMenuItems:

List<FocusedMenuItem> makeMenuItems(
  List<FocusedMenuItemData> items,
  Brightness currentBrightness,
) {
  return items
      .map(
        (item) => FocusedMenuItem(
          title: Text(
            item.textContent,
            style: TextStyle(fontSize: UiSizes.size_14),
          ),
          trailingIcon: Icon(
            item.textContent.contains('Report') 
                ? Icons.flag 
                : Icons.remove_circle_outline,
            color: item.textContent.contains('Report') 
                ? Colors.orange 
                : Colors.red,
            size: UiSizes.size_18,
          ),
          onPressed: item.action,
          backgroundColor: currentBrightness == Brightness.light
              ? Colors.white
              : Colors.black,
        ),
      )
      .toList();
}

Or extend FocusedMenuItemData to include optional icon/color parameters for better flexibility.

Also applies to: 107-112, 133-138

lib/views/screens/user_blocked_screen.dart (1)

12-24: Center the SVG image to prevent stretching.

The Column uses crossAxisAlignment: CrossAxisAlignment.stretch, which will stretch the SVG horizontally to fill the screen width. Since the SVG has a fixed square size (200×200), this stretching may distort the image.

Wrap the SvgPicture in a Center widget or use crossAxisAlignment: CrossAxisAlignment.center:

   body: Column(
-    crossAxisAlignment: CrossAxisAlignment.stretch,
+    crossAxisAlignment: CrossAxisAlignment.center,
     mainAxisAlignment: MainAxisAlignment.center,
     children: [
       SvgPicture.asset(
lib/services/api_service.dart (2)

130-132: Inconsistent error handling with deleteLiveChapterRoom.

This method catches AppwriteException specifically, while deleteLiveChapterRoom (line 160) uses a generic catch. For consistency and to avoid missed exceptions, both methods should use the same error-handling strategy.

Apply generic error handling to both methods:

-  } on AppwriteException catch (error) {
+  } catch (error) {
     throw Exception('ERROR $error');
   }

105-133: Preserve stack traces when re-throwing errors.

Wrapping errors with Exception('ERROR $error') discards the original stack trace, making debugging harder. Consider re-throwing the original exception or using a custom exception that preserves the cause.

If you must wrap, preserve the stack trace:

-    } catch (error) {
-      throw Exception('ERROR $error');
+    } catch (error, stackTrace) {
+      Error.throwWithStackTrace(
+        Exception('ERROR $error'),
+        stackTrace,
+      );
     }

Or simply re-throw:

     } catch (error) {
-      throw Exception('ERROR $error');
+      rethrow;
     }

Note: This same pattern appears in deleteLiveChapterRoom (lines 135-163) and should be addressed there as well.

lib/models/user_report_model.dart (1)

7-7: Fix typo in comment.

"recieving" should be "receiving".

-//This model is only supposed to be used when sending reports to the database, to use while recieving modify the fromJson method for reportedUser (Relationship attribute)
+//This model is only supposed to be used when sending reports to the database, to use while receiving modify the fromJson method for reportedUser (Relationship attribute)
lib/views/widgets/live_chapter_header.dart (1)

18-30: Use textTheme and overflow to avoid layout breaks on long titles.

Prefer Theme.of(context).textTheme styles and add overflow/softWrap where appropriate for chapterName/description to prevent overflow and improve theming consistency.

Apply this minimal change:

-        Text(
-          chapterName,
-          style: TextStyle(
-            fontSize: UiSizes.size_20,
-          ),
-        ),
+        Text(
+          chapterName,
+          maxLines: 1,
+          overflow: TextOverflow.ellipsis,
+          style: Theme.of(context).textTheme.titleMedium?.copyWith(
+                fontSize: UiSizes.size_20,
+              ),
+        ),
...
-        Text(
-          chapterDescription,
-          style: TextStyle(color: Colors.grey, fontSize: UiSizes.size_14),
-        ),
+        Text(
+          chapterDescription,
+          maxLines: 2,
+          overflow: TextOverflow.ellipsis,
+          style: Theme.of(context).textTheme.bodyMedium?.copyWith(
+                color: Colors.grey,
+                fontSize: UiSizes.size_14,
+              ),
+        ),
lib/views/widgets/live_chapter_list_tile.dart (1)

13-16: Align surface/background colors for contrast.

Using colorScheme.secondary as background with onSurface text may reduce contrast. Prefer secondaryContainer with onSecondaryContainer, or keep surface background with onSurface text.

-      decoration: BoxDecoration(
-        borderRadius: BorderRadius.circular(10),
-        color: Theme.of(context).colorScheme.secondary,
-      ),
+      decoration: BoxDecoration(
+        borderRadius: BorderRadius.circular(10),
+        color: Theme.of(context).colorScheme.secondaryContainer,
+      ),
-          style: Theme.of(context).textTheme.bodyMedium!.copyWith(
-            color: Theme.of(context).colorScheme.onSurface,
+          style: Theme.of(context).textTheme.bodyMedium!.copyWith(
+            color: Theme.of(context).colorScheme.onSecondaryContainer,

Also applies to: 29-31, 41-41, 47-48

lib/views/widgets/live_chapter_attendee_block.dart (1)

9-14: Prefer VoidCallback over Function for actions.

Use typedef VoidCallback for stricter typing and better tooling.

-class FocusedMenuItemData {
-  final String textContent;
-  final Function action;
+class FocusedMenuItemData {
+  final String textContent;
+  final VoidCallback action;
lib/models/live_chapter_attendees_model.dart (1)

10-13: Avoid reusing "users" JSON key for both input and output.
Rename the userIds write key to "userIds" (or add custom (de)serialization) so that read‐only users and write‐only userIds map to distinct JSON fields.

-    @JsonKey(includeFromJson: false, name: "users", includeToJson: true)
+    @JsonKey(includeFromJson: false, name: "userIds", includeToJson: true)
     List<String>? userIds,
lib/services/room_service.dart (1)

125-148: Consider extracting shared logic between createRoom and createLiveChapterRoom.

The methods createRoom (lines 91-123) and createLiveChapterRoom (lines 125-148) share significant logic for handling LiveKit socket URL replacement and secure storage operations. This duplication could lead to maintenance issues if the logic needs to change.

Consider extracting the shared logic into a helper method:

static Future<Map<String, String>> _storeLiveKitCredentials({
  required String livekitToken,
  required String livekitSocketUrl,
}) async {
  final processedUrl = livekitSocketUrl == "wss://host.docker.internal:7880"
      ? localhostLivekitEndpoint
      : livekitSocketUrl;

  const storage = FlutterSecureStorage();
  await storage.write(key: "createdRoomAdminToken", value: livekitToken);
  await storage.write(key: "createdRoomLivekitUrl", value: processedUrl);

  return {'token': livekitToken, 'url': processedUrl};
}

Then use it in both methods to reduce duplication.

lib/views/screens/live_chapter_screen.dart (2)

109-152: Review the double.maxFinite height constraint.

Line 111 sets height: double.maxFinite for the GridView container. This could cause layout issues if the parent doesn't properly constrain the height, potentially leading to overflow errors or unexpected rendering.

Consider using Expanded or calculating a more reasonable fixed/relative height:

           Obx(() {
-            return SizedBox(
-              height: double.maxFinite,
-              width: 400,
-              child: GridView.builder(
+            return Expanded(
+              child: SizedBox(
+                width: 400,
+                child: GridView.builder(
+                  shrinkWrap: true,
                   gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(

Or if you need a fixed height, calculate it based on screen dimensions:

height: MediaQuery.of(context).size.height * 0.6,

23-34: Consider extracting the dialog to a reusable utility.

The _deleteRoomDialog method is well-structured but could be useful in other contexts. If similar confirmation dialogs are needed elsewhere in the app, consider extracting this to a shared utility class.

lib/controllers/livekit_controller.dart (1)

81-87: Document the reasoning for disabling dynacast, adaptiveStream, and simulcast.

The room options explicitly disable several LiveKit features (dynacast, adaptiveStream, simulcast) without explanation. These options can significantly impact performance and bandwidth usage.

Add a comment explaining why these features are disabled:

         liveKitRoom = Room(
           roomOptions: const RoomOptions(
+            // Disabled for live chapter recording to ensure consistent quality
+            // and avoid adaptive stream switching during transcription
             dynacast: false,
             adaptiveStream: false,
             defaultVideoPublishOptions: VideoPublishOptions(simulcast: false),

If these should only be disabled for live chapters, consider making them conditional:

roomOptions: RoomOptions(
  dynacast: !isLiveChapter,
  adaptiveStream: !isLiveChapter,
  defaultVideoPublishOptions: VideoPublishOptions(
    simulcast: !isLiveChapter,
  ),
),
lib/controllers/live_chapter_controller.dart (2)

239-277: Simplify error handling in endLiveChapter.

The nested try-catch blocks (lines 244-257 inside the outer try starting at line 242) make the error flow difficult to follow. The inner catch logs but continues execution, while the outer catch logs and dismisses the loading dialog. This could lead to inconsistent state if the inner operations fail but transcription succeeds.

Consider refactoring to handle errors more explicitly:

Future<void> endLiveChapter() async {
  loadingDialog(Get.context!);

  try {
    Get.find<LiveKitController>().isRecording.value = false;
    
    // Clean up database documents
    await _cleanupLiveChapterDocuments();
    
    // Transcribe and delete room
    final transcripController = WhisperTranscriptionController();
    final String lyrics = await transcripController.transcribeChapter(
      liveChapterModel.value!.livekitRoomId,
    );
    
    await RoomService.deleteLiveChapterRoom(
      roomId: liveChapterModel.value!.livekitRoomId,
    );

    await liveChapterAttendeesSubscription?.close();
    await Get.delete<LiveKitController>(force: true);
    Get.back();
    await Future.delayed(const Duration(milliseconds: 500));
    Get.to(() => VerifyChapterDetailsScreen(lyricsString: lyrics));
  } catch (e) {
    log("Error ending live chapter: ${e.toString()}");
    Get.back(); // Dismiss loading dialog
    // Consider showing user-facing error
    rethrow;
  }
}

Future<void> _cleanupLiveChapterDocuments() async {
  try {
    await databases.deleteDocument(
      databaseId: storyDatabaseId,
      collectionId: liveChaptersCollectionId,
      documentId: liveChapterModel.value!.id,
    );
    await databases.deleteDocument(
      databaseId: userDatabaseID,
      collectionId: liveChapterAttendeesCollectionId,
      documentId: liveChapterModel.value!.id,
    );
  } catch (e) {
    log('Warning: Failed to cleanup live chapter documents: ${e.toString()}');
    // Continue execution even if cleanup fails
  }
}

71-136: Consider separating notification logic from room creation.

The startLiveChapter method mixes room creation, database operations, and notification sending (lines 114-129). This violates the single responsibility principle and makes testing more difficult.

Consider extracting the notification logic into a separate method:

Future<void> _sendLiveChapterNotification(
  String storyName,
  LiveChapterModel liveChapterData,
) async {
  if (authStateController.followerDocuments.isEmpty) return;

  log('Sending notification for live chapter');
  var body = json.encode({
    'creatorId': authStateController.uid,
    'payload': {
      'title': 'Live Chapter Starting!',
      'body':
          "${authStateController.displayName} is starting a Live Chapter in $storyName: ${liveChapterData.chapterTitle}. Tune In!",
    },
  });
  
  var results = await functions.createExecution(
    functionId: sendStoryNotificationFunctionID,
    body: body.toString(),
  );
  log('Notification status: ${results.status}');
}

Then call it from startLiveChapter:

await _sendLiveChapterNotification(storyName, liveChapterData);

This makes the code more testable and maintainable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7360e1 and eb79067.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (61)
  • ios/Runner/Info.plist (1 hunks)
  • l10n.yaml (1 hunks)
  • lib/controllers/about_app_screen_controller.dart (1 hunks)
  • lib/controllers/auth_state_controller.dart (4 hunks)
  • lib/controllers/create_room_controller.dart (1 hunks)
  • lib/controllers/explore_story_controller.dart (3 hunks)
  • lib/controllers/live_chapter_controller.dart (1 hunks)
  • lib/controllers/livekit_controller.dart (3 hunks)
  • lib/controllers/rooms_controller.dart (2 hunks)
  • lib/controllers/single_room_controller.dart (5 hunks)
  • lib/controllers/whisper_transcription_controller.dart (1 hunks)
  • lib/l10n/app_en.arb (25 hunks)
  • lib/l10n/app_gu.arb (1 hunks)
  • lib/l10n/app_hi.arb (23 hunks)
  • lib/l10n/app_localizations.dart (5 hunks)
  • lib/l10n/app_localizations_en.dart (4 hunks)
  • lib/l10n/app_localizations_gu.dart (1 hunks)
  • lib/l10n/app_localizations_hi.dart (2 hunks)
  • lib/main.dart (1 hunks)
  • lib/models/appwrite_room.dart (2 hunks)
  • lib/models/live_chapter_attendees_model.dart (1 hunks)
  • lib/models/live_chapter_attendees_model.freezed.dart (1 hunks)
  • lib/models/live_chapter_attendees_model.g.dart (1 hunks)
  • lib/models/live_chapter_model.dart (1 hunks)
  • lib/models/live_chapter_model.freezed.dart (1 hunks)
  • lib/models/live_chapter_model.g.dart (1 hunks)
  • lib/models/story.dart (3 hunks)
  • lib/models/user_report_model.dart (1 hunks)
  • lib/models/user_report_model.freezed.dart (1 hunks)
  • lib/models/user_report_model.g.dart (1 hunks)
  • lib/routes/app_pages.dart (3 hunks)
  • lib/routes/app_routes.dart (1 hunks)
  • lib/services/api_service.dart (1 hunks)
  • lib/services/room_service.dart (2 hunks)
  • lib/themes/theme_controller.dart (0 hunks)
  • lib/utils/constants.dart (3 hunks)
  • lib/utils/enums/report_type_enum.dart (1 hunks)
  • lib/views/screens/about_app_screen.dart (2 hunks)
  • lib/views/screens/add_chapter_screen.dart (1 hunks)
  • lib/views/screens/chapter_play_screen.dart (2 hunks)
  • lib/views/screens/edit_profile_screen.dart (3 hunks)
  • lib/views/screens/email_verification_screen.dart (4 hunks)
  • lib/views/screens/home_screen.dart (4 hunks)
  • lib/views/screens/live_chapter_screen.dart (1 hunks)
  • lib/views/screens/story_screen.dart (4 hunks)
  • lib/views/screens/tabview_screen.dart (3 hunks)
  • lib/views/screens/user_blocked_screen.dart (1 hunks)
  • lib/views/screens/verify_chapter_details_screen.dart (1 hunks)
  • lib/views/widgets/chapter_player.dart (1 hunks)
  • lib/views/widgets/live_chapter_attendee_block.dart (1 hunks)
  • lib/views/widgets/live_chapter_header.dart (1 hunks)
  • lib/views/widgets/live_chapter_list_tile.dart (1 hunks)
  • lib/views/widgets/live_room_tile.dart (1 hunks)
  • lib/views/widgets/participant_block.dart (3 hunks)
  • lib/views/widgets/report_widget.dart (1 hunks)
  • lib/views/widgets/start_live_chapter_dialog.dart (1 hunks)
  • project_structure.txt (10 hunks)
  • pubspec.yaml (2 hunks)
  • test/controllers/auth_state_controller_test.dart (1 hunks)
  • test/controllers/friends_controller_test.dart (0 hunks)
  • untranslated.txt (1 hunks)
💤 Files with no reviewable changes (2)
  • lib/themes/theme_controller.dart
  • test/controllers/friends_controller_test.dart
🧰 Additional context used
🪛 RuboCop (1.81.1)
lib/l10n/app_gu.arb

[warning] 611-611: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 612-612: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 613-613: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 614-614: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 615-615: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 616-616: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 617-617: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 618-618: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 619-619: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 620-620: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 621-621: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 622-622: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 623-623: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 624-624: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 625-625: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 626-626: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 627-627: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 628-628: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 629-629: Duplicated key in hash literal.

(Lint/DuplicateHashKey)


[warning] 630-630: Duplicated key in hash literal.

(Lint/DuplicateHashKey)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run Software Tests
🔇 Additional comments (36)
lib/views/screens/tabview_screen.dart (1)

1-1: LGTM! Appropriate import for logging.

Adding dart:developer enables the use of the log function, which is more suitable for debugging and production logging than print.

untranslated.txt (1)

1-23: LGTM!

The untranslated.txt file correctly tracks 20 Gujarati localization keys that remain untranslated. This is expected when adding a new locale and provides a clear roadmap for completing the Gujarati translation effort.

lib/views/screens/add_chapter_screen.dart (1)

169-169: LGTM!

The localization key update from pushNewChapters to newChapters is correctly applied and aligns with the broader localization refactoring in this PR.

ios/Runner/Info.plist (1)

11-11: LGTM!

The addition of Gujarati ("gu") to the CFBundleLocalizations array correctly configures iOS to recognize and support Gujarati localization throughout the app.

pubspec.yaml (2)

20-20: LGTM!

The version bump from 2.0.0+5 to 2.5.0+6 appropriately reflects the significant new features being introduced (Live Chapters and Host Moderation Controls) and follows semantic versioning conventions.


95-98: Dependencies up to date with no known vulnerabilities. flutter_webrtc 1.2.0, path_provider 2.1.5, whisper_flutter_new 1.0.1, and ffmpeg_kit_flutter_new 3.2.0 match the latest pub.dev releases and have no reported security advisories.

lib/controllers/auth_state_controller.dart (3)

54-54: LGTM!

The addition of the reportsCount field properly extends the user profile data model to support the new reporting and moderation features.


221-222: LGTM!

The reportsCount initialization correctly reads the length of the userReports array from the user data document, with proper null-safety handling (defaulting to 0 if the array is null).


111-119: LGTM!

Removing the unused NotificationSettings assignment from messaging.requestPermission(...) is appropriate, as the result was never referenced. The permission request itself is still properly invoked.

test/controllers/auth_state_controller_test.dart (1)

233-233: LGTM!

The test assertion correctly verifies that reportsCount is initialized to 0 when the user data document contains no userReports array, aligning with the null-safety implementation in the controller.

lib/main.dart (1)

78-78: LGTM: Gujarati locale support added.

The addition of Gujarati (gu) to supported locales is implemented correctly and aligns with the broader localization expansion in this PR.

lib/routes/app_routes.dart (1)

35-36: LGTM: New routes added for expanded features.

The new route constants for userBlockedScreen and liveChapterScreen follow the existing naming convention and support the reporting and live chapter features introduced in this release.

lib/models/appwrite_room.dart (1)

13-13: LGTM: Model extended for reporting feature.

The addition of the reportedUsers field correctly extends the room model to support the new user reporting functionality. The field is properly marked as required and late final, ensuring initialization at construction time.

Also applies to: 25-25

lib/utils/enums/report_type_enum.dart (1)

1-18: LGTM: Well-defined report type enum.

The ReportTypeEnum provides a comprehensive set of report categories with proper JSON serialization support via @JsonValue annotations. The enum values align with common moderation scenarios.

lib/views/widgets/live_room_tile.dart (1)

53-61: LGTM: Correct migration to SharePlus API.

The migration from the deprecated Share.share() to SharePlus.instance.share(ShareParams(...)) follows best practices and uses localized text for a better user experience.

Based on learnings

lib/views/screens/home_screen.dart (1)

346-346: LGTM: Dummy data updated for new model field.

The dummy AppwriteRoom instances are correctly updated with the new reportedUsers field, initialized to empty lists as appropriate for test data.

Also applies to: 362-362, 378-378, 394-394

lib/views/screens/chapter_play_screen.dart (1)

182-182: LGTM: Localization key simplified.

about key present in all ARB files.

lib/views/screens/email_verification_screen.dart (2)

107-107: LGTM! Localization key updated correctly.

The localization key change from verificationComplete to verificationCompleteMessage maintains consistency with the updated localization structure.


156-161: LGTM! Localization keys updated correctly.

The localization key changes from otpMismatchError to otpMismatch are applied consistently in both the snackbar and accessibility announcement.

lib/utils/constants.dart (3)

6-6: Verify the hardcoded IP address change.

The default baseDomain changed from '192.168.29.24' to '10.12.78.30'. Both are private IP addresses. Ensure this change is intentional and won't break existing development environments. Consider documenting the expected value or using environment configuration.


52-55: LGTM! New collection IDs added.

The new constants for user reports, live chapter attendees, and live chapters collections are properly defined and follow the existing naming convention.


81-82: LGTM! Live chapter function IDs added.

The new function IDs for creating and deleting live chapter rooms are properly defined and align with the API service changes.

lib/models/user_report_model.dart (1)

9-21: LGTM! Freezed model properly structured.

The model follows Freezed best practices with required/optional fields, proper factory constructors, and JSON serialization support.

lib/models/live_chapter_model.g.dart (1)

9-34: LGTM! Generated serialization code is correct.

The generated code properly handles:

  • Reading the Appwrite document ID from json['$id'] (line 21)
  • Excluding id from serialization (write-only concern)
  • Excluding attendees from JSON (runtime-only field)

This aligns with typical Appwrite response structure patterns.

lib/controllers/single_room_controller.dart (3)

106-110: LGTM! Good defensive programming.

The mic safety guard automatically turns off the microphone when a user loses speaker privileges while their mic is on. This prevents audio leakage and enforces permission boundaries.


218-218: LGTM! Proper cleanup of realtime subscription.

Explicitly closing the subscription when leaving the room prevents resource leaks and ensures clean teardown.


173-178: LGTM! Localized user removal notification.

The removal notification now uses localized strings for better user experience across languages.

lib/models/live_chapter_attendees_model.g.dart (1)

18-23: Asymmetric JSON key configuration is intentional The model uses includeToJson=false on users and includeFromJson=false, name:"users" on userIds to read raw user objects and write only IDs. Generated serializers are correct.

Likely an incorrect or invalid review comment.

lib/models/live_chapter_model.dart (1)

7-25: Model and JSON annotations look good.

Fields and JsonKey usage (id, attendees) align with typical Appwrite patterns.

lib/views/widgets/report_widget.dart (1)

95-116: Verify RadioListTile wiring (groupValue/onChanged).

RadioListTile usually needs explicit type and onChanged/groupValue to be interactive. Here they’re omitted inside RadioGroup; ensure RadioGroup injects them.

If not, update as:

-                      return RadioListTile(
-                        value: ReportTypeEnum.values.elementAt(index),
+                      return RadioListTile<ReportTypeEnum>(
+                        value: ReportTypeEnum.values.elementAt(index),
+                        groupValue: _selectedReportType,
+                        onChanged: (v) => setState(() => _selectedReportType = v),
                         title: Text(
                           AppLocalizations.of(context)!.reportType(
                             ReportTypeEnum.values.elementAt(index).name,
                           ),
                         ),
                       );

Also applies to: 104-111

lib/views/screens/story_screen.dart (2)

350-397: Verify the conditional indexing logic for live chapters.

The conditional itemCount increment and index offset logic (when liveChapter != null, the first item is the live chapter tile, and subsequent items are offset by -1) is correct but could be fragile if the list structure changes. Ensure that:

  1. The live chapter tile is always rendered first when present
  2. Regular chapters are correctly indexed after the offset
  3. Edge cases (empty chapters list with live chapter, single chapter with live chapter) behave as expected

Consider adding a comment explaining the indexing logic:

                             itemCount: widget.story.liveChapter != null
                                 ? widget.story.chapters.length + 1
                                 : widget.story.chapters.length,
                             itemBuilder: (context, index) {
-                                  log(index.toString());
-                                  log(widget.story.chapters.length.toString());
+                                  // When a live chapter exists, render it as the first item (index 0)
+                                  // and offset subsequent chapter indices by -1
                                   if (index == 0 &&

403-449: Good addition of dual action buttons for story creators.

The UI enhancement to provide separate "Add Chapter" and "Start Live Chapter" buttons improves the user experience by making both actions easily accessible.

lib/views/screens/live_chapter_screen.dart (1)

209-230: Clarify the recording button behavior.

The recording button allows starting a recording but blocks stopping it with a snackbar message (lines 214-219). This asymmetric behavior could confuse users. Consider either:

  1. Disabling the button entirely when recording is active (if stopping should only happen server-side or via a different mechanism)
  2. Allowing users to stop recordings if the "cannot stop" restriction is temporary or conditional

Please clarify the intended behavior: should users be able to stop recordings from this UI, or is the recording lifecycle managed elsewhere?

lib/controllers/livekit_controller.dart (1)

21-23: Good addition of live chapter recording infrastructure.

The new fields (isLiveChapter, mediaRecorder, isRecording) cleanly integrate recording capabilities into the LiveKit controller, with proper reactive state management using RxBool.

lib/controllers/live_chapter_controller.dart (1)

42-69: Good use of realtime subscriptions for attendee updates.

The realtime subscription implementation cleanly handles attendee updates and room deletion events. The automatic cleanup and navigation on delete (lines 57-66) provides a good user experience.

lib/l10n/app_localizations.dart (1)

2171-2272: Align reportType keys with enum values.

Please ensure the strings handled here (harassment, abuse, spam, impersonation, illegal, selfharm, misuse, other) match exactly what ReportTypeEnum emits (e.g., via .name). If the enum uses different casing or identifiers, the UI will fall back to the other branch.

Comment on lines +4 to +6
supported-locales:
- en
- hi
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing Gujarati locale in supported-locales configuration.

The PR introduces comprehensive Gujarati localization support (AppLocalizationsGu, app_gu.arb, Info.plist "gu" entry, untranslated.txt "gu" key), but the l10n.yaml configuration only lists "en" and "hi" in supported-locales. This inconsistency will prevent Flutter's localization system from properly generating and resolving Gujarati translations.

Apply this diff to add Gujarati locale support:

 supported-locales:
   - en
   - hi
+  - gu
🤖 Prompt for AI Agents
In l10n.yaml around lines 4 to 6, the supported-locales list only contains "en"
and "hi" but the PR added Gujarati resources; update the supported-locales block
to include "gu" (e.g. add a new list item "- gu"), save the file and re-run the
Flutter localization generation (flutter pub get / flutter gen-l10n) so Gujarati
is picked up.

Comment on lines +691 to +705
final attendeesDocument = await databases.getDocument(
databaseId: userDatabaseID,
collectionId: liveChapterAttendeesCollectionId,
documentId: liveStoryDocuments.first.data['\$id'],
);

final attendeesModel = LiveChapterAttendeesModel.fromJson(
attendeesDocument.data,
);
final liveChapterModel = LiveChapterModel.fromJson(
liveStoryDocuments.first.data,
).copyWith(attendees: attendeesModel);

return liveChapterModel;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Use the document ID when loading attendees.

liveStoryDocuments.first.data['\$id'] is always null$id lives on the Document object, not inside data. As written, the follow-up getDocument call will request documentId: null and throw. Use liveStoryDocuments.first.$id (and consider guarding for missing attendees).

-    final attendeesDocument = await databases.getDocument(
+    final attendeesDocument = await databases.getDocument(
       databaseId: userDatabaseID,
       collectionId: liveChapterAttendeesCollectionId,
-      documentId: liveStoryDocuments.first.data['\$id'],
+      documentId: liveStoryDocuments.first.$id,
     );
📝 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.

Suggested change
final attendeesDocument = await databases.getDocument(
databaseId: userDatabaseID,
collectionId: liveChapterAttendeesCollectionId,
documentId: liveStoryDocuments.first.data['\$id'],
);
final attendeesModel = LiveChapterAttendeesModel.fromJson(
attendeesDocument.data,
);
final liveChapterModel = LiveChapterModel.fromJson(
liveStoryDocuments.first.data,
).copyWith(attendees: attendeesModel);
return liveChapterModel;
}
final attendeesDocument = await databases.getDocument(
databaseId: userDatabaseID,
collectionId: liveChapterAttendeesCollectionId,
documentId: liveStoryDocuments.first.$id,
);
final attendeesModel = LiveChapterAttendeesModel.fromJson(
attendeesDocument.data,
);
final liveChapterModel = LiveChapterModel.fromJson(
liveStoryDocuments.first.data,
).copyWith(attendees: attendeesModel);
return liveChapterModel;
}
🤖 Prompt for AI Agents
In lib/controllers/explore_story_controller.dart around lines 691 to 705, the
code uses liveStoryDocuments.first.data['$id'] which is null because $id is a
property of the Document, not inside data; update the getDocument call to use
liveStoryDocuments.first.$id for documentId and add a null-check/early return or
throw if that $id is missing, and also guard the attendees lookup by checking
whether the attendees document exists (handle not-found or null result) before
constructing LiveChapterAttendeesModel so you don't pass null into fromJson.

Comment on lines +138 to +182
Future<void> joinLiveChapter(
String roomId,

LiveChapterModel liveChapterData,
) async {
try {
final newAttendeesModel = liveChapterData.attendees!.copyWith(
userIds: [
...liveChapterData.attendees!.users.map(
(element) => element["\$id"] as String,
),
authStateController.uid!,
],
users: [
...liveChapterData.attendees!.users,
{
"\$id": authStateController.uid!,
"name": authStateController.displayName,
"profileImageUrl": authStateController.profileImageUrl,
},
],
);
log(newAttendeesModel.toJson().toString());

await databases.updateDocument(
databaseId: userDatabaseID,
collectionId: liveChapterAttendeesCollectionId,
documentId: roomId,
data: newAttendeesModel.toJson(),
);

liveChapterModel.value = liveChapterData.copyWith(
attendees: newAttendeesModel,
);
await RoomService.joinLiveChapterRoom(
roomId: roomId,
userId: authStateController.uid!,
);
listenForAttendeesAdded();
Get.toNamed(AppRoutes.liveChapterScreen);
} catch (e) {
log(e.toString());
rethrow;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid force unwrapping and improve error handling in joinLiveChapter.

Line 169 (within the copyWith call on line 144-159) implicitly assumes that liveChapterData.attendees is non-null when accessing its properties. If attendees is null, this will throw a runtime exception. Additionally, the error handling at lines 178-181 only logs and rethrows, which may not provide enough context for debugging.

Consider adding null checks and improving error messages:

   Future<void> joinLiveChapter(
     String roomId,
-
     LiveChapterModel liveChapterData,
   ) async {
     try {
+      if (liveChapterData.attendees == null) {
+        throw Exception('Cannot join live chapter: attendees data is missing');
+      }
+
       final newAttendeesModel = liveChapterData.attendees!.copyWith(

And improve the error handling:

     } catch (e) {
-      log(e.toString());
-      rethrow;
+      log('Error joining live chapter $roomId: ${e.toString()}');
+      // Consider showing a user-facing error message
+      rethrow;
     }
📝 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.

Suggested change
Future<void> joinLiveChapter(
String roomId,
LiveChapterModel liveChapterData,
) async {
try {
final newAttendeesModel = liveChapterData.attendees!.copyWith(
userIds: [
...liveChapterData.attendees!.users.map(
(element) => element["\$id"] as String,
),
authStateController.uid!,
],
users: [
...liveChapterData.attendees!.users,
{
"\$id": authStateController.uid!,
"name": authStateController.displayName,
"profileImageUrl": authStateController.profileImageUrl,
},
],
);
log(newAttendeesModel.toJson().toString());
await databases.updateDocument(
databaseId: userDatabaseID,
collectionId: liveChapterAttendeesCollectionId,
documentId: roomId,
data: newAttendeesModel.toJson(),
);
liveChapterModel.value = liveChapterData.copyWith(
attendees: newAttendeesModel,
);
await RoomService.joinLiveChapterRoom(
roomId: roomId,
userId: authStateController.uid!,
);
listenForAttendeesAdded();
Get.toNamed(AppRoutes.liveChapterScreen);
} catch (e) {
log(e.toString());
rethrow;
}
}
Future<void> joinLiveChapter(
String roomId,
LiveChapterModel liveChapterData,
) async {
try {
if (liveChapterData.attendees == null) {
throw Exception('Cannot join live chapter: attendees data is missing');
}
final newAttendeesModel = liveChapterData.attendees!.copyWith(
userIds: [
...liveChapterData.attendees!.users.map(
(element) => element["\$id"] as String,
),
authStateController.uid!,
],
users: [
...liveChapterData.attendees!.users,
{
"\$id": authStateController.uid!,
"name": authStateController.displayName,
"profileImageUrl": authStateController.profileImageUrl,
},
],
);
log(newAttendeesModel.toJson().toString());
await databases.updateDocument(
databaseId: userDatabaseID,
collectionId: liveChapterAttendeesCollectionId,
documentId: roomId,
data: newAttendeesModel.toJson(),
);
liveChapterModel.value = liveChapterData.copyWith(
attendees: newAttendeesModel,
);
await RoomService.joinLiveChapterRoom(
roomId: roomId,
userId: authStateController.uid!,
);
listenForAttendeesAdded();
Get.toNamed(AppRoutes.liveChapterScreen);
} catch (e) {
log('Error joining live chapter $roomId: ${e.toString()}');
// Consider showing a user-facing error message
rethrow;
}
}
🤖 Prompt for AI Agents
In lib/controllers/live_chapter_controller.dart around lines 138 to 182, avoid
force-unwrapping liveChapterData.attendees and authStateController fields; first
null-check attendees and if null build a new AttendeesModel with the current
user as the sole attendee (or return with a clear error), validate
authStateController.uid/displayName/profileImageUrl before use and fail early
with a descriptive exception if missing, update the databases.updateDocument
payload accordingly, and move or guard
RoomService.joinLiveChapterRoom/listenForAttendeesAdded so they only run after a
successful DB update; also improve the catch block to log a contextual error
message including operation details and stacktrace (or wrap and rethrow a more
descriptive exception) rather than only rethrowing the raw error.

Comment on lines +279 to +313
Future<Chapter> createChapterFromLiveChapter(
String coverImgPath,
String audioFilePath,
String lyricsString,
) async {
final track = io.File(audioFilePath);
final metadata = readMetadata(track);
int playDuration = metadata.duration?.inMilliseconds ?? 0;

Color primaryColor;

if (!coverImgPath.contains('http')) {
ColorScheme imageColorScheme = await ColorScheme.fromImageProvider(
provider: FileImage(io.File(coverImgPath)),
);

primaryColor = imageColorScheme.primary;
} else {
primaryColor = const Color(0xffcbc6c6);
}

// coverImageUrl and audioFileUrl recieve paths while the chapter creation process
// as cannot push files to storage to get URL unless user is final on creating a story

return Chapter(
liveChapterModel.value!.id,
liveChapterModel.value!.chapterTitle,
coverImgPath,
liveChapterModel.value!.chapterDescription,
lyricsString,
audioFilePath,
playDuration,
primaryColor,
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for metadata reading.

Line 285 calls readMetadata(track) without a try-catch block. If the audio file is corrupted, missing, or in an unsupported format, this will throw an exception and crash the app.

Apply this diff to add error handling:

   Future<Chapter> createChapterFromLiveChapter(
     String coverImgPath,
     String audioFilePath,
     String lyricsString,
   ) async {
+    try {
       final track = io.File(audioFilePath);
       final metadata = readMetadata(track);
       int playDuration = metadata.duration?.inMilliseconds ?? 0;

       Color primaryColor;

       if (!coverImgPath.contains('http')) {
         ColorScheme imageColorScheme = await ColorScheme.fromImageProvider(
           provider: FileImage(io.File(coverImgPath)),
         );

         primaryColor = imageColorScheme.primary;
       } else {
         primaryColor = const Color(0xffcbc6c6);
       }

       return Chapter(
         liveChapterModel.value!.id,
         liveChapterModel.value!.chapterTitle,
         coverImgPath,
         liveChapterModel.value!.chapterDescription,
         lyricsString,
         audioFilePath,
         playDuration,
         primaryColor,
       );
+    } catch (e) {
+      log('Error creating chapter from live chapter: ${e.toString()}');
+      rethrow;
+    }
   }
📝 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.

Suggested change
Future<Chapter> createChapterFromLiveChapter(
String coverImgPath,
String audioFilePath,
String lyricsString,
) async {
final track = io.File(audioFilePath);
final metadata = readMetadata(track);
int playDuration = metadata.duration?.inMilliseconds ?? 0;
Color primaryColor;
if (!coverImgPath.contains('http')) {
ColorScheme imageColorScheme = await ColorScheme.fromImageProvider(
provider: FileImage(io.File(coverImgPath)),
);
primaryColor = imageColorScheme.primary;
} else {
primaryColor = const Color(0xffcbc6c6);
}
// coverImageUrl and audioFileUrl recieve paths while the chapter creation process
// as cannot push files to storage to get URL unless user is final on creating a story
return Chapter(
liveChapterModel.value!.id,
liveChapterModel.value!.chapterTitle,
coverImgPath,
liveChapterModel.value!.chapterDescription,
lyricsString,
audioFilePath,
playDuration,
primaryColor,
);
}
Future<Chapter> createChapterFromLiveChapter(
String coverImgPath,
String audioFilePath,
String lyricsString,
) async {
try {
final track = io.File(audioFilePath);
final metadata = readMetadata(track);
int playDuration = metadata.duration?.inMilliseconds ?? 0;
Color primaryColor;
if (!coverImgPath.contains('http')) {
ColorScheme imageColorScheme = await ColorScheme.fromImageProvider(
provider: FileImage(io.File(coverImgPath)),
);
primaryColor = imageColorScheme.primary;
} else {
primaryColor = const Color(0xffcbc6c6);
}
return Chapter(
liveChapterModel.value!.id,
liveChapterModel.value!.chapterTitle,
coverImgPath,
liveChapterModel.value!.chapterDescription,
lyricsString,
audioFilePath,
playDuration,
primaryColor,
);
} catch (e) {
log('Error creating chapter from live chapter: ${e.toString()}');
rethrow;
}
}
🤖 Prompt for AI Agents
In lib/controllers/live_chapter_controller.dart around lines 279 to 313, the
call to readMetadata(track) is unprotected and can throw for
corrupted/missing/unsupported audio files; wrap the readMetadata call in a
try-catch, on error set metadata to null (or a minimal fallback) and set
playDuration to 0, optionally log the exception (or surface a user-friendly
error) but do not let it crash the method — then proceed to compute primaryColor
and return the Chapter using the safe playDuration value.

Comment on lines +44 to +62
void listenToRecordingStateChanges() async {
final storagePath = await getApplicationDocumentsDirectory();
isRecording.listen((state) async {
if (state) {
log("Starting recording...");

log('Local track published: ${storagePath.path}');
await mediaRecorder.start(
'${storagePath.path}/recordings/${liveKitRoom.name}.mp4',
audioChannel: RecorderAudioChannel.INPUT,
);
} else {
await mediaRecorder.stop();
log(
'Recording stopped, file saved to: $storagePath/${liveKitRoom.name}.mp4',
);
}
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Ensure the recordings directory exists before writing.

The recording path on line 52 writes to ${storagePath.path}/recordings/${liveKitRoom.name}.mp4, but the code doesn't verify that the recordings subdirectory exists. This could cause the mediaRecorder.start() call to fail if the directory hasn't been created.

Apply this diff to ensure the directory exists:

   void listenToRecordingStateChanges() async {
     final storagePath = await getApplicationDocumentsDirectory();
+    final recordingsDir = Directory('${storagePath.path}/recordings');
+    if (!await recordingsDir.exists()) {
+      await recordingsDir.create(recursive: true);
+    }
+
     isRecording.listen((state) async {
       if (state) {
         log("Starting recording...");

Also add the import:

 import 'dart:developer';
 import 'dart:async';
+import 'dart:io';
 import 'package:flutter_webrtc/flutter_webrtc.dart';
📝 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.

Suggested change
void listenToRecordingStateChanges() async {
final storagePath = await getApplicationDocumentsDirectory();
isRecording.listen((state) async {
if (state) {
log("Starting recording...");
log('Local track published: ${storagePath.path}');
await mediaRecorder.start(
'${storagePath.path}/recordings/${liveKitRoom.name}.mp4',
audioChannel: RecorderAudioChannel.INPUT,
);
} else {
await mediaRecorder.stop();
log(
'Recording stopped, file saved to: $storagePath/${liveKitRoom.name}.mp4',
);
}
});
}
// Add this import
import 'dart:developer';
import 'dart:async';
import 'dart:io';
import 'package:flutter_webrtc/flutter_webrtc.dart';
void listenToRecordingStateChanges() async {
final storagePath = await getApplicationDocumentsDirectory();
final recordingsDir = Directory('${storagePath.path}/recordings');
if (!await recordingsDir.exists()) {
await recordingsDir.create(recursive: true);
}
isRecording.listen((state) async {
if (state) {
log("Starting recording...");
log('Local track published: ${storagePath.path}');
await mediaRecorder.start(
'${storagePath.path}/recordings/${liveKitRoom.name}.mp4',
audioChannel: RecorderAudioChannel.INPUT,
);
} else {
await mediaRecorder.stop();
log(
'Recording stopped, file saved to: $storagePath/${liveKitRoom.name}.mp4',
);
}
});
}
🤖 Prompt for AI Agents
In lib/controllers/livekit_controller.dart around lines 44 to 62, the code
writes to ${storagePath.path}/recordings/${liveKitRoom.name}.mp4 without
ensuring the recordings subdirectory exists; before calling
mediaRecorder.start(), create the recordings directory (e.g., use
Directory('${storagePath.path}/recordings') and call create(recursive: true) or
createSync(recursive: true) and handle/await errors), then proceed to start
recording; also add the required import for dart:io at the top of the file.

Comment on lines +29 to +35
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w500,
fontSize: 17,
fontStyle: FontStyle.normal,
fontFamily: 'Inter',
),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove hardcoded 'Inter' or include the font asset.

'Inter' isn't present in assets (project_structure.txt shows only Montserrat). This causes platform fallback and inconsistent metrics.

-          style: Theme.of(context).textTheme.bodyMedium!.copyWith(
+          style: Theme.of(context).textTheme.bodyMedium!.copyWith(
             color: Theme.of(context).colorScheme.onSurface,
             fontWeight: FontWeight.w500,
             fontSize: 17,
             fontStyle: FontStyle.normal,
-            fontFamily: 'Inter',
           ),
-          style: Theme.of(context).textTheme.bodyMedium!.copyWith(
+          style: Theme.of(context).textTheme.bodyMedium!.copyWith(
             fontSize: 12,
             fontStyle: FontStyle.normal,
-            fontFamily: 'Inter',
           ),

Also applies to: 41-45

🤖 Prompt for AI Agents
In lib/views/widgets/live_chapter_list_tile.dart around lines 29-35 (and
similarly lines 41-45), the TextStyle hardcodes fontFamily: 'Inter' while the
project only includes Montserrat, causing platform fallbacks and inconsistent
metrics; either remove the fontFamily property so the app uses the configured
Theme typography (or set it to
Theme.of(context).textTheme.bodyMedium!.fontFamily) OR add Inter as a bundled
font in pubspec.yaml and include the font files under assets/fonts and run
flutter pub get; update the TextStyle to reference the now-available font only
after the asset is added.

Comment on lines +27 to +31
class _ReportWidgetState extends State<ReportWidget> {
ReportTypeEnum? _selectedReportType;
TextEditingController reportTextController = TextEditingController();
final AuthStateController authStateController =
Get.find<AuthStateController>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Dispose the TextEditingController to avoid leaks.

Add dispose() to clean up the controller.

 class _ReportWidgetState extends State<ReportWidget> {
   ReportTypeEnum? _selectedReportType;
   TextEditingController reportTextController = TextEditingController();
   final AuthStateController authStateController =
       Get.find<AuthStateController>();
+  @override
+  void dispose() {
+    reportTextController.dispose();
+    super.dispose();
+  }
🤖 Prompt for AI Agents
In lib/views/widgets/report_widget.dart around lines 27 to 31, the
TextEditingController reportTextController is created but never disposed which
can leak resources; add an override of dispose() in the _ReportWidgetState that
calls reportTextController.dispose() and then super.dispose() to properly clean
up the controller when the widget is removed.

Comment on lines +43 to +51
final UserReportModel report = UserReportModel(
reporterUid: authStateController.uid!,
reportedUid: widget.participantId,
reportType: _selectedReportType!,
reportText: reportTextController.text.isEmpty
? null
: reportTextController.text,
reportedUser: widget.participantId,
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Wrong field assigned: reportedUser should be the name, not the id.

Currently both reportedUid and reportedUser use the id.

-      final UserReportModel report = UserReportModel(
+      final UserReportModel report = UserReportModel(
         reporterUid: authStateController.uid!,
         reportedUid: widget.participantId,
         reportType: _selectedReportType!,
         reportText: reportTextController.text.isEmpty
             ? null
             : reportTextController.text,
-        reportedUser: widget.participantId,
+        reportedUser: widget.participantName,
       );
🤖 Prompt for AI Agents
In lib/views/widgets/report_widget.dart around lines 43 to 51, the reportedUser
field is incorrectly assigned the participant ID; change it to use the
participant's name property from the widget (e.g., widget.participantName or the
correct display-name field on the widget) instead of widget.participantId, and
add a safe fallback or null check if the name can be null/empty.

Comment on lines +58 to +66
Get.back(result: true);
await Future.delayed(Duration(milliseconds: 500));
customSnackbar(
AppLocalizations.of(Get.context!)!.success,
AppLocalizations.of(Get.context!)!.reportSubmitted,
LogType.success,
);

return;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use local context (and show snackbar before popping) to avoid null/disposed context.

Get.context! may be null after Get.back; also avoid delayed snackbar after pop.

-      Get.back(result: true);
-      await Future.delayed(Duration(milliseconds: 500));
-      customSnackbar(
-        AppLocalizations.of(Get.context!)!.success,
-        AppLocalizations.of(Get.context!)!.reportSubmitted,
-        LogType.success,
-      );
+      customSnackbar(
+        AppLocalizations.of(context)!.success,
+        AppLocalizations.of(context)!.reportSubmitted,
+        LogType.success,
+      );
+      if (mounted) Get.back(result: true);
-      customSnackbar(
-        AppLocalizations.of(Get.context!)!.error,
-        '${AppLocalizations.of(Get.context!)!.reportFailed}: $e',
-        LogType.error,
-      );
+      customSnackbar(
+        AppLocalizations.of(context)!.error,
+        '${AppLocalizations.of(context)!.reportFailed}: $e',
+        LogType.error,
+      );

Also applies to: 70-73

🤖 Prompt for AI Agents
In lib/views/widgets/report_widget.dart around lines 58-66 (also applies to
70-73): the code uses Get.context! and calls Get.back before showing a snackbar
(with a delayed Future), which can make context null/disposed; instead retrieve
and store a local BuildContext (e.g., final ctx = Get.context or the widget's
context) before doing any navigation, call customSnackbar immediately using that
saved context (not Get.context!), and then call Get.back(); remove the arbitrary
Future.delayed and ensure snackbar is shown prior to popping so no null/disposed
context is used.

Comment on lines +66 to +75
await Get.put(
LiveChapterController(),
).startLiveChapter(
ID.unique(),
chapterName,
chapterDescription,
story.storyId,
story.title,
);
} catch (e) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Avoid re-registering LiveChapterController on every tap.

Calling Get.put(LiveChapterController()) each time the Start button is pressed re-registers the dependency; on the second launch it throws Instance "LiveChapterController" has already been created. Use the existing instance when registered.

-                            await Get.put(
-                              LiveChapterController(),
-                            ).startLiveChapter(
+                            final controller = Get.isRegistered<LiveChapterController>()
+                                ? Get.find<LiveChapterController>()
+                                : Get.put(LiveChapterController());
+                            await controller.startLiveChapter(
📝 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.

Suggested change
await Get.put(
LiveChapterController(),
).startLiveChapter(
ID.unique(),
chapterName,
chapterDescription,
story.storyId,
story.title,
);
} catch (e) {
final controller = Get.isRegistered<LiveChapterController>()
? Get.find<LiveChapterController>()
: Get.put(LiveChapterController());
await controller.startLiveChapter(
ID.unique(),
chapterName,
chapterDescription,
story.storyId,
story.title,
);
} catch (e) {
🤖 Prompt for AI Agents
In lib/views/widgets/start_live_chapter_dialog.dart around lines 66 to 75, the
code calls Get.put(LiveChapterController()) on every tap which re-registers the
controller and causes an "already been created" error; instead obtain the
existing instance (or register only if not present). Replace the direct Get.put
call with either Get.find<LiveChapterController>() to fetch the existing
controller, or guard with if (!Get.isRegistered<LiveChapterController>())
Get.put(LiveChapterController()) then use Get.find<LiveChapterController>() to
call startLiveChapter, ensuring you use the typed generic to avoid
re-registration.

@M4dhav M4dhav merged commit 1cc1dca into master Oct 12, 2025
2 of 3 checks passed
@github-actions
Copy link
Contributor

PR Closed - Thank You, @M4dhav!

  • If this PR was merged: Congratulations! Your contribution is now part of the project. 🚀
  • If this PR was closed without merging: Don’t worry! You can always improve it and submit again. 💪

We appreciate your effort and look forward to more contributions from you! 🤝

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
lib/views/screens/live_chapter_screen.dart (1)

243-261: Remove the redundant block in _buildMicButton.

There’s still a stray { ... } scope wrapping the logic inside the Obx. Please drop it to keep the function clean.

lib/l10n/app_localizations_gu.dart (1)

1080-1082: Remove escaped dollar signs in username interpolations.

The escaped dollar signs (\$) will render as literal "$" characters in the UI (e.g., "$john_doe"). Use normal Dart string interpolation without the backslash.

Apply this diff:

   String friendRequestAcceptedTo(String username) {
-    return 'તમે હવે \$$username સાથે મિત્ર છો.';
+    return 'તમે હવે $username સાથે મિત્ર છો.';
   }
   
   String friendRequestDeclinedTo(String username) {
-    return 'તમે \$$username ની મિત્ર વિનંતી નકારી છે.';
+    return 'તમે $username ની મિત્ર વિનંતી નકારી છે.';
   }
   
   String callDeclinedTo(String username) {
-    return 'યુઝર \$$username એ કૉલ નકારી છે.';
+    return 'યુઝર $username એ કૉલ નકારી છે.';
   }

Also applies to: 1088-1090, 1099-1101

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb79067 and ba67c1f.

📒 Files selected for processing (10)
  • lib/l10n/app_en.arb (25 hunks)
  • lib/l10n/app_hi.arb (23 hunks)
  • lib/l10n/app_localizations.dart (5 hunks)
  • lib/l10n/app_localizations_en.dart (4 hunks)
  • lib/l10n/app_localizations_gu.dart (1 hunks)
  • lib/l10n/app_localizations_hi.dart (2 hunks)
  • lib/views/screens/live_chapter_screen.dart (1 hunks)
  • lib/views/screens/profile_screen.dart (2 hunks)
  • lib/views/widgets/start_live_chapter_dialog.dart (1 hunks)
  • untranslated.txt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/views/widgets/start_live_chapter_dialog.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run Software Tests
🔇 Additional comments (3)
lib/l10n/app_localizations.dart (2)

9-9: LGTM! Gujarati localization properly integrated.

The Gujarati locale has been correctly wired into the localization system:

  • Import added for AppLocalizationsGu
  • 'gu' added to supportedLocales list
  • Delegate's isSupported updated to recognize 'gu'
  • Lookup logic extended to instantiate AppLocalizationsGu for Gujarati locale

Also applies to: 99-99, 2291-2291, 2302-2303


103-2278: LGTM! Comprehensive API expansion with proper documentation.

The public API surface has been significantly expanded with new localization keys for reporting, live chapters, and moderation features. All new entries include proper doc comments explaining their purpose and usage.

lib/l10n/app_en.arb (1)

2-1636: LGTM! Comprehensive localization metadata.

The English ARB file has been properly expanded with:

  • Detailed descriptions for all localization keys
  • Proper placeholder definitions with types and examples
  • Consistent formatting and structure
  • New keys for reporting and live chapter features

The metadata will improve code generation and provide clear documentation for translators.

Comment on lines +1170 to +1244
String get alert => 'Alert';

@override
String get removedFromRoom =>
'You have been reported or removed from the room';

@override
String reportType(String type) {
String _temp0 = intl.Intl.selectLogic(type, {
'harassment': 'Harassment / Hate Speech',
'abuse': 'Abusive content / Violence',
'spam': 'Spam / Scams / Fraud',
'impersonation': 'Impersonation / Fake Accounts',
'illegal': 'Illegal Activities',
'selfharm': 'Self-harm / Suicide / Mental health',
'misuse': 'Misuse of platform',
'other': 'Other',
});
return '$_temp0';
}

@override
String get userBlockedFromResonate =>
'You have received multiple reports from users and you have been blocked from using Resonate. Please contact AOSSIE if you believe this is a mistake.';

@override
String get reportParticipant => 'Report Participant';

@override
String get selectReportType => 'Please select a report type';

@override
String get reportSubmitted => 'Report Submitted Successfully';

@override
String get reportFailed => 'Report Submission Failed';

@override
String get additionalDetailsOptional => 'Additional details (optional)';

@override
String get submitReport => 'Submit Report';

@override
String get actionBlocked => 'Action Blocked';

@override
String get cannotStopRecording =>
'You cannot stop the recording manually, the recording will be stopped when the room is closed.';

@override
String get liveChapter => 'Live Chapter';

@override
String get viewOrEditLyrics => 'View or Edit Lyrics';

@override
String get close => 'Close';

@override
String get verifyChapterDetails => 'Verify Chapter Details';

@override
String get author => 'Author';

@override
String get startLiveChapter => 'Start a Live Chapter';

@override
String get fillAllFields => 'Please fill in all required fields';

@override
String get noRecordingError =>
'You have not recorded anything for the chapter. Please record a chapter before exiting the room';
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Translate the new keys to Gujarati.

The new reporting and live chapter strings (lines 1170-1244) are in English instead of Gujarati. This creates an inconsistent experience for Gujarati-speaking users.

Please translate these keys to Gujarati to maintain consistency with the rest of the file:

  • alert, removedFromRoom, reportType, userBlockedFromResonate
  • reportParticipant, selectReportType, reportSubmitted, reportFailed
  • additionalDetailsOptional, submitReport, actionBlocked
  • cannotStopRecording, liveChapter, viewOrEditLyrics, close
  • verifyChapterDetails, author, startLiveChapter, fillAllFields, noRecordingError

Comment on lines +1173 to +1247
@override
String get alert => 'अलर्ट';

@override
String get removedFromRoom =>
'आपको कमरे से रिपोर्ट किया गया है या हटा दिया गया है';

@override
String reportType(String type) {
String _temp0 = intl.Intl.selectLogic(type, {
'harassment': 'हिंसा / नफरत भरा भाषण',
'abuse': 'दुरुपयोग सामग्री / हिंसा',
'spam': 'स्पैम / धोखाधड़ी',
'impersonation': 'नकली खाते',
'illegal': 'गैरकानूनी गतिविधियाँ',
'selfharm': 'आत्म-हानि / आत्महत्या / मानसिक स्वास्थ्य',
'misuse': 'प्लेटफ़ॉर्म का दुरुपयोग',
'other': 'अन्य',
});
return '$_temp0';
}

@override
String get userBlockedFromResonate =>
'आपको कई उपयोगकर्ताओं द्वारा रिपोर्ट किया गया है और आपको रेज़ोनेट का उपयोग करने से ब्लॉक कर दिया गया है। यदि आपको लगता है कि यह गलती है, तो कृपया AOSSIE से संपर्क करें।';

@override
String get reportParticipant => 'प्रतिभागी की रिपोर्ट करें';

@override
String get selectReportType => 'रिपोर्ट का प्रकार चुनें';

@override
String get reportSubmitted => 'रिपोर्ट सफलतापूर्वक सबमिट की गई';

@override
String get reportFailed => 'रिपोर्ट सबमिशन फेल';

@override
String get additionalDetailsOptional => 'अतिरिक्त विवरण (वैकल्पिक)';

@override
String get submitReport => 'रिपोर्ट सबमिट करें';

@override
String get actionBlocked => 'कार्रवाई अवरुद्ध';

@override
String get cannotStopRecording =>
'आप रिकॉर्डिंग को मैन्युअल रूप से रोक नहीं सकते, रिकॉर्डिंग तब रोकी जाएगी जब कमरा बंद होगा।';

@override
String get liveChapter => 'लाइव चैप्टर';

@override
String get viewOrEditLyrics => 'गीत देखें या संपादित करें';

@override
String get close => 'बंद करें';

@override
String get verifyChapterDetails => 'चैप्टर विवरण सत्यापित करें';

@override
String get author => 'लेखक';

@override
String get startLiveChapter => 'लाइव चैप्टर शुरू करें';

@override
String get fillAllFields => 'कृपया सभी आवश्यक फ़ील्ड भरें';

@override
String get noRecordingError =>
'आपके पास कोई रिकॉर्डिंग नहीं है। लाइव चैप्टर रूम से बाहर निकलने के लिए, कृपया पहले रिकॉर्डिंग शुरू करें।';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Translate the new keys to Hindi.

The new reporting and live chapter strings (lines 1173-1247) are currently in English instead of Hindi. This creates an inconsistent experience for Hindi-speaking users.

All other strings in this file are properly translated to Hindi. Please translate these new keys to maintain consistency:

  • alert, removedFromRoom, reportType, userBlockedFromResonate
  • reportParticipant, selectReportType, reportSubmitted, reportFailed
  • additionalDetailsOptional, submitReport, actionBlocked
  • cannotStopRecording, liveChapter, viewOrEditLyrics, close
  • verifyChapterDetails, author, startLiveChapter, fillAllFields, noRecordingError
🤖 Prompt for AI Agents
In lib/l10n/app_localizations_hi.dart around lines 1173 to 1247 the new
reporting and live-chapter strings are still in English; replace them with Hindi
translations so the file remains consistent. Update the simple getters (alert,
removedFromRoom, userBlockedFromResonate, reportParticipant, selectReportType,
reportSubmitted, reportFailed, additionalDetailsOptional, submitReport,
actionBlocked, cannotStopRecording, liveChapter, viewOrEditLyrics, close,
verifyChapterDetails, author, startLiveChapter, fillAllFields, noRecordingError)
to proper Hindi text and update the reportType(String type) selectLogic map
values to Hindi equivalents for each key (harassment, abuse, spam,
impersonation, illegal, selfharm, misuse, other); ensure punctuation and
pluralization match surrounding entries and run l10n tests/build to verify no
syntax or Intl issues.

Comment on lines +420 to 434
try {
await friendsController.removeFriend(friendModel);
customSnackbar(
AppLocalizations.of(
context,
)!.friendRequestCancelled,
AppLocalizations.of(
context,
)!.friendRequestCancelledTo(
widget.creator!.name!,
),
LogType.info,
);
} catch (e) {
log(e.toString());
}
customSnackbar(
AppLocalizations.of(
context,
)!.friendRequestCancelled,
AppLocalizations.of(
context,
)!.friendRequestCancelledTo(widget.creator!.name!),
LogType.info,
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not show a success snackbar when the removal fails.

If friendsController.removeFriend throws, we still display friendRequestCancelled and mislead the user. Handle the failure path explicitly (e.g., surface an error toast and return early) instead of always announcing success.

🤖 Prompt for AI Agents
In lib/views/screens/profile_screen.dart around lines 420 to 434, the try/catch
currently logs removal errors but always shows the success snackbar; change the
flow so that if friendsController.removeFriend throws you surface an error
snackbar (or toast) with a clear message and return early without showing the
success message, and only show the friendRequestCancelled success snackbar when
removeFriend completes successfully; ensure the catch block awaits/handles the
error display and exits the surrounding block so success is not displayed on
failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app-update gsoc Part of Google Summer of Code season

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants