Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions openspec/changes/standardize-root-headers/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-08-11
23 changes: 23 additions & 0 deletions openspec/changes/standardize-root-headers/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## Context

The monorepo currently suffers from duplicated UI layout logic for root-level screens (e.g., Study, AI Support, Profile, Exams, Info). Each of these screens manually wraps a `Column` inside a `Container` with hardcoded top padding calculated via `MediaQuery.paddingOf(context)` to avoid overlaps with the system status bar and notches.

## Goals / Non-Goals

**Goals:**
- Unify all standard root-level and sub-level header layouts under a single `SectionHeader` widget.
- Support `leadingIcon` actions (menu/back buttons) explicitly.
- Support `trailingAction` widgets.
- Support optional `secondaryContent` below the main header row.

**Non-Goals:**
- Modifying the navigation paradigms or routing of the app.
- Changing the visual styling or colors (it should perfectly replicate the existing design).

## Decisions

- **Nullable Parameters:** To ensure maximal reusability, `leadingIcon`, `trailingAction`, and `secondaryContent` in `SectionHeader` will all be nullable.

## Risks / Trade-offs

- [Risk] Replacing manual paddings might slightly alter pixel-perfect alignments if not done carefully. -> Mitigation: Ensure `SectionHeader` uses the equivalent padding logic (`math.max(padding.left, design.spacing.md)`) as the original screens.
24 changes: 24 additions & 0 deletions openspec/changes/standardize-root-headers/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## Why

Many root-level screens (Study, AI, Profile, Info, etc.) manually implement identical container-based safe-area padding logic and header styling. Unifying them into a single widget reduces tech debt, ensures design consistency, and improves maintainability across the monorepo.

## What Changes

- Create a generic `SectionHeader` widget in the `core` package that supports built-in safe area padding, leading icons (menu/back buttons), title text, trailing actions, and secondary content (search bars, subtitles).
- Strip out manual header containers in `StudyScreen`, `AiScreen`, `InfoPage`, and `ExamsScreen` and replace them with `SectionHeader`.
- Strip out manual headers in `NotificationsScreen` and `CertificatesScreen` and replace them with `AppHeader` (removing subtitle layouts).

## Capabilities

### New Capabilities
- `SectionHeader`: A highly flexible layout widget in the core package for root-level and sub-level page headers, supporting dynamic leading icons, trailing actions, and secondary content.

### Modified Capabilities
None

## Impact

- **packages/core**: Adds `SectionHeader` widget, updates `AiScreen`.
- **packages/profile**: Updates `PaidActiveProfileScreen`, `NotificationsScreen`, `CertificatesScreen`.
- **packages/courses**: Updates `StudyScreen`, `InfoPage`, `StorePage`.
- **packages/exams**: Updates `ExamsScreen`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## ADDED Requirements

### Requirement: Unified Section Header Layout
The system SHALL provide a `SectionHeader` widget in the core package that unifies layout and safe-area padding for all major screens.

#### Scenario: Rendering standard title
- **WHEN** the header is rendered with a string title
- **THEN** it displays the text title using the `AppText.headline` style

#### Scenario: Rendering leading and trailing actions
- **WHEN** `leadingIcon` and/or `trailingAction` widgets are provided
- **THEN** they are displayed on the left and right sides of the title row respectively

#### Scenario: Rendering secondary content
- **WHEN** a `secondaryContent` widget is provided
- **THEN** it is displayed below the main title row with appropriate vertical spacing

#### Scenario: Automatic safe area handling
- **WHEN** the header is rendered on a device with a notch or status bar
- **THEN** it automatically applies top, left, and right padding using `MediaQuery.paddingOf(context)` combined with design spacing to ensure content is not obscured
23 changes: 23 additions & 0 deletions openspec/changes/standardize-root-headers/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## 1. Core Widget

- [x] 1.1 Create `SectionHeader` widget in `packages/core/lib/widgets/section_header.dart` with support for `leadingIcon`, `title`, `trailingAction`, and `secondaryContent`

## 2. Refactor Core usages

- [x] 2.1 Refactor `AiScreen` in `packages/core/lib/screens/ai_screen.dart` to use `SectionHeader`

## 3. Refactor Courses usages

- [x] 3.1 Refactor `StudyScreen` in `packages/courses/lib/screens/study_screen.dart` to use `SectionHeader` (passing search bar and filters to `secondaryContent`)
- [x] 3.2 Refactor `InfoPage` in `packages/courses/lib/screens/info/info_page.dart` to use `SectionHeader`
- [x] 3.3 Refactor `StorePage` in `packages/courses/lib/screens/store/store_page.dart` to use `SectionHeader`

## 4. Refactor Exams usages

- [x] 4.1 Refactor `ExamsScreen` in `packages/exams/lib/screens/exams_screen.dart` to use `SectionHeader`

## 5. Refactor Profile usages

- [x] 5.1 Refactor `PaidActiveProfileScreen` in `packages/profile/lib/screens/paid_active_profile_screen.dart` to use `SectionHeader`
- [x] 5.2 Refactor `NotificationsScreen` in `packages/profile/lib/screens/notifications_screen.dart` to use `AppHeader` (and removed subtitles since they are standard pages, not root headers)
- [x] 5.3 Refactor `CertificatesScreen` in `packages/profile/lib/screens/certificates_screen.dart` to use `AppHeader` (and removed subtitles since they are standard pages, not root headers)
1 change: 1 addition & 0 deletions packages/core/lib/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export 'widgets/app_drawer.dart';
export 'widgets/app_bottom_sheet.dart';
export 'widgets/app_error_view.dart';
export 'widgets/app_webview.dart';
export 'widgets/section_header.dart';
export 'widgets/dashboard_header.dart';
export 'widgets/lesson_detail_shell.dart';
export 'widgets/bookmark_folders_sheet.dart';
Expand Down
12 changes: 0 additions & 12 deletions packages/core/lib/generated/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -870,12 +870,6 @@ abstract class AppLocalizations {
/// **'Notifications'**
String get profileNotifications;

/// No description provided for @notificationsManagePreferences.
///
/// In en, this message translates to:
/// **'Manage your notification preferences'**
String get notificationsManagePreferences;

/// No description provided for @notificationsLiveClassReminders.
///
/// In en, this message translates to:
Expand Down Expand Up @@ -966,12 +960,6 @@ abstract class AppLocalizations {
/// **'Log out'**
String get logoutButtonLabel;

/// No description provided for @certificatesSubtitleAvailable.
///
/// In en, this message translates to:
/// **'View and download your course completion certificates'**
String get certificatesSubtitleAvailable;

/// No description provided for @certificatesEmptyPaidNewDesc.
///
/// In en, this message translates to:
Expand Down
8 changes: 0 additions & 8 deletions packages/core/lib/generated/l10n/app_localizations_ar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -417,10 +417,6 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get profileNotifications => 'التنبيهات';

@override
String get notificationsManagePreferences =>
'إدارة تفضيلات التنبيهات الخاصة بك';

@override
String get notificationsLiveClassReminders => 'تذكيرات الحصص المباشرة';

Expand Down Expand Up @@ -472,10 +468,6 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get logoutButtonLabel => 'تسجيل الخروج';

@override
String get certificatesSubtitleAvailable =>
'اعرض وحمّل شهادات إكمال الدورات الخاصة بك';

@override
String get certificatesEmptyPaidNewDesc => 'لا توجد شهادات متاحة بعد';

Expand Down
8 changes: 0 additions & 8 deletions packages/core/lib/generated/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -417,10 +417,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get profileNotifications => 'Notifications';

@override
String get notificationsManagePreferences =>
'Manage your notification preferences';

@override
String get notificationsLiveClassReminders => 'Live class reminders';

Expand Down Expand Up @@ -471,10 +467,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get logoutButtonLabel => 'Log out';

@override
String get certificatesSubtitleAvailable =>
'View and download your course completion certificates';

@override
String get certificatesEmptyPaidNewDesc => 'No certificates available yet';

Expand Down
8 changes: 0 additions & 8 deletions packages/core/lib/generated/l10n/app_localizations_ml.dart
Original file line number Diff line number Diff line change
Expand Up @@ -419,10 +419,6 @@ class AppLocalizationsMl extends AppLocalizations {
@override
String get profileNotifications => 'നോട്ടിഫിക്കേഷനുകൾ';

@override
String get notificationsManagePreferences =>
'നിങ്ങളുടെ നോട്ടിഫിക്കേഷൻ മുൻഗണനകൾ നിയന്ത്രിക്കുക';

@override
String get notificationsLiveClassReminders => 'ലൈവ് ക്ലാസ് റിമൈൻഡറുകൾ';

Expand Down Expand Up @@ -475,10 +471,6 @@ class AppLocalizationsMl extends AppLocalizations {
@override
String get logoutButtonLabel => 'ലോഗൗട്ട്';

@override
String get certificatesSubtitleAvailable =>
'നിങ്ങളുടെ കോഴ്‌സ് പൂർത്തീകരണ സർട്ടിഫിക്കറ്റുകൾ കാണുകയും ഡൗൺലോഡ് ചെയ്യുകയും ചെയ്യുക';

@override
String get certificatesEmptyPaidNewDesc =>
'ഇപ്പോൾ സർട്ടിഫിക്കറ്റുകൾ ഒന്നുമില്ല';
Expand Down
8 changes: 0 additions & 8 deletions packages/core/lib/generated/l10n/app_localizations_ta.dart
Original file line number Diff line number Diff line change
Expand Up @@ -423,10 +423,6 @@ class AppLocalizationsTa extends AppLocalizations {
@override
String get profileNotifications => 'அறிவிப்புகள்';

@override
String get notificationsManagePreferences =>
'உங்கள் அறிவிப்பு விருப்பங்களை நிர்வகிக்கவும்';

@override
String get notificationsLiveClassReminders => 'நேரலை வகுப்பு நினைவூட்டல்கள்';

Expand Down Expand Up @@ -479,10 +475,6 @@ class AppLocalizationsTa extends AppLocalizations {
@override
String get logoutButtonLabel => 'வெளியேறு';

@override
String get certificatesSubtitleAvailable =>
'உங்கள் பாடநெறி முடித்த சான்றிதழ்களைப் பார்த்து பதிவிறக்கவும்';

@override
String get certificatesEmptyPaidNewDesc =>
'இன்னும் சான்றிதழ்கள் கிடைக்கவில்லை';
Expand Down
2 changes: 0 additions & 2 deletions packages/core/lib/l10n/app_ar.arb
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@
"profileAccountSettingsTitle": "الحساب والتفضيلات",
"profileEditProfile": "تعديل الملف الشخصي",
"profileNotifications": "التنبيهات",
"notificationsManagePreferences": "إدارة تفضيلات التنبيهات الخاصة بك",
"notificationsLiveClassReminders": "تذكيرات الحصص المباشرة",
"notificationsLiveClassRemindersDesc": "تلقي تنبيه قبل بدء الحصص المباشرة",
"notificationsTestAssessmentAlerts": "تنبيهات الاختبارات والتقييمات",
Expand All @@ -172,7 +171,6 @@
"logoutConfirmationTitle": "هل تريد تسجيل الخروج؟",
"logoutConfirmationMessage": "ستحتاج إلى تسجيل الدخول مرة أخرى للوصول إلى حسابك",
"logoutButtonLabel": "تسجيل الخروج",
"certificatesSubtitleAvailable": "اعرض وحمّل شهادات إكمال الدورات الخاصة بك",
"certificatesEmptyPaidNewDesc": "لا توجد شهادات متاحة بعد",
"certificatesLockedBadge": "أكمل الدورة لفتح الشهادة",
"certificatesUnlockedBadge": "شهادة إكمال",
Expand Down
2 changes: 0 additions & 2 deletions packages/core/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@
"profileAccountSettingsTitle": "Account & preferences",
"profileEditProfile": "Edit Profile",
"profileNotifications": "Notifications",
"notificationsManagePreferences": "Manage your notification preferences",
"notificationsLiveClassReminders": "Live class reminders",
"notificationsLiveClassRemindersDesc": "Get notified before your live classes start",
"notificationsTestAssessmentAlerts": "Test and assessment alerts",
Expand All @@ -184,7 +183,6 @@
"logoutConfirmationTitle": "Log out?",
"logoutConfirmationMessage": "You'll need to log in again to access your account",
"logoutButtonLabel": "Log out",
"certificatesSubtitleAvailable": "View and download your course completion certificates",
"certificatesEmptyPaidNewDesc": "No certificates available yet",
"certificatesLockedBadge": "COMPLETE COURSE TO UNLOCK",
"certificatesUnlockedBadge": "CERTIFICATE OF COMPLETION",
Expand Down
2 changes: 0 additions & 2 deletions packages/core/lib/l10n/app_ml.arb
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@
"profileAccountSettingsTitle": "അക്കൗണ്ടും ക്രമീകരണങ്ങളും",
"profileEditProfile": "പ്രൊഫൈൽ എഡിറ്റ് ചെയ്യുക",
"profileNotifications": "നോട്ടിഫിക്കേഷനുകൾ",
"notificationsManagePreferences": "നിങ്ങളുടെ നോട്ടിഫിക്കേഷൻ മുൻഗണനകൾ നിയന്ത്രിക്കുക",
"notificationsLiveClassReminders": "ലൈവ് ക്ലാസ് റിമൈൻഡറുകൾ",
"notificationsLiveClassRemindersDesc": "ലൈവ് ക്ലാസുകൾ തുടങ്ങുന്നതിന് മുമ്പ് അറിയിപ്പുകൾ നേടുക",
"notificationsTestAssessmentAlerts": "ടെസ്റ്റ്, അസെസ്മെന്റ് അലർട്ടുകൾ",
Expand All @@ -172,7 +171,6 @@
"logoutConfirmationTitle": "ലോഗൗട്ട് ചെയ്യണോ?",
"logoutConfirmationMessage": "നിങ്ങളുടെ അക്കൗണ്ട് ആക്സസ് ചെയ്യാൻ വീണ്ടും ലോഗിൻ ചെയ്യേണ്ടതുണ്ട്",
"logoutButtonLabel": "ലോഗൗട്ട്",
"certificatesSubtitleAvailable": "നിങ്ങളുടെ കോഴ്‌സ് പൂർത്തീകരണ സർട്ടിഫിക്കറ്റുകൾ കാണുകയും ഡൗൺലോഡ് ചെയ്യുകയും ചെയ്യുക",
"certificatesEmptyPaidNewDesc": "ഇപ്പോൾ സർട്ടിഫിക്കറ്റുകൾ ഒന്നുമില്ല",
"certificatesLockedBadge": "അൺലോക്ക് ചെയ്യാൻ കോഴ്‌സ് പൂർത്തിയാക്കുക",
"certificatesUnlockedBadge": "പൂർത്തീകരണ സർട്ടിഫിക്കറ്റ്",
Expand Down
2 changes: 0 additions & 2 deletions packages/core/lib/l10n/app_ta.arb
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@
"profileAccountSettingsTitle": "கணக்கு மற்றும் விருப்பங்கள்",
"profileEditProfile": "சுயவிவரத்தைத் திருத்து",
"profileNotifications": "அறிவிப்புகள்",
"notificationsManagePreferences": "உங்கள் அறிவிப்பு விருப்பங்களை நிர்வகிக்கவும்",
"notificationsLiveClassReminders": "நேரலை வகுப்பு நினைவூட்டல்கள்",
"notificationsLiveClassRemindersDesc": "உங்கள் நேரலை வகுப்புகள் தொடங்கும் முன் அறிவிப்பைப் பெறவும்",
"notificationsTestAssessmentAlerts": "தேர்வு மற்றும் மதிப்பீட்டு விழிப்பூட்டல்கள்",
Expand All @@ -184,7 +183,6 @@
"logoutConfirmationTitle": "வெளியேறவா?",
"logoutConfirmationMessage": "உங்கள் கணக்கை அணுக மீண்டும் உள்நுழைய வேண்டும்",
"logoutButtonLabel": "வெளியேறு",
"certificatesSubtitleAvailable": "உங்கள் பாடநெறி முடித்த சான்றிதழ்களைப் பார்த்து பதிவிறக்கவும்",
"certificatesEmptyPaidNewDesc": "இன்னும் சான்றிதழ்கள் கிடைக்கவில்லை",
"certificatesLockedBadge": "திறக்க பாடத்தை முடிக்கவும்",
"certificatesUnlockedBadge": "முடித்ததற்கான சான்றிதழ்",
Expand Down
27 changes: 1 addition & 26 deletions packages/core/lib/screens/ai_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class AiScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final design = Design.of(context);
final l10n = L10n.of(context);
final padding = MediaQuery.paddingOf(context);
final user = ref.watch(userProvider).valueOrNull;
final userName = user?.name;

Expand All @@ -28,31 +27,7 @@ class AiScreen extends ConsumerWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
decoration: BoxDecoration(
color: design.colors.card,
border: Border(
bottom: BorderSide(color: design.colors.divider, width: 1),
),
),
padding: EdgeInsets.fromLTRB(
padding.left > design.spacing.md
? padding.left
: design.spacing.md,
padding.top + design.spacing.md,
padding.right > design.spacing.md
? padding.right
: design.spacing.md,
design.spacing.md,
),
child: AppSemantics.header(
label: l10n.aiSupportTitle,
child: AppText.headline(
l10n.aiSupportTitle,
color: design.colors.textPrimary,
),
),
),
SectionHeader(title: l10n.aiSupportTitle),
Expanded(
child: AppScroll(
padding: EdgeInsets.symmetric(
Expand Down
Loading