igen
This commit is contained in:
@@ -1,36 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ActionButton extends StatelessWidget {
|
||||
const ActionButton({Key? key, required this.label, this.activeColor, this.onTap}) : super(key: key);
|
||||
|
||||
final Color? activeColor;
|
||||
final void Function()? onTap;
|
||||
final String label;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 6.0, bottom: 6.0, right: 3.0),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
height: 32.0,
|
||||
decoration: BoxDecoration(
|
||||
color: (activeColor ?? Theme.of(context).colorScheme.secondary).withOpacity(0.25),
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 12.0),
|
||||
child: Center(
|
||||
child: Text(label,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.w600, color: activeColor ?? Theme.of(context).colorScheme.secondary))),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ActionButton extends StatelessWidget {
|
||||
const ActionButton({Key? key, required this.label, this.activeColor, this.onTap}) : super(key: key);
|
||||
|
||||
final Color? activeColor;
|
||||
final void Function()? onTap;
|
||||
final String label;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 6.0, bottom: 6.0, right: 3.0),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
height: 32.0,
|
||||
decoration: BoxDecoration(
|
||||
color: (activeColor ?? Theme.of(context).colorScheme.secondary).withOpacity(0.25),
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 12.0),
|
||||
child: Center(
|
||||
child: Text(label,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.w600, color: activeColor ?? Theme.of(context).colorScheme.secondary))),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:i18n_extension/i18n_widget.dart';
|
||||
|
||||
class AverageDisplay extends StatelessWidget {
|
||||
const AverageDisplay({Key? key, this.average = 0.0, this.border = false}) : super(key: key);
|
||||
|
||||
final double average;
|
||||
final bool border;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color color = average == 0.0 ? AppColors.of(context).text.withOpacity(.8) : gradeColor(context: context, value: average);
|
||||
|
||||
String averageText = average.toStringAsFixed(2);
|
||||
if (I18n.of(context).locale.languageCode != "en") averageText = averageText.replaceAll(".", ",");
|
||||
|
||||
return Container(
|
||||
width: border ? 57.0 : 54.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0 - (border ? 2 : 0), vertical: 6.0 - (border ? 2 : 0)),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
border: border ? Border.fromBorderSide(BorderSide(color: color.withOpacity(.5), width: 3.0)) : null,
|
||||
color: !border ? color.withOpacity(average == 0.0 ? .15 : .25) : null,
|
||||
),
|
||||
child: Text(
|
||||
average == 0.0 ? "-" : averageText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: color, fontWeight: FontWeight.w600),
|
||||
maxLines: 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:i18n_extension/i18n_widget.dart';
|
||||
|
||||
class AverageDisplay extends StatelessWidget {
|
||||
const AverageDisplay({Key? key, this.average = 0.0, this.border = false}) : super(key: key);
|
||||
|
||||
final double average;
|
||||
final bool border;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color color = average == 0.0 ? AppColors.of(context).text.withOpacity(.8) : gradeColor(context: context, value: average);
|
||||
|
||||
String averageText = average.toStringAsFixed(2);
|
||||
if (I18n.of(context).locale.languageCode != "en") averageText = averageText.replaceAll(".", ",");
|
||||
|
||||
return Container(
|
||||
width: border ? 57.0 : 54.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0 - (border ? 2 : 0), vertical: 6.0 - (border ? 2 : 0)),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
border: border ? Border.fromBorderSide(BorderSide(color: color.withOpacity(.5), width: 3.0)) : null,
|
||||
color: !border ? color.withOpacity(average == 0.0 ? .15 : .25) : null,
|
||||
),
|
||||
child: Text(
|
||||
average == 0.0 ? "-" : averageText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: color, fontWeight: FontWeight.w600),
|
||||
maxLines: 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomCard extends StatelessWidget {
|
||||
const BottomCard({Key? key, this.child}) : super(key: key);
|
||||
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 42.0,
|
||||
height: 4.0,
|
||||
margin: const EdgeInsets.only(top: 12.0, bottom: 4.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
color: AppColors.of(context).text.withOpacity(0.10),
|
||||
),
|
||||
),
|
||||
if (child != null) child!,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showBottomCard({
|
||||
required BuildContext context,
|
||||
Widget? child,
|
||||
bool rootNavigator = true,
|
||||
}) async =>
|
||||
await showModalBottomSheet(
|
||||
backgroundColor: const Color(0x00000000),
|
||||
useRootNavigator: rootNavigator,
|
||||
elevation: 0,
|
||||
isDismissible: true,
|
||||
context: context,
|
||||
builder: (context) => BottomCard(child: child));
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomCard extends StatelessWidget {
|
||||
const BottomCard({Key? key, this.child}) : super(key: key);
|
||||
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 42.0,
|
||||
height: 4.0,
|
||||
margin: const EdgeInsets.only(top: 12.0, bottom: 4.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
color: AppColors.of(context).text.withOpacity(0.10),
|
||||
),
|
||||
),
|
||||
if (child != null) child!,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showBottomCard({
|
||||
required BuildContext context,
|
||||
Widget? child,
|
||||
bool rootNavigator = true,
|
||||
}) async =>
|
||||
await showModalBottomSheet(
|
||||
backgroundColor: const Color(0x00000000),
|
||||
useRootNavigator: rootNavigator,
|
||||
elevation: 0,
|
||||
isDismissible: true,
|
||||
context: context,
|
||||
builder: (context) => BottomCard(child: child));
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomSheetMenu extends StatelessWidget {
|
||||
const BottomSheetMenu({Key? key, this.items = const []}) : super(key: key);
|
||||
|
||||
final List<Widget> items;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: items,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void showBottomSheetMenu(BuildContext context, {List<Widget> items = const []}) =>
|
||||
showRoundedModalBottomSheet(context, child: BottomSheetMenu(items: items));
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomSheetMenu extends StatelessWidget {
|
||||
const BottomSheetMenu({Key? key, this.items = const []}) : super(key: key);
|
||||
|
||||
final List<Widget> items;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: items,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void showBottomSheetMenu(BuildContext context, {List<Widget> items = const []}) =>
|
||||
showRoundedModalBottomSheet(context, child: BottomSheetMenu(items: items));
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomSheetMenuItem extends StatelessWidget {
|
||||
const BottomSheetMenuItem({Key? key, required this.onPressed, required this.title, this.icon}) : super(key: key);
|
||||
|
||||
final void Function()? onPressed;
|
||||
final Widget? title;
|
||||
final Widget? icon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PanelButton(
|
||||
onPressed: onPressed,
|
||||
leading: icon,
|
||||
title: title,
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomSheetMenuItem extends StatelessWidget {
|
||||
const BottomSheetMenuItem({Key? key, required this.onPressed, required this.title, this.icon}) : super(key: key);
|
||||
|
||||
final void Function()? onPressed;
|
||||
final Widget? title;
|
||||
final Widget? icon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PanelButton(
|
||||
onPressed: onPressed,
|
||||
leading: icon,
|
||||
title: title,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RoundedBottomSheet extends StatelessWidget {
|
||||
const RoundedBottomSheet({Key? key, this.child, this.borderRadius = 12.0, this.shrink = true, this.showHandle = true}) : super(key: key);
|
||||
|
||||
final Widget? child;
|
||||
final double borderRadius;
|
||||
final bool shrink;
|
||||
final bool showHandle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(borderRadius),
|
||||
topRight: Radius.circular(borderRadius),
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: shrink ? MainAxisSize.min : MainAxisSize.max,
|
||||
children: [
|
||||
if (showHandle)
|
||||
Container(
|
||||
width: 42.0,
|
||||
height: 4.0,
|
||||
margin: const EdgeInsets.only(top: 12.0, bottom: 4.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
color: AppColors.of(context).text.withOpacity(0.10),
|
||||
),
|
||||
),
|
||||
if (child != null) child!,
|
||||
SizedBox(height: MediaQuery.of(context).padding.bottom),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<T?> showRoundedModalBottomSheet<T>(
|
||||
BuildContext context, {
|
||||
required Widget child,
|
||||
bool rootNavigator = true,
|
||||
}) async {
|
||||
return await showModalBottomSheet<T>(
|
||||
context: context,
|
||||
backgroundColor: const Color(0x00000000),
|
||||
elevation: 0,
|
||||
isDismissible: true,
|
||||
useRootNavigator: rootNavigator,
|
||||
builder: (context) => RoundedBottomSheet(child: child));
|
||||
}
|
||||
|
||||
PersistentBottomSheetController<T> showRoundedBottomSheet<T>(
|
||||
BuildContext context, {
|
||||
required Widget child,
|
||||
}) {
|
||||
return showBottomSheet<T>(
|
||||
context: context,
|
||||
backgroundColor: const Color(0x00000000),
|
||||
elevation: 12.0,
|
||||
builder: (context) => RoundedBottomSheet(child: child),
|
||||
);
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RoundedBottomSheet extends StatelessWidget {
|
||||
const RoundedBottomSheet({Key? key, this.child, this.borderRadius = 12.0, this.shrink = true, this.showHandle = true}) : super(key: key);
|
||||
|
||||
final Widget? child;
|
||||
final double borderRadius;
|
||||
final bool shrink;
|
||||
final bool showHandle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(borderRadius),
|
||||
topRight: Radius.circular(borderRadius),
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: shrink ? MainAxisSize.min : MainAxisSize.max,
|
||||
children: [
|
||||
if (showHandle)
|
||||
Container(
|
||||
width: 42.0,
|
||||
height: 4.0,
|
||||
margin: const EdgeInsets.only(top: 12.0, bottom: 4.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
color: AppColors.of(context).text.withOpacity(0.10),
|
||||
),
|
||||
),
|
||||
if (child != null) child!,
|
||||
SizedBox(height: MediaQuery.of(context).padding.bottom),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<T?> showRoundedModalBottomSheet<T>(
|
||||
BuildContext context, {
|
||||
required Widget child,
|
||||
bool rootNavigator = true,
|
||||
}) async {
|
||||
return await showModalBottomSheet<T>(
|
||||
context: context,
|
||||
backgroundColor: const Color(0x00000000),
|
||||
elevation: 0,
|
||||
isDismissible: true,
|
||||
useRootNavigator: rootNavigator,
|
||||
builder: (context) => RoundedBottomSheet(child: child));
|
||||
}
|
||||
|
||||
PersistentBottomSheetController<T> showRoundedBottomSheet<T>(
|
||||
BuildContext context, {
|
||||
required Widget child,
|
||||
}) {
|
||||
return showBottomSheet<T>(
|
||||
context: context,
|
||||
backgroundColor: const Color(0x00000000),
|
||||
elevation: 12.0,
|
||||
builder: (context) => RoundedBottomSheet(child: child),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
SnackBar CustomSnackBar({
|
||||
required Widget content,
|
||||
required BuildContext context,
|
||||
Brightness? brightness,
|
||||
Color? backgroundColor,
|
||||
Duration? duration,
|
||||
}) {
|
||||
// backgroundColor > Brightness > Theme Background
|
||||
Color _backgroundColor = backgroundColor ?? (AppColors.fromBrightness(brightness ?? Theme.of(context).brightness).highlight);
|
||||
Color textColor = AppColors.fromBrightness(brightness ?? Theme.of(context).brightness).text;
|
||||
|
||||
return SnackBar(
|
||||
duration: duration ?? const Duration(seconds: 4),
|
||||
content: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: _backgroundColor,
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
boxShadow: [BoxShadow(color: Colors.black.withOpacity(.15), blurRadius: 4.0)],
|
||||
),
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: DefaultTextStyle(
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: textColor, fontWeight: FontWeight.w500),
|
||||
child: content,
|
||||
),
|
||||
),
|
||||
backgroundColor: const Color(0x00000000),
|
||||
elevation: 0,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
||||
);
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
SnackBar CustomSnackBar({
|
||||
required Widget content,
|
||||
required BuildContext context,
|
||||
Brightness? brightness,
|
||||
Color? backgroundColor,
|
||||
Duration? duration,
|
||||
}) {
|
||||
// backgroundColor > Brightness > Theme Background
|
||||
Color _backgroundColor = backgroundColor ?? (AppColors.fromBrightness(brightness ?? Theme.of(context).brightness).highlight);
|
||||
Color textColor = AppColors.fromBrightness(brightness ?? Theme.of(context).brightness).text;
|
||||
|
||||
return SnackBar(
|
||||
duration: duration ?? const Duration(seconds: 4),
|
||||
content: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: _backgroundColor,
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
boxShadow: [BoxShadow(color: Colors.black.withOpacity(.15), blurRadius: 4.0)],
|
||||
),
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: DefaultTextStyle(
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: textColor, fontWeight: FontWeight.w500),
|
||||
child: content,
|
||||
),
|
||||
),
|
||||
backgroundColor: const Color(0x00000000),
|
||||
elevation: 0,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Detail extends StatelessWidget {
|
||||
const Detail({Key? key, required this.title, required this.description, this.maxLines = 3}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
final String description;
|
||||
final int? maxLines;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 18.0),
|
||||
child: SelectableText.rich(
|
||||
TextSpan(
|
||||
text: "$title: ",
|
||||
style: TextStyle(fontWeight: FontWeight.w600, color: AppColors.of(context).text),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: description,
|
||||
style: TextStyle(fontWeight: FontWeight.w500, color: AppColors.of(context).text.withOpacity(0.85)),
|
||||
),
|
||||
],
|
||||
),
|
||||
minLines: 1,
|
||||
maxLines: maxLines,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Detail extends StatelessWidget {
|
||||
const Detail({Key? key, required this.title, required this.description, this.maxLines = 3}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
final String description;
|
||||
final int? maxLines;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 18.0),
|
||||
child: SelectableText.rich(
|
||||
TextSpan(
|
||||
text: "$title: ",
|
||||
style: TextStyle(fontWeight: FontWeight.w600, color: AppColors.of(context).text),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: description,
|
||||
style: TextStyle(fontWeight: FontWeight.w500, color: AppColors.of(context).text.withOpacity(0.85)),
|
||||
),
|
||||
],
|
||||
),
|
||||
minLines: 1,
|
||||
maxLines: maxLines,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DialogButton extends StatelessWidget {
|
||||
const DialogButton({Key? key, required this.label, this.onTap}) : super(key: key);
|
||||
|
||||
final String label;
|
||||
final Function()? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RawMaterialButton(
|
||||
onPressed: onTap,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
child: Text(
|
||||
label.toUpperCase(),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DialogButton extends StatelessWidget {
|
||||
const DialogButton({Key? key, required this.label, this.onTap}) : super(key: key);
|
||||
|
||||
final String label;
|
||||
final Function()? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RawMaterialButton(
|
||||
onPressed: onTap,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
child: Text(
|
||||
label.toUpperCase(),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Dot extends StatelessWidget {
|
||||
final Color color;
|
||||
final double size;
|
||||
|
||||
const Dot({Key? key, this.color = Colors.grey, this.size = 16.0}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Dot extends StatelessWidget {
|
||||
final Color color;
|
||||
final double size;
|
||||
|
||||
const Dot({Key? key, this.color = Colors.grey, this.size = 16.0}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
List<String> faces = [
|
||||
"(·.·)",
|
||||
"(≥o≤)",
|
||||
"(·_·)",
|
||||
"(˚Δ˚)b",
|
||||
"(^-^*)",
|
||||
"(='X'=)",
|
||||
"(>_<)",
|
||||
"(;-;)",
|
||||
"\\(^Д^)/",
|
||||
"\\(o_o)/",
|
||||
];
|
||||
|
||||
class Empty extends StatelessWidget {
|
||||
const Empty({Key? key, this.subtitle}) : super(key: key);
|
||||
|
||||
final String? subtitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// make the face randomness a bit more constant (to avoid strokes)
|
||||
int index = Random(DateTime.now().minute).nextInt(faces.length);
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
text: faces[index],
|
||||
style: TextStyle(fontSize: 32.0, fontWeight: FontWeight.w500, color: AppColors.of(context).text.withOpacity(.75)),
|
||||
children: subtitle != null
|
||||
? [TextSpan(text: "\n" + subtitle!, style: TextStyle(fontSize: 18.0, height: 2.0, color: AppColors.of(context).text.withOpacity(.5)))]
|
||||
: [],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
List<String> faces = [
|
||||
"(·.·)",
|
||||
"(≥o≤)",
|
||||
"(·_·)",
|
||||
"(˚Δ˚)b",
|
||||
"(^-^*)",
|
||||
"(='X'=)",
|
||||
"(>_<)",
|
||||
"(;-;)",
|
||||
"\\(^Д^)/",
|
||||
"\\(o_o)/",
|
||||
];
|
||||
|
||||
class Empty extends StatelessWidget {
|
||||
const Empty({Key? key, this.subtitle}) : super(key: key);
|
||||
|
||||
final String? subtitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// make the face randomness a bit more constant (to avoid strokes)
|
||||
int index = Random(DateTime.now().minute).nextInt(faces.length);
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
text: faces[index],
|
||||
style: TextStyle(fontSize: 32.0, fontWeight: FontWeight.w500, color: AppColors.of(context).text.withOpacity(.75)),
|
||||
children: subtitle != null
|
||||
? [TextSpan(text: "\n" + subtitle!, style: TextStyle(fontSize: 18.0, height: 2.0, color: AppColors.of(context).text.withOpacity(.5)))]
|
||||
: [],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,117 +1,117 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FilterBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
const FilterBar({
|
||||
Key? key,
|
||||
required this.items,
|
||||
required this.controller,
|
||||
this.onTap,
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
this.disableFading = false,
|
||||
this.scrollable = true,
|
||||
this.censored = false,
|
||||
}) : assert(items.length == controller.length),
|
||||
super(key: key);
|
||||
|
||||
final List<Widget> items;
|
||||
final TabController controller;
|
||||
final EdgeInsetsGeometry padding;
|
||||
final Function(int)? onTap;
|
||||
final bool disableFading;
|
||||
final bool scrollable;
|
||||
final bool censored;
|
||||
|
||||
@override
|
||||
final Size preferredSize = const Size.fromHeight(42.0);
|
||||
|
||||
@override
|
||||
State<FilterBar> createState() => _FilterBarState();
|
||||
}
|
||||
|
||||
class _FilterBarState extends State<FilterBar> {
|
||||
List<double> censoredItemsWidth = [];
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
censoredItemsWidth = List.generate(widget.items.length, (index) => 25 + Random().nextDouble() * 50).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tabbar = TabBar(
|
||||
controller: widget.controller,
|
||||
isScrollable: widget.scrollable,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
// Label
|
||||
labelStyle: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 15.0,
|
||||
),
|
||||
labelPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 3),
|
||||
labelColor: Theme.of(context).colorScheme.secondary,
|
||||
unselectedLabelColor: AppColors.of(context).text.withOpacity(0.65),
|
||||
// Indicator
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
indicatorPadding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
indicator: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.secondary.withOpacity(0.25),
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
overlayColor: MaterialStateProperty.all(const Color(0x00000000)),
|
||||
// Tabs
|
||||
padding: EdgeInsets.zero,
|
||||
tabs: widget.censored
|
||||
? censoredItemsWidth
|
||||
.map(
|
||||
(e) => Container(
|
||||
width: e,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.45),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
: widget.items,
|
||||
onTap: widget.onTap,
|
||||
);
|
||||
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 48.0,
|
||||
padding: widget.padding,
|
||||
child: widget.disableFading
|
||||
? tabbar
|
||||
: AnimatedBuilder(
|
||||
animation: widget.controller.animation!,
|
||||
builder: (ctx, child) {
|
||||
// avoid fading over selected tab
|
||||
return ShaderMask(
|
||||
shaderCallback: (Rect bounds) {
|
||||
final Color bg = Theme.of(context).scaffoldBackgroundColor;
|
||||
final double index = widget.controller.animation!.value;
|
||||
return LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [
|
||||
index < 0.2 ? Colors.transparent : bg,
|
||||
Colors.transparent,
|
||||
Colors.transparent,
|
||||
index > widget.controller.length - 1.2 ? Colors.transparent : bg
|
||||
], stops: const [
|
||||
0,
|
||||
0.1,
|
||||
0.9,
|
||||
1
|
||||
]).createShader(bounds);
|
||||
},
|
||||
blendMode: BlendMode.dstOut,
|
||||
child: child);
|
||||
},
|
||||
child: tabbar,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FilterBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
const FilterBar({
|
||||
Key? key,
|
||||
required this.items,
|
||||
required this.controller,
|
||||
this.onTap,
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
this.disableFading = false,
|
||||
this.scrollable = true,
|
||||
this.censored = false,
|
||||
}) : assert(items.length == controller.length),
|
||||
super(key: key);
|
||||
|
||||
final List<Widget> items;
|
||||
final TabController controller;
|
||||
final EdgeInsetsGeometry padding;
|
||||
final Function(int)? onTap;
|
||||
final bool disableFading;
|
||||
final bool scrollable;
|
||||
final bool censored;
|
||||
|
||||
@override
|
||||
final Size preferredSize = const Size.fromHeight(42.0);
|
||||
|
||||
@override
|
||||
State<FilterBar> createState() => _FilterBarState();
|
||||
}
|
||||
|
||||
class _FilterBarState extends State<FilterBar> {
|
||||
List<double> censoredItemsWidth = [];
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
censoredItemsWidth = List.generate(widget.items.length, (index) => 25 + Random().nextDouble() * 50).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tabbar = TabBar(
|
||||
controller: widget.controller,
|
||||
isScrollable: widget.scrollable,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
// Label
|
||||
labelStyle: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 15.0,
|
||||
),
|
||||
labelPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 3),
|
||||
labelColor: Theme.of(context).colorScheme.secondary,
|
||||
unselectedLabelColor: AppColors.of(context).text.withOpacity(0.65),
|
||||
// Indicator
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
indicatorPadding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
indicator: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.secondary.withOpacity(0.25),
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
overlayColor: MaterialStateProperty.all(const Color(0x00000000)),
|
||||
// Tabs
|
||||
padding: EdgeInsets.zero,
|
||||
tabs: widget.censored
|
||||
? censoredItemsWidth
|
||||
.map(
|
||||
(e) => Container(
|
||||
width: e,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.45),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
: widget.items,
|
||||
onTap: widget.onTap,
|
||||
);
|
||||
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 48.0,
|
||||
padding: widget.padding,
|
||||
child: widget.disableFading
|
||||
? tabbar
|
||||
: AnimatedBuilder(
|
||||
animation: widget.controller.animation!,
|
||||
builder: (ctx, child) {
|
||||
// avoid fading over selected tab
|
||||
return ShaderMask(
|
||||
shaderCallback: (Rect bounds) {
|
||||
final Color bg = Theme.of(context).scaffoldBackgroundColor;
|
||||
final double index = widget.controller.animation!.value;
|
||||
return LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [
|
||||
index < 0.2 ? Colors.transparent : bg,
|
||||
Colors.transparent,
|
||||
Colors.transparent,
|
||||
index > widget.controller.length - 1.2 ? Colors.transparent : bg
|
||||
], stops: const [
|
||||
0,
|
||||
0.1,
|
||||
0.9,
|
||||
1
|
||||
]).createShader(bounds);
|
||||
},
|
||||
blendMode: BlendMode.dstOut,
|
||||
child: child);
|
||||
},
|
||||
child: tabbar,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HeroDialogRoute<T> extends PageRoute<T> {
|
||||
HeroDialogRoute({required this.builder}) : super();
|
||||
|
||||
final WidgetBuilder builder;
|
||||
|
||||
@override
|
||||
bool get opaque => false;
|
||||
|
||||
@override
|
||||
bool get barrierDismissible => true;
|
||||
|
||||
@override
|
||||
String? get barrierLabel => "livecard";
|
||||
|
||||
@override
|
||||
Duration get transitionDuration => const Duration(milliseconds: 250);
|
||||
|
||||
@override
|
||||
bool get maintainState => true;
|
||||
|
||||
@override
|
||||
Color get barrierColor => Colors.black38;
|
||||
|
||||
@override
|
||||
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
||||
return FadeTransition(opacity: CurvedAnimation(parent: animation, curve: Curves.easeOut), child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||
return builder(context);
|
||||
}
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HeroDialogRoute<T> extends PageRoute<T> {
|
||||
HeroDialogRoute({required this.builder}) : super();
|
||||
|
||||
final WidgetBuilder builder;
|
||||
|
||||
@override
|
||||
bool get opaque => false;
|
||||
|
||||
@override
|
||||
bool get barrierDismissible => true;
|
||||
|
||||
@override
|
||||
String? get barrierLabel => "livecard";
|
||||
|
||||
@override
|
||||
Duration get transitionDuration => const Duration(milliseconds: 250);
|
||||
|
||||
@override
|
||||
bool get maintainState => true;
|
||||
|
||||
@override
|
||||
Color get barrierColor => Colors.black38;
|
||||
|
||||
@override
|
||||
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
||||
return FadeTransition(opacity: CurvedAnimation(parent: animation, curve: Curves.easeOut), child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||
return builder(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,133 +1,133 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
|
||||
class HeroScrollView extends StatefulWidget {
|
||||
const HeroScrollView(
|
||||
{Key? key,
|
||||
required this.child,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
this.italic = false,
|
||||
this.navBarItems = const [],
|
||||
this.onClose,
|
||||
this.iconSize = 64.0,
|
||||
this.scrollController})
|
||||
: super(key: key);
|
||||
|
||||
final Widget child;
|
||||
final String title;
|
||||
final IconData? icon;
|
||||
final List<Widget> navBarItems;
|
||||
final VoidCallback? onClose;
|
||||
final double iconSize;
|
||||
final ScrollController? scrollController;
|
||||
final bool italic;
|
||||
|
||||
@override
|
||||
_HeroScrollViewState createState() => _HeroScrollViewState();
|
||||
}
|
||||
|
||||
class _HeroScrollViewState extends State<HeroScrollView> {
|
||||
late ScrollController _scrollController;
|
||||
|
||||
bool showBarTitle = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController = widget.scrollController ?? ScrollController();
|
||||
|
||||
_scrollController.addListener(() {
|
||||
if (_scrollController.offset > 42.0) {
|
||||
if (showBarTitle == false) setState(() => showBarTitle = true);
|
||||
} else {
|
||||
if (showBarTitle == true) setState(() => showBarTitle = false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_scrollController.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NestedScrollView(
|
||||
controller: _scrollController,
|
||||
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
||||
headerSliverBuilder: (context, _) => [
|
||||
SliverAppBar(
|
||||
pinned: true,
|
||||
floating: false,
|
||||
snap: false,
|
||||
centerTitle: false,
|
||||
titleSpacing: 0,
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
title: AnimatedOpacity(
|
||||
opacity: showBarTitle ? 1.0 : 0.0,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(widget.icon, color: AppColors.of(context).text.withOpacity(.8)),
|
||||
const SizedBox(width: 8.0),
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.title.capital(),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text, fontWeight: FontWeight.w500, fontStyle: widget.italic ? FontStyle.italic : null),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
duration: const Duration(milliseconds: 200)),
|
||||
leading: BackButton(
|
||||
color: AppColors.of(context).text,
|
||||
onPressed: () {
|
||||
if (widget.onClose != null) {
|
||||
widget.onClose!();
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}),
|
||||
actions: widget.navBarItems,
|
||||
expandedHeight: 124.0,
|
||||
stretch: true,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: Icon(
|
||||
widget.icon,
|
||||
size: widget.iconSize,
|
||||
color: AppColors.of(context).text.withOpacity(.15),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
margin: const EdgeInsets.only(top: 82),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Text(
|
||||
widget.title.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 36.0,
|
||||
color: AppColors.of(context).text.withOpacity(.9),
|
||||
fontStyle: widget.italic ? FontStyle.italic : null,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
body: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
|
||||
class HeroScrollView extends StatefulWidget {
|
||||
const HeroScrollView(
|
||||
{Key? key,
|
||||
required this.child,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
this.italic = false,
|
||||
this.navBarItems = const [],
|
||||
this.onClose,
|
||||
this.iconSize = 64.0,
|
||||
this.scrollController})
|
||||
: super(key: key);
|
||||
|
||||
final Widget child;
|
||||
final String title;
|
||||
final IconData? icon;
|
||||
final List<Widget> navBarItems;
|
||||
final VoidCallback? onClose;
|
||||
final double iconSize;
|
||||
final ScrollController? scrollController;
|
||||
final bool italic;
|
||||
|
||||
@override
|
||||
_HeroScrollViewState createState() => _HeroScrollViewState();
|
||||
}
|
||||
|
||||
class _HeroScrollViewState extends State<HeroScrollView> {
|
||||
late ScrollController _scrollController;
|
||||
|
||||
bool showBarTitle = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController = widget.scrollController ?? ScrollController();
|
||||
|
||||
_scrollController.addListener(() {
|
||||
if (_scrollController.offset > 42.0) {
|
||||
if (showBarTitle == false) setState(() => showBarTitle = true);
|
||||
} else {
|
||||
if (showBarTitle == true) setState(() => showBarTitle = false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_scrollController.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NestedScrollView(
|
||||
controller: _scrollController,
|
||||
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
||||
headerSliverBuilder: (context, _) => [
|
||||
SliverAppBar(
|
||||
pinned: true,
|
||||
floating: false,
|
||||
snap: false,
|
||||
centerTitle: false,
|
||||
titleSpacing: 0,
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
title: AnimatedOpacity(
|
||||
opacity: showBarTitle ? 1.0 : 0.0,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(widget.icon, color: AppColors.of(context).text.withOpacity(.8)),
|
||||
const SizedBox(width: 8.0),
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.title.capital(),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text, fontWeight: FontWeight.w500, fontStyle: widget.italic ? FontStyle.italic : null),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
duration: const Duration(milliseconds: 200)),
|
||||
leading: BackButton(
|
||||
color: AppColors.of(context).text,
|
||||
onPressed: () {
|
||||
if (widget.onClose != null) {
|
||||
widget.onClose!();
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}),
|
||||
actions: widget.navBarItems,
|
||||
expandedHeight: 124.0,
|
||||
stretch: true,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: Icon(
|
||||
widget.icon,
|
||||
size: widget.iconSize,
|
||||
color: AppColors.of(context).text.withOpacity(.15),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
margin: const EdgeInsets.only(top: 82),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Text(
|
||||
widget.title.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 36.0,
|
||||
color: AppColors.of(context).text.withOpacity(.9),
|
||||
fontStyle: widget.italic ? FontStyle.italic : null,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
body: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MaterialActionButton extends StatelessWidget {
|
||||
const MaterialActionButton({
|
||||
Key? key,
|
||||
required this.child,
|
||||
this.onPressed,
|
||||
this.backgroundColor,
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget child;
|
||||
final Function()? onPressed;
|
||||
final Color? backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RawMaterialButton(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
shape: const StadiumBorder(),
|
||||
child: DefaultTextStyle(
|
||||
child: child,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: backgroundColor != null ? ColorUtils.foregroundColor(backgroundColor!) : null,
|
||||
),
|
||||
),
|
||||
fillColor: backgroundColor ?? AppColors.of(context).text.withOpacity(.15),
|
||||
elevation: 0,
|
||||
highlightElevation: 0,
|
||||
onPressed: onPressed,
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MaterialActionButton extends StatelessWidget {
|
||||
const MaterialActionButton({
|
||||
Key? key,
|
||||
required this.child,
|
||||
this.onPressed,
|
||||
this.backgroundColor,
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget child;
|
||||
final Function()? onPressed;
|
||||
final Color? backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RawMaterialButton(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
shape: const StadiumBorder(),
|
||||
child: DefaultTextStyle(
|
||||
child: child,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: backgroundColor != null ? ColorUtils.foregroundColor(backgroundColor!) : null,
|
||||
),
|
||||
),
|
||||
fillColor: backgroundColor ?? AppColors.of(context).text.withOpacity(.15),
|
||||
elevation: 0,
|
||||
highlightElevation: 0,
|
||||
onPressed: onPressed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NewContentIndicator extends StatelessWidget {
|
||||
const NewContentIndicator({Key? key, this.size = 64.0}) : super(key: key);
|
||||
|
||||
final double size;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
alignment: Alignment.topRight,
|
||||
width: size,
|
||||
height: size,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
height: size / 3.0,
|
||||
width: size / 3.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Theme.of(context).scaffoldBackgroundColor, width: size / 20.0),
|
||||
),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).red,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NewContentIndicator extends StatelessWidget {
|
||||
const NewContentIndicator({Key? key, this.size = 64.0}) : super(key: key);
|
||||
|
||||
final double size;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
alignment: Alignment.topRight,
|
||||
width: size,
|
||||
height: size,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
height: size / 3.0,
|
||||
width: size / 3.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Theme.of(context).scaffoldBackgroundColor, width: size / 20.0),
|
||||
),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).red,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,135 +1,135 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Panel extends StatelessWidget {
|
||||
const Panel({Key? key, this.child, this.title, this.padding, this.hasShadow = true}) : super(key: key);
|
||||
|
||||
final Widget? child;
|
||||
final Widget? title;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final bool hasShadow;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Panel Title
|
||||
if (title != null) PanelTitle(title: title!),
|
||||
|
||||
// Panel Body
|
||||
if (child != null)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
boxShadow: [
|
||||
if (hasShadow)
|
||||
BoxShadow(
|
||||
offset: const Offset(0, 21),
|
||||
blurRadius: 23.0,
|
||||
color: Theme.of(context).shadowColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
padding: padding ?? const EdgeInsets.all(8.0),
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PanelTitle extends StatelessWidget {
|
||||
const PanelTitle({Key? key, required this.title}) : super(key: key);
|
||||
|
||||
final Widget title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 14.0, bottom: 8.0),
|
||||
child: DefaultTextStyle(
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600, color: AppColors.of(context).text.withOpacity(0.65)),
|
||||
child: title,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PanelHeader extends StatelessWidget {
|
||||
const PanelHeader({Key? key, required this.padding}) : super(key: key);
|
||||
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0)),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
offset: const Offset(0, 21),
|
||||
blurRadius: 23.0,
|
||||
color: Theme.of(context).shadowColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PanelBody extends StatelessWidget {
|
||||
const PanelBody({Key? key, this.child, this.padding}) : super(key: key);
|
||||
|
||||
final Widget? child;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
offset: const Offset(0, 21),
|
||||
blurRadius: 23.0,
|
||||
color: Theme.of(context).shadowColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
padding: padding,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PanelFooter extends StatelessWidget {
|
||||
const PanelFooter({Key? key, required this.padding}) : super(key: key);
|
||||
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(16.0), bottomRight: Radius.circular(16.0)),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
offset: const Offset(0, 21),
|
||||
blurRadius: 23.0,
|
||||
color: Theme.of(context).shadowColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Panel extends StatelessWidget {
|
||||
const Panel({Key? key, this.child, this.title, this.padding, this.hasShadow = true}) : super(key: key);
|
||||
|
||||
final Widget? child;
|
||||
final Widget? title;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final bool hasShadow;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Panel Title
|
||||
if (title != null) PanelTitle(title: title!),
|
||||
|
||||
// Panel Body
|
||||
if (child != null)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
boxShadow: [
|
||||
if (hasShadow)
|
||||
BoxShadow(
|
||||
offset: const Offset(0, 21),
|
||||
blurRadius: 23.0,
|
||||
color: Theme.of(context).shadowColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
padding: padding ?? const EdgeInsets.all(8.0),
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PanelTitle extends StatelessWidget {
|
||||
const PanelTitle({Key? key, required this.title}) : super(key: key);
|
||||
|
||||
final Widget title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 14.0, bottom: 8.0),
|
||||
child: DefaultTextStyle(
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600, color: AppColors.of(context).text.withOpacity(0.65)),
|
||||
child: title,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PanelHeader extends StatelessWidget {
|
||||
const PanelHeader({Key? key, required this.padding}) : super(key: key);
|
||||
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0)),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
offset: const Offset(0, 21),
|
||||
blurRadius: 23.0,
|
||||
color: Theme.of(context).shadowColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PanelBody extends StatelessWidget {
|
||||
const PanelBody({Key? key, this.child, this.padding}) : super(key: key);
|
||||
|
||||
final Widget? child;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
offset: const Offset(0, 21),
|
||||
blurRadius: 23.0,
|
||||
color: Theme.of(context).shadowColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
padding: padding,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PanelFooter extends StatelessWidget {
|
||||
const PanelFooter({Key? key, required this.padding}) : super(key: key);
|
||||
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(16.0), bottomRight: Radius.circular(16.0)),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
offset: const Offset(0, 21),
|
||||
blurRadius: 23.0,
|
||||
color: Theme.of(context).shadowColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PanelActionButton extends StatelessWidget {
|
||||
const PanelActionButton({
|
||||
Key? key,
|
||||
this.onPressed,
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 14.0),
|
||||
this.leading,
|
||||
this.title,
|
||||
this.trailing,
|
||||
}) : super(key: key);
|
||||
|
||||
final void Function()? onPressed;
|
||||
final EdgeInsetsGeometry padding;
|
||||
final Widget? leading;
|
||||
final Widget? title;
|
||||
final Widget? trailing;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RawMaterialButton(
|
||||
onPressed: onPressed,
|
||||
padding: padding,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
side: BorderSide(color: Theme.of(context).colorScheme.secondary.withOpacity(.6), width: 2),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: leading != null
|
||||
? Theme(
|
||||
data: Theme.of(context).copyWith(iconTheme: IconThemeData(color: Theme.of(context).colorScheme.secondary)),
|
||||
child: leading!,
|
||||
)
|
||||
: null,
|
||||
trailing: trailing,
|
||||
title: title != null
|
||||
? DefaultTextStyle(style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w500, fontSize: 15.0), child: title!)
|
||||
: null,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PanelActionButton extends StatelessWidget {
|
||||
const PanelActionButton({
|
||||
Key? key,
|
||||
this.onPressed,
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 14.0),
|
||||
this.leading,
|
||||
this.title,
|
||||
this.trailing,
|
||||
}) : super(key: key);
|
||||
|
||||
final void Function()? onPressed;
|
||||
final EdgeInsetsGeometry padding;
|
||||
final Widget? leading;
|
||||
final Widget? title;
|
||||
final Widget? trailing;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RawMaterialButton(
|
||||
onPressed: onPressed,
|
||||
padding: padding,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
side: BorderSide(color: Theme.of(context).colorScheme.secondary.withOpacity(.6), width: 2),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: leading != null
|
||||
? Theme(
|
||||
data: Theme.of(context).copyWith(iconTheme: IconThemeData(color: Theme.of(context).colorScheme.secondary)),
|
||||
child: leading!,
|
||||
)
|
||||
: null,
|
||||
trailing: trailing,
|
||||
title: title != null
|
||||
? DefaultTextStyle(style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w500, fontSize: 15.0), child: title!)
|
||||
: null,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +1,74 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PanelButton extends StatelessWidget {
|
||||
const PanelButton({
|
||||
Key? key,
|
||||
this.onPressed,
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 14.0),
|
||||
this.leading,
|
||||
this.title,
|
||||
this.trailing,
|
||||
this.background = false,
|
||||
this.trailingDivider = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final void Function()? onPressed;
|
||||
final EdgeInsetsGeometry padding;
|
||||
final Widget? leading;
|
||||
final Widget? title;
|
||||
final Widget? trailing;
|
||||
final bool background;
|
||||
final bool trailingDivider;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final button = RawMaterialButton(
|
||||
onPressed: onPressed,
|
||||
padding: padding,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
|
||||
fillColor: background ? Colors.white.withOpacity(Theme.of(context).brightness == Brightness.light ? .35 : .2) : null,
|
||||
child: ListTile(
|
||||
leading: leading != null
|
||||
? Theme(
|
||||
data: Theme.of(context).copyWith(iconTheme: IconThemeData(color: Theme.of(context).colorScheme.secondary)),
|
||||
child: leading!,
|
||||
)
|
||||
: null,
|
||||
trailing: trailingDivider
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(right: 6.0),
|
||||
width: 2.0,
|
||||
height: 32.0,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.15),
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
),
|
||||
if (trailing != null) trailing!,
|
||||
],
|
||||
)
|
||||
: trailing,
|
||||
title: title != null
|
||||
? DefaultTextStyle(style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600, fontSize: 16.0), child: title!)
|
||||
: null,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
);
|
||||
|
||||
if (!background) return button;
|
||||
|
||||
return BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 12.0,
|
||||
sigmaY: 12.0,
|
||||
),
|
||||
child: button);
|
||||
}
|
||||
}
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PanelButton extends StatelessWidget {
|
||||
const PanelButton({
|
||||
Key? key,
|
||||
this.onPressed,
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 14.0),
|
||||
this.leading,
|
||||
this.title,
|
||||
this.trailing,
|
||||
this.background = false,
|
||||
this.trailingDivider = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final void Function()? onPressed;
|
||||
final EdgeInsetsGeometry padding;
|
||||
final Widget? leading;
|
||||
final Widget? title;
|
||||
final Widget? trailing;
|
||||
final bool background;
|
||||
final bool trailingDivider;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final button = RawMaterialButton(
|
||||
onPressed: onPressed,
|
||||
padding: padding,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
|
||||
fillColor: background ? Colors.white.withOpacity(Theme.of(context).brightness == Brightness.light ? .35 : .2) : null,
|
||||
child: ListTile(
|
||||
leading: leading != null
|
||||
? Theme(
|
||||
data: Theme.of(context).copyWith(iconTheme: IconThemeData(color: Theme.of(context).colorScheme.secondary)),
|
||||
child: leading!,
|
||||
)
|
||||
: null,
|
||||
trailing: trailingDivider
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(right: 6.0),
|
||||
width: 2.0,
|
||||
height: 32.0,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.15),
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
),
|
||||
if (trailing != null) trailing!,
|
||||
],
|
||||
)
|
||||
: trailing,
|
||||
title: title != null
|
||||
? DefaultTextStyle(style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600, fontSize: 16.0), child: title!)
|
||||
: null,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
);
|
||||
|
||||
if (!background) return button;
|
||||
|
||||
return BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 12.0,
|
||||
sigmaY: 12.0,
|
||||
),
|
||||
child: button);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/settings/settings_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sliding_sheet/sliding_sheet.dart';
|
||||
|
||||
class ProfileButton extends StatelessWidget {
|
||||
const ProfileButton({Key? key, required this.child}) : super(key: key);
|
||||
|
||||
final ProfileImage child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool pMode = Provider.of<SettingsProvider>(context, listen: false).presentationMode;
|
||||
|
||||
return ProfileImage(
|
||||
backgroundColor: !pMode ? child.backgroundColor : Theme.of(context).colorScheme.secondary,
|
||||
heroTag: child.heroTag,
|
||||
key: child.key,
|
||||
name: !pMode ? child.name : "Béla",
|
||||
radius: child.radius,
|
||||
badge: child.badge,
|
||||
role: child.role,
|
||||
profilePictureString: child.profilePictureString,
|
||||
onTap: () {
|
||||
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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/settings/settings_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sliding_sheet/sliding_sheet.dart';
|
||||
|
||||
class ProfileButton extends StatelessWidget {
|
||||
const ProfileButton({Key? key, required this.child}) : super(key: key);
|
||||
|
||||
final ProfileImage child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool pMode = Provider.of<SettingsProvider>(context, listen: false).presentationMode;
|
||||
|
||||
return ProfileImage(
|
||||
backgroundColor: !pMode ? child.backgroundColor : Theme.of(context).colorScheme.secondary,
|
||||
heroTag: child.heroTag,
|
||||
key: child.key,
|
||||
name: !pMode ? child.name : "Béla",
|
||||
radius: child.radius,
|
||||
badge: child.badge,
|
||||
role: child.role,
|
||||
profilePictureString: child.profilePictureString,
|
||||
onTap: () {
|
||||
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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,229 +1,229 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:filcnaplo/models/user.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/new_content_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
|
||||
class ProfileImage extends StatefulWidget {
|
||||
const ProfileImage({
|
||||
Key? key,
|
||||
this.onTap,
|
||||
this.name,
|
||||
this.backgroundColor,
|
||||
this.radius = 20.0,
|
||||
this.heroTag,
|
||||
this.badge = false,
|
||||
this.role = Role.student,
|
||||
this.censored = false,
|
||||
this.profilePictureString = "",
|
||||
}) : super(key: key);
|
||||
|
||||
final void Function()? onTap;
|
||||
final String? name;
|
||||
final Color? backgroundColor;
|
||||
final double radius;
|
||||
final String? heroTag;
|
||||
final bool badge;
|
||||
final Role? role;
|
||||
final bool censored;
|
||||
final String profilePictureString;
|
||||
|
||||
@override
|
||||
State<ProfileImage> createState() => _ProfileImageState();
|
||||
}
|
||||
|
||||
class _ProfileImageState extends State<ProfileImage> {
|
||||
Image? profilePicture;
|
||||
String? profPicSaved;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
updatePic();
|
||||
}
|
||||
|
||||
void updatePic() {
|
||||
profilePicture = widget.profilePictureString != ""
|
||||
? Image.memory(const Base64Decoder().convert(widget.profilePictureString), fit: BoxFit.scaleDown, gaplessPlayback: true)
|
||||
: null;
|
||||
profPicSaved = widget.profilePictureString;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (profPicSaved != widget.profilePictureString) updatePic();
|
||||
|
||||
if (widget.heroTag == null) {
|
||||
return buildWithoutHero(context);
|
||||
} else {
|
||||
return buildWithHero(context);
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildWithoutHero(BuildContext context) {
|
||||
Color color = ColorUtils.foregroundColor(widget.backgroundColor ?? Theme.of(context).scaffoldBackgroundColor);
|
||||
Color roleColor;
|
||||
|
||||
if (Theme.of(context).brightness == Brightness.light) {
|
||||
roleColor = const Color(0xFF444444);
|
||||
} else {
|
||||
roleColor = const Color(0xFF555555);
|
||||
}
|
||||
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: const CircleBorder(),
|
||||
color: widget.backgroundColor ?? AppColors.of(context).text.withOpacity(.15),
|
||||
child: InkWell(
|
||||
onTap: widget.onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
height: widget.radius * 2,
|
||||
width: widget.radius * 2,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: widget.name != null && (widget.name?.trim().length ?? 0) > 0
|
||||
? Center(
|
||||
child: widget.censored
|
||||
? Container(
|
||||
width: 15,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(.5),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
)
|
||||
: profilePicture ??
|
||||
Text(
|
||||
(widget.name?.trim().length ?? 0) > 0 ? (widget.name ?? "?").trim()[0] : "?",
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18.0 * (widget.radius / 20.0),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Role indicator
|
||||
if (widget.role == Role.parent)
|
||||
SizedBox(
|
||||
height: widget.radius * 2,
|
||||
width: widget.radius * 2,
|
||||
child: Container(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Icon(Icons.shield, color: roleColor, size: widget.radius / 1.3),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildWithHero(BuildContext context) {
|
||||
Color color = ColorUtils.foregroundColor(widget.backgroundColor ?? Theme.of(context).scaffoldBackgroundColor);
|
||||
Color roleColor;
|
||||
|
||||
if (Theme.of(context).brightness == Brightness.light) {
|
||||
roleColor = const Color(0xFF444444);
|
||||
} else {
|
||||
roleColor = const Color(0xFF555555);
|
||||
}
|
||||
|
||||
Widget child = FittedBox(
|
||||
fit: BoxFit.fitHeight,
|
||||
child: Text(
|
||||
(widget.name?.trim().length ?? 0) > 0 ? (widget.name ?? "?").trim()[0] : "?",
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18.0 * (widget.radius / 20.0),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
height: widget.radius * 2,
|
||||
width: widget.radius * 2,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
if (widget.name != null && (widget.name?.trim().length ?? 0) > 0)
|
||||
Hero(
|
||||
tag: widget.heroTag! + "background",
|
||||
transitionOnUserGestures: true,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: const CircleBorder(),
|
||||
color: profilePicture != null ? Colors.transparent : widget.backgroundColor ?? AppColors.of(context).text.withOpacity(.15),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
height: widget.radius * 2,
|
||||
width: widget.radius * 2,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: profilePicture,
|
||||
),
|
||||
),
|
||||
),
|
||||
Hero(
|
||||
tag: widget.heroTag! + "child",
|
||||
transitionOnUserGestures: true,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: profilePicture != null ? const CircleBorder() : null,
|
||||
child: profilePicture ?? child,
|
||||
type: MaterialType.transparency,
|
||||
),
|
||||
),
|
||||
|
||||
// Badge
|
||||
if (widget.badge)
|
||||
Hero(
|
||||
tag: widget.heroTag! + "new_content_indicator",
|
||||
child: NewContentIndicator(size: widget.radius * 2),
|
||||
),
|
||||
|
||||
// Role indicator
|
||||
if (widget.role == Role.parent)
|
||||
Hero(
|
||||
tag: widget.heroTag! + "role_indicator",
|
||||
child: FittedBox(
|
||||
fit: BoxFit.fitHeight,
|
||||
child: SizedBox(
|
||||
height: widget.radius * 2,
|
||||
width: widget.radius * 2,
|
||||
child: Container(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Icon(Icons.shield, color: roleColor, size: widget.radius / 1.3),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: const CircleBorder(),
|
||||
child: InkWell(
|
||||
onTap: widget.onTap,
|
||||
child: SizedBox(
|
||||
height: widget.radius * 2,
|
||||
width: widget.radius * 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:filcnaplo/models/user.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/new_content_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
|
||||
class ProfileImage extends StatefulWidget {
|
||||
const ProfileImage({
|
||||
Key? key,
|
||||
this.onTap,
|
||||
this.name,
|
||||
this.backgroundColor,
|
||||
this.radius = 20.0,
|
||||
this.heroTag,
|
||||
this.badge = false,
|
||||
this.role = Role.student,
|
||||
this.censored = false,
|
||||
this.profilePictureString = "",
|
||||
}) : super(key: key);
|
||||
|
||||
final void Function()? onTap;
|
||||
final String? name;
|
||||
final Color? backgroundColor;
|
||||
final double radius;
|
||||
final String? heroTag;
|
||||
final bool badge;
|
||||
final Role? role;
|
||||
final bool censored;
|
||||
final String profilePictureString;
|
||||
|
||||
@override
|
||||
State<ProfileImage> createState() => _ProfileImageState();
|
||||
}
|
||||
|
||||
class _ProfileImageState extends State<ProfileImage> {
|
||||
Image? profilePicture;
|
||||
String? profPicSaved;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
updatePic();
|
||||
}
|
||||
|
||||
void updatePic() {
|
||||
profilePicture = widget.profilePictureString != ""
|
||||
? Image.memory(const Base64Decoder().convert(widget.profilePictureString), fit: BoxFit.scaleDown, gaplessPlayback: true)
|
||||
: null;
|
||||
profPicSaved = widget.profilePictureString;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (profPicSaved != widget.profilePictureString) updatePic();
|
||||
|
||||
if (widget.heroTag == null) {
|
||||
return buildWithoutHero(context);
|
||||
} else {
|
||||
return buildWithHero(context);
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildWithoutHero(BuildContext context) {
|
||||
Color color = ColorUtils.foregroundColor(widget.backgroundColor ?? Theme.of(context).scaffoldBackgroundColor);
|
||||
Color roleColor;
|
||||
|
||||
if (Theme.of(context).brightness == Brightness.light) {
|
||||
roleColor = const Color(0xFF444444);
|
||||
} else {
|
||||
roleColor = const Color(0xFF555555);
|
||||
}
|
||||
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: const CircleBorder(),
|
||||
color: widget.backgroundColor ?? AppColors.of(context).text.withOpacity(.15),
|
||||
child: InkWell(
|
||||
onTap: widget.onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
height: widget.radius * 2,
|
||||
width: widget.radius * 2,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: widget.name != null && (widget.name?.trim().length ?? 0) > 0
|
||||
? Center(
|
||||
child: widget.censored
|
||||
? Container(
|
||||
width: 15,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(.5),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
)
|
||||
: profilePicture ??
|
||||
Text(
|
||||
(widget.name?.trim().length ?? 0) > 0 ? (widget.name ?? "?").trim()[0] : "?",
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18.0 * (widget.radius / 20.0),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Role indicator
|
||||
if (widget.role == Role.parent)
|
||||
SizedBox(
|
||||
height: widget.radius * 2,
|
||||
width: widget.radius * 2,
|
||||
child: Container(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Icon(Icons.shield, color: roleColor, size: widget.radius / 1.3),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildWithHero(BuildContext context) {
|
||||
Color color = ColorUtils.foregroundColor(widget.backgroundColor ?? Theme.of(context).scaffoldBackgroundColor);
|
||||
Color roleColor;
|
||||
|
||||
if (Theme.of(context).brightness == Brightness.light) {
|
||||
roleColor = const Color(0xFF444444);
|
||||
} else {
|
||||
roleColor = const Color(0xFF555555);
|
||||
}
|
||||
|
||||
Widget child = FittedBox(
|
||||
fit: BoxFit.fitHeight,
|
||||
child: Text(
|
||||
(widget.name?.trim().length ?? 0) > 0 ? (widget.name ?? "?").trim()[0] : "?",
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18.0 * (widget.radius / 20.0),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
height: widget.radius * 2,
|
||||
width: widget.radius * 2,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
if (widget.name != null && (widget.name?.trim().length ?? 0) > 0)
|
||||
Hero(
|
||||
tag: widget.heroTag! + "background",
|
||||
transitionOnUserGestures: true,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: const CircleBorder(),
|
||||
color: profilePicture != null ? Colors.transparent : widget.backgroundColor ?? AppColors.of(context).text.withOpacity(.15),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
height: widget.radius * 2,
|
||||
width: widget.radius * 2,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: profilePicture,
|
||||
),
|
||||
),
|
||||
),
|
||||
Hero(
|
||||
tag: widget.heroTag! + "child",
|
||||
transitionOnUserGestures: true,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: profilePicture != null ? const CircleBorder() : null,
|
||||
child: profilePicture ?? child,
|
||||
type: MaterialType.transparency,
|
||||
),
|
||||
),
|
||||
|
||||
// Badge
|
||||
if (widget.badge)
|
||||
Hero(
|
||||
tag: widget.heroTag! + "new_content_indicator",
|
||||
child: NewContentIndicator(size: widget.radius * 2),
|
||||
),
|
||||
|
||||
// Role indicator
|
||||
if (widget.role == Role.parent)
|
||||
Hero(
|
||||
tag: widget.heroTag! + "role_indicator",
|
||||
child: FittedBox(
|
||||
fit: BoxFit.fitHeight,
|
||||
child: SizedBox(
|
||||
height: widget.radius * 2,
|
||||
width: widget.radius * 2,
|
||||
child: Container(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Icon(Icons.shield, color: roleColor, size: widget.radius / 1.3),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: const CircleBorder(),
|
||||
child: InkWell(
|
||||
onTap: widget.onTap,
|
||||
child: SizedBox(
|
||||
height: widget.radius * 2,
|
||||
width: widget.radius * 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +1,69 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ProgressBar extends StatelessWidget {
|
||||
const ProgressBar({Key? key, required this.value, this.backgroundColor}) : super(key: key);
|
||||
|
||||
final double value;
|
||||
final Color? backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
// Background
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness == Brightness.light ? Colors.black.withOpacity(0.1) : Colors.white.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
width: double.infinity,
|
||||
height: 8.0,
|
||||
),
|
||||
|
||||
// Slider
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
width: double.infinity,
|
||||
child: CustomPaint(
|
||||
painter: ProgressPainter(
|
||||
backgroundColor: backgroundColor ?? Theme.of(context).colorScheme.secondary,
|
||||
height: 8.0,
|
||||
value: value.clamp(0, 1),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProgressPainter extends CustomPainter {
|
||||
ProgressPainter({required this.height, required this.value, required this.backgroundColor});
|
||||
|
||||
final double height;
|
||||
final double value;
|
||||
final Color backgroundColor;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
double width = size.width * value;
|
||||
|
||||
if (width <= 0) return;
|
||||
|
||||
// Slider
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(0, 0, width, height),
|
||||
const Radius.circular(45.0),
|
||||
),
|
||||
Paint()
|
||||
..color = backgroundColor
|
||||
..style = PaintingStyle.fill,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(ProgressPainter oldDelegate) {
|
||||
return value != oldDelegate.value || height != oldDelegate.height || backgroundColor != oldDelegate.backgroundColor;
|
||||
}
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ProgressBar extends StatelessWidget {
|
||||
const ProgressBar({Key? key, required this.value, this.backgroundColor}) : super(key: key);
|
||||
|
||||
final double value;
|
||||
final Color? backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
// Background
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness == Brightness.light ? Colors.black.withOpacity(0.1) : Colors.white.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
width: double.infinity,
|
||||
height: 8.0,
|
||||
),
|
||||
|
||||
// Slider
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
width: double.infinity,
|
||||
child: CustomPaint(
|
||||
painter: ProgressPainter(
|
||||
backgroundColor: backgroundColor ?? Theme.of(context).colorScheme.secondary,
|
||||
height: 8.0,
|
||||
value: value.clamp(0, 1),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProgressPainter extends CustomPainter {
|
||||
ProgressPainter({required this.height, required this.value, required this.backgroundColor});
|
||||
|
||||
final double height;
|
||||
final double value;
|
||||
final Color backgroundColor;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
double width = size.width * value;
|
||||
|
||||
if (width <= 0) return;
|
||||
|
||||
// Slider
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(0, 0, width, height),
|
||||
const Radius.circular(45.0),
|
||||
),
|
||||
Paint()
|
||||
..color = backgroundColor
|
||||
..style = PaintingStyle.fill,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(ProgressPainter oldDelegate) {
|
||||
return value != oldDelegate.value || height != oldDelegate.height || backgroundColor != oldDelegate.backgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension ScreensLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"home": "Home",
|
||||
"grades": "Grades",
|
||||
"timetable": "Timetable",
|
||||
"messages": "Messages",
|
||||
"absences": "Absences",
|
||||
},
|
||||
"hu_hu": {
|
||||
"home": "Kezdőlap",
|
||||
"grades": "Jegyek",
|
||||
"timetable": "Órarend",
|
||||
"messages": "Üzenetek",
|
||||
"absences": "Hiányok",
|
||||
},
|
||||
"de_de": {
|
||||
"home": "Zuhause",
|
||||
"grades": "Noten",
|
||||
"timetable": "Zeitplan",
|
||||
"messages": "Mitteilungen",
|
||||
"absences": "Fehlen",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension ScreensLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"home": "Home",
|
||||
"grades": "Grades",
|
||||
"timetable": "Timetable",
|
||||
"messages": "Messages",
|
||||
"absences": "Absences",
|
||||
},
|
||||
"hu_hu": {
|
||||
"home": "Kezdőlap",
|
||||
"grades": "Jegyek",
|
||||
"timetable": "Órarend",
|
||||
"messages": "Üzenetek",
|
||||
"absences": "Hiányok",
|
||||
},
|
||||
"de_de": {
|
||||
"home": "Zuhause",
|
||||
"grades": "Noten",
|
||||
"timetable": "Zeitplan",
|
||||
"messages": "Mitteilungen",
|
||||
"absences": "Fehlen",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sliding_sheet/sliding_sheet.dart' as ss;
|
||||
|
||||
void showSlidingBottomSheet({required Widget child, required BuildContext context}) => ss.showSlidingBottomSheet(context,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => ss.SlidingSheetDialog(
|
||||
cornerRadius: 16,
|
||||
cornerRadiusOnFullscreen: 0,
|
||||
avoidStatusBar: true,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
snapSpec: const ss.SnapSpec(
|
||||
snap: true,
|
||||
snappings: [0.5, 1.0],
|
||||
positioning: ss.SnapPositioning.relativeToAvailableSpace,
|
||||
),
|
||||
headerBuilder: (context, state) {
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
height: 4.0,
|
||||
width: 60.0,
|
||||
margin: const EdgeInsets.all(12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Padding(padding: const EdgeInsets.fromLTRB(12.0, 0, 12.0, 8.0), child: child),
|
||||
);
|
||||
},
|
||||
));
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sliding_sheet/sliding_sheet.dart' as ss;
|
||||
|
||||
void showSlidingBottomSheet({required Widget child, required BuildContext context}) => ss.showSlidingBottomSheet(context,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => ss.SlidingSheetDialog(
|
||||
cornerRadius: 16,
|
||||
cornerRadiusOnFullscreen: 0,
|
||||
avoidStatusBar: true,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
snapSpec: const ss.SnapSpec(
|
||||
snap: true,
|
||||
snappings: [0.5, 1.0],
|
||||
positioning: ss.SnapPositioning.relativeToAvailableSpace,
|
||||
),
|
||||
headerBuilder: (context, state) {
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
height: 4.0,
|
||||
width: 60.0,
|
||||
margin: const EdgeInsets.all(12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Padding(padding: const EdgeInsets.fromLTRB(12.0, 0, 12.0, 8.0), child: child),
|
||||
);
|
||||
},
|
||||
));
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
void setSystemChrome(BuildContext context) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.top, SystemUiOverlay.bottom]);
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: Theme.of(context).brightness == Brightness.light ? Brightness.dark : Brightness.light,
|
||||
systemNavigationBarColor: Theme.of(context).bottomNavigationBarTheme.backgroundColor,
|
||||
systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light ? Brightness.dark : Brightness.light,
|
||||
statusBarBrightness: Platform.isIOS ? Theme.of(context).brightness : null,
|
||||
));
|
||||
}
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
void setSystemChrome(BuildContext context) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.top, SystemUiOverlay.bottom]);
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: Theme.of(context).brightness == Brightness.light ? Brightness.dark : Brightness.light,
|
||||
systemNavigationBarColor: Theme.of(context).bottomNavigationBarTheme.backgroundColor,
|
||||
systemNavigationBarIconBrightness: Theme.of(context).brightness == Brightness.light ? Brightness.dark : Brightness.light,
|
||||
statusBarBrightness: Platform.isIOS ? Theme.of(context).brightness : null,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,59 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:i18n_extension/i18n_widget.dart';
|
||||
|
||||
class TrendDisplay<T extends num> extends StatelessWidget {
|
||||
const TrendDisplay({Key? key, required this.current, required this.previous, this.padding}) : super(key: key);
|
||||
|
||||
final T current;
|
||||
final T previous;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const upIcon = "▲";
|
||||
const downIcon = "▼";
|
||||
final upColor = Colors.lightGreenAccent.shade700;
|
||||
const downColor = Colors.redAccent;
|
||||
|
||||
Color color;
|
||||
String icon;
|
||||
|
||||
double percentage;
|
||||
|
||||
if (previous > 0) {
|
||||
percentage = (current - previous) * 100.0;
|
||||
} else {
|
||||
percentage = 0.0;
|
||||
}
|
||||
|
||||
final String percentageText = percentage.abs().toStringAsFixed(1).replaceAll('.', I18n.of(context).locale.languageCode != 'en' ? ',' : '.');
|
||||
|
||||
if (!percentage.isNegative) {
|
||||
color = upColor;
|
||||
icon = upIcon;
|
||||
} else {
|
||||
color = downColor;
|
||||
icon = downIcon;
|
||||
}
|
||||
|
||||
if (percentage == 0) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 2.0),
|
||||
child: Text(
|
||||
icon,
|
||||
style: TextStyle(fontSize: 18.0, color: color),
|
||||
),
|
||||
),
|
||||
Text("$percentageText%", style: TextStyle(color: color)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:i18n_extension/i18n_widget.dart';
|
||||
|
||||
class TrendDisplay<T extends num> extends StatelessWidget {
|
||||
const TrendDisplay({Key? key, required this.current, required this.previous, this.padding}) : super(key: key);
|
||||
|
||||
final T current;
|
||||
final T previous;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const upIcon = "▲";
|
||||
const downIcon = "▼";
|
||||
final upColor = Colors.lightGreenAccent.shade700;
|
||||
const downColor = Colors.redAccent;
|
||||
|
||||
Color color;
|
||||
String icon;
|
||||
|
||||
double percentage;
|
||||
|
||||
if (previous > 0) {
|
||||
percentage = (current - previous) * 100.0;
|
||||
} else {
|
||||
percentage = 0.0;
|
||||
}
|
||||
|
||||
final String percentageText = percentage.abs().toStringAsFixed(1).replaceAll('.', I18n.of(context).locale.languageCode != 'en' ? ',' : '.');
|
||||
|
||||
if (!percentage.isNegative) {
|
||||
color = upColor;
|
||||
icon = upIcon;
|
||||
} else {
|
||||
color = downColor;
|
||||
icon = downIcon;
|
||||
}
|
||||
|
||||
if (percentage == 0) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 2.0),
|
||||
child: Text(
|
||||
icon,
|
||||
style: TextStyle(fontSize: 18.0, color: color),
|
||||
),
|
||||
),
|
||||
Text("$percentageText%", style: TextStyle(color: color)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,50 +1,50 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
class AbsenceDisplay extends StatelessWidget {
|
||||
const AbsenceDisplay(this.excused, this.unexcused, this.pending, {Key? key}) : super(key: key);
|
||||
|
||||
final int excused;
|
||||
final int unexcused;
|
||||
final int pending;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 5.0),
|
||||
// padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 6.0),
|
||||
// decoration: BoxDecoration(
|
||||
// color: Theme.of(context).scaffoldBackgroundColor.withOpacity(.2),
|
||||
// borderRadius: BorderRadius.circular(12.0),
|
||||
// ),
|
||||
child: Row(children: [
|
||||
if (excused > 0)
|
||||
Icon(
|
||||
FeatherIcons.check,
|
||||
size: 16.0,
|
||||
color: AppColors.of(context).green,
|
||||
),
|
||||
if (excused > 0) const SizedBox(width: 2.0),
|
||||
if (excused > 0) Text(excused.toString(), style: const TextStyle(fontFamily: "monospace", fontSize: 14.0)),
|
||||
if (excused > 0 && pending > 0) const SizedBox(width: 6.0),
|
||||
if (pending > 0)
|
||||
Icon(
|
||||
FeatherIcons.slash,
|
||||
size: 14.0,
|
||||
color: AppColors.of(context).orange,
|
||||
),
|
||||
if (pending > 0) const SizedBox(width: 3.0),
|
||||
if (pending > 0) Text(pending.toString(), style: const TextStyle(fontFamily: "monospace", fontSize: 14.0)),
|
||||
if (unexcused > 0 && pending > 0) const SizedBox(width: 3.0),
|
||||
if (unexcused > 0)
|
||||
Icon(
|
||||
FeatherIcons.x,
|
||||
size: 18.0,
|
||||
color: AppColors.of(context).red,
|
||||
),
|
||||
if (unexcused > 0) Text(unexcused.toString(), style: const TextStyle(fontFamily: "monospace", fontSize: 14.0)),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
class AbsenceDisplay extends StatelessWidget {
|
||||
const AbsenceDisplay(this.excused, this.unexcused, this.pending, {Key? key}) : super(key: key);
|
||||
|
||||
final int excused;
|
||||
final int unexcused;
|
||||
final int pending;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 5.0),
|
||||
// padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 6.0),
|
||||
// decoration: BoxDecoration(
|
||||
// color: Theme.of(context).scaffoldBackgroundColor.withOpacity(.2),
|
||||
// borderRadius: BorderRadius.circular(12.0),
|
||||
// ),
|
||||
child: Row(children: [
|
||||
if (excused > 0)
|
||||
Icon(
|
||||
FeatherIcons.check,
|
||||
size: 16.0,
|
||||
color: AppColors.of(context).green,
|
||||
),
|
||||
if (excused > 0) const SizedBox(width: 2.0),
|
||||
if (excused > 0) Text(excused.toString(), style: const TextStyle(fontFamily: "monospace", fontSize: 14.0)),
|
||||
if (excused > 0 && pending > 0) const SizedBox(width: 6.0),
|
||||
if (pending > 0)
|
||||
Icon(
|
||||
FeatherIcons.slash,
|
||||
size: 14.0,
|
||||
color: AppColors.of(context).orange,
|
||||
),
|
||||
if (pending > 0) const SizedBox(width: 3.0),
|
||||
if (pending > 0) Text(pending.toString(), style: const TextStyle(fontFamily: "monospace", fontSize: 14.0)),
|
||||
if (unexcused > 0 && pending > 0) const SizedBox(width: 3.0),
|
||||
if (unexcused > 0)
|
||||
Icon(
|
||||
FeatherIcons.x,
|
||||
size: 18.0,
|
||||
color: AppColors.of(context).red,
|
||||
),
|
||||
if (unexcused > 0) Text(unexcused.toString(), style: const TextStyle(fontFamily: "monospace", fontSize: 14.0)),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +1,80 @@
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/subject.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_display.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AbsenceSubjectTile extends StatelessWidget {
|
||||
const AbsenceSubjectTile(this.subject, {Key? key, this.percentage = 0.0, this.excused = 0, this.unexcused = 0, this.pending = 0, this.onTap})
|
||||
: super(key: key);
|
||||
|
||||
final Subject subject;
|
||||
final void Function()? onTap;
|
||||
final double percentage;
|
||||
|
||||
final int excused;
|
||||
final int unexcused;
|
||||
final int pending;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: ListTile(
|
||||
// minLeadingWidth: 32.0,
|
||||
dense: true,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 6.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
visualDensity: VisualDensity.compact,
|
||||
onTap: onTap,
|
||||
leading: Icon(SubjectIcon.resolveVariant(subject: subject, context: context), size: 32.0),
|
||||
title: Text(
|
||||
subject.renamedTo ?? subject.name.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 15.0, fontStyle: subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
subtitle: AbsenceDisplay(excused, unexcused, pending),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(width: 8.0),
|
||||
if (percentage >= 0)
|
||||
Stack(
|
||||
alignment: Alignment.centerRight,
|
||||
children: [
|
||||
const Opacity(child: Text("100%", style: TextStyle(fontFamily: "monospace")), opacity: 0),
|
||||
Text(
|
||||
percentage.round().toString() + "%",
|
||||
style: TextStyle(
|
||||
// fontFamily: "monospace",
|
||||
color: getColorByPercentage(percentage, context: context),
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 24.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Color getColorByPercentage(double percentage, {required BuildContext context}) {
|
||||
Color color = AppColors.of(context).text;
|
||||
|
||||
percentage = percentage.round().toDouble();
|
||||
|
||||
if (percentage > 35) {
|
||||
color = AppColors.of(context).red;
|
||||
} else if (percentage > 25) {
|
||||
color = AppColors.of(context).orange;
|
||||
} else if (percentage > 15) {
|
||||
color = AppColors.of(context).yellow;
|
||||
}
|
||||
|
||||
return color.withOpacity(.8);
|
||||
}
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/subject.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_display.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AbsenceSubjectTile extends StatelessWidget {
|
||||
const AbsenceSubjectTile(this.subject, {Key? key, this.percentage = 0.0, this.excused = 0, this.unexcused = 0, this.pending = 0, this.onTap})
|
||||
: super(key: key);
|
||||
|
||||
final Subject subject;
|
||||
final void Function()? onTap;
|
||||
final double percentage;
|
||||
|
||||
final int excused;
|
||||
final int unexcused;
|
||||
final int pending;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: ListTile(
|
||||
// minLeadingWidth: 32.0,
|
||||
dense: true,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 6.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
visualDensity: VisualDensity.compact,
|
||||
onTap: onTap,
|
||||
leading: Icon(SubjectIcon.resolveVariant(subject: subject, context: context), size: 32.0),
|
||||
title: Text(
|
||||
subject.renamedTo ?? subject.name.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 15.0, fontStyle: subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
subtitle: AbsenceDisplay(excused, unexcused, pending),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(width: 8.0),
|
||||
if (percentage >= 0)
|
||||
Stack(
|
||||
alignment: Alignment.centerRight,
|
||||
children: [
|
||||
const Opacity(child: Text("100%", style: TextStyle(fontFamily: "monospace")), opacity: 0),
|
||||
Text(
|
||||
percentage.round().toString() + "%",
|
||||
style: TextStyle(
|
||||
// fontFamily: "monospace",
|
||||
color: getColorByPercentage(percentage, context: context),
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 24.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Color getColorByPercentage(double percentage, {required BuildContext context}) {
|
||||
Color color = AppColors.of(context).text;
|
||||
|
||||
percentage = percentage.round().toDouble();
|
||||
|
||||
if (percentage > 35) {
|
||||
color = AppColors.of(context).red;
|
||||
} else if (percentage > 25) {
|
||||
color = AppColors.of(context).orange;
|
||||
} else if (percentage > 15) {
|
||||
color = AppColors.of(context).yellow;
|
||||
}
|
||||
|
||||
return color.withOpacity(.8);
|
||||
}
|
||||
|
||||
@@ -1,118 +1,118 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/absence.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence_group/absence_group_container.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'absence_tile.i18n.dart';
|
||||
|
||||
class AbsenceTile extends StatelessWidget {
|
||||
const AbsenceTile(this.absence, {Key? key, this.onTap, this.elevation = 0.0, this.padding}) : super(key: key);
|
||||
|
||||
final Absence absence;
|
||||
final void Function()? onTap;
|
||||
final double elevation;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color color = justificationColor(absence.state, context: context);
|
||||
bool group = AbsenceGroupContainer.of(context) != null;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
if (elevation > 0)
|
||||
BoxShadow(
|
||||
offset: Offset(0, 21 * elevation),
|
||||
blurRadius: 23.0 * elevation,
|
||||
color: Theme.of(context).shadowColor,
|
||||
)
|
||||
],
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Padding(
|
||||
padding: padding ?? (group ? EdgeInsets.zero : const EdgeInsets.symmetric(horizontal: 8.0)),
|
||||
child: ListTile(
|
||||
onTap: onTap,
|
||||
visualDensity: VisualDensity.compact,
|
||||
dense: group,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(!group ? 14.0 : 12.0)),
|
||||
leading: Container(
|
||||
width: 44.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: !group ? color.withOpacity(.25) : null,
|
||||
),
|
||||
child: Center(child: Icon(justificationIcon(absence.state), color: color)),
|
||||
),
|
||||
title: !group
|
||||
? Text.rich(TextSpan(
|
||||
text: "${absence.delay == 0 ? "" : absence.delay}",
|
||||
style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 15.5),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: absence.delay == 0
|
||||
? justificationName(absence.state).fill(["absence".i18n]).capital()
|
||||
: 'minute'.plural(absence.delay) + justificationName(absence.state).fill(["delay".i18n]),
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
))
|
||||
: Text(
|
||||
(absence.lessonIndex != null ? "${absence.lessonIndex}. " : "") + (absence.subject.renamedTo ?? absence.subject.name.capital()),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14.0, fontStyle: absence.subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
subtitle: !group
|
||||
? Text(
|
||||
absence.subject.renamedTo ?? absence.subject.name.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
// DateFormat("MM. dd. (EEEEE)", I18n.of(context).locale.toString()).format(absence.date),
|
||||
style: TextStyle(fontWeight: FontWeight.w500, fontStyle: absence.subject.isRenamed ? FontStyle.italic : null),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static String justificationName(Justification state) {
|
||||
switch (state) {
|
||||
case Justification.excused:
|
||||
return "excused".i18n;
|
||||
case Justification.pending:
|
||||
return "pending".i18n;
|
||||
case Justification.unexcused:
|
||||
return "unexcused".i18n;
|
||||
}
|
||||
}
|
||||
|
||||
static Color justificationColor(Justification state, {required BuildContext context}) {
|
||||
switch (state) {
|
||||
case Justification.excused:
|
||||
return AppColors.of(context).green;
|
||||
case Justification.pending:
|
||||
return AppColors.of(context).orange;
|
||||
case Justification.unexcused:
|
||||
return AppColors.of(context).red;
|
||||
}
|
||||
}
|
||||
|
||||
static IconData justificationIcon(Justification state) {
|
||||
switch (state) {
|
||||
case Justification.excused:
|
||||
return FeatherIcons.check;
|
||||
case Justification.pending:
|
||||
return FeatherIcons.slash;
|
||||
case Justification.unexcused:
|
||||
return FeatherIcons.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/absence.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence_group/absence_group_container.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'absence_tile.i18n.dart';
|
||||
|
||||
class AbsenceTile extends StatelessWidget {
|
||||
const AbsenceTile(this.absence, {Key? key, this.onTap, this.elevation = 0.0, this.padding}) : super(key: key);
|
||||
|
||||
final Absence absence;
|
||||
final void Function()? onTap;
|
||||
final double elevation;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color color = justificationColor(absence.state, context: context);
|
||||
bool group = AbsenceGroupContainer.of(context) != null;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
if (elevation > 0)
|
||||
BoxShadow(
|
||||
offset: Offset(0, 21 * elevation),
|
||||
blurRadius: 23.0 * elevation,
|
||||
color: Theme.of(context).shadowColor,
|
||||
)
|
||||
],
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Padding(
|
||||
padding: padding ?? (group ? EdgeInsets.zero : const EdgeInsets.symmetric(horizontal: 8.0)),
|
||||
child: ListTile(
|
||||
onTap: onTap,
|
||||
visualDensity: VisualDensity.compact,
|
||||
dense: group,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(!group ? 14.0 : 12.0)),
|
||||
leading: Container(
|
||||
width: 44.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: !group ? color.withOpacity(.25) : null,
|
||||
),
|
||||
child: Center(child: Icon(justificationIcon(absence.state), color: color)),
|
||||
),
|
||||
title: !group
|
||||
? Text.rich(TextSpan(
|
||||
text: "${absence.delay == 0 ? "" : absence.delay}",
|
||||
style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 15.5),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: absence.delay == 0
|
||||
? justificationName(absence.state).fill(["absence".i18n]).capital()
|
||||
: 'minute'.plural(absence.delay) + justificationName(absence.state).fill(["delay".i18n]),
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
))
|
||||
: Text(
|
||||
(absence.lessonIndex != null ? "${absence.lessonIndex}. " : "") + (absence.subject.renamedTo ?? absence.subject.name.capital()),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14.0, fontStyle: absence.subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
subtitle: !group
|
||||
? Text(
|
||||
absence.subject.renamedTo ?? absence.subject.name.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
// DateFormat("MM. dd. (EEEEE)", I18n.of(context).locale.toString()).format(absence.date),
|
||||
style: TextStyle(fontWeight: FontWeight.w500, fontStyle: absence.subject.isRenamed ? FontStyle.italic : null),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static String justificationName(Justification state) {
|
||||
switch (state) {
|
||||
case Justification.excused:
|
||||
return "excused".i18n;
|
||||
case Justification.pending:
|
||||
return "pending".i18n;
|
||||
case Justification.unexcused:
|
||||
return "unexcused".i18n;
|
||||
}
|
||||
}
|
||||
|
||||
static Color justificationColor(Justification state, {required BuildContext context}) {
|
||||
switch (state) {
|
||||
case Justification.excused:
|
||||
return AppColors.of(context).green;
|
||||
case Justification.pending:
|
||||
return AppColors.of(context).orange;
|
||||
case Justification.unexcused:
|
||||
return AppColors.of(context).red;
|
||||
}
|
||||
}
|
||||
|
||||
static IconData justificationIcon(Justification state) {
|
||||
switch (state) {
|
||||
case Justification.excused:
|
||||
return FeatherIcons.check;
|
||||
case Justification.pending:
|
||||
return FeatherIcons.slash;
|
||||
case Justification.unexcused:
|
||||
return FeatherIcons.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"excused": "excused %s",
|
||||
"pending": "%s to be excused",
|
||||
"unexcused": "unexcused %s",
|
||||
"absence": "absence",
|
||||
"delay": "delay",
|
||||
"minute": " minutes of ".one(" minute of "),
|
||||
},
|
||||
"hu_hu": {
|
||||
"excused": "igazolt %s",
|
||||
"pending": "igazolandó %s",
|
||||
"unexcused": "igazolatlan %s",
|
||||
"absence": "hiányzás",
|
||||
"delay": "késés",
|
||||
"minute": " perc ",
|
||||
},
|
||||
"de_de": {
|
||||
"excused": "anerkannt %s",
|
||||
"pending": "%s zu anerkennen",
|
||||
"unexcused": "unanerkannt %s",
|
||||
"absence": "Abwesenheit",
|
||||
"delay": "Verspätung",
|
||||
"minute": " Minuten ".one(" Minute "),
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"excused": "excused %s",
|
||||
"pending": "%s to be excused",
|
||||
"unexcused": "unexcused %s",
|
||||
"absence": "absence",
|
||||
"delay": "delay",
|
||||
"minute": " minutes of ".one(" minute of "),
|
||||
},
|
||||
"hu_hu": {
|
||||
"excused": "igazolt %s",
|
||||
"pending": "igazolandó %s",
|
||||
"unexcused": "igazolatlan %s",
|
||||
"absence": "hiányzás",
|
||||
"delay": "késés",
|
||||
"minute": " perc ",
|
||||
},
|
||||
"de_de": {
|
||||
"excused": "anerkannt %s",
|
||||
"pending": "%s zu anerkennen",
|
||||
"unexcused": "unanerkannt %s",
|
||||
"absence": "Abwesenheit",
|
||||
"delay": "Verspätung",
|
||||
"minute": " Minuten ".one(" Minute "),
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,128 +1,128 @@
|
||||
// ignore_for_file: empty_catches
|
||||
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/absence.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_card.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/detail.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel_action_button.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_tile.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/timetable/timetable_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:filcnaplo/utils/reverse_search.dart';
|
||||
import 'absence_view.i18n.dart';
|
||||
|
||||
class AbsenceView extends StatelessWidget {
|
||||
const AbsenceView(this.absence, {Key? key, this.outsideContext, this.viewable = false}) : super(key: key);
|
||||
|
||||
final Absence absence;
|
||||
final BuildContext? outsideContext;
|
||||
final bool viewable;
|
||||
|
||||
static show(Absence absence, {required BuildContext context}) {
|
||||
showBottomCard(context: context, child: AbsenceView(absence, outsideContext: context));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color color = AbsenceTile.justificationColor(absence.state, context: context);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 16.0, right: 12.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
leading: Container(
|
||||
width: 44.0,
|
||||
height: 44.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: color.withOpacity(.25),
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
AbsenceTile.justificationIcon(absence.state),
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
absence.subject.renamedTo ?? absence.subject.name.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w700, fontStyle: absence.subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
subtitle: Text(
|
||||
absence.teacher,
|
||||
// DateFormat("MM. dd. (EEEEE)", I18n.of(context).locale.toString()).format(absence.date),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: Text(
|
||||
absence.date.format(context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
// Absence Details
|
||||
if (absence.delay > 0)
|
||||
Detail(
|
||||
title: "delay".i18n,
|
||||
description: absence.delay.toString() + " " + "minutes".i18n.plural(absence.delay),
|
||||
),
|
||||
if (absence.lessonIndex != null)
|
||||
Detail(
|
||||
title: "Lesson".i18n,
|
||||
description: "${absence.lessonIndex}. (${absence.lessonStart.format(context, timeOnly: true)}"
|
||||
" - "
|
||||
"${absence.lessonEnd.format(context, timeOnly: true)})",
|
||||
),
|
||||
if (absence.justification != null)
|
||||
Detail(
|
||||
title: "Excuse".i18n,
|
||||
description: absence.justification?.description ?? "",
|
||||
),
|
||||
if (absence.mode != null) Detail(title: "Mode".i18n, description: absence.mode?.description ?? ""),
|
||||
Detail(title: "Submit date".i18n, description: absence.submitDate.format(context)),
|
||||
|
||||
// Show in timetable
|
||||
if (!viewable)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 6.0, top: 12.0),
|
||||
child: PanelActionButton(
|
||||
leading: const Icon(FeatherIcons.calendar),
|
||||
title: Text(
|
||||
"show in timetable".i18n,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
if (outsideContext != null) {
|
||||
ReverseSearch.getLessonByAbsence(absence, context).then((lesson) {
|
||||
if (lesson != null) {
|
||||
TimetablePage.jump(outsideContext!, lesson: lesson);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
|
||||
content: Text("Cannot find lesson".i18n, style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: AppColors.of(context).red,
|
||||
context: context,
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// ignore_for_file: empty_catches
|
||||
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/absence.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_card.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/detail.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel_action_button.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_tile.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/timetable/timetable_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:filcnaplo/utils/reverse_search.dart';
|
||||
import 'absence_view.i18n.dart';
|
||||
|
||||
class AbsenceView extends StatelessWidget {
|
||||
const AbsenceView(this.absence, {Key? key, this.outsideContext, this.viewable = false}) : super(key: key);
|
||||
|
||||
final Absence absence;
|
||||
final BuildContext? outsideContext;
|
||||
final bool viewable;
|
||||
|
||||
static show(Absence absence, {required BuildContext context}) {
|
||||
showBottomCard(context: context, child: AbsenceView(absence, outsideContext: context));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color color = AbsenceTile.justificationColor(absence.state, context: context);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 16.0, right: 12.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
leading: Container(
|
||||
width: 44.0,
|
||||
height: 44.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: color.withOpacity(.25),
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
AbsenceTile.justificationIcon(absence.state),
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
absence.subject.renamedTo ?? absence.subject.name.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w700, fontStyle: absence.subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
subtitle: Text(
|
||||
absence.teacher,
|
||||
// DateFormat("MM. dd. (EEEEE)", I18n.of(context).locale.toString()).format(absence.date),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: Text(
|
||||
absence.date.format(context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
// Absence Details
|
||||
if (absence.delay > 0)
|
||||
Detail(
|
||||
title: "delay".i18n,
|
||||
description: absence.delay.toString() + " " + "minutes".i18n.plural(absence.delay),
|
||||
),
|
||||
if (absence.lessonIndex != null)
|
||||
Detail(
|
||||
title: "Lesson".i18n,
|
||||
description: "${absence.lessonIndex}. (${absence.lessonStart.format(context, timeOnly: true)}"
|
||||
" - "
|
||||
"${absence.lessonEnd.format(context, timeOnly: true)})",
|
||||
),
|
||||
if (absence.justification != null)
|
||||
Detail(
|
||||
title: "Excuse".i18n,
|
||||
description: absence.justification?.description ?? "",
|
||||
),
|
||||
if (absence.mode != null) Detail(title: "Mode".i18n, description: absence.mode?.description ?? ""),
|
||||
Detail(title: "Submit date".i18n, description: absence.submitDate.format(context)),
|
||||
|
||||
// Show in timetable
|
||||
if (!viewable)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 6.0, top: 12.0),
|
||||
child: PanelActionButton(
|
||||
leading: const Icon(FeatherIcons.calendar),
|
||||
title: Text(
|
||||
"show in timetable".i18n,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
if (outsideContext != null) {
|
||||
ReverseSearch.getLessonByAbsence(absence, context).then((lesson) {
|
||||
if (lesson != null) {
|
||||
TimetablePage.jump(outsideContext!, lesson: lesson);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
|
||||
content: Text("Cannot find lesson".i18n, style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: AppColors.of(context).red,
|
||||
context: context,
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Lesson": "Lesson",
|
||||
"Excuse": "Excuse",
|
||||
"Mode": "Mode",
|
||||
"Submit date": "Submit Date",
|
||||
"show in timetable": "Show in timetable",
|
||||
"minutes": "minutes".one("minute"),
|
||||
"delay": "Delay",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Lesson": "Óra",
|
||||
"Excuse": "Igazolás",
|
||||
"Mode": "Típus",
|
||||
"Submit date": "Rögzítés dátuma",
|
||||
"show in timetable": "Megtekintés az órarendben",
|
||||
"minutes": "perc",
|
||||
"delay": "Késés",
|
||||
},
|
||||
"de_de": {
|
||||
"Lesson": "Stunde",
|
||||
"Excuse": "Anerkannt",
|
||||
"Mode": "Typ",
|
||||
"Submit date": "Datum einreichen",
|
||||
"show in timetable": "im Stundenplan anzeigen",
|
||||
"minutes": "Minuten".one("Minute"),
|
||||
"delay": "Verspätung",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Lesson": "Lesson",
|
||||
"Excuse": "Excuse",
|
||||
"Mode": "Mode",
|
||||
"Submit date": "Submit Date",
|
||||
"show in timetable": "Show in timetable",
|
||||
"minutes": "minutes".one("minute"),
|
||||
"delay": "Delay",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Lesson": "Óra",
|
||||
"Excuse": "Igazolás",
|
||||
"Mode": "Típus",
|
||||
"Submit date": "Rögzítés dátuma",
|
||||
"show in timetable": "Megtekintés az órarendben",
|
||||
"minutes": "perc",
|
||||
"delay": "Késés",
|
||||
},
|
||||
"de_de": {
|
||||
"Lesson": "Stunde",
|
||||
"Excuse": "Anerkannt",
|
||||
"Mode": "Typ",
|
||||
"Submit date": "Datum einreichen",
|
||||
"show in timetable": "im Stundenplan anzeigen",
|
||||
"minutes": "Minuten".one("Minute"),
|
||||
"delay": "Verspätung",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,68 +1,68 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/absence.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_view.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence_group/absence_group_container.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/card_handle.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/absences/absence_subject_view_container.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/timetable/timetable_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/reverse_search.dart';
|
||||
|
||||
import 'absence_view.i18n.dart';
|
||||
|
||||
class AbsenceViewable extends StatelessWidget {
|
||||
const AbsenceViewable(this.absence, {Key? key, this.padding}) : super(key: key);
|
||||
|
||||
final Absence absence;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final subject = AbsenceSubjectViewContainer.of(context) != null;
|
||||
final group = AbsenceGroupContainer.of(context) != null;
|
||||
final tile = AbsenceTile(absence, padding: padding);
|
||||
|
||||
return Viewable(
|
||||
tile: group ? AbsenceGroupContainer(child: tile) : tile,
|
||||
view: CardHandle(child: AbsenceView(absence, viewable: true)),
|
||||
actions: [
|
||||
PanelButton(
|
||||
background: true,
|
||||
title: Text(
|
||||
"show in timetable".i18n,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
|
||||
if (subject) {
|
||||
Future.delayed(const Duration(milliseconds: 250)).then((_) {
|
||||
Navigator.of(context, rootNavigator: true).pop(absence);
|
||||
});
|
||||
} else {
|
||||
Future.delayed(const Duration(milliseconds: 250)).then((_) {
|
||||
ReverseSearch.getLessonByAbsence(absence, context).then((lesson) {
|
||||
if (lesson != null) {
|
||||
TimetablePage.jump(context, lesson: lesson);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
|
||||
content: Text("Cannot find lesson".i18n, style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: AppColors.of(context).red,
|
||||
context: context,
|
||||
));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/absence.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_view.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence_group/absence_group_container.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/card_handle.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/absences/absence_subject_view_container.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/timetable/timetable_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/reverse_search.dart';
|
||||
|
||||
import 'absence_view.i18n.dart';
|
||||
|
||||
class AbsenceViewable extends StatelessWidget {
|
||||
const AbsenceViewable(this.absence, {Key? key, this.padding}) : super(key: key);
|
||||
|
||||
final Absence absence;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final subject = AbsenceSubjectViewContainer.of(context) != null;
|
||||
final group = AbsenceGroupContainer.of(context) != null;
|
||||
final tile = AbsenceTile(absence, padding: padding);
|
||||
|
||||
return Viewable(
|
||||
tile: group ? AbsenceGroupContainer(child: tile) : tile,
|
||||
view: CardHandle(child: AbsenceView(absence, viewable: true)),
|
||||
actions: [
|
||||
PanelButton(
|
||||
background: true,
|
||||
title: Text(
|
||||
"show in timetable".i18n,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
|
||||
if (subject) {
|
||||
Future.delayed(const Duration(milliseconds: 250)).then((_) {
|
||||
Navigator.of(context, rootNavigator: true).pop(absence);
|
||||
});
|
||||
} else {
|
||||
Future.delayed(const Duration(milliseconds: 250)).then((_) {
|
||||
ReverseSearch.getLessonByAbsence(absence, context).then((lesson) {
|
||||
if (lesson != null) {
|
||||
TimetablePage.jump(context, lesson: lesson);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
|
||||
content: Text("Cannot find lesson".i18n, style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: AppColors.of(context).red,
|
||||
context: context,
|
||||
));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AbsenceGroupContainer extends InheritedWidget {
|
||||
const AbsenceGroupContainer({Key? key, required Widget child}) : super(key: key, child: child);
|
||||
|
||||
static AbsenceGroupContainer? of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<AbsenceGroupContainer>();
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(AbsenceGroupContainer oldWidget) => false;
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AbsenceGroupContainer extends InheritedWidget {
|
||||
const AbsenceGroupContainer({Key? key, required Widget child}) : super(key: key, child: child);
|
||||
|
||||
static AbsenceGroupContainer? of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<AbsenceGroupContainer>();
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(AbsenceGroupContainer oldWidget) => false;
|
||||
}
|
||||
|
||||
@@ -1,80 +1,80 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/absence.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence_group/absence_group_container.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_tile.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'absence_group_tile.i18n.dart';
|
||||
|
||||
class AbsenceGroupTile extends StatelessWidget {
|
||||
const AbsenceGroupTile(this.absences, {Key? key, this.showDate = false, this.padding}) : super(key: key);
|
||||
|
||||
final List<AbsenceViewable> absences;
|
||||
final bool showDate;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Justification state = getState(absences.map((e) => e.absence.state).toList());
|
||||
Color color = AbsenceTile.justificationColor(state, context: context);
|
||||
|
||||
absences.sort((a, b) => a.absence.lessonIndex?.compareTo(b.absence.lessonIndex ?? 0) ?? -1);
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: AbsenceGroupContainer(
|
||||
child: ExpansionTile(
|
||||
tilePadding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
backgroundColor: Colors.transparent,
|
||||
leading: Container(
|
||||
width: 44.0,
|
||||
height: 44.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: color.withOpacity(.25),
|
||||
),
|
||||
child: Center(child: Icon(AbsenceTile.justificationIcon(state), color: color)),
|
||||
),
|
||||
title: Text.rich(TextSpan(
|
||||
text: "${absences.where((a) => a.absence.state == state).length} ",
|
||||
style: TextStyle(fontWeight: FontWeight.w700, color: AppColors.of(context).text),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: AbsenceTile.justificationName(state).fill(["absence".i18n]),
|
||||
style: TextStyle(fontWeight: FontWeight.w600, color: AppColors.of(context).text),
|
||||
),
|
||||
],
|
||||
)),
|
||||
subtitle: showDate
|
||||
? Text(
|
||||
absences.first.absence.date.format(context, weekday: true),
|
||||
style: TextStyle(fontWeight: FontWeight.w500, color: AppColors.of(context).text.withOpacity(0.8)),
|
||||
)
|
||||
: null,
|
||||
children: absences,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Justification getState(List<Justification> states) {
|
||||
Justification state;
|
||||
|
||||
if (states.any((element) => element == Justification.unexcused)) {
|
||||
state = Justification.unexcused;
|
||||
} else if (states.any((element) => element == Justification.pending)) {
|
||||
state = Justification.pending;
|
||||
} else {
|
||||
state = Justification.excused;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/absence.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence_group/absence_group_container.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_tile.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'absence_group_tile.i18n.dart';
|
||||
|
||||
class AbsenceGroupTile extends StatelessWidget {
|
||||
const AbsenceGroupTile(this.absences, {Key? key, this.showDate = false, this.padding}) : super(key: key);
|
||||
|
||||
final List<AbsenceViewable> absences;
|
||||
final bool showDate;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Justification state = getState(absences.map((e) => e.absence.state).toList());
|
||||
Color color = AbsenceTile.justificationColor(state, context: context);
|
||||
|
||||
absences.sort((a, b) => a.absence.lessonIndex?.compareTo(b.absence.lessonIndex ?? 0) ?? -1);
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: AbsenceGroupContainer(
|
||||
child: ExpansionTile(
|
||||
tilePadding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
backgroundColor: Colors.transparent,
|
||||
leading: Container(
|
||||
width: 44.0,
|
||||
height: 44.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: color.withOpacity(.25),
|
||||
),
|
||||
child: Center(child: Icon(AbsenceTile.justificationIcon(state), color: color)),
|
||||
),
|
||||
title: Text.rich(TextSpan(
|
||||
text: "${absences.where((a) => a.absence.state == state).length} ",
|
||||
style: TextStyle(fontWeight: FontWeight.w700, color: AppColors.of(context).text),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: AbsenceTile.justificationName(state).fill(["absence".i18n]),
|
||||
style: TextStyle(fontWeight: FontWeight.w600, color: AppColors.of(context).text),
|
||||
),
|
||||
],
|
||||
)),
|
||||
subtitle: showDate
|
||||
? Text(
|
||||
absences.first.absence.date.format(context, weekday: true),
|
||||
style: TextStyle(fontWeight: FontWeight.w500, color: AppColors.of(context).text.withOpacity(0.8)),
|
||||
)
|
||||
: null,
|
||||
children: absences,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Justification getState(List<Justification> states) {
|
||||
Justification state;
|
||||
|
||||
if (states.any((element) => element == Justification.unexcused)) {
|
||||
state = Justification.unexcused;
|
||||
} else if (states.any((element) => element == Justification.pending)) {
|
||||
state = Justification.pending;
|
||||
} else {
|
||||
state = Justification.excused;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"absence": "absences",
|
||||
},
|
||||
"hu_hu": {
|
||||
"absence": "hiányzás",
|
||||
},
|
||||
"de_de": {
|
||||
"absence": "Fehlen",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"absence": "absences",
|
||||
},
|
||||
"hu_hu": {
|
||||
"absence": "hiányzás",
|
||||
},
|
||||
"de_de": {
|
||||
"absence": "Fehlen",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CardHandle extends StatelessWidget {
|
||||
const CardHandle({Key? key, this.child}) : super(key: key);
|
||||
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 42.0,
|
||||
height: 4.0,
|
||||
margin: const EdgeInsets.only(top: 12.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
color: AppColors.of(context).text.withOpacity(0.10),
|
||||
),
|
||||
),
|
||||
if (child != null) child!,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CardHandle extends StatelessWidget {
|
||||
const CardHandle({Key? key, this.child}) : super(key: key);
|
||||
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 42.0,
|
||||
height: 4.0,
|
||||
margin: const EdgeInsets.only(top: 12.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
color: AppColors.of(context).text.withOpacity(0.10),
|
||||
),
|
||||
),
|
||||
if (child != null) child!,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,108 +1,108 @@
|
||||
import 'package:filcnaplo/helpers/average_helper.dart';
|
||||
import 'package:i18n_extension/i18n_widget.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/cretification/certification_view.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'certification_card.i18n.dart';
|
||||
|
||||
class CertificationCard extends StatelessWidget {
|
||||
const CertificationCard(this.grades, {Key? key, required this.gradeType, this.padding}) : super(key: key);
|
||||
|
||||
final List<Grade> grades;
|
||||
final GradeType gradeType;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String title = getGradeTypeTitle(gradeType);
|
||||
double average = AverageHelper.averageEvals(grades, finalAvg: true);
|
||||
String averageText = average.toStringAsFixed(1);
|
||||
if (I18n.of(context).locale.languageCode != "en") averageText = averageText.replaceAll(".", ",");
|
||||
Color color = gradeColor(context: context, value: average);
|
||||
Color textColor;
|
||||
|
||||
if (color.computeLuminance() >= .5) {
|
||||
textColor = Colors.black;
|
||||
} else {
|
||||
textColor = Colors.white;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(vertical: 2.0, horizontal: 8.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
gradient: LinearGradient(
|
||||
colors: [color, color.withOpacity(.75)],
|
||||
),
|
||||
),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
|
||||
leading: Text(
|
||||
averageText,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 24.0,
|
||||
),
|
||||
),
|
||||
title: Text.rich(
|
||||
TextSpan(
|
||||
text: title,
|
||||
children: [
|
||||
TextSpan(
|
||||
text: " • ${grades.length}",
|
||||
style: TextStyle(
|
||||
color: textColor.withOpacity(.75),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 18.0,
|
||||
),
|
||||
),
|
||||
trailing: Icon(FeatherIcons.arrowRight, color: textColor),
|
||||
onTap: () => CertificationView.show(grades, context: context, gradeType: gradeType),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String getGradeTypeTitle(GradeType gradeType) {
|
||||
String title;
|
||||
|
||||
switch (gradeType) {
|
||||
case GradeType.halfYear:
|
||||
title = "mid".i18n;
|
||||
break;
|
||||
case GradeType.firstQ:
|
||||
title = "1q".i18n;
|
||||
break;
|
||||
case GradeType.secondQ:
|
||||
title = "2q".i18n;
|
||||
break;
|
||||
case GradeType.thirdQ:
|
||||
title = "3q".i18n;
|
||||
break;
|
||||
case GradeType.fourthQ:
|
||||
title = "4q".i18n;
|
||||
break;
|
||||
default:
|
||||
title = "final".i18n;
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
import 'package:filcnaplo/helpers/average_helper.dart';
|
||||
import 'package:i18n_extension/i18n_widget.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/cretification/certification_view.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'certification_card.i18n.dart';
|
||||
|
||||
class CertificationCard extends StatelessWidget {
|
||||
const CertificationCard(this.grades, {Key? key, required this.gradeType, this.padding}) : super(key: key);
|
||||
|
||||
final List<Grade> grades;
|
||||
final GradeType gradeType;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String title = getGradeTypeTitle(gradeType);
|
||||
double average = AverageHelper.averageEvals(grades, finalAvg: true);
|
||||
String averageText = average.toStringAsFixed(1);
|
||||
if (I18n.of(context).locale.languageCode != "en") averageText = averageText.replaceAll(".", ",");
|
||||
Color color = gradeColor(context: context, value: average);
|
||||
Color textColor;
|
||||
|
||||
if (color.computeLuminance() >= .5) {
|
||||
textColor = Colors.black;
|
||||
} else {
|
||||
textColor = Colors.white;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(vertical: 2.0, horizontal: 8.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
gradient: LinearGradient(
|
||||
colors: [color, color.withOpacity(.75)],
|
||||
),
|
||||
),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
|
||||
leading: Text(
|
||||
averageText,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 24.0,
|
||||
),
|
||||
),
|
||||
title: Text.rich(
|
||||
TextSpan(
|
||||
text: title,
|
||||
children: [
|
||||
TextSpan(
|
||||
text: " • ${grades.length}",
|
||||
style: TextStyle(
|
||||
color: textColor.withOpacity(.75),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 18.0,
|
||||
),
|
||||
),
|
||||
trailing: Icon(FeatherIcons.arrowRight, color: textColor),
|
||||
onTap: () => CertificationView.show(grades, context: context, gradeType: gradeType),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String getGradeTypeTitle(GradeType gradeType) {
|
||||
String title;
|
||||
|
||||
switch (gradeType) {
|
||||
case GradeType.halfYear:
|
||||
title = "mid".i18n;
|
||||
break;
|
||||
case GradeType.firstQ:
|
||||
title = "1q".i18n;
|
||||
break;
|
||||
case GradeType.secondQ:
|
||||
title = "2q".i18n;
|
||||
break;
|
||||
case GradeType.thirdQ:
|
||||
title = "3q".i18n;
|
||||
break;
|
||||
case GradeType.fourthQ:
|
||||
title = "4q".i18n;
|
||||
break;
|
||||
default:
|
||||
title = "final".i18n;
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"final": "Final grades",
|
||||
"mid": "Midterm grades",
|
||||
"1q": "1. Quarter grades",
|
||||
"2q": "2. Quarter grades",
|
||||
"3q": "3. Quarter grades",
|
||||
"4q": "4. Quarter grades",
|
||||
},
|
||||
"hu_hu": {
|
||||
"final": "Év végi jegyek",
|
||||
"mid": "Félévi jegyek",
|
||||
"1q": "1. Negyedéves jegyek",
|
||||
"2q": "2. Negyedéves jegyek",
|
||||
"3q": "3. Negyedéves jegyek",
|
||||
"4q": "4. Negyedéves jegyek",
|
||||
},
|
||||
"de_de": {
|
||||
"final": "Zeugnis Noten",
|
||||
"mid": "Halbjährlich Noten",
|
||||
"1q": "1. Quartal Noten",
|
||||
"2q": "2. Quartal Noten",
|
||||
"3q": "3. Quartal Noten",
|
||||
"4q": "4. Quartal Noten",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"final": "Final grades",
|
||||
"mid": "Midterm grades",
|
||||
"1q": "1. Quarter grades",
|
||||
"2q": "2. Quarter grades",
|
||||
"3q": "3. Quarter grades",
|
||||
"4q": "4. Quarter grades",
|
||||
},
|
||||
"hu_hu": {
|
||||
"final": "Év végi jegyek",
|
||||
"mid": "Félévi jegyek",
|
||||
"1q": "1. Negyedéves jegyek",
|
||||
"2q": "2. Negyedéves jegyek",
|
||||
"3q": "3. Negyedéves jegyek",
|
||||
"4q": "4. Negyedéves jegyek",
|
||||
},
|
||||
"de_de": {
|
||||
"final": "Zeugnis Noten",
|
||||
"mid": "Halbjährlich Noten",
|
||||
"1q": "1. Quartal Noten",
|
||||
"2q": "2. Quartal Noten",
|
||||
"3q": "3. Quartal Noten",
|
||||
"4q": "4. Quartal Noten",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,87 +1,87 @@
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/subject_grades_container.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'certification_tile.i18n.dart';
|
||||
|
||||
class CertificationTile extends StatelessWidget {
|
||||
const CertificationTile(this.grade, {Key? key, this.onTap, this.padding}) : super(key: key);
|
||||
|
||||
final Function()? onTap;
|
||||
final Grade grade;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isSubjectView = SubjectGradesContainer.of(context) != null;
|
||||
String certificationName;
|
||||
|
||||
switch (grade.type) {
|
||||
case GradeType.endYear:
|
||||
certificationName = "final".i18n;
|
||||
break;
|
||||
case GradeType.halfYear:
|
||||
certificationName = "mid".i18n;
|
||||
break;
|
||||
case GradeType.firstQ:
|
||||
certificationName = "1q".i18n;
|
||||
break;
|
||||
case GradeType.secondQ:
|
||||
certificationName = "2q".i18n;
|
||||
break;
|
||||
case GradeType.thirdQ:
|
||||
certificationName = "3q".i18n;
|
||||
break;
|
||||
case GradeType.fourthQ:
|
||||
certificationName = "4q".i18n;
|
||||
break;
|
||||
case GradeType.levelExam:
|
||||
certificationName = "equivalency".i18n;
|
||||
break;
|
||||
case GradeType.unknown:
|
||||
default:
|
||||
certificationName = "unknown".i18n;
|
||||
}
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding:
|
||||
isSubjectView ? const EdgeInsets.only(left: 12.0, right: 12.0, top: 2.0, bottom: 8.0) : const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
onTap: onTap,
|
||||
leading: isSubjectView
|
||||
? GradeValueWidget(
|
||||
grade.value,
|
||||
complemented: grade.description == 'Dicséret',
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(left: 2.0),
|
||||
child: Icon(SubjectIcon.resolveVariant(subject: grade.subject, context: context),
|
||||
size: 28.0, color: AppColors.of(context).text.withOpacity(.75)),
|
||||
),
|
||||
minLeadingWidth: isSubjectView ? 32.0 : 42.0,
|
||||
trailing: isSubjectView
|
||||
? const Icon(FeatherIcons.award)
|
||||
: GradeValueWidget(
|
||||
grade.value,
|
||||
complemented: grade.description == 'Dicséret',
|
||||
),
|
||||
title: Text(isSubjectView ? certificationName : grade.subject.renamedTo ?? grade.subject.name.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 18.0, fontStyle: grade.subject.isRenamed ? FontStyle.italic : null)),
|
||||
subtitle: Text(grade.value.valueName, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16.0)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/subject_grades_container.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'certification_tile.i18n.dart';
|
||||
|
||||
class CertificationTile extends StatelessWidget {
|
||||
const CertificationTile(this.grade, {Key? key, this.onTap, this.padding}) : super(key: key);
|
||||
|
||||
final Function()? onTap;
|
||||
final Grade grade;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isSubjectView = SubjectGradesContainer.of(context) != null;
|
||||
String certificationName;
|
||||
|
||||
switch (grade.type) {
|
||||
case GradeType.endYear:
|
||||
certificationName = "final".i18n;
|
||||
break;
|
||||
case GradeType.halfYear:
|
||||
certificationName = "mid".i18n;
|
||||
break;
|
||||
case GradeType.firstQ:
|
||||
certificationName = "1q".i18n;
|
||||
break;
|
||||
case GradeType.secondQ:
|
||||
certificationName = "2q".i18n;
|
||||
break;
|
||||
case GradeType.thirdQ:
|
||||
certificationName = "3q".i18n;
|
||||
break;
|
||||
case GradeType.fourthQ:
|
||||
certificationName = "4q".i18n;
|
||||
break;
|
||||
case GradeType.levelExam:
|
||||
certificationName = "equivalency".i18n;
|
||||
break;
|
||||
case GradeType.unknown:
|
||||
default:
|
||||
certificationName = "unknown".i18n;
|
||||
}
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding:
|
||||
isSubjectView ? const EdgeInsets.only(left: 12.0, right: 12.0, top: 2.0, bottom: 8.0) : const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
onTap: onTap,
|
||||
leading: isSubjectView
|
||||
? GradeValueWidget(
|
||||
grade.value,
|
||||
complemented: grade.description == 'Dicséret',
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(left: 2.0),
|
||||
child: Icon(SubjectIcon.resolveVariant(subject: grade.subject, context: context),
|
||||
size: 28.0, color: AppColors.of(context).text.withOpacity(.75)),
|
||||
),
|
||||
minLeadingWidth: isSubjectView ? 32.0 : 42.0,
|
||||
trailing: isSubjectView
|
||||
? const Icon(FeatherIcons.award)
|
||||
: GradeValueWidget(
|
||||
grade.value,
|
||||
complemented: grade.description == 'Dicséret',
|
||||
),
|
||||
title: Text(isSubjectView ? certificationName : grade.subject.renamedTo ?? grade.subject.name.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 18.0, fontStyle: grade.subject.isRenamed ? FontStyle.italic : null)),
|
||||
subtitle: Text(grade.value.valueName, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16.0)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"final": "Final",
|
||||
"mid": "Mid year",
|
||||
"1q": "1. Quarter",
|
||||
"2q": "2. Quarter",
|
||||
"3q": "3. Quarter",
|
||||
"4q": "4. Quarter",
|
||||
"equivalency": "Equivalency test",
|
||||
"unknown": "Unknown",
|
||||
"classavg": "Class Average",
|
||||
},
|
||||
"hu_hu": {
|
||||
"final": "Év vége",
|
||||
"mid": "Félév",
|
||||
"1q": "1. Negyedév",
|
||||
"2q": "2. Negyedév",
|
||||
"3q": "3. Negyedév",
|
||||
"4q": "4. Negyedév",
|
||||
"equivalency": "Osztályozó",
|
||||
"unknown": "Ismeretlen",
|
||||
"classavg": "Osztályátlag",
|
||||
},
|
||||
"de_de": {
|
||||
"final": "Zeugnis",
|
||||
"mid": "Halbjährlich",
|
||||
"1q": "1. Quartal",
|
||||
"2q": "2. Quartal",
|
||||
"3q": "3. Quartal",
|
||||
"4q": "4. Quartal",
|
||||
"equivalency": "Zulassungsprüfung",
|
||||
"unknown": "Unbekannt",
|
||||
"classavg": "Klassendurchschnitt",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"final": "Final",
|
||||
"mid": "Mid year",
|
||||
"1q": "1. Quarter",
|
||||
"2q": "2. Quarter",
|
||||
"3q": "3. Quarter",
|
||||
"4q": "4. Quarter",
|
||||
"equivalency": "Equivalency test",
|
||||
"unknown": "Unknown",
|
||||
"classavg": "Class Average",
|
||||
},
|
||||
"hu_hu": {
|
||||
"final": "Év vége",
|
||||
"mid": "Félév",
|
||||
"1q": "1. Negyedév",
|
||||
"2q": "2. Negyedév",
|
||||
"3q": "3. Negyedév",
|
||||
"4q": "4. Negyedév",
|
||||
"equivalency": "Osztályozó",
|
||||
"unknown": "Ismeretlen",
|
||||
"classavg": "Osztályátlag",
|
||||
},
|
||||
"de_de": {
|
||||
"final": "Zeugnis",
|
||||
"mid": "Halbjährlich",
|
||||
"1q": "1. Quartal",
|
||||
"2q": "2. Quartal",
|
||||
"3q": "3. Quartal",
|
||||
"4q": "4. Quartal",
|
||||
"equivalency": "Zulassungsprüfung",
|
||||
"unknown": "Unbekannt",
|
||||
"classavg": "Klassendurchschnitt",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/cretification/certification_card.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/cretification/certification_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/hero_scrollview.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
class CertificationView extends StatelessWidget {
|
||||
const CertificationView(this.grades, {Key? key, required this.gradeType}) : super(key: key);
|
||||
|
||||
final List<Grade> grades;
|
||||
final GradeType gradeType;
|
||||
|
||||
static show(List<Grade> grades, {required BuildContext context, required GradeType gradeType}) =>
|
||||
Navigator.of(context, rootNavigator: true).push(CupertinoPageRoute(builder: (context) => CertificationView(grades, gradeType: gradeType)));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
grades.sort((a, b) => a.subject.name.compareTo(b.subject.name));
|
||||
List<Widget> tiles = grades.map((e) => CertificationTile(e)).toList();
|
||||
return Scaffold(
|
||||
body: HeroScrollView(
|
||||
title: getGradeTypeTitle(gradeType),
|
||||
icon: FeatherIcons.award,
|
||||
iconSize: 50,
|
||||
child: ListView(
|
||||
children: [
|
||||
SafeArea(
|
||||
child: Panel(
|
||||
child: Column(
|
||||
children: tiles,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/cretification/certification_card.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/cretification/certification_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/hero_scrollview.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
class CertificationView extends StatelessWidget {
|
||||
const CertificationView(this.grades, {Key? key, required this.gradeType}) : super(key: key);
|
||||
|
||||
final List<Grade> grades;
|
||||
final GradeType gradeType;
|
||||
|
||||
static show(List<Grade> grades, {required BuildContext context, required GradeType gradeType}) =>
|
||||
Navigator.of(context, rootNavigator: true).push(CupertinoPageRoute(builder: (context) => CertificationView(grades, gradeType: gradeType)));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
grades.sort((a, b) => a.subject.name.compareTo(b.subject.name));
|
||||
List<Widget> tiles = grades.map((e) => CertificationTile(e)).toList();
|
||||
return Scaffold(
|
||||
body: HeroScrollView(
|
||||
title: getGradeTypeTitle(gradeType),
|
||||
icon: FeatherIcons.award,
|
||||
iconSize: 50,
|
||||
child: ListView(
|
||||
children: [
|
||||
SafeArea(
|
||||
child: Panel(
|
||||
child: Column(
|
||||
children: tiles,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomSwitch extends StatelessWidget {
|
||||
final ValueChanged<bool> onChanged;
|
||||
final bool value;
|
||||
|
||||
const CustomSwitch({
|
||||
Key? key,
|
||||
required this.onChanged,
|
||||
required this.value,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => onChanged(!value),
|
||||
child: SizedBox(
|
||||
height: 25,
|
||||
width: 50,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
AnimatedContainer(
|
||||
height: 25,
|
||||
width: 50,
|
||||
curve: Curves.ease,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(25.0),
|
||||
),
|
||||
color: value ? Theme.of(context).colorScheme.secondary : Theme.of(context).highlightColor,
|
||||
),
|
||||
),
|
||||
AnimatedAlign(
|
||||
curve: Curves.ease,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
alignment: !value ? Alignment.centerLeft : Alignment.centerRight,
|
||||
child: Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black12.withOpacity(0.1),
|
||||
spreadRadius: 0.5,
|
||||
blurRadius: 1,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomSwitch extends StatelessWidget {
|
||||
final ValueChanged<bool> onChanged;
|
||||
final bool value;
|
||||
|
||||
const CustomSwitch({
|
||||
Key? key,
|
||||
required this.onChanged,
|
||||
required this.value,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => onChanged(!value),
|
||||
child: SizedBox(
|
||||
height: 25,
|
||||
width: 50,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
AnimatedContainer(
|
||||
height: 25,
|
||||
width: 50,
|
||||
curve: Curves.ease,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(25.0),
|
||||
),
|
||||
color: value ? Theme.of(context).colorScheme.secondary : Theme.of(context).highlightColor,
|
||||
),
|
||||
),
|
||||
AnimatedAlign(
|
||||
curve: Curves.ease,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
alignment: !value ? Alignment.centerLeft : Alignment.centerRight,
|
||||
child: Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black12.withOpacity(0.1),
|
||||
spreadRadius: 0.5,
|
||||
blurRadius: 1,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
import 'package:filcnaplo_kreta_api/models/event.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EventTile extends StatelessWidget {
|
||||
const EventTile(this.event, {Key? key, this.onTap, this.padding}) : super(key: key);
|
||||
|
||||
final Event event;
|
||||
final void Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
onTap: onTap,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
|
||||
leading: const ProfileImage(
|
||||
name: "!",
|
||||
radius: 22.0,
|
||||
),
|
||||
title: Text(
|
||||
event.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
event.content.escapeHtml().replaceAll('\n', ' '),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/event.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EventTile extends StatelessWidget {
|
||||
const EventTile(this.event, {Key? key, this.onTap, this.padding}) : super(key: key);
|
||||
|
||||
final Event event;
|
||||
final void Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
onTap: onTap,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
|
||||
leading: const ProfileImage(
|
||||
name: "!",
|
||||
radius: 22.0,
|
||||
),
|
||||
title: Text(
|
||||
event.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
event.content.escapeHtml().replaceAll('\n', ' '),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
import 'package:filcnaplo_kreta_api/models/event.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/sliding_bottom_sheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
|
||||
class EventView extends StatelessWidget {
|
||||
const EventView(this.event, {Key? key}) : super(key: key);
|
||||
|
||||
final Event event;
|
||||
|
||||
static void show(Event event, {required BuildContext context}) => showSlidingBottomSheet(context: context, child: EventView(event));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header
|
||||
ListTile(
|
||||
title: Text(
|
||||
event.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
trailing: Text(
|
||||
event.start.format(context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
// Details
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SelectableLinkify(
|
||||
text: event.content.escapeHtml(),
|
||||
options: const LinkifyOptions(looseUrl: true, removeWww: true),
|
||||
onOpen: (link) {
|
||||
launch(link.url,
|
||||
customTabsOption: CustomTabsOption(
|
||||
toolbarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
showPageTitle: true,
|
||||
));
|
||||
},
|
||||
style: const TextStyle(fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/event.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/sliding_bottom_sheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
|
||||
class EventView extends StatelessWidget {
|
||||
const EventView(this.event, {Key? key}) : super(key: key);
|
||||
|
||||
final Event event;
|
||||
|
||||
static void show(Event event, {required BuildContext context}) => showSlidingBottomSheet(context: context, child: EventView(event));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header
|
||||
ListTile(
|
||||
title: Text(
|
||||
event.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
trailing: Text(
|
||||
event.start.format(context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
// Details
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SelectableLinkify(
|
||||
text: event.content.escapeHtml(),
|
||||
options: const LinkifyOptions(looseUrl: true, removeWww: true),
|
||||
onOpen: (link) {
|
||||
launch(link.url,
|
||||
customTabsOption: CustomTabsOption(
|
||||
toolbarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
showPageTitle: true,
|
||||
));
|
||||
},
|
||||
style: const TextStyle(fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import 'package:filcnaplo_kreta_api/models/event.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/event/event_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/event/event_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EventViewable extends StatelessWidget {
|
||||
const EventViewable(this.event, {Key? key}) : super(key: key);
|
||||
|
||||
final Event event;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EventTile(
|
||||
event,
|
||||
onTap: () => EventView.show(event, context: context),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/event.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/event/event_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/event/event_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EventViewable extends StatelessWidget {
|
||||
const EventViewable(this.event, {Key? key}) : super(key: key);
|
||||
|
||||
final Event event;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EventTile(
|
||||
event,
|
||||
onTap: () => EventView.show(event, context: context),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/exam.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
class ExamTile extends StatelessWidget {
|
||||
const ExamTile(this.exam, {Key? key, this.onTap, this.padding}) : super(key: key);
|
||||
|
||||
final Exam exam;
|
||||
final void Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
onTap: onTap,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
|
||||
leading: SizedBox(
|
||||
width: 44,
|
||||
height: 44,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 2.0),
|
||||
child: Icon(
|
||||
SubjectIcon.resolveVariant(subjectName: exam.subjectName, context: context),
|
||||
size: 28.0,
|
||||
color: AppColors.of(context).text.withOpacity(.75),
|
||||
),
|
||||
)),
|
||||
title: Text(
|
||||
exam.description != "" ? exam.description : (exam.mode?.description ?? "Számonkérés"),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
exam.subjectName.capital(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: Icon(
|
||||
FeatherIcons.edit,
|
||||
color: AppColors.of(context).text.withOpacity(.75),
|
||||
),
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/exam.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
class ExamTile extends StatelessWidget {
|
||||
const ExamTile(this.exam, {Key? key, this.onTap, this.padding}) : super(key: key);
|
||||
|
||||
final Exam exam;
|
||||
final void Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
onTap: onTap,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
|
||||
leading: SizedBox(
|
||||
width: 44,
|
||||
height: 44,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 2.0),
|
||||
child: Icon(
|
||||
SubjectIcon.resolveVariant(subjectName: exam.subjectName, context: context),
|
||||
size: 28.0,
|
||||
color: AppColors.of(context).text.withOpacity(.75),
|
||||
),
|
||||
)),
|
||||
title: Text(
|
||||
exam.description != "" ? exam.description : (exam.mode?.description ?? "Számonkérés"),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
exam.subjectName.capital(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: Icon(
|
||||
FeatherIcons.edit,
|
||||
color: AppColors.of(context).text.withOpacity(.75),
|
||||
),
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/exam.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_card.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/detail.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'exam_view.i18n.dart';
|
||||
|
||||
class ExamView extends StatelessWidget {
|
||||
const ExamView(this.exam, {Key? key}) : super(key: key);
|
||||
|
||||
final Exam exam;
|
||||
|
||||
static show(Exam exam, {required BuildContext context}) => showBottomCard(context: context, child: ExamView(exam));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header
|
||||
ListTile(
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.only(left: 6.0),
|
||||
child: Icon(
|
||||
SubjectIcon.resolveVariant(subjectName: exam.subjectName, context: context),
|
||||
size: 36.0,
|
||||
color: AppColors.of(context).text.withOpacity(.75),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
exam.subjectName.capital(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
exam.teacher,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: Text(
|
||||
exam.date.format(context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
// Details
|
||||
if (exam.writeDate.year != 0) Detail(title: "date".i18n, description: exam.writeDate.format(context)),
|
||||
if (exam.description != "") Detail(title: "description".i18n, description: exam.description),
|
||||
if (exam.mode != null) Detail(title: "mode".i18n, description: exam.mode!.description),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/exam.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_card.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/detail.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'exam_view.i18n.dart';
|
||||
|
||||
class ExamView extends StatelessWidget {
|
||||
const ExamView(this.exam, {Key? key}) : super(key: key);
|
||||
|
||||
final Exam exam;
|
||||
|
||||
static show(Exam exam, {required BuildContext context}) => showBottomCard(context: context, child: ExamView(exam));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header
|
||||
ListTile(
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.only(left: 6.0),
|
||||
child: Icon(
|
||||
SubjectIcon.resolveVariant(subjectName: exam.subjectName, context: context),
|
||||
size: 36.0,
|
||||
color: AppColors.of(context).text.withOpacity(.75),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
exam.subjectName.capital(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
exam.teacher,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: Text(
|
||||
exam.date.format(context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
// Details
|
||||
if (exam.writeDate.year != 0) Detail(title: "date".i18n, description: exam.writeDate.format(context)),
|
||||
if (exam.description != "") Detail(title: "description".i18n, description: exam.description),
|
||||
if (exam.mode != null) Detail(title: "mode".i18n, description: exam.mode!.description),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"date": "Date",
|
||||
"description": "Description",
|
||||
"mode": "Type",
|
||||
},
|
||||
"hu_hu": {
|
||||
"date": "Írás ideje",
|
||||
"description": "Leírás",
|
||||
"mode": "Típus",
|
||||
},
|
||||
"de_de": {
|
||||
"date": "Prüfungszeit",
|
||||
"description": "Bezeichnung",
|
||||
"mode": "Typ",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"date": "Date",
|
||||
"description": "Description",
|
||||
"mode": "Type",
|
||||
},
|
||||
"hu_hu": {
|
||||
"date": "Írás ideje",
|
||||
"description": "Leírás",
|
||||
"mode": "Típus",
|
||||
},
|
||||
"de_de": {
|
||||
"date": "Prüfungszeit",
|
||||
"description": "Bezeichnung",
|
||||
"mode": "Typ",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import 'package:filcnaplo_kreta_api/models/exam.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/card_handle.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/exam/exam_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/exam/exam_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ExamViewable extends StatelessWidget {
|
||||
const ExamViewable(this.exam, {Key? key}) : super(key: key);
|
||||
|
||||
final Exam exam;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Viewable(
|
||||
tile: ExamTile(exam),
|
||||
view: CardHandle(child: ExamView(exam)),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/exam.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/card_handle.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/exam/exam_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/exam/exam_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ExamViewable extends StatelessWidget {
|
||||
const ExamViewable(this.exam, {Key? key}) : super(key: key);
|
||||
|
||||
final Exam exam;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Viewable(
|
||||
tile: ExamTile(exam),
|
||||
view: CardHandle(child: ExamView(exam)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/subject.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/average_display.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GradeSubjectTile extends StatelessWidget {
|
||||
const GradeSubjectTile(this.subject, {Key? key, this.average = 0.0, this.groupAverage = 0.0, this.onTap, this.averageBefore = 0.0})
|
||||
: super(key: key);
|
||||
|
||||
final Subject subject;
|
||||
final void Function()? onTap;
|
||||
final double average;
|
||||
final double groupAverage;
|
||||
final double averageBefore;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color textColor = AppColors.of(context).text;
|
||||
|
||||
// Failing indicator
|
||||
if (average < 2.0 && average >= 1.0) {
|
||||
textColor = AppColors.of(context).red;
|
||||
}
|
||||
|
||||
final String changeIcon = average < averageBefore ? "▼" : "▲";
|
||||
final Color changeColor = average < averageBefore ? Colors.redAccent : Colors.lightGreenAccent.shade700;
|
||||
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: ListTile(
|
||||
minLeadingWidth: 32.0,
|
||||
dense: true,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 6.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
visualDensity: VisualDensity.compact,
|
||||
onTap: onTap,
|
||||
leading: Icon(SubjectIcon.resolveVariant(subject: subject, context: context), color: textColor.withOpacity(.75)),
|
||||
title: Text(
|
||||
subject.renamedTo ?? subject.name.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14.0, color: textColor, fontStyle: subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (groupAverage != 0 && averageBefore == 0.0) AverageDisplay(average: groupAverage, border: true),
|
||||
const SizedBox(width: 6.0),
|
||||
if (averageBefore != 0.0 && averageBefore != average) ...[
|
||||
AverageDisplay(average: averageBefore),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 6.0, right: 6.0, bottom: 3.5),
|
||||
child: Text(
|
||||
changeIcon,
|
||||
style: TextStyle(
|
||||
color: changeColor,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
AverageDisplay(average: average)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/subject.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/average_display.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GradeSubjectTile extends StatelessWidget {
|
||||
const GradeSubjectTile(this.subject, {Key? key, this.average = 0.0, this.groupAverage = 0.0, this.onTap, this.averageBefore = 0.0})
|
||||
: super(key: key);
|
||||
|
||||
final Subject subject;
|
||||
final void Function()? onTap;
|
||||
final double average;
|
||||
final double groupAverage;
|
||||
final double averageBefore;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color textColor = AppColors.of(context).text;
|
||||
|
||||
// Failing indicator
|
||||
if (average < 2.0 && average >= 1.0) {
|
||||
textColor = AppColors.of(context).red;
|
||||
}
|
||||
|
||||
final String changeIcon = average < averageBefore ? "▼" : "▲";
|
||||
final Color changeColor = average < averageBefore ? Colors.redAccent : Colors.lightGreenAccent.shade700;
|
||||
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: ListTile(
|
||||
minLeadingWidth: 32.0,
|
||||
dense: true,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 6.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
visualDensity: VisualDensity.compact,
|
||||
onTap: onTap,
|
||||
leading: Icon(SubjectIcon.resolveVariant(subject: subject, context: context), color: textColor.withOpacity(.75)),
|
||||
title: Text(
|
||||
subject.renamedTo ?? subject.name.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14.0, color: textColor, fontStyle: subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (groupAverage != 0 && averageBefore == 0.0) AverageDisplay(average: groupAverage, border: true),
|
||||
const SizedBox(width: 6.0),
|
||||
if (averageBefore != 0.0 && averageBefore != average) ...[
|
||||
AverageDisplay(average: averageBefore),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 6.0, right: 6.0, bottom: 3.5),
|
||||
child: Text(
|
||||
changeIcon,
|
||||
style: TextStyle(
|
||||
color: changeColor,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
AverageDisplay(average: average)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_card.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/detail.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'grade_view.i18n.dart';
|
||||
|
||||
class GradeView extends StatelessWidget {
|
||||
const GradeView(this.grade, {Key? key}) : super(key: key);
|
||||
|
||||
static show(Grade grade, {required BuildContext context}) => showBottomCard(context: context, child: GradeView(grade));
|
||||
|
||||
final Grade grade;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: GradeValueWidget(grade.value, fill: true),
|
||||
title: Text(
|
||||
grade.subject.renamedTo ?? grade.subject.name.capital(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w600, fontStyle: grade.subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
subtitle: Text(
|
||||
!Provider.of<SettingsProvider>(context, listen: false).presentationMode ? grade.teacher : "Tanár",
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: Text(
|
||||
grade.date.format(context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
// Grade Details
|
||||
Detail(
|
||||
title: "value".i18n,
|
||||
description: "${grade.value.valueName} " + percentText(),
|
||||
),
|
||||
if (grade.description != "") Detail(title: "description".i18n, description: grade.description),
|
||||
if (grade.mode.description != "") Detail(title: "mode".i18n, description: grade.mode.description),
|
||||
if (grade.writeDate.year != 0) Detail(title: "date".i18n, description: grade.writeDate.format(context)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String percentText() => grade.value.weight != 100 && grade.value.weight > 0 ? "${grade.value.weight}%" : "";
|
||||
}
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_card.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/detail.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'grade_view.i18n.dart';
|
||||
|
||||
class GradeView extends StatelessWidget {
|
||||
const GradeView(this.grade, {Key? key}) : super(key: key);
|
||||
|
||||
static show(Grade grade, {required BuildContext context}) => showBottomCard(context: context, child: GradeView(grade));
|
||||
|
||||
final Grade grade;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: GradeValueWidget(grade.value, fill: true),
|
||||
title: Text(
|
||||
grade.subject.renamedTo ?? grade.subject.name.capital(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w600, fontStyle: grade.subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
subtitle: Text(
|
||||
!Provider.of<SettingsProvider>(context, listen: false).presentationMode ? grade.teacher : "Tanár",
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: Text(
|
||||
grade.date.format(context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
// Grade Details
|
||||
Detail(
|
||||
title: "value".i18n,
|
||||
description: "${grade.value.valueName} " + percentText(),
|
||||
),
|
||||
if (grade.description != "") Detail(title: "description".i18n, description: grade.description),
|
||||
if (grade.mode.description != "") Detail(title: "mode".i18n, description: grade.mode.description),
|
||||
if (grade.writeDate.year != 0) Detail(title: "date".i18n, description: grade.writeDate.format(context)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String percentText() => grade.value.weight != 100 && grade.value.weight > 0 ? "${grade.value.weight}%" : "";
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"value": "Value",
|
||||
"date": "Date",
|
||||
"description": "Description",
|
||||
"mode": "Type",
|
||||
},
|
||||
"hu_hu": {
|
||||
"value": "Érték",
|
||||
"date": "Írás ideje",
|
||||
"description": "Leírás",
|
||||
"mode": "Típus",
|
||||
},
|
||||
"de_de": {
|
||||
"value": "Notenwert",
|
||||
"date": "Prüfungszeit",
|
||||
"description": "Bezeichnung",
|
||||
"mode": "Typ",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"value": "Value",
|
||||
"date": "Date",
|
||||
"description": "Description",
|
||||
"mode": "Type",
|
||||
},
|
||||
"hu_hu": {
|
||||
"value": "Érték",
|
||||
"date": "Írás ideje",
|
||||
"description": "Leírás",
|
||||
"mode": "Típus",
|
||||
},
|
||||
"de_de": {
|
||||
"value": "Notenwert",
|
||||
"date": "Prüfungszeit",
|
||||
"description": "Bezeichnung",
|
||||
"mode": "Typ",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/card_handle.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/grade/grade_view.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/subject_grades_container.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GradeViewable extends StatelessWidget {
|
||||
const GradeViewable(this.grade, {Key? key, this.padding}) : super(key: key);
|
||||
|
||||
final Grade grade;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final subject = SubjectGradesContainer.of(context) != null;
|
||||
final tile = GradeTile(grade, padding: subject ? EdgeInsets.zero : padding);
|
||||
|
||||
return Viewable(
|
||||
tile: subject ? SubjectGradesContainer(child: tile) : tile,
|
||||
view: CardHandle(child: GradeView(grade)),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/card_handle.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/grade/grade_view.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/subject_grades_container.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GradeViewable extends StatelessWidget {
|
||||
const GradeViewable(this.grade, {Key? key, this.padding}) : super(key: key);
|
||||
|
||||
final Grade grade;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final subject = SubjectGradesContainer.of(context) != null;
|
||||
final tile = GradeTile(grade, padding: subject ? EdgeInsets.zero : padding);
|
||||
|
||||
return Viewable(
|
||||
tile: subject ? SubjectGradesContainer(child: tile) : tile,
|
||||
view: CardHandle(child: GradeView(grade)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,158 +1,158 @@
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/grade/surprise_grade.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:rive/rive.dart';
|
||||
|
||||
import 'new_grades.i18n.dart';
|
||||
|
||||
class NewGradesSurprise extends StatelessWidget {
|
||||
const NewGradesSurprise(this.grades, {Key? key, this.censored = false}) : super(key: key);
|
||||
|
||||
final List<Grade> grades;
|
||||
final bool censored;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
width: 3.0,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
),
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
onTap: () => openingFun(context),
|
||||
minLeadingWidth: 54,
|
||||
leading: SizedBox(
|
||||
width: 44,
|
||||
height: 44,
|
||||
child: Center(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).colorScheme.secondary.withOpacity(.5),
|
||||
blurRadius: 18.0,
|
||||
)
|
||||
]),
|
||||
child: const RiveAnimation.asset("assets/animations/backpack-2.riv"),
|
||||
),
|
||||
),
|
||||
),
|
||||
title: censored
|
||||
? Wrap(
|
||||
children: [
|
||||
Container(
|
||||
width: 85,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.85),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
"new_grades".i18n,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: censored
|
||||
? Wrap(
|
||||
children: [
|
||||
Container(
|
||||
width: 125,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.45),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
"tap_to_open".i18n,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: censored
|
||||
? Wrap(
|
||||
children: [
|
||||
Container(
|
||||
width: 25,
|
||||
height: 25,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.45),
|
||||
borderRadius: BorderRadius.circular(25.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text.rich(
|
||||
TextSpan(children: [
|
||||
TextSpan(
|
||||
text: "${grades.length}",
|
||||
style: TextStyle(
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: AppColors.of(context).text.withOpacity(.2),
|
||||
offset: const Offset(2, 2),
|
||||
)
|
||||
],
|
||||
)),
|
||||
TextSpan(
|
||||
text: "x",
|
||||
style: TextStyle(
|
||||
fontSize: 20.0,
|
||||
color: AppColors.of(context).text.withOpacity(.5),
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
)
|
||||
]),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 28.0,
|
||||
color: AppColors.of(context).text.withOpacity(.75),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void openingFun(BuildContext context) {
|
||||
final settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
if (!settings.gradeOpeningFun) return;
|
||||
|
||||
final gradeProvider = Provider.of<GradeProvider>(context, listen: false);
|
||||
|
||||
final newGrades = gradeProvider.grades.where((element) => element.date.isAfter(gradeProvider.lastSeenDate)).toList();
|
||||
newGrades.sort((a, b) => a.date.compareTo(b.date));
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
for (final grade in newGrades) {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => SurpriseGrade(grade),
|
||||
useRootNavigator: true,
|
||||
barrierDismissible: false,
|
||||
barrierColor: Colors.transparent,
|
||||
useSafeArea: false,
|
||||
);
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
}
|
||||
await gradeProvider.seenAll();
|
||||
});
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/grade/surprise_grade.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:rive/rive.dart';
|
||||
|
||||
import 'new_grades.i18n.dart';
|
||||
|
||||
class NewGradesSurprise extends StatelessWidget {
|
||||
const NewGradesSurprise(this.grades, {Key? key, this.censored = false}) : super(key: key);
|
||||
|
||||
final List<Grade> grades;
|
||||
final bool censored;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
width: 3.0,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
),
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
onTap: () => openingFun(context),
|
||||
minLeadingWidth: 54,
|
||||
leading: SizedBox(
|
||||
width: 44,
|
||||
height: 44,
|
||||
child: Center(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).colorScheme.secondary.withOpacity(.5),
|
||||
blurRadius: 18.0,
|
||||
)
|
||||
]),
|
||||
child: const RiveAnimation.asset("assets/animations/backpack-2.riv"),
|
||||
),
|
||||
),
|
||||
),
|
||||
title: censored
|
||||
? Wrap(
|
||||
children: [
|
||||
Container(
|
||||
width: 85,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.85),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
"new_grades".i18n,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: censored
|
||||
? Wrap(
|
||||
children: [
|
||||
Container(
|
||||
width: 125,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.45),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
"tap_to_open".i18n,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: censored
|
||||
? Wrap(
|
||||
children: [
|
||||
Container(
|
||||
width: 25,
|
||||
height: 25,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.45),
|
||||
borderRadius: BorderRadius.circular(25.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text.rich(
|
||||
TextSpan(children: [
|
||||
TextSpan(
|
||||
text: "${grades.length}",
|
||||
style: TextStyle(
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: AppColors.of(context).text.withOpacity(.2),
|
||||
offset: const Offset(2, 2),
|
||||
)
|
||||
],
|
||||
)),
|
||||
TextSpan(
|
||||
text: "x",
|
||||
style: TextStyle(
|
||||
fontSize: 20.0,
|
||||
color: AppColors.of(context).text.withOpacity(.5),
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
)
|
||||
]),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 28.0,
|
||||
color: AppColors.of(context).text.withOpacity(.75),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void openingFun(BuildContext context) {
|
||||
final settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
if (!settings.gradeOpeningFun) return;
|
||||
|
||||
final gradeProvider = Provider.of<GradeProvider>(context, listen: false);
|
||||
|
||||
final newGrades = gradeProvider.grades.where((element) => element.date.isAfter(gradeProvider.lastSeenDate)).toList();
|
||||
newGrades.sort((a, b) => a.date.compareTo(b.date));
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
for (final grade in newGrades) {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => SurpriseGrade(grade),
|
||||
useRootNavigator: true,
|
||||
barrierDismissible: false,
|
||||
barrierColor: Colors.transparent,
|
||||
useSafeArea: false,
|
||||
);
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
}
|
||||
await gradeProvider.seenAll();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"common": "Common",
|
||||
"uncommon": "Uncommon",
|
||||
"rare": "Rare",
|
||||
"epic": "Epic",
|
||||
"legendary": "Legendary",
|
||||
"new_grades": "New grades",
|
||||
"tap_to_open": "Tap to open now!",
|
||||
"open_subtitle": "Tap to open...",
|
||||
},
|
||||
"hu_hu": {
|
||||
"common": "Gyakori",
|
||||
"uncommon": "Nem gyakori",
|
||||
"rare": "Ritka",
|
||||
"epic": "Epikus",
|
||||
"legendary": "Legendás",
|
||||
"new_grades": "Új jegyek",
|
||||
"tap_to_open": "Nyisd ki őket!",
|
||||
"open_subtitle": "Nyomd meg a kinyitáshoz...",
|
||||
},
|
||||
"de_de": {
|
||||
"common": "Gemeinsam",
|
||||
"uncommon": "Gelegentlich",
|
||||
"rare": "Selten",
|
||||
"epic": "Episch",
|
||||
"legendary": "Legendär",
|
||||
"new_grades": "Neue Noten",
|
||||
"tap_to_open": "Tippen, um jetzt zu öffnen!",
|
||||
"open_subtitle": "Antippen zum Öffnen...",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"common": "Common",
|
||||
"uncommon": "Uncommon",
|
||||
"rare": "Rare",
|
||||
"epic": "Epic",
|
||||
"legendary": "Legendary",
|
||||
"new_grades": "New grades",
|
||||
"tap_to_open": "Tap to open now!",
|
||||
"open_subtitle": "Tap to open...",
|
||||
},
|
||||
"hu_hu": {
|
||||
"common": "Gyakori",
|
||||
"uncommon": "Nem gyakori",
|
||||
"rare": "Ritka",
|
||||
"epic": "Epikus",
|
||||
"legendary": "Legendás",
|
||||
"new_grades": "Új jegyek",
|
||||
"tap_to_open": "Nyisd ki őket!",
|
||||
"open_subtitle": "Nyomd meg a kinyitáshoz...",
|
||||
},
|
||||
"de_de": {
|
||||
"common": "Gemeinsam",
|
||||
"uncommon": "Gelegentlich",
|
||||
"rare": "Selten",
|
||||
"epic": "Episch",
|
||||
"legendary": "Legendär",
|
||||
"new_grades": "Neue Noten",
|
||||
"tap_to_open": "Tippen, um jetzt zu öffnen!",
|
||||
"open_subtitle": "Antippen zum Öffnen...",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,389 +1,389 @@
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:animated_background/animated_background.dart' as bg;
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/home/particle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:rive/rive.dart' as rive;
|
||||
|
||||
import 'new_grades.i18n.dart';
|
||||
|
||||
class SurpriseGrade extends StatefulWidget {
|
||||
const SurpriseGrade(this.grade, {Key? key}) : super(key: key);
|
||||
|
||||
final Grade grade;
|
||||
|
||||
@override
|
||||
State<SurpriseGrade> createState() => _SurpriseGradeState();
|
||||
}
|
||||
|
||||
class _SurpriseGradeState extends State<SurpriseGrade> with TickerProviderStateMixin {
|
||||
late AnimationController _revealAnimFade;
|
||||
late AnimationController _revealAnimScale;
|
||||
late AnimationController _revealAnimGrade;
|
||||
late AnimationController _revealAnimParticle;
|
||||
late rive.RiveAnimationController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_revealAnimFade = AnimationController(vsync: this, duration: const Duration(milliseconds: 500));
|
||||
_revealAnimScale = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
|
||||
_revealAnimGrade = AnimationController(vsync: this, duration: const Duration(seconds: 1));
|
||||
_revealAnimParticle = AnimationController(vsync: this, duration: const Duration(seconds: 2));
|
||||
_revealAnimScale.animateTo(0.7, duration: Duration.zero);
|
||||
_controller = rive.SimpleAnimation('Timeline 1', autoplay: false);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_revealAnimFade.animateTo(1.0, curve: Curves.easeInOut);
|
||||
Future.delayed(const Duration(milliseconds: 200), () {
|
||||
_revealAnimScale.animateTo(1.0, curve: Curves.easeInOut).then((_) {
|
||||
setState(() => subtitle = true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
seed = Random().nextInt(100000000);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_revealAnimFade.dispose();
|
||||
_revealAnimScale.dispose();
|
||||
_revealAnimGrade.dispose();
|
||||
_revealAnimParticle.dispose();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool hold = false;
|
||||
bool subtitle = false;
|
||||
late int seed;
|
||||
|
||||
void reveal() async {
|
||||
if (!subtitle) {
|
||||
_revealAnimParticle.animateBack(0.0, curve: Curves.fastLinearToSlowEaseIn, duration: const Duration(milliseconds: 300));
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
_revealAnimGrade.animateBack(0.0, curve: Curves.fastLinearToSlowEaseIn);
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
_revealAnimFade.animateBack(0.0, curve: Curves.easeInOut);
|
||||
_revealAnimScale.animateBack(0.0, curve: Curves.easeInOut);
|
||||
if (mounted) Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
subtitle = false;
|
||||
setState(() => hold = false);
|
||||
_controller.isActive = true;
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
if (mounted) _revealAnimGrade.animateTo(1.0, curve: Curves.fastLinearToSlowEaseIn);
|
||||
await Future.delayed(const Duration(milliseconds: 700));
|
||||
if (mounted) await _revealAnimParticle.animateTo(1.0, curve: Curves.fastLinearToSlowEaseIn);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _revealAnimFade,
|
||||
builder: (context, child) {
|
||||
return FadeTransition(
|
||||
opacity: _revealAnimFade,
|
||||
child: Material(
|
||||
color: Colors.black.withOpacity(.75),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.secondary.withOpacity(.05),
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: RadialGradient(
|
||||
colors: [Colors.transparent, Colors.black],
|
||||
radius: 1.5,
|
||||
stops: [0.2, 1.0],
|
||||
),
|
||||
),
|
||||
child: bg.AnimatedBackground(
|
||||
vsync: this,
|
||||
behaviour: bg.RandomParticleBehaviour(
|
||||
options: bg.ParticleOptions(
|
||||
baseColor: Theme.of(context).colorScheme.secondary,
|
||||
spawnMinSpeed: 5.0,
|
||||
spawnMaxSpeed: 10.0,
|
||||
minOpacity: .05,
|
||||
maxOpacity: .08,
|
||||
spawnMinRadius: 30.0,
|
||||
spawnMaxRadius: 50.0,
|
||||
particleCount: 20,
|
||||
),
|
||||
),
|
||||
child: ScaleTransition(
|
||||
scale: _revealAnimScale,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: AnimatedBuilder(
|
||||
animation: _revealAnimGrade,
|
||||
builder: (context, child) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SlideTransition(
|
||||
position: _revealAnimGrade.drive(Tween(begin: Offset.zero, end: const Offset(0, 0.7))),
|
||||
child: AnimatedScale(
|
||||
scale: hold ? 1.1 : 1.0,
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: GestureDetector(
|
||||
onLongPressDown: (_) => setState(() => hold = true),
|
||||
onLongPressEnd: (_) => reveal(),
|
||||
onLongPressCancel: reveal,
|
||||
child: ScaleTransition(
|
||||
scale: CurvedAnimation(curve: Curves.easeInOut, parent: _revealAnimGrade.drive(Tween(begin: 1.0, end: 0.8))),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 300,
|
||||
height: 300,
|
||||
child: rive.RiveAnimation.asset(
|
||||
"assets/animations/backpack-2.riv",
|
||||
fit: BoxFit.contain,
|
||||
controllers: [_controller],
|
||||
antialiasing: false,
|
||||
),
|
||||
),
|
||||
SlideTransition(
|
||||
position: _revealAnimParticle.drive(Tween(begin: const Offset(0, 0.3), end: const Offset(0, 0.8))),
|
||||
child: FadeTransition(
|
||||
opacity: _revealAnimParticle,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 32.0, sigmaY: 32.0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32.0, vertical: 20.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(.3),
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
border: Border.all(color: Colors.black.withOpacity(.3), width: 1.0),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.grade.description != "")
|
||||
Text(
|
||||
widget.grade.description,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 26.0,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
widget.grade.subject.renamedTo ?? widget.grade.subject.name.capital(),
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(.8),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 24.0,
|
||||
fontStyle: widget.grade.subject.isRenamed ? FontStyle.italic : null),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
"${widget.grade.value.weight}%",
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(.7),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20.0),
|
||||
Icon(
|
||||
SubjectIcon.resolveVariant(subject: widget.grade.subject, context: context),
|
||||
color: Colors.white,
|
||||
size: 82.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 42.0),
|
||||
AnimatedOpacity(
|
||||
opacity: subtitle ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Text(
|
||||
"open_subtitle".i18n,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(.8),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 24.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_revealAnimGrade.value > 0)
|
||||
AnimatedBuilder(
|
||||
animation: _revealAnimParticle,
|
||||
builder: (context, child) {
|
||||
bool shouldPaint = false;
|
||||
if (_revealAnimParticle.status == AnimationStatus.forward || _revealAnimParticle.status == AnimationStatus.reverse) {
|
||||
shouldPaint = true;
|
||||
}
|
||||
return ScaleTransition(
|
||||
scale: _revealAnimGrade,
|
||||
child: FadeTransition(
|
||||
opacity: _revealAnimGrade,
|
||||
child: SlideTransition(
|
||||
position: _revealAnimGrade.drive(Tween(begin: Offset.zero, end: const Offset(0, -0.6))),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SlideTransition(
|
||||
position: _revealAnimGrade.drive(Tween(begin: Offset.zero, end: const Offset(0, -0.9))),
|
||||
child: Text(
|
||||
["legendary", "epic", "rare", "uncommon", "common"][5 - widget.grade.value.value].i18n,
|
||||
style: TextStyle(
|
||||
fontSize: 46.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: gradeColor(context: context, value: widget.grade.value.value),
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: gradeColor(context: context, value: widget.grade.value.value).withOpacity(.5),
|
||||
blurRadius: 24.0,
|
||||
),
|
||||
Shadow(
|
||||
color: gradeColor(context: context, value: widget.grade.value.value).withOpacity(.3),
|
||||
offset: const Offset(-3, -3),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32.0),
|
||||
ScaleTransition(
|
||||
scale: CurvedAnimation(curve: Curves.easeInOutBack, parent: _revealAnimParticle.drive(Tween(begin: 0.6, end: 1.0))),
|
||||
child: CustomPaint(
|
||||
painter: PimpPainter(
|
||||
particle: Sprinkles(),
|
||||
controller: _revealAnimParticle,
|
||||
seed: seed + 1,
|
||||
shouldPaint: shouldPaint,
|
||||
),
|
||||
child: CustomPaint(
|
||||
painter: PimpPainter(
|
||||
particle: Sprinkles(),
|
||||
controller: _revealAnimParticle,
|
||||
seed: seed,
|
||||
shouldPaint: shouldPaint,
|
||||
),
|
||||
child: RotationTransition(
|
||||
turns:
|
||||
CurvedAnimation(curve: Curves.easeInBack, parent: _revealAnimGrade).drive(Tween(begin: 0.95, end: 1.0)),
|
||||
child: GradeValueWidget(
|
||||
widget.grade.value,
|
||||
fill: true,
|
||||
contrast: true,
|
||||
shadow: true,
|
||||
outline: true,
|
||||
size: 100.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PimpPainter extends CustomPainter {
|
||||
PimpPainter({required this.particle, required this.seed, required this.controller, required this.shouldPaint}) : super(repaint: controller);
|
||||
|
||||
final Particle particle;
|
||||
final int seed;
|
||||
final AnimationController controller;
|
||||
final bool shouldPaint;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (shouldPaint) {
|
||||
canvas.translate(size.width / 2, size.height / 2);
|
||||
particle.paint(canvas, size, controller.value, seed);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(PimpPainter oldDelegate) => shouldPaint;
|
||||
}
|
||||
|
||||
Color randomColor(int c) {
|
||||
c = c % 5;
|
||||
if (c == 0) return Colors.red.shade300;
|
||||
if (c == 1) return Colors.green.shade300;
|
||||
if (c == 2) return Colors.orange.shade300;
|
||||
if (c == 3) return Colors.blue.shade300;
|
||||
if (c == 4) return Colors.pink.shade300;
|
||||
if (c == 5) return Colors.brown.shade300;
|
||||
return Colors.black;
|
||||
}
|
||||
|
||||
class Sprinkles extends Particle {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size, progress, seed) {
|
||||
Random random = Random(seed);
|
||||
int randomMirrorOffset = random.nextInt(8) + 1;
|
||||
CompositeParticle(children: [
|
||||
Firework(),
|
||||
RectangleMirror.builder(
|
||||
numberOfParticles: 6,
|
||||
particleBuilder: (n) {
|
||||
return AnimatedPositionedParticle(
|
||||
begin: const Offset(0.0, -10.0),
|
||||
end: const Offset(0.0, -60.0),
|
||||
child: FadingRect(width: 5.0, height: 15.0, color: randomColor(n)),
|
||||
);
|
||||
},
|
||||
initialDistance: -pi / randomMirrorOffset),
|
||||
]).paint(canvas, size, progress, seed);
|
||||
}
|
||||
}
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:animated_background/animated_background.dart' as bg;
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/home/particle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:rive/rive.dart' as rive;
|
||||
|
||||
import 'new_grades.i18n.dart';
|
||||
|
||||
class SurpriseGrade extends StatefulWidget {
|
||||
const SurpriseGrade(this.grade, {Key? key}) : super(key: key);
|
||||
|
||||
final Grade grade;
|
||||
|
||||
@override
|
||||
State<SurpriseGrade> createState() => _SurpriseGradeState();
|
||||
}
|
||||
|
||||
class _SurpriseGradeState extends State<SurpriseGrade> with TickerProviderStateMixin {
|
||||
late AnimationController _revealAnimFade;
|
||||
late AnimationController _revealAnimScale;
|
||||
late AnimationController _revealAnimGrade;
|
||||
late AnimationController _revealAnimParticle;
|
||||
late rive.RiveAnimationController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_revealAnimFade = AnimationController(vsync: this, duration: const Duration(milliseconds: 500));
|
||||
_revealAnimScale = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
|
||||
_revealAnimGrade = AnimationController(vsync: this, duration: const Duration(seconds: 1));
|
||||
_revealAnimParticle = AnimationController(vsync: this, duration: const Duration(seconds: 2));
|
||||
_revealAnimScale.animateTo(0.7, duration: Duration.zero);
|
||||
_controller = rive.SimpleAnimation('Timeline 1', autoplay: false);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_revealAnimFade.animateTo(1.0, curve: Curves.easeInOut);
|
||||
Future.delayed(const Duration(milliseconds: 200), () {
|
||||
_revealAnimScale.animateTo(1.0, curve: Curves.easeInOut).then((_) {
|
||||
setState(() => subtitle = true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
seed = Random().nextInt(100000000);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_revealAnimFade.dispose();
|
||||
_revealAnimScale.dispose();
|
||||
_revealAnimGrade.dispose();
|
||||
_revealAnimParticle.dispose();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool hold = false;
|
||||
bool subtitle = false;
|
||||
late int seed;
|
||||
|
||||
void reveal() async {
|
||||
if (!subtitle) {
|
||||
_revealAnimParticle.animateBack(0.0, curve: Curves.fastLinearToSlowEaseIn, duration: const Duration(milliseconds: 300));
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
_revealAnimGrade.animateBack(0.0, curve: Curves.fastLinearToSlowEaseIn);
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
_revealAnimFade.animateBack(0.0, curve: Curves.easeInOut);
|
||||
_revealAnimScale.animateBack(0.0, curve: Curves.easeInOut);
|
||||
if (mounted) Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
subtitle = false;
|
||||
setState(() => hold = false);
|
||||
_controller.isActive = true;
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
if (mounted) _revealAnimGrade.animateTo(1.0, curve: Curves.fastLinearToSlowEaseIn);
|
||||
await Future.delayed(const Duration(milliseconds: 700));
|
||||
if (mounted) await _revealAnimParticle.animateTo(1.0, curve: Curves.fastLinearToSlowEaseIn);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _revealAnimFade,
|
||||
builder: (context, child) {
|
||||
return FadeTransition(
|
||||
opacity: _revealAnimFade,
|
||||
child: Material(
|
||||
color: Colors.black.withOpacity(.75),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.secondary.withOpacity(.05),
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: RadialGradient(
|
||||
colors: [Colors.transparent, Colors.black],
|
||||
radius: 1.5,
|
||||
stops: [0.2, 1.0],
|
||||
),
|
||||
),
|
||||
child: bg.AnimatedBackground(
|
||||
vsync: this,
|
||||
behaviour: bg.RandomParticleBehaviour(
|
||||
options: bg.ParticleOptions(
|
||||
baseColor: Theme.of(context).colorScheme.secondary,
|
||||
spawnMinSpeed: 5.0,
|
||||
spawnMaxSpeed: 10.0,
|
||||
minOpacity: .05,
|
||||
maxOpacity: .08,
|
||||
spawnMinRadius: 30.0,
|
||||
spawnMaxRadius: 50.0,
|
||||
particleCount: 20,
|
||||
),
|
||||
),
|
||||
child: ScaleTransition(
|
||||
scale: _revealAnimScale,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: AnimatedBuilder(
|
||||
animation: _revealAnimGrade,
|
||||
builder: (context, child) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SlideTransition(
|
||||
position: _revealAnimGrade.drive(Tween(begin: Offset.zero, end: const Offset(0, 0.7))),
|
||||
child: AnimatedScale(
|
||||
scale: hold ? 1.1 : 1.0,
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: GestureDetector(
|
||||
onLongPressDown: (_) => setState(() => hold = true),
|
||||
onLongPressEnd: (_) => reveal(),
|
||||
onLongPressCancel: reveal,
|
||||
child: ScaleTransition(
|
||||
scale: CurvedAnimation(curve: Curves.easeInOut, parent: _revealAnimGrade.drive(Tween(begin: 1.0, end: 0.8))),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 300,
|
||||
height: 300,
|
||||
child: rive.RiveAnimation.asset(
|
||||
"assets/animations/backpack-2.riv",
|
||||
fit: BoxFit.contain,
|
||||
controllers: [_controller],
|
||||
antialiasing: false,
|
||||
),
|
||||
),
|
||||
SlideTransition(
|
||||
position: _revealAnimParticle.drive(Tween(begin: const Offset(0, 0.3), end: const Offset(0, 0.8))),
|
||||
child: FadeTransition(
|
||||
opacity: _revealAnimParticle,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 32.0, sigmaY: 32.0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32.0, vertical: 20.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(.3),
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
border: Border.all(color: Colors.black.withOpacity(.3), width: 1.0),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.grade.description != "")
|
||||
Text(
|
||||
widget.grade.description,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 26.0,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
widget.grade.subject.renamedTo ?? widget.grade.subject.name.capital(),
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(.8),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 24.0,
|
||||
fontStyle: widget.grade.subject.isRenamed ? FontStyle.italic : null),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
"${widget.grade.value.weight}%",
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(.7),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20.0),
|
||||
Icon(
|
||||
SubjectIcon.resolveVariant(subject: widget.grade.subject, context: context),
|
||||
color: Colors.white,
|
||||
size: 82.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 42.0),
|
||||
AnimatedOpacity(
|
||||
opacity: subtitle ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Text(
|
||||
"open_subtitle".i18n,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(.8),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 24.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_revealAnimGrade.value > 0)
|
||||
AnimatedBuilder(
|
||||
animation: _revealAnimParticle,
|
||||
builder: (context, child) {
|
||||
bool shouldPaint = false;
|
||||
if (_revealAnimParticle.status == AnimationStatus.forward || _revealAnimParticle.status == AnimationStatus.reverse) {
|
||||
shouldPaint = true;
|
||||
}
|
||||
return ScaleTransition(
|
||||
scale: _revealAnimGrade,
|
||||
child: FadeTransition(
|
||||
opacity: _revealAnimGrade,
|
||||
child: SlideTransition(
|
||||
position: _revealAnimGrade.drive(Tween(begin: Offset.zero, end: const Offset(0, -0.6))),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SlideTransition(
|
||||
position: _revealAnimGrade.drive(Tween(begin: Offset.zero, end: const Offset(0, -0.9))),
|
||||
child: Text(
|
||||
["legendary", "epic", "rare", "uncommon", "common"][5 - widget.grade.value.value].i18n,
|
||||
style: TextStyle(
|
||||
fontSize: 46.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: gradeColor(context: context, value: widget.grade.value.value),
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: gradeColor(context: context, value: widget.grade.value.value).withOpacity(.5),
|
||||
blurRadius: 24.0,
|
||||
),
|
||||
Shadow(
|
||||
color: gradeColor(context: context, value: widget.grade.value.value).withOpacity(.3),
|
||||
offset: const Offset(-3, -3),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32.0),
|
||||
ScaleTransition(
|
||||
scale: CurvedAnimation(curve: Curves.easeInOutBack, parent: _revealAnimParticle.drive(Tween(begin: 0.6, end: 1.0))),
|
||||
child: CustomPaint(
|
||||
painter: PimpPainter(
|
||||
particle: Sprinkles(),
|
||||
controller: _revealAnimParticle,
|
||||
seed: seed + 1,
|
||||
shouldPaint: shouldPaint,
|
||||
),
|
||||
child: CustomPaint(
|
||||
painter: PimpPainter(
|
||||
particle: Sprinkles(),
|
||||
controller: _revealAnimParticle,
|
||||
seed: seed,
|
||||
shouldPaint: shouldPaint,
|
||||
),
|
||||
child: RotationTransition(
|
||||
turns:
|
||||
CurvedAnimation(curve: Curves.easeInBack, parent: _revealAnimGrade).drive(Tween(begin: 0.95, end: 1.0)),
|
||||
child: GradeValueWidget(
|
||||
widget.grade.value,
|
||||
fill: true,
|
||||
contrast: true,
|
||||
shadow: true,
|
||||
outline: true,
|
||||
size: 100.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PimpPainter extends CustomPainter {
|
||||
PimpPainter({required this.particle, required this.seed, required this.controller, required this.shouldPaint}) : super(repaint: controller);
|
||||
|
||||
final Particle particle;
|
||||
final int seed;
|
||||
final AnimationController controller;
|
||||
final bool shouldPaint;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (shouldPaint) {
|
||||
canvas.translate(size.width / 2, size.height / 2);
|
||||
particle.paint(canvas, size, controller.value, seed);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(PimpPainter oldDelegate) => shouldPaint;
|
||||
}
|
||||
|
||||
Color randomColor(int c) {
|
||||
c = c % 5;
|
||||
if (c == 0) return Colors.red.shade300;
|
||||
if (c == 1) return Colors.green.shade300;
|
||||
if (c == 2) return Colors.orange.shade300;
|
||||
if (c == 3) return Colors.blue.shade300;
|
||||
if (c == 4) return Colors.pink.shade300;
|
||||
if (c == 5) return Colors.brown.shade300;
|
||||
return Colors.black;
|
||||
}
|
||||
|
||||
class Sprinkles extends Particle {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size, progress, seed) {
|
||||
Random random = Random(seed);
|
||||
int randomMirrorOffset = random.nextInt(8) + 1;
|
||||
CompositeParticle(children: [
|
||||
Firework(),
|
||||
RectangleMirror.builder(
|
||||
numberOfParticles: 6,
|
||||
particleBuilder: (n) {
|
||||
return AnimatedPositionedParticle(
|
||||
begin: const Offset(0.0, -10.0),
|
||||
end: const Offset(0.0, -60.0),
|
||||
child: FadingRect(width: 5.0, height: 15.0, color: randomColor(n)),
|
||||
);
|
||||
},
|
||||
initialDistance: -pi / randomMirrorOffset),
|
||||
]).paint(canvas, size, progress, seed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +1,89 @@
|
||||
import 'dart:io';
|
||||
import 'package:filcnaplo/helpers/attachment_helper.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/message/image_view.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
import 'package:filcnaplo_kreta_api/models/homework.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'homework_attachment_tile.i18n.dart';
|
||||
|
||||
class HomeworkAttachmentTile extends StatelessWidget {
|
||||
const HomeworkAttachmentTile(this.attachment, {Key? key}) : super(key: key);
|
||||
|
||||
final HomeworkAttachment attachment;
|
||||
|
||||
Widget buildImage(BuildContext context) {
|
||||
return FutureBuilder<String>(
|
||||
future: attachment.download(context),
|
||||
builder: (context, snapshot) {
|
||||
return snapshot.hasData
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
child: Material(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context, rootNavigator: true).push(MaterialPageRoute(
|
||||
builder: (context) => ImageView(snapshot.data!),
|
||||
));
|
||||
},
|
||||
child: Ink.image(
|
||||
image: FileImage(File(snapshot.data ?? "")),
|
||||
height: 200.0,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: CircularProgressIndicator(color: Theme.of(context).colorScheme.secondary),
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (attachment.isImage) return buildImage(context);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
onTap: () {
|
||||
attachment.open(context).then((value) {
|
||||
if (!value) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
|
||||
context: context,
|
||||
content: Text("Failed to open attachment".i18n),
|
||||
backgroundColor: AppColors.of(context).red,
|
||||
duration: const Duration(seconds: 1),
|
||||
));
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(children: [
|
||||
const Icon(FeatherIcons.paperclip),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Text(attachment.name, maxLines: 2, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'dart:io';
|
||||
import 'package:filcnaplo/helpers/attachment_helper.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/message/image_view.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
import 'package:filcnaplo_kreta_api/models/homework.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'homework_attachment_tile.i18n.dart';
|
||||
|
||||
class HomeworkAttachmentTile extends StatelessWidget {
|
||||
const HomeworkAttachmentTile(this.attachment, {Key? key}) : super(key: key);
|
||||
|
||||
final HomeworkAttachment attachment;
|
||||
|
||||
Widget buildImage(BuildContext context) {
|
||||
return FutureBuilder<String>(
|
||||
future: attachment.download(context),
|
||||
builder: (context, snapshot) {
|
||||
return snapshot.hasData
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
child: Material(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context, rootNavigator: true).push(MaterialPageRoute(
|
||||
builder: (context) => ImageView(snapshot.data!),
|
||||
));
|
||||
},
|
||||
child: Ink.image(
|
||||
image: FileImage(File(snapshot.data ?? "")),
|
||||
height: 200.0,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: CircularProgressIndicator(color: Theme.of(context).colorScheme.secondary),
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (attachment.isImage) return buildImage(context);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
onTap: () {
|
||||
attachment.open(context).then((value) {
|
||||
if (!value) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
|
||||
context: context,
|
||||
content: Text("Failed to open attachment".i18n),
|
||||
backgroundColor: AppColors.of(context).red,
|
||||
duration: const Duration(seconds: 1),
|
||||
));
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(children: [
|
||||
const Icon(FeatherIcons.paperclip),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Text(attachment.name, maxLines: 2, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Failed to open attachment": "Failed to open attachment",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Failed to open attachment": "Nem sikerült megnyitni a mellékletet",
|
||||
},
|
||||
"de_de": {
|
||||
"Failed to open attachment": "Anhang konnte nicht geöffnet werden",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Failed to open attachment": "Failed to open attachment",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Failed to open attachment": "Nem sikerült megnyitni a mellékletet",
|
||||
},
|
||||
"de_de": {
|
||||
"Failed to open attachment": "Anhang konnte nicht geöffnet werden",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,103 +1,103 @@
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/homework.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
class HomeworkTile extends StatelessWidget {
|
||||
const HomeworkTile(this.homework, {Key? key, this.onTap, this.padding, this.censored = false}) : super(key: key);
|
||||
|
||||
final Homework homework;
|
||||
final void Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final bool censored;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
onTap: onTap,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
leading: SizedBox(
|
||||
width: 44,
|
||||
height: 44,
|
||||
child: censored
|
||||
? Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.55),
|
||||
borderRadius: BorderRadius.circular(60.0),
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(top: 2.0),
|
||||
child: Icon(
|
||||
SubjectIcon.resolveVariant(subjectName: homework.subjectName, context: context),
|
||||
size: 28.0,
|
||||
color: AppColors.of(context).text.withOpacity(.75),
|
||||
),
|
||||
),
|
||||
),
|
||||
title: censored
|
||||
? Wrap(
|
||||
children: [
|
||||
Container(
|
||||
width: 160,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.85),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
homework.subjectName.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: censored
|
||||
? Wrap(
|
||||
children: [
|
||||
Container(
|
||||
width: 100,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.45),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
homework.content.escapeHtml().replaceAll('\n', ' '),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: censored
|
||||
? Container(
|
||||
width: 15,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.45),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
FeatherIcons.home,
|
||||
color: AppColors.of(context).text.withOpacity(.75),
|
||||
),
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/homework.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
class HomeworkTile extends StatelessWidget {
|
||||
const HomeworkTile(this.homework, {Key? key, this.onTap, this.padding, this.censored = false}) : super(key: key);
|
||||
|
||||
final Homework homework;
|
||||
final void Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final bool censored;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
onTap: onTap,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
leading: SizedBox(
|
||||
width: 44,
|
||||
height: 44,
|
||||
child: censored
|
||||
? Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.55),
|
||||
borderRadius: BorderRadius.circular(60.0),
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(top: 2.0),
|
||||
child: Icon(
|
||||
SubjectIcon.resolveVariant(subjectName: homework.subjectName, context: context),
|
||||
size: 28.0,
|
||||
color: AppColors.of(context).text.withOpacity(.75),
|
||||
),
|
||||
),
|
||||
),
|
||||
title: censored
|
||||
? Wrap(
|
||||
children: [
|
||||
Container(
|
||||
width: 160,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.85),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
homework.subjectName.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: censored
|
||||
? Wrap(
|
||||
children: [
|
||||
Container(
|
||||
width: 100,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.45),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
homework.content.escapeHtml().replaceAll('\n', ' '),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: censored
|
||||
? Container(
|
||||
width: 15,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.of(context).text.withOpacity(.45),
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
FeatherIcons.home,
|
||||
color: AppColors.of(context).text.withOpacity(.75),
|
||||
),
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,88 +1,88 @@
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/homework.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/detail.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/sliding_bottom_sheet.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/homework/homework_attachment_tile.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'homework_view.i18n.dart';
|
||||
|
||||
class HomeworkView extends StatelessWidget {
|
||||
const HomeworkView(this.homework, {Key? key}) : super(key: key);
|
||||
|
||||
final Homework homework;
|
||||
|
||||
static show(Homework homework, {required BuildContext context}) {
|
||||
showSlidingBottomSheet(context: context, child: HomeworkView(homework));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> attachmentTiles = [];
|
||||
|
||||
for (var attachment in homework.attachments) {
|
||||
attachmentTiles.add(Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
|
||||
child: HomeworkAttachmentTile(
|
||||
attachment,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
SubjectIcon.resolveVariant(subjectName: homework.subjectName, context: context),
|
||||
size: 36.0,
|
||||
),
|
||||
title: Text(
|
||||
homework.subjectName.capital(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
homework.teacher,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: Text(
|
||||
homework.date.format(context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
// Details
|
||||
if (homework.deadline.year != 0) Detail(title: "deadline".i18n, description: homework.deadline.format(context)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 6.0),
|
||||
child: SelectableLinkify(
|
||||
text: homework.content.escapeHtml(),
|
||||
options: const LinkifyOptions(looseUrl: true, removeWww: true),
|
||||
onOpen: (link) {
|
||||
launch(link.url,
|
||||
customTabsOption: CustomTabsOption(
|
||||
toolbarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
showPageTitle: true,
|
||||
));
|
||||
},
|
||||
style: const TextStyle(fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
|
||||
// Attachments
|
||||
...attachmentTiles,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/homework.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/detail.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/sliding_bottom_sheet.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/homework/homework_attachment_tile.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'homework_view.i18n.dart';
|
||||
|
||||
class HomeworkView extends StatelessWidget {
|
||||
const HomeworkView(this.homework, {Key? key}) : super(key: key);
|
||||
|
||||
final Homework homework;
|
||||
|
||||
static show(Homework homework, {required BuildContext context}) {
|
||||
showSlidingBottomSheet(context: context, child: HomeworkView(homework));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> attachmentTiles = [];
|
||||
|
||||
for (var attachment in homework.attachments) {
|
||||
attachmentTiles.add(Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
|
||||
child: HomeworkAttachmentTile(
|
||||
attachment,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header
|
||||
ListTile(
|
||||
leading: Icon(
|
||||
SubjectIcon.resolveVariant(subjectName: homework.subjectName, context: context),
|
||||
size: 36.0,
|
||||
),
|
||||
title: Text(
|
||||
homework.subjectName.capital(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
homework.teacher,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: Text(
|
||||
homework.date.format(context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
// Details
|
||||
if (homework.deadline.year != 0) Detail(title: "deadline".i18n, description: homework.deadline.format(context)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0, vertical: 6.0),
|
||||
child: SelectableLinkify(
|
||||
text: homework.content.escapeHtml(),
|
||||
options: const LinkifyOptions(looseUrl: true, removeWww: true),
|
||||
onOpen: (link) {
|
||||
launch(link.url,
|
||||
customTabsOption: CustomTabsOption(
|
||||
toolbarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
showPageTitle: true,
|
||||
));
|
||||
},
|
||||
style: const TextStyle(fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
|
||||
// Attachments
|
||||
...attachmentTiles,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"deadline": "Deadline",
|
||||
},
|
||||
"hu_hu": {
|
||||
"deadline": "Határidő",
|
||||
},
|
||||
"de_de": {
|
||||
"deadline": "Termin",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"deadline": "Deadline",
|
||||
},
|
||||
"hu_hu": {
|
||||
"deadline": "Határidő",
|
||||
},
|
||||
"de_de": {
|
||||
"deadline": "Termin",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import 'package:filcnaplo_kreta_api/models/homework.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/homework/homework_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/homework/homework_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HomeworkViewable extends StatelessWidget {
|
||||
const HomeworkViewable(this.homework, {Key? key}) : super(key: key);
|
||||
|
||||
final Homework homework;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return HomeworkTile(
|
||||
homework,
|
||||
onTap: () => HomeworkView.show(homework, context: context),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/homework.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/homework/homework_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/homework/homework_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HomeworkViewable extends StatelessWidget {
|
||||
const HomeworkViewable(this.homework, {Key? key}) : super(key: key);
|
||||
|
||||
final Homework homework;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return HomeworkTile(
|
||||
homework,
|
||||
onTap: () => HomeworkView.show(homework, context: context),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'changed_lesson_tile.i18n.dart';
|
||||
|
||||
class ChangedLessonTile extends StatelessWidget {
|
||||
const ChangedLessonTile(this.lesson, {Key? key, this.onTap, this.padding}) : super(key: key);
|
||||
|
||||
final Lesson lesson;
|
||||
final void Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String lessonIndexTrailing = "";
|
||||
|
||||
// Only put a trailing . if its a digit
|
||||
if (RegExp(r'\d').hasMatch(lesson.lessonIndex)) lessonIndexTrailing = ".";
|
||||
|
||||
Color accent = Theme.of(context).colorScheme.secondary;
|
||||
|
||||
if (lesson.substituteTeacher != "") {
|
||||
accent = AppColors.of(context).yellow;
|
||||
}
|
||||
|
||||
if (lesson.status?.name == "Elmaradt") {
|
||||
accent = AppColors.of(context).red;
|
||||
}
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
onTap: onTap,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
|
||||
leading: SizedBox(
|
||||
width: 44.0,
|
||||
height: 44.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
lesson.lessonIndex + lessonIndexTrailing,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 30.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: accent,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
lesson.substituteTeacher != "" ? "substituted".i18n : "cancelled".i18n,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
lesson.subject.renamedTo ?? lesson.subject.name.capital(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w500, fontStyle: lesson.subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
trailing: const Icon(FeatherIcons.arrowRight),
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'changed_lesson_tile.i18n.dart';
|
||||
|
||||
class ChangedLessonTile extends StatelessWidget {
|
||||
const ChangedLessonTile(this.lesson, {Key? key, this.onTap, this.padding}) : super(key: key);
|
||||
|
||||
final Lesson lesson;
|
||||
final void Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String lessonIndexTrailing = "";
|
||||
|
||||
// Only put a trailing . if its a digit
|
||||
if (RegExp(r'\d').hasMatch(lesson.lessonIndex)) lessonIndexTrailing = ".";
|
||||
|
||||
Color accent = Theme.of(context).colorScheme.secondary;
|
||||
|
||||
if (lesson.substituteTeacher != "") {
|
||||
accent = AppColors.of(context).yellow;
|
||||
}
|
||||
|
||||
if (lesson.status?.name == "Elmaradt") {
|
||||
accent = AppColors.of(context).red;
|
||||
}
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
onTap: onTap,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
|
||||
leading: SizedBox(
|
||||
width: 44.0,
|
||||
height: 44.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
lesson.lessonIndex + lessonIndexTrailing,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 30.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: accent,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
lesson.substituteTeacher != "" ? "substituted".i18n : "cancelled".i18n,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
lesson.subject.renamedTo ?? lesson.subject.name.capital(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w500, fontStyle: lesson.subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
trailing: const Icon(FeatherIcons.arrowRight),
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"cancelled": "Cancelled lesson",
|
||||
"substituted": "Substituted lesson",
|
||||
},
|
||||
"hu_hu": {
|
||||
"cancelled": "Elmaradó óra",
|
||||
"substituted": "Helyettesített óra",
|
||||
},
|
||||
"de_de": {
|
||||
"cancelled": "Abgesagte Stunde",
|
||||
"substituted": "Vertretene Stunden",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"cancelled": "Cancelled lesson",
|
||||
"substituted": "Substituted lesson",
|
||||
},
|
||||
"hu_hu": {
|
||||
"cancelled": "Elmaradó óra",
|
||||
"substituted": "Helyettesített óra",
|
||||
},
|
||||
"de_de": {
|
||||
"cancelled": "Abgesagte Stunde",
|
||||
"substituted": "Vertretene Stunden",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/lesson/changed_lesson_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/timetable/timetable_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChangedLessonViewable extends StatelessWidget {
|
||||
const ChangedLessonViewable(this.lesson, {Key? key}) : super(key: key);
|
||||
|
||||
final Lesson lesson;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangedLessonTile(
|
||||
lesson,
|
||||
onTap: () => TimetablePage.jump(context, lesson: lesson),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/lesson/changed_lesson_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/timetable/timetable_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChangedLessonViewable extends StatelessWidget {
|
||||
const ChangedLessonViewable(this.lesson, {Key? key}) : super(key: key);
|
||||
|
||||
final Lesson lesson;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangedLessonTile(
|
||||
lesson,
|
||||
onTap: () => TimetablePage.jump(context, lesson: lesson),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +1,80 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_card.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/detail.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'lesson_view.i18n.dart';
|
||||
|
||||
class LessonView extends StatelessWidget {
|
||||
const LessonView(this.lesson, {Key? key}) : super(key: key);
|
||||
|
||||
final Lesson lesson;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color accent = Theme.of(context).colorScheme.secondary;
|
||||
String lessonIndexTrailing = "";
|
||||
|
||||
if (RegExp(r'\d').hasMatch(lesson.lessonIndex)) lessonIndexTrailing = ".";
|
||||
|
||||
if (lesson.substituteTeacher != "") {
|
||||
accent = AppColors.of(context).yellow;
|
||||
}
|
||||
|
||||
if (lesson.status?.name == "Elmaradt") {
|
||||
accent = AppColors.of(context).red;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header
|
||||
ListTile(
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: Text(
|
||||
lesson.lessonIndex + lessonIndexTrailing,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 38.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: accent,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
lesson.subject.renamedTo ?? lesson.subject.name.capital(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w600, fontStyle: lesson.subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
subtitle: Text(
|
||||
lesson.substituteTeacher == "" ? lesson.teacher : lesson.substituteTeacher,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: Text(
|
||||
lesson.date.format(context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
// Details
|
||||
if (lesson.room != "") Detail(title: "Room".i18n, description: lesson.room.replaceAll("_", " ")),
|
||||
if (lesson.description != "") Detail(title: "Description".i18n, description: lesson.description),
|
||||
if (lesson.lessonYearIndex != null) Detail(title: "Lesson Number".i18n, description: "${lesson.lessonYearIndex}."),
|
||||
if (lesson.groupName != "") Detail(title: "Group".i18n, description: lesson.groupName),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static show(Lesson lesson, {required BuildContext context}) {
|
||||
showBottomCard(context: context, child: LessonView(lesson));
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_card.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/detail.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'lesson_view.i18n.dart';
|
||||
|
||||
class LessonView extends StatelessWidget {
|
||||
const LessonView(this.lesson, {Key? key}) : super(key: key);
|
||||
|
||||
final Lesson lesson;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color accent = Theme.of(context).colorScheme.secondary;
|
||||
String lessonIndexTrailing = "";
|
||||
|
||||
if (RegExp(r'\d').hasMatch(lesson.lessonIndex)) lessonIndexTrailing = ".";
|
||||
|
||||
if (lesson.substituteTeacher != "") {
|
||||
accent = AppColors.of(context).yellow;
|
||||
}
|
||||
|
||||
if (lesson.status?.name == "Elmaradt") {
|
||||
accent = AppColors.of(context).red;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header
|
||||
ListTile(
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: Text(
|
||||
lesson.lessonIndex + lessonIndexTrailing,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 38.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: accent,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
lesson.subject.renamedTo ?? lesson.subject.name.capital(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontWeight: FontWeight.w600, fontStyle: lesson.subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
subtitle: Text(
|
||||
lesson.substituteTeacher == "" ? lesson.teacher : lesson.substituteTeacher,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: Text(
|
||||
lesson.date.format(context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
// Details
|
||||
if (lesson.room != "") Detail(title: "Room".i18n, description: lesson.room.replaceAll("_", " ")),
|
||||
if (lesson.description != "") Detail(title: "Description".i18n, description: lesson.description),
|
||||
if (lesson.lessonYearIndex != null) Detail(title: "Lesson Number".i18n, description: "${lesson.lessonYearIndex}."),
|
||||
if (lesson.groupName != "") Detail(title: "Group".i18n, description: lesson.groupName),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static show(Lesson lesson, {required BuildContext context}) {
|
||||
showBottomCard(context: context, child: LessonView(lesson));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Room": "Room",
|
||||
"Description": "Description",
|
||||
"Lesson Number": "Lesson Number",
|
||||
"Group": "Group",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Room": "Terem",
|
||||
"Description": "Leírás",
|
||||
"Lesson Number": "Éves óraszám",
|
||||
"Group": "Csoport",
|
||||
},
|
||||
"de_de": {
|
||||
"Room": "Raum",
|
||||
"Description": "Bezeichnung",
|
||||
"Lesson Number": "Ordinalzahl",
|
||||
"Group": "Gruppe",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Room": "Room",
|
||||
"Description": "Description",
|
||||
"Lesson Number": "Lesson Number",
|
||||
"Group": "Group",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Room": "Terem",
|
||||
"Description": "Leírás",
|
||||
"Lesson Number": "Éves óraszám",
|
||||
"Group": "Csoport",
|
||||
},
|
||||
"de_de": {
|
||||
"Room": "Raum",
|
||||
"Description": "Bezeichnung",
|
||||
"Lesson Number": "Ordinalzahl",
|
||||
"Group": "Gruppe",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/card_handle.dart';
|
||||
import 'package:filcnaplo/ui/widgets/lesson/lesson_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/lesson/lesson_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LessonViewable extends StatelessWidget {
|
||||
const LessonViewable(this.lesson, {Key? key, this.swapDesc = false}) : super(key: key);
|
||||
|
||||
final Lesson lesson;
|
||||
final bool swapDesc;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tile = LessonTile(lesson, swapDesc: swapDesc);
|
||||
|
||||
if (lesson.subject.id == '' || tile.lesson.isEmpty) return tile;
|
||||
|
||||
return Viewable(
|
||||
tile: tile,
|
||||
view: CardHandle(child: LessonView(lesson)),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/card_handle.dart';
|
||||
import 'package:filcnaplo/ui/widgets/lesson/lesson_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/lesson/lesson_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LessonViewable extends StatelessWidget {
|
||||
const LessonViewable(this.lesson, {Key? key, this.swapDesc = false}) : super(key: key);
|
||||
|
||||
final Lesson lesson;
|
||||
final bool swapDesc;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tile = LessonTile(lesson, swapDesc: swapDesc);
|
||||
|
||||
if (lesson.subject.id == '' || tile.lesson.isEmpty) return tile;
|
||||
|
||||
return Viewable(
|
||||
tile: tile,
|
||||
view: CardHandle(child: LessonView(lesson)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +1,83 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:filcnaplo_kreta_api/models/attachment.dart';
|
||||
import 'package:filcnaplo/helpers/attachment_helper.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/message/image_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
class AttachmentTile extends StatelessWidget {
|
||||
const AttachmentTile(this.attachment, {Key? key}) : super(key: key);
|
||||
|
||||
final Attachment attachment;
|
||||
|
||||
Widget buildImage(BuildContext context) {
|
||||
return FutureBuilder<String>(
|
||||
future: attachment.download(context),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
child: Material(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
useRootNavigator: true,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ImageView(snapshot.data!);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Ink.image(
|
||||
image: FileImage(File(snapshot.data ?? "")),
|
||||
height: 200.0,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: CircularProgressIndicator(color: Theme.of(context).colorScheme.secondary),
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (attachment.isImage) return buildImage(context);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
onTap: () {
|
||||
attachment.open(context);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(children: [
|
||||
const Icon(FeatherIcons.paperclip),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Text(attachment.name, maxLines: 2, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:filcnaplo_kreta_api/models/attachment.dart';
|
||||
import 'package:filcnaplo/helpers/attachment_helper.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/message/image_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
class AttachmentTile extends StatelessWidget {
|
||||
const AttachmentTile(this.attachment, {Key? key}) : super(key: key);
|
||||
|
||||
final Attachment attachment;
|
||||
|
||||
Widget buildImage(BuildContext context) {
|
||||
return FutureBuilder<String>(
|
||||
future: attachment.download(context),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
child: Material(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
useRootNavigator: true,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ImageView(snapshot.data!);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Ink.image(
|
||||
image: FileImage(File(snapshot.data ?? "")),
|
||||
height: 200.0,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: CircularProgressIndicator(color: Theme.of(context).colorScheme.secondary),
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (attachment.isImage) return buildImage(context);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
onTap: () {
|
||||
attachment.open(context);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(children: [
|
||||
const Icon(FeatherIcons.paperclip),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Text(attachment.name, maxLines: 2, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:filcnaplo/helpers/share_helper.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
|
||||
class ImageView extends StatelessWidget {
|
||||
const ImageView(this.path, {Key? key}) : super(key: key);
|
||||
|
||||
final String path;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: SafeArea(
|
||||
minimum: const EdgeInsets.only(top: 24.0),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: IconButton(
|
||||
onPressed: () => ShareHelper.shareFile(path),
|
||||
icon: Icon(FeatherIcons.share2, color: AppColors.of(context).text),
|
||||
splashRadius: 24.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: PhotoView(
|
||||
imageProvider: FileImage(File(path)),
|
||||
maxScale: 4.0,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
backgroundDecoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:filcnaplo/helpers/share_helper.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
|
||||
class ImageView extends StatelessWidget {
|
||||
const ImageView(this.path, {Key? key}) : super(key: key);
|
||||
|
||||
final String path;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: SafeArea(
|
||||
minimum: const EdgeInsets.only(top: 24.0),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: IconButton(
|
||||
onPressed: () => ShareHelper.shareFile(path),
|
||||
icon: Icon(FeatherIcons.share2, color: AppColors.of(context).text),
|
||||
splashRadius: 24.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: PhotoView(
|
||||
imageProvider: FileImage(File(path)),
|
||||
maxScale: 4.0,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
backgroundDecoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/message.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/message/message_view_tile.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MessageView extends StatefulWidget {
|
||||
const MessageView(this.messages, {Key? key}) : super(key: key);
|
||||
|
||||
final List<Message> messages;
|
||||
|
||||
static show(List<Message> messages, {required BuildContext context}) =>
|
||||
Navigator.of(context, rootNavigator: true).push(CupertinoPageRoute(builder: (context) => MessageView(messages)));
|
||||
|
||||
@override
|
||||
_MessageViewState createState() => _MessageViewState();
|
||||
}
|
||||
|
||||
class _MessageViewState extends State<MessageView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leadingWidth: 64.0,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
elevation: 0,
|
||||
actions: const [
|
||||
// Padding(
|
||||
// padding: EdgeInsets.only(right: 8.0),
|
||||
// child: IconButton(
|
||||
// onPressed: () {},
|
||||
// icon: Icon(FeatherIcons.archive, color: AppColors.of(context).text),
|
||||
// splashRadius: 32.0,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: widget.messages.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: MessageViewTile(widget.messages[index]),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/message.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/message/message_view_tile.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MessageView extends StatefulWidget {
|
||||
const MessageView(this.messages, {Key? key}) : super(key: key);
|
||||
|
||||
final List<Message> messages;
|
||||
|
||||
static show(List<Message> messages, {required BuildContext context}) =>
|
||||
Navigator.of(context, rootNavigator: true).push(CupertinoPageRoute(builder: (context) => MessageView(messages)));
|
||||
|
||||
@override
|
||||
_MessageViewState createState() => _MessageViewState();
|
||||
}
|
||||
|
||||
class _MessageViewState extends State<MessageView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leadingWidth: 64.0,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
elevation: 0,
|
||||
actions: const [
|
||||
// Padding(
|
||||
// padding: EdgeInsets.only(right: 8.0),
|
||||
// child: IconButton(
|
||||
// onPressed: () {},
|
||||
// icon: Icon(FeatherIcons.archive, color: AppColors.of(context).text),
|
||||
// splashRadius: 32.0,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: widget.messages.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: MessageViewTile(widget.messages[index]),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,122 +1,122 @@
|
||||
import 'package:filcnaplo/api/providers/user_provider.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/message.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/message/attachment_tile.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||
// import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'message_view_tile.i18n.dart';
|
||||
|
||||
class MessageViewTile extends StatelessWidget {
|
||||
const MessageViewTile(this.message, {Key? key}) : super(key: key);
|
||||
|
||||
final Message message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
UserProvider user = Provider.of<UserProvider>(context, listen: false);
|
||||
String recipientLabel = "";
|
||||
|
||||
if (message.recipients.any((r) => r.name == user.student?.name)) recipientLabel = "me".i18n;
|
||||
|
||||
if (recipientLabel != "" && message.recipients.length > 1) {
|
||||
recipientLabel += " +";
|
||||
recipientLabel += message.recipients.where((r) => r.name != user.student?.name).length.toString();
|
||||
}
|
||||
|
||||
if (recipientLabel == "") {
|
||||
// note: convertint to set to remove duplicates
|
||||
recipientLabel += message.recipients.map((r) => r.name).toSet().join(", ");
|
||||
}
|
||||
|
||||
List<Widget> attachments = [];
|
||||
for (var a in message.attachments) {
|
||||
attachments.add(AttachmentTile(a));
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Subject
|
||||
Text(
|
||||
message.subject,
|
||||
softWrap: true,
|
||||
maxLines: 10,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 24.0,
|
||||
),
|
||||
),
|
||||
|
||||
// Author
|
||||
ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: ProfileImage(
|
||||
name: message.author,
|
||||
backgroundColor: ColorUtils.stringToColor(message.author),
|
||||
),
|
||||
title: Text(
|
||||
message.author,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
subtitle: Text(
|
||||
"to".i18n + " " + recipientLabel,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
// IconButton(
|
||||
// onPressed: () {},
|
||||
// icon: Icon(FeatherIcons.cornerUpLeft, color: AppColors.of(context).text),
|
||||
// splashRadius: 24.0,
|
||||
// padding: EdgeInsets.zero,
|
||||
// visualDensity: VisualDensity.compact,
|
||||
// ),
|
||||
// IconButton(
|
||||
// onPressed: () {},
|
||||
// icon: Icon(FeatherIcons.share2, color: AppColors.of(context).text),
|
||||
// splashRadius: 24.0,
|
||||
// padding: EdgeInsets.zero,
|
||||
// visualDensity: VisualDensity.compact,
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Content
|
||||
Panel(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: SelectableLinkify(
|
||||
text: message.content.escapeHtml(),
|
||||
options: const LinkifyOptions(looseUrl: true, removeWww: true),
|
||||
onOpen: (link) {
|
||||
launch(link.url,
|
||||
customTabsOption: CustomTabsOption(
|
||||
toolbarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
showPageTitle: true,
|
||||
));
|
||||
},
|
||||
style: const TextStyle(fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
|
||||
// Attachments
|
||||
...attachments,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/api/providers/user_provider.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/message.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/message/attachment_tile.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||
// import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'message_view_tile.i18n.dart';
|
||||
|
||||
class MessageViewTile extends StatelessWidget {
|
||||
const MessageViewTile(this.message, {Key? key}) : super(key: key);
|
||||
|
||||
final Message message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
UserProvider user = Provider.of<UserProvider>(context, listen: false);
|
||||
String recipientLabel = "";
|
||||
|
||||
if (message.recipients.any((r) => r.name == user.student?.name)) recipientLabel = "me".i18n;
|
||||
|
||||
if (recipientLabel != "" && message.recipients.length > 1) {
|
||||
recipientLabel += " +";
|
||||
recipientLabel += message.recipients.where((r) => r.name != user.student?.name).length.toString();
|
||||
}
|
||||
|
||||
if (recipientLabel == "") {
|
||||
// note: convertint to set to remove duplicates
|
||||
recipientLabel += message.recipients.map((r) => r.name).toSet().join(", ");
|
||||
}
|
||||
|
||||
List<Widget> attachments = [];
|
||||
for (var a in message.attachments) {
|
||||
attachments.add(AttachmentTile(a));
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Subject
|
||||
Text(
|
||||
message.subject,
|
||||
softWrap: true,
|
||||
maxLines: 10,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 24.0,
|
||||
),
|
||||
),
|
||||
|
||||
// Author
|
||||
ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: ProfileImage(
|
||||
name: message.author,
|
||||
backgroundColor: ColorUtils.stringToColor(message.author),
|
||||
),
|
||||
title: Text(
|
||||
message.author,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
subtitle: Text(
|
||||
"to".i18n + " " + recipientLabel,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
// IconButton(
|
||||
// onPressed: () {},
|
||||
// icon: Icon(FeatherIcons.cornerUpLeft, color: AppColors.of(context).text),
|
||||
// splashRadius: 24.0,
|
||||
// padding: EdgeInsets.zero,
|
||||
// visualDensity: VisualDensity.compact,
|
||||
// ),
|
||||
// IconButton(
|
||||
// onPressed: () {},
|
||||
// icon: Icon(FeatherIcons.share2, color: AppColors.of(context).text),
|
||||
// splashRadius: 24.0,
|
||||
// padding: EdgeInsets.zero,
|
||||
// visualDensity: VisualDensity.compact,
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Content
|
||||
Panel(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: SelectableLinkify(
|
||||
text: message.content.escapeHtml(),
|
||||
options: const LinkifyOptions(looseUrl: true, removeWww: true),
|
||||
onOpen: (link) {
|
||||
launch(link.url,
|
||||
customTabsOption: CustomTabsOption(
|
||||
toolbarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
showPageTitle: true,
|
||||
));
|
||||
},
|
||||
style: const TextStyle(fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
|
||||
// Attachments
|
||||
...attachments,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"me": "me",
|
||||
"to": "to",
|
||||
},
|
||||
"hu_hu": {
|
||||
"me": "én",
|
||||
"to": "Címzett:",
|
||||
},
|
||||
"de_de": {
|
||||
"me": "mich",
|
||||
"to": "zu",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"me": "me",
|
||||
"to": "to",
|
||||
},
|
||||
"hu_hu": {
|
||||
"me": "én",
|
||||
"to": "Címzett:",
|
||||
},
|
||||
"de_de": {
|
||||
"me": "mich",
|
||||
"to": "zu",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/message.dart';
|
||||
import 'package:filcnaplo/ui/widgets/message/message_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/message/message_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MessageViewable extends StatelessWidget {
|
||||
const MessageViewable(this.message, {Key? key}) : super(key: key);
|
||||
|
||||
final Message message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return OpenContainer(
|
||||
openBuilder: (context, _) {
|
||||
return MessageView([message]);
|
||||
},
|
||||
closedBuilder: (context, VoidCallback openContainer) {
|
||||
return MessageTile(message);
|
||||
},
|
||||
closedElevation: 0,
|
||||
openShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
closedShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
middleColor: Theme.of(context).colorScheme.background,
|
||||
openColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
closedColor: Theme.of(context).colorScheme.background,
|
||||
transitionType: ContainerTransitionType.fadeThrough,
|
||||
transitionDuration: const Duration(milliseconds: 400),
|
||||
useRootNavigator: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/message.dart';
|
||||
import 'package:filcnaplo/ui/widgets/message/message_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/message/message_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MessageViewable extends StatelessWidget {
|
||||
const MessageViewable(this.message, {Key? key}) : super(key: key);
|
||||
|
||||
final Message message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return OpenContainer(
|
||||
openBuilder: (context, _) {
|
||||
return MessageView([message]);
|
||||
},
|
||||
closedBuilder: (context, VoidCallback openContainer) {
|
||||
return MessageTile(message);
|
||||
},
|
||||
closedElevation: 0,
|
||||
openShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
closedShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
middleColor: Theme.of(context).colorScheme.background,
|
||||
openColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
closedColor: Theme.of(context).colorScheme.background,
|
||||
transitionType: ContainerTransitionType.fadeThrough,
|
||||
transitionDuration: const Duration(milliseconds: 400),
|
||||
useRootNavigator: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
import 'package:filcnaplo_kreta_api/models/note.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'miss_tile.i18n.dart';
|
||||
|
||||
class MissTile extends StatelessWidget {
|
||||
const MissTile(this.note, {Key? key}) : super(key: key);
|
||||
|
||||
final Note note;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: Icon(_missIcon(), color: Theme.of(context).colorScheme.secondary, size: 36.0),
|
||||
visualDensity: VisualDensity.compact,
|
||||
title: Text(
|
||||
_missName(),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
note.content.split("órán nem volt")[0].capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
IconData _missIcon() {
|
||||
if (note.type?.name == "HaziFeladatHiany") {
|
||||
return FeatherIcons.home;
|
||||
} else if (note.type?.name == "Felszereleshiany") {
|
||||
return FeatherIcons.book;
|
||||
}
|
||||
return FeatherIcons.slash;
|
||||
}
|
||||
|
||||
String _missName() {
|
||||
if (note.type?.name == "HaziFeladatHiany") {
|
||||
return "Missing homework".i18n;
|
||||
} else if (note.type?.name == "Felszereleshiany") {
|
||||
return "Missing equipment".i18n;
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/note.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'miss_tile.i18n.dart';
|
||||
|
||||
class MissTile extends StatelessWidget {
|
||||
const MissTile(this.note, {Key? key}) : super(key: key);
|
||||
|
||||
final Note note;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: Icon(_missIcon(), color: Theme.of(context).colorScheme.secondary, size: 36.0),
|
||||
visualDensity: VisualDensity.compact,
|
||||
title: Text(
|
||||
_missName(),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
note.content.split("órán nem volt")[0].capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
IconData _missIcon() {
|
||||
if (note.type?.name == "HaziFeladatHiany") {
|
||||
return FeatherIcons.home;
|
||||
} else if (note.type?.name == "Felszereleshiany") {
|
||||
return FeatherIcons.book;
|
||||
}
|
||||
return FeatherIcons.slash;
|
||||
}
|
||||
|
||||
String _missName() {
|
||||
if (note.type?.name == "HaziFeladatHiany") {
|
||||
return "Missing homework".i18n;
|
||||
} else if (note.type?.name == "Felszereleshiany") {
|
||||
return "Missing equipment".i18n;
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Missing homework": "Missing homework",
|
||||
"Missing equipment": "Missing equipment",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Missing homework": "Házi feladat hiány",
|
||||
"Missing equipment": "Felszerelés Hiány",
|
||||
},
|
||||
"de_de": {
|
||||
"Missing homework": "Fehlende Hausaufgaben",
|
||||
"Missing equipment": "Fehlende Ausrüstung",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Missing homework": "Missing homework",
|
||||
"Missing equipment": "Missing equipment",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Missing homework": "Házi feladat hiány",
|
||||
"Missing equipment": "Felszerelés Hiány",
|
||||
},
|
||||
"de_de": {
|
||||
"Missing homework": "Fehlende Hausaufgaben",
|
||||
"Missing equipment": "Fehlende Ausrüstung",
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'missed_exam_tile.i18n.dart';
|
||||
|
||||
class MissedExamTile extends StatelessWidget {
|
||||
const MissedExamTile(this.missedExams, {Key? key, this.onTap, this.padding}) : super(key: key);
|
||||
|
||||
final List<Lesson> missedExams;
|
||||
final Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: PanelButton(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6),
|
||||
leading: SizedBox(
|
||||
width: 36,
|
||||
height: 36,
|
||||
child: Icon(
|
||||
FeatherIcons.slash,
|
||||
color: AppColors.of(context).red.withOpacity(.75),
|
||||
size: 28.0,
|
||||
)),
|
||||
title: Text("missed_exams".plural(missedExams.length).fill([missedExams.length])),
|
||||
trailing: const Icon(FeatherIcons.arrowRight),
|
||||
onPressed: onTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'missed_exam_tile.i18n.dart';
|
||||
|
||||
class MissedExamTile extends StatelessWidget {
|
||||
const MissedExamTile(this.missedExams, {Key? key, this.onTap, this.padding}) : super(key: key);
|
||||
|
||||
final List<Lesson> missedExams;
|
||||
final Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: PanelButton(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6),
|
||||
leading: SizedBox(
|
||||
width: 36,
|
||||
height: 36,
|
||||
child: Icon(
|
||||
FeatherIcons.slash,
|
||||
color: AppColors.of(context).red.withOpacity(.75),
|
||||
size: 28.0,
|
||||
)),
|
||||
title: Text("missed_exams".plural(missedExams.length).fill([missedExams.length])),
|
||||
trailing: const Icon(FeatherIcons.arrowRight),
|
||||
onPressed: onTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +1,63 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"goodmorning": "Good morning, %s!",
|
||||
"goodafternoon": "Good afternoon, %s!",
|
||||
"goodevening": "Good evening, %s!",
|
||||
"goodrest": "⛱️ Have a nice holiday, %s!",
|
||||
"happybirthday": "🎂 Happy birthday, %s!",
|
||||
"merryxmas": "🎄 Merry Christmas, %s!",
|
||||
"happynewyear": "🎉 Happy New Year, %s!",
|
||||
"empty": "Nothing to see here.",
|
||||
"All": "All",
|
||||
"Grades": "Grades",
|
||||
"Messages": "Messages",
|
||||
"Absences": "Absences",
|
||||
"update_available": "Update Available",
|
||||
"missed_exams": "You missed %s exams this week.".one("You missed an exam this week."),
|
||||
"missed_exam_contact": "Contact %s, to resolve it!",
|
||||
},
|
||||
"hu_hu": {
|
||||
"goodmorning": "Jó reggelt, %s!",
|
||||
"goodafternoon": "Szép napot, %s!",
|
||||
"goodevening": "Szép estét, %s!",
|
||||
"goodrest": "⛱️ Jó szünetet, %s!",
|
||||
"happybirthday": "🎂 Boldog születésnapot, %s!",
|
||||
"merryxmas": "🎄 Boldog Karácsonyt, %s!",
|
||||
"happynewyear": "🎉 Boldog új évet, %s!",
|
||||
"empty": "Nincs itt semmi látnivaló.",
|
||||
"All": "Összes",
|
||||
"Grades": "Jegyek",
|
||||
"Messages": "Üzenetek",
|
||||
"Absences": "Hiányok",
|
||||
"update_available": "Frissítés elérhető",
|
||||
"missed_exams": "Ezen a héten hiányoztál %s dolgozatról.".one("Ezen a héten hiányoztál egy dolgozatról."),
|
||||
"missed_exam_contact": "Keresd %s-t, ha pótolni szeretnéd!",
|
||||
},
|
||||
"de_de": {
|
||||
"goodmorning": "Guten morgen, %s!",
|
||||
"goodafternoon": "Guten Tag, %s!",
|
||||
"goodevening": "Guten Abend, %s!",
|
||||
"goodrest": "⛱️ Schöne Ferien, %s!",
|
||||
"happybirthday": "🎂 Alles Gute zum Geburtstag, %s!",
|
||||
"merryxmas": "🎄 Frohe Weihnachten, %s!",
|
||||
"happynewyear": "🎉 Frohes neues Jahr, %s!",
|
||||
"empty": "Hier gibt es nichts zu sehen.",
|
||||
"All": "Alles",
|
||||
"Grades": "Noten",
|
||||
"Messages": "Nachrichten",
|
||||
"Absences": "Fehlen",
|
||||
"update_available": "Update verfügbar",
|
||||
"missed_exams": "Diese Woche haben Sie %s Prüfungen verpasst.".one("Diese Woche haben Sie eine Prüfung verpasst."),
|
||||
"missed_exam_contact": "Wenden Sie sich an %s, um sie zu erneuern!",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"goodmorning": "Good morning, %s!",
|
||||
"goodafternoon": "Good afternoon, %s!",
|
||||
"goodevening": "Good evening, %s!",
|
||||
"goodrest": "⛱️ Have a nice holiday, %s!",
|
||||
"happybirthday": "🎂 Happy birthday, %s!",
|
||||
"merryxmas": "🎄 Merry Christmas, %s!",
|
||||
"happynewyear": "🎉 Happy New Year, %s!",
|
||||
"empty": "Nothing to see here.",
|
||||
"All": "All",
|
||||
"Grades": "Grades",
|
||||
"Messages": "Messages",
|
||||
"Absences": "Absences",
|
||||
"update_available": "Update Available",
|
||||
"missed_exams": "You missed %s exams this week.".one("You missed an exam this week."),
|
||||
"missed_exam_contact": "Contact %s, to resolve it!",
|
||||
},
|
||||
"hu_hu": {
|
||||
"goodmorning": "Jó reggelt, %s!",
|
||||
"goodafternoon": "Szép napot, %s!",
|
||||
"goodevening": "Szép estét, %s!",
|
||||
"goodrest": "⛱️ Jó szünetet, %s!",
|
||||
"happybirthday": "🎂 Boldog születésnapot, %s!",
|
||||
"merryxmas": "🎄 Boldog Karácsonyt, %s!",
|
||||
"happynewyear": "🎉 Boldog új évet, %s!",
|
||||
"empty": "Nincs itt semmi látnivaló.",
|
||||
"All": "Összes",
|
||||
"Grades": "Jegyek",
|
||||
"Messages": "Üzenetek",
|
||||
"Absences": "Hiányok",
|
||||
"update_available": "Frissítés elérhető",
|
||||
"missed_exams": "Ezen a héten hiányoztál %s dolgozatról.".one("Ezen a héten hiányoztál egy dolgozatról."),
|
||||
"missed_exam_contact": "Keresd %s-t, ha pótolni szeretnéd!",
|
||||
},
|
||||
"de_de": {
|
||||
"goodmorning": "Guten morgen, %s!",
|
||||
"goodafternoon": "Guten Tag, %s!",
|
||||
"goodevening": "Guten Abend, %s!",
|
||||
"goodrest": "⛱️ Schöne Ferien, %s!",
|
||||
"happybirthday": "🎂 Alles Gute zum Geburtstag, %s!",
|
||||
"merryxmas": "🎄 Frohe Weihnachten, %s!",
|
||||
"happynewyear": "🎉 Frohes neues Jahr, %s!",
|
||||
"empty": "Hier gibt es nichts zu sehen.",
|
||||
"All": "Alles",
|
||||
"Grades": "Noten",
|
||||
"Messages": "Nachrichten",
|
||||
"Absences": "Fehlen",
|
||||
"update_available": "Update verfügbar",
|
||||
"missed_exams": "Diese Woche haben Sie %s Prüfungen verpasst.".one("Diese Woche haben Sie eine Prüfung verpasst."),
|
||||
"missed_exam_contact": "Wenden Sie sich an %s, um sie zu erneuern!",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/timetable/timetable_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'missed_exam_tile.i18n.dart';
|
||||
|
||||
class MissedExamView extends StatelessWidget {
|
||||
const MissedExamView(this.missedExams, {Key? key}) : super(key: key);
|
||||
|
||||
final List<Lesson> missedExams;
|
||||
|
||||
static show(List<Lesson> missedExams, {required BuildContext context}) => showRoundedModalBottomSheet(context, child: MissedExamView(missedExams));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> tiles = missedExams.map((e) => MissedExamViewTile(e)).toList();
|
||||
return Column(children: tiles);
|
||||
}
|
||||
}
|
||||
|
||||
class MissedExamViewTile extends StatelessWidget {
|
||||
const MissedExamViewTile(this.lesson, {Key? key, this.padding}) : super(key: key);
|
||||
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final Lesson lesson;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||
child: ListTile(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
leading: Icon(
|
||||
SubjectIcon.resolveVariant(subject: lesson.subject, context: context),
|
||||
color: AppColors.of(context).text.withOpacity(.8),
|
||||
size: 32.0,
|
||||
),
|
||||
title: Text(
|
||||
"${lesson.subject.renamedTo ?? lesson.subject.name.capital()} • ${lesson.date.format(context)}",
|
||||
style: TextStyle(fontWeight: FontWeight.w600, fontStyle: lesson.subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
subtitle: Text(
|
||||
"missed_exam_contact".i18n.fill([lesson.teacher]),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: const Icon(FeatherIcons.arrowRight),
|
||||
onTap: () {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
TimetablePage.jump(context, lesson: lesson);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/timetable/timetable_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'missed_exam_tile.i18n.dart';
|
||||
|
||||
class MissedExamView extends StatelessWidget {
|
||||
const MissedExamView(this.missedExams, {Key? key}) : super(key: key);
|
||||
|
||||
final List<Lesson> missedExams;
|
||||
|
||||
static show(List<Lesson> missedExams, {required BuildContext context}) => showRoundedModalBottomSheet(context, child: MissedExamView(missedExams));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> tiles = missedExams.map((e) => MissedExamViewTile(e)).toList();
|
||||
return Column(children: tiles);
|
||||
}
|
||||
}
|
||||
|
||||
class MissedExamViewTile extends StatelessWidget {
|
||||
const MissedExamViewTile(this.lesson, {Key? key, this.padding}) : super(key: key);
|
||||
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final Lesson lesson;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||
child: ListTile(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
leading: Icon(
|
||||
SubjectIcon.resolveVariant(subject: lesson.subject, context: context),
|
||||
color: AppColors.of(context).text.withOpacity(.8),
|
||||
size: 32.0,
|
||||
),
|
||||
title: Text(
|
||||
"${lesson.subject.renamedTo ?? lesson.subject.name.capital()} • ${lesson.date.format(context)}",
|
||||
style: TextStyle(fontWeight: FontWeight.w600, fontStyle: lesson.subject.isRenamed ? FontStyle.italic : null),
|
||||
),
|
||||
subtitle: Text(
|
||||
"missed_exam_contact".i18n.fill([lesson.teacher]),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: const Icon(FeatherIcons.arrowRight),
|
||||
onTap: () {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
TimetablePage.jump(context, lesson: lesson);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/missed_exam/missed_exam_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/missed_exam/missed_exam_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MissedExamViewable extends StatelessWidget {
|
||||
const MissedExamViewable(this.missedExams, {Key? key}) : super(key: key);
|
||||
|
||||
final List<Lesson> missedExams;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MissedExamTile(
|
||||
missedExams,
|
||||
onTap: () => MissedExamView.show(missedExams, context: context),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/missed_exam/missed_exam_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/missed_exam/missed_exam_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MissedExamViewable extends StatelessWidget {
|
||||
const MissedExamViewable(this.missedExams, {Key? key}) : super(key: key);
|
||||
|
||||
final List<Lesson> missedExams;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MissedExamTile(
|
||||
missedExams,
|
||||
onTap: () => MissedExamView.show(missedExams, context: context),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/note.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NoteTile extends StatelessWidget {
|
||||
const NoteTile(this.note, {Key? key, this.onTap, this.padding}) : super(key: key);
|
||||
|
||||
final Note note;
|
||||
final void Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
onTap: onTap,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
|
||||
leading: ProfileImage(
|
||||
name: note.teacher,
|
||||
radius: 22.0,
|
||||
backgroundColor: ColorUtils.stringToColor(note.teacher),
|
||||
),
|
||||
title: Text(
|
||||
note.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
note.content.replaceAll('\n', ' '),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/note.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NoteTile extends StatelessWidget {
|
||||
const NoteTile(this.note, {Key? key, this.onTap, this.padding}) : super(key: key);
|
||||
|
||||
final Note note;
|
||||
final void Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0, right: 12.0),
|
||||
onTap: onTap,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)),
|
||||
leading: ProfileImage(
|
||||
name: note.teacher,
|
||||
radius: 22.0,
|
||||
backgroundColor: ColorUtils.stringToColor(note.teacher),
|
||||
),
|
||||
title: Text(
|
||||
note.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
note.content.replaceAll('\n', ' '),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +1,72 @@
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/note.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/sliding_bottom_sheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
|
||||
class NoteView extends StatelessWidget {
|
||||
const NoteView(this.note, {Key? key}) : super(key: key);
|
||||
|
||||
final Note note;
|
||||
|
||||
static void show(Note note, {required BuildContext context}) => showSlidingBottomSheet(context: context, child: NoteView(note));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header
|
||||
ListTile(
|
||||
leading: ProfileImage(
|
||||
name: note.teacher,
|
||||
radius: 22.0,
|
||||
backgroundColor: ColorUtils.stringToColor(note.teacher),
|
||||
),
|
||||
title: Text(
|
||||
note.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
note.teacher,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: Text(
|
||||
note.date.format(context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
// Details
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SelectableLinkify(
|
||||
text: note.content.escapeHtml(),
|
||||
options: const LinkifyOptions(looseUrl: true, removeWww: true),
|
||||
onOpen: (link) {
|
||||
launch(link.url,
|
||||
customTabsOption: CustomTabsOption(
|
||||
toolbarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
showPageTitle: true,
|
||||
));
|
||||
},
|
||||
style: const TextStyle(fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/note.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/sliding_bottom_sheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
|
||||
class NoteView extends StatelessWidget {
|
||||
const NoteView(this.note, {Key? key}) : super(key: key);
|
||||
|
||||
final Note note;
|
||||
|
||||
static void show(Note note, {required BuildContext context}) => showSlidingBottomSheet(context: context, child: NoteView(note));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header
|
||||
ListTile(
|
||||
leading: ProfileImage(
|
||||
name: note.teacher,
|
||||
radius: 22.0,
|
||||
backgroundColor: ColorUtils.stringToColor(note.teacher),
|
||||
),
|
||||
title: Text(
|
||||
note.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
note.teacher,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: Text(
|
||||
note.date.format(context),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
|
||||
// Details
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SelectableLinkify(
|
||||
text: note.content.escapeHtml(),
|
||||
options: const LinkifyOptions(looseUrl: true, removeWww: true),
|
||||
onOpen: (link) {
|
||||
launch(link.url,
|
||||
customTabsOption: CustomTabsOption(
|
||||
toolbarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
showPageTitle: true,
|
||||
));
|
||||
},
|
||||
style: const TextStyle(fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import 'package:filcnaplo_kreta_api/models/note.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/note/note_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/note/note_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NoteViewable extends StatelessWidget {
|
||||
const NoteViewable(this.note, {Key? key}) : super(key: key);
|
||||
|
||||
final Note note;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NoteTile(
|
||||
note,
|
||||
onTap: () => NoteView.show(note, context: context),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/note.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/note/note_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/note/note_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NoteViewable extends StatelessWidget {
|
||||
const NoteViewable(this.note, {Key? key}) : super(key: key);
|
||||
|
||||
final Note note;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NoteTile(
|
||||
note,
|
||||
onTap: () => NoteView.show(note, context: context),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,107 +1,107 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:i18n_extension/i18n_widget.dart';
|
||||
|
||||
class StatisticsTile extends StatelessWidget {
|
||||
const StatisticsTile({
|
||||
Key? key,
|
||||
required this.value,
|
||||
this.title,
|
||||
this.decimal = true,
|
||||
this.color,
|
||||
this.valueSuffix = '',
|
||||
this.fill = false,
|
||||
this.outline = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final double value;
|
||||
final Widget? title;
|
||||
final bool decimal;
|
||||
final Color? color;
|
||||
final String valueSuffix;
|
||||
final bool fill;
|
||||
final bool outline;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String valueText;
|
||||
if (decimal) {
|
||||
valueText = value.toStringAsFixed(2);
|
||||
} else {
|
||||
valueText = value.toStringAsFixed(0);
|
||||
}
|
||||
if (I18n.of(context).locale.languageCode != "en") valueText = valueText.replaceAll(".", ",");
|
||||
|
||||
if (value.isNaN) {
|
||||
valueText = "?";
|
||||
}
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(18.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
offset: const Offset(0, 21),
|
||||
blurRadius: 23.0,
|
||||
color: Theme.of(context).shadowColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 140.0,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
if (title != null)
|
||||
DefaultTextStyle(
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18.0,
|
||||
),
|
||||
child: title!,
|
||||
),
|
||||
if (title != null) const SizedBox(height: 4.0),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 4.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0),
|
||||
decoration: BoxDecoration(
|
||||
color: fill ? (color ?? gradeColor(context: context, value: value)).withOpacity(.2) : null,
|
||||
border: outline || fill
|
||||
? Border.all(
|
||||
color: (color ?? gradeColor(context: context, value: value)).withOpacity(outline ? 1.0 : 0.0),
|
||||
width: fill ? 2.0 : 5.0,
|
||||
)
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
child: AutoSizeText.rich(
|
||||
TextSpan(
|
||||
text: valueText,
|
||||
children: [
|
||||
if (valueSuffix != "")
|
||||
TextSpan(
|
||||
text: valueSuffix,
|
||||
style: const TextStyle(fontSize: 24.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
maxLines: 1,
|
||||
minFontSize: 5,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: color ?? gradeColor(context: context, value: value),
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 32.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:i18n_extension/i18n_widget.dart';
|
||||
|
||||
class StatisticsTile extends StatelessWidget {
|
||||
const StatisticsTile({
|
||||
Key? key,
|
||||
required this.value,
|
||||
this.title,
|
||||
this.decimal = true,
|
||||
this.color,
|
||||
this.valueSuffix = '',
|
||||
this.fill = false,
|
||||
this.outline = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final double value;
|
||||
final Widget? title;
|
||||
final bool decimal;
|
||||
final Color? color;
|
||||
final String valueSuffix;
|
||||
final bool fill;
|
||||
final bool outline;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String valueText;
|
||||
if (decimal) {
|
||||
valueText = value.toStringAsFixed(2);
|
||||
} else {
|
||||
valueText = value.toStringAsFixed(0);
|
||||
}
|
||||
if (I18n.of(context).locale.languageCode != "en") valueText = valueText.replaceAll(".", ",");
|
||||
|
||||
if (value.isNaN) {
|
||||
valueText = "?";
|
||||
}
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(18.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
offset: const Offset(0, 21),
|
||||
blurRadius: 23.0,
|
||||
color: Theme.of(context).shadowColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 140.0,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
if (title != null)
|
||||
DefaultTextStyle(
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18.0,
|
||||
),
|
||||
child: title!,
|
||||
),
|
||||
if (title != null) const SizedBox(height: 4.0),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 4.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0),
|
||||
decoration: BoxDecoration(
|
||||
color: fill ? (color ?? gradeColor(context: context, value: value)).withOpacity(.2) : null,
|
||||
border: outline || fill
|
||||
? Border.all(
|
||||
color: (color ?? gradeColor(context: context, value: value)).withOpacity(outline ? 1.0 : 0.0),
|
||||
width: fill ? 2.0 : 5.0,
|
||||
)
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
child: AutoSizeText.rich(
|
||||
TextSpan(
|
||||
text: valueText,
|
||||
children: [
|
||||
if (valueSuffix != "")
|
||||
TextSpan(
|
||||
text: valueSuffix,
|
||||
style: const TextStyle(fontSize: 24.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
maxLines: 1,
|
||||
minFontSize: 5,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: color ?? gradeColor(context: context, value: value),
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 32.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
import 'package:filcnaplo/models/release.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'update_tile.i18n.dart';
|
||||
|
||||
class UpdateTile extends StatelessWidget {
|
||||
const UpdateTile(this.release, {Key? key, this.onTap, this.padding}) : super(key: key);
|
||||
|
||||
final Release release;
|
||||
final Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: PanelButton(
|
||||
onPressed: onTap,
|
||||
title: Text("update_available".i18n),
|
||||
leading: const Icon(FeatherIcons.download),
|
||||
trailing: Text(
|
||||
release.tag,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/models/release.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'update_tile.i18n.dart';
|
||||
|
||||
class UpdateTile extends StatelessWidget {
|
||||
const UpdateTile(this.release, {Key? key, this.onTap, this.padding}) : super(key: key);
|
||||
|
||||
final Release release;
|
||||
final Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: PanelButton(
|
||||
onPressed: onTap,
|
||||
title: Text("update_available".i18n),
|
||||
leading: const Icon(FeatherIcons.download),
|
||||
trailing: Text(
|
||||
release.tag,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"update_available": "Update Available",
|
||||
},
|
||||
"hu_hu": {
|
||||
"update_available": "Frissítés elérhető",
|
||||
},
|
||||
"de_de": {
|
||||
"update_available": "Update verfügbar",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"update_available": "Update Available",
|
||||
},
|
||||
"hu_hu": {
|
||||
"update_available": "Frissítés elérhető",
|
||||
},
|
||||
"de_de": {
|
||||
"update_available": "Update verfügbar",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import 'package:filcnaplo/models/release.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/update/update_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/update/updates_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class UpdateViewable extends StatelessWidget {
|
||||
const UpdateViewable(this.release, {Key? key}) : super(key: key);
|
||||
|
||||
final Release release;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UpdateTile(
|
||||
release,
|
||||
onTap: () => UpdateView.show(release, context: context),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/models/release.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/update/update_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/update/updates_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class UpdateViewable extends StatelessWidget {
|
||||
const UpdateViewable(this.release, {Key? key}) : super(key: key);
|
||||
|
||||
final Release release;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UpdateTile(
|
||||
release,
|
||||
onTap: () => UpdateView.show(release, context: context),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,170 +1,170 @@
|
||||
import 'package:filcnaplo/api/providers/status_provider.dart';
|
||||
import 'package:filcnaplo/models/release.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_card.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/material_action_button.dart';
|
||||
import 'package:filcnaplo/helpers/update_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'updates_view.i18n.dart';
|
||||
|
||||
class UpdateView extends StatefulWidget {
|
||||
const UpdateView(this.release, {Key? key}) : super(key: key);
|
||||
|
||||
final Release release;
|
||||
|
||||
static void show(Release release, {required BuildContext context}) => showBottomCard(context: context, child: UpdateView(release));
|
||||
|
||||
@override
|
||||
_UpdateViewState createState() => _UpdateViewState();
|
||||
}
|
||||
|
||||
class _UpdateViewState extends State<UpdateView> {
|
||||
double progress = 0.0;
|
||||
UpdateState state = UpdateState.none;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"new_update".i18n,
|
||||
style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 18.0),
|
||||
),
|
||||
Text(
|
||||
"${widget.release.version}",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(18.0),
|
||||
child: Image.asset(
|
||||
"assets/icons/ic_launcher.png",
|
||||
width: 64.0,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Description
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 8.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 125.0,
|
||||
child: Markdown(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
data: widget.release.body,
|
||||
onTapLink: (text, href, title) => launch(href ?? ""),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Download button
|
||||
Center(
|
||||
child: MaterialActionButton(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (state == UpdateState.downloading || state == UpdateState.preparing)
|
||||
Container(
|
||||
height: 18.0,
|
||||
width: 18.0,
|
||||
margin: const EdgeInsets.only(right: 8.0),
|
||||
child: CircularProgressIndicator(
|
||||
value: progress > 0.05 ? progress : null,
|
||||
color: ColorUtils.foregroundColor(AppColors.of(context).filc),
|
||||
),
|
||||
),
|
||||
Text(["download".i18n, "downloading".i18n, "downloading".i18n, "installing".i18n][state.index].toUpperCase()),
|
||||
],
|
||||
),
|
||||
backgroundColor: AppColors.of(context).filc,
|
||||
onPressed: state == UpdateState.none ? () => downloadPrecheck() : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String fmtSize() => "${(widget.release.downloads.first.size / 1024 / 1024).toStringAsFixed(1)} MB";
|
||||
|
||||
void downloadPrecheck() {
|
||||
final status = Provider.of<StatusProvider>(context, listen: false);
|
||||
if (status.networkType == ConnectivityResult.mobile) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text("mobileAlertTitle".i18n),
|
||||
content: Text("mobileAlertDesc".i18n.fill([fmtSize()])),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Text("no".i18n),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: Text("yes".i18n),
|
||||
),
|
||||
],
|
||||
),
|
||||
).then((value) => value ? download() : null);
|
||||
} else {
|
||||
download();
|
||||
}
|
||||
}
|
||||
|
||||
void download() {
|
||||
widget.release
|
||||
.install(updateCallback: (p, s) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
progress = p;
|
||||
state = s;
|
||||
});
|
||||
}
|
||||
})
|
||||
.then((_) => Navigator.of(context).maybePop())
|
||||
.catchError((error, stackTrace) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
|
||||
context: context,
|
||||
content: Text("error".i18n),
|
||||
backgroundColor: AppColors.of(context).red,
|
||||
));
|
||||
setState(() => state = UpdateState.none);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/api/providers/status_provider.dart';
|
||||
import 'package:filcnaplo/models/release.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_card.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/material_action_button.dart';
|
||||
import 'package:filcnaplo/helpers/update_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'updates_view.i18n.dart';
|
||||
|
||||
class UpdateView extends StatefulWidget {
|
||||
const UpdateView(this.release, {Key? key}) : super(key: key);
|
||||
|
||||
final Release release;
|
||||
|
||||
static void show(Release release, {required BuildContext context}) => showBottomCard(context: context, child: UpdateView(release));
|
||||
|
||||
@override
|
||||
_UpdateViewState createState() => _UpdateViewState();
|
||||
}
|
||||
|
||||
class _UpdateViewState extends State<UpdateView> {
|
||||
double progress = 0.0;
|
||||
UpdateState state = UpdateState.none;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"new_update".i18n,
|
||||
style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 18.0),
|
||||
),
|
||||
Text(
|
||||
"${widget.release.version}",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.6),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(18.0),
|
||||
child: Image.asset(
|
||||
"assets/icons/ic_launcher.png",
|
||||
width: 64.0,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Description
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 8.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 125.0,
|
||||
child: Markdown(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
data: widget.release.body,
|
||||
onTapLink: (text, href, title) => launch(href ?? ""),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Download button
|
||||
Center(
|
||||
child: MaterialActionButton(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (state == UpdateState.downloading || state == UpdateState.preparing)
|
||||
Container(
|
||||
height: 18.0,
|
||||
width: 18.0,
|
||||
margin: const EdgeInsets.only(right: 8.0),
|
||||
child: CircularProgressIndicator(
|
||||
value: progress > 0.05 ? progress : null,
|
||||
color: ColorUtils.foregroundColor(AppColors.of(context).filc),
|
||||
),
|
||||
),
|
||||
Text(["download".i18n, "downloading".i18n, "downloading".i18n, "installing".i18n][state.index].toUpperCase()),
|
||||
],
|
||||
),
|
||||
backgroundColor: AppColors.of(context).filc,
|
||||
onPressed: state == UpdateState.none ? () => downloadPrecheck() : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String fmtSize() => "${(widget.release.downloads.first.size / 1024 / 1024).toStringAsFixed(1)} MB";
|
||||
|
||||
void downloadPrecheck() {
|
||||
final status = Provider.of<StatusProvider>(context, listen: false);
|
||||
if (status.networkType == ConnectivityResult.mobile) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text("mobileAlertTitle".i18n),
|
||||
content: Text("mobileAlertDesc".i18n.fill([fmtSize()])),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Text("no".i18n),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: Text("yes".i18n),
|
||||
),
|
||||
],
|
||||
),
|
||||
).then((value) => value ? download() : null);
|
||||
} else {
|
||||
download();
|
||||
}
|
||||
}
|
||||
|
||||
void download() {
|
||||
widget.release
|
||||
.install(updateCallback: (p, s) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
progress = p;
|
||||
state = s;
|
||||
});
|
||||
}
|
||||
})
|
||||
.then((_) => Navigator.of(context).maybePop())
|
||||
.catchError((error, stackTrace) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
|
||||
context: context,
|
||||
content: Text("error".i18n),
|
||||
backgroundColor: AppColors.of(context).red,
|
||||
));
|
||||
setState(() => state = UpdateState.none);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"new_update": "New Update",
|
||||
"download": "download",
|
||||
"downloading": "downloading",
|
||||
"installing": "installing",
|
||||
"error": "Failed to install update!",
|
||||
"no": "No",
|
||||
"yes": "Yes",
|
||||
"mobileAlertTitle": "Hold up!",
|
||||
"mobileAlertDesc": "You're on mobile network trying to download a %s update. Are you sure you want to continue?"
|
||||
},
|
||||
"hu_hu": {
|
||||
"new_update": "Új frissítés",
|
||||
"download": "Letöltés",
|
||||
"downloading": "Letöltés",
|
||||
"installing": "Telepítés",
|
||||
"error": "Nem sikerült telepíteni a frissítést!",
|
||||
"no": "Nem",
|
||||
"yes": "Igen",
|
||||
"mobileAlertTitle": "Figyelem!",
|
||||
"mobileAlertDesc": "Jelenleg mobil interneten vagy, és egy %s méretű frissítést próbálsz letölteni. Biztosan folytatod?"
|
||||
},
|
||||
"de_de": {
|
||||
"new_update": "Neues Update",
|
||||
"download": "herunterladen",
|
||||
"downloading": "Herunterladen",
|
||||
"installing": "Installation",
|
||||
"error": "Update konnte nicht installiert werden!",
|
||||
"no": "Nein",
|
||||
"yes": "Ja",
|
||||
"mobileAlertTitle": "Achtung!",
|
||||
"mobileAlertDesc":
|
||||
"Sie befinden sich gerade im mobilen Internet und versuchen, ein %s Update herunterzuladen. Sind Sie sicher, dass Sie weitermachen wollen?"
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"new_update": "New Update",
|
||||
"download": "download",
|
||||
"downloading": "downloading",
|
||||
"installing": "installing",
|
||||
"error": "Failed to install update!",
|
||||
"no": "No",
|
||||
"yes": "Yes",
|
||||
"mobileAlertTitle": "Hold up!",
|
||||
"mobileAlertDesc": "You're on mobile network trying to download a %s update. Are you sure you want to continue?"
|
||||
},
|
||||
"hu_hu": {
|
||||
"new_update": "Új frissítés",
|
||||
"download": "Letöltés",
|
||||
"downloading": "Letöltés",
|
||||
"installing": "Telepítés",
|
||||
"error": "Nem sikerült telepíteni a frissítést!",
|
||||
"no": "Nem",
|
||||
"yes": "Igen",
|
||||
"mobileAlertTitle": "Figyelem!",
|
||||
"mobileAlertDesc": "Jelenleg mobil interneten vagy, és egy %s méretű frissítést próbálsz letölteni. Biztosan folytatod?"
|
||||
},
|
||||
"de_de": {
|
||||
"new_update": "Neues Update",
|
||||
"download": "herunterladen",
|
||||
"downloading": "Herunterladen",
|
||||
"installing": "Installation",
|
||||
"error": "Update konnte nicht installiert werden!",
|
||||
"no": "Nein",
|
||||
"yes": "Ja",
|
||||
"mobileAlertTitle": "Achtung!",
|
||||
"mobileAlertDesc":
|
||||
"Sie befinden sich gerade im mobilen Internet und versuchen, ein %s Update herunterzuladen. Sind Sie sicher, dass Sie weitermachen wollen?"
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,79 +1,79 @@
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo/utils/reverse_search.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/absence.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/subject.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/hero_scrollview.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/absences/absence_subject_view_container.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/timetable/timetable_page.dart';
|
||||
import 'package:filcnaplo/ui/filter/sort.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_view.i18n.dart';
|
||||
|
||||
class AbsenceSubjectView extends StatelessWidget {
|
||||
const AbsenceSubjectView(this.subject, {Key? key, this.absences = const []}) : super(key: key);
|
||||
|
||||
final Subject subject;
|
||||
final List<Absence> absences;
|
||||
|
||||
static void show(Subject subject, List<Absence> absences, {required BuildContext context}) {
|
||||
Navigator.of(context, rootNavigator: true)
|
||||
.push<Absence>(CupertinoPageRoute(builder: (context) => AbsenceSubjectView(subject, absences: absences)))
|
||||
.then((value) {
|
||||
if (value == null) return;
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 250)).then((_) {
|
||||
ReverseSearch.getLessonByAbsence(value, context).then((lesson) {
|
||||
if (lesson != null) {
|
||||
TimetablePage.jump(context, lesson: lesson);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
|
||||
content: Text("Cannot find lesson".i18n, style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: AppColors.of(context).red,
|
||||
context: context,
|
||||
));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final dateWidgets = absences
|
||||
.map((a) => DateWidget(
|
||||
widget: AbsenceViewable(a, padding: EdgeInsets.zero),
|
||||
date: a.date,
|
||||
))
|
||||
.toList();
|
||||
List<Widget> absenceTiles = sortDateWidgets(context, dateWidgets: dateWidgets, padding: EdgeInsets.zero, hasShadow: true);
|
||||
|
||||
return Scaffold(
|
||||
body: HeroScrollView(
|
||||
title: subject.renamedTo ?? subject.name.capital(),
|
||||
italic: subject.isRenamed,
|
||||
icon: SubjectIcon.resolveVariant(subject: subject, context: context),
|
||||
child: AbsenceSubjectViewContainer(
|
||||
child: CupertinoScrollbar(
|
||||
child: ListView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: absenceTiles[index],
|
||||
),
|
||||
itemCount: absenceTiles.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo/utils/reverse_search.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/absence.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/subject.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/hero_scrollview.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/absences/absence_subject_view_container.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/timetable/timetable_page.dart';
|
||||
import 'package:filcnaplo/ui/filter/sort.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_view.i18n.dart';
|
||||
|
||||
class AbsenceSubjectView extends StatelessWidget {
|
||||
const AbsenceSubjectView(this.subject, {Key? key, this.absences = const []}) : super(key: key);
|
||||
|
||||
final Subject subject;
|
||||
final List<Absence> absences;
|
||||
|
||||
static void show(Subject subject, List<Absence> absences, {required BuildContext context}) {
|
||||
Navigator.of(context, rootNavigator: true)
|
||||
.push<Absence>(CupertinoPageRoute(builder: (context) => AbsenceSubjectView(subject, absences: absences)))
|
||||
.then((value) {
|
||||
if (value == null) return;
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 250)).then((_) {
|
||||
ReverseSearch.getLessonByAbsence(value, context).then((lesson) {
|
||||
if (lesson != null) {
|
||||
TimetablePage.jump(context, lesson: lesson);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
|
||||
content: Text("Cannot find lesson".i18n, style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: AppColors.of(context).red,
|
||||
context: context,
|
||||
));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final dateWidgets = absences
|
||||
.map((a) => DateWidget(
|
||||
widget: AbsenceViewable(a, padding: EdgeInsets.zero),
|
||||
date: a.date,
|
||||
))
|
||||
.toList();
|
||||
List<Widget> absenceTiles = sortDateWidgets(context, dateWidgets: dateWidgets, padding: EdgeInsets.zero, hasShadow: true);
|
||||
|
||||
return Scaffold(
|
||||
body: HeroScrollView(
|
||||
title: subject.renamedTo ?? subject.name.capital(),
|
||||
italic: subject.isRenamed,
|
||||
icon: SubjectIcon.resolveVariant(subject: subject, context: context),
|
||||
child: AbsenceSubjectViewContainer(
|
||||
child: CupertinoScrollbar(
|
||||
child: ListView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: absenceTiles[index],
|
||||
),
|
||||
itemCount: absenceTiles.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AbsenceSubjectViewContainer extends InheritedWidget {
|
||||
const AbsenceSubjectViewContainer({Key? key, required Widget child}) : super(key: key, child: child);
|
||||
|
||||
static AbsenceSubjectViewContainer? of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<AbsenceSubjectViewContainer>();
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(AbsenceSubjectViewContainer oldWidget) => false;
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AbsenceSubjectViewContainer extends InheritedWidget {
|
||||
const AbsenceSubjectViewContainer({Key? key, required Widget child}) : super(key: key, child: child);
|
||||
|
||||
static AbsenceSubjectViewContainer? of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<AbsenceSubjectViewContainer>();
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(AbsenceSubjectViewContainer oldWidget) => false;
|
||||
}
|
||||
|
||||
@@ -1,382 +1,382 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:filcnaplo/api/providers/update_provider.dart';
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/absence.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/subject.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/week.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/absence_provider.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/note_provider.dart';
|
||||
import 'package:filcnaplo/api/providers/user_provider.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/action_button.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/empty.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/filter_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel.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/common/widgets/absence/absence_subject_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/statistics_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/miss_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/absences/absence_subject_view.dart';
|
||||
import 'package:filcnaplo/ui/filter/sort.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'absences_page.i18n.dart';
|
||||
|
||||
enum AbsenceFilter { absences, delays, misses }
|
||||
|
||||
class SubjectAbsence {
|
||||
Subject subject;
|
||||
List<Absence> absences;
|
||||
double percentage;
|
||||
|
||||
SubjectAbsence({required this.subject, this.absences = const [], this.percentage = 0.0});
|
||||
}
|
||||
|
||||
class AbsencesPage extends StatefulWidget {
|
||||
const AbsencesPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_AbsencesPageState createState() => _AbsencesPageState();
|
||||
}
|
||||
|
||||
class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMixin {
|
||||
late UserProvider user;
|
||||
late AbsenceProvider absenceProvider;
|
||||
late TimetableProvider timetableProvider;
|
||||
late NoteProvider noteProvider;
|
||||
late UpdateProvider updateProvider;
|
||||
late String firstName;
|
||||
late TabController _tabController;
|
||||
late List<SubjectAbsence> absences = [];
|
||||
final Map<Subject, Lesson> _lessonCount = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_tabController = TabController(length: 3, vsync: this);
|
||||
timetableProvider = Provider.of<TimetableProvider>(context, listen: false);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
for (final lesson in timetableProvider.getWeek(Week.current()) ?? []) {
|
||||
if (!lesson.isEmpty && lesson.subject.id != '' && lesson.lessonYearIndex != null) {
|
||||
_lessonCount.update(
|
||||
lesson.subject,
|
||||
(value) {
|
||||
if (lesson.lessonYearIndex! > value.lessonYearIndex!) {
|
||||
return lesson;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
ifAbsent: () => lesson,
|
||||
);
|
||||
}
|
||||
}
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
void buildSubjectAbsences() {
|
||||
Map<Subject, SubjectAbsence> _absences = {};
|
||||
|
||||
for (final absence in absenceProvider.absences) {
|
||||
if (absence.delay != 0) continue;
|
||||
|
||||
if (!_absences.containsKey(absence.subject)) {
|
||||
_absences[absence.subject] = SubjectAbsence(subject: absence.subject, absences: [absence]);
|
||||
} else {
|
||||
_absences[absence.subject]?.absences.add(absence);
|
||||
}
|
||||
}
|
||||
|
||||
_absences.forEach((subject, absence) {
|
||||
final absentLessonsOfSubject = absenceProvider.absences.where((e) => e.subject == subject && e.delay == 0).length;
|
||||
final totalLessonsOfSubject = _lessonCount[subject]?.lessonYearIndex ?? 0;
|
||||
|
||||
double absentLessonsOfSubjectPercentage;
|
||||
|
||||
if (absentLessonsOfSubject <= totalLessonsOfSubject) {
|
||||
absentLessonsOfSubjectPercentage = absentLessonsOfSubject / totalLessonsOfSubject * 100;
|
||||
} else {
|
||||
absentLessonsOfSubjectPercentage = -1;
|
||||
}
|
||||
|
||||
_absences[subject]?.percentage = absentLessonsOfSubjectPercentage.clamp(-1, 100.0);
|
||||
});
|
||||
|
||||
absences = _absences.values.toList();
|
||||
absences.sort((a, b) => -a.percentage.compareTo(b.percentage));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
user = Provider.of<UserProvider>(context);
|
||||
absenceProvider = Provider.of<AbsenceProvider>(context);
|
||||
noteProvider = Provider.of<NoteProvider>(context);
|
||||
updateProvider = Provider.of<UpdateProvider>(context);
|
||||
timetableProvider = Provider.of<TimetableProvider>(context);
|
||||
|
||||
List<String> nameParts = user.displayName?.split(" ") ?? ["?"];
|
||||
firstName = nameParts.length > 1 ? nameParts[1] : nameParts[0];
|
||||
|
||||
buildSubjectAbsences();
|
||||
|
||||
return Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: NestedScrollView(
|
||||
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
||||
headerSliverBuilder: (context, _) => [
|
||||
SliverAppBar(
|
||||
pinned: true,
|
||||
floating: false,
|
||||
snap: false,
|
||||
centerTitle: false,
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
actions: [
|
||||
// Profile Icon
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 24.0),
|
||||
child: ProfileButton(
|
||||
child: ProfileImage(
|
||||
heroTag: "profile",
|
||||
name: firstName,
|
||||
backgroundColor: ColorUtils.stringToColor(user.displayName ?? "?"),
|
||||
badge: updateProvider.available,
|
||||
role: user.role,
|
||||
profilePictureString: user.picture,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
automaticallyImplyLeading: false,
|
||||
shadowColor: Theme.of(context).shadowColor,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: Text(
|
||||
"Absences".i18n,
|
||||
style: TextStyle(color: AppColors.of(context).text, fontSize: 32.0, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
bottom: FilterBar(items: [
|
||||
Tab(text: "Absences".i18n),
|
||||
Tab(text: "Delays".i18n),
|
||||
Tab(text: "Misses".i18n),
|
||||
], controller: _tabController, disableFading: true),
|
||||
),
|
||||
],
|
||||
body: TabBarView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
controller: _tabController,
|
||||
children: List.generate(3, (index) => filterViewBuilder(context, index))),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<DateWidget> getFilterWidgets(AbsenceFilter activeData) {
|
||||
List<DateWidget> items = [];
|
||||
switch (activeData) {
|
||||
case AbsenceFilter.absences:
|
||||
for (var a in absences) {
|
||||
items.add(DateWidget(
|
||||
date: DateTime.fromMillisecondsSinceEpoch(0),
|
||||
widget: AbsenceSubjectTile(
|
||||
a.subject,
|
||||
percentage: a.percentage,
|
||||
excused: a.absences.where((a) => a.state == Justification.excused).length,
|
||||
unexcused: a.absences.where((a) => a.state == Justification.unexcused).length,
|
||||
pending: a.absences.where((a) => a.state == Justification.pending).length,
|
||||
onTap: () => AbsenceSubjectView.show(a.subject, a.absences, context: context),
|
||||
),
|
||||
));
|
||||
}
|
||||
break;
|
||||
case AbsenceFilter.delays:
|
||||
for (var absence in absenceProvider.absences) {
|
||||
if (absence.delay != 0) {
|
||||
items.add(DateWidget(
|
||||
date: absence.date,
|
||||
widget: AbsenceViewable(absence, padding: EdgeInsets.zero),
|
||||
));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AbsenceFilter.misses:
|
||||
for (var note in noteProvider.notes) {
|
||||
if (note.type?.name == "HaziFeladatHiany" || note.type?.name == "Felszereleshiany") {
|
||||
items.add(DateWidget(
|
||||
date: note.date,
|
||||
widget: MissTile(note),
|
||||
));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
Widget filterViewBuilder(context, int activeData) {
|
||||
List<Widget> filterWidgets = [];
|
||||
|
||||
if (activeData > 0) {
|
||||
filterWidgets = sortDateWidgets(
|
||||
context,
|
||||
dateWidgets: getFilterWidgets(AbsenceFilter.values[activeData]),
|
||||
padding: EdgeInsets.zero,
|
||||
hasShadow: true,
|
||||
);
|
||||
} else {
|
||||
filterWidgets = [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0),
|
||||
child: Panel(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text("Subjects".i18n),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4.0),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
|
||||
title: Text("attention".i18n),
|
||||
content: Text("attention_body".i18n),
|
||||
actions: [ActionButton(label: "Ok", onTap: () => Navigator.of(context).pop())],
|
||||
),
|
||||
);
|
||||
},
|
||||
padding: EdgeInsets.zero,
|
||||
splashRadius: 24.0,
|
||||
visualDensity: VisualDensity.compact,
|
||||
constraints: BoxConstraints.tight(const Size(42.0, 42.0)),
|
||||
icon: const Icon(FeatherIcons.info),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: PageTransitionSwitcher(
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> primaryAnimation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return FadeThroughTransition(
|
||||
child: child,
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
fillColor: Theme.of(context).colorScheme.background,
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
children: getFilterWidgets(AbsenceFilter.values[activeData]).map((e) => e.widget).cast<Widget>().toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: RefreshIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
onRefresh: () async {
|
||||
await absenceProvider.fetch();
|
||||
await noteProvider.fetch();
|
||||
},
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: max(filterWidgets.length + (activeData <= 1 ? 1 : 0), 1),
|
||||
itemBuilder: (context, index) {
|
||||
if (filterWidgets.isNotEmpty) {
|
||||
if ((index == 0 && activeData == 1) || (index == 0 && activeData == 0)) {
|
||||
int value1 = 0;
|
||||
int value2 = 0;
|
||||
String title1 = "";
|
||||
String title2 = "";
|
||||
String suffix = "";
|
||||
|
||||
if (activeData == AbsenceFilter.absences.index) {
|
||||
value1 = absenceProvider.absences.where((e) => e.delay == 0 && e.state == Justification.excused).length;
|
||||
value2 = absenceProvider.absences.where((e) => e.delay == 0 && e.state == Justification.unexcused).length;
|
||||
title1 = "stat_1".i18n;
|
||||
title2 = "stat_2".i18n;
|
||||
suffix = " " + "hr".i18n;
|
||||
} else if (activeData == AbsenceFilter.delays.index) {
|
||||
value1 = absenceProvider.absences
|
||||
.where((e) => e.delay != 0 && e.state == Justification.excused)
|
||||
.map((e) => e.delay)
|
||||
.fold(0, (a, b) => a + b);
|
||||
value2 = absenceProvider.absences
|
||||
.where((e) => e.delay != 0 && e.state == Justification.unexcused)
|
||||
.map((e) => e.delay)
|
||||
.fold(0, (a, b) => a + b);
|
||||
title1 = "stat_3".i18n;
|
||||
title2 = "stat_4".i18n;
|
||||
suffix = " " + "min".i18n;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0, left: 24.0, right: 24.0),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: StatisticsTile(
|
||||
title: AutoSizeText(
|
||||
title1,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
valueSuffix: suffix,
|
||||
value: value1.toDouble(),
|
||||
decimal: false,
|
||||
color: AppColors.of(context).green,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 24.0),
|
||||
Expanded(
|
||||
child: StatisticsTile(
|
||||
title: AutoSizeText(
|
||||
title2,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
valueSuffix: suffix,
|
||||
value: value2.toDouble(),
|
||||
decimal: false,
|
||||
color: AppColors.of(context).red,
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 6.0),
|
||||
child: filterWidgets[index - (activeData <= 1 ? 1 : 0)],
|
||||
);
|
||||
} else {
|
||||
return Empty(subtitle: "empty".i18n);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:filcnaplo/api/providers/update_provider.dart';
|
||||
import 'package:filcnaplo/ui/date_widget.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/absence.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/lesson.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/subject.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/week.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/absence_provider.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/note_provider.dart';
|
||||
import 'package:filcnaplo/api/providers/user_provider.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/action_button.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/empty.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/filter_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel.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/common/widgets/absence/absence_subject_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/absence/absence_viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/statistics_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/miss_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/absences/absence_subject_view.dart';
|
||||
import 'package:filcnaplo/ui/filter/sort.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'absences_page.i18n.dart';
|
||||
|
||||
enum AbsenceFilter { absences, delays, misses }
|
||||
|
||||
class SubjectAbsence {
|
||||
Subject subject;
|
||||
List<Absence> absences;
|
||||
double percentage;
|
||||
|
||||
SubjectAbsence({required this.subject, this.absences = const [], this.percentage = 0.0});
|
||||
}
|
||||
|
||||
class AbsencesPage extends StatefulWidget {
|
||||
const AbsencesPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_AbsencesPageState createState() => _AbsencesPageState();
|
||||
}
|
||||
|
||||
class _AbsencesPageState extends State<AbsencesPage> with TickerProviderStateMixin {
|
||||
late UserProvider user;
|
||||
late AbsenceProvider absenceProvider;
|
||||
late TimetableProvider timetableProvider;
|
||||
late NoteProvider noteProvider;
|
||||
late UpdateProvider updateProvider;
|
||||
late String firstName;
|
||||
late TabController _tabController;
|
||||
late List<SubjectAbsence> absences = [];
|
||||
final Map<Subject, Lesson> _lessonCount = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_tabController = TabController(length: 3, vsync: this);
|
||||
timetableProvider = Provider.of<TimetableProvider>(context, listen: false);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
for (final lesson in timetableProvider.getWeek(Week.current()) ?? []) {
|
||||
if (!lesson.isEmpty && lesson.subject.id != '' && lesson.lessonYearIndex != null) {
|
||||
_lessonCount.update(
|
||||
lesson.subject,
|
||||
(value) {
|
||||
if (lesson.lessonYearIndex! > value.lessonYearIndex!) {
|
||||
return lesson;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
ifAbsent: () => lesson,
|
||||
);
|
||||
}
|
||||
}
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
void buildSubjectAbsences() {
|
||||
Map<Subject, SubjectAbsence> _absences = {};
|
||||
|
||||
for (final absence in absenceProvider.absences) {
|
||||
if (absence.delay != 0) continue;
|
||||
|
||||
if (!_absences.containsKey(absence.subject)) {
|
||||
_absences[absence.subject] = SubjectAbsence(subject: absence.subject, absences: [absence]);
|
||||
} else {
|
||||
_absences[absence.subject]?.absences.add(absence);
|
||||
}
|
||||
}
|
||||
|
||||
_absences.forEach((subject, absence) {
|
||||
final absentLessonsOfSubject = absenceProvider.absences.where((e) => e.subject == subject && e.delay == 0).length;
|
||||
final totalLessonsOfSubject = _lessonCount[subject]?.lessonYearIndex ?? 0;
|
||||
|
||||
double absentLessonsOfSubjectPercentage;
|
||||
|
||||
if (absentLessonsOfSubject <= totalLessonsOfSubject) {
|
||||
absentLessonsOfSubjectPercentage = absentLessonsOfSubject / totalLessonsOfSubject * 100;
|
||||
} else {
|
||||
absentLessonsOfSubjectPercentage = -1;
|
||||
}
|
||||
|
||||
_absences[subject]?.percentage = absentLessonsOfSubjectPercentage.clamp(-1, 100.0);
|
||||
});
|
||||
|
||||
absences = _absences.values.toList();
|
||||
absences.sort((a, b) => -a.percentage.compareTo(b.percentage));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
user = Provider.of<UserProvider>(context);
|
||||
absenceProvider = Provider.of<AbsenceProvider>(context);
|
||||
noteProvider = Provider.of<NoteProvider>(context);
|
||||
updateProvider = Provider.of<UpdateProvider>(context);
|
||||
timetableProvider = Provider.of<TimetableProvider>(context);
|
||||
|
||||
List<String> nameParts = user.displayName?.split(" ") ?? ["?"];
|
||||
firstName = nameParts.length > 1 ? nameParts[1] : nameParts[0];
|
||||
|
||||
buildSubjectAbsences();
|
||||
|
||||
return Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: NestedScrollView(
|
||||
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
||||
headerSliverBuilder: (context, _) => [
|
||||
SliverAppBar(
|
||||
pinned: true,
|
||||
floating: false,
|
||||
snap: false,
|
||||
centerTitle: false,
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
actions: [
|
||||
// Profile Icon
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 24.0),
|
||||
child: ProfileButton(
|
||||
child: ProfileImage(
|
||||
heroTag: "profile",
|
||||
name: firstName,
|
||||
backgroundColor: ColorUtils.stringToColor(user.displayName ?? "?"),
|
||||
badge: updateProvider.available,
|
||||
role: user.role,
|
||||
profilePictureString: user.picture,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
automaticallyImplyLeading: false,
|
||||
shadowColor: Theme.of(context).shadowColor,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: Text(
|
||||
"Absences".i18n,
|
||||
style: TextStyle(color: AppColors.of(context).text, fontSize: 32.0, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
bottom: FilterBar(items: [
|
||||
Tab(text: "Absences".i18n),
|
||||
Tab(text: "Delays".i18n),
|
||||
Tab(text: "Misses".i18n),
|
||||
], controller: _tabController, disableFading: true),
|
||||
),
|
||||
],
|
||||
body: TabBarView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
controller: _tabController,
|
||||
children: List.generate(3, (index) => filterViewBuilder(context, index))),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<DateWidget> getFilterWidgets(AbsenceFilter activeData) {
|
||||
List<DateWidget> items = [];
|
||||
switch (activeData) {
|
||||
case AbsenceFilter.absences:
|
||||
for (var a in absences) {
|
||||
items.add(DateWidget(
|
||||
date: DateTime.fromMillisecondsSinceEpoch(0),
|
||||
widget: AbsenceSubjectTile(
|
||||
a.subject,
|
||||
percentage: a.percentage,
|
||||
excused: a.absences.where((a) => a.state == Justification.excused).length,
|
||||
unexcused: a.absences.where((a) => a.state == Justification.unexcused).length,
|
||||
pending: a.absences.where((a) => a.state == Justification.pending).length,
|
||||
onTap: () => AbsenceSubjectView.show(a.subject, a.absences, context: context),
|
||||
),
|
||||
));
|
||||
}
|
||||
break;
|
||||
case AbsenceFilter.delays:
|
||||
for (var absence in absenceProvider.absences) {
|
||||
if (absence.delay != 0) {
|
||||
items.add(DateWidget(
|
||||
date: absence.date,
|
||||
widget: AbsenceViewable(absence, padding: EdgeInsets.zero),
|
||||
));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AbsenceFilter.misses:
|
||||
for (var note in noteProvider.notes) {
|
||||
if (note.type?.name == "HaziFeladatHiany" || note.type?.name == "Felszereleshiany") {
|
||||
items.add(DateWidget(
|
||||
date: note.date,
|
||||
widget: MissTile(note),
|
||||
));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
Widget filterViewBuilder(context, int activeData) {
|
||||
List<Widget> filterWidgets = [];
|
||||
|
||||
if (activeData > 0) {
|
||||
filterWidgets = sortDateWidgets(
|
||||
context,
|
||||
dateWidgets: getFilterWidgets(AbsenceFilter.values[activeData]),
|
||||
padding: EdgeInsets.zero,
|
||||
hasShadow: true,
|
||||
);
|
||||
} else {
|
||||
filterWidgets = [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0),
|
||||
child: Panel(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text("Subjects".i18n),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4.0),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
|
||||
title: Text("attention".i18n),
|
||||
content: Text("attention_body".i18n),
|
||||
actions: [ActionButton(label: "Ok", onTap: () => Navigator.of(context).pop())],
|
||||
),
|
||||
);
|
||||
},
|
||||
padding: EdgeInsets.zero,
|
||||
splashRadius: 24.0,
|
||||
visualDensity: VisualDensity.compact,
|
||||
constraints: BoxConstraints.tight(const Size(42.0, 42.0)),
|
||||
icon: const Icon(FeatherIcons.info),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: PageTransitionSwitcher(
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> primaryAnimation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return FadeThroughTransition(
|
||||
child: child,
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
fillColor: Theme.of(context).colorScheme.background,
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
children: getFilterWidgets(AbsenceFilter.values[activeData]).map((e) => e.widget).cast<Widget>().toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: RefreshIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
onRefresh: () async {
|
||||
await absenceProvider.fetch();
|
||||
await noteProvider.fetch();
|
||||
},
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: max(filterWidgets.length + (activeData <= 1 ? 1 : 0), 1),
|
||||
itemBuilder: (context, index) {
|
||||
if (filterWidgets.isNotEmpty) {
|
||||
if ((index == 0 && activeData == 1) || (index == 0 && activeData == 0)) {
|
||||
int value1 = 0;
|
||||
int value2 = 0;
|
||||
String title1 = "";
|
||||
String title2 = "";
|
||||
String suffix = "";
|
||||
|
||||
if (activeData == AbsenceFilter.absences.index) {
|
||||
value1 = absenceProvider.absences.where((e) => e.delay == 0 && e.state == Justification.excused).length;
|
||||
value2 = absenceProvider.absences.where((e) => e.delay == 0 && e.state == Justification.unexcused).length;
|
||||
title1 = "stat_1".i18n;
|
||||
title2 = "stat_2".i18n;
|
||||
suffix = " " + "hr".i18n;
|
||||
} else if (activeData == AbsenceFilter.delays.index) {
|
||||
value1 = absenceProvider.absences
|
||||
.where((e) => e.delay != 0 && e.state == Justification.excused)
|
||||
.map((e) => e.delay)
|
||||
.fold(0, (a, b) => a + b);
|
||||
value2 = absenceProvider.absences
|
||||
.where((e) => e.delay != 0 && e.state == Justification.unexcused)
|
||||
.map((e) => e.delay)
|
||||
.fold(0, (a, b) => a + b);
|
||||
title1 = "stat_3".i18n;
|
||||
title2 = "stat_4".i18n;
|
||||
suffix = " " + "min".i18n;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0, left: 24.0, right: 24.0),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: StatisticsTile(
|
||||
title: AutoSizeText(
|
||||
title1,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
valueSuffix: suffix,
|
||||
value: value1.toDouble(),
|
||||
decimal: false,
|
||||
color: AppColors.of(context).green,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 24.0),
|
||||
Expanded(
|
||||
child: StatisticsTile(
|
||||
title: AutoSizeText(
|
||||
title2,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
valueSuffix: suffix,
|
||||
value: value2.toDouble(),
|
||||
decimal: false,
|
||||
color: AppColors.of(context).red,
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 6.0),
|
||||
child: filterWidgets[index - (activeData <= 1 ? 1 : 0)],
|
||||
);
|
||||
} else {
|
||||
return Empty(subtitle: "empty".i18n);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension ScreensLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Absences": "Absences",
|
||||
"Delays": "Delays",
|
||||
"Misses": "Misses",
|
||||
"empty": "You have no absences.",
|
||||
"stat_1": "Excused Absences",
|
||||
"stat_2": "Unexcused Absences",
|
||||
"stat_3": "Excused Delay",
|
||||
"stat_4": "Unexcused Delay",
|
||||
"min": "min",
|
||||
"hr": "hrs",
|
||||
"Subjects": "Subjects",
|
||||
"attention": "Attention!",
|
||||
"attention_body": "Percentage calculations are only an approximation so they may not be accurate.",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Absences": "Hiányzások",
|
||||
"Delays": "Késések",
|
||||
"Misses": "Hiányok",
|
||||
"empty": "Nincsenek hiányaid.",
|
||||
"stat_1": "Igazolt hiányzások",
|
||||
"stat_2": "Igazolatlan hiányzások",
|
||||
"stat_3": "Igazolt Késés",
|
||||
"stat_4": "Igazolatlan Késés",
|
||||
"min": "perc",
|
||||
"hr": "óra",
|
||||
"Subjects": "Tantárgyak",
|
||||
"attention": "Figyelem!",
|
||||
"attention_body": "A százalékos számítások csak közelítések, ezért előfordulhat, hogy nem pontosak.",
|
||||
},
|
||||
"de_de": {
|
||||
"Absences": "Fehlen",
|
||||
"Delays": "Verspätung",
|
||||
"Misses": "Fehlt",
|
||||
"empty": "Sie haben keine Fehlen.",
|
||||
"stat_1": "Entschuldigte Fehlen",
|
||||
"stat_2": "Unentschuldigte Fehlen",
|
||||
"stat_3": "Entschuldigte Verspätung",
|
||||
"stat_4": "Unentschuldigte Verspätung",
|
||||
"min": "min",
|
||||
"hr": "hrs",
|
||||
"Subjects": "Fächer",
|
||||
"attention": "Achtung!",
|
||||
"attention_body": "Prozentberechnungen sind nur eine Annäherung und können daher ungenau sein.",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension ScreensLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Absences": "Absences",
|
||||
"Delays": "Delays",
|
||||
"Misses": "Misses",
|
||||
"empty": "You have no absences.",
|
||||
"stat_1": "Excused Absences",
|
||||
"stat_2": "Unexcused Absences",
|
||||
"stat_3": "Excused Delay",
|
||||
"stat_4": "Unexcused Delay",
|
||||
"min": "min",
|
||||
"hr": "hrs",
|
||||
"Subjects": "Subjects",
|
||||
"attention": "Attention!",
|
||||
"attention_body": "Percentage calculations are only an approximation so they may not be accurate.",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Absences": "Hiányzások",
|
||||
"Delays": "Késések",
|
||||
"Misses": "Hiányok",
|
||||
"empty": "Nincsenek hiányaid.",
|
||||
"stat_1": "Igazolt hiányzások",
|
||||
"stat_2": "Igazolatlan hiányzások",
|
||||
"stat_3": "Igazolt Késés",
|
||||
"stat_4": "Igazolatlan Késés",
|
||||
"min": "perc",
|
||||
"hr": "óra",
|
||||
"Subjects": "Tantárgyak",
|
||||
"attention": "Figyelem!",
|
||||
"attention_body": "A százalékos számítások csak közelítések, ezért előfordulhat, hogy nem pontosak.",
|
||||
},
|
||||
"de_de": {
|
||||
"Absences": "Fehlen",
|
||||
"Delays": "Verspätung",
|
||||
"Misses": "Fehlt",
|
||||
"empty": "Sie haben keine Fehlen.",
|
||||
"stat_1": "Entschuldigte Fehlen",
|
||||
"stat_2": "Unentschuldigte Fehlen",
|
||||
"stat_3": "Entschuldigte Verspätung",
|
||||
"stat_4": "Unentschuldigte Verspätung",
|
||||
"min": "min",
|
||||
"hr": "hrs",
|
||||
"Subjects": "Fächer",
|
||||
"attention": "Achtung!",
|
||||
"attention_body": "Prozentberechnungen sind nur eine Annäherung und können daher ungenau sein.",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,167 +1,167 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:filcnaplo_kreta_api/models/category.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/subject.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/material_action_button.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'grade_calculator.i18n.dart';
|
||||
|
||||
class GradeCalculator extends StatefulWidget {
|
||||
const GradeCalculator(this.subject, {Key? key}) : super(key: key);
|
||||
|
||||
final Subject subject;
|
||||
|
||||
@override
|
||||
_GradeCalculatorState createState() => _GradeCalculatorState();
|
||||
}
|
||||
|
||||
class _GradeCalculatorState extends State<GradeCalculator> {
|
||||
late GradeCalculatorProvider calculatorProvider;
|
||||
|
||||
final _weightController = TextEditingController(text: "100");
|
||||
|
||||
double newValue = 5.0;
|
||||
double newWeight = 100.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
calculatorProvider = Provider.of<GradeCalculatorProvider>(context);
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(
|
||||
"Grade Calculator".i18n,
|
||||
style: const TextStyle(fontSize: 20.0, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
|
||||
// Grade value
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: Slider(
|
||||
thumbColor: Theme.of(context).colorScheme.secondary,
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
value: newValue,
|
||||
min: 1.0,
|
||||
max: 5.0,
|
||||
divisions: 4,
|
||||
label: "${newValue.toInt()}",
|
||||
onChanged: (value) => setState(() => newValue = value),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 80.0,
|
||||
padding: const EdgeInsets.only(right: 12.0),
|
||||
child: Center(child: GradeValueWidget(GradeValue(newValue.toInt(), "", "", 0))),
|
||||
),
|
||||
]),
|
||||
|
||||
// Grade weight
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: Slider(
|
||||
thumbColor: Theme.of(context).colorScheme.secondary,
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
value: newWeight.clamp(50, 400),
|
||||
min: 50.0,
|
||||
max: 400.0,
|
||||
divisions: 7,
|
||||
label: "${newWeight.toInt()}%",
|
||||
onChanged: (value) => setState(() {
|
||||
newWeight = value;
|
||||
_weightController.text = newWeight.toInt().toString();
|
||||
}),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 80.0,
|
||||
padding: const EdgeInsets.only(right: 12.0),
|
||||
child: Center(
|
||||
child: TextField(
|
||||
controller: _weightController,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 22.0),
|
||||
autocorrect: false,
|
||||
textAlign: TextAlign.right,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
|
||||
LengthLimitingTextInputFormatter(3),
|
||||
],
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
hintText: "100",
|
||||
suffixText: "%",
|
||||
suffixStyle: TextStyle(fontSize: 18.0),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
newWeight = double.tryParse(value) ?? 100.0;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
Container(
|
||||
width: 120.0,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
child: MaterialActionButton(
|
||||
child: Text("Add Grade".i18n),
|
||||
onPressed: () {
|
||||
if (calculatorProvider.ghosts.length >= 30) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(content: Text("limit_reached".i18n), context: context));
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime date;
|
||||
|
||||
if (calculatorProvider.ghosts.isNotEmpty) {
|
||||
List<Grade> grades = calculatorProvider.ghosts;
|
||||
grades.sort((a, b) => -a.writeDate.compareTo(b.writeDate));
|
||||
date = grades.first.date.add(const Duration(days: 7));
|
||||
} else {
|
||||
List<Grade> grades = calculatorProvider.grades.where((e) => e.type == GradeType.midYear && e.subject == widget.subject).toList();
|
||||
grades.sort((a, b) => -a.writeDate.compareTo(b.writeDate));
|
||||
date = grades.first.date;
|
||||
}
|
||||
|
||||
calculatorProvider.addGhost(Grade(
|
||||
id: randomId(),
|
||||
date: date,
|
||||
writeDate: date,
|
||||
description: "Ghost Grade".i18n,
|
||||
value: GradeValue(newValue.toInt(), "", "", newWeight.toInt()),
|
||||
teacher: "Ghost",
|
||||
type: GradeType.ghost,
|
||||
form: "",
|
||||
subject: widget.subject,
|
||||
mode: Category.fromJson({}),
|
||||
seenDate: DateTime(0),
|
||||
groupId: "",
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String randomId() {
|
||||
var rng = Random();
|
||||
return rng.nextInt(1000000000).toString();
|
||||
}
|
||||
}
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:filcnaplo_kreta_api/models/category.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/subject.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/material_action_button.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'grade_calculator.i18n.dart';
|
||||
|
||||
class GradeCalculator extends StatefulWidget {
|
||||
const GradeCalculator(this.subject, {Key? key}) : super(key: key);
|
||||
|
||||
final Subject subject;
|
||||
|
||||
@override
|
||||
_GradeCalculatorState createState() => _GradeCalculatorState();
|
||||
}
|
||||
|
||||
class _GradeCalculatorState extends State<GradeCalculator> {
|
||||
late GradeCalculatorProvider calculatorProvider;
|
||||
|
||||
final _weightController = TextEditingController(text: "100");
|
||||
|
||||
double newValue = 5.0;
|
||||
double newWeight = 100.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
calculatorProvider = Provider.of<GradeCalculatorProvider>(context);
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(
|
||||
"Grade Calculator".i18n,
|
||||
style: const TextStyle(fontSize: 20.0, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
|
||||
// Grade value
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: Slider(
|
||||
thumbColor: Theme.of(context).colorScheme.secondary,
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
value: newValue,
|
||||
min: 1.0,
|
||||
max: 5.0,
|
||||
divisions: 4,
|
||||
label: "${newValue.toInt()}",
|
||||
onChanged: (value) => setState(() => newValue = value),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 80.0,
|
||||
padding: const EdgeInsets.only(right: 12.0),
|
||||
child: Center(child: GradeValueWidget(GradeValue(newValue.toInt(), "", "", 0))),
|
||||
),
|
||||
]),
|
||||
|
||||
// Grade weight
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: Slider(
|
||||
thumbColor: Theme.of(context).colorScheme.secondary,
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
value: newWeight.clamp(50, 400),
|
||||
min: 50.0,
|
||||
max: 400.0,
|
||||
divisions: 7,
|
||||
label: "${newWeight.toInt()}%",
|
||||
onChanged: (value) => setState(() {
|
||||
newWeight = value;
|
||||
_weightController.text = newWeight.toInt().toString();
|
||||
}),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 80.0,
|
||||
padding: const EdgeInsets.only(right: 12.0),
|
||||
child: Center(
|
||||
child: TextField(
|
||||
controller: _weightController,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 22.0),
|
||||
autocorrect: false,
|
||||
textAlign: TextAlign.right,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
|
||||
LengthLimitingTextInputFormatter(3),
|
||||
],
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
hintText: "100",
|
||||
suffixText: "%",
|
||||
suffixStyle: TextStyle(fontSize: 18.0),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
newWeight = double.tryParse(value) ?? 100.0;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
Container(
|
||||
width: 120.0,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
child: MaterialActionButton(
|
||||
child: Text("Add Grade".i18n),
|
||||
onPressed: () {
|
||||
if (calculatorProvider.ghosts.length >= 30) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(content: Text("limit_reached".i18n), context: context));
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime date;
|
||||
|
||||
if (calculatorProvider.ghosts.isNotEmpty) {
|
||||
List<Grade> grades = calculatorProvider.ghosts;
|
||||
grades.sort((a, b) => -a.writeDate.compareTo(b.writeDate));
|
||||
date = grades.first.date.add(const Duration(days: 7));
|
||||
} else {
|
||||
List<Grade> grades = calculatorProvider.grades.where((e) => e.type == GradeType.midYear && e.subject == widget.subject).toList();
|
||||
grades.sort((a, b) => -a.writeDate.compareTo(b.writeDate));
|
||||
date = grades.first.date;
|
||||
}
|
||||
|
||||
calculatorProvider.addGhost(Grade(
|
||||
id: randomId(),
|
||||
date: date,
|
||||
writeDate: date,
|
||||
description: "Ghost Grade".i18n,
|
||||
value: GradeValue(newValue.toInt(), "", "", newWeight.toInt()),
|
||||
teacher: "Ghost",
|
||||
type: GradeType.ghost,
|
||||
form: "",
|
||||
subject: widget.subject,
|
||||
mode: Category.fromJson({}),
|
||||
seenDate: DateTime(0),
|
||||
groupId: "",
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String randomId() {
|
||||
var rng = Random();
|
||||
return rng.nextInt(1000000000).toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Grades": "Grades",
|
||||
"Ghost Grade": "Ghost Grade",
|
||||
"Grade Calculator": "Average calculator",
|
||||
"Add Grade": "Add Grade",
|
||||
"limit_reached": "You cannot add more Ghost Grades.",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Grades": "Jegyek",
|
||||
"Ghost Grade": "Szellem jegy",
|
||||
"Grade Calculator": "Átlag számoló",
|
||||
"Add Grade": "Hozzáadás",
|
||||
"limit_reached": "Nem adhatsz hozzá több jegyet.",
|
||||
},
|
||||
"de_de": {
|
||||
"Grades": "Noten",
|
||||
"Ghost Grade": "Geist Noten",
|
||||
"Grade Calculator": "Mittelwert-Rechner",
|
||||
"Add Grade": "Hinzufügen",
|
||||
"limit_reached": "Sie können keine weiteren Noten hinzufügen.",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Grades": "Grades",
|
||||
"Ghost Grade": "Ghost Grade",
|
||||
"Grade Calculator": "Average calculator",
|
||||
"Add Grade": "Add Grade",
|
||||
"limit_reached": "You cannot add more Ghost Grades.",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Grades": "Jegyek",
|
||||
"Ghost Grade": "Szellem jegy",
|
||||
"Grade Calculator": "Átlag számoló",
|
||||
"Add Grade": "Hozzáadás",
|
||||
"limit_reached": "Nem adhatsz hozzá több jegyet.",
|
||||
},
|
||||
"de_de": {
|
||||
"Grades": "Noten",
|
||||
"Ghost Grade": "Geist Noten",
|
||||
"Grade Calculator": "Mittelwert-Rechner",
|
||||
"Add Grade": "Hinzufügen",
|
||||
"limit_reached": "Sie können keine weiteren Noten hinzufügen.",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
import 'package:filcnaplo/api/providers/database_provider.dart';
|
||||
import 'package:filcnaplo/api/providers/user_provider.dart';
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo_kreta_api/client/client.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
|
||||
class GradeCalculatorProvider extends GradeProvider {
|
||||
GradeCalculatorProvider({
|
||||
List<Grade> initialGrades = const [],
|
||||
required SettingsProvider settings,
|
||||
required UserProvider user,
|
||||
required DatabaseProvider database,
|
||||
required KretaClient kreta,
|
||||
}) : super(
|
||||
initialGrades: initialGrades,
|
||||
settings: settings,
|
||||
database: database,
|
||||
kreta: kreta,
|
||||
user: user,
|
||||
);
|
||||
|
||||
List<Grade> _grades = [];
|
||||
List<Grade> _ghosts = [];
|
||||
@override
|
||||
List<Grade> get grades => _grades + _ghosts;
|
||||
List<Grade> get ghosts => _ghosts;
|
||||
|
||||
void addGhost(Grade grade) {
|
||||
_ghosts.add(grade);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void addGrade(Grade grade) {
|
||||
_grades.add(grade);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void removeGrade(Grade ghost) {
|
||||
_ghosts.removeWhere((e) => ghost.id == e.id);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void addAllGrades(List<Grade> grades) {
|
||||
_grades.addAll(grades);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_grades = [];
|
||||
_ghosts = [];
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/api/providers/database_provider.dart';
|
||||
import 'package:filcnaplo/api/providers/user_provider.dart';
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo_kreta_api/client/client.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
|
||||
class GradeCalculatorProvider extends GradeProvider {
|
||||
GradeCalculatorProvider({
|
||||
List<Grade> initialGrades = const [],
|
||||
required SettingsProvider settings,
|
||||
required UserProvider user,
|
||||
required DatabaseProvider database,
|
||||
required KretaClient kreta,
|
||||
}) : super(
|
||||
initialGrades: initialGrades,
|
||||
settings: settings,
|
||||
database: database,
|
||||
kreta: kreta,
|
||||
user: user,
|
||||
);
|
||||
|
||||
List<Grade> _grades = [];
|
||||
List<Grade> _ghosts = [];
|
||||
@override
|
||||
List<Grade> get grades => _grades + _ghosts;
|
||||
List<Grade> get ghosts => _ghosts;
|
||||
|
||||
void addGhost(Grade grade) {
|
||||
_ghosts.add(grade);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void addGrade(Grade grade) {
|
||||
_grades.add(grade);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void removeGrade(Grade ghost) {
|
||||
_ghosts.removeWhere((e) => ghost.id == e.id);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void addAllGrades(List<Grade> grades) {
|
||||
_grades.addAll(grades);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_grades = [];
|
||||
_ghosts = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
import 'package:filcnaplo_kreta_api/models/subject.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'grades_page.i18n.dart';
|
||||
|
||||
class FailWarning extends StatelessWidget {
|
||||
const FailWarning({Key? key, required this.subjectAvgs}) : super(key: key);
|
||||
|
||||
final Map<Subject, double> subjectAvgs;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final failingSubjectCount = subjectAvgs.values.where((avg) => avg < 2.0).length;
|
||||
|
||||
if (failingSubjectCount == 0) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0),
|
||||
child: Panel(
|
||||
title: Text("fail_warning".i18n),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
FeatherIcons.alertTriangle,
|
||||
color: Colors.orange.withOpacity(.5),
|
||||
size: 20.0,
|
||||
),
|
||||
const SizedBox(width: 12.0),
|
||||
Text("fail_warning_description".i18n.fill([failingSubjectCount])),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/subject.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'grades_page.i18n.dart';
|
||||
|
||||
class FailWarning extends StatelessWidget {
|
||||
const FailWarning({Key? key, required this.subjectAvgs}) : super(key: key);
|
||||
|
||||
final Map<Subject, double> subjectAvgs;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final failingSubjectCount = subjectAvgs.values.where((avg) => avg < 2.0).length;
|
||||
|
||||
if (failingSubjectCount == 0) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0),
|
||||
child: Panel(
|
||||
title: Text("fail_warning".i18n),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
FeatherIcons.alertTriangle,
|
||||
color: Colors.orange.withOpacity(.5),
|
||||
size: 20.0,
|
||||
),
|
||||
const SizedBox(width: 12.0),
|
||||
Text("fail_warning_description".i18n.fill([failingSubjectCount])),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,283 +1,283 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:filcnaplo/helpers/average_helper.dart';
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/subject.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/average_display.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/trend_display.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/cretification/certification_tile.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/grade/grade_viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/hero_scrollview.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_provider.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/grades_count.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/graph.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/subject_grades_container.dart';
|
||||
import 'package:filcnaplo_premium/models/premium_scopes.dart';
|
||||
import 'package:filcnaplo_premium/providers/premium_provider.dart';
|
||||
import 'package:filcnaplo_premium/ui/mobile/premium/upsell.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'grades_page.i18n.dart';
|
||||
// import 'package:filcnaplo_premium/ui/mobile/goalplanner/new_goal.dart';
|
||||
|
||||
class GradeSubjectView extends StatefulWidget {
|
||||
const GradeSubjectView(this.subject, {Key? key, this.groupAverage = 0.0}) : super(key: key);
|
||||
|
||||
final Subject subject;
|
||||
final double groupAverage;
|
||||
|
||||
void push(BuildContext context, {bool root = false}) {
|
||||
Navigator.of(context, rootNavigator: root).push(CupertinoPageRoute(builder: (context) => this));
|
||||
}
|
||||
|
||||
@override
|
||||
State<GradeSubjectView> createState() => _GradeSubjectViewState();
|
||||
}
|
||||
|
||||
class _GradeSubjectViewState extends State<GradeSubjectView> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
// Controllers
|
||||
PersistentBottomSheetController? _sheetController;
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
List<Widget> gradeTiles = [];
|
||||
|
||||
// Providers
|
||||
late GradeProvider gradeProvider;
|
||||
late GradeCalculatorProvider calculatorProvider;
|
||||
|
||||
late double average;
|
||||
late Widget gradeGraph;
|
||||
|
||||
bool gradeCalcMode = false;
|
||||
|
||||
List<Grade> getSubjectGrades(Subject subject) => !gradeCalcMode
|
||||
? gradeProvider.grades.where((e) => e.subject == subject).toList()
|
||||
: calculatorProvider.grades.where((e) => e.subject == subject).toList();
|
||||
|
||||
bool showGraph(List<Grade> subjectGrades) {
|
||||
if (gradeCalcMode) return true;
|
||||
|
||||
final gradeDates = subjectGrades.map((e) => e.date.millisecondsSinceEpoch);
|
||||
final maxGradeDate = gradeDates.fold(0, max);
|
||||
final minGradeDate = gradeDates.fold(0, min);
|
||||
if (maxGradeDate - minGradeDate < const Duration(days: 5).inMilliseconds) return false; // naplo/#78
|
||||
|
||||
return subjectGrades.where((e) => e.type == GradeType.midYear).length > 1;
|
||||
}
|
||||
|
||||
void buildTiles(List<Grade> subjectGrades) {
|
||||
List<Widget> tiles = [];
|
||||
|
||||
if (showGraph(subjectGrades)) {
|
||||
tiles.add(gradeGraph);
|
||||
} else {
|
||||
tiles.add(Container(height: 24.0));
|
||||
}
|
||||
|
||||
tiles.add(Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0),
|
||||
child: Panel(
|
||||
child: GradesCount(grades: getSubjectGrades(widget.subject).toList()),
|
||||
),
|
||||
));
|
||||
|
||||
List<Widget> _gradeTiles = [];
|
||||
|
||||
if (!gradeCalcMode) {
|
||||
subjectGrades.sort((a, b) => -a.date.compareTo(b.date));
|
||||
for (var grade in subjectGrades) {
|
||||
if (grade.type == GradeType.midYear) {
|
||||
_gradeTiles.add(GradeViewable(grade));
|
||||
} else {
|
||||
_gradeTiles.add(CertificationTile(grade, padding: EdgeInsets.zero));
|
||||
}
|
||||
}
|
||||
} else if (subjectGrades.isNotEmpty) {
|
||||
subjectGrades.sort((a, b) => -a.date.compareTo(b.date));
|
||||
for (var grade in subjectGrades) {
|
||||
_gradeTiles.add(GradeTile(grade));
|
||||
}
|
||||
}
|
||||
tiles.add(
|
||||
PageTransitionSwitcher(
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> primaryAnimation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return SharedAxisTransition(
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
transitionType: SharedAxisTransitionType.vertical,
|
||||
child: child,
|
||||
fillColor: Colors.transparent,
|
||||
);
|
||||
},
|
||||
child: _gradeTiles.isNotEmpty
|
||||
? Panel(
|
||||
key: ValueKey(gradeCalcMode),
|
||||
title: Text(
|
||||
gradeCalcMode ? "Ghost Grades".i18n : "Grades".i18n,
|
||||
),
|
||||
child: Column(
|
||||
children: _gradeTiles,
|
||||
))
|
||||
: const SizedBox(),
|
||||
),
|
||||
);
|
||||
|
||||
tiles.add(Padding(padding: EdgeInsets.only(bottom: !gradeCalcMode ? 24.0 : 250.0)));
|
||||
gradeTiles = List.castFrom(tiles);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
gradeProvider = Provider.of<GradeProvider>(context);
|
||||
calculatorProvider = Provider.of<GradeCalculatorProvider>(context);
|
||||
|
||||
List<Grade> subjectGrades = getSubjectGrades(widget.subject).toList();
|
||||
average = AverageHelper.averageEvals(subjectGrades);
|
||||
final prevAvg = subjectGrades.isNotEmpty
|
||||
? AverageHelper.averageEvals(subjectGrades
|
||||
.where((e) => e.date.isBefore(subjectGrades.reduce((v, e) => e.date.isAfter(v.date) ? e : v).date.subtract(const Duration(days: 30))))
|
||||
.toList())
|
||||
: 0.0;
|
||||
|
||||
gradeGraph = Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
|
||||
child: Panel(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text("annual_average".i18n),
|
||||
if (average != prevAvg) TrendDisplay(current: average, previous: prevAvg),
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 16.0, right: 12.0),
|
||||
child: GradeGraph(subjectGrades, dayThreshold: 5, classAvg: widget.groupAverage),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (!gradeCalcMode) {
|
||||
buildTiles(subjectGrades);
|
||||
} else {
|
||||
List<Grade> ghostGrades = calculatorProvider.ghosts.where((e) => e.subject == widget.subject).toList();
|
||||
buildTiles(ghostGrades);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
floatingActionButtonLocation: ExpandableFab.location,
|
||||
floatingActionButton: Visibility(
|
||||
visible: !gradeCalcMode && subjectGrades.where((e) => e.type == GradeType.midYear).isNotEmpty,
|
||||
child: ExpandableFab(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
type: ExpandableFabType.up,
|
||||
distance: 50,
|
||||
closeButtonStyle: ExpandableFabCloseButtonStyle(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
children: [
|
||||
FloatingActionButton.small(
|
||||
child: const Icon(FeatherIcons.plus),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
onPressed: () {
|
||||
gradeCalc(context);
|
||||
},
|
||||
),
|
||||
FloatingActionButton.small(
|
||||
child: const Icon(FeatherIcons.flag, size: 20.0),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
onPressed: () {
|
||||
if (!Provider.of<PremiumProvider>(context, listen: false).hasScope(PremiumScopes.goalPlanner)) {
|
||||
PremiumLockedFeatureUpsell.show(context: context, feature: PremiumFeature.goalplanner);
|
||||
return;
|
||||
}
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Hamarosan...")));
|
||||
|
||||
// Navigator.of(context).push(CupertinoPageRoute(builder: (context) => PremiumGoalplannerNewGoalScreen(subject: widget.subject)));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {},
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
child: HeroScrollView(
|
||||
onClose: () {
|
||||
if (_sheetController != null && gradeCalcMode) {
|
||||
_sheetController!.close();
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
navBarItems: [
|
||||
const SizedBox(width: 6.0),
|
||||
if (widget.groupAverage != 0) Center(child: AverageDisplay(average: widget.groupAverage, border: true)),
|
||||
const SizedBox(width: 6.0),
|
||||
if (average != 0) Center(child: AverageDisplay(average: average)),
|
||||
const SizedBox(width: 12.0),
|
||||
],
|
||||
icon: SubjectIcon.resolveVariant(subject: widget.subject, context: context),
|
||||
scrollController: _scrollController,
|
||||
title: widget.subject.renamedTo ?? widget.subject.name.capital(),
|
||||
italic: widget.subject.isRenamed,
|
||||
child: SubjectGradesContainer(
|
||||
child: CupertinoScrollbar(
|
||||
child: ListView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) => gradeTiles[index],
|
||||
itemCount: gradeTiles.length,
|
||||
),
|
||||
),
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
||||
void gradeCalc(BuildContext context) {
|
||||
// Scroll to the top of the page
|
||||
_scrollController.animateTo(75, duration: const Duration(milliseconds: 500), curve: Curves.ease);
|
||||
|
||||
calculatorProvider.clear();
|
||||
calculatorProvider.addAllGrades(gradeProvider.grades);
|
||||
|
||||
_sheetController = _scaffoldKey.currentState?.showBottomSheet(
|
||||
(context) => RoundedBottomSheet(child: GradeCalculator(widget.subject), borderRadius: 14.0),
|
||||
backgroundColor: const Color(0x00000000),
|
||||
elevation: 12.0,
|
||||
);
|
||||
|
||||
// Hide the fab and grades
|
||||
setState(() {
|
||||
gradeCalcMode = true;
|
||||
});
|
||||
|
||||
_sheetController!.closed.then((value) {
|
||||
// Show fab and grades
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
gradeCalcMode = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:filcnaplo/helpers/average_helper.dart';
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/grade.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/subject.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/average_display.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/trend_display.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/cretification/certification_tile.dart';
|
||||
import 'package:filcnaplo/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/widgets/grade/grade_viewable.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/hero_scrollview.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_provider.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/grades_count.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/graph.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/subject_grades_container.dart';
|
||||
import 'package:filcnaplo_premium/models/premium_scopes.dart';
|
||||
import 'package:filcnaplo_premium/providers/premium_provider.dart';
|
||||
import 'package:filcnaplo_premium/ui/mobile/premium/upsell.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'grades_page.i18n.dart';
|
||||
// import 'package:filcnaplo_premium/ui/mobile/goalplanner/new_goal.dart';
|
||||
|
||||
class GradeSubjectView extends StatefulWidget {
|
||||
const GradeSubjectView(this.subject, {Key? key, this.groupAverage = 0.0}) : super(key: key);
|
||||
|
||||
final Subject subject;
|
||||
final double groupAverage;
|
||||
|
||||
void push(BuildContext context, {bool root = false}) {
|
||||
Navigator.of(context, rootNavigator: root).push(CupertinoPageRoute(builder: (context) => this));
|
||||
}
|
||||
|
||||
@override
|
||||
State<GradeSubjectView> createState() => _GradeSubjectViewState();
|
||||
}
|
||||
|
||||
class _GradeSubjectViewState extends State<GradeSubjectView> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
// Controllers
|
||||
PersistentBottomSheetController? _sheetController;
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
List<Widget> gradeTiles = [];
|
||||
|
||||
// Providers
|
||||
late GradeProvider gradeProvider;
|
||||
late GradeCalculatorProvider calculatorProvider;
|
||||
|
||||
late double average;
|
||||
late Widget gradeGraph;
|
||||
|
||||
bool gradeCalcMode = false;
|
||||
|
||||
List<Grade> getSubjectGrades(Subject subject) => !gradeCalcMode
|
||||
? gradeProvider.grades.where((e) => e.subject == subject).toList()
|
||||
: calculatorProvider.grades.where((e) => e.subject == subject).toList();
|
||||
|
||||
bool showGraph(List<Grade> subjectGrades) {
|
||||
if (gradeCalcMode) return true;
|
||||
|
||||
final gradeDates = subjectGrades.map((e) => e.date.millisecondsSinceEpoch);
|
||||
final maxGradeDate = gradeDates.fold(0, max);
|
||||
final minGradeDate = gradeDates.fold(0, min);
|
||||
if (maxGradeDate - minGradeDate < const Duration(days: 5).inMilliseconds) return false; // naplo/#78
|
||||
|
||||
return subjectGrades.where((e) => e.type == GradeType.midYear).length > 1;
|
||||
}
|
||||
|
||||
void buildTiles(List<Grade> subjectGrades) {
|
||||
List<Widget> tiles = [];
|
||||
|
||||
if (showGraph(subjectGrades)) {
|
||||
tiles.add(gradeGraph);
|
||||
} else {
|
||||
tiles.add(Container(height: 24.0));
|
||||
}
|
||||
|
||||
tiles.add(Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0),
|
||||
child: Panel(
|
||||
child: GradesCount(grades: getSubjectGrades(widget.subject).toList()),
|
||||
),
|
||||
));
|
||||
|
||||
List<Widget> _gradeTiles = [];
|
||||
|
||||
if (!gradeCalcMode) {
|
||||
subjectGrades.sort((a, b) => -a.date.compareTo(b.date));
|
||||
for (var grade in subjectGrades) {
|
||||
if (grade.type == GradeType.midYear) {
|
||||
_gradeTiles.add(GradeViewable(grade));
|
||||
} else {
|
||||
_gradeTiles.add(CertificationTile(grade, padding: EdgeInsets.zero));
|
||||
}
|
||||
}
|
||||
} else if (subjectGrades.isNotEmpty) {
|
||||
subjectGrades.sort((a, b) => -a.date.compareTo(b.date));
|
||||
for (var grade in subjectGrades) {
|
||||
_gradeTiles.add(GradeTile(grade));
|
||||
}
|
||||
}
|
||||
tiles.add(
|
||||
PageTransitionSwitcher(
|
||||
transitionBuilder: (
|
||||
Widget child,
|
||||
Animation<double> primaryAnimation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
return SharedAxisTransition(
|
||||
animation: primaryAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
transitionType: SharedAxisTransitionType.vertical,
|
||||
child: child,
|
||||
fillColor: Colors.transparent,
|
||||
);
|
||||
},
|
||||
child: _gradeTiles.isNotEmpty
|
||||
? Panel(
|
||||
key: ValueKey(gradeCalcMode),
|
||||
title: Text(
|
||||
gradeCalcMode ? "Ghost Grades".i18n : "Grades".i18n,
|
||||
),
|
||||
child: Column(
|
||||
children: _gradeTiles,
|
||||
))
|
||||
: const SizedBox(),
|
||||
),
|
||||
);
|
||||
|
||||
tiles.add(Padding(padding: EdgeInsets.only(bottom: !gradeCalcMode ? 24.0 : 250.0)));
|
||||
gradeTiles = List.castFrom(tiles);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
gradeProvider = Provider.of<GradeProvider>(context);
|
||||
calculatorProvider = Provider.of<GradeCalculatorProvider>(context);
|
||||
|
||||
List<Grade> subjectGrades = getSubjectGrades(widget.subject).toList();
|
||||
average = AverageHelper.averageEvals(subjectGrades);
|
||||
final prevAvg = subjectGrades.isNotEmpty
|
||||
? AverageHelper.averageEvals(subjectGrades
|
||||
.where((e) => e.date.isBefore(subjectGrades.reduce((v, e) => e.date.isAfter(v.date) ? e : v).date.subtract(const Duration(days: 30))))
|
||||
.toList())
|
||||
: 0.0;
|
||||
|
||||
gradeGraph = Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
|
||||
child: Panel(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text("annual_average".i18n),
|
||||
if (average != prevAvg) TrendDisplay(current: average, previous: prevAvg),
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 16.0, right: 12.0),
|
||||
child: GradeGraph(subjectGrades, dayThreshold: 5, classAvg: widget.groupAverage),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (!gradeCalcMode) {
|
||||
buildTiles(subjectGrades);
|
||||
} else {
|
||||
List<Grade> ghostGrades = calculatorProvider.ghosts.where((e) => e.subject == widget.subject).toList();
|
||||
buildTiles(ghostGrades);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
floatingActionButtonLocation: ExpandableFab.location,
|
||||
floatingActionButton: Visibility(
|
||||
visible: !gradeCalcMode && subjectGrades.where((e) => e.type == GradeType.midYear).isNotEmpty,
|
||||
child: ExpandableFab(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
type: ExpandableFabType.up,
|
||||
distance: 50,
|
||||
closeButtonStyle: ExpandableFabCloseButtonStyle(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
children: [
|
||||
FloatingActionButton.small(
|
||||
child: const Icon(FeatherIcons.plus),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
onPressed: () {
|
||||
gradeCalc(context);
|
||||
},
|
||||
),
|
||||
FloatingActionButton.small(
|
||||
child: const Icon(FeatherIcons.flag, size: 20.0),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
onPressed: () {
|
||||
if (!Provider.of<PremiumProvider>(context, listen: false).hasScope(PremiumScopes.goalPlanner)) {
|
||||
PremiumLockedFeatureUpsell.show(context: context, feature: PremiumFeature.goalplanner);
|
||||
return;
|
||||
}
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Hamarosan...")));
|
||||
|
||||
// Navigator.of(context).push(CupertinoPageRoute(builder: (context) => PremiumGoalplannerNewGoalScreen(subject: widget.subject)));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {},
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
child: HeroScrollView(
|
||||
onClose: () {
|
||||
if (_sheetController != null && gradeCalcMode) {
|
||||
_sheetController!.close();
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
navBarItems: [
|
||||
const SizedBox(width: 6.0),
|
||||
if (widget.groupAverage != 0) Center(child: AverageDisplay(average: widget.groupAverage, border: true)),
|
||||
const SizedBox(width: 6.0),
|
||||
if (average != 0) Center(child: AverageDisplay(average: average)),
|
||||
const SizedBox(width: 12.0),
|
||||
],
|
||||
icon: SubjectIcon.resolveVariant(subject: widget.subject, context: context),
|
||||
scrollController: _scrollController,
|
||||
title: widget.subject.renamedTo ?? widget.subject.name.capital(),
|
||||
italic: widget.subject.isRenamed,
|
||||
child: SubjectGradesContainer(
|
||||
child: CupertinoScrollbar(
|
||||
child: ListView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) => gradeTiles[index],
|
||||
itemCount: gradeTiles.length,
|
||||
),
|
||||
),
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
||||
void gradeCalc(BuildContext context) {
|
||||
// Scroll to the top of the page
|
||||
_scrollController.animateTo(75, duration: const Duration(milliseconds: 500), curve: Curves.ease);
|
||||
|
||||
calculatorProvider.clear();
|
||||
calculatorProvider.addAllGrades(gradeProvider.grades);
|
||||
|
||||
_sheetController = _scaffoldKey.currentState?.showBottomSheet(
|
||||
(context) => RoundedBottomSheet(child: GradeCalculator(widget.subject), borderRadius: 14.0),
|
||||
backgroundColor: const Color(0x00000000),
|
||||
elevation: 12.0,
|
||||
);
|
||||
|
||||
// Hide the fab and grades
|
||||
setState(() {
|
||||
gradeCalcMode = true;
|
||||
});
|
||||
|
||||
_sheetController!.closed.then((value) {
|
||||
// Show fab and grades
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
gradeCalcMode = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user