feat: Implement husband features and fix iOS Safari web startup
Implement initial features for husband's companion app, including mock data service and husband notes screen. Refactor scripture and cycle services for improved stability and testability. Address iOS Safari web app startup issue by removing deprecated initialization. - Implemented MockDataService and HusbandNotesScreen. - Converted _DashboardTab and DevotionalScreen to StatefulWidgets for robust scripture provider initialization. - Refactored CycleService to use immutable CycleInfo class, reducing UI rebuilds. - Removed deprecated window.flutterConfiguration from index.html, resolving Flutter web app startup failure on iOS Safari. - Updated and fixed related tests.
This commit is contained in:
156
lib/screens/husband/learn_article_screen.dart
Normal file
156
lib/screens/husband/learn_article_screen.dart
Normal file
@@ -0,0 +1,156 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../data/learn_content.dart';
|
||||
import '../../theme/app_theme.dart';
|
||||
|
||||
/// Screen to display full learn article content
|
||||
class LearnArticleScreen extends StatelessWidget {
|
||||
final String articleId;
|
||||
|
||||
const LearnArticleScreen({super.key, required this.articleId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final article = LearnContent.getArticle(articleId);
|
||||
|
||||
if (article == null) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Article Not Found')),
|
||||
body: const Center(child: Text('Article not found')),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.warmCream,
|
||||
appBar: AppBar(
|
||||
backgroundColor: AppColors.warmCream,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: AppColors.navyBlue),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
title: Text(
|
||||
article.category,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Title
|
||||
Text(
|
||||
article.title,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.navyBlue,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
article.subtitle,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 15,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Divider
|
||||
Container(
|
||||
height: 3,
|
||||
width: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.gold,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Sections
|
||||
...article.sections.map((section) => _buildSection(section)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSection(LearnSection section) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (section.heading != null) ...[
|
||||
Text(
|
||||
section.heading!,
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.navyBlue,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
_buildRichText(section.content),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRichText(String content) {
|
||||
// Handle basic markdown-like formatting
|
||||
final List<InlineSpan> spans = [];
|
||||
final RegExp boldPattern = RegExp(r'\*\*(.*?)\*\*');
|
||||
|
||||
int currentIndex = 0;
|
||||
for (final match in boldPattern.allMatches(content)) {
|
||||
// Add text before the match
|
||||
if (match.start > currentIndex) {
|
||||
spans.add(TextSpan(
|
||||
text: content.substring(currentIndex, match.start),
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 15,
|
||||
color: AppColors.charcoal,
|
||||
height: 1.7,
|
||||
),
|
||||
));
|
||||
}
|
||||
// Add bold text
|
||||
spans.add(TextSpan(
|
||||
text: match.group(1),
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.navyBlue,
|
||||
height: 1.7,
|
||||
),
|
||||
));
|
||||
currentIndex = match.end;
|
||||
}
|
||||
|
||||
// Add remaining text
|
||||
if (currentIndex < content.length) {
|
||||
spans.add(TextSpan(
|
||||
text: content.substring(currentIndex),
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 15,
|
||||
color: AppColors.charcoal,
|
||||
height: 1.7,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
return RichText(
|
||||
text: TextSpan(children: spans),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user