remelem mukszik

This commit is contained in:
ReinerRego
2023-05-26 21:51:21 +02:00
parent baec76c29f
commit 0ece9382af
170 changed files with 15575 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
import 'package:filcnaplo_mobile_ui/screens/navigation/navbar_item.dart';
import 'package:flutter/material.dart';
class Navbar extends StatelessWidget {
const Navbar({Key? key, required this.selectedIndex, required this.onSelected, required this.items}) : super(key: key);
final int selectedIndex;
final void Function(int index) onSelected;
final List<NavItem> items;
@override
Widget build(BuildContext context) {
final List<Widget> buttons = List.generate(
items.length,
(index) => NavbarItem(
item: items[index],
active: index == selectedIndex,
onTap: () => onSelected(index),
),
);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: buttons,
);
}
}

View File

@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
class NavItem {
final String title;
final Widget icon;
final Widget activeIcon;
const NavItem({required this.title, required this.icon, required this.activeIcon});
}
class NavbarItem extends StatelessWidget {
const NavbarItem({
Key? key,
required this.item,
required this.active,
required this.onTap,
}) : super(key: key);
final NavItem item;
final bool active;
final void Function() onTap;
@override
Widget build(BuildContext context) {
final Widget icon = active ? item.activeIcon : item.icon;
return SafeArea(
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 6.0),
child: Container(
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: active ? Theme.of(context).colorScheme.secondary.withOpacity(.4) : null,
borderRadius: BorderRadius.circular(14.0),
),
child: Stack(
children: [
IconTheme(
data: IconThemeData(
color: Theme.of(context).colorScheme.secondary,
),
child: icon,
),
IconTheme(
data: IconThemeData(
color: Theme.of(context).brightness == Brightness.light ? Colors.black.withOpacity(.5) : Colors.white.withOpacity(.3),
),
child: icon,
),
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,25 @@
class NavigationRoute {
late String _name;
late int _index;
final List<String> _internalPageMap = [
"home",
"grades",
"timetable",
"messages",
"absences",
];
String get name => _name;
int get index => _index;
set name(String n) {
_name = n;
_index = _internalPageMap.indexOf(n);
}
set index(int i) {
_index = i;
_name = _internalPageMap.elementAt(i);
}
}

View File

@@ -0,0 +1,38 @@
import 'package:filcnaplo_mobile_ui/pages/absences/absences_page.dart';
import 'package:filcnaplo_mobile_ui/pages/grades/grades_page.dart';
import 'package:filcnaplo_mobile_ui/pages/home/home_page.dart';
import 'package:filcnaplo_mobile_ui/pages/messages/messages_page.dart';
import 'package:filcnaplo_mobile_ui/pages/timetable/timetable_page.dart';
import 'package:flutter/material.dart';
import 'package:animations/animations.dart';
Route navigationRouteHandler(RouteSettings settings) {
switch (settings.name) {
case "home":
return navigationPageRoute((context) => const HomePage());
case "grades":
return navigationPageRoute((context) => const GradesPage());
case "timetable":
return navigationPageRoute((context) => const TimetablePage());
case "messages":
return navigationPageRoute((context) => const MessagesPage());
case "absences":
return navigationPageRoute((context) => const AbsencesPage());
default:
return navigationPageRoute((context) => const HomePage());
}
}
Route navigationPageRoute(Widget Function(BuildContext) builder) {
return PageRouteBuilder(
pageBuilder: (context, _, __) => builder(context),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeThroughTransition(
fillColor: Theme.of(context).scaffoldBackgroundColor,
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
);
},
);
}

View File

@@ -0,0 +1,302 @@
import 'package:filcnaplo/api/providers/update_provider.dart';
import 'package:filcnaplo/helpers/quick_actions.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/theme/observer.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_mobile_ui/common/system_chrome.dart';
import 'package:filcnaplo_mobile_ui/screens/navigation/nabar.dart';
import 'package:filcnaplo_mobile_ui/screens/navigation/navbar_item.dart';
import 'package:filcnaplo_mobile_ui/screens/navigation/navigation_route.dart';
import 'package:filcnaplo_mobile_ui/screens/navigation/navigation_route_handler.dart';
import 'package:filcnaplo/icons/filc_icons.dart';
import 'package:filcnaplo_mobile_ui/screens/navigation/status_bar.dart';
import 'package:filcnaplo_mobile_ui/screens/news/news_view.dart';
import 'package:filcnaplo_mobile_ui/screens/settings/settings_screen.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
import 'package:filcnaplo_mobile_ui/common/screens.i18n.dart';
import 'package:filcnaplo/api/providers/news_provider.dart';
import 'package:filcnaplo/api/providers/sync.dart';
import 'package:home_widget/home_widget.dart';
import 'package:sliding_sheet/sliding_sheet.dart';
import 'package:background_fetch/background_fetch.dart';
class NavigationScreen extends StatefulWidget {
const NavigationScreen({Key? key}) : super(key: key);
static NavigationScreenState? of(BuildContext context) => context.findAncestorStateOfType<NavigationScreenState>();
@override
NavigationScreenState createState() => NavigationScreenState();
}
class NavigationScreenState extends State<NavigationScreen> with WidgetsBindingObserver {
late NavigationRoute selected;
List<String> initializers = [];
final _navigatorState = GlobalKey<NavigatorState>();
late SettingsProvider settings;
late NewsProvider newsProvider;
late UpdateProvider updateProvider;
NavigatorState? get navigator => _navigatorState.currentState;
void customRoute(Route route) => navigator?.pushReplacement(route);
bool init(String id) {
if (initializers.contains(id)) return false;
initializers.add(id);
return true;
}
void _checkForWidgetLaunch() {
HomeWidget.initiallyLaunchedFromHomeWidget().then(_launchedFromWidget);
}
void _launchedFromWidget(Uri? uri) async {
if (uri == null) return;
if (uri.scheme == "timetable" && uri.authority == "refresh") {
Navigator.of(context).popUntil((route) => route.isFirst);
setPage("timetable");
_navigatorState.currentState?.pushNamedAndRemoveUntil("timetable", (_) => false);
} else if (uri.scheme == "settings" && uri.authority == "premium") {
Navigator.of(context).popUntil((route) => route.isFirst);
showSlidingBottomSheet(
context,
useRootNavigator: true,
builder: (context) => SlidingSheetDialog(
color: Theme.of(context).scaffoldBackgroundColor,
duration: const Duration(milliseconds: 400),
scrollSpec: const ScrollSpec.bouncingScroll(),
snapSpec: const SnapSpec(
snap: true,
snappings: [1.0],
positioning: SnapPositioning.relativeToSheetHeight,
),
cornerRadius: 16,
cornerRadiusOnFullscreen: 0,
builder: (context, state) => Material(
color: Theme.of(context).scaffoldBackgroundColor,
child: const SettingsScreen(),
),
),
);
}
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
// Configure BackgroundFetch.
int status = await BackgroundFetch.configure(
BackgroundFetchConfig(
minimumFetchInterval: 15,
stopOnTerminate: false,
enableHeadless: true,
requiresBatteryNotLow: false,
requiresCharging: false,
requiresStorageNotLow: false,
requiresDeviceIdle: false,
requiredNetworkType: NetworkType.ANY), (String taskId) async {
// <-- Event handler
// This is the fetch-event callback.
print("[BackgroundFetch] Event received $taskId");
// IMPORTANT: You must signal completion of your task or the OS can punish your app
// for taking too long in the background.
BackgroundFetch.finish(taskId);
}, (String taskId) async {
// <-- Task timeout handler.
// This task has exceeded its allowed running-time. You must stop what you're doing and immediately .finish(taskId)
print("[BackgroundFetch] TASK TIMEOUT taskId: $taskId");
BackgroundFetch.finish(taskId);
});
print('[BackgroundFetch] configure success: $status');
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
}
@override
void initState() {
super.initState();
initPlatformState();
HomeWidget.setAppGroupId('hu.filc.naplo.group');
_checkForWidgetLaunch();
HomeWidget.widgetClicked.listen(_launchedFromWidget);
settings = Provider.of<SettingsProvider>(context, listen: false);
selected = NavigationRoute();
selected.index = settings.startPage.index; // set page index to start page
// add brightness observer
WidgetsBinding.instance.addObserver(this);
// set client User-Agent
Provider.of<KretaClient>(context, listen: false).userAgent = settings.config.userAgent;
// Get news
newsProvider = Provider.of<NewsProvider>(context, listen: false);
newsProvider.restore().then((value) => newsProvider.fetch());
// Get releases
updateProvider = Provider.of<UpdateProvider>(context, listen: false);
updateProvider.fetch();
// Initial sync
syncAll(context);
setupQuickActions();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangePlatformBrightness() {
if (settings.theme == ThemeMode.system) {
Brightness? brightness = WidgetsBinding.instance.window.platformBrightness;
Provider.of<ThemeModeObserver>(context, listen: false).changeTheme(brightness == Brightness.light ? ThemeMode.light : ThemeMode.dark);
}
super.didChangePlatformBrightness();
}
void setPage(String page) => setState(() => selected.name = page);
@override
Widget build(BuildContext context) {
setSystemChrome(context);
settings = Provider.of<SettingsProvider>(context);
newsProvider = Provider.of<NewsProvider>(context);
// Show news
WidgetsBinding.instance.addPostFrameCallback((_) {
if (newsProvider.show) {
newsProvider.lock();
NewsView.show(newsProvider.news[newsProvider.state], context: context).then((value) => newsProvider.release());
}
});
handleQuickActions(context, (page) {
setPage(page);
_navigatorState.currentState?.pushReplacementNamed(page);
});
return WillPopScope(
onWillPop: () async {
if (_navigatorState.currentState?.canPop() ?? false) {
_navigatorState.currentState?.pop();
if (!kDebugMode) {
return true;
}
return false;
}
if (selected.index != 0) {
setState(() => selected.index = 0);
_navigatorState.currentState?.pushReplacementNamed(selected.name);
}
return false;
},
child: Scaffold(
body: Column(
children: [
Expanded(
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Navigator(
key: _navigatorState,
initialRoute: selected.name,
onGenerateRoute: (settings) => navigationRouteHandler(settings),
),
],
),
),
// Status bar
Material(
color: Theme.of(context).colorScheme.background,
child: const StatusBar(),
),
// Bottom Navigaton Bar
Material(
color: Theme.of(context).scaffoldBackgroundColor,
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: Navbar(
selectedIndex: selected.index,
onSelected: onPageSelected,
items: [
NavItem(
title: "home".i18n,
icon: const Icon(FilcIcons.home),
activeIcon: const Icon(FilcIcons.homefill),
),
NavItem(
title: "grades".i18n,
icon: const Icon(FeatherIcons.bookmark),
activeIcon: const Icon(FilcIcons.gradesfill),
),
NavItem(
title: "timetable".i18n,
icon: const Icon(FeatherIcons.calendar),
activeIcon: const Icon(FilcIcons.timetablefill),
),
NavItem(
title: "messages".i18n,
icon: const Icon(FeatherIcons.messageSquare),
activeIcon: const Icon(FilcIcons.messagesfill),
),
NavItem(
title: "absences".i18n,
icon: const Icon(FeatherIcons.clock),
activeIcon: const Icon(FilcIcons.absencesfill),
),
],
),
),
),
],
),
),
);
}
void onPageSelected(int index) {
// Vibrate, then set the active screen
if (selected.index != index) {
switch (settings.vibrate) {
case VibrationStrength.light:
HapticFeedback.lightImpact();
break;
case VibrationStrength.medium:
HapticFeedback.mediumImpact();
break;
case VibrationStrength.strong:
HapticFeedback.heavyImpact();
break;
default:
}
setState(() => selected.index = index);
_navigatorState.currentState?.pushReplacementNamed(selected.name);
}
}
}

View File

@@ -0,0 +1,110 @@
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:filcnaplo/utils/color.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:filcnaplo/api/providers/status_provider.dart';
import 'status_bar.i18n.dart';
class StatusBar extends StatefulWidget {
const StatusBar({Key? key}) : super(key: key);
@override
_StatusBarState createState() => _StatusBarState();
}
class _StatusBarState extends State<StatusBar> {
late StatusProvider statusProvider;
@override
Widget build(BuildContext context) {
statusProvider = Provider.of<StatusProvider>(context);
Status? currentStatus = statusProvider.getStatus();
Color backgroundColor = _statusColor(currentStatus);
Color color = ColorUtils.foregroundColor(backgroundColor);
return AnimatedContainer(
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
height: currentStatus != null ? 32.0 : 0,
width: double.infinity,
color: Theme.of(context).scaffoldBackgroundColor,
child: Stack(
children: [
// Background
AnimatedContainer(
margin: const EdgeInsets.only(left: 6.0, right: 6.0, top: 8.0),
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
height: currentStatus != null ? 28.0 : 0,
decoration: BoxDecoration(
color: backgroundColor,
boxShadow: [BoxShadow(color: Theme.of(context).shadowColor, blurRadius: 8.0)],
borderRadius: BorderRadius.circular(45.0),
),
),
// Progress bar
if (currentStatus == Status.syncing)
Container(
margin: const EdgeInsets.only(left: 6.0, right: 6.0, top: 8.0),
alignment: Alignment.bottomLeft,
child: AnimatedContainer(
height: currentStatus != null ? 28.0 : 0,
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
width: MediaQuery.of(context).size.width * statusProvider.progress - 16.0,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondary.withOpacity(0.8),
borderRadius: BorderRadius.circular(45.0),
),
),
),
// Text
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Center(
child: Text(
_statusString(currentStatus),
style: TextStyle(color: color, fontWeight: FontWeight.w500),
),
),
),
],
),
);
}
String _statusString(Status? status) {
switch (status) {
case Status.syncing:
return "Syncing data".i18n;
case Status.maintenance:
return "KRETA Maintenance".i18n;
case Status.network:
return "No connection".i18n;
default:
return "";
}
}
Color _statusColor(Status? status) {
switch (status) {
case Status.maintenance:
return AppColors.of(context).red;
case Status.network:
case Status.syncing:
default:
HSLColor color = HSLColor.fromColor(Theme.of(context).scaffoldBackgroundColor);
if (color.lightness >= 0.5) {
color = color.withSaturation(0.3);
color = color.withLightness(color.lightness - 0.1);
} else {
color = color.withSaturation(0);
color = color.withLightness(color.lightness + 0.2);
}
return color.toColor();
}
}
}

View File

@@ -0,0 +1,27 @@
import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String {
static final _t = Translations.byLocale("hu_hu") +
{
"en_en": {
"Syncing data": "Syncing data",
"KRETA Maintenance": "KRETA Maintenance",
"No connection": "No connection",
},
"hu_hu": {
"Syncing data": "Adatok frissítése",
"KRETA Maintenance": "KRÉTA Karbantartás",
"No connection": "Nincs kapcsolat",
},
"de_de": {
"Syncing data": "Daten aktualisieren",
"KRETA Maintenance": "KRETA Wartung",
"No connection": "Keine Verbindung",
},
};
String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t);
}