wearos home screen
This commit is contained in:
parent
442ba79445
commit
47ba1a1bba
@ -1,33 +1,223 @@
|
|||||||
// ignore_for_file: avoid_print
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:firka/helpers/api/model/timetable.dart';
|
||||||
import 'package:firka/wear_main.dart';
|
import 'package:firka/wear_main.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class WearHomeScreen extends StatelessWidget {
|
import '../../../ui/colors.dart';
|
||||||
|
import '../../../ui/widgets/circular_progress_indicator.dart';
|
||||||
|
|
||||||
|
class WearHomeScreen extends StatefulWidget {
|
||||||
final WearAppInitialization data;
|
final WearAppInitialization data;
|
||||||
const WearHomeScreen(this.data, {super.key});
|
const WearHomeScreen(this.data, {super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<WearHomeScreen> createState() => _WearHomeScreenState(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WearHomeScreenState extends State<WearHomeScreen> {
|
||||||
|
final WearAppInitialization data;
|
||||||
|
_WearHomeScreenState(this.data);
|
||||||
|
|
||||||
|
List<Lesson> today = List.empty(growable: true);
|
||||||
|
String apiError = "";
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
Timer? timer;
|
||||||
|
bool init = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
now = DateTime.now();
|
||||||
|
|
||||||
|
timer = Timer.periodic(Duration(seconds: 5), (timer) {
|
||||||
|
setState(() {
|
||||||
|
now = DateTime.now();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
initStateAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initStateAsync() async {
|
||||||
|
var kreta = data.client;
|
||||||
|
|
||||||
|
now = DateTime.now();
|
||||||
|
var todayStart = now.subtract(Duration(hours: now.hour, minutes: now.minute
|
||||||
|
, seconds: now.second));
|
||||||
|
var todayEnd = todayStart.add(Duration(hours: 23, minutes: 59));
|
||||||
|
var classes = await kreta.getTimeTable(todayStart, todayEnd);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
if (classes.response != null) today = classes.response!;
|
||||||
|
if (classes.statusCode != 200) {
|
||||||
|
apiError = "Unexpected status : ${classes.statusCode}";
|
||||||
|
}
|
||||||
|
if (classes.err != null) apiError = classes.err!;
|
||||||
|
|
||||||
|
init = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> buildBody(BuildContext context) {
|
||||||
|
var body = List<Widget>.empty(growable: true);
|
||||||
|
if (!init) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (today.isEmpty && apiError != "") {
|
||||||
|
body.add(Text(
|
||||||
|
apiError,
|
||||||
|
style: TextStyle(color: defaultColors.secondaryText, fontSize: 18),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
));
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
if (today.isEmpty) {
|
||||||
|
body.add(Text(
|
||||||
|
"You don't have any classes today",
|
||||||
|
style: TextStyle(color: defaultColors.secondaryText, fontSize: 18),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
));
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
if (now.isAfter(today.last.end)) {
|
||||||
|
body.add(Text(
|
||||||
|
"You don't have any more classes today",
|
||||||
|
style: TextStyle(color: defaultColors.secondaryText, fontSize: 18),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
));
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
if (now.isAfter(today.first.start)
|
||||||
|
&& now.isBefore(today.last.end)) {
|
||||||
|
Lesson? currentLesson;
|
||||||
|
Lesson? lastLesson; // last as in the last lesson that you've been to
|
||||||
|
Lesson? next;
|
||||||
|
Duration? currentBreak;
|
||||||
|
Duration? currentBreakProgress;
|
||||||
|
for (int i = 0; i < today.length; i++) {
|
||||||
|
var lesson = today[i];
|
||||||
|
if (now.isAfter(lesson.start) && now.isBefore(lesson.end)) {
|
||||||
|
currentLesson = lesson;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (now.isAfter(lesson.end)) {
|
||||||
|
if (lastLesson == null) {
|
||||||
|
lastLesson = lesson;
|
||||||
|
if (i < today.length) next = today[i+1];
|
||||||
|
} else {
|
||||||
|
if (lesson.end.isAfter(lastLesson.end)) {
|
||||||
|
lastLesson = lesson;
|
||||||
|
if (i < today.length) next = today[i+1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastLesson != null && next != null) {
|
||||||
|
currentBreak = next.start.difference(lastLesson.end);
|
||||||
|
currentBreakProgress = next.start.difference(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentLesson == null) {
|
||||||
|
if (currentBreak == null) {
|
||||||
|
throw Exception("currentBreak == null");
|
||||||
|
}
|
||||||
|
if (currentBreakProgress == null) {
|
||||||
|
throw Exception("currentBreakProgress == null");
|
||||||
|
}
|
||||||
|
|
||||||
|
body.add(CustomPaint(
|
||||||
|
painter: CircularProgressPainter(
|
||||||
|
progress: currentBreakProgress.inMilliseconds / currentBreak.inMilliseconds,
|
||||||
|
screenSize: MediaQuery.of(context).size,
|
||||||
|
strokeWidth: 4,
|
||||||
|
color: defaultColors.radiusColor
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
"Break",
|
||||||
|
style: TextStyle(color: defaultColors.secondaryText, fontSize: 20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
"${currentBreakProgress.inMinutes} "
|
||||||
|
"min${currentBreakProgress.inMinutes == 1 ? '' : 's'} left",
|
||||||
|
style: TextStyle(color: defaultColors.secondaryText, fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
return body;
|
||||||
|
} else {
|
||||||
|
var duration = currentLesson.start.difference(currentLesson.end);
|
||||||
|
var elapsed = currentLesson.start.difference(now);
|
||||||
|
var timeLeft = currentLesson.end.difference(now);
|
||||||
|
|
||||||
|
body.add(CustomPaint(
|
||||||
|
painter: CircularProgressPainter(
|
||||||
|
progress: elapsed.inMilliseconds / duration.inMilliseconds,
|
||||||
|
screenSize: MediaQuery
|
||||||
|
.of(context)
|
||||||
|
.size,
|
||||||
|
strokeWidth: 4,
|
||||||
|
color: defaultColors.radiusColor
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
currentLesson.name,
|
||||||
|
style: TextStyle(
|
||||||
|
color: defaultColors.secondaryText, fontSize: 20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
"${timeLeft.inMinutes} "
|
||||||
|
"min${timeLeft.inMinutes == 1 ? '' : 's'} left",
|
||||||
|
style: TextStyle(
|
||||||
|
color: defaultColors.secondaryText, fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception("unexpected state");
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var body = buildBody(context);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
backgroundColor: defaultColors.ambientBackgroundColor,
|
||||||
title: const Text('Stub'),
|
|
||||||
centerTitle: true,
|
|
||||||
),
|
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: body,
|
||||||
const Text(
|
|
||||||
'Home',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
timer?.cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
30
firka/lib/ui/colors.dart
Normal file
30
firka/lib/ui/colors.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
class WearColors {
|
||||||
|
|
||||||
|
Color backgroundColor;
|
||||||
|
Color ambientBackgroundColor;
|
||||||
|
Color radiusColor;
|
||||||
|
Color primaryText;
|
||||||
|
Color secondaryText;
|
||||||
|
Color tertiaryText;
|
||||||
|
|
||||||
|
WearColors({
|
||||||
|
required this.backgroundColor,
|
||||||
|
required this.ambientBackgroundColor,
|
||||||
|
required this.radiusColor,
|
||||||
|
required this.primaryText,
|
||||||
|
required this.secondaryText,
|
||||||
|
required this.tertiaryText
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
WearColors defaultColors = WearColors(
|
||||||
|
backgroundColor: Color(0xff0c1201),
|
||||||
|
ambientBackgroundColor: Color(0xff000000),
|
||||||
|
radiusColor: Color(0xffa6dc22),
|
||||||
|
primaryText: Color(0xffcbee71),
|
||||||
|
secondaryText: Color(0xffe9f7cb),
|
||||||
|
tertiaryText: Color(0xffa7b290),
|
||||||
|
);
|
101
firka/lib/ui/widgets/circular_progress_indicator.dart
Normal file
101
firka/lib/ui/widgets/circular_progress_indicator.dart
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CircularProgressIndicator extends StatefulWidget {
|
||||||
|
final double progress;
|
||||||
|
final double strokeWidth;
|
||||||
|
final Color color;
|
||||||
|
final Size screenSize;
|
||||||
|
|
||||||
|
CircularProgressIndicator({
|
||||||
|
required this.progress,
|
||||||
|
required this.screenSize,
|
||||||
|
this.strokeWidth = 8.0,
|
||||||
|
required this.color,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CircularProgressIndicatorState createState() => _CircularProgressIndicatorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CircularProgressIndicatorState extends State<CircularProgressIndicator>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _controller;
|
||||||
|
late Animation<double> _animation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_animation = Tween<double>(begin: 0.0, end: widget.progress).animate(_controller);
|
||||||
|
_controller.forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: _animation,
|
||||||
|
builder: (context, child) {
|
||||||
|
return CustomPaint(
|
||||||
|
painter: CircularProgressPainter(
|
||||||
|
progress: _animation.value,
|
||||||
|
strokeWidth: widget.strokeWidth,
|
||||||
|
color: widget.color,
|
||||||
|
screenSize: widget.screenSize,
|
||||||
|
),
|
||||||
|
child: SizedBox.expand(), // Fill the entire screen
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CircularProgressPainter extends CustomPainter {
|
||||||
|
final double progress;
|
||||||
|
final double strokeWidth;
|
||||||
|
final Color color;
|
||||||
|
final Size screenSize;
|
||||||
|
|
||||||
|
CircularProgressPainter({
|
||||||
|
required this.progress,
|
||||||
|
required this.strokeWidth,
|
||||||
|
required this.color,
|
||||||
|
required this.screenSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final center = Offset(screenSize.width / 2, screenSize.height / 7.4);
|
||||||
|
final radius = min(screenSize.width, screenSize.height) / 2 - strokeWidth / 2;
|
||||||
|
final startAngle = -pi / 2;
|
||||||
|
final sweepAngle = 2 * pi * progress;
|
||||||
|
|
||||||
|
final paint = Paint()
|
||||||
|
..color = color
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = strokeWidth;
|
||||||
|
|
||||||
|
canvas.drawArc(
|
||||||
|
Rect.fromCircle(center: center, radius: radius),
|
||||||
|
startAngle,
|
||||||
|
sweepAngle,
|
||||||
|
false,
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:firka/helpers/db/models/generic_cache_model.dart';
|
||||||
|
import 'package:firka/helpers/db/models/timetable_cache_model.dart';
|
||||||
import 'package:firka/helpers/db/models/token_model.dart';
|
import 'package:firka/helpers/db/models/token_model.dart';
|
||||||
import 'package:firka/pages/error/wear_error_page.dart';
|
|
||||||
import 'package:firka/screens/wear/home/home_screen.dart';
|
import 'package:firka/screens/wear/home/home_screen.dart';
|
||||||
import 'package:firka/screens/wear/login/login_screen.dart';
|
import 'package:firka/screens/wear/login/login_screen.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -10,11 +11,14 @@ import 'package:isar/isar.dart';
|
|||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:wear_plus/wear_plus.dart';
|
import 'package:wear_plus/wear_plus.dart';
|
||||||
|
|
||||||
|
import 'helpers/api/client/kreta_client.dart';
|
||||||
|
|
||||||
late Isar isar;
|
late Isar isar;
|
||||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
class WearAppInitialization {
|
class WearAppInitialization {
|
||||||
final Isar isar;
|
final Isar isar;
|
||||||
|
late KretaClient client;
|
||||||
final int tokenCount;
|
final int tokenCount;
|
||||||
|
|
||||||
WearAppInitialization({
|
WearAppInitialization({
|
||||||
@ -27,7 +31,7 @@ Future<Isar> initDB() async {
|
|||||||
final dir = await getApplicationDocumentsDirectory();
|
final dir = await getApplicationDocumentsDirectory();
|
||||||
|
|
||||||
return Isar.open(
|
return Isar.open(
|
||||||
[TokenModelSchema],
|
[TokenModelSchema, GenericCacheModelSchema, TimetableCacheModelSchema],
|
||||||
inspector: true,
|
inspector: true,
|
||||||
directory: dir.path,
|
directory: dir.path,
|
||||||
);
|
);
|
||||||
@ -41,27 +45,25 @@ Future<WearAppInitialization> initializeApp() async {
|
|||||||
tokenCount: await isar.tokenModels.count()
|
tokenCount: await isar.tokenModels.count()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
resetOldTimeTableCache(isar);
|
||||||
|
|
||||||
|
// TODO: Account selection
|
||||||
|
if (init.tokenCount > 0) {
|
||||||
|
init.client = KretaClient(
|
||||||
|
(await isar.tokenModels.where().findFirst())!,
|
||||||
|
isar
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return init;
|
return init;
|
||||||
}
|
}
|
||||||
|
|
||||||
void wearMain(MethodChannel platform) async {
|
void wearMain(MethodChannel platform) async {
|
||||||
// TODO: fix the error handling currently not pushing to the error page
|
// TODO: fix the error handling currently not pushing to the error page
|
||||||
runZonedGuarded(() async {
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
|
|
||||||
// Run App Initialization
|
// Run App Initialization
|
||||||
runApp(WearInitializationScreen());
|
runApp(WearInitializationScreen());
|
||||||
|
|
||||||
}, (error, stackTrace) {
|
|
||||||
debugPrint('Caught error: $error');
|
|
||||||
debugPrint('Stack trace: $stackTrace');
|
|
||||||
|
|
||||||
navigatorKey.currentState?.push(
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => WearErrorPage(exception: error.toString()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class WearInitializationScreen extends StatelessWidget {
|
class WearInitializationScreen extends StatelessWidget {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user