firka_wear: extract home body to part, add vertical scrollable PageView with placeholders

This commit is contained in:
2026-03-02 18:29:16 +01:00
parent 636c2ea68d
commit d0c3938510
2 changed files with 170 additions and 98 deletions

View File

@@ -20,6 +20,8 @@ import 'package:firka_wear/ui/theme/style.dart';
import 'package:firka_wear/ui/shared/class_icon.dart'; import 'package:firka_wear/ui/shared/class_icon.dart';
import 'package:firka_wear/ui/wear/widgets/circular_progress_indicator.dart'; import 'package:firka_wear/ui/wear/widgets/circular_progress_indicator.dart';
part 'home_screen_body.dart';
class WearHomeScreen extends StatefulWidget { class WearHomeScreen extends StatefulWidget {
final WearAppInitialization data; final WearAppInitialization data;
@@ -43,6 +45,7 @@ class _WearHomeScreenState extends State<WearHomeScreen> {
final watch = WatchConnectivity(); final watch = WatchConnectivity();
StreamSubscription? _messageSub; StreamSubscription? _messageSub;
WearSyncCubit? _syncCubit; WearSyncCubit? _syncCubit;
late final PageController _pageController;
bool disposed = false; bool disposed = false;
@@ -55,6 +58,7 @@ class _WearHomeScreenState extends State<WearHomeScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_pageController = PageController(initialPage: 2);
now = timeNow(); now = timeNow();
today = data.syncStore.getLessonsForDate(now); today = data.syncStore.getLessonsForDate(now);
init = data.syncStore.timetable.isNotEmpty; init = data.syncStore.timetable.isNotEmpty;
@@ -123,12 +127,13 @@ class _WearHomeScreenState extends State<WearHomeScreen> {
}); });
} }
(List<Widget>, double) buildBody(BuildContext context, WearMode mode) { (List<Widget>, double, double?) buildBody(
ScreenUtil.init(context); BuildContext context,
WearMode mode,
) {
var body = List<Widget>.empty(growable: true); var body = List<Widget>.empty(growable: true);
if (!init) { if (!init) {
return (body, 255.h); return (body, 255.h, null);
} }
if (today.isEmpty && if (today.isEmpty &&
@@ -143,7 +148,7 @@ class _WearHomeScreenState extends State<WearHomeScreen> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
); );
return (body, 255.h); return (body, 255.h, null);
} }
if (today.isEmpty) { if (today.isEmpty) {
body.add( body.add(
@@ -157,7 +162,7 @@ class _WearHomeScreenState extends State<WearHomeScreen> {
); );
platform.invokeMethod('activity_cancel'); platform.invokeMethod('activity_cancel');
return (body, 255.h); return (body, 255.h, null);
} }
if (now.isAfter(today.last.end)) { if (now.isAfter(today.last.end)) {
body.add( body.add(
@@ -171,7 +176,7 @@ class _WearHomeScreenState extends State<WearHomeScreen> {
); );
platform.invokeMethod('activity_cancel'); platform.invokeMethod('activity_cancel');
return (body, 300.h); return (body, 300.h, null);
} }
if (now.isBefore(today.first.start)) { if (now.isBefore(today.first.start)) {
var untilFirst = today.first.start.difference(now); var untilFirst = today.first.start.difference(now);
@@ -187,7 +192,7 @@ class _WearHomeScreenState extends State<WearHomeScreen> {
); );
platform.invokeMethod('activity_update'); platform.invokeMethod('activity_update');
return (body, 255.h); return (body, 255.h, null);
} }
currentLessonNo = null; currentLessonNo = null;
if (now.isAfter(today.first.start) && now.isBefore(today.last.end)) { if (now.isAfter(today.first.start) && now.isBefore(today.last.end)) {
@@ -216,57 +221,48 @@ class _WearHomeScreenState extends State<WearHomeScreen> {
} }
var minutes = currentBreakProgress.inMinutes + 1; var minutes = currentBreakProgress.inMinutes + 1;
final progress =
currentBreakProgress.inMilliseconds / currentBreak.inMilliseconds;
body.add( body.add(
CustomPaint( Column(
painter: CircularProgressPainter( mainAxisAlignment: MainAxisAlignment.start,
progress: children: [
currentBreakProgress.inMilliseconds / Center(
currentBreak.inMilliseconds, child: Text(
// progress: 5 / 10, AppLocalizations.of(context)!.breakTxt,
screenSize: MediaQuery.of(context).size, style: TextStyle(
strokeWidth: 4, color: wearStyle.colors.textPrimary,
color: wearStyle.colors.accent, fontSize: 14,
), fontFamily: 'Montserrat',
child: Column( fontVariations: [FontVariation('wght', 600)],
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(height: 55.h),
Center(
child: Text(
AppLocalizations.of(context)!.breakTxt,
style: TextStyle(
color: wearStyle.colors.textPrimary,
fontSize: 14,
fontFamily: 'Montserrat',
fontVariations: [FontVariation('wght', 600)],
),
), ),
), ),
Center( ),
child: Text( Center(
AppLocalizations.of(context)!.timeLeft(minutes), child: Text(
style: TextStyle( AppLocalizations.of(context)!.timeLeft(minutes),
color: wearStyle.colors.textPrimary, style: TextStyle(
fontSize: 12, color: wearStyle.colors.textPrimary,
fontFamily: 'Montserrat', fontSize: 12,
fontVariations: [FontVariation('wght', 400)], fontFamily: 'Montserrat',
), fontVariations: [FontVariation('wght', 400)],
), ),
), ),
], ),
), ],
), ),
); );
platform.invokeMethod('activity_update'); platform.invokeMethod('activity_update');
return (body, 200.h); return (body, 20.h, progress);
} else { } else {
var duration = currentLesson.start.difference(currentLesson.end); var duration = currentLesson.start.difference(currentLesson.end);
var elapsed = currentLesson.start.difference(now); var elapsed = currentLesson.start.difference(now);
var timeLeft = currentLesson.end.difference(now); var timeLeft = currentLesson.end.difference(now);
var minutes = timeLeft.inMinutes + 1; var minutes = timeLeft.inMinutes + 1;
final progress = elapsed.inMilliseconds / duration.inMilliseconds;
Widget nextLessonWidget = SizedBox(); Widget nextLessonWidget = SizedBox();
@@ -297,57 +293,48 @@ class _WearHomeScreenState extends State<WearHomeScreen> {
} }
body.add( body.add(
CustomPaint( Column(
painter: CircularProgressPainter( children: [
progress: elapsed.inMilliseconds / duration.inMilliseconds, Center(
screenSize: MediaQuery.of(context).size, child: ClassIconWidget(
strokeWidth: 4, color: wearStyle.colors.accent,
color: wearStyle.colors.accent, size: 16,
), uid: currentLesson.uid,
child: Column( className: currentLesson.name,
children: [ category: currentLesson.subject?.name ?? '',
SizedBox(height: nextLesson == null ? 20.h : 0), ).build(context),
Center( ),
child: ClassIconWidget( const SizedBox(height: 4),
color: wearStyle.colors.accent, Center(
size: 16, child: Text(
uid: currentLesson.uid, currentLessonText,
className: currentLesson.name, style: TextStyle(
category: currentLesson.subject?.name ?? '', color: wearStyle.colors.textPrimary,
).build(context), fontSize: 14,
), fontFamily: 'Montserrat',
const SizedBox(height: 4), fontVariations: [FontVariation('wght', 600)],
Center(
child: Text(
currentLessonText,
style: TextStyle(
color: wearStyle.colors.textPrimary,
fontSize: 14,
fontFamily: 'Montserrat',
fontVariations: [FontVariation('wght', 600)],
),
), ),
), ),
Center( ),
child: Text( Center(
AppLocalizations.of(context)!.timeLeft(minutes), child: Text(
style: TextStyle( AppLocalizations.of(context)!.timeLeft(minutes),
color: wearStyle.colors.textPrimary, style: TextStyle(
fontSize: 12, color: wearStyle.colors.textPrimary,
fontFamily: 'Montserrat', fontSize: 12,
fontVariations: [FontVariation('wght', 400)], fontFamily: 'Montserrat',
), fontVariations: [FontVariation('wght', 400)],
), ),
), ),
const SizedBox(height: 8), ),
nextLessonWidget, const SizedBox(height: 8),
], nextLessonWidget,
), ],
), ),
); );
platform.invokeMethod('activity_update'); platform.invokeMethod('activity_update');
return (body, 200.h); return (body, 0.h, progress);
} }
} }
@@ -357,6 +344,7 @@ class _WearHomeScreenState extends State<WearHomeScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
ScreenUtil.init(context);
Widget titleBar = SizedBox(); Widget titleBar = SizedBox();
if (currentLessonNo != null) { if (currentLessonNo != null) {
@@ -377,6 +365,9 @@ class _WearHomeScreenState extends State<WearHomeScreen> {
return BlocBuilder<WearSyncCubit, WearSyncState>( return BlocBuilder<WearSyncCubit, WearSyncState>(
builder: (context, syncState) { builder: (context, syncState) {
var (body, padding, progress) = buildBody(context, mode);
final viewportHeight = MediaQuery.of(context).size.height;
return Scaffold( return Scaffold(
backgroundColor: mode == WearMode.active backgroundColor: mode == WearMode.active
? wearStyle.colors.background ? wearStyle.colors.background
@@ -384,6 +375,18 @@ class _WearHomeScreenState extends State<WearHomeScreen> {
body: Stack( body: Stack(
children: [ children: [
Center(child: titleBar), Center(child: titleBar),
Transform.translate(
offset: Offset(0, 200.h),
child: CustomPaint(
painter: CircularProgressPainter(
progress: progress ?? 0.0,
screenSize: MediaQuery.of(context).size,
strokeWidth: 4,
color: wearStyle.colors.accent,
),
child: SizedBox.expand(),
),
),
Center( Center(
child: Column( child: Column(
children: [ children: [
@@ -404,19 +407,35 @@ class _WearHomeScreenState extends State<WearHomeScreen> {
}); });
} }
var (body, padding) = buildBody(context, mode); return SizedBox(
height: viewportHeight,
return Column( child: PageView(
mainAxisAlignment: MainAxisAlignment.center, controller: _pageController,
children: <Widget>[ scrollDirection: Axis.vertical,
Container( children: [
padding: EdgeInsets.only(top: padding), _PlaceholderPage(
child: Column( index: 1,
mainAxisAlignment: MainAxisAlignment.center, viewportHeight: viewportHeight,
children: [...body],
), ),
), _PlaceholderPage(
], index: 2,
viewportHeight: viewportHeight,
),
_HomeScreenBodyPage(
body: body,
padding: padding,
viewportHeight: viewportHeight,
),
_PlaceholderPage(
index: 3,
viewportHeight: viewportHeight,
),
_PlaceholderPage(
index: 4,
viewportHeight: viewportHeight,
),
],
),
); );
}, },
), ),
@@ -460,6 +479,7 @@ class _WearHomeScreenState extends State<WearHomeScreen> {
void dispose() { void dispose() {
_messageSub?.cancel(); _messageSub?.cancel();
timer?.cancel(); timer?.cancel();
_pageController.dispose();
disposed = true; disposed = true;
super.dispose(); super.dispose();
} }

View File

@@ -0,0 +1,52 @@
part of 'home_screen.dart';
class _HomeScreenBodyPage extends StatelessWidget {
final List<Widget> body;
final double padding;
final double viewportHeight;
const _HomeScreenBodyPage({
required this.body,
required this.padding,
required this.viewportHeight,
});
@override
Widget build(BuildContext context) {
return SizedBox(
height: viewportHeight,
child: Container(
padding: EdgeInsets.only(top: padding),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [...body],
),
),
);
}
}
class _PlaceholderPage extends StatelessWidget {
final int index;
final double viewportHeight;
const _PlaceholderPage({required this.index, required this.viewportHeight});
@override
Widget build(BuildContext context) {
return SizedBox(
height: viewportHeight,
child: Center(
child: Text(
'Placeholder $index',
style: TextStyle(
color: wearStyle.colors.textPrimary,
fontSize: 14,
fontFamily: 'Montserrat',
fontVariations: [FontVariation('wght', 400)],
),
),
),
);
}
}