igen
This commit is contained in:
@@ -1,200 +1,200 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:filcnaplo/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, {Key? key}) : super(key: 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(
|
||||
child: BackButton(),
|
||||
alignment: Alignment.topLeft,
|
||||
),
|
||||
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, {Key? key}) : super(key: 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, {Key? key}) : super(key: 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(
|
||||
child: Text(
|
||||
content,
|
||||
style: const TextStyle(fontFamily: 'SpaceMono', color: Colors.white),
|
||||
),
|
||||
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)))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:filcnaplo/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, {Key? key}) : super(key: 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(
|
||||
child: BackButton(),
|
||||
alignment: Alignment.topLeft,
|
||||
),
|
||||
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, {Key? key}) : super(key: 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, {Key? key}) : super(key: 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(
|
||||
child: Text(
|
||||
content,
|
||||
style: const TextStyle(fontFamily: 'SpaceMono', color: Colors.white),
|
||||
),
|
||||
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)))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
import 'package:filcnaplo/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, {Key? key}) : super(key: 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"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/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, {Key? key}) : super(key: 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"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LoginButton extends StatelessWidget {
|
||||
const LoginButton({Key? key, required this.onPressed, required this.child}) : super(key: key);
|
||||
|
||||
final void Function()? onPressed;
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialButton(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15.0,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
elevation: 0,
|
||||
focusElevation: 0,
|
||||
hoverElevation: 0,
|
||||
highlightElevation: 0,
|
||||
minWidth: MediaQuery.of(context).size.width - 64.0,
|
||||
onPressed: onPressed,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
|
||||
color: Colors.white,
|
||||
textColor: Colors.black,
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LoginButton extends StatelessWidget {
|
||||
const LoginButton({Key? key, required this.onPressed, required this.child}) : super(key: key);
|
||||
|
||||
final void Function()? onPressed;
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialButton(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 15.0,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
elevation: 0,
|
||||
focusElevation: 0,
|
||||
hoverElevation: 0,
|
||||
highlightElevation: 0,
|
||||
minWidth: MediaQuery.of(context).size.width - 64.0,
|
||||
onPressed: onPressed,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
|
||||
color: Colors.white,
|
||||
textColor: Colors.black,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,97 +1,97 @@
|
||||
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({Key? key, required this.style, this.controller, this.focusNode, this.onClear}) : super(key: key);
|
||||
|
||||
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: const Color(0xff20AC9B),
|
||||
textInputAction: TextInputAction.next,
|
||||
autofillHints: [autofill],
|
||||
obscureText: obscure,
|
||||
scrollPhysics: const BouncingScrollPhysics(),
|
||||
decoration: InputDecoration(
|
||||
fillColor: Colors.black.withOpacity(0.15),
|
||||
filled: true,
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: const BorderSide(width: 0, color: Colors.transparent),
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: const BorderSide(width: 0, color: Colors.transparent),
|
||||
),
|
||||
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: Icon(
|
||||
widget.style == LoginInputStyle.password
|
||||
? obscure
|
||||
? FeatherIcons.eye
|
||||
: FeatherIcons.eyeOff
|
||||
: FeatherIcons.x,
|
||||
color: Colors.white),
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
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({Key? key, required this.style, this.controller, this.focusNode, this.onClear}) : super(key: key);
|
||||
|
||||
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: const Color(0xff20AC9B),
|
||||
textInputAction: TextInputAction.next,
|
||||
autofillHints: [autofill],
|
||||
obscureText: obscure,
|
||||
scrollPhysics: const BouncingScrollPhysics(),
|
||||
decoration: InputDecoration(
|
||||
fillColor: Colors.black.withOpacity(0.15),
|
||||
filled: true,
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: const BorderSide(width: 0, color: Colors.transparent),
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: const BorderSide(width: 0, color: Colors.transparent),
|
||||
),
|
||||
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: Icon(
|
||||
widget.style == LoginInputStyle.password
|
||||
? obscure
|
||||
? FeatherIcons.eye
|
||||
: FeatherIcons.eyeOff
|
||||
: FeatherIcons.x,
|
||||
color: Colors.white),
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,303 +1,303 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:filcnaplo/api/client.dart';
|
||||
import 'package:filcnaplo/api/login.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/system_chrome.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/login/login_button.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/login/login_input.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/login/school_input/school_input.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'login_screen.i18n.dart';
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({Key? key, this.back = false}) : super(key: key);
|
||||
|
||||
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(0xff20AC9B),
|
||||
Color(0xff20AC9B),
|
||||
Color(0xff123323),
|
||||
],
|
||||
begin: Alignment(-0.8, -1.0),
|
||||
end: Alignment(0.8, 1.0),
|
||||
stops: [-1.0, 0.0, 1.0],
|
||||
);
|
||||
|
||||
@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(gradient: _backgroundGradient),
|
||||
child: SingleChildScrollView(
|
||||
physics: const ClampingScrollPhysics(),
|
||||
controller: _scrollController,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(gradient: _backgroundGradient),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (showBack)
|
||||
Container(
|
||||
alignment: Alignment.topLeft,
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 12.0),
|
||||
child: const ClipOval(
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: BackButton(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// App logo
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0),
|
||||
child: ClipRect(
|
||||
child: Container(
|
||||
// Png shadow *hack*
|
||||
child: Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Opacity(child: Image.asset("assets/icons/ic_splash.png", color: Colors.black), opacity: 0.3),
|
||||
),
|
||||
BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 6.0, sigmaY: 6.0),
|
||||
child: Image.asset("assets/icons/ic_splash.png"),
|
||||
)
|
||||
],
|
||||
),
|
||||
width: MediaQuery.of(context).size.width / 4,
|
||||
margin: const EdgeInsets.only(left: 12.0, right: 12.0, bottom: 12.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Inputs
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32.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: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"usernameHint".i18n,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
color: Colors.white54,
|
||||
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: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"passwordHint".i18n,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
color: Colors.white54,
|
||||
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: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
SchoolInput(
|
||||
scroll: _scrollController,
|
||||
controller: schoolController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Log in button
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 42.0),
|
||||
child: Visibility(
|
||||
child: LoginButton(
|
||||
child: Text("login".i18n,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 15.0,
|
||||
)),
|
||||
onPressed: () => _loginApi(context: context),
|
||||
),
|
||||
visible: _loginState != LoginState.inProgress,
|
||||
replacement: const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 6.0),
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_loginState == LoginState.missingFields || _loginState == LoginState.invalidGrant || _loginState == LoginState.failed)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
["missing_fields", "invalid_grant", "error"][_loginState.index].i18n,
|
||||
style: const TextStyle(color: Colors.red, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
const Spacer()
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _loginApi({required BuildContext context}) {
|
||||
String username = usernameController.text;
|
||||
String password = passwordController.text;
|
||||
|
||||
if (username == "" || password == "" || schoolController.selectedSchool == null) {
|
||||
return setState(() => _loginState = LoginState.missingFields);
|
||||
}
|
||||
|
||||
setState(() => _loginState = LoginState.inProgress);
|
||||
|
||||
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(() => _loginState = res));
|
||||
}
|
||||
}
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:filcnaplo/api/client.dart';
|
||||
import 'package:filcnaplo/api/login.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/custom_snack_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/system_chrome.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/login/login_button.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/login/login_input.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/login/school_input/school_input.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'login_screen.i18n.dart';
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({Key? key, this.back = false}) : super(key: key);
|
||||
|
||||
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(0xff20AC9B),
|
||||
Color(0xff20AC9B),
|
||||
Color(0xff123323),
|
||||
],
|
||||
begin: Alignment(-0.8, -1.0),
|
||||
end: Alignment(0.8, 1.0),
|
||||
stops: [-1.0, 0.0, 1.0],
|
||||
);
|
||||
|
||||
@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(gradient: _backgroundGradient),
|
||||
child: SingleChildScrollView(
|
||||
physics: const ClampingScrollPhysics(),
|
||||
controller: _scrollController,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(gradient: _backgroundGradient),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (showBack)
|
||||
Container(
|
||||
alignment: Alignment.topLeft,
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 12.0),
|
||||
child: const ClipOval(
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: BackButton(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// App logo
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0),
|
||||
child: ClipRect(
|
||||
child: Container(
|
||||
// Png shadow *hack*
|
||||
child: Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Opacity(child: Image.asset("assets/icons/ic_splash.png", color: Colors.black), opacity: 0.3),
|
||||
),
|
||||
BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 6.0, sigmaY: 6.0),
|
||||
child: Image.asset("assets/icons/ic_splash.png"),
|
||||
)
|
||||
],
|
||||
),
|
||||
width: MediaQuery.of(context).size.width / 4,
|
||||
margin: const EdgeInsets.only(left: 12.0, right: 12.0, bottom: 12.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Inputs
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32.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: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"usernameHint".i18n,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
color: Colors.white54,
|
||||
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: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"passwordHint".i18n,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
color: Colors.white54,
|
||||
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: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
SchoolInput(
|
||||
scroll: _scrollController,
|
||||
controller: schoolController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Log in button
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 42.0),
|
||||
child: Visibility(
|
||||
child: LoginButton(
|
||||
child: Text("login".i18n,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 15.0,
|
||||
)),
|
||||
onPressed: () => _loginApi(context: context),
|
||||
),
|
||||
visible: _loginState != LoginState.inProgress,
|
||||
replacement: const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 6.0),
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_loginState == LoginState.missingFields || _loginState == LoginState.invalidGrant || _loginState == LoginState.failed)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
["missing_fields", "invalid_grant", "error"][_loginState.index].i18n,
|
||||
style: const TextStyle(color: Colors.red, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
const Spacer()
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _loginApi({required BuildContext context}) {
|
||||
String username = usernameController.text;
|
||||
String password = passwordController.text;
|
||||
|
||||
if (username == "" || password == "" || schoolController.selectedSchool == null) {
|
||||
return setState(() => _loginState = LoginState.missingFields);
|
||||
}
|
||||
|
||||
setState(() => _loginState = LoginState.inProgress);
|
||||
|
||||
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(() => _loginState = res));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
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!",
|
||||
"error": "Failed to log in.",
|
||||
"schools_error": "Failed to get schools."
|
||||
},
|
||||
"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ó!",
|
||||
"error": "Sikertelen bejelentkezés.",
|
||||
"schools_error": "Nem sikerült lekérni az iskolákat."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
};
|
||||
|
||||
String get i18n => localize(this, _t);
|
||||
String fill(List<Object> params) => localizeFill(this, params);
|
||||
String plural(int value) => localizePlural(value, this, _t);
|
||||
String version(Object modifier) => localizeVersion(modifier, this, _t);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"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!",
|
||||
"error": "Failed to log in.",
|
||||
"schools_error": "Failed to get schools."
|
||||
},
|
||||
"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ó!",
|
||||
"error": "Sikertelen bejelentkezés.",
|
||||
"schools_error": "Nem sikerült lekérni az iskolákat."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
};
|
||||
|
||||
String get i18n => localize(this, _t);
|
||||
String fill(List<Object> params) => localizeFill(this, params);
|
||||
String plural(int value) => localizePlural(value, this, _t);
|
||||
String version(Object modifier) => localizeVersion(modifier, this, _t);
|
||||
}
|
||||
|
||||
@@ -1,117 +1,117 @@
|
||||
import 'package:filcnaplo_mobile_ui/screens/login/login_input.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/login/school_input/school_input_overlay.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/login/school_input/school_input_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/login/school_input/school_search.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/school.dart';
|
||||
|
||||
class SchoolInput extends StatefulWidget {
|
||||
const SchoolInput({Key? key, required this.controller, required this.scroll}) : super(key: key);
|
||||
|
||||
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;
|
||||
}
|
||||
import 'package:filcnaplo_mobile_ui/screens/login/login_input.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/login/school_input/school_input_overlay.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/login/school_input/school_input_tile.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/login/school_input/school_search.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo_kreta_api/models/school.dart';
|
||||
|
||||
class SchoolInput extends StatefulWidget {
|
||||
const SchoolInput({Key? key, required this.controller, required this.scroll}) : super(key: key);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,72 +1,72 @@
|
||||
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(
|
||||
children: children,
|
||||
size: size,
|
||||
layerLink: layerLink,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SchoolInputOverlayWidget extends StatelessWidget {
|
||||
const SchoolInputOverlayWidget({
|
||||
Key? key,
|
||||
required this.children,
|
||||
required this.size,
|
||||
required this.layerLink,
|
||||
}) : super(key: key);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
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(
|
||||
children: children,
|
||||
size: size,
|
||||
layerLink: layerLink,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SchoolInputOverlayWidget extends StatelessWidget {
|
||||
const SchoolInputOverlayWidget({
|
||||
Key? key,
|
||||
required this.children,
|
||||
required this.size,
|
||||
required this.layerLink,
|
||||
}) : super(key: key);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
import 'package:filcnaplo_kreta_api/models/school.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SchoolInputTile extends StatelessWidget {
|
||||
const SchoolInputTile({Key? key, required this.school, this.onTap}) : super(key: key);
|
||||
|
||||
final School school;
|
||||
final Function()? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: GestureDetector(
|
||||
onPanDown: (e) {
|
||||
onTap!();
|
||||
},
|
||||
child: InkWell(
|
||||
onTapDown: (e) {},
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/school.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SchoolInputTile extends StatelessWidget {
|
||||
const SchoolInputTile({Key? key, required this.school, this.onTap}) : super(key: key);
|
||||
|
||||
final School school;
|
||||
final Function()? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: GestureDetector(
|
||||
onPanDown: (e) {
|
||||
onTap!();
|
||||
},
|
||||
child: InkWell(
|
||||
onTapDown: (e) {},
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import 'package:filcnaplo_kreta_api/models/school.dart';
|
||||
import 'package:filcnaplo/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);
|
||||
}
|
||||
|
||||
results.sort((a, b) => a.name.compareTo(b.name));
|
||||
|
||||
return results;
|
||||
}
|
||||
import 'package:filcnaplo_kreta_api/models/school.dart';
|
||||
import 'package:filcnaplo/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);
|
||||
}
|
||||
|
||||
results.sort((a, b) => a.name.compareTo(b.name));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import 'package:filcnaplo_mobile_ui/screens/navigation/navbar_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Navbar extends StatelessWidget {
|
||||
const Navbar({Key? key, required this.selectedIndex, required this.onSelected, required this.items}) : super(key: key);
|
||||
|
||||
final int selectedIndex;
|
||||
final void Function(int index) onSelected;
|
||||
final List<NavItem> items;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> buttons = List.generate(
|
||||
items.length,
|
||||
(index) => NavbarItem(
|
||||
item: items[index],
|
||||
active: index == selectedIndex,
|
||||
onTap: () => onSelected(index),
|
||||
),
|
||||
);
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: buttons,
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo_mobile_ui/screens/navigation/navbar_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Navbar extends StatelessWidget {
|
||||
const Navbar({Key? key, required this.selectedIndex, required this.onSelected, required this.items}) : super(key: key);
|
||||
|
||||
final int selectedIndex;
|
||||
final void Function(int index) onSelected;
|
||||
final List<NavItem> items;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> buttons = List.generate(
|
||||
items.length,
|
||||
(index) => NavbarItem(
|
||||
item: items[index],
|
||||
active: index == selectedIndex,
|
||||
onTap: () => onSelected(index),
|
||||
),
|
||||
);
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: buttons,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NavItem {
|
||||
final String title;
|
||||
final Widget icon;
|
||||
final Widget activeIcon;
|
||||
|
||||
const NavItem({required this.title, required this.icon, required this.activeIcon});
|
||||
}
|
||||
|
||||
class NavbarItem extends StatelessWidget {
|
||||
const NavbarItem({
|
||||
Key? key,
|
||||
required this.item,
|
||||
required this.active,
|
||||
required this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
final NavItem item;
|
||||
final bool active;
|
||||
final void Function() onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Widget icon = active ? item.activeIcon : item.icon;
|
||||
|
||||
return SafeArea(
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 6.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
decoration: BoxDecoration(
|
||||
color: active ? Theme.of(context).colorScheme.secondary.withOpacity(.4) : null,
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
child: icon,
|
||||
),
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color: Theme.of(context).brightness == Brightness.light ? Colors.black.withOpacity(.5) : Colors.white.withOpacity(.3),
|
||||
),
|
||||
child: icon,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NavItem {
|
||||
final String title;
|
||||
final Widget icon;
|
||||
final Widget activeIcon;
|
||||
|
||||
const NavItem({required this.title, required this.icon, required this.activeIcon});
|
||||
}
|
||||
|
||||
class NavbarItem extends StatelessWidget {
|
||||
const NavbarItem({
|
||||
Key? key,
|
||||
required this.item,
|
||||
required this.active,
|
||||
required this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
final NavItem item;
|
||||
final bool active;
|
||||
final void Function() onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Widget icon = active ? item.activeIcon : item.icon;
|
||||
|
||||
return SafeArea(
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 6.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
decoration: BoxDecoration(
|
||||
color: active ? Theme.of(context).colorScheme.secondary.withOpacity(.4) : null,
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
child: icon,
|
||||
),
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color: Theme.of(context).brightness == Brightness.light ? Colors.black.withOpacity(.5) : Colors.white.withOpacity(.3),
|
||||
),
|
||||
child: icon,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import 'package:filcnaplo_mobile_ui/pages/absences/absences_page.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/grades_page.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/home/home_page.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/messages/messages_page.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/timetable/timetable_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:animations/animations.dart';
|
||||
|
||||
Route navigationRouteHandler(RouteSettings settings) {
|
||||
switch (settings.name) {
|
||||
case "home":
|
||||
return navigationPageRoute((context) => const HomePage());
|
||||
case "grades":
|
||||
return navigationPageRoute((context) => const GradesPage());
|
||||
case "timetable":
|
||||
return navigationPageRoute((context) => const TimetablePage());
|
||||
case "messages":
|
||||
return navigationPageRoute((context) => const MessagesPage());
|
||||
case "absences":
|
||||
return navigationPageRoute((context) => const AbsencesPage());
|
||||
default:
|
||||
return navigationPageRoute((context) => const HomePage());
|
||||
}
|
||||
}
|
||||
|
||||
Route navigationPageRoute(Widget Function(BuildContext) builder) {
|
||||
return PageRouteBuilder(
|
||||
pageBuilder: (context, _, __) => builder(context),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return FadeThroughTransition(
|
||||
fillColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
animation: animation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
import 'package:filcnaplo_mobile_ui/pages/absences/absences_page.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/grades/grades_page.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/home/home_page.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/messages/messages_page.dart';
|
||||
import 'package:filcnaplo_mobile_ui/pages/timetable/timetable_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:animations/animations.dart';
|
||||
|
||||
Route navigationRouteHandler(RouteSettings settings) {
|
||||
switch (settings.name) {
|
||||
case "home":
|
||||
return navigationPageRoute((context) => const HomePage());
|
||||
case "grades":
|
||||
return navigationPageRoute((context) => const GradesPage());
|
||||
case "timetable":
|
||||
return navigationPageRoute((context) => const TimetablePage());
|
||||
case "messages":
|
||||
return navigationPageRoute((context) => const MessagesPage());
|
||||
case "absences":
|
||||
return navigationPageRoute((context) => const AbsencesPage());
|
||||
default:
|
||||
return navigationPageRoute((context) => const HomePage());
|
||||
}
|
||||
}
|
||||
|
||||
Route navigationPageRoute(Widget Function(BuildContext) builder) {
|
||||
return PageRouteBuilder(
|
||||
pageBuilder: (context, _, __) => builder(context),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return FadeThroughTransition(
|
||||
fillColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
animation: animation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,302 +1,302 @@
|
||||
import 'package:filcnaplo/api/providers/update_provider.dart';
|
||||
import 'package:filcnaplo/helpers/quick_actions.dart';
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo/theme/observer.dart';
|
||||
import 'package:filcnaplo_kreta_api/client/client.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/system_chrome.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/navigation/nabar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/navigation/navbar_item.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/navigation/navigation_route.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/navigation/navigation_route_handler.dart';
|
||||
import 'package:filcnaplo/icons/filc_icons.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/navigation/status_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/news/news_view.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/settings/settings_screen.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/screens.i18n.dart';
|
||||
import 'package:filcnaplo/api/providers/news_provider.dart';
|
||||
import 'package:filcnaplo/api/providers/sync.dart';
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
import 'package:sliding_sheet/sliding_sheet.dart';
|
||||
import 'package:background_fetch/background_fetch.dart';
|
||||
|
||||
class NavigationScreen extends StatefulWidget {
|
||||
const NavigationScreen({Key? key}) : super(key: key);
|
||||
|
||||
static NavigationScreenState? of(BuildContext context) => context.findAncestorStateOfType<NavigationScreenState>();
|
||||
|
||||
@override
|
||||
NavigationScreenState createState() => NavigationScreenState();
|
||||
}
|
||||
|
||||
class NavigationScreenState extends State<NavigationScreen> with WidgetsBindingObserver {
|
||||
late NavigationRoute selected;
|
||||
List<String> initializers = [];
|
||||
final _navigatorState = GlobalKey<NavigatorState>();
|
||||
|
||||
late SettingsProvider settings;
|
||||
late NewsProvider newsProvider;
|
||||
late UpdateProvider updateProvider;
|
||||
|
||||
NavigatorState? get navigator => _navigatorState.currentState;
|
||||
|
||||
void customRoute(Route route) => navigator?.pushReplacement(route);
|
||||
|
||||
bool init(String id) {
|
||||
if (initializers.contains(id)) return false;
|
||||
|
||||
initializers.add(id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void _checkForWidgetLaunch() {
|
||||
HomeWidget.initiallyLaunchedFromHomeWidget().then(_launchedFromWidget);
|
||||
}
|
||||
|
||||
void _launchedFromWidget(Uri? uri) async {
|
||||
if (uri == null) return;
|
||||
|
||||
if (uri.scheme == "timetable" && uri.authority == "refresh") {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
|
||||
setPage("timetable");
|
||||
_navigatorState.currentState?.pushNamedAndRemoveUntil("timetable", (_) => false);
|
||||
} else if (uri.scheme == "settings" && uri.authority == "premium") {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
|
||||
showSlidingBottomSheet(
|
||||
context,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => SlidingSheetDialog(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
scrollSpec: const ScrollSpec.bouncingScroll(),
|
||||
snapSpec: const SnapSpec(
|
||||
snap: true,
|
||||
snappings: [1.0],
|
||||
positioning: SnapPositioning.relativeToSheetHeight,
|
||||
),
|
||||
cornerRadius: 16,
|
||||
cornerRadiusOnFullscreen: 0,
|
||||
builder: (context, state) => Material(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: const SettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Platform messages are asynchronous, so we initialize in an async method.
|
||||
Future<void> initPlatformState() async {
|
||||
// Configure BackgroundFetch.
|
||||
int status = await BackgroundFetch.configure(
|
||||
BackgroundFetchConfig(
|
||||
minimumFetchInterval: 15,
|
||||
stopOnTerminate: false,
|
||||
enableHeadless: true,
|
||||
requiresBatteryNotLow: false,
|
||||
requiresCharging: false,
|
||||
requiresStorageNotLow: false,
|
||||
requiresDeviceIdle: false,
|
||||
requiredNetworkType: NetworkType.ANY), (String taskId) async {
|
||||
// <-- Event handler
|
||||
// This is the fetch-event callback.
|
||||
print("[BackgroundFetch] Event received $taskId");
|
||||
|
||||
// IMPORTANT: You must signal completion of your task or the OS can punish your app
|
||||
// for taking too long in the background.
|
||||
BackgroundFetch.finish(taskId);
|
||||
}, (String taskId) async {
|
||||
// <-- Task timeout handler.
|
||||
// This task has exceeded its allowed running-time. You must stop what you're doing and immediately .finish(taskId)
|
||||
print("[BackgroundFetch] TASK TIMEOUT taskId: $taskId");
|
||||
BackgroundFetch.finish(taskId);
|
||||
});
|
||||
print('[BackgroundFetch] configure success: $status');
|
||||
|
||||
// If the widget was removed from the tree while the asynchronous platform
|
||||
// message was in flight, we want to discard the reply rather than calling
|
||||
// setState to update our non-existent appearance.
|
||||
if (!mounted) return;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
initPlatformState();
|
||||
|
||||
HomeWidget.setAppGroupId('hu.filc.naplo.group');
|
||||
|
||||
_checkForWidgetLaunch();
|
||||
HomeWidget.widgetClicked.listen(_launchedFromWidget);
|
||||
|
||||
settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
selected = NavigationRoute();
|
||||
selected.index = settings.startPage.index; // set page index to start page
|
||||
|
||||
// add brightness observer
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
// set client User-Agent
|
||||
Provider.of<KretaClient>(context, listen: false).userAgent = settings.config.userAgent;
|
||||
|
||||
// Get news
|
||||
newsProvider = Provider.of<NewsProvider>(context, listen: false);
|
||||
newsProvider.restore().then((value) => newsProvider.fetch());
|
||||
|
||||
// Get releases
|
||||
updateProvider = Provider.of<UpdateProvider>(context, listen: false);
|
||||
updateProvider.fetch();
|
||||
|
||||
// Initial sync
|
||||
syncAll(context);
|
||||
setupQuickActions();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangePlatformBrightness() {
|
||||
if (settings.theme == ThemeMode.system) {
|
||||
Brightness? brightness = WidgetsBinding.instance.window.platformBrightness;
|
||||
Provider.of<ThemeModeObserver>(context, listen: false).changeTheme(brightness == Brightness.light ? ThemeMode.light : ThemeMode.dark);
|
||||
}
|
||||
super.didChangePlatformBrightness();
|
||||
}
|
||||
|
||||
void setPage(String page) => setState(() => selected.name = page);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
setSystemChrome(context);
|
||||
settings = Provider.of<SettingsProvider>(context);
|
||||
newsProvider = Provider.of<NewsProvider>(context);
|
||||
|
||||
// Show news
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (newsProvider.show) {
|
||||
newsProvider.lock();
|
||||
NewsView.show(newsProvider.news[newsProvider.state], context: context).then((value) => newsProvider.release());
|
||||
}
|
||||
});
|
||||
|
||||
handleQuickActions(context, (page) {
|
||||
setPage(page);
|
||||
_navigatorState.currentState?.pushReplacementNamed(page);
|
||||
});
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (_navigatorState.currentState?.canPop() ?? false) {
|
||||
_navigatorState.currentState?.pop();
|
||||
if (!kDebugMode) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (selected.index != 0) {
|
||||
setState(() => selected.index = 0);
|
||||
_navigatorState.currentState?.pushReplacementNamed(selected.name);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
child: Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
Navigator(
|
||||
key: _navigatorState,
|
||||
initialRoute: selected.name,
|
||||
onGenerateRoute: (settings) => navigationRouteHandler(settings),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Status bar
|
||||
Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: const StatusBar(),
|
||||
),
|
||||
|
||||
// Bottom Navigaton Bar
|
||||
Material(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: MediaQuery.removePadding(
|
||||
context: context,
|
||||
removeTop: true,
|
||||
child: Navbar(
|
||||
selectedIndex: selected.index,
|
||||
onSelected: onPageSelected,
|
||||
items: [
|
||||
NavItem(
|
||||
title: "home".i18n,
|
||||
icon: const Icon(FilcIcons.home),
|
||||
activeIcon: const Icon(FilcIcons.homefill),
|
||||
),
|
||||
NavItem(
|
||||
title: "grades".i18n,
|
||||
icon: const Icon(FeatherIcons.bookmark),
|
||||
activeIcon: const Icon(FilcIcons.gradesfill),
|
||||
),
|
||||
NavItem(
|
||||
title: "timetable".i18n,
|
||||
icon: const Icon(FeatherIcons.calendar),
|
||||
activeIcon: const Icon(FilcIcons.timetablefill),
|
||||
),
|
||||
NavItem(
|
||||
title: "messages".i18n,
|
||||
icon: const Icon(FeatherIcons.messageSquare),
|
||||
activeIcon: const Icon(FilcIcons.messagesfill),
|
||||
),
|
||||
NavItem(
|
||||
title: "absences".i18n,
|
||||
icon: const Icon(FeatherIcons.clock),
|
||||
activeIcon: const Icon(FilcIcons.absencesfill),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void onPageSelected(int index) {
|
||||
// Vibrate, then set the active screen
|
||||
if (selected.index != index) {
|
||||
switch (settings.vibrate) {
|
||||
case VibrationStrength.light:
|
||||
HapticFeedback.lightImpact();
|
||||
break;
|
||||
case VibrationStrength.medium:
|
||||
HapticFeedback.mediumImpact();
|
||||
break;
|
||||
case VibrationStrength.strong:
|
||||
HapticFeedback.heavyImpact();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
setState(() => selected.index = index);
|
||||
_navigatorState.currentState?.pushReplacementNamed(selected.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/api/providers/update_provider.dart';
|
||||
import 'package:filcnaplo/helpers/quick_actions.dart';
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo/theme/observer.dart';
|
||||
import 'package:filcnaplo_kreta_api/client/client.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/system_chrome.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/navigation/nabar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/navigation/navbar_item.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/navigation/navigation_route.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/navigation/navigation_route_handler.dart';
|
||||
import 'package:filcnaplo/icons/filc_icons.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/navigation/status_bar.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/news/news_view.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/settings/settings_screen.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/screens.i18n.dart';
|
||||
import 'package:filcnaplo/api/providers/news_provider.dart';
|
||||
import 'package:filcnaplo/api/providers/sync.dart';
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
import 'package:sliding_sheet/sliding_sheet.dart';
|
||||
import 'package:background_fetch/background_fetch.dart';
|
||||
|
||||
class NavigationScreen extends StatefulWidget {
|
||||
const NavigationScreen({Key? key}) : super(key: key);
|
||||
|
||||
static NavigationScreenState? of(BuildContext context) => context.findAncestorStateOfType<NavigationScreenState>();
|
||||
|
||||
@override
|
||||
NavigationScreenState createState() => NavigationScreenState();
|
||||
}
|
||||
|
||||
class NavigationScreenState extends State<NavigationScreen> with WidgetsBindingObserver {
|
||||
late NavigationRoute selected;
|
||||
List<String> initializers = [];
|
||||
final _navigatorState = GlobalKey<NavigatorState>();
|
||||
|
||||
late SettingsProvider settings;
|
||||
late NewsProvider newsProvider;
|
||||
late UpdateProvider updateProvider;
|
||||
|
||||
NavigatorState? get navigator => _navigatorState.currentState;
|
||||
|
||||
void customRoute(Route route) => navigator?.pushReplacement(route);
|
||||
|
||||
bool init(String id) {
|
||||
if (initializers.contains(id)) return false;
|
||||
|
||||
initializers.add(id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void _checkForWidgetLaunch() {
|
||||
HomeWidget.initiallyLaunchedFromHomeWidget().then(_launchedFromWidget);
|
||||
}
|
||||
|
||||
void _launchedFromWidget(Uri? uri) async {
|
||||
if (uri == null) return;
|
||||
|
||||
if (uri.scheme == "timetable" && uri.authority == "refresh") {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
|
||||
setPage("timetable");
|
||||
_navigatorState.currentState?.pushNamedAndRemoveUntil("timetable", (_) => false);
|
||||
} else if (uri.scheme == "settings" && uri.authority == "premium") {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
|
||||
showSlidingBottomSheet(
|
||||
context,
|
||||
useRootNavigator: true,
|
||||
builder: (context) => SlidingSheetDialog(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
scrollSpec: const ScrollSpec.bouncingScroll(),
|
||||
snapSpec: const SnapSpec(
|
||||
snap: true,
|
||||
snappings: [1.0],
|
||||
positioning: SnapPositioning.relativeToSheetHeight,
|
||||
),
|
||||
cornerRadius: 16,
|
||||
cornerRadiusOnFullscreen: 0,
|
||||
builder: (context, state) => Material(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: const SettingsScreen(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Platform messages are asynchronous, so we initialize in an async method.
|
||||
Future<void> initPlatformState() async {
|
||||
// Configure BackgroundFetch.
|
||||
int status = await BackgroundFetch.configure(
|
||||
BackgroundFetchConfig(
|
||||
minimumFetchInterval: 15,
|
||||
stopOnTerminate: false,
|
||||
enableHeadless: true,
|
||||
requiresBatteryNotLow: false,
|
||||
requiresCharging: false,
|
||||
requiresStorageNotLow: false,
|
||||
requiresDeviceIdle: false,
|
||||
requiredNetworkType: NetworkType.ANY), (String taskId) async {
|
||||
// <-- Event handler
|
||||
// This is the fetch-event callback.
|
||||
print("[BackgroundFetch] Event received $taskId");
|
||||
|
||||
// IMPORTANT: You must signal completion of your task or the OS can punish your app
|
||||
// for taking too long in the background.
|
||||
BackgroundFetch.finish(taskId);
|
||||
}, (String taskId) async {
|
||||
// <-- Task timeout handler.
|
||||
// This task has exceeded its allowed running-time. You must stop what you're doing and immediately .finish(taskId)
|
||||
print("[BackgroundFetch] TASK TIMEOUT taskId: $taskId");
|
||||
BackgroundFetch.finish(taskId);
|
||||
});
|
||||
print('[BackgroundFetch] configure success: $status');
|
||||
|
||||
// If the widget was removed from the tree while the asynchronous platform
|
||||
// message was in flight, we want to discard the reply rather than calling
|
||||
// setState to update our non-existent appearance.
|
||||
if (!mounted) return;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
initPlatformState();
|
||||
|
||||
HomeWidget.setAppGroupId('hu.filc.naplo.group');
|
||||
|
||||
_checkForWidgetLaunch();
|
||||
HomeWidget.widgetClicked.listen(_launchedFromWidget);
|
||||
|
||||
settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
selected = NavigationRoute();
|
||||
selected.index = settings.startPage.index; // set page index to start page
|
||||
|
||||
// add brightness observer
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
// set client User-Agent
|
||||
Provider.of<KretaClient>(context, listen: false).userAgent = settings.config.userAgent;
|
||||
|
||||
// Get news
|
||||
newsProvider = Provider.of<NewsProvider>(context, listen: false);
|
||||
newsProvider.restore().then((value) => newsProvider.fetch());
|
||||
|
||||
// Get releases
|
||||
updateProvider = Provider.of<UpdateProvider>(context, listen: false);
|
||||
updateProvider.fetch();
|
||||
|
||||
// Initial sync
|
||||
syncAll(context);
|
||||
setupQuickActions();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangePlatformBrightness() {
|
||||
if (settings.theme == ThemeMode.system) {
|
||||
Brightness? brightness = WidgetsBinding.instance.window.platformBrightness;
|
||||
Provider.of<ThemeModeObserver>(context, listen: false).changeTheme(brightness == Brightness.light ? ThemeMode.light : ThemeMode.dark);
|
||||
}
|
||||
super.didChangePlatformBrightness();
|
||||
}
|
||||
|
||||
void setPage(String page) => setState(() => selected.name = page);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
setSystemChrome(context);
|
||||
settings = Provider.of<SettingsProvider>(context);
|
||||
newsProvider = Provider.of<NewsProvider>(context);
|
||||
|
||||
// Show news
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (newsProvider.show) {
|
||||
newsProvider.lock();
|
||||
NewsView.show(newsProvider.news[newsProvider.state], context: context).then((value) => newsProvider.release());
|
||||
}
|
||||
});
|
||||
|
||||
handleQuickActions(context, (page) {
|
||||
setPage(page);
|
||||
_navigatorState.currentState?.pushReplacementNamed(page);
|
||||
});
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (_navigatorState.currentState?.canPop() ?? false) {
|
||||
_navigatorState.currentState?.pop();
|
||||
if (!kDebugMode) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (selected.index != 0) {
|
||||
setState(() => selected.index = 0);
|
||||
_navigatorState.currentState?.pushReplacementNamed(selected.name);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
child: Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
Navigator(
|
||||
key: _navigatorState,
|
||||
initialRoute: selected.name,
|
||||
onGenerateRoute: (settings) => navigationRouteHandler(settings),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Status bar
|
||||
Material(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: const StatusBar(),
|
||||
),
|
||||
|
||||
// Bottom Navigaton Bar
|
||||
Material(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: MediaQuery.removePadding(
|
||||
context: context,
|
||||
removeTop: true,
|
||||
child: Navbar(
|
||||
selectedIndex: selected.index,
|
||||
onSelected: onPageSelected,
|
||||
items: [
|
||||
NavItem(
|
||||
title: "home".i18n,
|
||||
icon: const Icon(FilcIcons.home),
|
||||
activeIcon: const Icon(FilcIcons.homefill),
|
||||
),
|
||||
NavItem(
|
||||
title: "grades".i18n,
|
||||
icon: const Icon(FeatherIcons.bookmark),
|
||||
activeIcon: const Icon(FilcIcons.gradesfill),
|
||||
),
|
||||
NavItem(
|
||||
title: "timetable".i18n,
|
||||
icon: const Icon(FeatherIcons.calendar),
|
||||
activeIcon: const Icon(FilcIcons.timetablefill),
|
||||
),
|
||||
NavItem(
|
||||
title: "messages".i18n,
|
||||
icon: const Icon(FeatherIcons.messageSquare),
|
||||
activeIcon: const Icon(FilcIcons.messagesfill),
|
||||
),
|
||||
NavItem(
|
||||
title: "absences".i18n,
|
||||
icon: const Icon(FeatherIcons.clock),
|
||||
activeIcon: const Icon(FilcIcons.absencesfill),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void onPageSelected(int index) {
|
||||
// Vibrate, then set the active screen
|
||||
if (selected.index != index) {
|
||||
switch (settings.vibrate) {
|
||||
case VibrationStrength.light:
|
||||
HapticFeedback.lightImpact();
|
||||
break;
|
||||
case VibrationStrength.medium:
|
||||
HapticFeedback.mediumImpact();
|
||||
break;
|
||||
case VibrationStrength.strong:
|
||||
HapticFeedback.heavyImpact();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
setState(() => selected.index = index);
|
||||
_navigatorState.currentState?.pushReplacementNamed(selected.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,110 +1,110 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:filcnaplo/api/providers/status_provider.dart';
|
||||
import 'status_bar.i18n.dart';
|
||||
|
||||
class StatusBar extends StatefulWidget {
|
||||
const StatusBar({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_StatusBarState createState() => _StatusBarState();
|
||||
}
|
||||
|
||||
class _StatusBarState extends State<StatusBar> {
|
||||
late StatusProvider statusProvider;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
statusProvider = Provider.of<StatusProvider>(context);
|
||||
|
||||
Status? currentStatus = statusProvider.getStatus();
|
||||
Color backgroundColor = _statusColor(currentStatus);
|
||||
Color color = ColorUtils.foregroundColor(backgroundColor);
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
height: currentStatus != null ? 32.0 : 0,
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: Stack(
|
||||
children: [
|
||||
// Background
|
||||
AnimatedContainer(
|
||||
margin: const EdgeInsets.only(left: 6.0, right: 6.0, top: 8.0),
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
height: currentStatus != null ? 28.0 : 0,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
boxShadow: [BoxShadow(color: Theme.of(context).shadowColor, blurRadius: 8.0)],
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
),
|
||||
|
||||
// Progress bar
|
||||
if (currentStatus == Status.syncing)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 6.0, right: 6.0, top: 8.0),
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: AnimatedContainer(
|
||||
height: currentStatus != null ? 28.0 : 0,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
width: MediaQuery.of(context).size.width * statusProvider.progress - 16.0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.secondary.withOpacity(0.8),
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Text
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
_statusString(currentStatus),
|
||||
style: TextStyle(color: color, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _statusString(Status? status) {
|
||||
switch (status) {
|
||||
case Status.syncing:
|
||||
return "Syncing data".i18n;
|
||||
case Status.maintenance:
|
||||
return "KRETA Maintenance".i18n;
|
||||
case Status.network:
|
||||
return "No connection".i18n;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
Color _statusColor(Status? status) {
|
||||
switch (status) {
|
||||
case Status.maintenance:
|
||||
return AppColors.of(context).red;
|
||||
case Status.network:
|
||||
case Status.syncing:
|
||||
default:
|
||||
HSLColor color = HSLColor.fromColor(Theme.of(context).scaffoldBackgroundColor);
|
||||
if (color.lightness >= 0.5) {
|
||||
color = color.withSaturation(0.3);
|
||||
color = color.withLightness(color.lightness - 0.1);
|
||||
} else {
|
||||
color = color.withSaturation(0);
|
||||
color = color.withLightness(color.lightness + 0.2);
|
||||
}
|
||||
return color.toColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:filcnaplo/api/providers/status_provider.dart';
|
||||
import 'status_bar.i18n.dart';
|
||||
|
||||
class StatusBar extends StatefulWidget {
|
||||
const StatusBar({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_StatusBarState createState() => _StatusBarState();
|
||||
}
|
||||
|
||||
class _StatusBarState extends State<StatusBar> {
|
||||
late StatusProvider statusProvider;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
statusProvider = Provider.of<StatusProvider>(context);
|
||||
|
||||
Status? currentStatus = statusProvider.getStatus();
|
||||
Color backgroundColor = _statusColor(currentStatus);
|
||||
Color color = ColorUtils.foregroundColor(backgroundColor);
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
height: currentStatus != null ? 32.0 : 0,
|
||||
width: double.infinity,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: Stack(
|
||||
children: [
|
||||
// Background
|
||||
AnimatedContainer(
|
||||
margin: const EdgeInsets.only(left: 6.0, right: 6.0, top: 8.0),
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
height: currentStatus != null ? 28.0 : 0,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
boxShadow: [BoxShadow(color: Theme.of(context).shadowColor, blurRadius: 8.0)],
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
),
|
||||
|
||||
// Progress bar
|
||||
if (currentStatus == Status.syncing)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 6.0, right: 6.0, top: 8.0),
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: AnimatedContainer(
|
||||
height: currentStatus != null ? 28.0 : 0,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
width: MediaQuery.of(context).size.width * statusProvider.progress - 16.0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.secondary.withOpacity(0.8),
|
||||
borderRadius: BorderRadius.circular(45.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Text
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
_statusString(currentStatus),
|
||||
style: TextStyle(color: color, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _statusString(Status? status) {
|
||||
switch (status) {
|
||||
case Status.syncing:
|
||||
return "Syncing data".i18n;
|
||||
case Status.maintenance:
|
||||
return "KRETA Maintenance".i18n;
|
||||
case Status.network:
|
||||
return "No connection".i18n;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
Color _statusColor(Status? status) {
|
||||
switch (status) {
|
||||
case Status.maintenance:
|
||||
return AppColors.of(context).red;
|
||||
case Status.network:
|
||||
case Status.syncing:
|
||||
default:
|
||||
HSLColor color = HSLColor.fromColor(Theme.of(context).scaffoldBackgroundColor);
|
||||
if (color.lightness >= 0.5) {
|
||||
color = color.withSaturation(0.3);
|
||||
color = color.withLightness(color.lightness - 0.1);
|
||||
} else {
|
||||
color = color.withSaturation(0);
|
||||
color = color.withLightness(color.lightness + 0.2);
|
||||
}
|
||||
return color.toColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Syncing data": "Syncing data",
|
||||
"KRETA Maintenance": "KRETA Maintenance",
|
||||
"No connection": "No connection",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Syncing data": "Adatok frissítése",
|
||||
"KRETA Maintenance": "KRÉTA Karbantartás",
|
||||
"No connection": "Nincs kapcsolat",
|
||||
},
|
||||
"de_de": {
|
||||
"Syncing data": "Daten aktualisieren",
|
||||
"KRETA Maintenance": "KRETA Wartung",
|
||||
"No connection": "Keine Verbindung",
|
||||
},
|
||||
};
|
||||
|
||||
String get i18n => localize(this, _t);
|
||||
String fill(List<Object> params) => localizeFill(this, params);
|
||||
String plural(int value) => localizePlural(value, this, _t);
|
||||
String version(Object modifier) => localizeVersion(modifier, this, _t);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension Localization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"Syncing data": "Syncing data",
|
||||
"KRETA Maintenance": "KRETA Maintenance",
|
||||
"No connection": "No connection",
|
||||
},
|
||||
"hu_hu": {
|
||||
"Syncing data": "Adatok frissítése",
|
||||
"KRETA Maintenance": "KRÉTA Karbantartás",
|
||||
"No connection": "Nincs kapcsolat",
|
||||
},
|
||||
"de_de": {
|
||||
"Syncing data": "Daten aktualisieren",
|
||||
"KRETA Maintenance": "KRETA Wartung",
|
||||
"No connection": "Keine Verbindung",
|
||||
},
|
||||
};
|
||||
|
||||
String get i18n => localize(this, _t);
|
||||
String fill(List<Object> params) => localizeFill(this, params);
|
||||
String plural(int value) => localizePlural(value, this, _t);
|
||||
String version(Object modifier) => localizeVersion(modifier, this, _t);
|
||||
}
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/empty.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/news/news_tile.dart';
|
||||
import 'package:filcnaplo/models/news.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/news/news_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:filcnaplo/api/providers/news_provider.dart';
|
||||
|
||||
class NewsScreen extends StatelessWidget {
|
||||
const NewsScreen({Key? key}) : super(key: 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", 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"),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/empty.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/panel/panel.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/news/news_tile.dart';
|
||||
import 'package:filcnaplo/models/news.dart';
|
||||
import 'package:filcnaplo_mobile_ui/screens/news/news_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:filcnaplo/api/providers/news_provider.dart';
|
||||
|
||||
class NewsScreen extends StatelessWidget {
|
||||
const NewsScreen({Key? key}) : super(key: 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", 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"),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/models/news.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
|
||||
class NewsTile extends StatelessWidget {
|
||||
const NewsTile(this.news, {Key? key, this.onTap}) : super(key: key);
|
||||
|
||||
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)),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/models/news.dart';
|
||||
import 'package:filcnaplo/utils/format.dart';
|
||||
|
||||
class NewsTile extends StatelessWidget {
|
||||
const NewsTile(this.news, {Key? key, this.onTap}) : super(key: key);
|
||||
|
||||
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)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,116 +1,116 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/dialog_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/models/news.dart';
|
||||
import 'package:filcnaplo/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:filcnaplo_mobile_ui/screens/settings/settings_screen.i18n.dart';
|
||||
|
||||
class NewsView extends StatelessWidget {
|
||||
const NewsView(this.news, {Key? key}) : super(key: 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 == '' || 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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:filcnaplo/models/settings.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/dialog_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:filcnaplo/models/news.dart';
|
||||
import 'package:filcnaplo/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:filcnaplo_mobile_ui/screens/settings/settings_screen.i18n.dart';
|
||||
|
||||
class NewsView extends StatelessWidget {
|
||||
const NewsView(this.news, {Key? key}) : super(key: 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 == '' || 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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
class AccountTile extends StatelessWidget {
|
||||
const AccountTile({Key? key, this.onTap, this.onTapMenu, this.profileImage, this.name, this.username}) : super(key: key);
|
||||
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
|
||||
class AccountTile extends StatelessWidget {
|
||||
const AccountTile({Key? key, this.onTap, this.onTapMenu, this.profileImage, this.name, this.username}) : super(key: key);
|
||||
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
import 'package:filcnaplo/models/user.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_card.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/detail.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
|
||||
import 'package:filcnaplo_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, {Key? key}) : super(key: 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: ColorUtils.stringToColor(user.name),
|
||||
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(", ")),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/models/user.dart';
|
||||
import 'package:filcnaplo/utils/color.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/bottom_card.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/detail.dart';
|
||||
import 'package:filcnaplo_mobile_ui/common/profile_image/profile_image.dart';
|
||||
import 'package:filcnaplo_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, {Key? key}) : super(key: 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: ColorUtils.stringToColor(user.name),
|
||||
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(", ")),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,81 +1,81 @@
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SubjectIconGallery extends StatelessWidget {
|
||||
const SubjectIconGallery({Key? key}) : super(key: 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"),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SubjectIconItem extends StatelessWidget {
|
||||
const SubjectIconItem(this.name, {Key? key}) : super(key: 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:filcnaplo/helpers/subject.dart';
|
||||
import 'package:filcnaplo/theme/colors/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SubjectIconGallery extends StatelessWidget {
|
||||
const SubjectIconGallery({Key? key}) : super(key: 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"),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SubjectIconItem extends StatelessWidget {
|
||||
const SubjectIconItem(this.name, {Key? key}) : super(key: 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
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({Key? key}) : super(key: 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 Filc Napló egy 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 Filc fejlesztői és üzemeltetői a tanulmányi adataidat semmilyen célból nem másolják, nem tárolják és harmadik félnek nem továbbítják. Ezeket így az e-Kréta Informatikai Zrt. kezeli, az ő 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. Ezt a beállításokban kikapcsolhatod.
|
||||
Kérünk, hogy ha csak teheted, hagyd ezt a funkciót bekapcsolva.
|
||||
|
||||
Amikor az alkalmazás hibába ütközik, lehetőség van hibajelentés küldésére.
|
||||
Ez személyes- vagy tanulmányi adatokat nem tartalmaz, viszont részletes információval szolgál a hibá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 Filc 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 belépéskor a GitHub API segítségével ellenőrzi, hogy elérhető-e új verzió, és kérésre innen is tölti le a telepítőt.
|
||||
|
||||
Ha az adataiddal kapcsolatban bármilyen kérdésed van (törlés, módosítás, adathordozás), keress minket a filcnaplo@filcnaplo.hu címen.
|
||||
|
||||
Az alkalmazás használatával jelzed, hogy ezt a tájékoztatót tudomásul vetted.
|
||||
|
||||
Utolsó módosítás: 2021. 09. 25.
|
||||
""",
|
||||
onOpen: (link) => launch(link.url,
|
||||
customTabsOption: CustomTabsOption(
|
||||
toolbarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
showPageTitle: true,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
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({Key? key}) : super(key: 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 Filc Napló egy 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 Filc fejlesztői és üzemeltetői a tanulmányi adataidat semmilyen célból nem másolják, nem tárolják és harmadik félnek nem továbbítják. Ezeket így az e-Kréta Informatikai Zrt. kezeli, az ő 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. Ezt a beállításokban kikapcsolhatod.
|
||||
Kérünk, hogy ha csak teheted, hagyd ezt a funkciót bekapcsolva.
|
||||
|
||||
Amikor az alkalmazás hibába ütközik, lehetőség van hibajelentés küldésére.
|
||||
Ez személyes- vagy tanulmányi adatokat nem tartalmaz, viszont részletes információval szolgál a hibá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 Filc 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 belépéskor a GitHub API segítségével ellenőrzi, hogy elérhető-e új verzió, és kérésre innen is tölti le a telepítőt.
|
||||
|
||||
Ha az adataiddal kapcsolatban bármilyen kérdésed van (törlés, módosítás, adathordozás), keress minket a filcnaplo@filcnaplo.hu címen.
|
||||
|
||||
Az alkalmazás használatával jelzed, hogy ezt a tájékoztatót tudomásul vetted.
|
||||
|
||||
Utolsó módosítás: 2021. 09. 25.
|
||||
""",
|
||||
onOpen: (link) => launch(link.url,
|
||||
customTabsOption: CustomTabsOption(
|
||||
toolbarColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
showPageTitle: true,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,194 +1,194 @@
|
||||
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 DKT",
|
||||
"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",
|
||||
"notifications": "Notifications",
|
||||
"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. Android), App version (eg. 3.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":
|
||||
"Filc Napló® Informatikai Zrt. 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",
|
||||
},
|
||||
"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",
|
||||
"notifications": "Értesítések",
|
||||
"news": "Hírek",
|
||||
"extras": "Extrák",
|
||||
"about": "Névjegy",
|
||||
"supporters": "Támogatók",
|
||||
"privacy": "Adatvédelmi irányelvek",
|
||||
"licenses": "Licenszek",
|
||||
"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. Android), App verzió (pl. 3.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 Filc Napló® Informatikai Zrt. 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 magadadat 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",
|
||||
},
|
||||
"de_de": {
|
||||
"personal_details": "Persönliche Angaben",
|
||||
"open_dkt": "Öffnen DKT",
|
||||
"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",
|
||||
"notifications": "Benachrichtigungen",
|
||||
"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. Android), App version (z.B. 3.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": "Same in English.",
|
||||
"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",
|
||||
},
|
||||
};
|
||||
|
||||
String get i18n => localize(this, _t);
|
||||
String fill(List<Object> params) => localizeFill(this, params);
|
||||
String plural(int value) => localizePlural(value, this, _t);
|
||||
String version(Object modifier) => localizeVersion(modifier, this, _t);
|
||||
}
|
||||
import 'package:i18n_extension/i18n_extension.dart';
|
||||
|
||||
extension SettingsLocalization on String {
|
||||
static final _t = Translations.byLocale("hu_hu") +
|
||||
{
|
||||
"en_en": {
|
||||
"personal_details": "Personal Details",
|
||||
"open_dkt": "Open DKT",
|
||||
"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",
|
||||
"notifications": "Notifications",
|
||||
"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. Android), App version (eg. 3.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":
|
||||
"Filc Napló® Informatikai Zrt. 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",
|
||||
},
|
||||
"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",
|
||||
"notifications": "Értesítések",
|
||||
"news": "Hírek",
|
||||
"extras": "Extrák",
|
||||
"about": "Névjegy",
|
||||
"supporters": "Támogatók",
|
||||
"privacy": "Adatvédelmi irányelvek",
|
||||
"licenses": "Licenszek",
|
||||
"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. Android), App verzió (pl. 3.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 Filc Napló® Informatikai Zrt. 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 magadadat 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",
|
||||
},
|
||||
"de_de": {
|
||||
"personal_details": "Persönliche Angaben",
|
||||
"open_dkt": "Öffnen DKT",
|
||||
"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",
|
||||
"notifications": "Benachrichtigungen",
|
||||
"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. Android), App version (z.B. 3.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": "Same in English.",
|
||||
"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",
|
||||
},
|
||||
};
|
||||
|
||||
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