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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ db/*.db
# OS files
.DS_Store
Thumbs.db
cookies.txt
249 changes: 249 additions & 0 deletions IMPLEMENTATION_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
# Implementation Summary: Flash Message Visibility & M3U8 Single-Stream Field

## Overview
This implementation addresses two key user experience improvements for the RetroIPTVGuide tuner management interface:
1. Persistent flash messages that users can actually read
2. Simplified single-stream M3U8 URL support

## Changes Implemented

### 1. Flash Message Visibility Enhancement

#### Problem Solved
Previously, flash messages (validation errors, success messages, warnings) would disappear too quickly for users to read them, especially on the change_tuner page where they weren't even displayed.

#### Solution Implemented
- **Flash message display added** to `templates/change_tuner.html`
- **Sticky positioning** keeps messages visible while scrolling
- **8-second persistence** with smooth fade-out animation
- **Manual dismiss button** (×) for immediate closure
- **Color-coded categories**: success (green), warning (yellow), error (red), info (blue)
- **Theme support**: Proper contrast in both dark and light themes

#### Code Structure
```html
<!-- Flash Messages Container -->
<div class="flash-messages-container">
<div class="flash-message flash-{{ category }}">
<span class="flash-content">{{ message }}</span>
<button class="flash-close" aria-label="Close message">&times;</button>
</div>
</div>
```

#### CSS Features
- Sticky positioning at `top: 20px`
- Smooth opacity transition (0.5s)
- Box shadow for depth
- Responsive padding and margins
- Theme-specific color adjustments

#### JavaScript Behavior
- Auto-dismiss after 8000ms
- Manual close on button click
- Fade-out animation (500ms) before removal
- Error logging for debugging

---

### 2. M3U8 Single-Stream Support

#### Problem Solved
Users wanted to add single M3U8 stream URLs but had to use both XML and M3U URL fields, which was confusing and redundant for single streams.

#### Solution Implemented
- **Radio button mode selector**: Standard Playlist Mode vs. Single Stream Mode
- **Dynamic field visibility**: Shows/hides fields based on selected mode
- **Conditional validation**: Required fields adapt to mode selection
- **Backend support**: Handles single-stream by using same URL for both fields
- **Backward compatibility**: Existing tuners work unchanged

#### UI Layout
```
┌─────────────────────────────────────────────┐
│ Add new tuner │
├─────────────────────────────────────────────┤
│ Tuner Name: [_______________] │
│ │
│ Tuner Mode: │
│ ○ Standard Playlist Mode │
│ ○ Single Stream Mode │
│ │
│ [Conditional fields shown based on mode] │
│ │
│ [Add Tuner] │
└─────────────────────────────────────────────┘
```

#### JavaScript Mode Switching
```javascript
function updateFormMode() {
const selectedMode = document.querySelector('input[name="tuner_mode"]:checked');
if (selectedMode && selectedMode.value === 'single_stream') {
// Show stream field, hide standard fields
// Update required attributes
} else {
// Show standard fields, hide stream field
// Update required attributes
}
}
```

#### Backend Logic
```python
if tuner_mode == "single_stream":
# Use same M3U8 URL for both XML and M3U fields
add_tuner(name, m3u8_stream_url, m3u8_stream_url)
else:
# Standard mode: different URLs
add_tuner(name, xml_url, m3u_url)
```

---

## Files Modified

### 1. templates/change_tuner.html
- Added flash message display container (lines 15-27)
- Added mode selection radio buttons (lines 135-149)
- Added M3U8 stream input field (lines 159-163)
- Added flash message auto-dismiss JavaScript (lines 201-220)
- Added mode switching JavaScript (lines 346-373)

### 2. static/css/change_tuner.css
- Added flash message base styles (lines 308-340)
- Added flash message type styles (lines 342-374)
- Added light theme adjustments (lines 376-407)

### 3. app.py
- Updated add_tuner action to detect mode (line 868)
- Added single-stream mode handling (lines 871-882)
- Added standard mode validation (lines 884-898)
- Updated flash message categories to use "success"

### 4. tests/test_single_stream_mode.py (NEW)
- Test suite with 4 comprehensive tests
- Tests single-stream mode addition
- Tests standard mode addition
- Tests duplicate name rejection
- Tests empty URL validation

---

## Testing Results

### Test Suite
```
18 tests passing
- 4 new tests for single-stream mode
- 13 existing tuner validation tests
- 1 placeholder test
```

### Security Scan
```
CodeQL Analysis: 0 alerts
No security vulnerabilities found
```

---

## Accessibility Improvements

1. **Proper label associations**: Radio buttons have matching ID and for attributes
2. **ARIA labels**: Close button has aria-label="Close message"
3. **Semantic HTML**: Proper use of sections, labels, and form elements
4. **Keyboard navigation**: All interactive elements are keyboard accessible
5. **Error messages**: Clear console logging with user guidance

---

## Backward Compatibility

✅ Existing tuners continue to work without modification
✅ Standard mode is the default selection
✅ No database schema changes
✅ No breaking changes to API
✅ All existing functionality preserved

---

## User Experience Improvements

### Before
- Flash messages disappeared too quickly to read
- No way to manually dismiss messages
- Single-stream M3U8 URLs required filling both XML and M3U fields
- Confusing for users with single streams

### After
- Flash messages persist for 8 seconds
- Users can manually close messages
- Clear mode selection with helpful text
- Single stream mode simplifies the process
- Form validation adapts to mode

---

## Success Criteria Met

✅ Flash messages persist for at least 8 seconds
✅ Manual dismiss button works on all flash messages
✅ Radio buttons toggle between playlist/stream modes
✅ Single stream mode only requires M3U8 URL
✅ Standard mode requires both XML and M3U URLs
✅ Form validation adapts to selected mode
✅ Existing tuners continue to work without changes
✅ Clear help text explains the difference between modes
✅ Zero security vulnerabilities
✅ All tests passing
✅ Proper accessibility implementation

---

## Future Enhancements (Out of Scope)

- Toast notifications for less intrusive messages
- Animation library for more sophisticated transitions
- Persistent storage of user's preferred mode
- Bulk import of M3U8 URLs
- Preview/validation of streams before adding

---

## Technical Notes

### Flash Message Categories
The implementation supports the following flash message categories:
- `success` - Green (operations completed successfully)
- `warning` - Yellow (warnings, validation issues)
- `error` or `danger` - Red (errors, failures)
- `info` - Blue (informational messages)
- Default (no category) - Green (treated as success)

### Single-Stream Mode Implementation
The single-stream mode works by using the same M3U8 URL for both the XML and M3U database fields. This approach:
- Requires no database schema changes
- Maintains backward compatibility
- Leverages existing parsing logic
- Allows the M3U parser to handle single-stream URLs (already supported)

### Browser Compatibility
- Modern browsers (Chrome, Firefox, Safari, Edge)
- CSS transitions and animations
- ES6 JavaScript features (arrow functions, const/let)
- Sticky positioning (widely supported)

---

## Conclusion

This implementation successfully delivers both requested features with:
- Clean, maintainable code
- Comprehensive testing
- Zero security issues
- Full backward compatibility
- Improved accessibility
- Better user experience

The changes are minimal, focused, and well-tested, meeting all success criteria outlined in the original requirements.
41 changes: 31 additions & 10 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -865,20 +865,41 @@ def change_tuner():

elif action == "add_tuner":
name = request.form["tuner_name"].strip()
xml_url = request.form["xml_url"].strip()
m3u_url = request.form["m3u_url"].strip()

tuner_mode = request.form.get("tuner_mode", "standard")

if not name:
flash("Tuner name cannot be empty.", "warning")
else:
try:
add_tuner(name, xml_url, m3u_url)
log_event(current_user.username, f"Added tuner {name}")
flash(f"Tuner {name} added successfully.")

# Validate URLs (DNS/reachability check) after successful add
validate_tuner_url(xml_url, label=f"{name} XML")
validate_tuner_url(m3u_url, label=f"{name} M3U")
if tuner_mode == "single_stream":
# Single stream mode: use M3U8 stream URL for both XML and M3U
m3u8_stream_url = request.form.get("m3u8_stream_url", "").strip()
if not m3u8_stream_url:
flash("M3U8 Stream URL is required for single stream mode.", "warning")
else:
# For single stream mode, we use the same URL for both fields
# This allows the existing infrastructure to work without major changes
add_tuner(name, m3u8_stream_url, m3u8_stream_url)
log_event(current_user.username, f"Added single-stream tuner {name}")
flash(f"Single-stream tuner {name} added successfully.", "success")

# Validate URL (DNS/reachability check) after successful add
validate_tuner_url(m3u8_stream_url, label=f"{name} M3U8")
else:
# Standard mode: require both XML and M3U URLs
xml_url = request.form.get("xml_url", "").strip()
m3u_url = request.form.get("m3u_url", "").strip()

if not xml_url or not m3u_url:
flash("Both XML URL and M3U URL are required for standard mode.", "warning")
else:
add_tuner(name, xml_url, m3u_url)
log_event(current_user.username, f"Added tuner {name}")
flash(f"Tuner {name} added successfully.", "success")

# Validate URLs (DNS/reachability check) after successful add
validate_tuner_url(xml_url, label=f"{name} XML")
validate_tuner_url(m3u_url, label=f"{name} M3U")
except ValueError as e:
flash(f"Failed to add tuner: {str(e)}", "warning")
logging.warning(f"Failed to add tuner {name}: {e}")
Expand Down
Loading