423 lines
18 KiB
Dart
Executable File
423 lines
18 KiB
Dart
Executable File
// ignore_for_file: dead_code
|
|
import 'dart:math';
|
|
|
|
import 'package:filcnaplo/api/providers/live_card_provider.dart';
|
|
import 'package:filcnaplo/ui/date_widget.dart';
|
|
import 'package:filcnaplo_premium/providers/premium_provider.dart';
|
|
import 'package:animated_list_plus/animated_list_plus.dart';
|
|
import 'package:filcnaplo/api/providers/update_provider.dart';
|
|
import 'package:filcnaplo/api/providers/sync.dart';
|
|
import 'package:confetti/confetti.dart';
|
|
import 'package:filcnaplo/models/settings.dart';
|
|
import 'package:filcnaplo_kreta_api/providers/absence_provider.dart';
|
|
import 'package:filcnaplo_kreta_api/providers/event_provider.dart';
|
|
import 'package:filcnaplo_kreta_api/providers/exam_provider.dart';
|
|
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
|
|
import 'package:filcnaplo_kreta_api/providers/homework_provider.dart';
|
|
import 'package:filcnaplo_kreta_api/providers/message_provider.dart';
|
|
import 'package:filcnaplo_kreta_api/providers/note_provider.dart';
|
|
import 'package:filcnaplo/api/providers/user_provider.dart';
|
|
import 'package:filcnaplo/api/providers/status_provider.dart';
|
|
import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
|
|
import 'package:filcnaplo_mobile_ui/common/empty.dart';
|
|
import 'package:filcnaplo_mobile_ui/common/filter_bar.dart';
|
|
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_button.dart';
|
|
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
|
|
import 'package:filcnaplo_mobile_ui/pages/home/live_card/live_card.dart';
|
|
import 'package:filcnaplo_mobile_ui/screens/navigation/navigation_screen.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'home_page.i18n.dart';
|
|
import 'package:filcnaplo/ui/filter/widgets.dart';
|
|
import 'package:filcnaplo/ui/filter/sort.dart';
|
|
import 'package:i18n_extension/i18n_extension.dart';
|
|
|
|
class HomePage extends StatefulWidget {
|
|
const HomePage({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
_HomePageState createState() => _HomePageState();
|
|
}
|
|
|
|
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|
late TabController _tabController;
|
|
late UserProvider user;
|
|
late SettingsProvider settings;
|
|
late UpdateProvider updateProvider;
|
|
late StatusProvider statusProvider;
|
|
late GradeProvider gradeProvider;
|
|
late TimetableProvider timetableProvider;
|
|
late MessageProvider messageProvider;
|
|
late AbsenceProvider absenceProvider;
|
|
late HomeworkProvider homeworkProvider;
|
|
late ExamProvider examProvider;
|
|
late NoteProvider noteProvider;
|
|
late EventProvider eventProvider;
|
|
|
|
late PageController _pageController;
|
|
ConfettiController? _confettiController;
|
|
late LiveCardProvider _liveCard;
|
|
late AnimationController _liveCardAnimation;
|
|
|
|
late String greeting;
|
|
late String firstName;
|
|
|
|
late List<String> listOrder;
|
|
static const pageCount = 4;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
_tabController = TabController(length: pageCount, vsync: this);
|
|
_pageController = PageController();
|
|
user = Provider.of<UserProvider>(context, listen: false);
|
|
_liveCard = Provider.of<LiveCardProvider>(context, listen: false);
|
|
_liveCardAnimation = AnimationController(
|
|
vsync: this, duration: const Duration(milliseconds: 500));
|
|
|
|
_liveCardAnimation.animateTo(_liveCard.show ? 1.0 : 0.0,
|
|
duration: Duration.zero);
|
|
|
|
listOrder = List.generate(pageCount, (index) => "$index");
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
// _filterController.dispose();
|
|
_pageController.dispose();
|
|
_tabController.dispose();
|
|
_confettiController?.dispose();
|
|
_liveCardAnimation.dispose();
|
|
|
|
super.dispose();
|
|
}
|
|
|
|
void setGreeting() {
|
|
DateTime now = DateTime.now();
|
|
List<String> nameParts = user.displayName?.split(" ") ?? ["?"];
|
|
if (!settings.presentationMode) {
|
|
firstName = nameParts.length > 1 ? nameParts[1] : nameParts[0];
|
|
} else {
|
|
firstName = "János";
|
|
}
|
|
|
|
bool customWelcome = false;
|
|
|
|
if (now.isBefore(DateTime(now.year, DateTime.august, 31)) &&
|
|
now.isAfter(DateTime(now.year, DateTime.june, 14))) {
|
|
greeting = "goodrest";
|
|
|
|
if (NavigationScreen.of(context)?.init("confetti") ?? false) {
|
|
_confettiController =
|
|
ConfettiController(duration: const Duration(seconds: 1));
|
|
Future.delayed(const Duration(seconds: 1))
|
|
.then((value) => mounted ? _confettiController?.play() : null);
|
|
}
|
|
} else if (now.month == user.student?.birth.month &&
|
|
now.day == user.student?.birth.day) {
|
|
greeting = "happybirthday";
|
|
|
|
if (NavigationScreen.of(context)?.init("confetti") ?? false) {
|
|
_confettiController =
|
|
ConfettiController(duration: const Duration(seconds: 3));
|
|
Future.delayed(const Duration(seconds: 1))
|
|
.then((value) => mounted ? _confettiController?.play() : null);
|
|
}
|
|
} else if (now.isAfter(DateTime(now.year, DateTime.may, 28)) &&
|
|
now.isBefore(DateTime(now.year, DateTime.may, 30))) {
|
|
greeting = "refilcopen";
|
|
|
|
if (NavigationScreen.of(context)?.init("confetti") ?? false) {
|
|
_confettiController =
|
|
ConfettiController(duration: const Duration(seconds: 3));
|
|
Future.delayed(const Duration(seconds: 1))
|
|
.then((value) => mounted ? _confettiController?.play() : null);
|
|
}
|
|
} else if (now.month == DateTime.december &&
|
|
now.day >= 24 &&
|
|
now.day <= 26) {
|
|
greeting = "merryxmas";
|
|
} else if (now.month == DateTime.january && now.day == 1) {
|
|
greeting = "happynewyear";
|
|
} else if (settings.welcomeMessage.replaceAll(' ', '') != '') {
|
|
greeting = settings.welcomeMessage;
|
|
greeting = localizeFill(
|
|
settings.welcomeMessage,
|
|
[firstName],
|
|
);
|
|
|
|
customWelcome = true;
|
|
} else if (now.hour >= 18) {
|
|
greeting = "goodevening";
|
|
} else if (now.hour >= 10) {
|
|
greeting = "goodafternoon";
|
|
} else if (now.hour >= 4) {
|
|
greeting = "goodmorning";
|
|
} else {
|
|
greeting = "goodevening";
|
|
}
|
|
|
|
greeting = customWelcome ? greeting : greeting.i18n.fill([firstName]);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
user = Provider.of<UserProvider>(context);
|
|
settings = Provider.of<SettingsProvider>(context);
|
|
statusProvider = Provider.of<StatusProvider>(context, listen: false);
|
|
updateProvider = Provider.of<UpdateProvider>(context);
|
|
_liveCard = Provider.of<LiveCardProvider>(context);
|
|
gradeProvider = Provider.of<GradeProvider>(context);
|
|
context.watch<PremiumProvider>();
|
|
|
|
_liveCardAnimation.animateTo(_liveCard.show ? 1.0 : 0.0);
|
|
|
|
setGreeting();
|
|
|
|
return Scaffold(
|
|
body: Stack(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 12.0),
|
|
child: NestedScrollView(
|
|
physics: const BouncingScrollPhysics(
|
|
parent: AlwaysScrollableScrollPhysics()),
|
|
headerSliverBuilder: (context, _) => [
|
|
AnimatedBuilder(
|
|
animation: _liveCardAnimation,
|
|
builder: (context, child) {
|
|
return SliverAppBar(
|
|
automaticallyImplyLeading: false,
|
|
surfaceTintColor:
|
|
Theme.of(context).scaffoldBackgroundColor,
|
|
centerTitle: false,
|
|
titleSpacing: 0.0,
|
|
// Welcome text
|
|
title: Padding(
|
|
padding: const EdgeInsets.only(left: 24.0),
|
|
child: Text(
|
|
greeting,
|
|
overflow: TextOverflow.fade,
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 18.0,
|
|
color: Theme.of(context)
|
|
.textTheme
|
|
.bodyMedium
|
|
?.color,
|
|
),
|
|
),
|
|
),
|
|
actions: [
|
|
// Profile Icon
|
|
Padding(
|
|
padding: const EdgeInsets.only(right: 24.0),
|
|
child: ProfileButton(
|
|
child: ProfileImage(
|
|
heroTag: "profile",
|
|
name: firstName,
|
|
backgroundColor: Theme.of(context)
|
|
.colorScheme
|
|
.primary, //!settings.presentationMode
|
|
//? ColorUtils.stringToColor(user.displayName ?? "?")
|
|
//: Theme.of(context).colorScheme.secondary,
|
|
badge: updateProvider.available,
|
|
role: user.role,
|
|
profilePictureString: user.picture,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
|
|
expandedHeight: _liveCardAnimation.value * 234.0,
|
|
|
|
// Live Card
|
|
flexibleSpace: FlexibleSpaceBar(
|
|
background: Padding(
|
|
padding: EdgeInsets.only(
|
|
left: 24.0,
|
|
right: 24.0,
|
|
top:
|
|
58.0 + MediaQuery.of(context).padding.top,
|
|
bottom: 52.0,
|
|
),
|
|
child: Transform.scale(
|
|
scale: _liveCardAnimation.value,
|
|
child: Opacity(
|
|
opacity: _liveCardAnimation.value,
|
|
child: const LiveCard(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
shadowColor: Colors.black,
|
|
|
|
// Filter Bar
|
|
bottom: FilterBar(
|
|
items: [
|
|
Tab(text: "All".i18n),
|
|
Tab(text: "Grades".i18n),
|
|
Tab(text: "Messages".i18n),
|
|
Tab(text: "Absences".i18n),
|
|
],
|
|
controller: _tabController,
|
|
disableFading: true,
|
|
onTap: (i) async {
|
|
int selectedPage =
|
|
_pageController.page!.round();
|
|
|
|
if (i == selectedPage) return;
|
|
if (_pageController.page?.roundToDouble() !=
|
|
_pageController.page) {
|
|
_pageController.animateToPage(i,
|
|
curve: Curves.easeIn,
|
|
duration: kTabScrollDuration);
|
|
return;
|
|
}
|
|
|
|
// swap current page with target page
|
|
setState(() {
|
|
_pageController.jumpToPage(i);
|
|
String currentList = listOrder[selectedPage];
|
|
listOrder[selectedPage] = listOrder[i];
|
|
listOrder[i] = currentList;
|
|
});
|
|
},
|
|
),
|
|
pinned: true,
|
|
floating: false,
|
|
snap: false,
|
|
);
|
|
},
|
|
),
|
|
],
|
|
body: Padding(
|
|
padding: const EdgeInsets.only(top: 12.0),
|
|
child: NotificationListener<ScrollNotification>(
|
|
onNotification: (notification) {
|
|
// from flutter source
|
|
if (notification is ScrollUpdateNotification &&
|
|
!_tabController.indexIsChanging) {
|
|
if ((_pageController.page! - _tabController.index)
|
|
.abs() >
|
|
1.0) {
|
|
_tabController.index = _pageController.page!.floor();
|
|
}
|
|
_tabController.offset =
|
|
(_pageController.page! - _tabController.index)
|
|
.clamp(-1.0, 1.0);
|
|
} else if (notification is ScrollEndNotification) {
|
|
_tabController.index = _pageController.page!.round();
|
|
if (!_tabController.indexIsChanging) {
|
|
_tabController.offset =
|
|
(_pageController.page! - _tabController.index)
|
|
.clamp(-1.0, 1.0);
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
child: PageView.custom(
|
|
controller: _pageController,
|
|
childrenDelegate: SliverChildBuilderDelegate(
|
|
(BuildContext context, int index) {
|
|
return FutureBuilder<List<DateWidget>>(
|
|
key: ValueKey<String>(listOrder[index]),
|
|
future: getFilterWidgets(homeFilters[index],
|
|
context: context),
|
|
builder: (context, dateWidgets) => dateWidgets
|
|
.data !=
|
|
null
|
|
? RefreshIndicator(
|
|
color:
|
|
Theme.of(context).colorScheme.secondary,
|
|
onRefresh: () => syncAll(context),
|
|
child: ImplicitlyAnimatedList<Widget>(
|
|
items: [
|
|
if (index == 0)
|
|
const SizedBox(key: Key("\$premium")),
|
|
...sortDateWidgets(context,
|
|
dateWidgets: dateWidgets.data!),
|
|
],
|
|
itemBuilder: filterItemBuilder,
|
|
spawnIsolate: false,
|
|
areItemsTheSame: (a, b) => a.key == b.key,
|
|
physics: const BouncingScrollPhysics(
|
|
parent:
|
|
AlwaysScrollableScrollPhysics()),
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 24.0),
|
|
))
|
|
: Container(),
|
|
);
|
|
},
|
|
childCount: 4,
|
|
findChildIndexCallback: (Key key) {
|
|
final ValueKey<String> valueKey =
|
|
key as ValueKey<String>;
|
|
final String data = valueKey.value;
|
|
return listOrder.indexOf(data);
|
|
},
|
|
),
|
|
physics: const PageScrollPhysics()
|
|
.applyTo(const BouncingScrollPhysics()),
|
|
),
|
|
),
|
|
)),
|
|
),
|
|
|
|
// confetti 🎊
|
|
if (_confettiController != null)
|
|
Align(
|
|
alignment: Alignment.topCenter,
|
|
child: ConfettiWidget(
|
|
confettiController: _confettiController!,
|
|
blastDirectionality: BlastDirectionality.explosive,
|
|
emissionFrequency: 0.02,
|
|
numberOfParticles: 120,
|
|
maxBlastForce: 20,
|
|
minBlastForce: 10,
|
|
gravity: 0.3,
|
|
minimumSize: const Size(5, 5),
|
|
maximumSize: const Size(20, 20),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<Widget> filterViewBuilder(context, int activeData) async {
|
|
final activeFilter = homeFilters[activeData];
|
|
|
|
List<Widget> filterWidgets = sortDateWidgets(
|
|
context,
|
|
dateWidgets: await getFilterWidgets(activeFilter, context: context),
|
|
showDivider: true,
|
|
);
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.only(top: 12.0),
|
|
child: RefreshIndicator(
|
|
color: Theme.of(context).colorScheme.secondary,
|
|
onRefresh: () => syncAll(context),
|
|
child: ListView.builder(
|
|
padding: EdgeInsets.zero,
|
|
physics: const BouncingScrollPhysics(),
|
|
itemBuilder: (context, index) {
|
|
if (filterWidgets.isNotEmpty) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
|
child: filterWidgets[index],
|
|
);
|
|
} else {
|
|
return Empty(subtitle: "empty".i18n);
|
|
}
|
|
},
|
|
itemCount: max(filterWidgets.length, 1),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|