changed everything from filcnaplo to refilc finally
This commit is contained in:
217
refilc_mobile_ui/lib/screens/error_report_screen.dart
Normal file
217
refilc_mobile_ui/lib/screens/error_report_screen.dart
Normal file
@@ -0,0 +1,217 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:refilc/api/client.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'error_report_screen.i18n.dart';
|
||||
|
||||
class ErrorReportScreen extends StatelessWidget {
|
||||
final FlutterErrorDetails details;
|
||||
|
||||
const ErrorReportScreen(this.details, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.red,
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
children: [
|
||||
const Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: BackButton(),
|
||||
),
|
||||
const Spacer(),
|
||||
const Icon(
|
||||
FeatherIcons.alertTriangle,
|
||||
size: 100,
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: Text(
|
||||
"uhoh".i18n,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 32.0,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"description".i18n,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(.95),
|
||||
fontSize: 24.0,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Stack(
|
||||
alignment: Alignment.topRight,
|
||||
children: [
|
||||
Container(
|
||||
height: 110.0,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
color: Colors.black.withOpacity(.2)),
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Text(
|
||||
details.exceptionAsString(),
|
||||
style: const TextStyle(fontFamily: 'SpaceMono'),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(FeatherIcons.info),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StacktracePopup(details));
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(
|
||||
const EdgeInsets.symmetric(vertical: 14.0)),
|
||||
backgroundColor: MaterialStateProperty.all(Colors.white),
|
||||
shape: MaterialStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0)),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"submit".i18n,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 17.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
onPressed: () => reportProblem(context),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32.0)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future reportProblem(BuildContext context) async {
|
||||
final report = ErrorReport(
|
||||
os: "${Platform.operatingSystem} ${Platform.operatingSystemVersion}",
|
||||
error: details.exceptionAsString(),
|
||||
version: const String.fromEnvironment("APPVER", defaultValue: "?"),
|
||||
stack: details.stack.toString(),
|
||||
);
|
||||
FilcAPI.sendReport(report);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
|
||||
class StacktracePopup extends StatelessWidget {
|
||||
final FlutterErrorDetails details;
|
||||
|
||||
const StacktracePopup(this.details, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String stack = details.stack.toString();
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(32.0),
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
),
|
||||
padding: const EdgeInsets.only(top: 15.0, right: 15.0, left: 15.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10.0),
|
||||
child: Text(
|
||||
"details".i18n,
|
||||
style: const TextStyle(fontSize: 20.0),
|
||||
),
|
||||
),
|
||||
ErrorDetail(
|
||||
"error".i18n,
|
||||
details.exceptionAsString(),
|
||||
),
|
||||
ErrorDetail("os".i18n,
|
||||
"${Platform.operatingSystem} ${Platform.operatingSystemVersion}"),
|
||||
ErrorDetail(
|
||||
"version".i18n,
|
||||
const String.fromEnvironment("APPVER",
|
||||
defaultValue: "?")),
|
||||
ErrorDetail(
|
||||
"stack".i18n, stack.substring(0, min(stack.length, 5000)))
|
||||
]),
|
||||
),
|
||||
TextButton(
|
||||
child: Text("done".i18n,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary)),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorDetail extends StatelessWidget {
|
||||
final String title;
|
||||
final String content;
|
||||
|
||||
const ErrorDetail(this.title, this.content, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 6.5, vertical: 4.0),
|
||||
margin: const EdgeInsets.only(top: 4.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black26,
|
||||
borderRadius: BorderRadius.circular(4.0)),
|
||||
child: Text(
|
||||
content,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'SpaceMono', color: Colors.white),
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
45
refilc_mobile_ui/lib/screens/error_report_screen.i18n.dart
Normal file
45
refilc_mobile_ui/lib/screens/error_report_screen.i18n.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension SettingsLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"uhoh": "Uh Oh!",
|
||||
"description": "An error occurred!",
|
||||
"submit": "Submit",
|
||||
"details": "Details",
|
||||
"error": "Error",
|
||||
"os": "Operating System",
|
||||
"version": "App Version",
|
||||
"stack": "Stack Trace",
|
||||
"done": "Done",
|
||||
},
|
||||
"hu_hu": {
|
||||
"uhoh": "Ajajj!",
|
||||
"description": "Hiba történt!",
|
||||
"submit": "Probléma Jelentése",
|
||||
"details": "Részletek",
|
||||
"error": "Hiba",
|
||||
"os": "Operációs Rendszer",
|
||||
"version": "App Verzió",
|
||||
"stack": "Stacktrace",
|
||||
"done": "Kész",
|
||||
},
|
||||
"de_de": {
|
||||
"uhoh": "Uh Oh!",
|
||||
"description": "Ein Fehler ist aufgetreten!",
|
||||
"submit": "Abschicken",
|
||||
"details": "Details",
|
||||
"error": "Fehler",
|
||||
"os": "Betriebssystem",
|
||||
"version": "App Version",
|
||||
"stack": "Stack Trace",
|
||||
"done": "Fertig",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
65
refilc_mobile_ui/lib/screens/error_screen.dart
Normal file
65
refilc_mobile_ui/lib/screens/error_screen.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
class ErrorScreen extends StatelessWidget {
|
||||
const ErrorScreen(this.details, {super.key});
|
||||
|
||||
final FlutterErrorDetails details;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
shadowColor: Colors.transparent,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Icon(FeatherIcons.alertTriangle,
|
||||
size: 48.0, color: AppColors.of(context).red),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(12.0),
|
||||
child: Text(
|
||||
"An error occurred...",
|
||||
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16.0),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
child: CupertinoScrollbar(
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SelectableText(
|
||||
('${details.exceptionAsString()}\n'),
|
||||
style: const TextStyle(fontFamily: "monospace"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
30
refilc_mobile_ui/lib/screens/login/login_button.dart
Normal file
30
refilc_mobile_ui/lib/screens/login/login_button.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LoginButton extends StatelessWidget {
|
||||
const LoginButton({super.key, required this.onPressed, required this.child});
|
||||
|
||||
final void Function()? onPressed;
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialButton(
|
||||
elevation: 0,
|
||||
focusElevation: 0,
|
||||
hoverElevation: 0,
|
||||
highlightElevation: 0,
|
||||
minWidth: double.infinity,
|
||||
onPressed: onPressed,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
|
||||
color: AppColors.of(context).buttonBackground,
|
||||
textColor: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 6.0,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
129
refilc_mobile_ui/lib/screens/login/login_input.dart
Normal file
129
refilc_mobile_ui/lib/screens/login/login_input.dart
Normal file
@@ -0,0 +1,129 @@
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
enum LoginInputStyle { username, password, school }
|
||||
|
||||
class LoginInput extends StatefulWidget {
|
||||
const LoginInput(
|
||||
{super.key,
|
||||
required this.style,
|
||||
this.controller,
|
||||
this.focusNode,
|
||||
this.onClear});
|
||||
|
||||
final Function()? onClear;
|
||||
final LoginInputStyle style;
|
||||
final TextEditingController? controller;
|
||||
final FocusNode? focusNode;
|
||||
|
||||
@override
|
||||
State<LoginInput> createState() => _LoginInputState();
|
||||
}
|
||||
|
||||
class _LoginInputState extends State<LoginInput> {
|
||||
late bool obscure;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
obscure = widget.style == LoginInputStyle.password;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String autofill;
|
||||
|
||||
switch (widget.style) {
|
||||
case LoginInputStyle.username:
|
||||
autofill = AutofillHints.username;
|
||||
break;
|
||||
case LoginInputStyle.password:
|
||||
autofill = AutofillHints.password;
|
||||
break;
|
||||
case LoginInputStyle.school:
|
||||
autofill = AutofillHints.organizationName;
|
||||
break;
|
||||
}
|
||||
|
||||
return TextField(
|
||||
focusNode: widget.focusNode,
|
||||
controller: widget.controller,
|
||||
cursorColor: AppColors.of(context).filc,
|
||||
textInputAction: TextInputAction.next,
|
||||
autofillHints: [autofill],
|
||||
obscureText: obscure,
|
||||
scrollPhysics: const BouncingScrollPhysics(),
|
||||
decoration: InputDecoration(
|
||||
// fillColor: Colors.black.withOpacity(0.15),
|
||||
filled: false,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: BorderSide(
|
||||
width: 1,
|
||||
color: AppColors.of(context).inputBorder,
|
||||
),
|
||||
),
|
||||
// focusedBorder: UnderlineInputBorder(
|
||||
// borderRadius: BorderRadius.circular(12.0),
|
||||
// borderSide: const BorderSide(width: 0, color: Colors.transparent),
|
||||
// ),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: BorderSide(
|
||||
width: 1,
|
||||
color: AppColors.of(context).filc,
|
||||
),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: const BorderSide(
|
||||
width: 1,
|
||||
color: Color(0xFFFF0000),
|
||||
),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
suffixIconConstraints:
|
||||
const BoxConstraints(maxHeight: 42.0, maxWidth: 48.0),
|
||||
suffixIcon: widget.style == LoginInputStyle.password ||
|
||||
widget.style == LoginInputStyle.school
|
||||
? ClipOval(
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: IconButton(
|
||||
splashRadius: 20.0,
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (widget.style == LoginInputStyle.password) {
|
||||
setState(() => obscure = !obscure);
|
||||
} else {
|
||||
widget.controller?.clear();
|
||||
if (widget.onClear != null) widget.onClear!();
|
||||
}
|
||||
},
|
||||
icon: widget.style == LoginInputStyle.password
|
||||
? Icon(
|
||||
obscure ? FeatherIcons.eye : FeatherIcons.eyeOff,
|
||||
color: AppColors.of(context).text.withOpacity(0.8),
|
||||
weight: 0.1,
|
||||
size: 18.0,
|
||||
)
|
||||
: Icon(
|
||||
FeatherIcons.x,
|
||||
color: AppColors.of(context).text.withOpacity(0.8),
|
||||
weight: 0.1,
|
||||
size: 20.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.of(context).text.withOpacity(0.8),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
21
refilc_mobile_ui/lib/screens/login/login_route.dart
Normal file
21
refilc_mobile_ui/lib/screens/login/login_route.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Route loginRoute(Widget widget) {
|
||||
return PageRouteBuilder(
|
||||
pageBuilder: (context, animation, secondaryAnimation) => widget,
|
||||
transitionDuration: const Duration(milliseconds: 650),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
var curve = Curves.easeInOut;
|
||||
var curveTween = CurveTween(curve: curve);
|
||||
var begin = const Offset(1.0, 0.0);
|
||||
var end = Offset.zero;
|
||||
var tween = Tween(begin: begin, end: end).chain(curveTween);
|
||||
var offsetAnimation = animation.drive(tween);
|
||||
|
||||
return SlideTransition(
|
||||
position: offsetAnimation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
390
refilc_mobile_ui/lib/screens/login/login_screen.dart
Normal file
390
refilc_mobile_ui/lib/screens/login/login_screen.dart
Normal file
@@ -0,0 +1,390 @@
|
||||
// import 'dart:async';
|
||||
|
||||
import 'package:refilc/api/client.dart';
|
||||
import 'package:refilc/api/login.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:refilc_mobile_ui/common/system_chrome.dart';
|
||||
import 'package:refilc_mobile_ui/screens/login/login_button.dart';
|
||||
import 'package:refilc_mobile_ui/screens/login/login_input.dart';
|
||||
import 'package:refilc_mobile_ui/screens/login/school_input/school_input.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/privacy_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'login_screen.i18n.dart';
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({super.key, this.back = false});
|
||||
|
||||
final bool back;
|
||||
|
||||
@override
|
||||
LoginScreenState createState() => LoginScreenState();
|
||||
}
|
||||
|
||||
class LoginScreenState extends State<LoginScreen> {
|
||||
final usernameController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
final schoolController = SchoolInputController();
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
LoginState _loginState = LoginState.normal;
|
||||
bool showBack = false;
|
||||
|
||||
// Scaffold Gradient background
|
||||
// final LinearGradient _backgroundGradient = const LinearGradient(
|
||||
// colors: [
|
||||
// Color.fromARGB(255, 61, 122, 244),
|
||||
// Color.fromARGB(255, 23, 77, 185),
|
||||
// Color.fromARGB(255, 7, 42, 112),
|
||||
// ],
|
||||
// begin: Alignment(-0.8, -1.0),
|
||||
// end: Alignment(0.8, 1.0),
|
||||
// stops: [-1.0, 0.0, 1.0],
|
||||
// );
|
||||
|
||||
late String tempUsername = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
showBack = widget.back;
|
||||
|
||||
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarColor: Colors.white,
|
||||
systemNavigationBarIconBrightness: Brightness.dark,
|
||||
));
|
||||
|
||||
FilcAPI.getSchools().then((schools) {
|
||||
if (schools != null) {
|
||||
schoolController.update(() {
|
||||
schoolController.schools = schools;
|
||||
});
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
|
||||
content: Text("schools_error".i18n,
|
||||
style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: AppColors.of(context).red,
|
||||
context: context,
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: BoxDecoration(color: AppColors.of(context).loginBackground),
|
||||
child: SingleChildScrollView(
|
||||
physics: const ClampingScrollPhysics(),
|
||||
controller: _scrollController,
|
||||
child: Container(
|
||||
decoration:
|
||||
BoxDecoration(color: AppColors.of(context).loginBackground),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
alignment: Alignment.topLeft,
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 12.0),
|
||||
child: ClipOval(
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: showBack
|
||||
? BackButton(
|
||||
color: AppColors.of(context).loginPrimary)
|
||||
: const SizedBox(height: 48.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 50.0,
|
||||
),
|
||||
|
||||
// app icon
|
||||
Padding(
|
||||
padding: EdgeInsets.zero,
|
||||
child: Image.asset(
|
||||
'assets/icons/ic_rounded.png',
|
||||
width: 50.0,
|
||||
),
|
||||
),
|
||||
|
||||
// texts
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10.0,
|
||||
vertical: 12.0,
|
||||
),
|
||||
child: Text(
|
||||
'reFilc',
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).loginPrimary,
|
||||
fontSize: 28.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10.0,
|
||||
),
|
||||
child: Text(
|
||||
'login_w_kreten'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).loginPrimary,
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// inputs
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 22.0,
|
||||
right: 22.0,
|
||||
top: 150.0,
|
||||
),
|
||||
child: AutofillGroup(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// username
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"username".i18n,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).loginPrimary,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"usernameHint".i18n,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
color:
|
||||
AppColors.of(context).loginSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: LoginInput(
|
||||
style: LoginInputStyle.username,
|
||||
controller: usernameController,
|
||||
),
|
||||
),
|
||||
|
||||
// password
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"password".i18n,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).loginPrimary,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"passwordHint".i18n,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
color:
|
||||
AppColors.of(context).loginSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: LoginInput(
|
||||
style: LoginInputStyle.password,
|
||||
controller: passwordController,
|
||||
),
|
||||
),
|
||||
|
||||
// school
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6.0),
|
||||
child: Text(
|
||||
"school".i18n,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).loginPrimary,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
SchoolInput(
|
||||
scroll: _scrollController,
|
||||
controller: schoolController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// login button
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 35.0,
|
||||
left: 22.0,
|
||||
right: 22.0,
|
||||
),
|
||||
child: Visibility(
|
||||
visible: _loginState != LoginState.inProgress,
|
||||
replacement: const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 6.0),
|
||||
child: CircularProgressIndicator(
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
),
|
||||
child: LoginButton(
|
||||
child: Text("login".i18n,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20.0,
|
||||
)),
|
||||
onPressed: () => _loginAPI(context: context),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// error messages
|
||||
if (_loginState == LoginState.missingFields ||
|
||||
_loginState == LoginState.invalidGrant ||
|
||||
_loginState == LoginState.failed)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 8.0, left: 12.0, right: 12.0),
|
||||
child: Text(
|
||||
[
|
||||
"missing_fields",
|
||||
"invalid_grant",
|
||||
"error"
|
||||
][_loginState.index]
|
||||
.i18n,
|
||||
style: const TextStyle(
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 22.0),
|
||||
|
||||
// privacy policy
|
||||
GestureDetector(
|
||||
onTap: () => PrivacyView.show(context),
|
||||
child: Text(
|
||||
'privacy'.i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).loginSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _loginAPI({required BuildContext context}) {
|
||||
String username = usernameController.text;
|
||||
String password = passwordController.text;
|
||||
|
||||
tempUsername = username;
|
||||
|
||||
if (username == "" ||
|
||||
password == "" ||
|
||||
schoolController.selectedSchool == null) {
|
||||
return setState(() => _loginState = LoginState.missingFields);
|
||||
}
|
||||
|
||||
// ignore: no_leading_underscores_for_local_identifiers
|
||||
void _callAPI() {
|
||||
loginAPI(
|
||||
username: username,
|
||||
password: password,
|
||||
instituteCode: schoolController.selectedSchool!.instituteCode,
|
||||
context: context,
|
||||
onLogin: (user) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
|
||||
context: context,
|
||||
brightness: Brightness.light,
|
||||
content: Text("welcome".i18n.fill([user.name]),
|
||||
overflow: TextOverflow.ellipsis),
|
||||
));
|
||||
},
|
||||
onSuccess: () {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
setSystemChrome(context);
|
||||
Navigator.of(context).pushReplacementNamed("login_to_navigation");
|
||||
}).then(
|
||||
(res) => setState(() {
|
||||
// if (res == LoginState.invalidGrant &&
|
||||
// tempUsername.replaceAll(username, '').length <= 3) {
|
||||
// tempUsername = username + ' ';
|
||||
// Timer(
|
||||
// const Duration(milliseconds: 500),
|
||||
// () => _loginAPI(context: context),
|
||||
// );
|
||||
// // _loginAPI(context: context);
|
||||
// } else {
|
||||
_loginState = res;
|
||||
// }
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
setState(() => _loginState = LoginState.inProgress);
|
||||
_callAPI();
|
||||
}
|
||||
}
|
||||
63
refilc_mobile_ui/lib/screens/login/login_screen.i18n.dart
Normal file
63
refilc_mobile_ui/lib/screens/login/login_screen.i18n.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"username": "Username",
|
||||
"usernameHint": "Student ID number",
|
||||
"password": "Password",
|
||||
"passwordHint": "Date of birth",
|
||||
"school": "School",
|
||||
"login": "Log in",
|
||||
"welcome": "Welcome, %s!",
|
||||
"missing_fields": "Missing Fields!",
|
||||
"invalid_grant":
|
||||
// "Invalid Username/Password! (Try adding spaces after Username)",
|
||||
"Invalid Username/Password!",
|
||||
"error": "Failed to log in.",
|
||||
"schools_error": "Failed to get schools.",
|
||||
"login_w_kreten": "Log in with your e-KRÉTA account to continue!",
|
||||
"privacy": "Privacy Policy",
|
||||
},
|
||||
"hu_hu": {
|
||||
"username": "Felhasználónév",
|
||||
"usernameHint": "Oktatási azonosító",
|
||||
"password": "Jelszó",
|
||||
"passwordHint": "Születési dátum",
|
||||
"school": "Iskola",
|
||||
"login": "Belépés",
|
||||
"welcome": "Üdv, %s!",
|
||||
"missing_fields": "Hiányzó adatok!",
|
||||
"invalid_grant":
|
||||
// "Helytelen Felhasználónév/Jelszó! (Próbálj szóközöket írni a Felhasználónév után)",
|
||||
"Helytelen Felhasználónév/Jelszó!",
|
||||
"error": "Sikertelen bejelentkezés.",
|
||||
"schools_error": "Nem sikerült lekérni az iskolákat.",
|
||||
"login_w_kreten":
|
||||
"Jelentkezz be az e-KRÉTA fiókoddal a folytatáshoz!",
|
||||
"privacy": "Adatkezelési tájékoztató",
|
||||
},
|
||||
"de_de": {
|
||||
"username": "Benutzername",
|
||||
"usernameHint": "Ausbildung ID",
|
||||
"password": "Passwort",
|
||||
"passwordHint": "Geburtsdatum",
|
||||
"school": "Schule",
|
||||
"login": "Einloggen",
|
||||
"welcome": "Wilkommen, %s!",
|
||||
"missing_fields": "Fehlende Felder!",
|
||||
"invalid_grant": "Ungültiger Benutzername/Passwort!",
|
||||
"error": "Anmeldung fehlgeschlagen.",
|
||||
"schools_error": "Keine Schulen gefunden.",
|
||||
"login_w_kreten":
|
||||
"Melden Sie sich mit Ihrem e-KRÉTA-Konto an, um fortzufahren!",
|
||||
"privacy": "Datenschutzrichtlinie",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import 'package:refilc_mobile_ui/screens/login/login_input.dart';
|
||||
import 'package:refilc_mobile_ui/screens/login/school_input/school_input_overlay.dart';
|
||||
import 'package:refilc_mobile_ui/screens/login/school_input/school_input_tile.dart';
|
||||
import 'package:refilc_mobile_ui/screens/login/school_input/school_search.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:refilc_kreta_api/models/school.dart';
|
||||
|
||||
class SchoolInput extends StatefulWidget {
|
||||
const SchoolInput(
|
||||
{super.key, required this.controller, required this.scroll});
|
||||
|
||||
final SchoolInputController controller;
|
||||
final ScrollController scroll;
|
||||
|
||||
@override
|
||||
SchoolInputState createState() => SchoolInputState();
|
||||
}
|
||||
|
||||
class SchoolInputState extends State<SchoolInput> {
|
||||
final _focusNode = FocusNode();
|
||||
final _layerLink = LayerLink();
|
||||
late SchoolInputOverlay overlay;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
widget.controller.update = (fn) {
|
||||
if (mounted) setState(fn);
|
||||
};
|
||||
|
||||
overlay = SchoolInputOverlay(layerLink: _layerLink);
|
||||
|
||||
// Show school list when focused
|
||||
_focusNode.addListener(() {
|
||||
if (_focusNode.hasFocus) {
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) => overlay.createOverlayEntry(context));
|
||||
Future.delayed(const Duration(milliseconds: 100)).then((value) {
|
||||
if (mounted && widget.scroll.hasClients) {
|
||||
widget.scroll.animateTo(widget.scroll.offset + 500,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.ease);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
overlay.entry?.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// LoginInput TextField listener
|
||||
widget.controller.textController.addListener(() {
|
||||
String text = widget.controller.textController.text;
|
||||
if (text.isEmpty) {
|
||||
overlay.children = null;
|
||||
return;
|
||||
}
|
||||
|
||||
List<School> results =
|
||||
searchSchools(widget.controller.schools ?? [], text);
|
||||
setState(() {
|
||||
overlay.children = results
|
||||
.map((School e) => SchoolInputTile(
|
||||
school: e,
|
||||
onTap: () => _selectSchool(e),
|
||||
))
|
||||
.toList();
|
||||
});
|
||||
Overlay.of(context).setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
void _selectSchool(School school) {
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
|
||||
setState(() {
|
||||
widget.controller.selectedSchool = school;
|
||||
widget.controller.textController.text = school.name;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CompositedTransformTarget(
|
||||
link: _layerLink,
|
||||
child: widget.controller.schools == null
|
||||
? Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
child: const Center(
|
||||
child: SizedBox(
|
||||
height: 28.0,
|
||||
width: 28.0,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: LoginInput(
|
||||
style: LoginInputStyle.school,
|
||||
focusNode: _focusNode,
|
||||
onClear: () {
|
||||
widget.controller.selectedSchool = null;
|
||||
FocusScope.of(context).requestFocus(_focusNode);
|
||||
},
|
||||
controller: widget.controller.textController,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SchoolInputController {
|
||||
final textController = TextEditingController();
|
||||
School? selectedSchool;
|
||||
List<School>? schools;
|
||||
late void Function(void Function()) update;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'school_input_overlay.i18n.dart';
|
||||
|
||||
class SchoolInputOverlay {
|
||||
OverlayEntry? entry;
|
||||
final LayerLink layerLink;
|
||||
List<Widget>? children;
|
||||
|
||||
SchoolInputOverlay({required this.layerLink});
|
||||
|
||||
void createOverlayEntry(BuildContext context) {
|
||||
entry = OverlayEntry(builder: (_) => buildOverlayEntry(context));
|
||||
Overlay.of(context).insert(entry!);
|
||||
}
|
||||
|
||||
Widget buildOverlayEntry(BuildContext context) {
|
||||
RenderBox renderBox = context.findRenderObject()! as RenderBox;
|
||||
var size = renderBox.size;
|
||||
return SchoolInputOverlayWidget(
|
||||
size: size,
|
||||
layerLink: layerLink,
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SchoolInputOverlayWidget extends StatelessWidget {
|
||||
const SchoolInputOverlayWidget({
|
||||
super.key,
|
||||
required this.children,
|
||||
required this.size,
|
||||
required this.layerLink,
|
||||
});
|
||||
|
||||
final Size size;
|
||||
final List<Widget>? children;
|
||||
final LayerLink layerLink;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return children != null
|
||||
? Positioned(
|
||||
width: size.width,
|
||||
height: (children?.length ?? 0) > 0 ? 150.0 : 50.0,
|
||||
child: CompositedTransformFollower(
|
||||
link: layerLink,
|
||||
showWhenUnlinked: false,
|
||||
offset: Offset(0.0, size.height + 5.0),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0)),
|
||||
elevation: 4.0,
|
||||
shadowColor: Colors.black,
|
||||
child: (children?.length ?? 0) > 0
|
||||
? ListView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: children?.length ?? 0,
|
||||
itemBuilder: (context, index) {
|
||||
return children?[index] ?? Container();
|
||||
},
|
||||
)
|
||||
: Center(
|
||||
child: Text("noresults".i18n),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"noresults": "No results!",
|
||||
},
|
||||
"hu_hu": {
|
||||
"noresults": "Nincs találat!",
|
||||
},
|
||||
"de_de": {
|
||||
"noresults": "Keine Treffer!",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:refilc_kreta_api/models/school.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SchoolInputTile extends StatelessWidget {
|
||||
const SchoolInputTile({super.key, required this.school, this.onTap});
|
||||
|
||||
final School school;
|
||||
final Function()? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
onTap!();
|
||||
},
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// School name
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: Text(
|
||||
school.name,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
// School id
|
||||
Expanded(
|
||||
child: Text(
|
||||
school.instituteCode,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
// School city
|
||||
Expanded(
|
||||
child: Text(
|
||||
school.city,
|
||||
textAlign: TextAlign.right,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:refilc_kreta_api/models/school.dart';
|
||||
import 'package:refilc/utils/format.dart';
|
||||
|
||||
List<School> searchSchools(List<School> all, String pattern) {
|
||||
pattern = pattern.toLowerCase().specialChars();
|
||||
if (pattern == "") return all;
|
||||
|
||||
List<School> results = [];
|
||||
|
||||
for (var item in all) {
|
||||
int contains = 0;
|
||||
|
||||
pattern.split(" ").forEach((variation) {
|
||||
if (item.name.toLowerCase().specialChars().contains(variation)) {
|
||||
contains++;
|
||||
}
|
||||
});
|
||||
|
||||
if (contains == pattern.split(" ").length) results.add(item);
|
||||
|
||||
if (item.instituteCode.toLowerCase().specialChars().contains(pattern))
|
||||
results.add(item);
|
||||
}
|
||||
|
||||
results.sort((a, b) => a.name.compareTo(b.name));
|
||||
|
||||
return results;
|
||||
}
|
||||
31
refilc_mobile_ui/lib/screens/navigation/nabar.dart
Normal file
31
refilc_mobile_ui/lib/screens/navigation/nabar.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:refilc_mobile_ui/screens/navigation/navbar_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Navbar extends StatelessWidget {
|
||||
const Navbar(
|
||||
{super.key,
|
||||
required this.selectedIndex,
|
||||
required this.onSelected,
|
||||
required this.items});
|
||||
|
||||
final int selectedIndex;
|
||||
final void Function(int index) onSelected;
|
||||
final List<NavItem> items;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> buttons = List.generate(
|
||||
items.length,
|
||||
(index) => NavbarItem(
|
||||
item: items[index],
|
||||
active: index == selectedIndex,
|
||||
onTap: () => onSelected(index),
|
||||
),
|
||||
);
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: buttons,
|
||||
);
|
||||
}
|
||||
}
|
||||
64
refilc_mobile_ui/lib/screens/navigation/navbar_item.dart
Normal file
64
refilc_mobile_ui/lib/screens/navigation/navbar_item.dart
Normal file
@@ -0,0 +1,64 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NavItem {
|
||||
final String title;
|
||||
final Widget icon;
|
||||
final Widget activeIcon;
|
||||
|
||||
const NavItem(
|
||||
{required this.title, required this.icon, required this.activeIcon});
|
||||
}
|
||||
|
||||
class NavbarItem extends StatelessWidget {
|
||||
const NavbarItem({
|
||||
super.key,
|
||||
required this.item,
|
||||
required this.active,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final NavItem item;
|
||||
final bool active;
|
||||
final void Function() onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Widget icon = active ? item.activeIcon : item.icon;
|
||||
|
||||
return SafeArea(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 6.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
decoration: BoxDecoration(
|
||||
color: active
|
||||
? Theme.of(context).colorScheme.secondary.withOpacity(.4)
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
child: icon,
|
||||
),
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? Colors.black.withOpacity(.5)
|
||||
: Colors.white.withOpacity(.3),
|
||||
),
|
||||
child: icon,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
class NavigationRoute {
|
||||
late String _name;
|
||||
late int _index;
|
||||
|
||||
final List<String> _internalPageMap = [
|
||||
"home",
|
||||
"grades",
|
||||
"timetable",
|
||||
"messages",
|
||||
"absences",
|
||||
];
|
||||
|
||||
String get name => _name;
|
||||
int get index => _index;
|
||||
|
||||
set name(String n) {
|
||||
_name = n;
|
||||
_index = _internalPageMap.indexOf(n);
|
||||
}
|
||||
|
||||
set index(int i) {
|
||||
_index = i;
|
||||
_name = _internalPageMap.elementAt(i);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:refilc_mobile_ui/pages/absences/absences_page.dart';
|
||||
import 'package:refilc_mobile_ui/pages/grades/grades_page.dart';
|
||||
import 'package:refilc_mobile_ui/pages/home/home_page.dart';
|
||||
import 'package:refilc_mobile_ui/pages/messages/messages_page.dart';
|
||||
import 'package:refilc_mobile_ui/pages/timetable/timetable_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:animations/animations.dart';
|
||||
|
||||
Route navigationRouteHandler(RouteSettings settings) {
|
||||
switch (settings.name) {
|
||||
case "home":
|
||||
return navigationPageRoute((context) => const HomePage());
|
||||
case "grades":
|
||||
return navigationPageRoute((context) => const GradesPage());
|
||||
case "timetable":
|
||||
return navigationPageRoute((context) => const TimetablePage());
|
||||
case "messages":
|
||||
return navigationPageRoute((context) => const MessagesPage());
|
||||
case "absences":
|
||||
return navigationPageRoute((context) => const AbsencesPage());
|
||||
default:
|
||||
return navigationPageRoute((context) => const HomePage());
|
||||
}
|
||||
}
|
||||
|
||||
Route navigationPageRoute(Widget Function(BuildContext) builder) {
|
||||
return PageRouteBuilder(
|
||||
pageBuilder: (context, _, __) => builder(context),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return FadeThroughTransition(
|
||||
fillColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
animation: animation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
342
refilc_mobile_ui/lib/screens/navigation/navigation_screen.dart
Normal file
342
refilc_mobile_ui/lib/screens/navigation/navigation_screen.dart
Normal file
@@ -0,0 +1,342 @@
|
||||
import 'package:refilc/api/providers/update_provider.dart';
|
||||
import 'package:refilc/helpers/quick_actions.dart';
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/theme/observer.dart';
|
||||
import 'package:refilc_kreta_api/client/client.dart';
|
||||
import 'package:refilc_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:refilc_mobile_ui/common/system_chrome.dart';
|
||||
import 'package:refilc_mobile_ui/screens/navigation/nabar.dart';
|
||||
import 'package:refilc_mobile_ui/screens/navigation/navbar_item.dart';
|
||||
import 'package:refilc_mobile_ui/screens/navigation/navigation_route.dart';
|
||||
import 'package:refilc_mobile_ui/screens/navigation/navigation_route_handler.dart';
|
||||
import 'package:refilc/icons/filc_icons.dart';
|
||||
import 'package:refilc_mobile_ui/screens/navigation/status_bar.dart';
|
||||
import 'package:refilc_mobile_ui/screens/news/news_view.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/settings_screen.dart';
|
||||
import 'package:refilc_plus/ui/mobile/goal_planner/goal_complete_modal.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:refilc_mobile_ui/common/screens.i18n.dart';
|
||||
import 'package:refilc/api/providers/news_provider.dart';
|
||||
import 'package:refilc/api/providers/sync.dart';
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
import 'package:wtf_sliding_sheet/wtf_sliding_sheet.dart';
|
||||
import 'package:background_fetch/background_fetch.dart';
|
||||
import 'package:refilc_plus/providers/goal_provider.dart';
|
||||
import 'package:refilc/api/providers/ad_provider.dart';
|
||||
|
||||
class NavigationScreen extends StatefulWidget {
|
||||
const NavigationScreen({super.key});
|
||||
|
||||
static NavigationScreenState? of(BuildContext context) =>
|
||||
context.findAncestorStateOfType<NavigationScreenState>();
|
||||
|
||||
@override
|
||||
NavigationScreenState createState() => NavigationScreenState();
|
||||
}
|
||||
|
||||
class NavigationScreenState extends State<NavigationScreen>
|
||||
with WidgetsBindingObserver {
|
||||
late NavigationRoute selected;
|
||||
List<String> initializers = [];
|
||||
final _navigatorState = GlobalKey<NavigatorState>();
|
||||
|
||||
late SettingsProvider settings;
|
||||
late NewsProvider newsProvider;
|
||||
late GoalProvider goalProvider;
|
||||
late UpdateProvider updateProvider;
|
||||
late GradeProvider gradeProvicer;
|
||||
late AdProvider adProvider;
|
||||
|
||||
NavigatorState? get navigator => _navigatorState.currentState;
|
||||
|
||||
void customRoute(Route route) => navigator?.pushReplacement(route);
|
||||
|
||||
bool init(String id) {
|
||||
if (initializers.contains(id)) return false;
|
||||
|
||||
initializers.add(id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void _checkForWidgetLaunch() {
|
||||
HomeWidget.initiallyLaunchedFromHomeWidget().then(_launchedFromWidget);
|
||||
}
|
||||
|
||||
void _launchedFromWidget(Uri? uri) async {
|
||||
if (uri == null) return;
|
||||
|
||||
if (uri.scheme == "timetable" && uri.authority == "refresh") {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
|
||||
setPage("timetable");
|
||||
_navigatorState.currentState
|
||||
?.pushNamedAndRemoveUntil("timetable", (_) => false);
|
||||
} else if (uri.scheme == "settings" && uri.authority == "premium") {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
|
||||
showSlidingBottomSheet(
|
||||
context,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => SlidingSheetDialog(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
scrollSpec: const ScrollSpec.bouncingScroll(),
|
||||
snapSpec: const SnapSpec(
|
||||
snap: true,
|
||||
snappings: [1.0],
|
||||
initialSnap: 1.0,
|
||||
positioning: SnapPositioning.relativeToSheetHeight,
|
||||
),
|
||||
cornerRadius: 16,
|
||||
cornerRadiusOnFullscreen: 0,
|
||||
builder: (context, state) => Material(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: const SettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Platform messages are asynchronous, so we initialize in an async method.
|
||||
Future<void> initPlatformState() async {
|
||||
// Configure BackgroundFetch.
|
||||
int status = await BackgroundFetch.configure(
|
||||
BackgroundFetchConfig(
|
||||
minimumFetchInterval: 15,
|
||||
stopOnTerminate: false,
|
||||
enableHeadless: true,
|
||||
requiresBatteryNotLow: false,
|
||||
requiresCharging: false,
|
||||
requiresStorageNotLow: false,
|
||||
requiresDeviceIdle: false,
|
||||
requiredNetworkType: NetworkType.ANY), (String taskId) async {
|
||||
// <-- Event handler
|
||||
// This is the fetch-event callback.
|
||||
if (kDebugMode) {
|
||||
print("[BackgroundFetch] Event received $taskId");
|
||||
}
|
||||
|
||||
// IMPORTANT: You must signal completion of your task or the OS can punish your app
|
||||
// for taking too long in the background.
|
||||
BackgroundFetch.finish(taskId);
|
||||
}, (String taskId) async {
|
||||
// <-- Task timeout handler.
|
||||
// This task has exceeded its allowed running-time. You must stop what you're doing and immediately .finish(taskId)
|
||||
if (kDebugMode) {
|
||||
print("[BackgroundFetch] TASK TIMEOUT taskId: $taskId");
|
||||
}
|
||||
BackgroundFetch.finish(taskId);
|
||||
});
|
||||
if (kDebugMode) {
|
||||
print('[BackgroundFetch] configure success: $status');
|
||||
}
|
||||
|
||||
// If the widget was removed from the tree while the asynchronous platform
|
||||
// message was in flight, we want to discard the reply rather than calling
|
||||
// setState to update our non-existent appearance.
|
||||
if (!mounted) return;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
HomeWidget.setAppGroupId('hu.refilc.naplo.group');
|
||||
|
||||
_checkForWidgetLaunch();
|
||||
HomeWidget.widgetClicked.listen(_launchedFromWidget);
|
||||
|
||||
settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
selected = NavigationRoute();
|
||||
selected.index = settings.startPage.index; // set page index to start page
|
||||
|
||||
// add brightness observer
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
// set client User-Agent
|
||||
Provider.of<KretaClient>(context, listen: false).userAgent =
|
||||
settings.config.userAgent;
|
||||
|
||||
// get news
|
||||
newsProvider = Provider.of<NewsProvider>(context, listen: false);
|
||||
newsProvider.restore().then((value) => newsProvider.fetch());
|
||||
|
||||
// init grade provider (for goals)
|
||||
gradeProvicer = Provider.of<GradeProvider>(context, listen: false);
|
||||
|
||||
// get goals
|
||||
goalProvider = Provider.of<GoalProvider>(context, listen: false);
|
||||
goalProvider.fetchDone(gradeProvider: gradeProvicer);
|
||||
|
||||
// get releases
|
||||
updateProvider = Provider.of<UpdateProvider>(context, listen: false);
|
||||
updateProvider.fetch();
|
||||
|
||||
// get advertisements
|
||||
adProvider = Provider.of<AdProvider>(context, listen: false);
|
||||
adProvider.fetch();
|
||||
|
||||
// initial sync
|
||||
syncAll(context);
|
||||
setupQuickActions();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangePlatformBrightness() {
|
||||
if (settings.theme == ThemeMode.system) {
|
||||
// ignore: deprecated_member_use
|
||||
Brightness? brightness =
|
||||
// ignore: deprecated_member_use
|
||||
WidgetsBinding.instance.window.platformBrightness;
|
||||
Provider.of<ThemeModeObserver>(context, listen: false).changeTheme(
|
||||
brightness == Brightness.light ? ThemeMode.light : ThemeMode.dark);
|
||||
}
|
||||
super.didChangePlatformBrightness();
|
||||
}
|
||||
|
||||
void setPage(String page) => setState(() => selected.name = page);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
setSystemChrome(context);
|
||||
settings = Provider.of<SettingsProvider>(context);
|
||||
newsProvider = Provider.of<NewsProvider>(context);
|
||||
goalProvider = Provider.of<GoalProvider>(context);
|
||||
|
||||
// show news and complete goals
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (newsProvider.show) {
|
||||
NewsView.show(newsProvider.news[0], context: context)
|
||||
.then((value) => newsProvider.release());
|
||||
newsProvider.lock();
|
||||
}
|
||||
|
||||
if (goalProvider.hasDoneGoals) {
|
||||
GoalCompleteModal.show(goalProvider.doneSubject!, context: context);
|
||||
goalProvider.lock();
|
||||
}
|
||||
});
|
||||
|
||||
handleQuickActions(context, (page) {
|
||||
setPage(page);
|
||||
_navigatorState.currentState?.pushReplacementNamed(page);
|
||||
});
|
||||
|
||||
// ignore: deprecated_member_use
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (_navigatorState.currentState?.canPop() ?? false) {
|
||||
_navigatorState.currentState?.pop();
|
||||
if (!kDebugMode) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (selected.index != 0) {
|
||||
setState(() => selected.index = 0);
|
||||
_navigatorState.currentState?.pushReplacementNamed(selected.name);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
child: Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
Navigator(
|
||||
key: _navigatorState,
|
||||
initialRoute: selected.name,
|
||||
onGenerateRoute: (settings) =>
|
||||
navigationRouteHandler(settings),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Status bar
|
||||
Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: const StatusBar(),
|
||||
),
|
||||
|
||||
// Bottom Navigaton Bar
|
||||
Material(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: MediaQuery.removePadding(
|
||||
context: context,
|
||||
removeTop: true,
|
||||
child: Navbar(
|
||||
selectedIndex: selected.index,
|
||||
onSelected: onPageSelected,
|
||||
items: [
|
||||
NavItem(
|
||||
title: "home".i18n,
|
||||
icon: const Icon(FilcIcons.home),
|
||||
activeIcon: const Icon(FilcIcons.homefill),
|
||||
),
|
||||
NavItem(
|
||||
title: "grades".i18n,
|
||||
icon: const Icon(FeatherIcons.bookmark),
|
||||
activeIcon: const Icon(FilcIcons.gradesfill),
|
||||
),
|
||||
NavItem(
|
||||
title: "timetable".i18n,
|
||||
icon: const Icon(FeatherIcons.calendar),
|
||||
activeIcon: const Icon(FilcIcons.timetablefill),
|
||||
),
|
||||
NavItem(
|
||||
title: "messages".i18n,
|
||||
icon: const Icon(FeatherIcons.messageSquare),
|
||||
activeIcon: const Icon(FilcIcons.messagesfill),
|
||||
),
|
||||
NavItem(
|
||||
title: "absences".i18n,
|
||||
icon: const Icon(FeatherIcons.clock),
|
||||
activeIcon: const Icon(FilcIcons.absencesfill),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void onPageSelected(int index) {
|
||||
// Vibrate, then set the active screen
|
||||
if (selected.index != index) {
|
||||
switch (settings.vibrate) {
|
||||
case VibrationStrength.light:
|
||||
HapticFeedback.lightImpact();
|
||||
break;
|
||||
case VibrationStrength.medium:
|
||||
HapticFeedback.mediumImpact();
|
||||
break;
|
||||
case VibrationStrength.strong:
|
||||
HapticFeedback.heavyImpact();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
setState(() => selected.index = index);
|
||||
_navigatorState.currentState?.pushReplacementNamed(selected.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
120
refilc_mobile_ui/lib/screens/navigation/status_bar.dart
Normal file
120
refilc_mobile_ui/lib/screens/navigation/status_bar.dart
Normal file
@@ -0,0 +1,120 @@
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc/utils/color.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:refilc/api/providers/status_provider.dart';
|
||||
import 'status_bar.i18n.dart';
|
||||
|
||||
class StatusBar extends StatefulWidget {
|
||||
const StatusBar({super.key});
|
||||
|
||||
@override
|
||||
StatusBarState createState() => StatusBarState();
|
||||
}
|
||||
|
||||
class StatusBarState extends State<StatusBar> {
|
||||
late StatusProvider statusProvider;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
statusProvider = Provider.of<StatusProvider>(context);
|
||||
|
||||
Status? currentStatus = statusProvider.getStatus();
|
||||
Color backgroundColor = _statusColor(currentStatus);
|
||||
Color color = ColorUtils.foregroundColor(backgroundColor);
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
height: currentStatus != null ? 32.0 : 0,
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: Stack(
|
||||
children: [
|
||||
// Background
|
||||
AnimatedContainer(
|
||||
margin: const EdgeInsets.only(left: 6.0, right: 6.0, top: 8.0),
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
height: currentStatus != null ? 28.0 : 0,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
boxShadow: [
|
||||
BoxShadow(color: Theme.of(context).shadowColor, blurRadius: 8.0)
|
||||
],
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
),
|
||||
|
||||
// Progress bar
|
||||
if (currentStatus == Status.syncing)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 6.0, right: 6.0, top: 8.0),
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: AnimatedContainer(
|
||||
height: currentStatus != null ? 28.0 : 0,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
width: MediaQuery.of(context).size.width *
|
||||
statusProvider.progress -
|
||||
16.0,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondary.withOpacity(0.8),
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Text
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
_statusString(currentStatus),
|
||||
style: TextStyle(color: color, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _statusString(Status? status) {
|
||||
switch (status) {
|
||||
case Status.syncing:
|
||||
return "Syncing data".i18n;
|
||||
case Status.maintenance:
|
||||
return "KRETA Maintenance".i18n;
|
||||
case Status.apiError:
|
||||
return "KRETA API error".i18n;
|
||||
case Status.network:
|
||||
return "No connection".i18n;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
Color _statusColor(Status? status) {
|
||||
switch (status) {
|
||||
case Status.maintenance:
|
||||
return AppColors.of(context).red;
|
||||
case Status.apiError:
|
||||
return AppColors.of(context).red;
|
||||
case Status.network:
|
||||
case Status.syncing:
|
||||
default:
|
||||
HSLColor color =
|
||||
HSLColor.fromColor(Theme.of(context).scaffoldBackgroundColor);
|
||||
if (color.lightness >= 0.5) {
|
||||
color = color.withSaturation(0.3);
|
||||
color = color.withLightness(color.lightness - 0.1);
|
||||
} else {
|
||||
color = color.withSaturation(0);
|
||||
color = color.withLightness(color.lightness + 0.2);
|
||||
}
|
||||
return color.toColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
30
refilc_mobile_ui/lib/screens/navigation/status_bar.i18n.dart
Normal file
30
refilc_mobile_ui/lib/screens/navigation/status_bar.i18n.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Syncing data": "Syncing data",
|
||||
"KRETA Maintenance": "KRETA Maintenance",
|
||||
"KRETA API error": "KRETA API Error",
|
||||
"No connection": "No connection",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Syncing data": "Adatok frissítése",
|
||||
"KRETA Maintenance": "KRÉTA Karbantartás",
|
||||
"KRETA API error": "KRÉTA API Hiba",
|
||||
"No connection": "Nincs kapcsolat",
|
||||
},
|
||||
"de_de": {
|
||||
"Syncing data": "Daten aktualisieren",
|
||||
"KRETA Maintenance": "KRETA Wartung",
|
||||
"KRETA API error": "KRETA API Fehler",
|
||||
"No connection": "Keine Verbindung",
|
||||
},
|
||||
};
|
||||
|
||||
String get i18n => localize(this, _t);
|
||||
String fill(List<Object> params) => localizeFill(this, params);
|
||||
String plural(int value) => localizePlural(value, this, _t);
|
||||
String version(Object modifier) => localizeVersion(modifier, this, _t);
|
||||
}
|
||||
66
refilc_mobile_ui/lib/screens/news/news_screen.dart
Normal file
66
refilc_mobile_ui/lib/screens/news/news_screen.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc_mobile_ui/common/empty.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:refilc_mobile_ui/screens/news/news_tile.dart';
|
||||
import 'package:refilc/models/news.dart';
|
||||
import 'package:refilc_mobile_ui/screens/news/news_view.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:refilc/api/providers/news_provider.dart';
|
||||
|
||||
class NewsScreen extends StatelessWidget {
|
||||
const NewsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var newsProvider = Provider.of<NewsProvider>(context);
|
||||
|
||||
List<News> news = [];
|
||||
news = newsProvider.news.where((e) => e.title != "").toList();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
title: Text("news".i18n,
|
||||
style: TextStyle(color: AppColors.of(context).text)),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => newsProvider.fetch(),
|
||||
child: ListView.builder(
|
||||
physics: const BouncingScrollPhysics(
|
||||
parent: AlwaysScrollableScrollPhysics()),
|
||||
itemCount: max(news.length, 1),
|
||||
itemBuilder: (context, index) {
|
||||
if (news.isNotEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24.0, vertical: 12.0),
|
||||
child: Panel(
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: NewsTile(
|
||||
news[index],
|
||||
onTap: () => NewsView.show(news[index],
|
||||
context: context, force: true),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(top: 24.0),
|
||||
child: Empty(subtitle: "Nothing to see here"),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
30
refilc_mobile_ui/lib/screens/news/news_tile.dart
Normal file
30
refilc_mobile_ui/lib/screens/news/news_tile.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:refilc/models/news.dart';
|
||||
import 'package:refilc/utils/format.dart';
|
||||
|
||||
class NewsTile extends StatelessWidget {
|
||||
const NewsTile(this.news, {super.key, this.onTap});
|
||||
|
||||
final News news;
|
||||
final Function()? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
news.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
news.content.escapeHtml().replaceAll("\n", " "),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onTap: onTap,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
|
||||
);
|
||||
}
|
||||
}
|
||||
137
refilc_mobile_ui/lib/screens/news/news_view.dart
Normal file
137
refilc_mobile_ui/lib/screens/news/news_view.dart
Normal file
@@ -0,0 +1,137 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc_mobile_ui/common/dialog_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:refilc/models/news.dart';
|
||||
import 'package:refilc/utils/format.dart';
|
||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
|
||||
|
||||
class NewsView extends StatelessWidget {
|
||||
const NewsView(this.news, {super.key});
|
||||
|
||||
final News news;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 100.0, horizontal: 32.0),
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// Content
|
||||
Expanded(
|
||||
child: ListView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8.0, right: 6.0, top: 14.0, bottom: 8.0),
|
||||
child: Text(
|
||||
news.title,
|
||||
maxLines: 3,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600, fontSize: 18.0),
|
||||
),
|
||||
),
|
||||
SelectableLinkify(
|
||||
text: news.content.escapeHtml(),
|
||||
options:
|
||||
const LinkifyOptions(looseUrl: true, removeWww: true),
|
||||
onOpen: (link) {
|
||||
launch(
|
||||
link.url,
|
||||
customTabsOption: CustomTabsOption(
|
||||
showPageTitle: true,
|
||||
toolbarColor:
|
||||
Theme.of(context).scaffoldBackgroundColor),
|
||||
);
|
||||
},
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500, fontSize: 14.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Actions
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (news.link != "")
|
||||
DialogButton(
|
||||
label: news.openLabel != ""
|
||||
? news.openLabel
|
||||
: "open".i18n.toUpperCase(),
|
||||
onTap: () => launch(
|
||||
news.link,
|
||||
customTabsOption: CustomTabsOption(
|
||||
showPageTitle: true,
|
||||
toolbarColor:
|
||||
Theme.of(context).scaffoldBackgroundColor),
|
||||
),
|
||||
),
|
||||
DialogButton(
|
||||
label: "done".i18n,
|
||||
onTap: () => Navigator.of(context).maybePop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<T?> show<T>(News news,
|
||||
{required BuildContext context, bool force = false}) {
|
||||
if (news.title == "") return Future<T?>.value(null);
|
||||
|
||||
bool popup = news.platform == 'all' || force;
|
||||
|
||||
if (Provider.of<SettingsProvider>(context, listen: false).newsEnabled ||
|
||||
news.emergency ||
|
||||
force) {
|
||||
switch (news.platform.trim().toLowerCase()) {
|
||||
case "android":
|
||||
if (Platform.isAndroid) popup = true;
|
||||
break;
|
||||
case "ios":
|
||||
if (Platform.isIOS) popup = true;
|
||||
break;
|
||||
case "linux":
|
||||
if (Platform.isLinux) popup = true;
|
||||
break;
|
||||
case "windows":
|
||||
if (Platform.isWindows) popup = true;
|
||||
break;
|
||||
case "macos":
|
||||
if (Platform.isMacOS) popup = true;
|
||||
break;
|
||||
case "all":
|
||||
popup = true;
|
||||
break;
|
||||
default:
|
||||
popup = true;
|
||||
}
|
||||
} else {
|
||||
popup = false;
|
||||
}
|
||||
|
||||
if (popup) {
|
||||
return showDialog<T?>(
|
||||
context: context,
|
||||
builder: (context) => NewsView(news),
|
||||
barrierDismissible: true);
|
||||
} else {
|
||||
return Future<T?>.value(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
186
refilc_mobile_ui/lib/screens/notes/add_note_screen.dart
Normal file
186
refilc_mobile_ui/lib/screens/notes/add_note_screen.dart
Normal file
@@ -0,0 +1,186 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:refilc/api/providers/database_provider.dart';
|
||||
import 'package:refilc/api/providers/self_note_provider.dart';
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/models/self_note.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc_kreta_api/providers/homework_provider.dart';
|
||||
import 'package:refilc_mobile_ui/screens/notes/notes_screen.i18n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class AddNoteScreen extends StatefulWidget {
|
||||
const AddNoteScreen({super.key, this.initialNote});
|
||||
|
||||
final SelfNote? initialNote;
|
||||
|
||||
@override
|
||||
AddNoteScreenState createState() => AddNoteScreenState();
|
||||
}
|
||||
|
||||
class AddNoteScreenState extends State<AddNoteScreen> {
|
||||
late UserProvider user;
|
||||
late HomeworkProvider homeworkProvider;
|
||||
late DatabaseProvider databaseProvider;
|
||||
late SelfNoteProvider selfNoteProvider;
|
||||
|
||||
final _contentController = TextEditingController();
|
||||
final _titleController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_contentController.text = widget.initialNote?.content ?? '';
|
||||
_titleController.text = widget.initialNote?.title ?? '';
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
user = Provider.of<UserProvider>(context);
|
||||
homeworkProvider = Provider.of<HomeworkProvider>(context);
|
||||
databaseProvider = Provider.of<DatabaseProvider>(context);
|
||||
selfNoteProvider = Provider.of<SelfNoteProvider>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
title: Text(
|
||||
widget.initialNote == null ? 'new_note'.i18n : 'edit_note'.i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text,
|
||||
fontSize: 26.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10.1),
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
// handle tap
|
||||
if (_contentController.text.replaceAll(' ', '') == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
var notes = selfNoteProvider.notes;
|
||||
|
||||
if (widget.initialNote == null) {
|
||||
notes.add(SelfNote.fromJson({
|
||||
'id': const Uuid().v4(),
|
||||
'title': _titleController.text.replaceAll(' ', '') == ''
|
||||
? null
|
||||
: _titleController.text,
|
||||
'content': _contentController.text
|
||||
}));
|
||||
} else {
|
||||
var i =
|
||||
notes.indexWhere((e) => e.id == widget.initialNote!.id);
|
||||
|
||||
notes[i] = SelfNote.fromJson({
|
||||
'id': notes[i].id,
|
||||
'title': _titleController.text.replaceAll(' ', '') == ''
|
||||
? null
|
||||
: _titleController.text,
|
||||
'content': _contentController.text,
|
||||
});
|
||||
}
|
||||
|
||||
await selfNoteProvider.store(notes);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
if (widget.initialNote != null) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.4),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
child: const Icon(
|
||||
FeatherIcons.check,
|
||||
size: 20.0,
|
||||
),
|
||||
),
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color:
|
||||
Theme.of(context).brightness == Brightness.light
|
||||
? Colors.black.withOpacity(.5)
|
||||
: Colors.white.withOpacity(.3),
|
||||
),
|
||||
child: const Icon(
|
||||
FeatherIcons.check,
|
||||
size: 20.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
||||
child: Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: _titleController,
|
||||
expands: false,
|
||||
maxLines: 1,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
hintText: "hint_t".i18n,
|
||||
hintStyle: const TextStyle(
|
||||
fontSize: 22.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
fontSize: 22.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _contentController,
|
||||
expands: true,
|
||||
minLines: null,
|
||||
maxLines: null,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
hintText: "hint".i18n,
|
||||
hintStyle: const TextStyle(fontSize: 16.0),
|
||||
),
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(fontSize: 16.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
153
refilc_mobile_ui/lib/screens/notes/note_view_screen.dart
Normal file
153
refilc_mobile_ui/lib/screens/notes/note_view_screen.dart
Normal file
@@ -0,0 +1,153 @@
|
||||
import 'package:refilc/api/providers/self_note_provider.dart';
|
||||
import 'package:refilc/models/self_note.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc_mobile_ui/screens/notes/add_note_screen.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class NoteViewScreen extends StatefulWidget {
|
||||
const NoteViewScreen({super.key, required this.note});
|
||||
|
||||
final SelfNote note;
|
||||
|
||||
@override
|
||||
NoteViewScreenState createState() => NoteViewScreenState();
|
||||
}
|
||||
|
||||
class NoteViewScreenState extends State<NoteViewScreen> {
|
||||
late SelfNoteProvider selfNoteProvider;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
selfNoteProvider = Provider.of<SelfNoteProvider>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
title: Text(
|
||||
widget.note.title ?? '${widget.note.content.split(' ')[0]}...',
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text,
|
||||
fontSize: 26.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10.1),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
// handle tap
|
||||
Navigator.of(context, rootNavigator: true).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) =>
|
||||
AddNoteScreen(initialNote: widget.note)));
|
||||
},
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.4),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
child: const Icon(
|
||||
FeatherIcons.edit,
|
||||
size: 20.0,
|
||||
),
|
||||
),
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color:
|
||||
Theme.of(context).brightness == Brightness.light
|
||||
? Colors.black.withOpacity(.5)
|
||||
: Colors.white.withOpacity(.3),
|
||||
),
|
||||
child: const Icon(
|
||||
FeatherIcons.edit,
|
||||
size: 20.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10.1),
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
// handle tap
|
||||
var notes = selfNoteProvider.notes;
|
||||
notes.removeWhere((e) => e.id == widget.note.id);
|
||||
await selfNoteProvider.store(notes);
|
||||
|
||||
// ignore: use_build_context_synchronously
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.4),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
child: const Icon(
|
||||
FeatherIcons.trash2,
|
||||
size: 20.0,
|
||||
),
|
||||
),
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color:
|
||||
Theme.of(context).brightness == Brightness.light
|
||||
? Colors.black.withOpacity(.5)
|
||||
: Colors.white.withOpacity(.3),
|
||||
),
|
||||
child: const Icon(
|
||||
FeatherIcons.trash2,
|
||||
size: 20.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.note.content,
|
||||
textAlign: TextAlign.justify,
|
||||
style: const TextStyle(fontSize: 18.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
282
refilc_mobile_ui/lib/screens/notes/notes_screen.dart
Normal file
282
refilc_mobile_ui/lib/screens/notes/notes_screen.dart
Normal file
@@ -0,0 +1,282 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:refilc/api/providers/database_provider.dart';
|
||||
import 'package:refilc/api/providers/self_note_provider.dart';
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc/utils/format.dart';
|
||||
import 'package:refilc_kreta_api/models/homework.dart';
|
||||
import 'package:refilc_kreta_api/providers/homework_provider.dart';
|
||||
import 'package:refilc_mobile_ui/common/empty.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:refilc_mobile_ui/common/soon_alert/soon_alert.dart';
|
||||
import 'package:refilc_mobile_ui/common/widgets/tick_tile.dart';
|
||||
import 'package:refilc_mobile_ui/screens/notes/add_note_screen.dart';
|
||||
import 'package:refilc_mobile_ui/screens/notes/note_view_screen.dart';
|
||||
import 'package:refilc_mobile_ui/screens/notes/notes_screen.i18n.dart';
|
||||
import 'package:refilc_mobile_ui/screens/notes/self_note_tile.dart';
|
||||
import 'package:refilc_plus/providers/premium_provider.dart';
|
||||
import 'package:refilc_plus/ui/mobile/premium/premium_inline.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class NotesScreen extends StatefulWidget {
|
||||
const NotesScreen({super.key, required this.doneItems});
|
||||
|
||||
final Map<String, bool> doneItems;
|
||||
|
||||
@override
|
||||
NotesScreenState createState() => NotesScreenState();
|
||||
}
|
||||
|
||||
class NotesScreenState extends State<NotesScreen> {
|
||||
late UserProvider user;
|
||||
late HomeworkProvider homeworkProvider;
|
||||
late DatabaseProvider databaseProvider;
|
||||
late SelfNoteProvider selfNoteProvider;
|
||||
|
||||
List<Widget> noteTiles = [];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
user = Provider.of<UserProvider>(context);
|
||||
homeworkProvider = Provider.of<HomeworkProvider>(context);
|
||||
databaseProvider = Provider.of<DatabaseProvider>(context);
|
||||
selfNoteProvider = Provider.of<SelfNoteProvider>(context);
|
||||
|
||||
void generateTiles() {
|
||||
List<Widget> tiles = [];
|
||||
|
||||
List<Homework> hw = homeworkProvider.homework
|
||||
.where((e) => e.deadline.isAfter(DateTime.now()))
|
||||
// e.deadline.isBefore(DateTime(DateTime.now().year,
|
||||
// DateTime.now().month, DateTime.now().day + 3)))
|
||||
.toList();
|
||||
|
||||
// todo tiles
|
||||
List<Widget> toDoTiles = [];
|
||||
|
||||
if (hw.isNotEmpty) {
|
||||
toDoTiles.addAll(hw.map((e) => TickTile(
|
||||
padding: EdgeInsets.zero,
|
||||
title: 'homework'.i18n,
|
||||
description:
|
||||
'${(e.subject.isRenamed ? e.subject.renamedTo : e.subject.name) ?? ''}, ${e.content.escapeHtml()}',
|
||||
isTicked: widget.doneItems[e.id] ?? false,
|
||||
onTap: (p0) async {
|
||||
if (!widget.doneItems.containsKey(e.id)) {
|
||||
widget.doneItems.addAll({e.id: p0});
|
||||
} else {
|
||||
widget.doneItems[e.id] = p0;
|
||||
}
|
||||
await databaseProvider.userStore
|
||||
.storeToDoItem(widget.doneItems, userId: user.id!);
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
if (toDoTiles.isNotEmpty) {
|
||||
tiles.add(Panel(
|
||||
title: Text('todo'.i18n),
|
||||
child: Column(
|
||||
children: toDoTiles,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
// self notes
|
||||
List<Widget> selfNoteTiles = [];
|
||||
|
||||
if (selfNoteProvider.notes.isNotEmpty) {
|
||||
selfNoteTiles.addAll(selfNoteProvider.notes.map(
|
||||
(e) => SelfNoteTile(
|
||||
title: e.title ?? e.content.split(' ')[0],
|
||||
content: e.content,
|
||||
onTap: () => Navigator.of(context, rootNavigator: true).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => NoteViewScreen(note: e))),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
if (selfNoteTiles.isNotEmpty) {
|
||||
// padding
|
||||
tiles.add(const SizedBox(
|
||||
height: 28.0,
|
||||
));
|
||||
|
||||
// actual thing
|
||||
tiles.add(Panel(
|
||||
title: Text('your_notes'.i18n),
|
||||
padding: EdgeInsets.zero,
|
||||
isTransparent: true,
|
||||
child: Wrap(
|
||||
spacing: 18.0,
|
||||
runSpacing: 18.0,
|
||||
children: selfNoteTiles,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
// insert empty tile
|
||||
if (tiles.isEmpty) {
|
||||
tiles.insert(
|
||||
0,
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24.0),
|
||||
child: Empty(subtitle: "empty".i18n),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
tiles.add(Provider.of<PremiumProvider>(context, listen: false).hasPremium
|
||||
? const SizedBox()
|
||||
: const Padding(
|
||||
padding: EdgeInsets.only(top: 24.0),
|
||||
child: PremiumInline(features: [
|
||||
PremiumInlineFeature.stats,
|
||||
]),
|
||||
));
|
||||
|
||||
// padding
|
||||
tiles.add(const SizedBox(height: 32.0));
|
||||
|
||||
noteTiles = List.castFrom(tiles);
|
||||
}
|
||||
|
||||
generateTiles();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
title: Text(
|
||||
"notes".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text,
|
||||
fontSize: 26.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10.1),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
// handle tap
|
||||
SoonAlert.show(context: context);
|
||||
},
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.4),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
child: const Icon(
|
||||
FeatherIcons.search,
|
||||
size: 20.0,
|
||||
),
|
||||
),
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color:
|
||||
Theme.of(context).brightness == Brightness.light
|
||||
? Colors.black.withOpacity(.5)
|
||||
: Colors.white.withOpacity(.3),
|
||||
),
|
||||
child: const Icon(
|
||||
FeatherIcons.search,
|
||||
size: 20.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10.1),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
// handle tap
|
||||
Navigator.of(context, rootNavigator: true).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => const AddNoteScreen()));
|
||||
},
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.4),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
child: const Icon(
|
||||
FeatherIcons.plus,
|
||||
size: 20.0,
|
||||
),
|
||||
),
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color:
|
||||
Theme.of(context).brightness == Brightness.light
|
||||
? Colors.black.withOpacity(.5)
|
||||
: Colors.white.withOpacity(.3),
|
||||
),
|
||||
child: const Icon(
|
||||
FeatherIcons.plus,
|
||||
size: 20.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () {
|
||||
Provider.of<HomeworkProvider>(context, listen: false)
|
||||
.fetch(from: DateTime.now().subtract(const Duration(days: 30)));
|
||||
Provider.of<SelfNoteProvider>(context, listen: false).restore();
|
||||
|
||||
return Future(() => null);
|
||||
},
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: max(noteTiles.length, 1),
|
||||
itemBuilder: (context, index) {
|
||||
if (noteTiles.isNotEmpty) {
|
||||
EdgeInsetsGeometry panelPadding =
|
||||
const EdgeInsets.symmetric(horizontal: 24.0);
|
||||
|
||||
return Padding(padding: panelPadding, child: noteTiles[index]);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
45
refilc_mobile_ui/lib/screens/notes/notes_screen.i18n.dart
Normal file
45
refilc_mobile_ui/lib/screens/notes/notes_screen.i18n.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension SettingsLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"notes": "Notes",
|
||||
"empty": "You don't have any notes",
|
||||
"todo": "Tasks",
|
||||
"homework": "Homework",
|
||||
"new_note": "New Note",
|
||||
"edit_note": "Edit Note",
|
||||
"hint": "Note content...",
|
||||
"hint_t": "Note title...",
|
||||
"your_notes": "Your Notes",
|
||||
},
|
||||
"hu_hu": {
|
||||
"notes": "Füzet",
|
||||
"empty": "Nincsenek jegyzeteid",
|
||||
"todo": "Feladatok",
|
||||
"homework": "Házi feladat",
|
||||
"new_note": "Új jegyzet",
|
||||
"edit_note": "Jegyzet szerkesztése",
|
||||
"hint": "Jegyzet tartalma...",
|
||||
"hint_t": "Jegyzet címe...",
|
||||
"your_notes": "Jegyzeteid",
|
||||
},
|
||||
"de_de": {
|
||||
"notes": "Broschüre",
|
||||
"empty": "Sie haben keine Notizen",
|
||||
"todo": "Aufgaben",
|
||||
"homework": "Hausaufgaben",
|
||||
"new_note": "Neue Notiz",
|
||||
"edit_note": "Notiz bearbeiten",
|
||||
"hint": "Inhalt beachten...",
|
||||
"hint_t": "Titel notieren...",
|
||||
"your_notes": "Deine 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);
|
||||
}
|
||||
64
refilc_mobile_ui/lib/screens/notes/self_note_tile.dart
Normal file
64
refilc_mobile_ui/lib/screens/notes/self_note_tile.dart
Normal file
@@ -0,0 +1,64 @@
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class SelfNoteTile extends StatelessWidget {
|
||||
const SelfNoteTile(
|
||||
{super.key, required this.title, required this.content, this.onTap});
|
||||
|
||||
final String title;
|
||||
final String content;
|
||||
final Function()? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 172.0,
|
||||
width: 172.0,
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
boxShadow: [
|
||||
if (Provider.of<SettingsProvider>(context, listen: false)
|
||||
.shadowEffect)
|
||||
BoxShadow(
|
||||
offset: const Offset(0, 21),
|
||||
blurRadius: 23.0,
|
||||
color: Theme.of(context).shadowColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Text(
|
||||
content.replaceAll('\n', ' '),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.start,
|
||||
maxLines: 6,
|
||||
style: const TextStyle(fontSize: 17.0),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5.0,
|
||||
),
|
||||
SizedBox(
|
||||
width: 152.0,
|
||||
child: Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
class AccountTile extends StatelessWidget {
|
||||
const AccountTile(
|
||||
{super.key,
|
||||
this.onTap,
|
||||
this.onTapMenu,
|
||||
this.profileImage,
|
||||
this.name,
|
||||
this.username});
|
||||
|
||||
final void Function()? onTap;
|
||||
final void Function()? onTapMenu;
|
||||
final Widget? profileImage;
|
||||
final Widget? name;
|
||||
final Widget? username;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 12.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
onTap: onTap,
|
||||
onLongPress: onTapMenu,
|
||||
leading: profileImage,
|
||||
title: name,
|
||||
subtitle: username,
|
||||
trailing: onTapMenu != null
|
||||
? Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
splashRadius: 24.0,
|
||||
onPressed: onTapMenu,
|
||||
icon: Icon(FeatherIcons.moreVertical,
|
||||
color: AppColors.of(context).text.withOpacity(0.8)),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// ignore_for_file: no_leading_underscores_for_local_identifiers
|
||||
|
||||
import 'package:refilc/models/user.dart';
|
||||
import 'package:refilc_mobile_ui/common/bottom_card.dart';
|
||||
import 'package:refilc_mobile_ui/common/detail.dart';
|
||||
import 'package:refilc_mobile_ui/common/profile_image/profile_image.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/accounts/account_tile.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'account_view.i18n.dart';
|
||||
|
||||
class AccountView extends StatelessWidget {
|
||||
const AccountView(this.user, {super.key});
|
||||
|
||||
final User user;
|
||||
|
||||
static void show(User user, {required BuildContext context}) =>
|
||||
showBottomCard(context: context, child: AccountView(user));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<String> _nameParts = user.name.split(" ");
|
||||
String _firstName = _nameParts.length > 1 ? _nameParts[1] : _nameParts[0];
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AccountTile(
|
||||
profileImage: ProfileImage(
|
||||
name: _firstName,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
role: user.role,
|
||||
),
|
||||
name: SelectableText(
|
||||
user.name,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
maxLines: 2,
|
||||
minLines: 1,
|
||||
),
|
||||
username: SelectableText(user.username),
|
||||
),
|
||||
|
||||
// User details
|
||||
Detail(
|
||||
title: "birthdate".i18n,
|
||||
description:
|
||||
DateFormat("yyyy. MM. dd.").format(user.student.birth)),
|
||||
Detail(title: "school".i18n, description: user.student.school.name),
|
||||
if (user.student.className != null)
|
||||
Detail(title: "class".i18n, description: user.student.className!),
|
||||
if (user.student.address != null)
|
||||
Detail(title: "address".i18n, description: user.student.address!),
|
||||
if (user.student.parents.isNotEmpty)
|
||||
Detail(
|
||||
title: "parents".plural(user.student.parents.length),
|
||||
description: user.student.parents.join(", ")),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"birthdate": "Birth date",
|
||||
"school": "School",
|
||||
"class": "Class",
|
||||
"address": "Home address",
|
||||
"parents": "Parents".one("Parent"),
|
||||
},
|
||||
"hu_hu": {
|
||||
"birthdate": "Születési dátum",
|
||||
"school": "Iskola",
|
||||
"class": "Osztály",
|
||||
"address": "Lakcím",
|
||||
"parents": "Szülők".one("Szülő"),
|
||||
},
|
||||
"de_de": {
|
||||
"birthdate": "Geburtsdatum",
|
||||
"school": "Schule",
|
||||
"class": "Klasse",
|
||||
"address": "Wohnanschrift",
|
||||
"parents": "Eltern",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import 'package:refilc/helpers/subject.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SubjectIconGallery extends StatelessWidget {
|
||||
const SubjectIconGallery({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
title: Text(
|
||||
"Subject Icon Gallery",
|
||||
style: TextStyle(color: AppColors.of(context).text),
|
||||
),
|
||||
),
|
||||
body: ListView(
|
||||
children: const [
|
||||
SubjectIconItem("Matematika"),
|
||||
SubjectIconItem("Magyar Nyelv"),
|
||||
SubjectIconItem("Nyelvtan"),
|
||||
SubjectIconItem("Irodalom"),
|
||||
SubjectIconItem("Történelem"),
|
||||
SubjectIconItem("Földrajz"),
|
||||
SubjectIconItem("Rajz"),
|
||||
SubjectIconItem("Vizuális kultúra"),
|
||||
SubjectIconItem("Fizika"),
|
||||
SubjectIconItem("Ének"),
|
||||
SubjectIconItem("Testnevelés"),
|
||||
SubjectIconItem("Kémia"),
|
||||
SubjectIconItem("Biológia"),
|
||||
SubjectIconItem("Természetismeret"),
|
||||
SubjectIconItem("Erkölcstan"),
|
||||
SubjectIconItem("Pénzügy"),
|
||||
SubjectIconItem("Informatika"),
|
||||
SubjectIconItem("Digitális kultúra"),
|
||||
SubjectIconItem("Programozás"),
|
||||
SubjectIconItem("Hálózat"),
|
||||
SubjectIconItem("Színház technika"),
|
||||
SubjectIconItem("Média"),
|
||||
SubjectIconItem("Elektronika"),
|
||||
SubjectIconItem("Gépészet"),
|
||||
SubjectIconItem("Technika"),
|
||||
SubjectIconItem("Tánc"),
|
||||
SubjectIconItem("Filozófia"),
|
||||
SubjectIconItem("Osztályfőnöki"),
|
||||
SubjectIconItem("Gazdaság"),
|
||||
SubjectIconItem("Szorgalom"),
|
||||
SubjectIconItem("Magatartás"),
|
||||
SubjectIconItem("Angol nyelv"),
|
||||
SubjectIconItem("Linux"),
|
||||
SubjectIconItem("Adatbázis"),
|
||||
SubjectIconItem("Asztali alkalmazások"),
|
||||
SubjectIconItem("Projekt"),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SubjectIconItem extends StatelessWidget {
|
||||
const SubjectIconItem(this.name, {super.key});
|
||||
|
||||
final String name;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
SubjectIcon.resolveVariant(subjectName: name, context: context),
|
||||
color: AppColors.of(context).text,
|
||||
),
|
||||
title: Text(
|
||||
name,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
94
refilc_mobile_ui/lib/screens/settings/desktop_settings.dart
Normal file
94
refilc_mobile_ui/lib/screens/settings/desktop_settings.dart
Normal file
@@ -0,0 +1,94 @@
|
||||
import 'package:refilc/api/providers/database_provider.dart';
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
// import 'package:refilc/utils/format.dart';
|
||||
// import 'package:refilc_kreta_api/models/teacher.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
|
||||
// import 'package:refilc_plus/models/premium_scopes.dart';
|
||||
// import 'package:refilc_plus/providers/premium_provider.dart';
|
||||
// import 'package:refilc_plus/ui/mobile/premium/upsell.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
|
||||
import 'package:refilc_mobile_ui/common/beta_chip.dart';
|
||||
|
||||
class MenuDesktopSettings extends StatelessWidget {
|
||||
const MenuDesktopSettings({super.key, required this.settings});
|
||||
|
||||
final SettingsProvider settings;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0),
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => const ModifyDesktopSettings()),
|
||||
);
|
||||
},
|
||||
title: Row(
|
||||
children: [
|
||||
Text(
|
||||
"desktop_settings".i18n,
|
||||
style: TextStyle(color: AppColors.of(context).text),
|
||||
),
|
||||
const Spacer(),
|
||||
const BetaChip(),
|
||||
const Spacer()
|
||||
],
|
||||
),
|
||||
leading: Icon(FeatherIcons.monitor,
|
||||
color: Theme.of(context).colorScheme.secondary),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ModifyDesktopSettings extends StatefulWidget {
|
||||
const ModifyDesktopSettings({super.key});
|
||||
|
||||
@override
|
||||
State<ModifyDesktopSettings> createState() => _ModifyDesktopSettingsState();
|
||||
}
|
||||
|
||||
class _ModifyDesktopSettingsState extends State<ModifyDesktopSettings> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
late UserProvider user;
|
||||
late DatabaseProvider dbProvider;
|
||||
late SettingsProvider settings;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
user = Provider.of<UserProvider>(context, listen: false);
|
||||
dbProvider = Provider.of<DatabaseProvider>(context, listen: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
settings = Provider.of<SettingsProvider>(context);
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
title: Text(
|
||||
"desktop_settings".i18n,
|
||||
style: TextStyle(color: AppColors.of(context).text),
|
||||
),
|
||||
),
|
||||
body: const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
72
refilc_mobile_ui/lib/screens/settings/modify_names.i18n.dart
Normal file
72
refilc_mobile_ui/lib/screens/settings/modify_names.i18n.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension SettingsLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
// subject rename
|
||||
"renamed_subjects": "Renamed Subjects",
|
||||
"rename_subjects": "Rename Subjects",
|
||||
"rename_subject": "Rename Subject",
|
||||
"select_subject": "Select Subject",
|
||||
"modified_name": "Modified Name",
|
||||
"modify_subjects": "Modify Subjects",
|
||||
"cancel": "Cancel",
|
||||
"done": "Done",
|
||||
"rename_new_subject": "Rename New Subject",
|
||||
"italics_toggle": "Italic Font",
|
||||
// teacher rename
|
||||
"renamed_teachers": "Renamed Teachers",
|
||||
"rename_teachers": "Rename Teachers",
|
||||
"rename_teacher": "Rename Teacher",
|
||||
"select_teacher": "Select Teacher",
|
||||
"modify_teachers": "Modify Teachers",
|
||||
"rename_new_teacher": "Rename New Teacher",
|
||||
},
|
||||
"hu_hu": {
|
||||
// subject rename
|
||||
"renamed_subjects": "Átnevezett Tantárgyaid",
|
||||
"rename_subjects": "Tantárgyak átnevezése",
|
||||
"rename_subject": "Tantárgy átnevezése",
|
||||
"select_subject": "Válassz tantárgyat",
|
||||
"modified_name": "Módosított név",
|
||||
"modify_subjects": "Tantárgyak módosítása",
|
||||
"cancel": "Mégse",
|
||||
"done": "Kész",
|
||||
"rename_new_subject": "Új tantárgy átnevezése",
|
||||
"italics_toggle": "Dőlt betűs megjelenítés",
|
||||
// teacher rename
|
||||
"renamed_teachers": "Átnevezett tanáraid",
|
||||
"rename_teachers": "Tanárok átnevezése",
|
||||
"rename_teacher": "Tanár átnevezése",
|
||||
"select_teacher": "Válassz tanárt",
|
||||
"modify_teachers": "Tanárok módosítása",
|
||||
"rename_new_teacher": "Új tanár átnevezése",
|
||||
},
|
||||
"de_de": {
|
||||
// subject rename
|
||||
"renamed_subjects": "Umbenannte Fächer",
|
||||
"rename_subjects": "Fächer umbenennen",
|
||||
"rename_subject": "Fach umbenennen",
|
||||
"select_subject": "Fach auswählen",
|
||||
"modified_name": "Geänderter Name",
|
||||
"modify_subjects": "Fächer ändern",
|
||||
"cancel": "Abbrechen",
|
||||
"done": "Erledigt",
|
||||
"rename_new_subject": "Neues Fach umbenennen",
|
||||
"italics_toggle": "Kursivschrift umschalten",
|
||||
// teacher rename
|
||||
"renamed_teachers": "Renamed Teachers",
|
||||
"rename_teachers": "Rename Teachers",
|
||||
"rename_teacher": "Rename Teacher",
|
||||
"select_teacher": "Select Teacher",
|
||||
"modify_teachers": "Modify Teachers",
|
||||
"rename_new_teacher": "Rename New Teacher",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
439
refilc_mobile_ui/lib/screens/settings/modify_subject_names.dart
Normal file
439
refilc_mobile_ui/lib/screens/settings/modify_subject_names.dart
Normal file
@@ -0,0 +1,439 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:refilc/api/providers/database_provider.dart';
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/helpers/subject.dart';
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc/utils/format.dart';
|
||||
import 'package:refilc_kreta_api/models/subject.dart';
|
||||
import 'package:refilc_kreta_api/providers/absence_provider.dart';
|
||||
import 'package:refilc_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:refilc_kreta_api/providers/timetable_provider.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
|
||||
// import 'package:refilc_plus/models/premium_scopes.dart';
|
||||
// import 'package:refilc_plus/providers/premium_provider.dart';
|
||||
// import 'package:refilc_plus/ui/mobile/premium/upsell.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'modify_names.i18n.dart';
|
||||
|
||||
class MenuRenamedSubjects extends StatelessWidget {
|
||||
const MenuRenamedSubjects({super.key, required this.settings});
|
||||
|
||||
final SettingsProvider settings;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0),
|
||||
onPressed: () {
|
||||
// if (!Provider.of<PremiumProvider>(context, listen: false)
|
||||
// .hasScope(PremiumScopes.renameSubjects)) {
|
||||
// PremiumLockedFeatureUpsell.show(
|
||||
// context: context, feature: PremiumFeature.subjectrename);
|
||||
// return;
|
||||
// }
|
||||
|
||||
Navigator.of(context, rootNavigator: true).push(
|
||||
CupertinoPageRoute(builder: (context) => const ModifySubjectNames()),
|
||||
);
|
||||
},
|
||||
title: Text(
|
||||
"rename_subjects".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context)
|
||||
.text
|
||||
.withOpacity(settings.renamedSubjectsEnabled ? 1.0 : .5)),
|
||||
),
|
||||
leading: settings.renamedSubjectsEnabled
|
||||
? const Icon(FeatherIcons.penTool)
|
||||
: Icon(FeatherIcons.penTool,
|
||||
color: AppColors.of(context).text.withOpacity(.25)),
|
||||
trailingDivider: true,
|
||||
trailing: Switch(
|
||||
onChanged: (v) async {
|
||||
// if (!Provider.of<PremiumProvider>(context, listen: false)
|
||||
// .hasScope(PremiumScopes.renameSubjects)) {
|
||||
// PremiumLockedFeatureUpsell.show(
|
||||
// context: context, feature: PremiumFeature.subjectrename);
|
||||
// return;
|
||||
// }
|
||||
|
||||
settings.update(renamedSubjectsEnabled: v);
|
||||
await Provider.of<GradeProvider>(context, listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<TimetableProvider>(context, listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<AbsenceProvider>(context, listen: false)
|
||||
.convertBySettings();
|
||||
},
|
||||
value: settings.renamedSubjectsEnabled,
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ModifySubjectNames extends StatefulWidget {
|
||||
const ModifySubjectNames({super.key});
|
||||
|
||||
@override
|
||||
State<ModifySubjectNames> createState() => _ModifySubjectNamesState();
|
||||
}
|
||||
|
||||
class _ModifySubjectNamesState extends State<ModifySubjectNames> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
final _subjectName = TextEditingController();
|
||||
String? selectedSubjectId;
|
||||
|
||||
late List<GradeSubject> subjects;
|
||||
late UserProvider user;
|
||||
late DatabaseProvider dbProvider;
|
||||
late SettingsProvider settings;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
subjects = Provider.of<GradeProvider>(context, listen: false)
|
||||
.grades
|
||||
.map((e) => e.subject)
|
||||
.toSet()
|
||||
.toList()
|
||||
..sort((a, b) => a.name.compareTo(b.name));
|
||||
user = Provider.of<UserProvider>(context, listen: false);
|
||||
dbProvider = Provider.of<DatabaseProvider>(context, listen: false);
|
||||
}
|
||||
|
||||
Future<Map<String, String>> fetchRenamedSubjects() async {
|
||||
return await dbProvider.userQuery.renamedSubjects(userId: user.id!);
|
||||
}
|
||||
|
||||
void showRenameDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(builder: (context, setS) {
|
||||
return AlertDialog(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(14.0))),
|
||||
title: Text("rename_subject".i18n),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
DropdownButton2(
|
||||
items: subjects
|
||||
.map((item) => DropdownMenuItem<String>(
|
||||
value: item.id,
|
||||
child: Text(
|
||||
item.name,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.of(context).text,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (String? v) async {
|
||||
final renamedSubs = await fetchRenamedSubjects();
|
||||
|
||||
setS(() {
|
||||
selectedSubjectId = v;
|
||||
|
||||
if (renamedSubs.containsKey(selectedSubjectId)) {
|
||||
_subjectName.text = renamedSubs[selectedSubjectId]!;
|
||||
} else {
|
||||
_subjectName.text = "";
|
||||
}
|
||||
});
|
||||
},
|
||||
iconSize: 14,
|
||||
iconEnabledColor: AppColors.of(context).text,
|
||||
iconDisabledColor: AppColors.of(context).text,
|
||||
underline: const SizedBox(),
|
||||
itemHeight: 40,
|
||||
itemPadding: const EdgeInsets.only(left: 14, right: 14),
|
||||
buttonWidth: 50,
|
||||
dropdownWidth: 300,
|
||||
dropdownPadding: null,
|
||||
buttonDecoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
dropdownDecoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
dropdownElevation: 8,
|
||||
scrollbarRadius: const Radius.circular(40),
|
||||
scrollbarThickness: 6,
|
||||
scrollbarAlwaysShow: true,
|
||||
offset: const Offset(-10, -10),
|
||||
buttonSplashColor: Colors.transparent,
|
||||
customButton: Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey, width: 2),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0, horizontal: 8.0),
|
||||
child: Text(
|
||||
selectedSubjectId == null
|
||||
? "select_subject".i18n
|
||||
: subjects
|
||||
.firstWhere(
|
||||
(element) => element.id == selectedSubjectId)
|
||||
.name,
|
||||
style: Theme.of(context).textTheme.titleSmall!.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.of(context).text.withOpacity(0.75)),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Icon(FeatherIcons.arrowDown, size: 32),
|
||||
),
|
||||
TextField(
|
||||
controller: _subjectName,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(
|
||||
borderSide:
|
||||
const BorderSide(color: Colors.grey, width: 1.5),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide:
|
||||
const BorderSide(color: Colors.grey, width: 1.5),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
hintText: "modified_name".i18n,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(
|
||||
FeatherIcons.x,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_subjectName.text = "";
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
"cancel".i18n,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
"done".i18n,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onPressed: () async {
|
||||
if (selectedSubjectId != null) {
|
||||
final renamedSubs = await fetchRenamedSubjects();
|
||||
|
||||
renamedSubs[selectedSubjectId!] = _subjectName.text;
|
||||
await dbProvider.userStore
|
||||
.storeRenamedSubjects(renamedSubs, userId: user.id!);
|
||||
await Provider.of<GradeProvider>(context, listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<TimetableProvider>(context, listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<AbsenceProvider>(context, listen: false)
|
||||
.convertBySettings();
|
||||
}
|
||||
Navigator.of(context).pop(true);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
).then((val) {
|
||||
_subjectName.text = "";
|
||||
selectedSubjectId = null;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
settings = Provider.of<SettingsProvider>(context);
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
title: Text(
|
||||
"modify_subjects".i18n,
|
||||
style: TextStyle(color: AppColors.of(context).text),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Panel(
|
||||
child: SwitchListTile(
|
||||
title: Text("italics_toggle".i18n),
|
||||
onChanged: (value) =>
|
||||
settings.update(renamedSubjectsItalics: value),
|
||||
value: settings.renamedSubjectsItalics,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
InkWell(
|
||||
onTap: showRenameDialog,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey, width: 2),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 18.0, horizontal: 12.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"rename_new_subject".i18n,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18,
|
||||
color: AppColors.of(context).text.withOpacity(.85),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
FutureBuilder<Map<String, String>>(
|
||||
future: fetchRenamedSubjects(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
return Panel(
|
||||
title: Text("renamed_subjects".i18n),
|
||||
child: Column(
|
||||
children: snapshot.data!.keys.map(
|
||||
(key) {
|
||||
GradeSubject? subject = subjects
|
||||
.firstWhere((element) => key == element.id);
|
||||
String renameTo = snapshot.data![key]!;
|
||||
return RenamedSubjectItem(
|
||||
subject: subject,
|
||||
renamedTo: renameTo,
|
||||
modifyCallback: () {
|
||||
setState(() {
|
||||
selectedSubjectId = subject.id;
|
||||
_subjectName.text = renameTo;
|
||||
});
|
||||
showRenameDialog();
|
||||
},
|
||||
removeCallback: () {
|
||||
setState(() {
|
||||
Map<String, String> subs =
|
||||
Map.from(snapshot.data!);
|
||||
subs.remove(key);
|
||||
dbProvider.userStore.storeRenamedSubjects(
|
||||
subs,
|
||||
userId: user.id!);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class RenamedSubjectItem extends StatelessWidget {
|
||||
const RenamedSubjectItem({
|
||||
super.key,
|
||||
required this.subject,
|
||||
required this.renamedTo,
|
||||
required this.modifyCallback,
|
||||
required this.removeCallback,
|
||||
});
|
||||
|
||||
final GradeSubject subject;
|
||||
final String renamedTo;
|
||||
final void Function() modifyCallback;
|
||||
final void Function() removeCallback;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
minLeadingWidth: 32.0,
|
||||
dense: true,
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 6.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
|
||||
visualDensity: VisualDensity.compact,
|
||||
onTap: () {},
|
||||
leading: Icon(
|
||||
SubjectIcon.resolveVariant(subject: subject, context: context),
|
||||
color: AppColors.of(context).text.withOpacity(.75)),
|
||||
title: InkWell(
|
||||
onTap: modifyCallback,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
subject.name.capital(),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
color: AppColors.of(context).text.withOpacity(.75)),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
renamedTo,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 16),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
trailing: InkWell(
|
||||
onTap: removeCallback,
|
||||
child: Icon(FeatherIcons.trash,
|
||||
color: AppColors.of(context).red.withOpacity(.75)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
222
refilc_mobile_ui/lib/screens/settings/notifications_screen.dart
Normal file
222
refilc_mobile_ui/lib/screens/settings/notifications_screen.dart
Normal file
@@ -0,0 +1,222 @@
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc_mobile_ui/common/beta_chip.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'notifications_screen.i18n.dart';
|
||||
|
||||
class MenuNotifications extends StatelessWidget {
|
||||
const MenuNotifications({
|
||||
super.key,
|
||||
this.borderRadius = const BorderRadius.vertical(
|
||||
top: Radius.circular(4.0), bottom: Radius.circular(4.0)),
|
||||
});
|
||||
|
||||
final BorderRadius borderRadius;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PanelButton(
|
||||
onPressed: () => Navigator.of(context, rootNavigator: true).push(
|
||||
CupertinoPageRoute(builder: (context) => const NotificationsScreen()),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Text(
|
||||
"notifications_screen".i18n,
|
||||
),
|
||||
const SizedBox(width: 5.0),
|
||||
const BetaChip(
|
||||
disabled: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.messageCircle,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
trailing: Icon(
|
||||
FeatherIcons.chevronRight,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
borderRadius: borderRadius,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationsScreen extends StatelessWidget {
|
||||
const NotificationsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SettingsProvider settings = Provider.of<SettingsProvider>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
title: Text(
|
||||
"notifications_screen".i18n,
|
||||
style: TextStyle(color: AppColors.of(context).text),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||
child: Panel(
|
||||
child: Column(
|
||||
children: [
|
||||
Material(
|
||||
type: MaterialType.transparency,
|
||||
child: SwitchListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0)),
|
||||
value: settings.notificationsGradesEnabled,
|
||||
onChanged: (v) =>
|
||||
settings.update(notificationsGradesEnabled: v),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(
|
||||
FeatherIcons.bookmark,
|
||||
color: settings.notificationsGradesEnabled
|
||||
? Theme.of(context).colorScheme.secondary
|
||||
: AppColors.of(context).text.withOpacity(.25),
|
||||
),
|
||||
const SizedBox(width: 14.0),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"grades".i18n,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16.0,
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settings.notificationsGradesEnabled
|
||||
? 1.0
|
||||
: .5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Material(
|
||||
type: MaterialType.transparency,
|
||||
child: SwitchListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0)),
|
||||
value: settings.notificationsAbsencesEnabled,
|
||||
onChanged: (v) =>
|
||||
settings.update(notificationsAbsencesEnabled: v),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(
|
||||
FeatherIcons.clock,
|
||||
color: settings.notificationsAbsencesEnabled
|
||||
? Theme.of(context).colorScheme.secondary
|
||||
: AppColors.of(context).text.withOpacity(.25),
|
||||
),
|
||||
const SizedBox(width: 14.0),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"absences".i18n,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16.0,
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settings.notificationsAbsencesEnabled
|
||||
? 1.0
|
||||
: .5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Material(
|
||||
type: MaterialType.transparency,
|
||||
child: SwitchListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0)),
|
||||
value: settings.notificationsMessagesEnabled,
|
||||
onChanged: (v) =>
|
||||
settings.update(notificationsMessagesEnabled: v),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(
|
||||
FeatherIcons.messageSquare,
|
||||
color: settings.notificationsMessagesEnabled
|
||||
? Theme.of(context).colorScheme.secondary
|
||||
: AppColors.of(context).text.withOpacity(.25),
|
||||
),
|
||||
const SizedBox(width: 14.0),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"messages".i18n,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16.0,
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settings.notificationsMessagesEnabled
|
||||
? 1.0
|
||||
: .5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Material(
|
||||
type: MaterialType.transparency,
|
||||
child: SwitchListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0)),
|
||||
value: settings.notificationsLessonsEnabled,
|
||||
onChanged: (v) =>
|
||||
settings.update(notificationsLessonsEnabled: v),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(
|
||||
FeatherIcons.calendar,
|
||||
color: settings.notificationsLessonsEnabled
|
||||
? Theme.of(context).colorScheme.secondary
|
||||
: AppColors.of(context).text.withOpacity(.25),
|
||||
),
|
||||
const SizedBox(width: 14.0),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"lessons".i18n,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16.0,
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settings.notificationsLessonsEnabled
|
||||
? 1.0
|
||||
: .5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension SettingsLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"notifications_screen": "Notifications",
|
||||
"grades": "Grades",
|
||||
"absences": "Absences",
|
||||
"messages": "Messages",
|
||||
"lessons": "Lessons"
|
||||
|
||||
},
|
||||
"hu_hu": {
|
||||
"notifications_screen": "Értesítések",
|
||||
"grades": "Jegyek",
|
||||
"absences": "Hiányzások",
|
||||
"messages": "Üzenetek",
|
||||
"lessons": "Órák"
|
||||
},
|
||||
"de_de": {
|
||||
"notifications_screen": "Mitteilung",
|
||||
"grades": "Noten",
|
||||
"absences": "Fehlen",
|
||||
"messages": "Nachrichten",
|
||||
"lessons": "Unterricht"
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
59
refilc_mobile_ui/lib/screens/settings/privacy_view.dart
Normal file
59
refilc_mobile_ui/lib/screens/settings/privacy_view.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'settings_screen.i18n.dart';
|
||||
|
||||
class PrivacyView extends StatelessWidget {
|
||||
const PrivacyView({super.key});
|
||||
|
||||
static void show(BuildContext context) => showDialog(
|
||||
context: context,
|
||||
builder: (context) => const PrivacyView(),
|
||||
barrierDismissible: true);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 100.0, horizontal: 32.0),
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: ListView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text("privacy".i18n),
|
||||
),
|
||||
SelectableLinkify(
|
||||
text: """
|
||||
• A reFilc (továbbiakban alkalmazás) egy mobilos, asztali és webes kliensalkalmazás, segítségével az e-Kréta rendszeréből letöltheted és felhasználóbarát módon megjelenítheted az adataidat. Tanulmányi adataid csak közvetlenül az alkalmazás és a Kréta-szerverek között közlekednek, titkosított kapcsolaton keresztül.
|
||||
|
||||
• A reFilc fejlesztői és/vagy üzemeltetői, valamint az alkalmazás a tanulmányi és személyes adataidat semmilyen célból és semmilyen körülmények között nem másolják, nem tárolják és harmadik félnek nem továbbítják. Ezeket így az Educational Development Informatikai Zrt. kezeli, az Ő adatkezeléssel kapcsolatos tájékoztatójukat itt találod: https://tudasbazis.ekreta.hu/pages/viewpage.action?pageId=4065038
|
||||
|
||||
• Azok törlésével vagy módosítával kapcsolatban keresd az osztályfőnöködet vagy az iskolád rendszergazdáját.
|
||||
|
||||
• Az alkalmazás névtelen használati statisztikákat gyűjt, ezek alapján tudjuk meghatározni a felhasználók és a telepítések számát, valamint az eszközük platformját. Ezt a beállításokban kikapcsolhatod. Kérünk, hogy ha csak teheted, hagyd ezt a funkciót bekapcsolva, hogy pontosabb információnk legyen a felhasználóink platform-megoszlásáról.
|
||||
|
||||
• Amikor az alkalmazás hibába ütközik, lehetőség van hibajelentés küldésére. Ez személyes- és/vagy tanulmányi adatokat nem tartalmaz, viszont részletes információval szolgál a hibáról, annak okáról és eszközödről. A küldés előtt megjelenő képernyőn a te felelősséged átnézni a továbbításra kerülő adatsort. A hibajelentéseket a reFilc fejlesztői felületén és egy privát Discord szobában tároljuk, ezekhez csak az app fejlesztői férnek hozzá.
|
||||
|
||||
• Az alkalmazás (az alábbi platformokon: Android, Linux, Windows) minden egyes indításakor a reFilc API, valamint a Github API segítségével ellenőrzi, hogy elérhető-e új verzió, és kérésre innen letölti és telepíti a frissítést.
|
||||
|
||||
• Amennyiben az adataiddal kapcsolatban bármilyen kérdésed van (megtekintés, törlés, módosítás, adathordozás), keress minket a social@refilc.hu e-mail címen, vagy Discord szerverünkön!
|
||||
|
||||
• A kliensalkalmazás bármely eszközön és platformon történő használatával tudomásul vetted és elfogadod a jelen adatkezelési tájékoztatót. A reFilc csapata fenntartja a jogot a tájékoztató módosítására és a módosításokról nem köteles értesíteni a felhasználóit!
|
||||
""",
|
||||
onOpen: (link) => launch(link.url,
|
||||
customTabsOption: CustomTabsOption(
|
||||
toolbarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
showPageTitle: true,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
983
refilc_mobile_ui/lib/screens/settings/settings_helper.dart
Normal file
983
refilc_mobile_ui/lib/screens/settings/settings_helper.dart
Normal file
@@ -0,0 +1,983 @@
|
||||
// ignore_for_file: prefer_function_declarations_over_variables, library_private_types_in_public_api, use_build_context_synchronously
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:refilc/api/providers/database_provider.dart';
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/helpers/quick_actions.dart';
|
||||
import 'package:refilc/icons/filc_icons.dart';
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc/theme/observer.dart';
|
||||
import 'package:refilc_kreta_api/models/grade.dart';
|
||||
import 'package:refilc_kreta_api/models/subject.dart';
|
||||
import 'package:refilc_kreta_api/models/week.dart';
|
||||
import 'package:refilc_kreta_api/providers/absence_provider.dart';
|
||||
import 'package:refilc_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:refilc_kreta_api/providers/timetable_provider.dart';
|
||||
import 'package:refilc_mobile_ui/common/bottom_sheet_menu/bottom_sheet_menu.dart';
|
||||
import 'package:refilc_mobile_ui/common/bottom_sheet_menu/bottom_sheet_menu_item.dart';
|
||||
import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart';
|
||||
import 'package:refilc_mobile_ui/common/filter_bar.dart';
|
||||
import 'package:refilc_mobile_ui/common/material_action_button.dart';
|
||||
import 'package:refilc/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:refilc_mobile_ui/common/system_chrome.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:i18n_extension/i18n_widget.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:refilc_mobile_ui/common/screens.i18n.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
|
||||
import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
|
||||
import 'package:refilc/models/icon_pack.dart';
|
||||
import 'package:refilc/utils/format.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/theme_screen.dart';
|
||||
import 'package:refilc_plus/models/premium_scopes.dart';
|
||||
import 'package:refilc_plus/providers/premium_provider.dart';
|
||||
import 'package:refilc_plus/ui/mobile/premium/upsell.dart';
|
||||
|
||||
class SettingsHelper {
|
||||
static const Map<String, String> langMap = {
|
||||
"en": "🇬🇧 English",
|
||||
"hu": "🇭🇺 Magyar",
|
||||
"de": "🇩🇪 Deutsch"
|
||||
};
|
||||
|
||||
static const List<String> fontList = [
|
||||
"Montserrat",
|
||||
"Merienda",
|
||||
"M PLUS Code Latin",
|
||||
"Figtree",
|
||||
"Fira Code",
|
||||
"Vollkorn",
|
||||
];
|
||||
|
||||
static const Map<Pages, String> pageTitle = {
|
||||
Pages.home: "home",
|
||||
Pages.grades: "grades",
|
||||
Pages.timetable: "timetable",
|
||||
Pages.messages: "messages",
|
||||
Pages.absences: "absences",
|
||||
};
|
||||
|
||||
static Map<VibrationStrength, String> vibrationTitle = {
|
||||
VibrationStrength.off: "voff",
|
||||
VibrationStrength.light: "vlight",
|
||||
VibrationStrength.medium: "vmedium",
|
||||
VibrationStrength.strong: "vstrong",
|
||||
};
|
||||
|
||||
static Map<Pages, String> localizedPageTitles() => pageTitle
|
||||
.map((key, value) => MapEntry(key, ScreensLocalization(value).i18n));
|
||||
static Map<VibrationStrength, String> localizedVibrationTitles() =>
|
||||
vibrationTitle
|
||||
.map((key, value) => MapEntry(key, SettingsLocalization(value).i18n));
|
||||
|
||||
static void language(BuildContext context) {
|
||||
showBottomSheetMenu(
|
||||
context,
|
||||
items: List.generate(langMap.length, (index) {
|
||||
String lang = langMap.keys.toList()[index];
|
||||
return BottomSheetMenuItem(
|
||||
onPressed: () {
|
||||
Provider.of<SettingsProvider>(context, listen: false)
|
||||
.update(language: lang);
|
||||
I18n.of(context).locale = Locale(lang, lang.toUpperCase());
|
||||
Navigator.of(context).maybePop();
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
setupQuickActions();
|
||||
}
|
||||
},
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(langMap.values.toList()[index]),
|
||||
if (lang == I18n.of(context).locale.languageCode)
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// static void uwuMode(BuildContext context, value) {
|
||||
// final settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
// if (value) {
|
||||
// I18n.of(context).locale = const Locale('uw', 'UW');
|
||||
// } else {
|
||||
// I18n.of(context).locale =
|
||||
// Locale(settings.language, settings.language.toUpperCase());
|
||||
// }
|
||||
// if (Platform.isAndroid || Platform.isIOS) {
|
||||
// setupQuickActions();
|
||||
// }
|
||||
// }
|
||||
|
||||
static void fontFamily(BuildContext context) {
|
||||
SettingsProvider settings =
|
||||
Provider.of<SettingsProvider>(context, listen: false);
|
||||
|
||||
showBottomSheetMenu(
|
||||
context,
|
||||
items: List.generate(fontList.length, (index) {
|
||||
String font = fontList[index];
|
||||
return BottomSheetMenuItem(
|
||||
onPressed: () {
|
||||
settings.update(fontFamily: font == 'Montserrat' ? '' : font);
|
||||
Provider.of<ThemeModeObserver>(context, listen: false)
|
||||
.changeTheme(settings.theme, updateNavbarColor: false);
|
||||
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
font,
|
||||
style: GoogleFonts.getFont(font),
|
||||
),
|
||||
if (font == settings.fontFamily ||
|
||||
font.replaceAll('Montserrat', '') == settings.fontFamily)
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
static void iconPack(BuildContext context) {
|
||||
final settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
showBottomSheetMenu(
|
||||
context,
|
||||
items: List.generate(IconPack.values.length, (index) {
|
||||
IconPack current = IconPack.values[index];
|
||||
return BottomSheetMenuItem(
|
||||
onPressed: () {
|
||||
settings.update(iconPack: current);
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(current.name.capital()),
|
||||
if (current == settings.iconPack)
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
static void startPage(BuildContext context) {
|
||||
Map<Pages, IconData> pageIcons = {
|
||||
Pages.home: FilcIcons.home,
|
||||
Pages.grades: FeatherIcons.bookmark,
|
||||
Pages.timetable: FeatherIcons.calendar,
|
||||
Pages.messages: FeatherIcons.messageSquare,
|
||||
Pages.absences: FeatherIcons.clock,
|
||||
};
|
||||
|
||||
showBottomSheetMenu(
|
||||
context,
|
||||
items: List.generate(Pages.values.length, (index) {
|
||||
return BottomSheetMenuItem(
|
||||
onPressed: () {
|
||||
Provider.of<SettingsProvider>(context, listen: false)
|
||||
.update(startPage: Pages.values[index]);
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(pageIcons[Pages.values[index]],
|
||||
size: 20.0, color: Theme.of(context).colorScheme.secondary),
|
||||
const SizedBox(width: 16.0),
|
||||
Text(localizedPageTitles()[Pages.values[index]] ?? ""),
|
||||
const Spacer(),
|
||||
if (Pages.values[index] ==
|
||||
Provider.of<SettingsProvider>(context, listen: false)
|
||||
.startPage)
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
static void rounding(BuildContext context) {
|
||||
showRoundedModalBottomSheet(
|
||||
context,
|
||||
child: const RoundingSetting(),
|
||||
);
|
||||
}
|
||||
|
||||
// new v5 roundings
|
||||
static void newRoundings(BuildContext context, GradeSubject subject) {
|
||||
showRoundedModalBottomSheet(
|
||||
context,
|
||||
child: RoundingSetting(
|
||||
rounding: subject.customRounding,
|
||||
subjectId: subject.id,
|
||||
),
|
||||
);
|
||||
}
|
||||
// end
|
||||
|
||||
static void theme(BuildContext context) {
|
||||
var settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
void Function(ThemeMode) setTheme = (mode) {
|
||||
settings.update(theme: mode);
|
||||
Provider.of<ThemeModeObserver>(context, listen: false).changeTheme(mode);
|
||||
Navigator.of(context).maybePop();
|
||||
};
|
||||
|
||||
showBottomSheetMenu(context, items: [
|
||||
BottomSheetMenuItem(
|
||||
onPressed: () => setTheme(ThemeMode.system),
|
||||
title: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: Icon(FeatherIcons.smartphone,
|
||||
size: 20.0, color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
Text(SettingsLocalization("system").i18n),
|
||||
const Spacer(),
|
||||
if (settings.theme == ThemeMode.system)
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
BottomSheetMenuItem(
|
||||
onPressed: () => setTheme(ThemeMode.light),
|
||||
title: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: Icon(FeatherIcons.sun,
|
||||
size: 20.0, color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
Text(SettingsLocalization("light").i18n),
|
||||
const Spacer(),
|
||||
if (settings.theme == ThemeMode.light)
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
BottomSheetMenuItem(
|
||||
onPressed: () => setTheme(ThemeMode.dark),
|
||||
title: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: Icon(FeatherIcons.moon,
|
||||
size: 20.0, color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
Text(SettingsLocalization("dark").i18n),
|
||||
const Spacer(),
|
||||
if (settings.theme == ThemeMode.dark)
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
static void accentColor(BuildContext context) {
|
||||
Navigator.of(context, rootNavigator: true).push(
|
||||
PageRouteBuilder(
|
||||
pageBuilder: (context, _, __) =>
|
||||
const PremiumCustomAccentColorSetting(),
|
||||
transitionDuration: Duration.zero,
|
||||
reverseTransitionDuration: Duration.zero,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static void gradeColors(BuildContext context) {
|
||||
showRoundedModalBottomSheet(
|
||||
context,
|
||||
child: const GradeColorsSetting(),
|
||||
);
|
||||
}
|
||||
|
||||
static void liveActivityColor(BuildContext context) {
|
||||
showRoundedModalBottomSheet(
|
||||
context,
|
||||
child: const LiveActivityColorSetting(),
|
||||
);
|
||||
}
|
||||
|
||||
static void vibrate(BuildContext context) {
|
||||
showBottomSheetMenu(
|
||||
context,
|
||||
items: List.generate(VibrationStrength.values.length, (index) {
|
||||
VibrationStrength value = VibrationStrength.values[index];
|
||||
|
||||
return BottomSheetMenuItem(
|
||||
onPressed: () {
|
||||
Provider.of<SettingsProvider>(context, listen: false)
|
||||
.update(vibrate: value);
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
title: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 12.0,
|
||||
height: 12.0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary
|
||||
.withOpacity((index + 1) / (vibrationTitle.length + 1)),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16.0),
|
||||
Text(localizedVibrationTitles()[value] ?? "?"),
|
||||
const Spacer(),
|
||||
if (value ==
|
||||
Provider.of<SettingsProvider>(context, listen: false).vibrate)
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
static void bellDelay(BuildContext context) {
|
||||
showRoundedModalBottomSheet(
|
||||
context,
|
||||
child: const BellDelaySetting(),
|
||||
);
|
||||
}
|
||||
|
||||
// v5 user changer
|
||||
static void changeCurrentUser(BuildContext context, List<Widget> accountTiles,
|
||||
int len, String addUsrLocTxt) {
|
||||
showBottomSheetMenu(
|
||||
context,
|
||||
items: List.generate(len, (index) {
|
||||
if (index == accountTiles.length) {
|
||||
return const SizedBox(
|
||||
height: 10.0,
|
||||
);
|
||||
// return Center(
|
||||
// child: Container(
|
||||
// margin: const EdgeInsets.only(top: 12.0, bottom: 4.0),
|
||||
// height: 3.0,
|
||||
// width: 175.0,
|
||||
// decoration: BoxDecoration(
|
||||
// borderRadius: BorderRadius.circular(12.0),
|
||||
// color: AppColors.of(context).text.withOpacity(.25),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
} else if (index == accountTiles.length + 1) {
|
||||
return PanelButton(
|
||||
onPressed: () {
|
||||
if (!Provider.of<PremiumProvider>(context, listen: false)
|
||||
.hasScope(PremiumScopes.maxTwoAccounts)) {
|
||||
PremiumLockedFeatureUpsell.show(
|
||||
context: context, feature: PremiumFeature.moreAccounts);
|
||||
return;
|
||||
}
|
||||
|
||||
Navigator.of(context).pushNamed("login_back").then((value) {
|
||||
setSystemChrome(context);
|
||||
});
|
||||
},
|
||||
title: Text(addUsrLocTxt),
|
||||
leading: const Padding(
|
||||
padding: EdgeInsets.only(left: 8.22, right: 6.9),
|
||||
child: Icon(FeatherIcons.userPlus),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return accountTiles[index];
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// v5 grade rarity texts
|
||||
static void surpriseGradeRarityText(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
required String cancel,
|
||||
required String done,
|
||||
required List<String> rarities,
|
||||
}) {
|
||||
showRoundedModalBottomSheet(
|
||||
context,
|
||||
child: GradeRarityTextSetting(
|
||||
title: title,
|
||||
cancel: cancel,
|
||||
done: done,
|
||||
defaultRarities: rarities,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Rounding modal
|
||||
class RoundingSetting extends StatefulWidget {
|
||||
const RoundingSetting({super.key, this.rounding, this.subjectId});
|
||||
|
||||
final double? rounding;
|
||||
final String? subjectId;
|
||||
|
||||
@override
|
||||
_RoundingSettingState createState() => _RoundingSettingState();
|
||||
}
|
||||
|
||||
class _RoundingSettingState extends State<RoundingSetting> {
|
||||
late double rounding;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
rounding = (widget.rounding ??
|
||||
Provider.of<SettingsProvider>(context, listen: false).rounding) /
|
||||
10;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
UserProvider userProvider =
|
||||
Provider.of<UserProvider>(context, listen: false);
|
||||
DatabaseProvider databaseProvider =
|
||||
Provider.of<DatabaseProvider>(context, listen: false);
|
||||
|
||||
int roundingResult;
|
||||
|
||||
if (4.5 >= 4.5.floor() + rounding) {
|
||||
roundingResult = 5;
|
||||
} else {
|
||||
roundingResult = 4;
|
||||
}
|
||||
|
||||
return Column(children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: rounding,
|
||||
min: 0.1,
|
||||
max: 0.9,
|
||||
divisions: 8,
|
||||
label: rounding.toStringAsFixed(1),
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
thumbColor: Theme.of(context).colorScheme.secondary,
|
||||
onChanged: (v) => setState(() => rounding = v),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 50.0,
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: Center(
|
||||
child: Text(rounding.toStringAsFixed(1),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18.0,
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text("4.5",
|
||||
style: TextStyle(fontSize: 26.0, fontWeight: FontWeight.w500)),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Icon(FeatherIcons.arrowRight, color: Colors.grey),
|
||||
),
|
||||
GradeValueWidget(GradeValue(roundingResult, "", "", 100),
|
||||
fill: true, size: 32.0),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0, top: 6.0),
|
||||
child: MaterialActionButton(
|
||||
child: Text(SettingsLocalization("done").i18n),
|
||||
onPressed: () async {
|
||||
if (widget.rounding == null) {
|
||||
Provider.of<SettingsProvider>(context, listen: false)
|
||||
.update(rounding: (rounding * 10).toInt());
|
||||
} else {
|
||||
Map<String, String> roundings = await databaseProvider.userQuery
|
||||
.getRoundings(userId: userProvider.id!);
|
||||
|
||||
roundings[widget.subjectId!] = (rounding * 10).toStringAsFixed(2);
|
||||
|
||||
await databaseProvider.userStore
|
||||
.storeRoundings(roundings, userId: userProvider.id!);
|
||||
|
||||
await Provider.of<GradeProvider>(context, listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<TimetableProvider>(context, listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<AbsenceProvider>(context, listen: false)
|
||||
.convertBySettings();
|
||||
}
|
||||
|
||||
// ik i'm like a kreta dev, but setstate isn't working, so please don't kill me bye :3
|
||||
// actually it also looks good and it's kinda useful
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
// setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Bell Delay Modal
|
||||
|
||||
class BellDelaySetting extends StatefulWidget {
|
||||
const BellDelaySetting({super.key});
|
||||
|
||||
@override
|
||||
State<BellDelaySetting> createState() => _BellDelaySettingState();
|
||||
}
|
||||
|
||||
class _BellDelaySettingState extends State<BellDelaySetting>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
late Duration currentDelay;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(
|
||||
length: 2,
|
||||
vsync: this,
|
||||
initialIndex:
|
||||
Provider.of<SettingsProvider>(context, listen: false).bellDelay > 0
|
||||
? 1
|
||||
: 0);
|
||||
currentDelay = Duration(
|
||||
seconds:
|
||||
Provider.of<SettingsProvider>(context, listen: false).bellDelay);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
FilterBar(
|
||||
scrollable: true,
|
||||
tabAlignment: TabAlignment.center,
|
||||
items: [
|
||||
Tab(text: SettingsLocalization("delay").i18n),
|
||||
Tab(text: SettingsLocalization("hurry").i18n),
|
||||
],
|
||||
controller: _tabController,
|
||||
onTap: (i) async {
|
||||
// swap current page with target page
|
||||
setState(() {
|
||||
currentDelay = i == 0 ? -currentDelay.abs() : currentDelay.abs();
|
||||
});
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: CupertinoTheme(
|
||||
data: CupertinoThemeData(
|
||||
brightness: Theme.of(context).brightness,
|
||||
),
|
||||
child: CupertinoTimerPicker(
|
||||
key: UniqueKey(),
|
||||
mode: CupertinoTimerPickerMode.ms,
|
||||
initialTimerDuration: currentDelay.abs(),
|
||||
onTimerDurationChanged: (Duration d) {
|
||||
HapticFeedback.selectionClick();
|
||||
|
||||
currentDelay = _tabController.index == 0 ? -d : d;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(SettingsLocalization("sync_help").i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.of(context).text.withOpacity(.75))),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0, top: 6.0),
|
||||
child: Column(
|
||||
children: [
|
||||
MaterialActionButton(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
child: Text(SettingsLocalization("sync").i18n),
|
||||
onPressed: () {
|
||||
final lessonProvider =
|
||||
Provider.of<TimetableProvider>(context, listen: false);
|
||||
|
||||
Duration? closest;
|
||||
DateTime now = DateTime.now();
|
||||
for (var lesson
|
||||
in lessonProvider.getWeek(Week.current()) ?? []) {
|
||||
Duration sdiff = lesson.start.difference(now);
|
||||
Duration ediff = lesson.end.difference(now);
|
||||
|
||||
if (closest == null || sdiff.abs() < closest.abs()) {
|
||||
closest = sdiff;
|
||||
}
|
||||
if (ediff.abs() < closest.abs()) closest = ediff;
|
||||
}
|
||||
if (closest != null) {
|
||||
if (closest.inHours.abs() >= 1) return;
|
||||
currentDelay = closest;
|
||||
Provider.of<SettingsProvider>(context, listen: false)
|
||||
.update(bellDelay: currentDelay.inSeconds);
|
||||
_tabController.index = currentDelay.inSeconds > 0 ? 1 : 0;
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
),
|
||||
MaterialActionButton(
|
||||
child: Text(SettingsLocalization("done").i18n),
|
||||
onPressed: () {
|
||||
//Provider.of<SettingsProvider>(context, listen: false).update(context, rounding: (r * 10).toInt());
|
||||
Provider.of<SettingsProvider>(context, listen: false)
|
||||
.update(bellDelay: currentDelay.inSeconds);
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GradeColorsSetting extends StatefulWidget {
|
||||
const GradeColorsSetting({super.key});
|
||||
|
||||
@override
|
||||
_GradeColorsSettingState createState() => _GradeColorsSettingState();
|
||||
}
|
||||
|
||||
class _GradeColorsSettingState extends State<GradeColorsSetting> {
|
||||
Color currentColor = const Color(0x00000000);
|
||||
late SettingsProvider settings;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: List.generate(5, (index) {
|
||||
return ClipOval(
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
currentColor = settings.gradeColors[index];
|
||||
showRoundedModalBottomSheet(
|
||||
context,
|
||||
child: Column(children: [
|
||||
MaterialColorPicker(
|
||||
selectedColor: settings.gradeColors[index],
|
||||
onColorChange: (v) {
|
||||
setState(() {
|
||||
currentColor = v;
|
||||
});
|
||||
},
|
||||
allowShades: true,
|
||||
elevation: 0,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
MaterialActionButton(
|
||||
onPressed: () {
|
||||
List<Color> colors =
|
||||
List.castFrom(settings.gradeColors);
|
||||
var defaultColors =
|
||||
SettingsProvider.defaultSettings()
|
||||
.gradeColors;
|
||||
colors[index] = defaultColors[index];
|
||||
settings.update(gradeColors: colors);
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
child: Text(SettingsLocalization("reset").i18n),
|
||||
),
|
||||
MaterialActionButton(
|
||||
onPressed: () {
|
||||
List<Color> colors =
|
||||
List.castFrom(settings.gradeColors);
|
||||
colors[index] = currentColor.withAlpha(255);
|
||||
settings.update(
|
||||
gradeColors: settings.gradeColors);
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
child: Text(SettingsLocalization("done").i18n),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
]),
|
||||
).then((value) => setState(() {}));
|
||||
},
|
||||
child: GradeValueWidget(GradeValue(index + 1, "", "", 0),
|
||||
fill: true, size: 36.0),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class GradeRarityTextSetting extends StatefulWidget {
|
||||
const GradeRarityTextSetting({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.cancel,
|
||||
required this.done,
|
||||
required this.defaultRarities,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String cancel;
|
||||
final String done;
|
||||
final List<String> defaultRarities;
|
||||
|
||||
@override
|
||||
_GradeRarityTextSettingState createState() => _GradeRarityTextSettingState();
|
||||
}
|
||||
|
||||
class _GradeRarityTextSettingState extends State<GradeRarityTextSetting> {
|
||||
late SettingsProvider settings;
|
||||
late DatabaseProvider db;
|
||||
late UserProvider user;
|
||||
|
||||
final _rarityText = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
db = Provider.of<DatabaseProvider>(context, listen: false);
|
||||
user = Provider.of<UserProvider>(context, listen: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: List.generate(5, (index) {
|
||||
return ClipOval(
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
showRenameDialog(
|
||||
title: widget.title,
|
||||
cancel: widget.cancel,
|
||||
done: widget.done,
|
||||
rarities:
|
||||
await db.userQuery.getGradeRarities(userId: user.id!),
|
||||
gradeIndex: (index + 1).toString(),
|
||||
defaultRarities: widget.defaultRarities,
|
||||
);
|
||||
},
|
||||
child: GradeValueWidget(GradeValue(index + 1, "", "", 0),
|
||||
fill: true, size: 36.0),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
void showRenameDialog(
|
||||
{required String title,
|
||||
required String cancel,
|
||||
required String done,
|
||||
required Map<String, String> rarities,
|
||||
required String gradeIndex,
|
||||
required List<String> defaultRarities,
|
||||
required}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(builder: (context, setS) {
|
||||
String? rr = rarities[gradeIndex];
|
||||
rr ??= '';
|
||||
|
||||
_rarityText.text = rr;
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(title),
|
||||
content: TextField(
|
||||
controller: _rarityText,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
label: Text(defaultRarities[int.parse(gradeIndex) - 1]),
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(FeatherIcons.x),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_rarityText.clear();
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
cancel,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
done,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onPressed: () {
|
||||
rarities[gradeIndex] = _rarityText.text;
|
||||
|
||||
Provider.of<DatabaseProvider>(context, listen: false)
|
||||
.userStore
|
||||
.storeGradeRarities(rarities, userId: user.id!);
|
||||
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
).then((val) {
|
||||
_rarityText.clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class LiveActivityColorSetting extends StatefulWidget {
|
||||
const LiveActivityColorSetting({super.key});
|
||||
|
||||
@override
|
||||
_LiveActivityColorSettingState createState() =>
|
||||
_LiveActivityColorSettingState();
|
||||
}
|
||||
|
||||
class _LiveActivityColorSettingState extends State<LiveActivityColorSetting> {
|
||||
late SettingsProvider settings;
|
||||
Color currentColor = const Color(0x00000000);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Column(children: [
|
||||
MaterialColorPicker(
|
||||
allowShades: false,
|
||||
selectedColor: settings.liveActivityColor,
|
||||
onMainColorChange: (k) {
|
||||
setState(() {
|
||||
currentColor = k as Color;
|
||||
settings.update(
|
||||
liveActivityColor: currentColor.withAlpha(255));
|
||||
Navigator.of(context).maybePop();
|
||||
});
|
||||
},
|
||||
elevation: 0,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0, top: 40.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
MaterialActionButton(
|
||||
onPressed: () {
|
||||
var defaultColors =
|
||||
SettingsProvider.defaultSettings().liveActivityColor;
|
||||
settings.update(liveActivityColor: defaultColors);
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
child: Text(SettingsLocalization("reset").i18n),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
21
refilc_mobile_ui/lib/screens/settings/settings_route.dart
Normal file
21
refilc_mobile_ui/lib/screens/settings/settings_route.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Route settingsRoute(Widget widget) {
|
||||
return PageRouteBuilder(
|
||||
pageBuilder: (context, animation, secondaryAnimation) => widget,
|
||||
transitionDuration: const Duration(milliseconds: 500),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
var curve = Curves.ease;
|
||||
var curveTween = CurveTween(curve: curve);
|
||||
var begin = const Offset(0.0, 1.0);
|
||||
var end = Offset.zero;
|
||||
var tween = Tween(begin: begin, end: end).chain(curveTween);
|
||||
var offsetAnimation = animation.drive(tween);
|
||||
|
||||
return SlideTransition(
|
||||
position: offsetAnimation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
1139
refilc_mobile_ui/lib/screens/settings/settings_screen.dart
Normal file
1139
refilc_mobile_ui/lib/screens/settings/settings_screen.dart
Normal file
File diff suppressed because it is too large
Load Diff
303
refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart
Normal file
303
refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart
Normal file
@@ -0,0 +1,303 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension SettingsLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"personal_details": "Personal Details",
|
||||
"open_dkt": "Open DCS",
|
||||
"edit_nickname": "Edit Nickname",
|
||||
"edit_profile_picture": "Edit Profile Picture",
|
||||
"remove_profile_picture": "Remove Profile Picture",
|
||||
"select_profile_picture": "to select a picture",
|
||||
"click_here": "Click here",
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"system": "System",
|
||||
"add_user": "Add User",
|
||||
"log_out": "Log Out",
|
||||
"update_available": "Update Available",
|
||||
"general": "General",
|
||||
"language": "Language",
|
||||
"startpage": "Startpage",
|
||||
"rounding": "Rounding",
|
||||
"appearance": "Appearance",
|
||||
"theme": "Theme",
|
||||
"color": "Color",
|
||||
"grade_colors": "Grade Colors",
|
||||
"live_activity_color": "Live Activity Color",
|
||||
"notifications": "Notifications",
|
||||
"popups": "Pop-up Alerts",
|
||||
"news": "News",
|
||||
"extras": "Extras",
|
||||
"about": "About",
|
||||
"supporters": "Supporters",
|
||||
"privacy": "Privacy Policy",
|
||||
"licenses": "Licenses",
|
||||
"vibrate": "Vibration",
|
||||
"voff": "Off",
|
||||
"vlight": "Light",
|
||||
"vmedium": "Medium",
|
||||
"vstrong": "Strong",
|
||||
"cancel": "Cancel",
|
||||
"done": "Done",
|
||||
"reset": "Reset",
|
||||
"open": "Open",
|
||||
"data_collected":
|
||||
"Data collected: Platform (eg. iOS), App version (eg. 5.0.0), Unique Install Identifier",
|
||||
"Analytics": "Analytics",
|
||||
"Anonymous Usage Analytics": "Anonymous Usage Analytics",
|
||||
"graph_class_avg": "Class average on graph",
|
||||
"goodstudent": "Good student mode",
|
||||
"attention": "Attention!",
|
||||
"goodstudent_disclaimer":
|
||||
"reFilc can not be held liable for the usage of this feature.\n\n(if your mother beats you up because you showed her fake grades, you can only blame yourself for it)",
|
||||
"understand": "I understand",
|
||||
"secret": "Secret Settings",
|
||||
"bell_delay": "Bell Delay",
|
||||
"delay": "Delay",
|
||||
"hurry": "Hurry",
|
||||
"sync": "Synchronize",
|
||||
"sync_help": "Press the Synchronize button when the bell rings.",
|
||||
"surprise_grades": "Surprise Grades",
|
||||
"icon_pack": "Icon Pack",
|
||||
"change_username": "Set a nickname",
|
||||
"Accent Color": "Accent Color",
|
||||
"Background Color": "Background Color",
|
||||
"Highlight Color": "Highlight Color",
|
||||
"Adaptive Theme": "Adaptive Theme",
|
||||
"presentation": "Presentation mode",
|
||||
"uwufymode": "UwU-fied mode (hungarian)",
|
||||
"devmoretaps": "You are %s taps away from Developer Mode.",
|
||||
"devactivated": "Developer Mode successfully activated.",
|
||||
"devsettings": "Developer Settings",
|
||||
"devmode": "Developer Mode",
|
||||
"copy_jwt": "Copy JWT",
|
||||
"welcome_msg": "Welcome Message",
|
||||
"default": "Dynamic",
|
||||
"edit_welcome_msg": "Edit welcome message",
|
||||
"shadow_effect": "Shadow Effect",
|
||||
"app_icon": "App Icon",
|
||||
"settings": "Settings",
|
||||
"personalization": "Personalization",
|
||||
"edit": "Edit",
|
||||
"switch_account": "Switch Account",
|
||||
"subjects": "Subjects",
|
||||
"select_subject": "Select Subject",
|
||||
"own_paints": "Own Paints",
|
||||
"dl_paint": "Redeemed",
|
||||
"public_paint": "Public Paints",
|
||||
"no_pub_paint": "No Public Paints",
|
||||
"enter_id": "Enter Paint ID",
|
||||
"paint_id": "Paint ID...",
|
||||
"set_as_current": "Set as current",
|
||||
"share_subj_theme": "Share Theme",
|
||||
"no_name": "Untitled Paint",
|
||||
"current_paint": "Current Paint",
|
||||
"rename_subjects": "Rename Subjects",
|
||||
"rename_teachers": "Rename Teachers",
|
||||
"show_breaks": "Show Breaks",
|
||||
"fonts": "Fonts",
|
||||
"font_family": "Font Family",
|
||||
},
|
||||
"hu_hu": {
|
||||
"personal_details": "Személyes információk",
|
||||
"open_dkt": "DKT megnyitása",
|
||||
"edit_nickname": "Becenév szerkesztése",
|
||||
"edit_profile_picture": "Profil-kép szerkesztése",
|
||||
"remove_profile_picture": "Profil-kép törlése",
|
||||
"select_profile_picture": "a kép kiválasztásához",
|
||||
"click_here": "Kattints ide",
|
||||
"light": "Világos",
|
||||
"dark": "Sötét",
|
||||
"system": "Rendszer",
|
||||
"add_user": "Felhasználó hozzáadása",
|
||||
"log_out": "Kijelentkezés",
|
||||
"update_available": "Frissítés elérhető",
|
||||
"general": "Általános",
|
||||
"language": "Nyelv",
|
||||
"startpage": "Kezdőlap",
|
||||
"rounding": "Kerekítés",
|
||||
"appearance": "Kinézet",
|
||||
"theme": "Téma",
|
||||
"color": "Színek",
|
||||
"grade_colors": "Jegyek színei",
|
||||
"live_activity_color": "Live Activity színe",
|
||||
"notifications": "Értesítések",
|
||||
"popups": "Pop-up értesítések",
|
||||
"news": "Hírek",
|
||||
"extras": "Extrák",
|
||||
"about": "Névjegy",
|
||||
"supporters": "Támogatók",
|
||||
"privacy": "Adatvédelmi irányelvek",
|
||||
"licenses": "Licencek",
|
||||
"vibrate": "Rezgés",
|
||||
"voff": "Kikapcsolás",
|
||||
"vlight": "Alacsony",
|
||||
"vmedium": "Közepes",
|
||||
"vstrong": "Erős",
|
||||
"cancel": "Mégsem",
|
||||
"done": "Kész",
|
||||
"reset": "Visszaállítás",
|
||||
"open": "Megnyitás",
|
||||
"data_collected":
|
||||
"Gyűjtött adat: platform (pl.: iOS), app verzió (pl.: 5.0.0), egyedi telepítési azonosító",
|
||||
"Analytics": "Analitika",
|
||||
"Anonymous Usage Analytics": "Névtelen használati analitika",
|
||||
"graph_class_avg": "Osztályátlag a grafikonon",
|
||||
"goodstudent": "Jó tanuló mód",
|
||||
"attention": "Figyelem!",
|
||||
"goodstudent_disclaimer":
|
||||
"A reFilc minden felelősséget elhárít a funkció használatával kapcsolatban.\n\n(Értsd: ha az anyád megver, mert megtévesztő ábrákat mutattál neki, azért csakis magadat hibáztathatod.)",
|
||||
"understand": "Értem",
|
||||
"secret": "Titkos Beállítások",
|
||||
"bell_delay": "Csengő eltolódása",
|
||||
"delay": "Késleltetés",
|
||||
"hurry": "Siettetés",
|
||||
"sync": "Szinkronizálás",
|
||||
"sync_help": "Csengetéskor nyomd meg a Szinkronizálás gombot.",
|
||||
"surprise_grades": "Meglepetés jegyek",
|
||||
"icon_pack": "Ikon séma",
|
||||
"change_username": "Becenév beállítása",
|
||||
"Accent Color": "Egyedi szín",
|
||||
"Background Color": "Háttér színe",
|
||||
"Highlight Color": "Panelek színe",
|
||||
"Adaptive Theme": "Adaptív téma",
|
||||
"presentation": "Bemutató mód",
|
||||
"uwufymode": "UwU mód (magyar)",
|
||||
"devmoretaps": "Még %s koppintásra vagy a Fejlesztői módtól.",
|
||||
"devactivated": "Fejlesztői mód sikeresen aktiválva.",
|
||||
"devsettings": "Fejlesztői Beállítások",
|
||||
"devmode": "Fejlesztői mód",
|
||||
"copy_jwt": "JWT másolása",
|
||||
"welcome_msg": "Üdvözlő üzenet",
|
||||
"default": "Dinamikus",
|
||||
"edit_welcome_msg": "Üdvözlő üzenet szerkesztése",
|
||||
"shadow_effect": "Árnyékhatás",
|
||||
"app_icon": "Alkalmazásikon",
|
||||
"settings": "Beállítások",
|
||||
"personalization": "Személyre szabás",
|
||||
"edit": "Szerkesztés",
|
||||
"switch_account": "Fiókváltás",
|
||||
"subjects": "Tantárgyak",
|
||||
"select_subject": "Válassz tantárgyat",
|
||||
"own_paints": "Saját témák",
|
||||
"dl_paint": "Beszerzett",
|
||||
"public_paint": "Nyilvános témák",
|
||||
"no_pub_paint": "Nincsenek nyilvános festékek",
|
||||
"enter_id": "Azonosító megadása",
|
||||
"paint_id": "Téma azonosító...",
|
||||
"set_as_current": "Beállítás jelenleginek",
|
||||
"share_subj_theme": "Téma Megosztás",
|
||||
"no_name": "Névtelen téma",
|
||||
"current_paint": "Jelenlegi téma",
|
||||
"rename_subjects": "Tantárgyak átnevezése",
|
||||
"rename_teachers": "Tanárok átnevezése",
|
||||
"show_breaks": "Szünetek megjelenítése",
|
||||
"fonts": "Betűk",
|
||||
"font_family": "Betűtípus",
|
||||
},
|
||||
"de_de": {
|
||||
"personal_details": "Persönliche Angaben",
|
||||
"open_dkt": "Öffnen RDZ",
|
||||
"edit_nickname": "Spitznamen bearbeiten",
|
||||
"edit_profile_picture": "Profilbild bearbeiten",
|
||||
"remove_profile_picture": "Profilbild entfernen",
|
||||
"select_profile_picture": "um ein Bild auszuwählen",
|
||||
"click_here": "Klick hier",
|
||||
"light": "Licht",
|
||||
"dark": "Dunkel",
|
||||
"system": "System",
|
||||
"add_user": "Benutzer hinzufügen",
|
||||
"log_out": "Abmelden",
|
||||
"update_available": "Update verfügbar",
|
||||
"general": "Allgemein",
|
||||
"language": "Sprache",
|
||||
"startpage": "Startseite",
|
||||
"rounding": "Rundung",
|
||||
"appearance": "Erscheinungsbild",
|
||||
"theme": "Thema",
|
||||
"color": "Farbe",
|
||||
"grade_colors": "Grad Farben",
|
||||
"live_activity_color": "Live Activity Farben",
|
||||
"notifications": "Mitteilung",
|
||||
"popups": "Popup-Nachrichten",
|
||||
"news": "Nachrichten",
|
||||
"extras": "Extras",
|
||||
"about": "Informationen",
|
||||
"supporters": "Unterstützer",
|
||||
"privacy": "Datenschutzbestimmungen",
|
||||
"licenses": "Lizenzen",
|
||||
"vibrate": "Vibration",
|
||||
"voff": "Aus",
|
||||
"vlight": "Leicht",
|
||||
"vmedium": "Mittel",
|
||||
"vstrong": "Stark",
|
||||
"cancel": "Abbrechen",
|
||||
"done": "Fertig",
|
||||
"reset": "Zurücksetzen",
|
||||
"open": "Öffnen",
|
||||
"data_collected":
|
||||
"Erhobene Daten: Plattform (z.B. iOS), App version (z.B. 5.0.0), Eindeutige Installationskennung",
|
||||
"Analytics": "Analytik",
|
||||
"Anonymous Usage Analytics": "Anonyme Nutzungsanalyse",
|
||||
"graph_class_avg": "Klassendurchschnitt in der Grafik",
|
||||
"goodstudent": "Guter Student Modus",
|
||||
"attention": "Achtung!",
|
||||
"goodstudent_disclaimer":
|
||||
"reFilc kann nicht für die Nutzung dieser Funktion haftbar gemacht werden.\n\n(Wenn deine Mutter dich verprügelt, weil du ihr falsche Noten gezeigt hast, kannst du dir nur die Schuld dafür geben)",
|
||||
"understand": "Ich verstehe",
|
||||
"secret": "Geheime Einstellungen",
|
||||
"bell_delay": "Klingelverzögerung",
|
||||
"delay": "Verzögern",
|
||||
"hurry": "Eile",
|
||||
"sync": "Synchronisieren",
|
||||
"sync_help": "Drücken Sie die Sync-Taste, wenn die Glocke läutet.",
|
||||
"surprise_grades": "Überraschungsnoten",
|
||||
"icon_pack": "Icon-Pack",
|
||||
"change_username": "Einen Spitznamen festlegen",
|
||||
"Accent Color": "Accent Color",
|
||||
"Background Color": "Background Color",
|
||||
"Highlight Color": "Highlight Color",
|
||||
"Adaptive Theme": "Adaptive Theme",
|
||||
"presentation": "Präsentationsmodus",
|
||||
"uwufymode": "UwU-Modus (ungarisch)",
|
||||
"devmoretaps": "Sie sind %s Taps vom Entwicklermodus entfernt.",
|
||||
"devactivated": "Entwicklermodus erfolgreich aktiviert.",
|
||||
"devsettings": "Entwickleroptionen",
|
||||
"devmode": "Entwicklermodus",
|
||||
"copy_jwt": "JWT kopieren",
|
||||
"welcome_msg": "Willkommensnachricht",
|
||||
"default": "Dynamisch",
|
||||
"edit_welcome_msg": "Begrüßungsnachricht bearbeiten",
|
||||
"shadow_effect": "Schatteneffekt",
|
||||
"app_icon": "App-Symbol",
|
||||
"settings": "Einstellungen",
|
||||
"personalization": "Personalisierung",
|
||||
"edit": "Bearbeiten",
|
||||
"switch_account": "Benutzer wechseln",
|
||||
"subjects": "Themen",
|
||||
"select_subject": "Fach auswählen",
|
||||
"own_paints": "Meine Themen",
|
||||
"dl_paint": "Eingelöst",
|
||||
"public_paint": "Öffentliche Themen",
|
||||
"no_pub_paint": "Keine öffentlichen Anstriche",
|
||||
"enter_id": "Themen-ID eingeben",
|
||||
"paint_id": "Themen-ID...",
|
||||
"set_as_current": "Als aktuell einstellen",
|
||||
"share_subj_theme": "Thema Teilen",
|
||||
"no_name": "Anonymes Thema",
|
||||
"current_paint": "Aktuelles Thema",
|
||||
"rename_subjects": "Fächer umbenennen",
|
||||
"rename_teachers": "Lehrer umbenennen",
|
||||
"show_breaks": "Pausen anzeigen",
|
||||
"fonts": "Schriftarten",
|
||||
"font_family": "Schriftfamilie",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
398
refilc_mobile_ui/lib/screens/settings/submenu/edit_subject.dart
Normal file
398
refilc_mobile_ui/lib/screens/settings/submenu/edit_subject.dart
Normal file
@@ -0,0 +1,398 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:refilc/api/providers/database_provider.dart';
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc/utils/format.dart';
|
||||
import 'package:refilc_kreta_api/models/subject.dart';
|
||||
import 'package:refilc_kreta_api/models/teacher.dart';
|
||||
import 'package:refilc_kreta_api/providers/absence_provider.dart';
|
||||
import 'package:refilc_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:refilc_kreta_api/providers/timetable_provider.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
|
||||
// import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'edit_subject.i18n.dart';
|
||||
|
||||
class EditSubjectScreen extends StatefulWidget {
|
||||
const EditSubjectScreen({
|
||||
super.key,
|
||||
required this.subject,
|
||||
required this.teacher,
|
||||
});
|
||||
|
||||
final GradeSubject subject;
|
||||
final Teacher teacher;
|
||||
|
||||
@override
|
||||
EditSubjectScreenState createState() => EditSubjectScreenState();
|
||||
}
|
||||
|
||||
class EditSubjectScreenState extends State<EditSubjectScreen> {
|
||||
late SettingsProvider settingsProvider;
|
||||
late DatabaseProvider databaseProvider;
|
||||
late UserProvider user;
|
||||
|
||||
final _subjectName = TextEditingController();
|
||||
final _teacherName = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
settingsProvider = Provider.of<SettingsProvider>(context);
|
||||
databaseProvider = Provider.of<DatabaseProvider>(context);
|
||||
user = Provider.of<UserProvider>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
title: Text(
|
||||
(widget.subject.isRenamed && settingsProvider.renamedSubjectsEnabled
|
||||
? widget.subject.renamedTo
|
||||
: widget.subject.name.capital()) ??
|
||||
'',
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text,
|
||||
fontStyle: settingsProvider.renamedSubjectsItalics
|
||||
? FontStyle.italic
|
||||
: FontStyle.normal,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
Map<String, String> subs = await databaseProvider.userQuery
|
||||
.renamedSubjects(userId: user.id!);
|
||||
subs.remove(widget.subject.id);
|
||||
await databaseProvider.userStore
|
||||
.storeRenamedSubjects(subs, userId: user.id!);
|
||||
|
||||
Map<String, String> teach = await databaseProvider.userQuery
|
||||
.renamedTeachers(userId: user.id!);
|
||||
teach.remove(widget.teacher.id);
|
||||
await databaseProvider.userStore
|
||||
.storeRenamedTeachers(teach, userId: user.id!);
|
||||
|
||||
updateProviders();
|
||||
|
||||
// im crying rn
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
setState(() {});
|
||||
},
|
||||
icon: const Icon(FeatherIcons.trash2),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// rename subject
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
onPressed: () {
|
||||
showSubjectRenameDialog();
|
||||
},
|
||||
title: Text("rename_it".i18n),
|
||||
leading: Icon(
|
||||
FeatherIcons.penTool,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// rename teacher
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
onPressed: () {
|
||||
showTeacherRenameDialog();
|
||||
},
|
||||
title: Text("rename_te".i18n),
|
||||
leading: Icon(
|
||||
FeatherIcons.user,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// edit rounding
|
||||
// SplittedPanel(
|
||||
// padding: const EdgeInsets.only(top: 9.0),
|
||||
// cardPadding: const EdgeInsets.all(4.0),
|
||||
// isSeparated: true,
|
||||
// children: [
|
||||
// PanelButton(
|
||||
// onPressed: () {
|
||||
// SettingsHelper.newRoundings(context, widget.subject);
|
||||
// setState(() {});
|
||||
// },
|
||||
// title: Text(
|
||||
// "rounding".i18n,
|
||||
// style: TextStyle(
|
||||
// color: AppColors.of(context).text.withOpacity(.95),
|
||||
// ),
|
||||
// ),
|
||||
// leading: Icon(
|
||||
// FeatherIcons.gitCommit,
|
||||
// size: 22.0,
|
||||
// color: AppColors.of(context).text.withOpacity(.95),
|
||||
// ),
|
||||
// trailing: Text(
|
||||
// ((widget.subject.customRounding ??
|
||||
// settingsProvider.rounding) /
|
||||
// 10)
|
||||
// .toStringAsFixed(1),
|
||||
// style: const TextStyle(fontSize: 14.0),
|
||||
// ),
|
||||
// borderRadius: const BorderRadius.vertical(
|
||||
// top: Radius.circular(12.0),
|
||||
// bottom: Radius.circular(12.0),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// rename dialogs
|
||||
void showSubjectRenameDialog() {
|
||||
_subjectName.text = widget.subject.renamedTo ?? '';
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(builder: (context, setS) {
|
||||
return AlertDialog(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(14.0))),
|
||||
title: Text("rename_it".i18n),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(12.0))),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
widget.subject.name,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500, fontSize: 16.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Icon(FeatherIcons.arrowDown, size: 32),
|
||||
),
|
||||
TextField(
|
||||
controller: _subjectName,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(
|
||||
borderSide:
|
||||
const BorderSide(color: Colors.grey, width: 1.5),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide:
|
||||
const BorderSide(color: Colors.grey, width: 1.5),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
hintText: "modified_name".i18n,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(
|
||||
FeatherIcons.x,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_subjectName.text = "";
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
"cancel".i18n,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
"done".i18n,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onPressed: () async {
|
||||
Map<String, String> renamedSubjs = await databaseProvider
|
||||
.userQuery
|
||||
.renamedSubjects(userId: user.id!);
|
||||
|
||||
renamedSubjs[widget.subject.id] = _subjectName.text;
|
||||
|
||||
await databaseProvider.userStore
|
||||
.storeRenamedSubjects(renamedSubjs, userId: user.id!);
|
||||
|
||||
updateProviders();
|
||||
|
||||
Navigator.of(context).pop();
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
).then((val) {
|
||||
_subjectName.text = "";
|
||||
});
|
||||
}
|
||||
|
||||
void showTeacherRenameDialog() {
|
||||
_teacherName.text = widget.teacher.renamedTo ?? '';
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(builder: (context, setS) {
|
||||
return AlertDialog(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(14.0))),
|
||||
title: Text("rename_te".i18n),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(12.0))),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
widget.teacher.name,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500, fontSize: 16.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Icon(FeatherIcons.arrowDown, size: 32),
|
||||
),
|
||||
TextField(
|
||||
controller: _teacherName,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(
|
||||
borderSide:
|
||||
const BorderSide(color: Colors.grey, width: 1.5),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide:
|
||||
const BorderSide(color: Colors.grey, width: 1.5),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
hintText: "modified_name".i18n,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(
|
||||
FeatherIcons.x,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_teacherName.text = "";
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
"cancel".i18n,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
"done".i18n,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onPressed: () async {
|
||||
Map<String, String> renamedTeach = await databaseProvider
|
||||
.userQuery
|
||||
.renamedTeachers(userId: user.id!);
|
||||
|
||||
renamedTeach[widget.teacher.id] = _teacherName.text;
|
||||
|
||||
await databaseProvider.userStore
|
||||
.storeRenamedTeachers(renamedTeach, userId: user.id!);
|
||||
|
||||
updateProviders();
|
||||
|
||||
Navigator.of(context).pop();
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
).then((val) {
|
||||
_teacherName.text = "";
|
||||
});
|
||||
}
|
||||
|
||||
void updateProviders() async {
|
||||
await Provider.of<GradeProvider>(context, listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<TimetableProvider>(context, listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<AbsenceProvider>(context, listen: false)
|
||||
.convertBySettings();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension SettingsLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"rename_it": "Rename Subject",
|
||||
"rename_te": "Rename Teacher",
|
||||
"rounding": "Rounding",
|
||||
"gs_mode": "Good Student Mode",
|
||||
"rename_subject": "Rename Subject",
|
||||
"modified_name": "Modified Name",
|
||||
"cancel": "Cancel",
|
||||
"done": "Done",
|
||||
},
|
||||
"hu_hu": {
|
||||
"rename_it": "Tantárgy átnevezése",
|
||||
"rename_te": "Tanár átnevezése",
|
||||
"rounding": "Kerekítés",
|
||||
"gs_mode": "Jó tanuló mód",
|
||||
"rename_subject": "Tantárgy átnevezése",
|
||||
"modified_name": "Módosított név",
|
||||
"cancel": "Mégse",
|
||||
"done": "Kész",
|
||||
},
|
||||
"de_de": {
|
||||
"rename_it": "Betreff umbenennen",
|
||||
"rename_te": "Lehrer umbenennen",
|
||||
"rounding": "Rundung",
|
||||
"gs_mode": "Guter Student Modus",
|
||||
"rename_subject": "Fach umbenennen",
|
||||
"modified_name": "Geänderter Name",
|
||||
"cancel": "Abbrechen",
|
||||
"done": "Erledigt",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
156
refilc_mobile_ui/lib/screens/settings/submenu/extras_screen.dart
Normal file
156
refilc_mobile_ui/lib/screens/settings/submenu/extras_screen.dart
Normal file
@@ -0,0 +1,156 @@
|
||||
// import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:refilc_plus/models/premium_scopes.dart';
|
||||
import 'package:refilc_plus/providers/premium_provider.dart';
|
||||
import 'package:refilc_plus/ui/mobile/premium/upsell.dart';
|
||||
import 'package:refilc_plus/ui/mobile/settings/welcome_message.dart';
|
||||
// import 'package:provider/provider.dart';
|
||||
import 'submenu_screen.i18n.dart';
|
||||
|
||||
class MenuExtrasSettings extends StatelessWidget {
|
||||
const MenuExtrasSettings({
|
||||
super.key,
|
||||
this.borderRadius = const BorderRadius.vertical(
|
||||
top: Radius.circular(4.0), bottom: Radius.circular(4.0)),
|
||||
});
|
||||
|
||||
final BorderRadius borderRadius;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PanelButton(
|
||||
onPressed: () => Navigator.of(context, rootNavigator: true).push(
|
||||
CupertinoPageRoute(builder: (context) => const ExtrasSettingsScreen()),
|
||||
),
|
||||
title: Text("extras".i18n),
|
||||
leading: Icon(
|
||||
FeatherIcons.edit,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
trailing: Icon(
|
||||
FeatherIcons.chevronRight,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
borderRadius: borderRadius,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExtrasSettingsScreen extends StatefulWidget {
|
||||
const ExtrasSettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
ExtrasSettingsScreenState createState() => ExtrasSettingsScreenState();
|
||||
}
|
||||
|
||||
class ExtrasSettingsScreenState extends State<ExtrasSettingsScreen> {
|
||||
late SettingsProvider settingsProvider;
|
||||
late UserProvider user;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context);
|
||||
UserProvider user = Provider.of<UserProvider>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
title: Text(
|
||||
"extras".i18n,
|
||||
style: TextStyle(color: AppColors.of(context).text),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||
child: Column(
|
||||
children: [
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
|
||||
onPressed: () async {
|
||||
if (!Provider.of<PremiumProvider>(context, listen: false)
|
||||
.hasScope(PremiumScopes.customGradeRarities)) {
|
||||
return PremiumLockedFeatureUpsell.show(
|
||||
context: context,
|
||||
feature: PremiumFeature.gradeRarities);
|
||||
}
|
||||
|
||||
// settingsProvider.update(
|
||||
// gradeOpeningFun: !settingsProvider.gradeOpeningFun);
|
||||
SettingsHelper.surpriseGradeRarityText(
|
||||
context,
|
||||
title: 'rarity_title'.i18n,
|
||||
cancel: 'cancel'.i18n,
|
||||
done: 'done'.i18n,
|
||||
rarities: [
|
||||
"common".i18n,
|
||||
"uncommon".i18n,
|
||||
"rare".i18n,
|
||||
"epic".i18n,
|
||||
"legendary".i18n,
|
||||
],
|
||||
);
|
||||
setState(() {});
|
||||
},
|
||||
trailingDivider: true,
|
||||
title: Text(
|
||||
"surprise_grades".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.gradeOpeningFun ? .95 : .25),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.gift,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.gradeOpeningFun ? .95 : .25),
|
||||
),
|
||||
trailing: Switch(
|
||||
onChanged: (v) async {
|
||||
settingsProvider.update(gradeOpeningFun: v);
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
value: settingsProvider.gradeOpeningFun,
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
WelcomeMessagePanelButton(settingsProvider, user),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc/utils/format.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
|
||||
|
||||
class MenuGeneralSettings extends StatelessWidget {
|
||||
const MenuGeneralSettings({
|
||||
super.key,
|
||||
this.borderRadius = const BorderRadius.vertical(
|
||||
top: Radius.circular(4.0), bottom: Radius.circular(4.0)),
|
||||
});
|
||||
|
||||
final BorderRadius borderRadius;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PanelButton(
|
||||
onPressed: () => Navigator.of(context, rootNavigator: true).push(
|
||||
CupertinoPageRoute(builder: (context) => const GeneralSettingsScreen()),
|
||||
),
|
||||
title: Text("general".i18n),
|
||||
leading: Icon(
|
||||
FeatherIcons.settings,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
trailing: Icon(
|
||||
FeatherIcons.chevronRight,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
borderRadius: borderRadius,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GeneralSettingsScreen extends StatefulWidget {
|
||||
const GeneralSettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
GeneralSettingsScreenState createState() => GeneralSettingsScreenState();
|
||||
}
|
||||
|
||||
class GeneralSettingsScreenState extends State<GeneralSettingsScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context);
|
||||
|
||||
String startPageTitle =
|
||||
SettingsHelper.localizedPageTitles()[settingsProvider.startPage] ?? "?";
|
||||
String languageText =
|
||||
SettingsHelper.langMap[settingsProvider.language] ?? "?";
|
||||
String vibrateTitle = {
|
||||
VibrationStrength.off: "voff".i18n,
|
||||
VibrationStrength.light: "vlight".i18n,
|
||||
VibrationStrength.medium: "vmedium".i18n,
|
||||
VibrationStrength.strong: "vstrong".i18n,
|
||||
}[settingsProvider.vibrate] ??
|
||||
"?";
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
title: Text(
|
||||
"general".i18n,
|
||||
style: TextStyle(color: AppColors.of(context).text),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||
child: Column(
|
||||
children: [
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
|
||||
onPressed: () {
|
||||
SettingsHelper.bellDelay(context);
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
"bell_delay".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.bellDelayEnabled ? .95 : .25),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
settingsProvider.bellDelayEnabled
|
||||
? FeatherIcons.bell
|
||||
: FeatherIcons.bellOff,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.bellDelayEnabled ? .95 : .25),
|
||||
),
|
||||
trailingDivider: true,
|
||||
trailing: Switch(
|
||||
onChanged: (v) =>
|
||||
settingsProvider.update(bellDelayEnabled: v),
|
||||
value: settingsProvider.bellDelayEnabled,
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
onPressed: () {
|
||||
SettingsHelper.rounding(context);
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
"rounding".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.gitCommit,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
trailing: Text(
|
||||
(settingsProvider.rounding / 10).toStringAsFixed(1),
|
||||
style: const TextStyle(fontSize: 14.0),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
|
||||
onPressed: () {
|
||||
settingsProvider.update(
|
||||
graphClassAvg: !settingsProvider.graphClassAvg);
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
"graph_class_avg".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.graphClassAvg ? .95 : .25),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.barChart,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.graphClassAvg ? .95 : .25),
|
||||
),
|
||||
trailing: Switch(
|
||||
onChanged: (v) =>
|
||||
settingsProvider.update(graphClassAvg: v),
|
||||
value: settingsProvider.graphClassAvg,
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
onPressed: () {
|
||||
SettingsHelper.startPage(context);
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
"startpage".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.play,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
trailing: Text(
|
||||
startPageTitle.capital(),
|
||||
style: const TextStyle(fontSize: 14.0),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
onPressed: () {
|
||||
SettingsHelper.language(context);
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
"language".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.globe,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
trailing: Text(
|
||||
languageText,
|
||||
style: const TextStyle(fontSize: 14.0),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
onPressed: () {
|
||||
SettingsHelper.vibrate(context);
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
"vibrate".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.radio,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
trailing: Text(
|
||||
vibrateTitle,
|
||||
style: const TextStyle(fontSize: 14.0),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
|
||||
onPressed: () {
|
||||
settingsProvider.update(
|
||||
showBreaks: !settingsProvider.showBreaks);
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
"show_breaks".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.showBreaks ? .95 : .25),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
settingsProvider.showBreaks
|
||||
? FeatherIcons.eye
|
||||
: FeatherIcons.eyeOff,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context)
|
||||
.text
|
||||
.withOpacity(settingsProvider.showBreaks ? .95 : .25),
|
||||
),
|
||||
trailing: Switch(
|
||||
onChanged: (v) => settingsProvider.update(showBreaks: v),
|
||||
value: settingsProvider.showBreaks,
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
|
||||
onPressed: () {
|
||||
settingsProvider.update(
|
||||
newsEnabled: !settingsProvider.newsEnabled);
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
"news".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.newsEnabled ? .95 : .25),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
Icons.newspaper_outlined,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.newsEnabled ? .95 : .25),
|
||||
),
|
||||
trailing: Switch(
|
||||
onChanged: (v) => settingsProvider.update(newsEnabled: v),
|
||||
value: settingsProvider.newsEnabled,
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
572
refilc_mobile_ui/lib/screens/settings/submenu/paint_list.dart
Normal file
572
refilc_mobile_ui/lib/screens/settings/submenu/paint_list.dart
Normal file
@@ -0,0 +1,572 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/models/shared_theme.dart';
|
||||
import 'package:refilc/theme/colors/accent.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc/theme/observer.dart';
|
||||
import 'package:refilc_kreta_api/providers/share_provider.dart';
|
||||
import 'package:refilc_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:refilc_mobile_ui/common/empty.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class MenuPaintList extends StatelessWidget {
|
||||
const MenuPaintList({
|
||||
super.key,
|
||||
this.borderRadius = const BorderRadius.vertical(
|
||||
top: Radius.circular(4.0), bottom: Radius.circular(4.0)),
|
||||
});
|
||||
|
||||
final BorderRadius borderRadius;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PanelButton(
|
||||
onPressed: () async {
|
||||
List<SharedTheme> publicThemes =
|
||||
await Provider.of<ShareProvider>(context, listen: false)
|
||||
.getAllPublicThemes(context);
|
||||
|
||||
Navigator.of(context, rootNavigator: true).push(CupertinoPageRoute(
|
||||
builder: (context) => PaintListScreen(publicThemes: publicThemes)));
|
||||
},
|
||||
title: Text(
|
||||
"own_paints".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.list,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
trailing: Icon(
|
||||
FeatherIcons.chevronRight,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
borderRadius: borderRadius,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PaintListScreen extends StatefulWidget {
|
||||
const PaintListScreen({super.key, required this.publicThemes});
|
||||
|
||||
final List<SharedTheme> publicThemes;
|
||||
|
||||
@override
|
||||
PaintListScreenState createState() => PaintListScreenState();
|
||||
}
|
||||
|
||||
class PaintListScreenState extends State<PaintListScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late SettingsProvider settingsProvider;
|
||||
late UserProvider user;
|
||||
late ShareProvider shareProvider;
|
||||
|
||||
late AnimationController _hideContainersController;
|
||||
|
||||
late List<Widget> tiles;
|
||||
|
||||
final _paintId = TextEditingController();
|
||||
|
||||
SharedTheme? newThemeByID;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
shareProvider = Provider.of<ShareProvider>(context, listen: false);
|
||||
|
||||
_hideContainersController = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 200));
|
||||
}
|
||||
|
||||
void buildPublicPaintTiles() async {
|
||||
List<Widget> subjectTiles = [];
|
||||
|
||||
var added = [];
|
||||
var i = 0;
|
||||
|
||||
for (var t in widget.publicThemes) {
|
||||
if (added.contains(t.id)) continue;
|
||||
|
||||
Widget w = PanelButton(
|
||||
onPressed: () => {
|
||||
// TODO: set theme
|
||||
},
|
||||
title: Column(
|
||||
children: [
|
||||
Text(
|
||||
t.displayName,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
t.nickname,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.75),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 2.0),
|
||||
width: 12.0,
|
||||
height: 12.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: t.backgroundColor,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 2.0),
|
||||
width: 12.0,
|
||||
height: 12.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: t.panelsColor,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 2.0),
|
||||
width: 12.0,
|
||||
height: 12.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: t.accentColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(i == 0 ? 12.0 : 4.0),
|
||||
bottom:
|
||||
Radius.circular(i + 1 == widget.publicThemes.length ? 12.0 : 4.0),
|
||||
),
|
||||
);
|
||||
|
||||
i += 1;
|
||||
subjectTiles.add(w);
|
||||
added.add(t.id);
|
||||
}
|
||||
|
||||
if (widget.publicThemes.isEmpty) {
|
||||
subjectTiles.add(Empty(
|
||||
subtitle: 'no_pub_paint'.i18n,
|
||||
));
|
||||
}
|
||||
|
||||
tiles = subjectTiles;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
settingsProvider = Provider.of<SettingsProvider>(context);
|
||||
user = Provider.of<UserProvider>(context);
|
||||
|
||||
buildPublicPaintTiles();
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: _hideContainersController,
|
||||
builder: (context, child) => Opacity(
|
||||
opacity: 1 - _hideContainersController.value,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
title: Text(
|
||||
"own_paints".i18n,
|
||||
style: TextStyle(color: AppColors.of(context).text),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// enter id
|
||||
SplittedPanel(
|
||||
padding: EdgeInsets.zero,
|
||||
cardPadding: const EdgeInsets.all(3.0),
|
||||
hasBorder: true,
|
||||
isTransparent: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
onPressed: () => showEnterIDDialog(),
|
||||
title: Text(
|
||||
"enter_id".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.plus,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(
|
||||
height: 18.0,
|
||||
),
|
||||
// current paint
|
||||
SplittedPanel(
|
||||
title: Text('current_paint'.i18n),
|
||||
padding: EdgeInsets.zero,
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
children: [
|
||||
PanelButton(
|
||||
onPressed: () async {
|
||||
if (settingsProvider.currentThemeId != '') {
|
||||
Share.share(
|
||||
settingsProvider.currentThemeId,
|
||||
subject: 'share_subj_theme'.i18n,
|
||||
);
|
||||
} else {
|
||||
SharedGradeColors gradeColors = await shareProvider
|
||||
.shareCurrentGradeColors(context);
|
||||
SharedTheme theme =
|
||||
await shareProvider.shareCurrentTheme(
|
||||
context,
|
||||
gradeColors: gradeColors,
|
||||
);
|
||||
|
||||
Share.share(
|
||||
theme.id,
|
||||
subject: 'share_subj_theme'.i18n,
|
||||
);
|
||||
}
|
||||
},
|
||||
longPressInstead: true,
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
settingsProvider.currentThemeDisplayName != ''
|
||||
? settingsProvider.currentThemeDisplayName
|
||||
: 'no_name'.i18n,
|
||||
style: TextStyle(
|
||||
color:
|
||||
AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
settingsProvider.currentThemeCreator != ''
|
||||
? settingsProvider.currentThemeCreator
|
||||
: 'Anonymous',
|
||||
style: TextStyle(
|
||||
color:
|
||||
AppColors.of(context).text.withOpacity(.65),
|
||||
fontSize: 15.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: Transform.translate(
|
||||
offset: const Offset(8.0, 0.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 2.0),
|
||||
width: 14.0,
|
||||
height: 14.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color:
|
||||
(settingsProvider.customBackgroundColor ??
|
||||
SettingsProvider.defaultSettings()
|
||||
.customBackgroundColor),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.of(context)
|
||||
.text
|
||||
.withOpacity(0.15),
|
||||
offset: const Offset(1, 2),
|
||||
blurRadius: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Transform.translate(
|
||||
offset: const Offset(-4.0, 0.0),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 2.0),
|
||||
width: 14.0,
|
||||
height: 14.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: (settingsProvider
|
||||
.customHighlightColor ??
|
||||
SettingsProvider.defaultSettings()
|
||||
.customHighlightColor),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.of(context)
|
||||
.text
|
||||
.withOpacity(0.15),
|
||||
offset: const Offset(1, 2),
|
||||
blurRadius: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Transform.translate(
|
||||
offset: const Offset(-8.0, 0.0),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 2.0),
|
||||
width: 14.0,
|
||||
height: 14.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: settingsProvider.customAccentColor ??
|
||||
accentColorMap[
|
||||
settingsProvider.accentColor],
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColors.of(context)
|
||||
.text
|
||||
.withOpacity(0.15),
|
||||
offset: const Offset(1, 2),
|
||||
blurRadius: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12),
|
||||
bottom: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(
|
||||
height: 18.0,
|
||||
),
|
||||
// own paints
|
||||
SplittedPanel(
|
||||
title: Text('public_paint'.i18n),
|
||||
padding: EdgeInsets.zero,
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
children: tiles,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// enter id dialog
|
||||
void showEnterIDDialog() {
|
||||
_paintId.text = '';
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(builder: (context, setS) {
|
||||
return AlertDialog(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(14.0))),
|
||||
title: Text("enter_id".i18n),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: _paintId,
|
||||
onEditingComplete: () async {
|
||||
// SharedTheme? theme = await shareProvider.getThemeById(
|
||||
// context,
|
||||
// id: _paintId.text.replaceAll(' ', ''),
|
||||
// );
|
||||
|
||||
// if (theme != null) {
|
||||
// // set theme variable
|
||||
// newThemeByID = theme;
|
||||
|
||||
// _paintId.clear();
|
||||
// } else {
|
||||
// ScaffoldMessenger.of(context).showSnackBar(
|
||||
// CustomSnackBar(
|
||||
// content: Text("theme_not_found".i18n,
|
||||
// style: const TextStyle(color: Colors.white)),
|
||||
// backgroundColor: AppColors.of(context).red,
|
||||
// context: context,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(
|
||||
borderSide:
|
||||
const BorderSide(color: Colors.grey, width: 1.5),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide:
|
||||
const BorderSide(color: Colors.grey, width: 1.5),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
hintText: 'paint_id'.i18n,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(
|
||||
FeatherIcons.x,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_paintId.text = '';
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Icon(FeatherIcons.arrowDown, size: 32),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(12.0))),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'set_as_current'.i18n,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500, fontSize: 16.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
"cancel".i18n,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
"done".i18n,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onPressed: () async {
|
||||
// get sex
|
||||
|
||||
SharedTheme? theme = await shareProvider.getThemeById(
|
||||
context,
|
||||
id: _paintId.text.replaceAll(' ', ''),
|
||||
);
|
||||
|
||||
if (theme != null) {
|
||||
// set theme variable
|
||||
newThemeByID = theme;
|
||||
|
||||
_paintId.clear();
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
CustomSnackBar(
|
||||
content: Text("theme_not_found".i18n,
|
||||
style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: AppColors.of(context).red,
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// slay
|
||||
|
||||
setPaint();
|
||||
|
||||
setState(() {});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
).then((val) {
|
||||
_paintId.clear();
|
||||
});
|
||||
}
|
||||
|
||||
void setPaint() async {
|
||||
if (newThemeByID == null) return;
|
||||
|
||||
// changing grade colors
|
||||
List<Color> colors = [
|
||||
newThemeByID!.gradeColors.oneColor,
|
||||
newThemeByID!.gradeColors.twoColor,
|
||||
newThemeByID!.gradeColors.threeColor,
|
||||
newThemeByID!.gradeColors.fourColor,
|
||||
newThemeByID!.gradeColors.fiveColor,
|
||||
];
|
||||
settingsProvider.update(gradeColors: colors, store: true);
|
||||
|
||||
// changing shadow effect
|
||||
settingsProvider.update(
|
||||
shadowEffect: newThemeByID!.shadowEffect, store: true);
|
||||
|
||||
// changing theme mode
|
||||
settingsProvider.update(theme: newThemeByID!.themeMode, store: true);
|
||||
|
||||
// changing theme
|
||||
settingsProvider.update(
|
||||
customBackgroundColor: newThemeByID!.backgroundColor,
|
||||
customHighlightColor: newThemeByID!.panelsColor,
|
||||
customAccentColor: newThemeByID!.accentColor,
|
||||
customIconColor: newThemeByID!.iconColor,
|
||||
// new things
|
||||
currentThemeId: newThemeByID!.id,
|
||||
currentThemeDisplayName: newThemeByID!.displayName,
|
||||
currentThemeCreator: newThemeByID!.nickname,
|
||||
// we should store it
|
||||
store: true,
|
||||
);
|
||||
|
||||
// changing font family
|
||||
settingsProvider.update(fontFamily: newThemeByID!.fontFamily, store: true);
|
||||
|
||||
// seems weird but it works, trust me (idk why)
|
||||
// await settingsProvider.update(theme: settingsProvider.theme, store: true);
|
||||
Provider.of<ThemeModeObserver>(context, listen: false)
|
||||
.changeTheme(settingsProvider.theme, updateNavbarColor: true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,834 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/helpers/subject.dart';
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc/utils/format.dart';
|
||||
import 'package:refilc_kreta_api/models/grade.dart';
|
||||
import 'package:refilc_kreta_api/providers/absence_provider.dart';
|
||||
import 'package:refilc_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:refilc_kreta_api/providers/timetable_provider.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/submenu/edit_subject.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/submenu/paint_list.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
|
||||
import 'package:refilc_plus/models/premium_scopes.dart';
|
||||
import 'package:refilc_plus/providers/premium_provider.dart';
|
||||
import 'package:refilc_plus/ui/mobile/premium/upsell.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class MenuPersonalizeSettings extends StatelessWidget {
|
||||
const MenuPersonalizeSettings({
|
||||
super.key,
|
||||
this.borderRadius = const BorderRadius.vertical(
|
||||
top: Radius.circular(4.0), bottom: Radius.circular(4.0)),
|
||||
});
|
||||
|
||||
final BorderRadius borderRadius;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PanelButton(
|
||||
onPressed: () => Navigator.of(context, rootNavigator: true).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => const PersonalizeSettingsScreen()),
|
||||
),
|
||||
title: Text("personalization".i18n),
|
||||
leading: Icon(
|
||||
Icons.palette_outlined,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
trailing: Icon(
|
||||
FeatherIcons.chevronRight,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
borderRadius: borderRadius,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PersonalizeSettingsScreen extends StatefulWidget {
|
||||
const PersonalizeSettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
PersonalizeSettingsScreenState createState() =>
|
||||
PersonalizeSettingsScreenState();
|
||||
}
|
||||
|
||||
class PersonalizeSettingsScreenState extends State<PersonalizeSettingsScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late SettingsProvider settingsProvider;
|
||||
late UserProvider user;
|
||||
|
||||
late AnimationController _hideContainersController;
|
||||
|
||||
late List<Grade> editedShit;
|
||||
late List<Grade> otherShit;
|
||||
|
||||
late List<Widget> tiles;
|
||||
// late List<Widget> fontTiles;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// editedShit = Provider.of<GradeProvider>(context, listen: false)
|
||||
// .grades
|
||||
// .where((e) => e.teacher.isRenamed || e.subject.isRenamed)
|
||||
// // .map((e) => e.subject)
|
||||
// .toSet()
|
||||
// .toList()
|
||||
// ..sort((a, b) => a.subject.name.compareTo(b.subject.name));
|
||||
|
||||
List<Grade> other = Provider.of<GradeProvider>(context, listen: false)
|
||||
.grades
|
||||
.where((e) => !e.teacher.isRenamed && !e.subject.isRenamed)
|
||||
.toSet()
|
||||
.toList()
|
||||
..sort((a, b) => a.subject.name.compareTo(b.subject.name));
|
||||
|
||||
otherShit = [];
|
||||
var addedOthers = [];
|
||||
|
||||
for (var e in other) {
|
||||
if (addedOthers.contains(e.subject.id)) continue;
|
||||
addedOthers.add(e.subject.id);
|
||||
|
||||
otherShit.add(e);
|
||||
}
|
||||
|
||||
otherShit = otherShit
|
||||
..sort((a, b) =>
|
||||
a.subject.name.compareTo(b.subject.name)); // just cuz why not
|
||||
|
||||
// editedTeachers = Provider.of<GradeProvider>(context, listen: false)
|
||||
// .grades
|
||||
// .where((e) => e.teacher.isRenamed || e.subject.isRenamed)
|
||||
// .map((e) => e.teacher)
|
||||
// .toSet()
|
||||
// .toList();
|
||||
// // ..sort((a, b) => a.name.compareTo(b.name));
|
||||
// otherTeachers = Provider.of<GradeProvider>(context, listen: false)
|
||||
// .grades
|
||||
// .where((e) => !e.teacher.isRenamed && !e.subject.isRenamed)
|
||||
// .map((e) => e.teacher)
|
||||
// .toSet()
|
||||
// .toList();
|
||||
// ..sort((a, b) => a.name.compareTo(b.name));
|
||||
|
||||
_hideContainersController = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 200));
|
||||
}
|
||||
|
||||
void buildSubjectTiles() {
|
||||
List<Widget> subjectTiles = [];
|
||||
|
||||
var added = [];
|
||||
var i = 0;
|
||||
|
||||
List<Grade> need = [];
|
||||
for (var s in editedShit) {
|
||||
if (added.contains(s.subject.id)) continue;
|
||||
need.add(s);
|
||||
added.add(s.subject.id);
|
||||
}
|
||||
|
||||
for (var s in need) {
|
||||
Widget widget = PanelButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context, rootNavigator: true).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => EditSubjectScreen(
|
||||
subject: s.subject,
|
||||
teacher: s.teacher, // not sure why, but it works tho
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
(s.subject.isRenamed && settingsProvider.renamedSubjectsEnabled
|
||||
? s.subject.renamedTo
|
||||
: s.subject.name.capital()) ??
|
||||
'',
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
fontStyle: settingsProvider.renamedSubjectsItalics
|
||||
? FontStyle.italic
|
||||
: FontStyle.normal,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
(s.teacher.isRenamed && settingsProvider.renamedTeachersEnabled
|
||||
? s.teacher.renamedTo
|
||||
: s.teacher.name.capital()) ??
|
||||
'',
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.85),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 15.0,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
leading: Icon(
|
||||
SubjectIcon.resolveVariant(context: context, subject: s.subject),
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
trailing: Icon(
|
||||
FeatherIcons.chevronRight,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(i == 0 ? 12.0 : 4.0),
|
||||
bottom: Radius.circular(i + 1 == need.length ? 12.0 : 4.0),
|
||||
),
|
||||
);
|
||||
|
||||
i += 1;
|
||||
subjectTiles.add(widget);
|
||||
}
|
||||
|
||||
tiles = subjectTiles;
|
||||
}
|
||||
|
||||
// void buildFontTiles() {
|
||||
// List<String> fonts = [
|
||||
// "Merienda",
|
||||
// "M PLUS Code Latin",
|
||||
// "Figtree",
|
||||
// "Fira Code",
|
||||
// "Vollkorn",
|
||||
// ];
|
||||
|
||||
// List<Widget> fTiles = [];
|
||||
// var added = [];
|
||||
|
||||
// for (var f in fonts) {
|
||||
// if (added.contains(f)) continue;
|
||||
|
||||
// Widget widget = PanelButton(
|
||||
// onPressed: () async {
|
||||
// settingsProvider.update(fontFamily: f);
|
||||
// setState(() {});
|
||||
// },
|
||||
// title: Text(
|
||||
// f,
|
||||
// style: GoogleFonts.getFont(
|
||||
// f,
|
||||
// color: AppColors.of(context).text.withOpacity(.95),
|
||||
// fontStyle: settingsProvider.renamedSubjectsItalics
|
||||
// ? FontStyle.italic
|
||||
// : FontStyle.normal,
|
||||
// ),
|
||||
// ),
|
||||
// trailing: settingsProvider.fontFamily == f
|
||||
// ? Icon(
|
||||
// FeatherIcons.chevronRight,
|
||||
// size: 22.0,
|
||||
// color: AppColors.of(context).text.withOpacity(0.95),
|
||||
// )
|
||||
// : null,
|
||||
// borderRadius: BorderRadius.circular(12.0),
|
||||
// );
|
||||
|
||||
// fTiles.add(widget);
|
||||
// added.add(f);
|
||||
// }
|
||||
|
||||
// fontTiles = fTiles;
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
settingsProvider = Provider.of<SettingsProvider>(context);
|
||||
user = Provider.of<UserProvider>(context);
|
||||
|
||||
// get edited shit
|
||||
editedShit = Provider.of<GradeProvider>(context, listen: false)
|
||||
.grades
|
||||
.where((e) => e.teacher.isRenamed || e.subject.isRenamed)
|
||||
// .map((e) => e.subject)
|
||||
.toSet()
|
||||
.toList()
|
||||
..sort((a, b) => a.subject.name.compareTo(b.subject.name));
|
||||
|
||||
String themeModeText = {
|
||||
ThemeMode.light: "light".i18n,
|
||||
ThemeMode.dark: "dark".i18n,
|
||||
ThemeMode.system: "system".i18n
|
||||
}[settingsProvider.theme] ??
|
||||
"?";
|
||||
|
||||
// build da tilés
|
||||
buildSubjectTiles();
|
||||
// buildFontTiles();
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: _hideContainersController,
|
||||
builder: (context, child) => Opacity(
|
||||
opacity: 1 - _hideContainersController.value,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
title: Text(
|
||||
"personalization".i18n,
|
||||
style: TextStyle(color: AppColors.of(context).text),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// app theme
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
onPressed: () {
|
||||
SettingsHelper.theme(context);
|
||||
setState(() {});
|
||||
},
|
||||
title: Text("theme".i18n),
|
||||
leading: Icon(
|
||||
FeatherIcons.sun,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
trailing: Text(
|
||||
themeModeText,
|
||||
style: const TextStyle(fontSize: 14.0),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// color magic shit
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: false,
|
||||
children: [
|
||||
PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0, right: 14.0),
|
||||
onPressed: () async {
|
||||
await _hideContainersController.forward();
|
||||
SettingsHelper.accentColor(context);
|
||||
setState(() {});
|
||||
_hideContainersController.reset();
|
||||
},
|
||||
title: Text(
|
||||
"color".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.droplet,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
trailing: Container(
|
||||
width: 12.0,
|
||||
height: 12.0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(4.0),
|
||||
),
|
||||
),
|
||||
const MenuPaintList(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(4.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// shadow toggle
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
|
||||
onPressed: () async {
|
||||
settingsProvider.update(
|
||||
shadowEffect: !settingsProvider.shadowEffect);
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
"shadow_effect".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.shadowEffect ? .95 : .25),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.moon,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.shadowEffect ? .95 : .25),
|
||||
),
|
||||
trailing: Switch(
|
||||
onChanged: (v) async {
|
||||
settingsProvider.update(shadowEffect: v);
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
value: settingsProvider.shadowEffect,
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// change subject icons
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
onPressed: () {
|
||||
SettingsHelper.iconPack(context);
|
||||
},
|
||||
title: Text(
|
||||
"icon_pack".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.grid,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
trailing: Text(
|
||||
settingsProvider.iconPack.name.capital(),
|
||||
style: const TextStyle(fontSize: 14.0),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// grade colors
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
onPressed: () {
|
||||
SettingsHelper.gradeColors(context);
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
"grade_colors".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.star,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: List.generate(
|
||||
5,
|
||||
(i) => Container(
|
||||
margin: const EdgeInsets.only(left: 2.0),
|
||||
width: 12.0,
|
||||
height: 12.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: settingsProvider.gradeColors[i],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// rename things
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: false,
|
||||
children: [
|
||||
// rename subjects
|
||||
PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
|
||||
onPressed: () async {
|
||||
settingsProvider.update(
|
||||
renamedSubjectsEnabled:
|
||||
!settingsProvider.renamedSubjectsEnabled);
|
||||
await Provider.of<GradeProvider>(context,
|
||||
listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<TimetableProvider>(context,
|
||||
listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<AbsenceProvider>(context,
|
||||
listen: false)
|
||||
.convertBySettings();
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
"rename_subjects".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.renamedSubjectsEnabled
|
||||
? .95
|
||||
: .25),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
Icons.school_outlined,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.renamedSubjectsEnabled
|
||||
? .95
|
||||
: .25),
|
||||
),
|
||||
trailing: Switch(
|
||||
onChanged: (v) async {
|
||||
settingsProvider.update(renamedSubjectsEnabled: v);
|
||||
await Provider.of<GradeProvider>(context,
|
||||
listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<TimetableProvider>(context,
|
||||
listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<AbsenceProvider>(context,
|
||||
listen: false)
|
||||
.convertBySettings();
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
value: settingsProvider.renamedSubjectsEnabled,
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(4.0),
|
||||
),
|
||||
),
|
||||
// rename teachers
|
||||
PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
|
||||
onPressed: () async {
|
||||
settingsProvider.update(
|
||||
renamedTeachersEnabled:
|
||||
!settingsProvider.renamedTeachersEnabled);
|
||||
await Provider.of<GradeProvider>(context,
|
||||
listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<TimetableProvider>(context,
|
||||
listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<AbsenceProvider>(context,
|
||||
listen: false)
|
||||
.convertBySettings();
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
"rename_teachers".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.renamedTeachersEnabled
|
||||
? .95
|
||||
: .25),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.user,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.renamedTeachersEnabled
|
||||
? .95
|
||||
: .25),
|
||||
),
|
||||
trailing: Switch(
|
||||
onChanged: (v) async {
|
||||
settingsProvider.update(renamedTeachersEnabled: v);
|
||||
await Provider.of<GradeProvider>(context,
|
||||
listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<TimetableProvider>(context,
|
||||
listen: false)
|
||||
.convertBySettings();
|
||||
await Provider.of<AbsenceProvider>(context,
|
||||
listen: false)
|
||||
.convertBySettings();
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
value: settingsProvider.renamedTeachersEnabled,
|
||||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(4.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// live activity color
|
||||
if (Platform.isIOS)
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 9.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
onPressed: () {
|
||||
if (!Provider.of<PremiumProvider>(context,
|
||||
listen: false)
|
||||
.hasScope(PremiumScopes.liveActivityColor)) {
|
||||
PremiumLockedFeatureUpsell.show(
|
||||
context: context,
|
||||
feature: PremiumFeature.liveActivity,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsHelper.liveActivityColor(context);
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
"live_activity_color".i18n,
|
||||
style: TextStyle(
|
||||
color:
|
||||
AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.activity,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
trailing: Container(
|
||||
margin: const EdgeInsets.only(left: 2.0),
|
||||
width: 12.0,
|
||||
height: 12.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: settingsProvider.liveActivityColor,
|
||||
),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// SplittedPanel(
|
||||
// padding: const EdgeInsets.only(top: 9.0),
|
||||
// cardPadding: const EdgeInsets.all(4.0),
|
||||
// isSeparated: true,
|
||||
// children: [],
|
||||
// ),
|
||||
if (settingsProvider.renamedSubjectsEnabled ||
|
||||
settingsProvider.renamedTeachersEnabled)
|
||||
Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 18.0,
|
||||
),
|
||||
SplittedPanel(
|
||||
title: Text('subjects'.i18n),
|
||||
padding: EdgeInsets.zero,
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
children: tiles,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 9.0,
|
||||
),
|
||||
SplittedPanel(
|
||||
padding: EdgeInsets.zero,
|
||||
cardPadding: const EdgeInsets.all(3.0),
|
||||
hasBorder: true,
|
||||
isTransparent: true,
|
||||
children: [
|
||||
DropdownButton2(
|
||||
items: otherShit
|
||||
.map((item) => DropdownMenuItem<String>(
|
||||
value: item.subject.id,
|
||||
child: Text(
|
||||
item.subject.name,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.of(context).text,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (String? v) async {
|
||||
Navigator.of(context, rootNavigator: true).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => EditSubjectScreen(
|
||||
subject: otherShit
|
||||
.firstWhere((e) => e.subject.id == v)
|
||||
.subject,
|
||||
teacher: otherShit
|
||||
.firstWhere((e) => e.subject.id == v)
|
||||
.teacher,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
setState(() {});
|
||||
// _subjectName.text = "";
|
||||
},
|
||||
iconSize: 14,
|
||||
iconEnabledColor: AppColors.of(context).text,
|
||||
iconDisabledColor: AppColors.of(context).text,
|
||||
underline: const SizedBox(),
|
||||
itemHeight: 40,
|
||||
itemPadding:
|
||||
const EdgeInsets.only(left: 14, right: 14),
|
||||
buttonWidth: 50,
|
||||
dropdownWidth: 300,
|
||||
dropdownPadding: null,
|
||||
buttonDecoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
dropdownDecoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
dropdownElevation: 8,
|
||||
scrollbarRadius: const Radius.circular(40),
|
||||
scrollbarThickness: 6,
|
||||
scrollbarAlwaysShow: true,
|
||||
offset: const Offset(-10, -10),
|
||||
buttonSplashColor: Colors.transparent,
|
||||
customButton: PanelButton(
|
||||
title: Text(
|
||||
"select_subject".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context)
|
||||
.text
|
||||
.withOpacity(.95),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.plus,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context)
|
||||
.text
|
||||
.withOpacity(.95),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
// custom fonts
|
||||
const SizedBox(
|
||||
height: 18.0,
|
||||
),
|
||||
SplittedPanel(
|
||||
title: Text('fonts'.i18n),
|
||||
padding: EdgeInsets.zero,
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
onPressed: () {
|
||||
SettingsHelper.fontFamily(context);
|
||||
setState(() {});
|
||||
},
|
||||
title: Text(
|
||||
"font_family".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
FeatherIcons.type,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(.95),
|
||||
),
|
||||
trailing: Text(
|
||||
settingsProvider.fontFamily != ''
|
||||
? settingsProvider.fontFamily
|
||||
: 'Montserrat',
|
||||
style: GoogleFonts.getFont(
|
||||
settingsProvider.fontFamily != ''
|
||||
? settingsProvider.fontFamily
|
||||
: 'Montserrat',
|
||||
fontSize: 14.0),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension SettingsLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"general": "General",
|
||||
"personalization": "Personalization",
|
||||
"extras": "Extras",
|
||||
"surprise_grades": "Surprise Grades",
|
||||
"cancel": "Cancel",
|
||||
"done": "Done",
|
||||
"rarity_title": "Rarity Text",
|
||||
// default rarities
|
||||
"common": "Common",
|
||||
"uncommon": "Uncommon",
|
||||
"rare": "Rare",
|
||||
"epic": "Epic",
|
||||
"legendary": "Legendary",
|
||||
},
|
||||
"hu_hu": {
|
||||
"general": "Általános",
|
||||
"personalization": "Személyre szabás",
|
||||
"extras": "Extrák",
|
||||
"surprise_grades": "Meglepetés jegyek",
|
||||
"cancel": "Mégse",
|
||||
"done": "Kész",
|
||||
"rarity_title": "Ritkaság szövege",
|
||||
// default rarities
|
||||
"common": "Gyakori",
|
||||
"uncommon": "Nem gyakori",
|
||||
"rare": "Ritka",
|
||||
"epic": "Epikus",
|
||||
"legendary": "Legendás",
|
||||
},
|
||||
"de_de": {
|
||||
"general": "Allgemeine",
|
||||
"personalization": "Personalisierung",
|
||||
"extras": "Extras",
|
||||
"surprise_grades": "Überraschende Noten",
|
||||
"cancel": "Abbrechen",
|
||||
"done": "Bereit",
|
||||
"rarity_title": "Text zur Seltenheit",
|
||||
// default rarities
|
||||
"common": "Gemeinsam",
|
||||
"uncommon": "Gelegentlich",
|
||||
"rare": "Selten",
|
||||
"epic": "Episch",
|
||||
"legendary": "Legendär",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
996
refilc_mobile_ui/lib/screens/settings/theme_screen.dart
Normal file
996
refilc_mobile_ui/lib/screens/settings/theme_screen.dart
Normal file
@@ -0,0 +1,996 @@
|
||||
// ignore_for_file: use_build_context_synchronously, deprecated_member_use
|
||||
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/models/shared_theme.dart';
|
||||
import 'package:refilc/theme/colors/accent.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc/theme/observer.dart';
|
||||
import 'package:refilc/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:refilc/ui/widgets/message/message_tile.dart';
|
||||
import 'package:refilc_kreta_api/models/grade.dart';
|
||||
import 'package:refilc_kreta_api/models/homework.dart';
|
||||
import 'package:refilc_kreta_api/models/message.dart';
|
||||
import 'package:refilc_mobile_ui/common/action_button.dart';
|
||||
import 'package:refilc_mobile_ui/common/filter_bar.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:refilc_mobile_ui/common/widgets/grade/new_grades.dart';
|
||||
import 'package:refilc_mobile_ui/common/widgets/homework/homework_tile.dart';
|
||||
// import 'package:refilc_plus/models/premium_scopes.dart';
|
||||
// import 'package:refilc_plus/providers/premium_provider.dart';
|
||||
import 'package:refilc_kreta_api/providers/share_provider.dart';
|
||||
import 'package:refilc/ui/flutter_colorpicker/colorpicker.dart';
|
||||
// import 'package:refilc_plus/ui/mobile/premium/upsell.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'theme_screen.i18n.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class PremiumCustomAccentColorSetting extends StatefulWidget {
|
||||
const PremiumCustomAccentColorSetting({super.key});
|
||||
|
||||
@override
|
||||
State<PremiumCustomAccentColorSetting> createState() =>
|
||||
_PremiumCustomAccentColorSettingState();
|
||||
}
|
||||
|
||||
enum CustomColorMode {
|
||||
theme,
|
||||
saved,
|
||||
accent,
|
||||
background,
|
||||
highlight,
|
||||
icon,
|
||||
enterId,
|
||||
}
|
||||
|
||||
class _PremiumCustomAccentColorSettingState
|
||||
extends State<PremiumCustomAccentColorSetting>
|
||||
with TickerProviderStateMixin {
|
||||
late final SettingsProvider settings;
|
||||
late final ShareProvider shareProvider;
|
||||
bool colorSelection = false;
|
||||
bool customColorMenu = false;
|
||||
CustomColorMode colorMode = CustomColorMode.theme;
|
||||
final customColorInput = TextEditingController();
|
||||
final unknownColor = Colors.black;
|
||||
|
||||
late TabController _testTabController;
|
||||
late TabController _colorsTabController;
|
||||
late AnimationController _openAnimController;
|
||||
|
||||
late final Animation<double> backgroundAnimation =
|
||||
Tween<double>(begin: 0, end: 1).animate(
|
||||
CurvedAnimation(
|
||||
parent: _openAnimController,
|
||||
curve: const Interval(0.2, 1.0, curve: Curves.easeInOut),
|
||||
),
|
||||
);
|
||||
|
||||
late final Animation<double> fullPageAnimation =
|
||||
Tween<double>(begin: 0, end: 1).animate(
|
||||
CurvedAnimation(
|
||||
parent: _openAnimController,
|
||||
curve: const Interval(0.0, 0.6, curve: Curves.easeInOut),
|
||||
),
|
||||
);
|
||||
|
||||
late final Animation<double> backContainerAnimation =
|
||||
Tween<double>(begin: 100, end: 0).animate(
|
||||
CurvedAnimation(
|
||||
parent: _openAnimController,
|
||||
curve: const Interval(0.0, 0.9, curve: Curves.easeInOut),
|
||||
),
|
||||
);
|
||||
|
||||
late final Animation<double> backContentAnimation =
|
||||
Tween<double>(begin: 100, end: 0).animate(
|
||||
CurvedAnimation(
|
||||
parent: _openAnimController,
|
||||
curve: const Interval(0.2, 1.0, curve: Curves.easeInOut),
|
||||
),
|
||||
);
|
||||
|
||||
late final Animation<double> backContentScaleAnimation =
|
||||
Tween<double>(begin: 0.8, end: 0.9).animate(
|
||||
CurvedAnimation(
|
||||
parent: _openAnimController,
|
||||
curve: const Interval(0.45, 1.0, curve: Curves.easeInOut),
|
||||
),
|
||||
);
|
||||
|
||||
late final Animation<double> pickerContainerAnimation =
|
||||
Tween<double>(begin: 0, end: 1).animate(
|
||||
CurvedAnimation(
|
||||
parent: _openAnimController,
|
||||
curve: const Interval(0.25, 0.8, curve: Curves.easeInOut),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_colorsTabController = TabController(length: 4, vsync: this);
|
||||
_testTabController = TabController(length: 4, vsync: this);
|
||||
settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
shareProvider = Provider.of<ShareProvider>(context, listen: false);
|
||||
|
||||
_openAnimController = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 750));
|
||||
_openAnimController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_openAnimController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void setTheme(ThemeMode mode, bool store) async {
|
||||
await settings.update(theme: mode, store: store);
|
||||
Provider.of<ThemeModeObserver>(context, listen: false)
|
||||
.changeTheme(mode, updateNavbarColor: false);
|
||||
}
|
||||
|
||||
dynamic getCustomColor() {
|
||||
switch (colorMode) {
|
||||
case CustomColorMode.theme:
|
||||
return accentColorMap[settings.accentColor];
|
||||
case CustomColorMode.saved:
|
||||
return [
|
||||
settings.customBackgroundColor,
|
||||
settings.customHighlightColor,
|
||||
settings.customAccentColor
|
||||
];
|
||||
case CustomColorMode.background:
|
||||
return settings.customBackgroundColor;
|
||||
case CustomColorMode.highlight:
|
||||
return settings.customHighlightColor;
|
||||
case CustomColorMode.accent:
|
||||
return settings.customAccentColor;
|
||||
case CustomColorMode.icon:
|
||||
return settings.customIconColor;
|
||||
case CustomColorMode.enterId:
|
||||
// do nothing here lol
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void updateCustomColor(dynamic v, bool store,
|
||||
{Color? accent, Color? background, Color? panels, Color? icon}) {
|
||||
// reset custom theme id
|
||||
settings.update(
|
||||
currentThemeId: '',
|
||||
currentThemeDisplayName: '',
|
||||
currentThemeCreator: '',
|
||||
store: store,
|
||||
);
|
||||
|
||||
if (colorMode != CustomColorMode.theme) {
|
||||
settings.update(accentColor: AccentColor.custom, store: store);
|
||||
}
|
||||
switch (colorMode) {
|
||||
case CustomColorMode.theme:
|
||||
settings.update(
|
||||
accentColor: accentColorMap.keys.firstWhere(
|
||||
(element) => accentColorMap[element] == v,
|
||||
orElse: () => AccentColor.filc),
|
||||
store: store);
|
||||
settings.update(
|
||||
customBackgroundColor: AppColors.of(context).background,
|
||||
store: store);
|
||||
settings.update(
|
||||
customHighlightColor: AppColors.of(context).highlight,
|
||||
store: store);
|
||||
settings.update(customAccentColor: v, store: store);
|
||||
break;
|
||||
case CustomColorMode.saved:
|
||||
settings.update(customBackgroundColor: v[0], store: store);
|
||||
settings.update(customHighlightColor: v[1], store: store);
|
||||
settings.update(customAccentColor: v[3], store: store);
|
||||
break;
|
||||
case CustomColorMode.background:
|
||||
settings.update(customBackgroundColor: v, store: store);
|
||||
break;
|
||||
case CustomColorMode.highlight:
|
||||
settings.update(customHighlightColor: v, store: store);
|
||||
break;
|
||||
case CustomColorMode.accent:
|
||||
settings.update(customAccentColor: v, store: store);
|
||||
break;
|
||||
case CustomColorMode.icon:
|
||||
settings.update(customIconColor: v, store: store);
|
||||
break;
|
||||
case CustomColorMode.enterId:
|
||||
settings.update(customBackgroundColor: background, store: store);
|
||||
settings.update(customHighlightColor: panels, store: store);
|
||||
settings.update(customAccentColor: accent, store: store);
|
||||
settings.update(customIconColor: icon, store: store);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// bool hasAccess = Provider.of<PremiumProvider>(context)
|
||||
// .hasScope(PremiumScopes.customColors);
|
||||
bool hasAccess = true;
|
||||
bool isBackgroundDifferent = Theme.of(context).colorScheme.background !=
|
||||
AppColors.of(context).background;
|
||||
|
||||
ThemeMode currentTheme = Theme.of(context).brightness == Brightness.light
|
||||
? ThemeMode.light
|
||||
: ThemeMode.dark;
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
Provider.of<ThemeModeObserver>(context, listen: false)
|
||||
.changeTheme(settings.theme, updateNavbarColor: true);
|
||||
return true;
|
||||
},
|
||||
child: AnimatedBuilder(
|
||||
animation: _openAnimController,
|
||||
builder: (context, child) {
|
||||
final backgroundGradientBottomColor = isBackgroundDifferent
|
||||
? Theme.of(context).colorScheme.background
|
||||
: HSVColor.fromColor(Theme.of(context).colorScheme.background)
|
||||
.withValue(currentTheme == ThemeMode.dark
|
||||
? 0.1 * _openAnimController.value
|
||||
: 1.0 - (0.1 * _openAnimController.value))
|
||||
.withAlpha(1.0)
|
||||
.toColor();
|
||||
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: backgroundGradientBottomColor,
|
||||
));
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
stops: const [0.0, 0.75],
|
||||
colors: isBackgroundDifferent
|
||||
? [
|
||||
Theme.of(context).colorScheme.background.withOpacity(1 -
|
||||
((currentTheme == ThemeMode.dark ? 0.65 : 0.25) *
|
||||
backgroundAnimation.value)),
|
||||
backgroundGradientBottomColor,
|
||||
]
|
||||
: [
|
||||
backgroundGradientBottomColor,
|
||||
backgroundGradientBottomColor
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Opacity(
|
||||
opacity: fullPageAnimation.value,
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10.0),
|
||||
child: IconButton(
|
||||
focusColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
onPressed: () async {
|
||||
// ScaffoldMessenger.of(context).showSnackBar(
|
||||
// const SnackBar(
|
||||
// duration: Duration(milliseconds: 1000),
|
||||
// content: Text(
|
||||
// "Hamarosan...",
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0)),
|
||||
title: Text("attention".i18n),
|
||||
content: Text("share_disclaimer".i18n),
|
||||
actions: [
|
||||
ActionButton(
|
||||
label: "understand".i18n,
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
SharedGradeColors gradeColors =
|
||||
await shareProvider
|
||||
.shareCurrentGradeColors(context);
|
||||
SharedTheme theme =
|
||||
await shareProvider.shareCurrentTheme(
|
||||
context,
|
||||
gradeColors: gradeColors,
|
||||
);
|
||||
|
||||
Share.share(
|
||||
theme.id,
|
||||
subject: 'share_subj_theme'.i18n,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
FeatherIcons.share2,
|
||||
size: 22.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
title: Text(
|
||||
"theme_prev".i18n,
|
||||
style: TextStyle(color: AppColors.of(context).text),
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: 1 - backContainerAnimation.value * (1 / 100),
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, backContainerAnimation.value),
|
||||
child: Container(
|
||||
height: double.infinity,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
gradient: LinearGradient(
|
||||
// https://discord.com/channels/1111649116020285532/1153619667848548452
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
stops: const [
|
||||
0.35,
|
||||
0.75
|
||||
],
|
||||
colors: [
|
||||
settings.customBackgroundColor ??
|
||||
Theme.of(context).colorScheme.background,
|
||||
isBackgroundDifferent
|
||||
? HSVColor.fromColor(Theme.of(context)
|
||||
.colorScheme
|
||||
.background)
|
||||
.withSaturation((HSVColor.fromColor(
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.background)
|
||||
.saturation -
|
||||
0.15)
|
||||
.clamp(0.0, 1.0))
|
||||
.toColor()
|
||||
: backgroundGradientBottomColor,
|
||||
]),
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 30, horizontal: 20),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 24.0),
|
||||
child: Opacity(
|
||||
opacity: 1 - backContentAnimation.value * (1 / 100),
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Transform.translate(
|
||||
offset:
|
||||
Offset(0, -24 + backContentAnimation.value),
|
||||
child: Transform.scale(
|
||||
scale: backContentScaleAnimation.value,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32.0,
|
||||
right: 32.0,
|
||||
top: 16.0,
|
||||
bottom: 6.0,
|
||||
),
|
||||
child: FilterBar(
|
||||
items: const [
|
||||
Tab(text: "All"),
|
||||
Tab(text: "Grades"),
|
||||
Tab(text: "Messages"),
|
||||
Tab(text: "Absences"),
|
||||
],
|
||||
controller: _testTabController,
|
||||
padding: EdgeInsets.zero,
|
||||
censored: true,
|
||||
disableFading: true,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 18.0, vertical: 8.0),
|
||||
child: NewGradesSurprise(
|
||||
[
|
||||
Grade.fromJson(
|
||||
{
|
||||
"Uid": "0,Ertekeles",
|
||||
"RogzitesDatuma":
|
||||
"2022-01-01T23:00:00Z",
|
||||
"KeszitesDatuma":
|
||||
"2022-01-01T23:00:00Z",
|
||||
"LattamozasDatuma": null,
|
||||
"Tantargy": {
|
||||
"Uid": "0",
|
||||
"Nev": "reFilc szakirodalom",
|
||||
"Kategoria": {
|
||||
"Uid": "0,_",
|
||||
"Nev": "_",
|
||||
"Leiras": "Nem mondom meg"
|
||||
},
|
||||
"SortIndex": 2
|
||||
},
|
||||
"Tema":
|
||||
"Kupak csomag vásárlás vizsga",
|
||||
"Tipus": {
|
||||
"Uid": "0,_",
|
||||
"Nev": "_",
|
||||
"Leiras":
|
||||
"Évközi jegy/értékelés",
|
||||
},
|
||||
"Mod": {
|
||||
"Uid": "0,_",
|
||||
"Nev": "_",
|
||||
"Leiras": "_ feladat",
|
||||
},
|
||||
"ErtekFajta": {
|
||||
"Uid": "1,Osztalyzat",
|
||||
"Nev": "Osztalyzat",
|
||||
"Leiras":
|
||||
"Elégtelen (1) és Jeles (5) között az öt alapértelmezett érték"
|
||||
},
|
||||
"ErtekeloTanarNeve": "Premium",
|
||||
"Jelleg": "Ertekeles",
|
||||
"SzamErtek": 5,
|
||||
"SzovegesErtek": "Jeles(5)",
|
||||
"SulySzazalekErteke": 100,
|
||||
"SzovegesErtekelesRovidNev":
|
||||
null,
|
||||
"OsztalyCsoport": {"Uid": "0"},
|
||||
"SortIndex": 2
|
||||
},
|
||||
),
|
||||
],
|
||||
censored: true,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24.0, vertical: 6.0),
|
||||
child: Panel(
|
||||
child: GradeTile(
|
||||
Grade.fromJson(
|
||||
{
|
||||
"Uid": "0,Ertekeles",
|
||||
"RogzitesDatuma":
|
||||
"2022-01-01T23:00:00Z",
|
||||
"KeszitesDatuma":
|
||||
"2022-01-01T23:00:00Z",
|
||||
"LattamozasDatuma": null,
|
||||
"Tantargy": {
|
||||
"Uid": "0",
|
||||
"Nev": "reFilc szakosztály",
|
||||
"Kategoria": {
|
||||
"Uid": "0,_",
|
||||
"Nev": "_",
|
||||
"Leiras": "Nem mondom meg"
|
||||
},
|
||||
"SortIndex": 2
|
||||
},
|
||||
"Tema":
|
||||
"Kupak csomag vásárlás vizsga",
|
||||
"Tipus": {
|
||||
"Uid": "0,_",
|
||||
"Nev": "_",
|
||||
"Leiras":
|
||||
"Évközi jegy/értékelés",
|
||||
},
|
||||
"Mod": {
|
||||
"Uid": "0,_",
|
||||
"Nev": "_",
|
||||
"Leiras": "_ feladat",
|
||||
},
|
||||
"ErtekFajta": {
|
||||
"Uid": "1,Osztalyzat",
|
||||
"Nev": "Osztalyzat",
|
||||
"Leiras":
|
||||
"Elégtelen (1) és Jeles (5) között az öt alapértelmezett érték"
|
||||
},
|
||||
"ErtekeloTanarNeve": "Premium",
|
||||
"Jelleg": "Ertekeles",
|
||||
"SzamErtek": 5,
|
||||
"SzovegesErtek": "Jeles(5)",
|
||||
"SulySzazalekErteke": 100,
|
||||
"SzovegesErtekelesRovidNev":
|
||||
null,
|
||||
"OsztalyCsoport": {"Uid": "0"},
|
||||
"SortIndex": 2
|
||||
},
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
censored: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24.0, vertical: 6.0),
|
||||
child: Panel(
|
||||
child: HomeworkTile(
|
||||
Homework.fromJson(
|
||||
{
|
||||
"Uid": "0",
|
||||
"Tantargy": {
|
||||
"Uid": "0",
|
||||
"Nev":
|
||||
"reFilc premium előnyei",
|
||||
"Kategoria": {
|
||||
"Uid": "0,_",
|
||||
"Nev": "_",
|
||||
"Leiras":
|
||||
"reFilc premium előnyei",
|
||||
},
|
||||
"SortIndex": 0
|
||||
},
|
||||
"TantargyNeve":
|
||||
"reFilc premium előnyei",
|
||||
"RogzitoTanarNeve":
|
||||
"Kupak János",
|
||||
"Szoveg":
|
||||
"45 perc filctollal való rajzolás",
|
||||
"FeladasDatuma":
|
||||
"2022-01-01T23:00:00Z",
|
||||
"HataridoDatuma":
|
||||
"2022-01-01T23:00:00Z",
|
||||
"RogzitesIdopontja":
|
||||
"2022-01-01T23:00:00Z",
|
||||
"IsTanarRogzitette": true,
|
||||
"IsTanuloHaziFeladatEnabled":
|
||||
false,
|
||||
"IsMegoldva": false,
|
||||
"IsBeadhato": false,
|
||||
"OsztalyCsoport": {"Uid": "0"},
|
||||
"IsCsatolasEngedelyezes": false
|
||||
},
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
censored: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24.0, vertical: 6.0),
|
||||
child: Panel(
|
||||
child: MessageTile(
|
||||
Message.fromJson(
|
||||
{
|
||||
"azonosito": 0,
|
||||
"isElolvasva": true,
|
||||
"isToroltElem": false,
|
||||
"tipus": {
|
||||
"azonosito": 1,
|
||||
"kod": "BEERKEZETT",
|
||||
"rovidNev":
|
||||
"Beérkezett üzenet",
|
||||
"nev": "Beérkezett üzenet",
|
||||
"leiras": "Beérkezett üzenet"
|
||||
},
|
||||
"uzenet": {
|
||||
"azonosito": 0,
|
||||
"kuldesDatum":
|
||||
"2022-01-01T23:00:00",
|
||||
"feladoNev": "reFilc",
|
||||
"feladoTitulus":
|
||||
"Nagyon magas szintű személy",
|
||||
"szoveg":
|
||||
"<p>Kedves Felhasználó!</p><p><br></p><p>A prémium vásárlásakor kapott filctollal 90%-al több esély van jó jegyek szerzésére.</p>",
|
||||
"targy":
|
||||
"Filctoll használati útmutató",
|
||||
"statusz": {
|
||||
"azonosito": 2,
|
||||
"kod": "KIKULDVE",
|
||||
"rovidNev": "Kiküldve",
|
||||
"nev": "Kiküldve",
|
||||
"leiras": "Kiküldve"
|
||||
},
|
||||
"cimzettLista": [
|
||||
{
|
||||
"azonosito": 0,
|
||||
"kretaAzonosito": 0,
|
||||
"nev": "Tinta Józsi",
|
||||
"tipus": {
|
||||
"azonosito": 0,
|
||||
"kod": "TANULO",
|
||||
"rovidNev": "Tanuló",
|
||||
"nev": "Tanuló",
|
||||
"leiras": "Tanuló"
|
||||
}
|
||||
},
|
||||
],
|
||||
"csatolmanyok": [
|
||||
{
|
||||
"azonosito": 0,
|
||||
"fajlNev": "Filctoll.doc"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
),
|
||||
censored: true,
|
||||
padding: const EdgeInsets.only(
|
||||
right: 6.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Wrap(
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: pickerContainerAnimation.value,
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: backgroundGradientBottomColor,
|
||||
offset: const Offset(0, -8),
|
||||
blurRadius: 16,
|
||||
spreadRadius: 18,
|
||||
),
|
||||
],
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
stops: const [
|
||||
0.0,
|
||||
0.175
|
||||
],
|
||||
colors: [
|
||||
backgroundGradientBottomColor,
|
||||
backgroundGradientBottomColor,
|
||||
]),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0),
|
||||
child: FilterBar(
|
||||
items: [
|
||||
ColorTab(
|
||||
color: accentColorMap[
|
||||
settings.accentColor] ??
|
||||
unknownColor,
|
||||
tab: Tab(
|
||||
text: "colorpicker_presets"
|
||||
.i18n)),
|
||||
// ColorTab(
|
||||
// color: unknownColor,
|
||||
// tab: Tab(text: "enter_id".i18n)),
|
||||
/*ColorTab(
|
||||
color:
|
||||
settings.customAccentColor ??
|
||||
unknownColor,
|
||||
tab: Tab(
|
||||
text: "colorpicker_saved"
|
||||
.i18n)),*/
|
||||
ColorTab(
|
||||
unlocked: hasAccess,
|
||||
color: settings
|
||||
.customBackgroundColor ??
|
||||
unknownColor,
|
||||
tab: Tab(
|
||||
text: "colorpicker_background"
|
||||
.i18n)),
|
||||
ColorTab(
|
||||
unlocked: hasAccess,
|
||||
color: settings
|
||||
.customHighlightColor ??
|
||||
unknownColor,
|
||||
tab: Tab(
|
||||
text: "colorpicker_panels"
|
||||
.i18n)),
|
||||
ColorTab(
|
||||
unlocked: hasAccess,
|
||||
color:
|
||||
settings.customAccentColor ??
|
||||
unknownColor,
|
||||
tab: Tab(
|
||||
text: "colorpicker_accent"
|
||||
.i18n)),
|
||||
// ColorTab(
|
||||
// unlocked: hasAccess,
|
||||
// color: settings.customIconColor ??
|
||||
// unknownColor,
|
||||
// tab: Tab(
|
||||
// text:
|
||||
// "colorpicker_icon".i18n)),
|
||||
],
|
||||
onTap: (index) {
|
||||
// if (!hasAccess) {
|
||||
// index = 0;
|
||||
// _colorsTabController.animateTo(0,
|
||||
// duration: Duration.zero);
|
||||
|
||||
// PremiumLockedFeatureUpsell.show(
|
||||
// context: context,
|
||||
// feature: PremiumFeature
|
||||
// .customcolors);
|
||||
// }
|
||||
|
||||
switch (index) {
|
||||
case 0:
|
||||
setState(() {
|
||||
colorMode =
|
||||
CustomColorMode.theme;
|
||||
});
|
||||
break;
|
||||
// case 1:
|
||||
// setState(() {
|
||||
// colorMode =
|
||||
// CustomColorMode.enterId;
|
||||
// });
|
||||
// break;
|
||||
/*case 1:
|
||||
setState(() {
|
||||
colorMode =
|
||||
CustomColorMode.saved;
|
||||
});
|
||||
break;*/
|
||||
case 1:
|
||||
setState(() {
|
||||
colorMode =
|
||||
CustomColorMode.background;
|
||||
});
|
||||
break;
|
||||
case 2:
|
||||
setState(() {
|
||||
colorMode =
|
||||
CustomColorMode.highlight;
|
||||
});
|
||||
break;
|
||||
case 3:
|
||||
setState(() {
|
||||
colorMode =
|
||||
CustomColorMode.accent;
|
||||
});
|
||||
break;
|
||||
case 4:
|
||||
setState(() {
|
||||
colorMode =
|
||||
CustomColorMode.icon;
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
controller: _colorsTabController,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0),
|
||||
child: SafeArea(
|
||||
child: FilcColorPicker(
|
||||
colorMode: colorMode,
|
||||
pickerColor: colorMode ==
|
||||
CustomColorMode.accent
|
||||
? settings.customAccentColor ??
|
||||
unknownColor
|
||||
: colorMode ==
|
||||
CustomColorMode.background
|
||||
? settings
|
||||
.customBackgroundColor ??
|
||||
unknownColor
|
||||
: colorMode ==
|
||||
CustomColorMode.theme
|
||||
? (accentColorMap[settings
|
||||
.accentColor] ??
|
||||
AppColors.of(context)
|
||||
.text) // idk what else
|
||||
: colorMode ==
|
||||
CustomColorMode
|
||||
.highlight
|
||||
? settings
|
||||
.customHighlightColor ??
|
||||
unknownColor
|
||||
: settings
|
||||
.customIconColor ??
|
||||
unknownColor,
|
||||
onColorChanged: (c) {
|
||||
setState(() {
|
||||
updateCustomColor(c, false);
|
||||
});
|
||||
setTheme(settings.theme, false);
|
||||
},
|
||||
onColorChangeEnd: (c, {adaptive}) {
|
||||
setState(() {
|
||||
if (adaptive == true) {
|
||||
settings.update(
|
||||
accentColor:
|
||||
AccentColor.adaptive);
|
||||
settings.update(
|
||||
customBackgroundColor:
|
||||
AppColors.of(context)
|
||||
.background,
|
||||
store: true);
|
||||
settings.update(
|
||||
customHighlightColor:
|
||||
AppColors.of(context)
|
||||
.highlight,
|
||||
store: true);
|
||||
settings.update(
|
||||
customIconColor:
|
||||
const Color(0x00000000),
|
||||
store: true);
|
||||
} else {
|
||||
updateCustomColor(c, true);
|
||||
}
|
||||
});
|
||||
setTheme(settings.theme, true);
|
||||
},
|
||||
onThemeIdProvided: (theme) {
|
||||
// changing grade colors
|
||||
List<Color> colors = [
|
||||
theme.gradeColors.oneColor,
|
||||
theme.gradeColors.twoColor,
|
||||
theme.gradeColors.threeColor,
|
||||
theme.gradeColors.fourColor,
|
||||
theme.gradeColors.fiveColor,
|
||||
];
|
||||
settings.update(
|
||||
gradeColors: colors);
|
||||
|
||||
// changing shadow effect
|
||||
settings.update(
|
||||
shadowEffect:
|
||||
theme.shadowEffect);
|
||||
|
||||
// changing theme
|
||||
setState(() {
|
||||
updateCustomColor(
|
||||
null,
|
||||
true,
|
||||
accent: theme.accentColor,
|
||||
background:
|
||||
theme.backgroundColor,
|
||||
panels: theme.panelsColor,
|
||||
icon: theme.iconColor,
|
||||
);
|
||||
});
|
||||
setTheme(settings.theme, true);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ColorTab extends StatelessWidget {
|
||||
const ColorTab(
|
||||
{super.key,
|
||||
required this.tab,
|
||||
required this.color,
|
||||
this.unlocked = true});
|
||||
|
||||
final Tab tab;
|
||||
final Color color;
|
||||
final bool unlocked;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Transform.translate(
|
||||
offset: const Offset(-3, 1),
|
||||
child: unlocked
|
||||
? Container(
|
||||
width: 15,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: color,
|
||||
border: Border.all(color: Colors.black, width: 2.0),
|
||||
),
|
||||
)
|
||||
: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
child: Icon(Icons.lock,
|
||||
color: Color.fromARGB(255, 82, 82, 82), size: 18),
|
||||
),
|
||||
),
|
||||
tab
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PremiumColorPickerItem extends StatelessWidget {
|
||||
const PremiumColorPickerItem(
|
||||
{super.key, required this.label, this.onTap, required this.color});
|
||||
|
||||
final String label;
|
||||
final void Function()? onTap;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: color, shape: BoxShape.circle, border: Border.all()),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
67
refilc_mobile_ui/lib/screens/settings/theme_screen.i18n.dart
Normal file
67
refilc_mobile_ui/lib/screens/settings/theme_screen.i18n.dart
Normal file
@@ -0,0 +1,67 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension SettingsLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"theme_prev": "Preview",
|
||||
"colorpicker_presets": "Presets",
|
||||
"colorpicker_background": "Background",
|
||||
"colorpicker_panels": "Panels",
|
||||
"colorpicker_accent": "Accent",
|
||||
"colorpicker_icon": "Icon",
|
||||
"need_sub": "You need Kupak subscription to use modify this.",
|
||||
"advanced": "Advanced",
|
||||
"enter_id": "Enter ID",
|
||||
"theme_id": "Theme ID...",
|
||||
"theme_not_found": "Theme not found!",
|
||||
"attention": "Attention!",
|
||||
"share_disclaimer":
|
||||
"By sharing the theme, you agree that the nickname you set and all settings of the theme will be shared publicly.",
|
||||
"understand": "I understand",
|
||||
"share_subj_theme": "Share Theme",
|
||||
},
|
||||
"hu_hu": {
|
||||
"theme_prev": "Előnézet",
|
||||
"colorpicker_presets": "Téma",
|
||||
"colorpicker_background": "Háttér",
|
||||
"colorpicker_panels": "Panelek",
|
||||
"colorpicker_accent": "Színtónus",
|
||||
"colorpicker_icon": "Ikon",
|
||||
"need_sub": "A módosításhoz Kupak szintű támogatás szükséges.",
|
||||
"advanced": "Haladó",
|
||||
"enter_id": "ID megadása",
|
||||
"theme_id": "Téma azonosító...",
|
||||
"theme_not_found": "A téma nem található!",
|
||||
"attention": "Figyelem!",
|
||||
"share_disclaimer":
|
||||
"A téma megosztásával elfogadod, hogy az általad beállított becenév és a téma minden beállítása nyilvánosan megosztásra kerüljön.",
|
||||
"understand": "Értem",
|
||||
"share_subj_theme": "Téma Megosztás",
|
||||
},
|
||||
"de_de": {
|
||||
"theme_prev": "Vorschau",
|
||||
"colorpicker_presets": "Farben",
|
||||
"colorpicker_background": "Hintergrund",
|
||||
"colorpicker_panels": "Tafeln",
|
||||
"colorpicker_accent": "Akzent",
|
||||
"colorpicker_icon": "Ikone",
|
||||
"need_sub":
|
||||
"Sie benötigen ein Kupak-Abonnement, um diese Funktion zu ändern.",
|
||||
"advanced": "Fortschrittlich",
|
||||
"enter_id": "Geben Sie die ID ein",
|
||||
"theme_id": "Themen-ID...",
|
||||
"theme_not_found": "Thema nicht gefunden!",
|
||||
"attention": "Achtung!",
|
||||
"share_disclaimer":
|
||||
"Durch das Teilen des Themes erklären Sie sich damit einverstanden, dass der von Ihnen festgelegte Spitzname und alle Einstellungen des Themes öffentlich geteilt werden.",
|
||||
"understand": "Ich verstehe",
|
||||
"share_subj_theme": "Thema Teilen",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
105
refilc_mobile_ui/lib/screens/settings/user/nickname.dart
Normal file
105
refilc_mobile_ui/lib/screens/settings/user/nickname.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
import 'package:refilc/api/providers/database_provider.dart';
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/models/user.dart';
|
||||
import 'package:refilc_mobile_ui/common/bottom_sheet_menu/bottom_sheet_menu_item.dart';
|
||||
// import 'package:refilc_plus/models/premium_scopes.dart';
|
||||
// import 'package:refilc_plus/providers/premium_provider.dart';
|
||||
// import 'package:refilc_plus/ui/mobile/premium/upsell.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class UserMenuNickname extends StatelessWidget {
|
||||
late User u;
|
||||
|
||||
UserMenuNickname(this.u, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BottomSheetMenuItem(
|
||||
onPressed: () {
|
||||
// if (!Provider.of<PremiumProvider>(context, listen: false)
|
||||
// .hasScope(PremiumScopes.nickname)) {
|
||||
// PremiumLockedFeatureUpsell.show(
|
||||
// context: context, feature: PremiumFeature.profile);
|
||||
// return;
|
||||
// }
|
||||
showDialog(
|
||||
context: context, builder: (context) => UserNicknameEditor(u));
|
||||
},
|
||||
icon: const Icon(FeatherIcons.edit2),
|
||||
title: Text("edit_nickname".i18n),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class UserNicknameEditor extends StatefulWidget {
|
||||
late User u;
|
||||
|
||||
UserNicknameEditor(this.u, {super.key});
|
||||
|
||||
@override
|
||||
State<UserNicknameEditor> createState() => _UserNicknameEditorState();
|
||||
}
|
||||
|
||||
class _UserNicknameEditorState extends State<UserNicknameEditor> {
|
||||
final _userName = TextEditingController();
|
||||
late final UserProvider user;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
user = Provider.of<UserProvider>(context, listen: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text("change_username".i18n),
|
||||
content: TextField(
|
||||
controller: _userName,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
label: Text(widget.u.name),
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(FeatherIcons.x),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_userName.text = "";
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
"cancel".i18n,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
"done".i18n,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onPressed: () {
|
||||
widget.u.nickname = _userName.text.trim();
|
||||
Provider.of<DatabaseProvider>(context, listen: false)
|
||||
.store
|
||||
.storeUser(widget.u);
|
||||
Provider.of<UserProvider>(context, listen: false).refresh();
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
236
refilc_mobile_ui/lib/screens/settings/user/profile_pic.dart
Normal file
236
refilc_mobile_ui/lib/screens/settings/user/profile_pic.dart
Normal file
@@ -0,0 +1,236 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:refilc/api/providers/database_provider.dart';
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/models/user.dart';
|
||||
import 'package:refilc_mobile_ui/common/bottom_sheet_menu/bottom_sheet_menu_item.dart';
|
||||
// import 'package:refilc_plus/models/premium_scopes.dart';
|
||||
// import 'package:refilc_plus/providers/premium_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:image_crop/image_crop.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class UserMenuProfilePic extends StatelessWidget {
|
||||
late User u;
|
||||
|
||||
UserMenuProfilePic(this.u, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// if (!Provider.of<PremiumProvider>(context)
|
||||
// .hasScope(PremiumScopes.nickname)) {
|
||||
// return const SizedBox();
|
||||
// }
|
||||
|
||||
return BottomSheetMenuItem(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context, builder: (context) => UserProfilePicEditor(u));
|
||||
},
|
||||
icon: const Icon(FeatherIcons.camera),
|
||||
title: Text("edit_profile_picture".i18n),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class UserProfilePicEditor extends StatefulWidget {
|
||||
late User u;
|
||||
|
||||
UserProfilePicEditor(this.u, {super.key});
|
||||
|
||||
@override
|
||||
State<UserProfilePicEditor> createState() => _UserProfilePicEditorState();
|
||||
}
|
||||
|
||||
class _UserProfilePicEditorState extends State<UserProfilePicEditor> {
|
||||
late final UserProvider user;
|
||||
|
||||
final cropKey = GlobalKey<CropState>();
|
||||
File? _file;
|
||||
File? _sample;
|
||||
File? _lastCropped;
|
||||
|
||||
File? image;
|
||||
Future pickImage() async {
|
||||
try {
|
||||
final image = await ImagePicker().pickImage(source: ImageSource.gallery);
|
||||
if (image == null) return;
|
||||
File imageFile = File(image.path);
|
||||
|
||||
final sample = await ImageCrop.sampleImage(
|
||||
file: imageFile,
|
||||
preferredSize: context.size!.longestSide.ceil(),
|
||||
);
|
||||
|
||||
_sample?.delete();
|
||||
_file?.delete();
|
||||
|
||||
setState(() {
|
||||
_sample = sample;
|
||||
_file = imageFile;
|
||||
});
|
||||
} on PlatformException catch (e) {
|
||||
log('Failed to pick image: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Widget cropImageWidget() {
|
||||
return SizedBox(
|
||||
height: 300,
|
||||
child: Crop.file(
|
||||
_sample!,
|
||||
key: cropKey,
|
||||
aspectRatio: 1.0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget openImageWidget() {
|
||||
return InkWell(
|
||||
customBorder: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
),
|
||||
onTap: () => pickImage(),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
),
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 32.0, horizontal: 8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
"click_here".i18n,
|
||||
style: const TextStyle(
|
||||
fontSize: 22.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"select_profile_picture".i18n,
|
||||
style: const TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _cropImage() async {
|
||||
final scale = cropKey.currentState!.scale;
|
||||
final area = cropKey.currentState!.area;
|
||||
if (area == null || _file == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final sample = await ImageCrop.sampleImage(
|
||||
file: _file!,
|
||||
preferredSize: (2000 / scale).round(),
|
||||
);
|
||||
|
||||
final file = await ImageCrop.cropImage(
|
||||
file: sample,
|
||||
area: area,
|
||||
);
|
||||
|
||||
sample.delete();
|
||||
|
||||
_lastCropped?.delete();
|
||||
_lastCropped = file;
|
||||
|
||||
List<int> imageBytes = await _lastCropped!.readAsBytes();
|
||||
String base64Image = base64Encode(imageBytes);
|
||||
widget.u.picture = base64Image;
|
||||
Provider.of<DatabaseProvider>(context, listen: false)
|
||||
.store
|
||||
.storeUser(widget.u);
|
||||
Provider.of<UserProvider>(context, listen: false).refresh();
|
||||
|
||||
debugPrint('$file');
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
user = Provider.of<UserProvider>(context, listen: false);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_file?.delete();
|
||||
_sample?.delete();
|
||||
_lastCropped?.delete();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(14.0))),
|
||||
contentPadding: const EdgeInsets.only(top: 10.0),
|
||||
title: Text("edit_profile_picture".i18n),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0),
|
||||
child: _sample == null ? openImageWidget() : cropImageWidget(),
|
||||
),
|
||||
if (widget.u.picture != "")
|
||||
TextButton(
|
||||
child: Text(
|
||||
"remove_profile_picture".i18n,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500, color: Colors.red),
|
||||
),
|
||||
onPressed: () {
|
||||
widget.u.picture = "";
|
||||
Provider.of<DatabaseProvider>(context, listen: false)
|
||||
.store
|
||||
.storeUser(widget.u);
|
||||
Provider.of<UserProvider>(context, listen: false).refresh();
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
"cancel".i18n,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
"done".i18n,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onPressed: () async {
|
||||
await _cropImage();
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
231
refilc_mobile_ui/lib/screens/summary/pages/allsum_page.dart
Normal file
231
refilc_mobile_ui/lib/screens/summary/pages/allsum_page.dart
Normal file
@@ -0,0 +1,231 @@
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc_kreta_api/models/absence.dart';
|
||||
import 'package:refilc_kreta_api/models/grade.dart';
|
||||
import 'package:refilc_kreta_api/models/subject.dart';
|
||||
import 'package:refilc_kreta_api/providers/absence_provider.dart';
|
||||
import 'package:refilc_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:refilc_kreta_api/providers/homework_provider.dart';
|
||||
import 'package:refilc_mobile_ui/screens/summary/summary_screen.i18n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class AllSumBody extends StatefulWidget {
|
||||
const AllSumBody({super.key});
|
||||
|
||||
@override
|
||||
AllSumBodyState createState() => AllSumBodyState();
|
||||
}
|
||||
|
||||
class AllSumBodyState extends State<AllSumBody> {
|
||||
late UserProvider user;
|
||||
late GradeProvider gradeProvider;
|
||||
late HomeworkProvider homeworkProvider;
|
||||
late AbsenceProvider absenceProvider;
|
||||
//late TimetableProvider timetableProvider;
|
||||
|
||||
late Map<String, Map<String, dynamic>> things = {};
|
||||
late List<Widget> firstSixTiles = [];
|
||||
late List<Widget> lastSixTiles = [];
|
||||
|
||||
int avgDropValue = 0;
|
||||
bool animation = false;
|
||||
|
||||
List<Grade> getSubjectGrades(GradeSubject subject, {int days = 0}) =>
|
||||
gradeProvider.grades
|
||||
.where((e) =>
|
||||
e.subject == subject &&
|
||||
e.type == GradeType.midYear &&
|
||||
(days == 0 ||
|
||||
e.date
|
||||
.isBefore(DateTime.now().subtract(Duration(days: days)))))
|
||||
.toList();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
gradeProvider = Provider.of<GradeProvider>(context, listen: false);
|
||||
homeworkProvider = Provider.of<HomeworkProvider>(context, listen: false);
|
||||
absenceProvider = Provider.of<AbsenceProvider>(context, listen: false);
|
||||
//timetableProvider = Provider.of<TimetableProvider>(context, listen: false);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
setState(() {
|
||||
animation = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void getGrades() {
|
||||
var allGrades = gradeProvider.grades;
|
||||
var testsGrades = gradeProvider.grades.where((a) => a.value.weight == 100);
|
||||
var closingTestsGrades =
|
||||
gradeProvider.grades.where((a) => a.value.weight >= 200);
|
||||
|
||||
things.addAll({
|
||||
'tests': {'name': 'test'.i18n, 'value': testsGrades.length},
|
||||
'closingTests': {
|
||||
'name': 'closingtest'.i18n,
|
||||
'value': closingTestsGrades.length
|
||||
},
|
||||
'grades': {'name': 'grade'.i18n, 'value': allGrades.length}
|
||||
});
|
||||
}
|
||||
|
||||
void getHomework() {
|
||||
var allHomework = homeworkProvider.homework;
|
||||
|
||||
things.addAll({
|
||||
'homework': {'name': 'hw'.i18n, 'value': allHomework.length}
|
||||
});
|
||||
}
|
||||
|
||||
void getSubjects() {
|
||||
var allSubjects = gradeProvider.grades
|
||||
.map((e) => e.subject)
|
||||
.toSet()
|
||||
.toList()
|
||||
..sort((a, b) => a.name.compareTo(b.name));
|
||||
//var totalLessons;
|
||||
var totalLessons = 0;
|
||||
|
||||
things.addAll({
|
||||
'subjects': {'name': 'subject'.i18n, 'value': allSubjects.length},
|
||||
'lessons': {'name': 'lesson'.i18n, 'value': totalLessons}
|
||||
});
|
||||
}
|
||||
|
||||
void getAbsences() {
|
||||
var allAbsences = absenceProvider.absences.where((a) => a.delay == 0);
|
||||
var excusedAbsences = absenceProvider.absences
|
||||
.where((a) => a.state == Justification.excused && a.delay == 0);
|
||||
var unexcusedAbsences = absenceProvider.absences.where((a) =>
|
||||
(a.state == Justification.unexcused ||
|
||||
a.state == Justification.pending) &&
|
||||
a.delay == 0);
|
||||
|
||||
things.addAll({
|
||||
'absences': {'name': 'absence_sum'.i18n, 'value': allAbsences.length},
|
||||
'excusedAbsences': {
|
||||
'name': 'excused'.i18n,
|
||||
'value': excusedAbsences.length
|
||||
},
|
||||
'unexcusedAbsences': {
|
||||
'name': 'unexcused'.i18n,
|
||||
'value': unexcusedAbsences.length
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void getDelays() {
|
||||
var allDelays = absenceProvider.absences.where((a) => a.delay > 0);
|
||||
var delayTimeList = (allDelays.map((a) {
|
||||
return a.delay;
|
||||
}).toList());
|
||||
var totalDelayTime = 0;
|
||||
if (delayTimeList.isNotEmpty) {
|
||||
totalDelayTime = delayTimeList.reduce((a, b) => a + b);
|
||||
}
|
||||
var unexcusedDelays = absenceProvider.absences
|
||||
.where((a) => a.state == Justification.unexcused && a.delay > 0);
|
||||
|
||||
things.addAll({
|
||||
'delays': {'name': 'delay_sum'.i18n, 'value': allDelays.length},
|
||||
'totalDelay': {'name': 'min'.i18n, 'value': totalDelayTime},
|
||||
'unexcusedDelays': {
|
||||
'name': 'unexcused'.i18n,
|
||||
'value': unexcusedDelays.length
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void getEverything() {
|
||||
getGrades();
|
||||
getHomework();
|
||||
getSubjects();
|
||||
getAbsences();
|
||||
getDelays();
|
||||
}
|
||||
|
||||
void generateTiles() {
|
||||
for (var i in things.values) {
|
||||
Widget w = Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
i.values.toList()[1].toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 36.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
i.values.toList()[0],
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 18.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// TO-DO: az orakat es a hazikat szarul keri le, de majd meg lesz csinalva
|
||||
if (firstSixTiles.length < 6) {
|
||||
firstSixTiles.add(w);
|
||||
} else if (lastSixTiles.length < 6) {
|
||||
lastSixTiles.add(w);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
getEverything();
|
||||
generateTiles();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 45,
|
||||
),
|
||||
AnimatedContainer(
|
||||
curve: Curves.easeInOut,
|
||||
duration: const Duration(milliseconds: 420),
|
||||
transform: Matrix4.translationValues(
|
||||
animation ? 0 : MediaQuery.of(context).size.width, 0, 0),
|
||||
height: 250,
|
||||
child: GridView.count(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 0,
|
||||
crossAxisSpacing: 5,
|
||||
children: firstSixTiles,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
AnimatedContainer(
|
||||
curve: Curves.easeInOut,
|
||||
duration: const Duration(milliseconds: 420),
|
||||
transform: Matrix4.translationValues(
|
||||
animation ? 0 : -MediaQuery.of(context).size.width, 0, 0),
|
||||
height: 250,
|
||||
child: GridView.count(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 0,
|
||||
crossAxisSpacing: 5,
|
||||
children: lastSixTiles,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
363
refilc_mobile_ui/lib/screens/summary/pages/grades_page.dart
Normal file
363
refilc_mobile_ui/lib/screens/summary/pages/grades_page.dart
Normal file
@@ -0,0 +1,363 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/helpers/average_helper.dart';
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/ui/widgets/grade/grade_tile.dart';
|
||||
import 'package:refilc/utils/format.dart';
|
||||
import 'package:refilc_kreta_api/models/grade.dart';
|
||||
import 'package:refilc_kreta_api/models/subject.dart';
|
||||
import 'package:refilc_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:refilc_mobile_ui/screens/summary/summary_screen.i18n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:i18n_extension/i18n_widget.dart';
|
||||
|
||||
List<String> faces = [
|
||||
"(·.·)",
|
||||
"(≥o≤)",
|
||||
"(·_·)",
|
||||
"(˚Δ˚)b",
|
||||
"(^-^*)",
|
||||
"(='X'=)",
|
||||
"(>_<)",
|
||||
"(;-;)",
|
||||
"\\(^Д^)/",
|
||||
"\\(o_o)/",
|
||||
];
|
||||
|
||||
class GradesBody extends StatefulWidget {
|
||||
const GradesBody({super.key});
|
||||
|
||||
@override
|
||||
GradesBodyState createState() => GradesBodyState();
|
||||
}
|
||||
|
||||
class GradesBodyState extends State<GradesBody> {
|
||||
late UserProvider user;
|
||||
late GradeProvider gradeProvider;
|
||||
late SettingsProvider settings;
|
||||
|
||||
late double subjectAvg;
|
||||
late double endYearAvg;
|
||||
late String endYearAvgText;
|
||||
|
||||
List<Widget> subjectTiles5 = [];
|
||||
List<Widget> subjectTiles3 = [];
|
||||
List<Widget> subjectTiles1 = [];
|
||||
|
||||
int avgDropValue = 0;
|
||||
bool animation = false;
|
||||
|
||||
List<Grade> getSubjectGrades(GradeSubject subject, {int days = 0}) =>
|
||||
gradeProvider.grades
|
||||
.where((e) =>
|
||||
e.subject == subject &&
|
||||
e.type == GradeType.midYear &&
|
||||
(days == 0 ||
|
||||
e.date
|
||||
.isBefore(DateTime.now().subtract(Duration(days: days)))))
|
||||
.toList();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
gradeProvider = Provider.of<GradeProvider>(context, listen: false);
|
||||
settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
setState(() {
|
||||
animation = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void generateTiles({required int filter}) {
|
||||
List<GradeSubject> subjects = gradeProvider.grades
|
||||
.map((e) => e.subject)
|
||||
.toSet()
|
||||
.toList()
|
||||
..sort((a, b) => a.name.compareTo(b.name));
|
||||
List<Widget> tiles = [];
|
||||
|
||||
Map<GradeSubject, double> subjectAvgs = {};
|
||||
|
||||
var count = 1;
|
||||
|
||||
for (GradeSubject subject in subjects) {
|
||||
List<Grade> subjectGrades = getSubjectGrades(subject);
|
||||
|
||||
double avg = AverageHelper.averageEvals(subjectGrades);
|
||||
if (avg != 0) subjectAvgs[subject] = avg;
|
||||
|
||||
Widget widget = AnimatedContainer(
|
||||
curve: Curves.easeInOut,
|
||||
duration: Duration(milliseconds: 300 + (count * 120)),
|
||||
transform: Matrix4.translationValues(
|
||||
animation ? 0 : MediaQuery.of(context).size.width, 0, 0),
|
||||
child: Row(
|
||||
children: [
|
||||
GradeValueWidget(
|
||||
GradeValue(avg.round(), '', '', 100),
|
||||
fill: true,
|
||||
size: 28.0,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
subject.renamedTo ?? subject.name.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20.0,
|
||||
color: Colors.white.withOpacity(0.98),
|
||||
fontStyle: settings.renamedSubjectsItalics && subject.isRenamed
|
||||
? FontStyle.italic
|
||||
: null,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (avg.round() == filter) {
|
||||
tiles.add(widget);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (tiles.isEmpty) {
|
||||
int index = Random(DateTime.now().minute).nextInt(faces.length);
|
||||
Widget faceWidget = Center(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
text: faces[index],
|
||||
style: const TextStyle(
|
||||
fontSize: 32.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "\n${'no_grades'.i18n}",
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
height: 2.0,
|
||||
color: Colors.white.withOpacity(0.5)),
|
||||
),
|
||||
],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
tiles.insert(0, faceWidget);
|
||||
}
|
||||
|
||||
subjectAvg = subjectAvgs.isNotEmpty
|
||||
? subjectAvgs.values.fold(0.0, (double a, double b) => a + b) /
|
||||
subjectAvgs.length
|
||||
: 0.0;
|
||||
|
||||
List<Grade> endYearGrades = gradeProvider.grades
|
||||
.where((grade) => grade.type == GradeType.endYear)
|
||||
.toList();
|
||||
endYearAvg = AverageHelper.averageEvals(endYearGrades, finalAvg: true);
|
||||
endYearAvgText = endYearAvg.toStringAsFixed(1);
|
||||
if (I18n.of(context).locale.languageCode != "en") {
|
||||
endYearAvgText = endYearAvgText.replaceAll(".", ",");
|
||||
}
|
||||
|
||||
if (filter == 5) {
|
||||
subjectTiles5 = List.castFrom(tiles);
|
||||
if (subjectTiles5.length > 4) {
|
||||
subjectTiles5.length = 4;
|
||||
}
|
||||
} else if (filter == 3) {
|
||||
subjectTiles3 = List.castFrom(tiles);
|
||||
if (subjectTiles3.length > 3) {
|
||||
subjectTiles3.length = 3;
|
||||
}
|
||||
} else if (filter == 1) {
|
||||
subjectTiles1 = List.castFrom(tiles);
|
||||
if (subjectTiles1.length > 2) {
|
||||
subjectTiles1.length = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void getGrades() {
|
||||
generateTiles(filter: 5);
|
||||
generateTiles(filter: 3);
|
||||
generateTiles(filter: 1);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
user = Provider.of<UserProvider>(context);
|
||||
settings = Provider.of<SettingsProvider>(context);
|
||||
|
||||
getGrades();
|
||||
|
||||
return Expanded(
|
||||
child: ListView(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: ((100 * subjectTiles5.length) /
|
||||
(subjectTiles5[0].runtimeType == AnimatedContainer
|
||||
? 1.95
|
||||
: 1.2))
|
||||
.toDouble(),
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.only(left: 5),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: max(subjectTiles5.length, 1),
|
||||
itemBuilder: (context, index) {
|
||||
if (subjectTiles5.isNotEmpty) {
|
||||
EdgeInsetsGeometry panelPadding =
|
||||
const EdgeInsets.symmetric(horizontal: 24.0);
|
||||
|
||||
if (subjectTiles5[index].runtimeType == AnimatedContainer) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: subjectTiles5[index]);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: panelPadding, child: subjectTiles5[index]);
|
||||
}
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12.0),
|
||||
Text(
|
||||
'tryagain'.i18n,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12.0),
|
||||
SizedBox(
|
||||
height: ((100 * subjectTiles3.length) /
|
||||
(subjectTiles3[0].runtimeType == AnimatedContainer
|
||||
? 1.95
|
||||
: 1.2))
|
||||
.toDouble(),
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.only(left: 5),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: max(subjectTiles3.length, 1),
|
||||
itemBuilder: (context, index) {
|
||||
if (subjectTiles3.isNotEmpty) {
|
||||
EdgeInsetsGeometry panelPadding =
|
||||
const EdgeInsets.symmetric(horizontal: 24.0);
|
||||
|
||||
if (subjectTiles3[index].runtimeType == AnimatedContainer) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: subjectTiles3[index]);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: panelPadding, child: subjectTiles3[index]);
|
||||
}
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12.0),
|
||||
Text(
|
||||
'oops'.i18n,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12.0),
|
||||
SizedBox(
|
||||
height: ((100 * subjectTiles1.length) /
|
||||
(subjectTiles1[0].runtimeType == AnimatedContainer
|
||||
? 1.95
|
||||
: 1.2))
|
||||
.toDouble(),
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.only(left: 5),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: max(subjectTiles1.length, 1),
|
||||
itemBuilder: (context, index) {
|
||||
if (subjectTiles1.isNotEmpty) {
|
||||
EdgeInsetsGeometry panelPadding =
|
||||
const EdgeInsets.symmetric(horizontal: 24.0);
|
||||
|
||||
if (subjectTiles1[index].runtimeType == AnimatedContainer) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: subjectTiles1[index]);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: panelPadding, child: subjectTiles1[index]);
|
||||
}
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30.0),
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'endyear_avg'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 10.0),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0, vertical: 4.0),
|
||||
decoration: BoxDecoration(
|
||||
color: gradeColor(context: context, value: endYearAvg)
|
||||
.withOpacity(.2),
|
||||
border: Border.all(
|
||||
color: (gradeColor(context: context, value: endYearAvg))
|
||||
.withOpacity(0.0),
|
||||
width: 2.0,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
child: AutoSizeText.rich(
|
||||
TextSpan(
|
||||
text: endYearAvgText,
|
||||
),
|
||||
maxLines: 1,
|
||||
minFontSize: 5,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: gradeColor(context: context, value: endYearAvg),
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 32.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
321
refilc_mobile_ui/lib/screens/summary/pages/lessons_page.dart
Normal file
321
refilc_mobile_ui/lib/screens/summary/pages/lessons_page.dart
Normal file
@@ -0,0 +1,321 @@
|
||||
// ignore_for_file: no_leading_underscores_for_local_identifiers
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/helpers/subject.dart';
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/utils/format.dart';
|
||||
import 'package:refilc_kreta_api/models/absence.dart';
|
||||
import 'package:refilc_kreta_api/models/lesson.dart';
|
||||
import 'package:refilc_kreta_api/models/subject.dart';
|
||||
import 'package:refilc_kreta_api/models/week.dart';
|
||||
import 'package:refilc_kreta_api/providers/absence_provider.dart';
|
||||
import 'package:refilc_kreta_api/providers/timetable_provider.dart';
|
||||
import 'package:refilc_mobile_ui/screens/summary/summary_screen.i18n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
List<String> faces = [
|
||||
"(·.·)",
|
||||
"(≥o≤)",
|
||||
"(·_·)",
|
||||
"(˚Δ˚)b",
|
||||
"(^-^*)",
|
||||
"(='X'=)",
|
||||
"(>_<)",
|
||||
"(;-;)",
|
||||
"\\(^Д^)/",
|
||||
"\\(o_o)/",
|
||||
];
|
||||
|
||||
class SubjectAbsence {
|
||||
GradeSubject subject;
|
||||
List<Absence> absences;
|
||||
double percentage;
|
||||
|
||||
SubjectAbsence(
|
||||
{required this.subject, this.absences = const [], this.percentage = 0.0});
|
||||
}
|
||||
|
||||
class LessonsBody extends StatefulWidget {
|
||||
const LessonsBody({super.key});
|
||||
|
||||
@override
|
||||
LessonsBodyState createState() => LessonsBodyState();
|
||||
}
|
||||
|
||||
class LessonsBodyState extends State<LessonsBody> {
|
||||
late UserProvider user;
|
||||
late AbsenceProvider absenceProvider;
|
||||
late SettingsProvider settingsProvider;
|
||||
late TimetableProvider timetableProvider;
|
||||
|
||||
late List<SubjectAbsence> absences = [];
|
||||
late List<Widget> lessons = [];
|
||||
late List<Absence> delays = [];
|
||||
final Map<GradeSubject, Lesson> _lessonCount = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
absenceProvider = Provider.of<AbsenceProvider>(context, listen: false);
|
||||
settingsProvider = Provider.of<SettingsProvider>(context, listen: false);
|
||||
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<GradeSubject, 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));
|
||||
}
|
||||
|
||||
void getAndSortDelays() {
|
||||
delays = absenceProvider.absences;
|
||||
delays.sort((a, b) => -a.delay.compareTo(b.delay));
|
||||
}
|
||||
|
||||
void generateTiles() {
|
||||
Widget leastAbsent = Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
SubjectIcon.resolveVariant(
|
||||
subject: absences.last.subject, context: context),
|
||||
color: Colors.white,
|
||||
size: 64,
|
||||
),
|
||||
Text(
|
||||
absences.last.subject.renamedTo ??
|
||||
absences.last.subject.name.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 36.0,
|
||||
fontStyle: absences.last.subject.isRenamed &&
|
||||
settingsProvider.renamedSubjectsItalics
|
||||
? FontStyle.italic
|
||||
: null,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'absence'.i18n.fill([absences.last.absences.length]),
|
||||
style: const TextStyle(
|
||||
fontSize: 18.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
if (absences.last.absences.isNotEmpty) {
|
||||
lessons.add(leastAbsent);
|
||||
} else {
|
||||
lessons.add(buildFaceWidget());
|
||||
}
|
||||
|
||||
Widget mostAbsent = Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
SubjectIcon.resolveVariant(
|
||||
subject: absences.first.subject, context: context),
|
||||
color: Colors.white,
|
||||
size: 64,
|
||||
),
|
||||
Text(
|
||||
absences.first.subject.renamedTo ??
|
||||
absences.first.subject.name.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 36.0,
|
||||
fontStyle: absences.first.subject.isRenamed &&
|
||||
settingsProvider.renamedSubjectsItalics
|
||||
? FontStyle.italic
|
||||
: null,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'absence'.i18n.fill([absences.first.absences.length]),
|
||||
style: const TextStyle(
|
||||
fontSize: 18.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
if (absences.first.absences.isNotEmpty) {
|
||||
lessons.add(mostAbsent);
|
||||
} else {
|
||||
lessons.add(buildFaceWidget());
|
||||
}
|
||||
|
||||
Widget mostDelays = Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
SubjectIcon.resolveVariant(
|
||||
subject: delays.first.subject, context: context),
|
||||
color: Colors.white,
|
||||
size: 64,
|
||||
),
|
||||
Text(
|
||||
delays.first.subject.renamedTo ??
|
||||
delays.first.subject.name.capital(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 36.0,
|
||||
fontStyle: delays.first.subject.isRenamed &&
|
||||
settingsProvider.renamedSubjectsItalics
|
||||
? FontStyle.italic
|
||||
: null,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'delay'.i18n.fill([delays.first.delay]),
|
||||
style: const TextStyle(
|
||||
fontSize: 18.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
if (delays.first.delay != 0) {
|
||||
lessons.add(mostDelays);
|
||||
} else {
|
||||
lessons.add(buildFaceWidget());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
buildSubjectAbsences();
|
||||
getAndSortDelays();
|
||||
generateTiles();
|
||||
|
||||
return Expanded(
|
||||
child: ListView(
|
||||
children: [
|
||||
lessons[0],
|
||||
const SizedBox(height: 18.0),
|
||||
Text(
|
||||
'dontfelt'.i18n,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18.0),
|
||||
lessons[1],
|
||||
const SizedBox(height: 18.0),
|
||||
Text(
|
||||
'youlate'.i18n,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18.0),
|
||||
lessons[2],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildFaceWidget() {
|
||||
int index = Random(DateTime.now().minute).nextInt(faces.length);
|
||||
return Center(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
text: faces[index],
|
||||
style: const TextStyle(
|
||||
fontSize: 32.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "\n${'no_lesson'.i18n}",
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
height: 2.0,
|
||||
color: Colors.white.withOpacity(0.5)),
|
||||
),
|
||||
],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
128
refilc_mobile_ui/lib/screens/summary/pages/personality_page.dart
Normal file
128
refilc_mobile_ui/lib/screens/summary/pages/personality_page.dart
Normal file
@@ -0,0 +1,128 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc_mobile_ui/common/personality_card/empty_card.dart';
|
||||
import 'package:refilc_mobile_ui/common/personality_card/personality_card.dart';
|
||||
import 'package:refilc_mobile_ui/screens/summary/summary_screen.i18n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:screenshot/screenshot.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
||||
|
||||
class PersonalityBody extends StatefulWidget {
|
||||
const PersonalityBody({super.key});
|
||||
|
||||
@override
|
||||
PersonalityBodyState createState() => PersonalityBodyState();
|
||||
}
|
||||
|
||||
class PersonalityBodyState extends State<PersonalityBody> {
|
||||
late UserProvider user;
|
||||
|
||||
bool isRevealed = false;
|
||||
|
||||
ScreenshotController screenshotController = ScreenshotController();
|
||||
|
||||
sharePersonality() async {
|
||||
await screenshotController.capture().then((image) async {
|
||||
if (image != null) {
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
if (await File('${directory.path}/refilc_personality.png').exists()) {
|
||||
await File('${directory.path}/refilc_personality.png').delete();
|
||||
}
|
||||
final imagePath =
|
||||
await File('${directory.path}/refilc_personality.png').create();
|
||||
await imagePath.writeAsBytes(image);
|
||||
|
||||
await Share.shareXFiles([XFile(imagePath.path)]);
|
||||
}
|
||||
}).catchError((err) {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
savePersonality() async {
|
||||
await screenshotController.capture().then((image) async {
|
||||
if (image != null) {
|
||||
await ImageGallerySaver.saveImage(image, name: 'refilc_personality');
|
||||
}
|
||||
}).catchError((err) {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
user = Provider.of<UserProvider>(context);
|
||||
|
||||
return Expanded(
|
||||
child: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 30),
|
||||
AnimatedCrossFade(
|
||||
duration: const Duration(milliseconds: 1000),
|
||||
sizeCurve: Curves.easeInToLinear,
|
||||
firstChild: Screenshot(
|
||||
controller: screenshotController,
|
||||
child: PersonalityCard(user: user),
|
||||
),
|
||||
secondChild: GestureDetector(
|
||||
onTap: () => setState(() {
|
||||
isRevealed = true;
|
||||
}),
|
||||
child: EmptyCard(text: 'click_reveal'.i18n),
|
||||
),
|
||||
crossFadeState: isRevealed
|
||||
? CrossFadeState.showFirst
|
||||
: CrossFadeState.showSecond,
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
if (isRevealed)
|
||||
Center(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await sharePersonality();
|
||||
},
|
||||
icon: const Icon(
|
||||
FeatherIcons.share,
|
||||
color: Colors.white,
|
||||
size: 30,
|
||||
),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all(
|
||||
Colors.white.withOpacity(0.2)),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await savePersonality();
|
||||
},
|
||||
icon: const Icon(
|
||||
FeatherIcons.bookmark,
|
||||
color: Colors.white,
|
||||
size: 30,
|
||||
),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all(
|
||||
Colors.white.withOpacity(0.2)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 60),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
98
refilc_mobile_ui/lib/screens/summary/pages/start_page.dart
Normal file
98
refilc_mobile_ui/lib/screens/summary/pages/start_page.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc_kreta_api/providers/grade_provider.dart';
|
||||
import 'package:refilc_mobile_ui/screens/summary/summary_screen.dart';
|
||||
import 'package:refilc_mobile_ui/screens/summary/summary_screen.i18n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wtf_sliding_sheet/wtf_sliding_sheet.dart';
|
||||
|
||||
class StartBody extends StatefulWidget {
|
||||
const StartBody({super.key});
|
||||
|
||||
@override
|
||||
StartBodyState createState() => StartBodyState();
|
||||
}
|
||||
|
||||
class StartBodyState extends State<StartBody> {
|
||||
late UserProvider user;
|
||||
late GradeProvider gradeProvider;
|
||||
late SettingsProvider settings;
|
||||
|
||||
late String firstName;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
gradeProvider = Provider.of<GradeProvider>(context, listen: false);
|
||||
settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SizedBox(height: 40.0),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
showSlidingBottomSheet(
|
||||
context,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => SlidingSheetDialog(
|
||||
color: Colors.black.withOpacity(0.99),
|
||||
duration: const Duration(milliseconds: 400),
|
||||
scrollSpec: const ScrollSpec.bouncingScroll(),
|
||||
snapSpec: const SnapSpec(
|
||||
snap: true,
|
||||
snappings: [1.0],
|
||||
initialSnap: 1.0,
|
||||
positioning: SnapPositioning.relativeToAvailableSpace,
|
||||
),
|
||||
minHeight: MediaQuery.of(context).size.height,
|
||||
cornerRadius: 16,
|
||||
cornerRadiusOnFullscreen: 0,
|
||||
builder: (context, state) => const Material(
|
||||
color: Colors.black,
|
||||
child: SummaryScreen(
|
||||
currentPage: 'grades',
|
||||
isBottomSheet: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
FeatherIcons.arrowRight,
|
||||
size: 145,
|
||||
color: Colors.white,
|
||||
grade: 0.001,
|
||||
weight: 0.001,
|
||||
),
|
||||
Text(
|
||||
'start'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 16.0,
|
||||
color: Colors.white.withOpacity(0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 169.69),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
222
refilc_mobile_ui/lib/screens/summary/summary_screen.dart
Normal file
222
refilc_mobile_ui/lib/screens/summary/summary_screen.dart
Normal file
@@ -0,0 +1,222 @@
|
||||
import 'package:confetti/confetti.dart';
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wtf_sliding_sheet/wtf_sliding_sheet.dart';
|
||||
import 'summary_screen.i18n.dart';
|
||||
|
||||
import 'pages/allsum_page.dart';
|
||||
import 'pages/start_page.dart';
|
||||
import 'pages/grades_page.dart';
|
||||
import 'pages/lessons_page.dart';
|
||||
import 'pages/personality_page.dart';
|
||||
|
||||
class SummaryScreen extends StatefulWidget {
|
||||
final String currentPage;
|
||||
final bool isBottomSheet;
|
||||
|
||||
const SummaryScreen({
|
||||
super.key,
|
||||
this.currentPage = 'personality',
|
||||
this.isBottomSheet = false,
|
||||
});
|
||||
|
||||
@override
|
||||
SummaryScreenState createState() => SummaryScreenState();
|
||||
|
||||
static show(
|
||||
{required BuildContext context,
|
||||
String currentPage = 'personality'}) =>
|
||||
Navigator.of(context, rootNavigator: true).push(CupertinoPageRoute(
|
||||
builder: (context) => SummaryScreen(currentPage: currentPage)));
|
||||
}
|
||||
|
||||
class SummaryScreenState extends State<SummaryScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late UserProvider user;
|
||||
late SettingsProvider settings;
|
||||
|
||||
ConfettiController? _confettiController;
|
||||
|
||||
late String firstName;
|
||||
|
||||
final LinearGradient _backgroundGradient = const LinearGradient(
|
||||
colors: [
|
||||
Color(0xff1d56ac),
|
||||
Color(0xff170a3d),
|
||||
],
|
||||
begin: Alignment(-0.8, -1.0),
|
||||
end: Alignment(0.8, 1.0),
|
||||
stops: [-1.0, 1.0],
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_confettiController?.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
user = Provider.of<UserProvider>(context);
|
||||
settings = Provider.of<SettingsProvider>(context);
|
||||
|
||||
List<String> nameParts = user.displayName?.split(" ") ?? ["?"];
|
||||
if (!settings.presentationMode) {
|
||||
firstName = nameParts.length > 1 ? nameParts[1] : nameParts[0];
|
||||
} else {
|
||||
firstName = "János";
|
||||
}
|
||||
|
||||
return widget.isBottomSheet
|
||||
? buildContainer()
|
||||
: Scaffold(
|
||||
body: buildContainer(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildContainer() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(gradient: _backgroundGradient),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(gradient: _backgroundGradient),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24.0,
|
||||
right: 24.0,
|
||||
top: 15.0,
|
||||
bottom: 40.0,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: widget.currentPage == 'start'
|
||||
? CrossAxisAlignment.center
|
||||
: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: widget.currentPage == 'start'
|
||||
? MainAxisAlignment.spaceBetween
|
||||
: MainAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'greeting'.i18n.fill([firstName]),
|
||||
textAlign: TextAlign.left,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 26.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
widget.currentPage == 'start'
|
||||
? 'title_start'.i18n
|
||||
: widget.currentPage == 'grades'
|
||||
? 'title_grades'.i18n
|
||||
: widget.currentPage == 'lessons'
|
||||
? 'title_lessons'.i18n
|
||||
: widget.currentPage == 'personality'
|
||||
? 'title_personality'.i18n
|
||||
: '',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
widget.currentPage != 'start'
|
||||
? IconButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
if (widget.currentPage == 'grades') {
|
||||
openNewPage(page: 'lessons');
|
||||
} else if (widget.currentPage == 'lessons') {
|
||||
openNewPage(page: 'allsum');
|
||||
} else if (widget.currentPage == 'allsum') {
|
||||
openNewPage(page: 'personality');
|
||||
} else {
|
||||
Navigator.of(context).maybePop();
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
widget.currentPage == 'personality'
|
||||
? FeatherIcons.x
|
||||
: FeatherIcons.arrowRight,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: Container()
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12.0),
|
||||
widget.currentPage == 'start'
|
||||
? const StartBody()
|
||||
: widget.currentPage == 'grades'
|
||||
? const GradesBody()
|
||||
: widget.currentPage == 'lessons'
|
||||
? const LessonsBody()
|
||||
: widget.currentPage == 'allsum'
|
||||
? const AllSumBody()
|
||||
: const PersonalityBody(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void openNewPage({String page = 'personality'}) {
|
||||
showSlidingBottomSheet(
|
||||
context,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => SlidingSheetDialog(
|
||||
color: Colors.black.withOpacity(0.99),
|
||||
duration: const Duration(milliseconds: 400),
|
||||
scrollSpec: const ScrollSpec.bouncingScroll(),
|
||||
snapSpec: const SnapSpec(
|
||||
snap: true,
|
||||
snappings: [1.0],
|
||||
initialSnap: 1.0,
|
||||
positioning: SnapPositioning.relativeToAvailableSpace,
|
||||
),
|
||||
minHeight: MediaQuery.of(context).size.height,
|
||||
cornerRadius: 16,
|
||||
cornerRadiusOnFullscreen: 0,
|
||||
builder: (context, state) => Material(
|
||||
color: Colors.black,
|
||||
child: SummaryScreen(
|
||||
currentPage: page,
|
||||
isBottomSheet: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
//SummaryScreen.show(context: context, currentPage: page);
|
||||
}
|
||||
}
|
||||
117
refilc_mobile_ui/lib/screens/summary/summary_screen.i18n.dart
Normal file
117
refilc_mobile_ui/lib/screens/summary/summary_screen.i18n.dart
Normal file
@@ -0,0 +1,117 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension SettingsLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
// main thingies
|
||||
"no_grades": "No grades found",
|
||||
"no_lesson": "No lessons found",
|
||||
"greeting": "You had a good year, %s!",
|
||||
"title_start": "So let's summarize...",
|
||||
"title_grades": "Let's look at your marks... 📖",
|
||||
"title_lessons": "Your favorite lesson 💓",
|
||||
"title_personality": "Your personality is...",
|
||||
// start page
|
||||
"start": "Start",
|
||||
// grades page
|
||||
"tryagain": "He puts the master to the test! 🔃",
|
||||
"oops": "Ouch... 🥴",
|
||||
"endyear_avg": "Year-end average",
|
||||
// lessons page
|
||||
"absence": "%s absence(s)",
|
||||
"delay": "A total of %s minute(s) late",
|
||||
"dontfelt": "You didn't like it...",
|
||||
"youlate": "You're late!",
|
||||
// allsum page
|
||||
"test": "test(s)",
|
||||
"closingtest": "module test(s)",
|
||||
"grade": "grades",
|
||||
"hw": "homework",
|
||||
"subject": "subjects",
|
||||
"lesson": "lessons",
|
||||
"absence_sum": "absence(s)",
|
||||
"excused": "excused",
|
||||
"unexcused": "unexcused",
|
||||
"delay_sum": "delay(s)",
|
||||
"min": "minute(s)",
|
||||
// personality page
|
||||
"click_reveal": "Click to reveal...",
|
||||
},
|
||||
"hu_hu": {
|
||||
// main thingies
|
||||
"no_grades": "Nincsenek jegyek",
|
||||
"no_lesson": "Nincsenek tanórák",
|
||||
"greeting": "Jó éved volt, %s!",
|
||||
"title_start": "Összegezzünk hát...",
|
||||
"title_grades": "Nézzük a jegyeidet... 📖",
|
||||
"title_lessons": "A kedvenc órád 💓",
|
||||
"title_personality": "A te személyiséged...",
|
||||
// start page
|
||||
"start": "Kezdés",
|
||||
// grades page
|
||||
"tryagain": "Próba teszi a mestert! 🔃",
|
||||
"oops": "Ajjaj... 🥴",
|
||||
"endyear_avg": "Év végi átlagod",
|
||||
// lessons page
|
||||
"absence": "%s hiányzás",
|
||||
"delay": "Összesen %s perc késés",
|
||||
"dontfelt": "Nem volt kedved hozzá...",
|
||||
"youlate": "Késtél!",
|
||||
// allsum page
|
||||
"test": "dolgozat",
|
||||
"closingtest": "témazáró",
|
||||
"grade": "jegy",
|
||||
"hw": "házi",
|
||||
"subject": "tantárgy",
|
||||
"lesson": "óra",
|
||||
"absence_sum": "hiányzás",
|
||||
"excused": "igazolt",
|
||||
"unexcused": "igazolatlan",
|
||||
"delay_sum": "késés",
|
||||
"min": "perc",
|
||||
// personality page
|
||||
"click_reveal": "Kattints a felfedéshez...",
|
||||
},
|
||||
"de_de": {
|
||||
// main thingies
|
||||
"no_grades": "Keine Grade gefunden",
|
||||
"no_lesson": "Keine Lektionen gefunden",
|
||||
"greeting": "Du hattest ein gutes Jahr, %s!",
|
||||
"title_start": "Fassen wir also zusammen...",
|
||||
"title_grades": "Schauen wir uns eure Tickets an... 📖",
|
||||
"title_lessons": "Deine Lieblingsuhr 💓",
|
||||
"title_personality": "Deine Persönlichkeit...",
|
||||
// start page
|
||||
"start": "Anfang",
|
||||
// grades page
|
||||
"tryagain": "Er stellt den Meister auf die Probe! 🔃",
|
||||
"oops": "Autsch... 🥴",
|
||||
"endyear_avg": "Ihr Jahresenddurchschnitt",
|
||||
// lessons page
|
||||
"absence": "%s Abwesenheit(en)",
|
||||
"delay": "Insgesamt %s Minute(n) zu spät",
|
||||
"dontfelt": "Es hat dir nicht gefallen...",
|
||||
"youlate": "Du bist spät!",
|
||||
// allsum page
|
||||
"test": "These(n)",
|
||||
"closingtest": "Modultest",
|
||||
"grade": "Grad",
|
||||
"hw": "Hausaufgaben",
|
||||
"subject": "Themen",
|
||||
"lesson": "Lektionen",
|
||||
"absence_sum": "Abwesenheit(en)",
|
||||
"excused": "bescheinigte",
|
||||
"unexcused": "unentschuldigte",
|
||||
"delay_sum": "Verzögerung(en)",
|
||||
"min": "Minute(n)",
|
||||
// personality page
|
||||
"click_reveal": "Klicken Sie hier, um es anzuzeigen...",
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user