Skip to content

Pull request from feature/permission-home - #4

Closed
nusteam wants to merge 3 commits into
mainfrom
feature/permission-home
Closed

nusteam wants to merge 3 commits into
mainfrom
feature/permission-home

Conversation

@nusteam

@nusteam nusteam commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added a livestream feed with live items, pull-to-refresh, and infinite scrolling.
    • Added a live stream setup screen with camera preview, mic/camera toggles, stream title input, and a Go Live action.
    • Added permission prompts and settings shortcuts for camera/microphone access.
  • Bug Fixes

    • Updated app startup flow to show the new feed experience and improved live-stream related messaging.
  • Localization

    • Added new English and French text for the feed, live setup, and permission dialogs.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a livestream feed and preview flow with new camera and microphone permissions, localized labels, route and dependency wiring, and updated startup tests.

Changes

Live stream rollout

Layer / File(s) Summary
Shared permissions and data contracts
android/app/src/main/AndroidManifest.xml, assets/i18n/*.json, ios/Runner/Info.plist, lib/core/localization/app_translation_keys.dart, lib/features/home/models/live_stream_item.dart, lib/utils/*.dart, pubspec.yaml
Adds camera/audio/notification permissions, localized feed and permission strings, the stream item model, shared permission and viewer-count helpers, and the camera/audioplayers dependencies.
Route and plugin wiring
lib/core/bindings/app_binding.dart, lib/routes/app_routes.dart, lib/routes/app_router.dart, linux/flutter/generated_*, macos/Flutter/GeneratedPluginRegistrant.swift, windows/flutter/generated_*
Registers the live preview route, adds controller bindings, and wires audioplayers into the generated desktop plugin registrants.
Feed browser and home shell
lib/features/home/controllers/feed_controller.dart, lib/features/home/views/feed_view.dart, lib/features/home/views/home_view.dart, test/widget_test.dart
Adds the paginated home feed, swaps the home page content to the feed, and updates the app-start widget test to assert the splash-to-feed path.
Live preview state
lib/features/home/controllers/live_preview_controller.dart
Manages camera initialization, camera and microphone toggles, title validation, streaming state, and preview cleanup.
Live preview UI
lib/features/home/views/live_preview_screen.dart, lib/features/home/widgets/*.dart
Renders the live preview screen, camera preview, camera and mic controls, permission dialog, stream title input, and go-live button.
App formatting updates
lib/features/auth/controllers/setup_account_controller.dart, lib/features/auth/models/setup_account_request.g.dart, lib/features/auth/services/auth_api_service.dart, lib/features/auth/views/setup_account_view.dart, lib/features/auth/views/sign_up_view.dart, lib/features/splash/views/splash_view.dart, lib/widgets/ViewPagerScroll.dart
Reflows auth, splash, and pager code without changing the existing logic.

Sequence Diagram(s)

sequenceDiagram
  participant FeedView
  participant FeedController
  participant PermissionHelper
  participant PermissionDialog
  participant GoRouter
  participant LivePreviewScreen

  FeedView->>FeedController: startLiveStream(context)
  FeedController->>PermissionHelper: checkAndRequestPermissions()
  alt permissions granted
    FeedController->>GoRouter: go(AppRoutes.livePreview)
    GoRouter->>LivePreviewScreen: build route
  else permissions need attention
    FeedController->>PermissionDialog: show(context)
  end
Loading

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

A bunny hopped through feeds so bright,
With camera, mic, and live-stream light.
“Go Live!” said the carrot in the screen,
And feed and preview both came clean. 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is generic and only references the source branch, not the actual change set. Use a concise title that names the main feature, such as adding home feed livestream and permission handling.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@android/app/src/main/AndroidManifest.xml`:
- Around line 10-12: The manifest’s camera, autofocus, and microphone
declarations in the AndroidManifest need to be marked optional so they do not
block install eligibility on devices without those features. Update the existing
<uses-feature> entries in the manifest to explicitly set them as not required,
keeping the livestreaming capabilities available without excluding users who
only use auth/home/feed flows.

In `@lib/core/localization/app_translation_keys.dart`:
- Around line 43-52: The live-preview localization contract is incomplete
because live_preview_controller still uses hardcoded English error/snackbar
text. Add the missing localization keys alongside the existing
AppTranslationKeys livePreview* entries, then update live_preview_controller and
any related UI to read those messages through the translation layer instead of
inline strings. Use the existing AppTranslationKeys constants and the
live_preview_controller flow as the main points to locate and replace the
remaining untranslated strings.

In `@lib/features/home/controllers/feed_controller.dart`:
- Around line 151-168: The recovery flow in startLiveStream is using the stored
live_permissions_requested flag instead of the current permission state, which
can skip the dialog after a permanently denied first request. Update the
permission-failure branch in feed_controller.dart to query
PermissionHelper.isPermanentlyDenied() immediately after
PermissionHelper.checkAndRequestPermissions(), and drive PermissionDialog.show
from that result rather than the historical storage flag. Keep the existing
context.mounted checks around any navigation or dialog display.

In `@lib/features/home/controllers/live_preview_controller.dart`:
- Around line 38-80: The `_initializeCamera` flow in `LivePreviewController`
swallows all failures into a generic message and never handles permission
denial, leaving no recovery path for first-time users. Update
`_initializeCamera` to detect camera-permission-specific failures around
`availableCameras()` / `CameraController.initialize()`, then invoke the existing
permission dialog/helper or settings-routing flow instead of only setting
`cameraError.value`. Keep the generic fallback for non-permission errors, but
ensure denied users are guided through the permission recovery path from this
controller.
- Around line 145-186: The mic toggle flow in toggleMic() and
_reinitializeCameraWithAudio() currently disposes the active CameraController
before the replacement is confirmed, so a failed initialize leaves
cameraController null and the preview blank. Update
_reinitializeCameraWithAudio() to keep the existing controller alive until the
new CameraController has initialized successfully, or otherwise recreate/restore
the previous camera session inside the catch path before returning. Make sure
the rollback restores cameraController, preserves the prior preview state, and
only then reverts isMicOn when initialization fails.
- Around line 240-267: The Go Live flow in startLiveStream is a dead end because
it flips isStreaming on and then immediately back off without creating a session
or navigating anywhere. Update startLiveStream in live_preview_controller.dart
to either call the real stream-session creation and transition to the streamer
screen, or keep the CTA disabled until that path is implemented. Make sure
isStreaming is only set to true while an actual live-start operation is in
progress and is reset in the success/error paths, not unconditionally right
away.
- Around line 83-99: The toggleCamera() flow in LivePreviewController is
treating pausePreview()/resumePreview() as a true camera on/off switch, but that
only pauses rendering while the camera session stays active. Update the behavior
around toggleCamera() and cameraController so that a real off state disposes the
controller and a later on state recreates/initializes it, or else change the
UI/labeling to make it clear the control only pauses the preview rather than
disabling the camera.

In `@lib/features/home/models/live_stream_item.dart`:
- Around line 37-43: The LiveStreamItem.fromMap factory is masking malformed
payloads by defaulting missing or renamed fields to empty strings and 0, which
hides upstream contract issues. Update fromMap to explicitly read and validate
the required fields for viewerCount, title, streamerName, and thumbnailUrl, and
fail fast when any are missing or invalid instead of constructing a partially
corrupted LiveStreamItem.

In `@lib/features/home/views/feed_view.dart`:
- Around line 44-47: The search header action is currently a dead control
because IconButton in feed_view.dart has an empty onPressed handler. Update the
widget that builds the header action so the search IconButton is either removed
entirely or disabled until real behavior exists, using the IconButton in the
feed header as the unique place to adjust.
- Around line 299-300: The stream card’s live badge text is still hard-coded in
the feed view, so it bypasses localization. Update the Text widget used for the
LIVE badge in the feed card UI to pull from the app’s localization system
instead of using the literal string, and keep the surrounding badge widget
structure unchanged.

In `@lib/features/home/views/live_preview_screen.dart`:
- Around line 44-140: The preview screen still hard-codes user-facing English
copy instead of using the localization layer. Update the strings in _buildHeader
and _buildGoLiveButton (and any related preview copy in this flow) to use
translation keys via the existing localization API rather than literal text.
Keep the labels aligned with the livestream localization setup so the
close/preview controls and the “Go Live” action are translated consistently.

In `@test/widget_test.dart`:
- Around line 56-58: The widget test is coupled to
FeedController.loadInitialData() by waiting an exact 500 ms delay, so replace
that timing-based pump sequence with a condition-based wait for the home screen
state/marker. Update the test in widget_test.dart to assert the navigation
outcome using the relevant home widget or stable UI signal instead of mirroring
the controller’s internal delay, so changes in FeedController do not break the
test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0a9aad93-8265-4459-9b62-edc38a25c2be

📥 Commits

Reviewing files that changed from the base of the PR and between 8cdfcd0 and 936a0bc.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (36)
  • android/app/src/main/AndroidManifest.xml
  • assets/i18n/en.json
  • assets/i18n/fr.json
  • ios/Runner/Info.plist
  • lib/core/bindings/app_binding.dart
  • lib/core/localization/app_translation_keys.dart
  • lib/features/auth/controllers/setup_account_controller.dart
  • lib/features/auth/models/setup_account_request.g.dart
  • lib/features/auth/services/auth_api_service.dart
  • lib/features/auth/views/setup_account_view.dart
  • lib/features/auth/views/sign_up_view.dart
  • lib/features/home/controllers/feed_controller.dart
  • lib/features/home/controllers/live_preview_controller.dart
  • lib/features/home/models/live_stream_item.dart
  • lib/features/home/views/feed_view.dart
  • lib/features/home/views/home_content_view.dart
  • lib/features/home/views/home_view.dart
  • lib/features/home/views/live_preview_screen.dart
  • lib/features/home/widgets/camera_control_button.dart
  • lib/features/home/widgets/camera_preview_widget.dart
  • lib/features/home/widgets/mic_control_button.dart
  • lib/features/home/widgets/permission_dialog.dart
  • lib/features/home/widgets/stream_title_input.dart
  • lib/features/splash/views/splash_view.dart
  • lib/routes/app_router.dart
  • lib/routes/app_routes.dart
  • lib/utils/number_formatter.dart
  • lib/utils/permission_helper.dart
  • lib/widgets/ViewPagerScroll.dart
  • linux/flutter/generated_plugin_registrant.cc
  • linux/flutter/generated_plugins.cmake
  • macos/Flutter/GeneratedPluginRegistrant.swift
  • pubspec.yaml
  • test/widget_test.dart
  • windows/flutter/generated_plugin_registrant.cc
  • windows/flutter/generated_plugins.cmake
💤 Files with no reviewable changes (1)
  • lib/features/home/views/home_content_view.dart

Comment on lines +10 to +12
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:name="android.hardware.microphone" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Mark the capture hardware features optional.

<uses-feature> defaults to android:required="true", so these entries will exclude devices without a camera/autofocus/mic from install eligibility. Since livestreaming is additive here, that unnecessarily blocks users who only need the existing auth/home/feed flows.

Suggested manifest change
-    <uses-feature android:name="android.hardware.camera" />
-    <uses-feature android:name="android.hardware.camera.autofocus" />
-    <uses-feature android:name="android.hardware.microphone" />
+    <uses-feature android:name="android.hardware.camera" android:required="false" />
+    <uses-feature
+        android:name="android.hardware.camera.autofocus"
+        android:required="false" />
+    <uses-feature android:name="android.hardware.microphone" android:required="false" />
📝 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
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:name="android.hardware.microphone" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<uses-feature android:name="android.hardware.microphone" android:required="false" />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/app/src/main/AndroidManifest.xml` around lines 10 - 12, The
manifest’s camera, autofocus, and microphone declarations in the AndroidManifest
need to be marked optional so they do not block install eligibility on devices
without those features. Update the existing <uses-feature> entries in the
manifest to explicitly set them as not required, keeping the livestreaming
capabilities available without excluding users who only use auth/home/feed
flows.

Comment on lines +43 to +52
static const permissionTitle = 'live.permission.title';
static const permissionDialogMessage = 'live.permission.message';
static const permissionSettingsAction = 'live.permission.settings';
static const permissionLaterAction = 'live.permission.later';

static const livePreviewTitle = 'live.preview.title';
static const livePreviewFieldHint = 'live.preview.fieldHint';
static const livePreviewCameraLabel = 'live.preview.camera';
static const livePreviewMicLabel = 'live.preview.mic';
static const livePreviewTitleError = 'live.preview.titleError';

@coderabbitai coderabbitai Bot Jun 26, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Complete the live-preview localization contract.

lib/features/home/controllers/live_preview_controller.dart still shows several hardcoded English errors/snackbar messages, so adding only these keys leaves the new flow partially untranslated. Please add keys for the remaining live-preview strings and route the controller/UI through them as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/core/localization/app_translation_keys.dart` around lines 43 - 52, The
live-preview localization contract is incomplete because live_preview_controller
still uses hardcoded English error/snackbar text. Add the missing localization
keys alongside the existing AppTranslationKeys livePreview* entries, then update
live_preview_controller and any related UI to read those messages through the
translation layer instead of inline strings. Use the existing AppTranslationKeys
constants and the live_preview_controller flow as the main points to locate and
replace the remaining untranslated strings.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Won't fix in this PR. These localization entries are part of an upcoming feature update and will be added together with the corresponding UI changes to avoid introducing unused translations.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@nustony Understood — deferring the remaining live-preview localization to the upcoming feature update makes sense if you want to avoid landing unused keys in this PR.

If you'd like, I can open a small follow-up issue to track the untranslated live-preview strings for a later PR.

(◔̯◔)🐇

Comment thread lib/features/home/controllers/feed_controller.dart
Comment on lines +38 to +80
Future<void> _initializeCamera() async {
try {
isCameraInitializing.value = true;
cameraError.value = '';
cameras = await availableCameras();

if (cameras == null || cameras!.isEmpty) {
cameraError.value = 'No cameras available';
return;
}

for (var camera in cameras!) {
if (camera.lensDirection == CameraLensDirection.front) {
frontCamera = camera;
} else if (camera.lensDirection == CameraLensDirection.back) {
backCamera = camera;
}
}

final selectedCamera = frontCamera ?? cameras!.first;
isFrontCamera.value = selectedCamera.lensDirection == CameraLensDirection.front;

final controller = CameraController(
selectedCamera,
ResolutionPreset.medium,
enableAudio: isMicOn.value,
);

await controller.initialize();
await controller.setFlashMode(FlashMode.off);

if (isFrontCamera.value) {
await controller.setExposureMode(ExposureMode.auto);
await controller.setFocusMode(FocusMode.auto);
}

cameraController.value = controller;
} catch (e) {
debugPrint('Camera initialization error: $e');
cameraError.value = 'Camera is not ready';
} finally {
isCameraInitializing.value = false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Permission denial still has no recovery path.

The preview flow goes straight into camera initialization and collapses every failure into Camera is not ready. In this rollout you already added a permission dialog/helper, but this controller never requests permissions or routes denied users to settings, so a first-time denial leaves the feature unusable until they back out. Catch the permission-specific failure and invoke that flow here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/features/home/controllers/live_preview_controller.dart` around lines 38 -
80, The `_initializeCamera` flow in `LivePreviewController` swallows all
failures into a generic message and never handles permission denial, leaving no
recovery path for first-time users. Update `_initializeCamera` to detect
camera-permission-specific failures around `availableCameras()` /
`CameraController.initialize()`, then invoke the existing permission
dialog/helper or settings-routing flow instead of only setting
`cameraError.value`. Keep the generic fallback for non-permission errors, but
ensure denied users are guided through the permission recovery path from this
controller.

Comment on lines +83 to +99
Future<void> toggleCamera() async {
final controller = cameraController.value;
if (controller == null) return;
if (!controller.value.isInitialized) return;

isCameraOn.value = !isCameraOn.value;

try {
if (!isCameraOn.value) {
await controller.pausePreview();
} else {
await controller.resumePreview();
}
} catch (e) {
debugPrint('Toggle camera error: $e');
isCameraOn.value = !isCameraOn.value;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For Flutter's camera plugin version 0.12.0+1, does CameraController.pausePreview() release/stop the underlying camera capture session, or does it only pause preview rendering?

💡 Result:

In Flutter's camera plugin, including version 0.12.0+1, CameraController.pausePreview is designed to pause the rendering of the camera preview rather than stopping the underlying camera capture session [1][2]. The method updates the controller's state (setting isPreviewPaused to true) and calls the platform-specific implementation to pause the preview feed [1][2]. It does not dispose of the camera, release the hardware, or stop processes like video recording or image stream availability [1][2]. Because the underlying session remains active, features such as taking pictures generally remain functional, although specific platform behaviors—such as potential bugs where takePicture might experience issues if called immediately after pausePreview—have been reported on certain Android implementations [3]. The primary purpose of pausePreview is to provide a "frozen" visual state for the user interface, such as after capturing an image, without the overhead of re-initializing the camera hardware [4][5].

Citations:


pausePreview() isn’t a real camera-off state. It only pauses preview rendering and keeps the camera session active, so the UI can show Off while the device is still using the camera. If this is meant to disable the camera, dispose and recreate the controller; otherwise rename the control to reflect preview-only behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/features/home/controllers/live_preview_controller.dart` around lines 83 -
99, The toggleCamera() flow in LivePreviewController is treating
pausePreview()/resumePreview() as a true camera on/off switch, but that only
pauses rendering while the camera session stays active. Update the behavior
around toggleCamera() and cameraController so that a real off state disposes the
controller and a later on state recreates/initializes it, or else change the
UI/labeling to make it clear the control only pauses the preview rather than
disabling the camera.

Comment on lines +37 to +43
factory LiveStreamItem.fromMap(Map<String, dynamic> map) {
return LiveStreamItem(
viewerCount: map['viewerCount'] ?? 0,
title: map['title'] ?? '',
streamerName: map['streamerName'] ?? '',
thumbnailUrl: map['thumbnailUrl'] ?? '',
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fail fast on malformed stream payloads.

This factory silently turns missing or renamed fields into blank strings and 0 viewers, which hides upstream contract breaks and lets corrupted items render as valid feed entries. Parse and validate the required fields explicitly instead of defaulting them here.

Proposed fix
   factory LiveStreamItem.fromMap(Map<String, dynamic> map) {
+    final viewerCount = map['viewerCount'];
+    final title = map['title'];
+    final streamerName = map['streamerName'];
+    final thumbnailUrl = map['thumbnailUrl'];
+
+    if (viewerCount is! num ||
+        title is! String ||
+        streamerName is! String ||
+        thumbnailUrl is! String) {
+      throw const FormatException('Invalid LiveStreamItem payload');
+    }
+
     return LiveStreamItem(
-      viewerCount: map['viewerCount'] ?? 0,
-      title: map['title'] ?? '',
-      streamerName: map['streamerName'] ?? '',
-      thumbnailUrl: map['thumbnailUrl'] ?? '',
+      viewerCount: viewerCount.toInt(),
+      title: title,
+      streamerName: streamerName,
+      thumbnailUrl: thumbnailUrl,
     );
   }
📝 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
factory LiveStreamItem.fromMap(Map<String, dynamic> map) {
return LiveStreamItem(
viewerCount: map['viewerCount'] ?? 0,
title: map['title'] ?? '',
streamerName: map['streamerName'] ?? '',
thumbnailUrl: map['thumbnailUrl'] ?? '',
);
factory LiveStreamItem.fromMap(Map<String, dynamic> map) {
final viewerCount = map['viewerCount'];
final title = map['title'];
final streamerName = map['streamerName'];
final thumbnailUrl = map['thumbnailUrl'];
if (viewerCount is! num ||
title is! String ||
streamerName is! String ||
thumbnailUrl is! String) {
throw const FormatException('Invalid LiveStreamItem payload');
}
return LiveStreamItem(
viewerCount: viewerCount.toInt(),
title: title,
streamerName: streamerName,
thumbnailUrl: thumbnailUrl,
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/features/home/models/live_stream_item.dart` around lines 37 - 43, The
LiveStreamItem.fromMap factory is masking malformed payloads by defaulting
missing or renamed fields to empty strings and 0, which hides upstream contract
issues. Update fromMap to explicitly read and validate the required fields for
viewerCount, title, streamerName, and thumbnailUrl, and fail fast when any are
missing or invalid instead of constructing a partially corrupted LiveStreamItem.

Comment on lines +44 to +47
IconButton(
onPressed: () {},
icon: const Icon(Icons.search, size: 30),
),

@coderabbitai coderabbitai Bot Jun 26, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove or disable the search button until it has behavior.

This adds a tappable header action with an empty handler, so the new feed ships a dead control.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/features/home/views/feed_view.dart` around lines 44 - 47, The search
header action is currently a dead control because IconButton in feed_view.dart
has an empty onPressed handler. Update the widget that builds the header action
so the search IconButton is either removed entirely or disabled until real
behavior exists, using the IconButton in the feed header as the unique place to
adjust.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Won't fix in this PR. These localization entries are part of an upcoming feature update and will be added together with the corresponding UI changes to avoid introducing unused translations.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@nustony Understood — and that localization approach makes sense; it also aligns with the repo preference to land translation keys together with the UI that uses them.

For this specific thread, though, my note was about the empty search button in lib/features/home/views/feed_view.dart at Lines 44-47. If that button is also intentionally deferred to a later update, that's fine for me to leave as follow-up.

If you'd like, I can open a GitHub issue to track either:

  • wiring up the search action, or
  • hiding/disabling the button until the feature lands.

(◔_◔)


🧠 Learnings used
Learnt from: nustony
Repo: nustechnology/LiveStream-Zuzu PR: 4
File: lib/features/home/controllers/live_preview_controller.dart:240-267
Timestamp: 2026-06-26T02:36:24.431Z
Learning: In this Flutter repo, when making UI changes that introduce or modify user-facing text (including strings for upcoming features), ensure the corresponding localization entries are added/updated in the localization resources at the same time. Reviewers should confirm that any new translation keys used by the updated widgets exist, and that unused translation keys are not introduced/left behind alongside UI changes.

Comment on lines +299 to +300
child: const Text(
'LIVE',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localize the live badge text.

'LIVE' is still hard-coded here, so translated screens will show mixed-language UI on every stream card.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/features/home/views/feed_view.dart` around lines 299 - 300, The stream
card’s live badge text is still hard-coded in the feed view, so it bypasses
localization. Update the Text widget used for the LIVE badge in the feed card UI
to pull from the app’s localization system instead of using the literal string,
and keep the surrounding badge widget structure unchanged.

Comment on lines +44 to +140
Widget _buildHeader(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Title
const Text(
'Set up your stream',
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),

// Close button
IconButton(
onPressed: () async {
await controller.closePreview();
if (context.mounted && context.canPop()) {
context.pop();
}
},
icon: const Icon(Icons.close, color: Colors.white, size: 28),
),
],
),
);
}

Widget _buildControls() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Camera On/Off
CameraControlButton(
isOn: controller.isCameraOn,
onTap: controller.toggleCamera,
),

// Mic On/Off
MicControlButton(
isOn: controller.isMicOn,
onTap: controller.toggleMic,
),
],
),
);
}

Widget _buildGoLiveButton() {
return Obx(() {
final isLoading = controller.isStreaming.value;

return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed: isLoading ? null : controller.startLiveStream,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFF4D67),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
disabledBackgroundColor: Colors.grey.shade600,
elevation: 0,
),
child: isLoading
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.play_arrow_rounded),
SizedBox(width: 8),
Text(
'Go Live',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

New preview copy bypasses the localization layer.

This screen ships hard-coded English strings (Set up your stream, Go Live), and the sibling preview widgets/controller add more raw copy in the same flow. The livestream rollout already introduced localization support, so these labels should move to translation keys before merge.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/features/home/views/live_preview_screen.dart` around lines 44 - 140, The
preview screen still hard-codes user-facing English copy instead of using the
localization layer. Update the strings in _buildHeader and _buildGoLiveButton
(and any related preview copy in this flow) to use translation keys via the
existing localization API rather than literal text. Keep the labels aligned with
the livestream localization setup so the close/preview controls and the “Go
Live” action are translated consistently.

Comment thread test/widget_test.dart
Comment on lines 56 to +58
await tester.pump(AppDurations.splashDelay);
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Decouple this test from FeedController's current 500 ms delay.

These pumps only pass because lib/features/home/controllers/feed_controller.dart currently waits 500 ms in loadInitialData(). Any harmless timing tweak there will break this test even if splash navigation still works. Prefer waiting for the home marker/widget condition instead of mirroring the controller's internal delay.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/widget_test.dart` around lines 56 - 58, The widget test is coupled to
FeedController.loadInitialData() by waiting an exact 500 ms delay, so replace
that timing-based pump sequence with a condition-based wait for the home screen
state/marker. Update the test in widget_test.dart to assert the navigation
outcome using the relevant home widget or stable UI signal instead of mirroring
the controller’s internal delay, so changes in FeedController do not break the
test.

@nustony nustony closed this Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants