Files
jamshalat-masjid-screen/admin-ui-brief.md

884 lines
21 KiB
Markdown

# Flutter TV Admin UI: Best Practices & Implementation Guide
## Executive Summary
**Objective**: Build a world-class admin dashboard experience for Android TV using Flutter, maintaining the single-APK architecture while delivering excellent UX despite TV input constraints.
**Key Insight**: Flutter is fully capable of building excellent TV dashboards. The cross-platform nature doesn't limit quality - it means we can build platform-optimized experiences with proper focus management and TV-specific widgets.
**Target Platform**: Android TV Box (1920x1080 landscape, D-pad navigation, no touch input)
---
## 1. Understanding Android TV UX Constraints
### Critical Challenges
```
❌ No touch input (D-pad navigation only)
❌ Awkward text input (on-screen keyboard via remote)
❌ Screen distance (2-3 meters from viewer)
❌ Limited input methods (remote control only)
❌ No mouse hover states
❌ Small text is unreadable
```
### Our Advantages
```
✅ Flutter has excellent focus management built-in
✅ Rich animation and transition support
✅ Custom widgets are straightforward to build
✅ TV-specific packages available
✅ We control the entire experience
✅ Hardware acceleration for smooth animations
✅ Scalable UI via MediaQuery
```
---
## 2. Focus Management (The Foundation)
Focus management is **the most critical** aspect of TV UI. All interactions flow through proper focus handling.
### Core Principles
1. **Always show focus**: Users must see what's currently selected
2. **Predictable navigation**: D-pad moves in expected directions
3. **Large focus targets**: Everything must be easily selectable
4. **Clear visual feedback**: Animated focus indicators
5. **Sound feedback**: Audible confirmation of focus changes
### Implementation Pattern
```dart
// All TV widgets should follow this pattern:
class TVWidget extends StatefulWidget {
@override
State<TVWidget> createState() => _TVWidgetState();
}
class _TVWidgetState extends State<TVWidget> {
late FocusNode _focusNode;
@override
void initState() {
super.initState();
_focusNode = FocusNode();
// Listen to focus changes for visual feedback
_focusNode.addListener(() {
setState(() {
// Update UI when focus changes
});
// Play sound feedback
if (_focusNode.hasFocus) {
TVSoundService().playFocusSound();
}
});
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Focus(
focusNode: _focusNode,
onKeyEvent: (node, event) {
// Handle D-pad navigation
return KeyEventResult.ignored;
},
child: Builder(
builder: (context) {
final isFocused = _focusNode.hasFocus;
// Build focused/unfocused states
},
),
);
}
}
```
---
## 3. TV Scaling System
All dimensions must scale relative to screen width to ensure consistency across different TV sizes.
```dart
class TVScaling {
static double scale(BuildContext context) {
return MediaQuery.of(context).size.width / 1920;
}
// Helper methods for common scaled values
static double padding(BuildContext context) => 32 * scale(context);
static double gap(BuildContext context) => 24 * scale(context);
static double borderRadius(BuildContext context) => 12 * scale(context);
// Typography scale
static double fontSizeHeading(BuildContext context) => 48 * scale(context);
static double fontSizeTitle(BuildContext context) => 36 * scale(context);
static double fontSizeBody(BuildContext context) => 28 * scale(context);
static double fontSizeCaption(BuildContext context) => 22 * scale(context);
}
```
---
## 4. TV-Optimized Form Widgets
### A. TV TextField
**Key Features:**
- Extra large hit targets (minimum 48x48dp)
- Clear focus indication (border + background + shadow)
- Keyboard hint for users
- Auto-scroll into view when focused
- Sound feedback on focus
**Usage:**
```dart
TVTextField(
label: 'Nama Masjid',
value: settings.masjidName,
onChanged: (value) => updateSetting('masjidName', value),
hint: 'Masjid Ar-Rahman',
autofocus: false,
)
```
### B. TV Selection Field
**Key Features:**
- Dropdown/selector pattern (avoids typing)
- Search functionality for long lists
- Clear visual hierarchy
- Keyboard navigation (arrow keys)
- Selected state indication
**Usage:**
```dart
TVSelectionField<String>(
label: 'Pilih Lokasi',
value: settings.cityIdApi,
options: [
TVSelectionOption(
label: 'Kota Yogyakarta',
value: '577ef1154f3240ad5b9b413aa7346a1e',
subtitle: 'Daerah Istimewa Yogyakarta',
icon: Icons.location_city,
),
TVSelectionOption(
label: 'Jakarta Pusat',
value: 'another_id_here',
subtitle: 'DKI Jakarta',
icon: Icons.location_city,
),
],
onChanged: (value) => saveLocation(value),
)
```
### C. TV Button
**Key Features:**
- Large touch targets (minimum 48x48dp)
- Multiple style variants (primary, secondary, danger)
- Icon + label support
- Animated focus feedback
- Sound on press
- Keyboard activation (Enter/Select)
**Usage:**
```dart
TVButton(
label: 'Simpan Pengaturan',
icon: Icons.save,
style: TVButtonStyle.primary,
onPressed: () => saveSettings(),
)
// Icon-only variant
TVButton.icon(
icon: Icons.arrow_back,
label: 'Kembali',
onPressed: () => Navigator.pop(context),
)
```
### D. TV Toggle Switch
**Key Features:**
- Large clickable area (entire row)
- Clear on/off state
- Animated transition
- Focus indication
- Label with description
**Usage:**
```dart
TVSwitch(
label: 'Auto Kalibrasi Online',
subtitle: 'Sinkronisasi waktu otomatis saat online',
value: settings.autoCalibrationEnabled,
onChanged: (value) => updateSetting('autoCalibration', value),
)
```
---
## 5. Layout Patterns
### A. Grid-Based Dashboard
**Purpose**: Main admin navigation screen
**Characteristics:**
- 3-column grid layout
- Large cards with icons
- Clear visual hierarchy
- Easy D-pad navigation
- Focus flows naturally left-to-right, top-to-bottom
**Structure:**
```
┌─────────────┬─────────────┬─────────────┐
│ Location │ Appearance │ Settings │
│ & Schedule│ │ │
├─────────────┼─────────────┼─────────────┤
│ Rotation │ About │ │
└─────────────┴─────────────┴─────────────┘
```
### B. Wizard-Style Multi-Step Forms
**Purpose**: Break complex forms into manageable steps
**Benefits:**
- Reduces cognitive load
- Easier navigation
- Progress indication
- Can save state between steps
- Less overwhelming than long forms
**When to Use:**
- Initial setup wizard
- Complex configuration with multiple sections
- Settings that require multiple steps
- Data entry with validation
**Structure:**
```
Step 1: Identity → Step 2: Location → Step 3: Appearance → Step 4: Confirm
↓ ↓ ↓ ↓
[Masjid Name] [Select City] [Theme Color] [Review All]
[Address] [Sync Data] [Background] [Save & Exit]
```
### C. Tabbed Interface
**Purpose**: Organize related settings
**Benefits:**
- Clear categorization
- Easy to add new sections
- Familiar pattern from web
- Works well with D-pad
**Tab Structure:**
1. **Identitas** - Mosque name, address
2. **Jadwal & Sync** - Location, synchronization
3. **Tampilan** - Theme, background, slideshow
4. **Rotasi** - Screen timing
5. **Pembarisan** - Updates, version
6. **Tentang** - App information
---
## 6. Reducing Text Input (The Secret Sauce)
The best TV admin interfaces **minimize typing**. This is crucial because:
1. TV remotes are terrible for text input
2. On-screen keyboards are slow and error-prone
3. Screen distance makes typing difficult
4. User frustration increases with typing
### Strategies to Minimize Typing
#### A. Use Selection Instead of Text Input
**Bad (Text Input):**
```dart
TextField(
decoration: InputDecoration(labelText: 'City ID API'),
)
```
**Good (Selection):**
```dart
TVSelectionField<String>(
label: 'Pilih Kota',
value: settings.cityIdApi,
options: cityOptions,
onChanged: (value) => saveCity(value),
)
```
#### B. Search + Select Pattern
For long lists (like 500+ Indonesian cities):
```dart
TVSearchSelectField<String>(
label: 'Cari Lokasi',
hint: 'Ketik nama kota...',
searchDelegate: MyQuranCitySearchDelegate(),
onSelect: (city) => saveCity(city),
)
```
#### C. Preset Options with Custom Value
```dart
TVSelectionField<String>(
label: 'Warna Tema',
value: settings.themeColor,
options: [
TVSelectionOption(label: 'Hijau', value: '0xFF006400'),
TVSelectionOption(label: 'Biru', value: '0xFF0000FF'),
TVSelectionOption(label: 'Merah', value: '0xFFFF0000'),
TVSelectionOption(label: 'Emas', value: '0xFFFFD700'),
TVSelectionOption(label: 'Kustom...', value: 'custom'),
],
onChanged: (value) {
if (value == 'custom') {
// Show color picker dialog
} else {
saveThemeColor(value);
}
},
)
```
#### D. Slider for Numeric Values
**Bad (Number Input):**
```dart
TextField(
keyboardType: TextInputType.number,
decoration: InputDecoration(labelText: 'Durasi Iqomah (menit)'),
)
```
**Good (Slider):**
```dart
TVSlider(
label: 'Durasi Iqomah',
value: settings.iqomahDuration.inMinutes,
min: 5,
max: 20,
divisions: 15,
unit: 'menit',
onChanged: (value) => saveIqomahDuration(Duration(minutes: value)),
)
```
#### E. Time Picker
```dart
TVTimePicker(
label: 'Waktu Mulai Subuh',
value: settings.subuhStartTime,
onChanged: (value) => saveSubuhStartTime(value),
)
```
---
## 7. Visual Design Guidelines
### Focus Indicators
Focus indicators must be:
- **High contrast** (visible from 2-3 meters)
- **Animated** (smooth transitions, ~200ms)
- **Multi-layered** (border + background + shadow)
- **Consistent** (same pattern throughout app)
**Recommended Focus States:**
```dart
// Unfocused
border: 1px solid grey
background: white
shadow: none
// Focused
border: 3px solid primary_color
background: primary_color_10_percent
shadow: primary_color_30_percent_blur_20px
```
### Typography Scale
```dart
// Based on 1920px width baseline
Display (Clock): 120px // For large time displays
Heading: 48px // Screen titles
Title: 36px // Section headers, button labels
Body: 28px // Form labels, content
Caption: 22px // Metadata, hints
```
### Color Contrast
- **Minimum contrast ratio**: 4.5:1 (WCAG AA)
- **Recommended contrast ratio**: 7:1 (WCAG AAA)
- **Focus indication**: Always use high contrast
- **Error states**: Red with white text
- **Success states**: Green with white text
### Spacing System
```dart
// Based on 8px grid system
padding_xs: 8px // Tight spacing
padding_sm: 16px // Compact
padding_md: 24px // Default
padding_lg: 32px // Comfortable
padding_xl: 48px // Generous
```
---
## 8. Navigation Patterns
### D-Pad Navigation Flow
```
← [Focus] →
```
**Rules:**
1. **Natural flow**: Left-to-right, top-to-bottom
2. **Wrap around**: Last item → first item in same row
3. **Section boundaries**: Clear visual separators
4. **Skip separators**: Focus should jump over dividers
### Keyboard Shortcuts
```dart
// Standard Android TV shortcuts
KEY_ENTER / KEY_SELECT: Activate focused item
KEY_BACK: Go back / cancel
KEY_MENU: Show context menu
KEY_MEDIA_PLAY_PAUSE: Play/pause media
KEY_MEDIA_FAST_FORWARD: Skip forward
KEY_MEDIA_REWIND: Skip back
```
---
## 9. Feedback Systems
### Visual Feedback
1. **Focus indicator**: Border + background + shadow
2. **Pressed state**: Scale down slightly (0.95x)
3. **Loading state**: Progress indicator + "Loading..." text
4. **Success state**: Green checkmark + success message
5. **Error state**: Red X + error message
### Sound Feedback
```dart
// Sound effects for different interactions
focus.mp3: Subtle "click" when focus moves
select.mp3: Confirming "ding" when item activated
error.mp3: Low "buzz" for errors
success.mp3: Pleasant "chime" for success
```
**Implementation:**
```dart
class TVSoundService {
static final TVSoundService _instance = TVSoundService._internal();
factory TVSoundService() => _instance;
TVSoundService._internal();
final AudioPlayer _audioPlayer = AudioPlayer();
Future<void> playFocus() => _audioPlayer.play(AssetSource('sounds/focus.mp3'));
Future<void> playSelect() => _audioPlayer.play(AssetSource('sounds/select.mp3'));
Future<void> playError() => _audioPlayer.play(AssetSource('sounds/error.mp3'));
Future<void> playSuccess() => _audioPlayer.play(AssetSource('sounds/success.mp3'));
}
```
### Haptic Feedback
```dart
// For game controllers with vibration
if (_focusNode.hasFocus) {
HapticFeedback.lightImpact(); // Subtle vibration on focus
}
```
---
## 10. Performance Considerations
### Animation Performance
```dart
// Use AnimatedBuilder for efficient rebuilds
AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Opacity(
opacity: _animation.value,
child: child,
);
},
child: ExpensiveWidget(), // Built only once
)
```
### Lazy Loading
```dart
// Use ListView.builder for long lists
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return TVListItem(item: items[index]);
},
)
```
### Image Optimization
```dart
// Cache images, use placeholders
CachedNetworkImage(
imageUrl: url,
placeholder: (context, url) => TVLoadingIndicator(),
errorWidget: (context, url, error) => TVErrorIcon(),
)
```
---
## 11. Accessibility
### Screen Reader Support
```dart
// Add semantic labels
Semantics(
label: 'Nama masjid field',
value: settings.masjidName,
hint: 'Masukkan nama masjid',
child: TVTextField(...),
)
```
### High Contrast Mode
```dart
// Respect system high contrast setting
final isHighContrast = MediaQuery.of(context).highContrast;
if (isHighContrast) {
// Use black and white only
// Remove subtle gradients
// Increase border widths
}
```
### Font Scaling
```dart
// Respect user's font size preferences
final scaleFactor = MediaQuery.of(context).textScaleFactor;
Text(
'Hello',
style: TextStyle(fontSize: 28 * scaleFactor),
)
```
---
## 12. Implementation Roadmap
### Phase 1: Foundation (Week 1)
**Focus Management System**
- [ ] Create `TVFocusManager` widget
- [ ] Implement focus scope nodes
- [ ] Add focus listener utilities
- [ ] Create focus ring animation
- [ ] Test D-pad navigation
**Scaling System**
- [ ] Create `TVScaling` utility
- [ ] Implement responsive layout helpers
- [ ] Define typography scale
- [ ] Test on different screen sizes
### Phase 2: Core Widgets (Week 2)
**Form Widgets**
- [ ] `TVTextField` with focus management
- [ ] `TVSelectionField` with dropdown
- [ ] `TVButton` with multiple styles
- [ ] `TVSwitch` for toggles
- [ ] `TVSlider` for numeric values
**Layout Widgets**
- [ ] `TVDashboardTile` for grid items
- [ ] `TVListItem` for list items
- [ ] `TVCard` for containers
- [ ] `TVSection` for grouping
### Phase 3: Advanced Features (Week 3)
**Navigation & Layouts**
- [ ] Grid-based dashboard screen
- [ ] Wizard-style multi-step form
- [ ] Tabbed interface
- [ ] Breadcrumb navigation
**Input Optimization**
- [ ] `TVSearchSelectField` with search
- [ ] `TVTimePicker` for time selection
- [ ] `TVDatePicker` for date selection
- [ ] `TVColorPicker` for color selection
### Phase 4: Polish (Week 4)
**Feedback Systems**
- [ ] Sound effects (focus, select, error, success)
- [ ] Visual feedback animations
- [ ] Loading indicators
- [ ] Error state UIs
- [ ] Success state UIs
**Performance**
- [ ] Animation optimization
- [ ] Image caching
- [ ] Lazy loading
- [ ] Memory leak testing
**Accessibility**
- [ ] Screen reader support
- [ ] High contrast mode
- [ ] Font scaling
- [ ] Focus indicators for color blind users
---
## 13. Testing Guidelines
### Unit Tests
```dart
testWidgets('TVTextField should show focus indicator', (tester) async {
await tester.pumpWidget(
MaterialApp(home: TVTextField(label: 'Test')),
);
// Simulate focus
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
await tester.pump();
// Verify focus indicator is visible
expect(find.byType(Border), findsOneWidget);
});
```
### Integration Tests
```dart
testWidgets('Complete admin flow should work', (tester) async {
// Build admin screen
await tester.pumpWidget(MyApp());
// Navigate to location settings
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
await tester.pumpAndSettle();
// Select city
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
await tester.pumpAndSettle();
// Verify selection was saved
expect(find.text('Kota Yogyakarta'), findsOneWidget);
});
```
### Manual Testing Checklist
- [ ] All widgets are focusable with D-pad
- [ ] Focus indicators are clearly visible
- [ ] Navigation flow is predictable
- [ ] Text input works (though awkward)
- [ ] Sound feedback plays correctly
- [ ] Animations are smooth (60fps)
- [ ] Forms can be completed without mouse/touch
- [ ] Error states are clear
- [ ] Success states provide feedback
- [ ] Back button exits correctly
---
## 14. Common Pitfalls to Avoid
### ❌ Don't
1. **Don't use small hit targets**: Everything must be at least 48x48dp
2. **Don't rely on hover states**: TV remotes don't have hover
3. **Don't use subtle gradients**: Hard to see from distance
4. **Don't require extensive typing**: It's frustrating on TV
5. **Don't use small fonts**: Under 24px is unreadable from 2m
6. **Don't hide navigation**: Always show clear focus indicators
7. **Don't use web-only patterns**: TV ≠ web browser
8. **Don't forget sound feedback**: Crucial for accessibility
9. **Don't ignore keyboard shortcuts**: Power users expect them
10. **Don't make users backtrack**: Forward-flow navigation only
### ✅ Do
1. **Do make focus obvious**: Large borders, backgrounds, shadows
2. **Do provide clear feedback**: Visual + audio + haptic
3. **Do minimize text input**: Use selectors, wizards, presets
4. **Do use large fonts**: Minimum 24px, recommended 28px+
5. **Do test on real TV**: Emulator ≠ real experience
6. **Do optimize for D-pad**: Natural navigation flow
7. **Do add sound effects: Helpful feedback
8. **Do use animations: Smooth transitions (200ms)
9. **Do provide shortcuts: Keyboard navigation
10. **Do think in steps: Break complex forms into wizards
---
## 15. Recommended Packages
```yaml
dependencies:
# TV-specific components
flutter_tencent_eui: ^latest # TV-optimized UI components
# Enhanced focus management
flutter_focus: ^latest # Better focus control
# Sound feedback
audioplayers: ^latest # Sound effects
# Animations
animations: ^latest # Smooth transition helpers
# Form validation
form_field_validator: ^latest # Reduce user errors
# Image handling
cached_network_image: ^latest # Image caching
```
---
## 16. Success Metrics
### UX Metrics
- **Time to complete common tasks**:
- Change mosque name: < 30 seconds
- Change location: < 2 minutes
- Sync monthly data: < 1 minute
- Update theme: < 45 seconds
- **Error rate**:
- Form validation errors: < 5%
- Navigation errors: < 1%
- Focus loss: < 2%
- **User satisfaction**:
- Task completion rate: > 95%
- User-reported frustration: < 10%
- Willingness to use again: > 90%
### Technical Metrics
- **Performance**:
- Focus change: < 16ms (60fps)
- Screen transition: < 200ms
- Form submission: < 500ms
- No frame drops during animations
- **Reliability**:
- No crashes in 24h operation
- No memory leaks
- No focus loss bugs
- Graceful error handling
---
## 17. Conclusion
Flutter is **fully capable** of building excellent TV admin dashboards. The key is:
1. **Embrace TV constraints** rather than fighting them
2. **Think in focus**, not clicks
3. **Minimize text input** through smart UI choices
4. **Provide clear feedback** at every interaction
5. **Test on real hardware** with actual users
The single-APK architecture is **totally viable** with these TV-optimized widgets. You don't need a separate phone app to deliver a great admin experience.
### Next Steps
1. **Start with focus management** - this is the foundation
2. **Build TV-optimized widgets** - large, clear, responsive
3. **Reduce text input** - use selectors and wizards
4. **Add polish** - sounds, animations, feedback
5. **Test thoroughly** - on real TV with real users
Your admin dashboard will be excellent!
---
## Appendix: Code Examples
### Complete TV TextField Example
See `/lib/ui/widgets/tv/tv_text_field.dart` for full implementation.
### Complete TV Selection Field Example
See `/lib/ui/widgets/tv/tv_selection_field.dart` for full implementation.
### Complete TV Dashboard Example
See `/lib/ui/screens/tv_admin_screen.dart` for full implementation.
---
**Document Version**: 1.0
**Last Updated**: 2025-01-XX
**Author**: Claude Code
**Status**: Ready for Implementation