changed everything from filcnaplo to refilc finally

This commit is contained in:
Kima
2024-02-24 20:12:25 +01:00
parent 0d1c7b7143
commit 1171e3aaaf
655 changed files with 38728 additions and 44967 deletions

View File

@@ -0,0 +1,47 @@
import 'package:refilc/theme/colors/colors.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
class AccountTile extends StatelessWidget {
const AccountTile(
{super.key,
this.onTap,
this.onTapMenu,
this.profileImage,
this.name,
this.username});
final void Function()? onTap;
final void Function()? onTapMenu;
final Widget? profileImage;
final Widget? name;
final Widget? username;
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: ListTile(
visualDensity: VisualDensity.compact,
contentPadding: const EdgeInsets.only(left: 12.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
onTap: onTap,
onLongPress: onTapMenu,
leading: profileImage,
title: name,
subtitle: username,
trailing: onTapMenu != null
? Material(
color: Colors.transparent,
child: IconButton(
splashRadius: 24.0,
onPressed: onTapMenu,
icon: Icon(FeatherIcons.moreVertical,
color: AppColors.of(context).text.withOpacity(0.8)),
),
)
: null,
),
);
}
}

View File

@@ -0,0 +1,63 @@
// ignore_for_file: no_leading_underscores_for_local_identifiers
import 'package:refilc/models/user.dart';
import 'package:refilc_mobile_ui/common/bottom_card.dart';
import 'package:refilc_mobile_ui/common/detail.dart';
import 'package:refilc_mobile_ui/common/profile_image/profile_image.dart';
import 'package:refilc_mobile_ui/screens/settings/accounts/account_tile.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'account_view.i18n.dart';
class AccountView extends StatelessWidget {
const AccountView(this.user, {super.key});
final User user;
static void show(User user, {required BuildContext context}) =>
showBottomCard(context: context, child: AccountView(user));
@override
Widget build(BuildContext context) {
List<String> _nameParts = user.name.split(" ");
String _firstName = _nameParts.length > 1 ? _nameParts[1] : _nameParts[0];
return Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccountTile(
profileImage: ProfileImage(
name: _firstName,
backgroundColor: Theme.of(context).colorScheme.primary,
role: user.role,
),
name: SelectableText(
user.name,
style: const TextStyle(fontWeight: FontWeight.w500),
maxLines: 2,
minLines: 1,
),
username: SelectableText(user.username),
),
// User details
Detail(
title: "birthdate".i18n,
description:
DateFormat("yyyy. MM. dd.").format(user.student.birth)),
Detail(title: "school".i18n, description: user.student.school.name),
if (user.student.className != null)
Detail(title: "class".i18n, description: user.student.className!),
if (user.student.address != null)
Detail(title: "address".i18n, description: user.student.address!),
if (user.student.parents.isNotEmpty)
Detail(
title: "parents".plural(user.student.parents.length),
description: user.student.parents.join(", ")),
],
),
);
}
}

View File

@@ -0,0 +1,33 @@
import 'package:i18n_extension/i18n_extension.dart';
extension Localization on String {
static final _t = Translations.byLocale("hu_hu") +
{
"en_en": {
"birthdate": "Birth date",
"school": "School",
"class": "Class",
"address": "Home address",
"parents": "Parents".one("Parent"),
},
"hu_hu": {
"birthdate": "Születési dátum",
"school": "Iskola",
"class": "Osztály",
"address": "Lakcím",
"parents": "Szülők".one("Szülő"),
},
"de_de": {
"birthdate": "Geburtsdatum",
"school": "Schule",
"class": "Klasse",
"address": "Wohnanschrift",
"parents": "Eltern",
},
};
String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t);
}

View File

@@ -0,0 +1,84 @@
import 'package:refilc/helpers/subject.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:flutter/material.dart';
class SubjectIconGallery extends StatelessWidget {
const SubjectIconGallery({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
leading: BackButton(color: AppColors.of(context).text),
title: Text(
"Subject Icon Gallery",
style: TextStyle(color: AppColors.of(context).text),
),
),
body: ListView(
children: const [
SubjectIconItem("Matematika"),
SubjectIconItem("Magyar Nyelv"),
SubjectIconItem("Nyelvtan"),
SubjectIconItem("Irodalom"),
SubjectIconItem("Történelem"),
SubjectIconItem("Földrajz"),
SubjectIconItem("Rajz"),
SubjectIconItem("Vizuális kultúra"),
SubjectIconItem("Fizika"),
SubjectIconItem("Ének"),
SubjectIconItem("Testnevelés"),
SubjectIconItem("Kémia"),
SubjectIconItem("Biológia"),
SubjectIconItem("Természetismeret"),
SubjectIconItem("Erkölcstan"),
SubjectIconItem("Pénzügy"),
SubjectIconItem("Informatika"),
SubjectIconItem("Digitális kultúra"),
SubjectIconItem("Programozás"),
SubjectIconItem("Hálózat"),
SubjectIconItem("Színház technika"),
SubjectIconItem("Média"),
SubjectIconItem("Elektronika"),
SubjectIconItem("Gépészet"),
SubjectIconItem("Technika"),
SubjectIconItem("Tánc"),
SubjectIconItem("Filozófia"),
SubjectIconItem("Osztályfőnöki"),
SubjectIconItem("Gazdaság"),
SubjectIconItem("Szorgalom"),
SubjectIconItem("Magatartás"),
SubjectIconItem("Angol nyelv"),
SubjectIconItem("Linux"),
SubjectIconItem("Adatbázis"),
SubjectIconItem("Asztali alkalmazások"),
SubjectIconItem("Projekt"),
],
),
);
}
}
class SubjectIconItem extends StatelessWidget {
const SubjectIconItem(this.name, {super.key});
final String name;
@override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(
SubjectIcon.resolveVariant(subjectName: name, context: context),
color: AppColors.of(context).text,
),
title: Text(
name,
style: TextStyle(
color: AppColors.of(context).text,
fontWeight: FontWeight.w500,
),
),
);
}
}

View File

@@ -0,0 +1,94 @@
import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/models/settings.dart';
import 'package:refilc/theme/colors/colors.dart';
// import 'package:refilc/utils/format.dart';
// import 'package:refilc_kreta_api/models/teacher.dart';
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
// import 'package:refilc_plus/models/premium_scopes.dart';
// import 'package:refilc_plus/providers/premium_provider.dart';
// import 'package:refilc_plus/ui/mobile/premium/upsell.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
import 'package:refilc_mobile_ui/common/beta_chip.dart';
class MenuDesktopSettings extends StatelessWidget {
const MenuDesktopSettings({super.key, required this.settings});
final SettingsProvider settings;
@override
Widget build(BuildContext context) {
return PanelButton(
padding: const EdgeInsets.only(left: 14.0),
onPressed: () {
Navigator.of(context, rootNavigator: true).push(
CupertinoPageRoute(
builder: (context) => const ModifyDesktopSettings()),
);
},
title: Row(
children: [
Text(
"desktop_settings".i18n,
style: TextStyle(color: AppColors.of(context).text),
),
const Spacer(),
const BetaChip(),
const Spacer()
],
),
leading: Icon(FeatherIcons.monitor,
color: Theme.of(context).colorScheme.secondary),
);
}
}
class ModifyDesktopSettings extends StatefulWidget {
const ModifyDesktopSettings({super.key});
@override
State<ModifyDesktopSettings> createState() => _ModifyDesktopSettingsState();
}
class _ModifyDesktopSettingsState extends State<ModifyDesktopSettings> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
late UserProvider user;
late DatabaseProvider dbProvider;
late SettingsProvider settings;
@override
void initState() {
super.initState();
user = Provider.of<UserProvider>(context, listen: false);
dbProvider = Provider.of<DatabaseProvider>(context, listen: false);
}
@override
Widget build(BuildContext context) {
settings = Provider.of<SettingsProvider>(context);
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
leading: BackButton(color: AppColors.of(context).text),
title: Text(
"desktop_settings".i18n,
style: TextStyle(color: AppColors.of(context).text),
),
),
body: const Padding(
padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [],
),
),
));
}
}

View File

@@ -0,0 +1,72 @@
import 'package:i18n_extension/i18n_extension.dart';
extension SettingsLocalization on String {
static final _t = Translations.byLocale("hu_hu") +
{
"en_en": {
// subject rename
"renamed_subjects": "Renamed Subjects",
"rename_subjects": "Rename Subjects",
"rename_subject": "Rename Subject",
"select_subject": "Select Subject",
"modified_name": "Modified Name",
"modify_subjects": "Modify Subjects",
"cancel": "Cancel",
"done": "Done",
"rename_new_subject": "Rename New Subject",
"italics_toggle": "Italic Font",
// teacher rename
"renamed_teachers": "Renamed Teachers",
"rename_teachers": "Rename Teachers",
"rename_teacher": "Rename Teacher",
"select_teacher": "Select Teacher",
"modify_teachers": "Modify Teachers",
"rename_new_teacher": "Rename New Teacher",
},
"hu_hu": {
// subject rename
"renamed_subjects": "Átnevezett Tantárgyaid",
"rename_subjects": "Tantárgyak átnevezése",
"rename_subject": "Tantárgy átnevezése",
"select_subject": "Válassz tantárgyat",
"modified_name": "Módosított név",
"modify_subjects": "Tantárgyak módosítása",
"cancel": "Mégse",
"done": "Kész",
"rename_new_subject": "Új tantárgy átnevezése",
"italics_toggle": "Dőlt betűs megjelenítés",
// teacher rename
"renamed_teachers": "Átnevezett tanáraid",
"rename_teachers": "Tanárok átnevezése",
"rename_teacher": "Tanár átnevezése",
"select_teacher": "Válassz tanárt",
"modify_teachers": "Tanárok módosítása",
"rename_new_teacher": "Új tanár átnevezése",
},
"de_de": {
// subject rename
"renamed_subjects": "Umbenannte Fächer",
"rename_subjects": "Fächer umbenennen",
"rename_subject": "Fach umbenennen",
"select_subject": "Fach auswählen",
"modified_name": "Geänderter Name",
"modify_subjects": "Fächer ändern",
"cancel": "Abbrechen",
"done": "Erledigt",
"rename_new_subject": "Neues Fach umbenennen",
"italics_toggle": "Kursivschrift umschalten",
// teacher rename
"renamed_teachers": "Renamed Teachers",
"rename_teachers": "Rename Teachers",
"rename_teacher": "Rename Teacher",
"select_teacher": "Select Teacher",
"modify_teachers": "Modify Teachers",
"rename_new_teacher": "Rename New Teacher",
},
};
String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t);
}

View File

@@ -0,0 +1,439 @@
// ignore_for_file: use_build_context_synchronously
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/helpers/subject.dart';
import 'package:refilc/models/settings.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc/utils/format.dart';
import 'package:refilc_kreta_api/models/subject.dart';
import 'package:refilc_kreta_api/providers/absence_provider.dart';
import 'package:refilc_kreta_api/providers/grade_provider.dart';
import 'package:refilc_kreta_api/providers/timetable_provider.dart';
import 'package:refilc_mobile_ui/common/panel/panel.dart';
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
// import 'package:refilc_plus/models/premium_scopes.dart';
// import 'package:refilc_plus/providers/premium_provider.dart';
// import 'package:refilc_plus/ui/mobile/premium/upsell.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
import 'modify_names.i18n.dart';
class MenuRenamedSubjects extends StatelessWidget {
const MenuRenamedSubjects({super.key, required this.settings});
final SettingsProvider settings;
@override
Widget build(BuildContext context) {
return PanelButton(
padding: const EdgeInsets.only(left: 14.0),
onPressed: () {
// if (!Provider.of<PremiumProvider>(context, listen: false)
// .hasScope(PremiumScopes.renameSubjects)) {
// PremiumLockedFeatureUpsell.show(
// context: context, feature: PremiumFeature.subjectrename);
// return;
// }
Navigator.of(context, rootNavigator: true).push(
CupertinoPageRoute(builder: (context) => const ModifySubjectNames()),
);
},
title: Text(
"rename_subjects".i18n,
style: TextStyle(
color: AppColors.of(context)
.text
.withOpacity(settings.renamedSubjectsEnabled ? 1.0 : .5)),
),
leading: settings.renamedSubjectsEnabled
? const Icon(FeatherIcons.penTool)
: Icon(FeatherIcons.penTool,
color: AppColors.of(context).text.withOpacity(.25)),
trailingDivider: true,
trailing: Switch(
onChanged: (v) async {
// if (!Provider.of<PremiumProvider>(context, listen: false)
// .hasScope(PremiumScopes.renameSubjects)) {
// PremiumLockedFeatureUpsell.show(
// context: context, feature: PremiumFeature.subjectrename);
// return;
// }
settings.update(renamedSubjectsEnabled: v);
await Provider.of<GradeProvider>(context, listen: false)
.convertBySettings();
await Provider.of<TimetableProvider>(context, listen: false)
.convertBySettings();
await Provider.of<AbsenceProvider>(context, listen: false)
.convertBySettings();
},
value: settings.renamedSubjectsEnabled,
activeColor: Theme.of(context).colorScheme.secondary,
),
);
}
}
class ModifySubjectNames extends StatefulWidget {
const ModifySubjectNames({super.key});
@override
State<ModifySubjectNames> createState() => _ModifySubjectNamesState();
}
class _ModifySubjectNamesState extends State<ModifySubjectNames> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final _subjectName = TextEditingController();
String? selectedSubjectId;
late List<GradeSubject> subjects;
late UserProvider user;
late DatabaseProvider dbProvider;
late SettingsProvider settings;
@override
void initState() {
super.initState();
subjects = Provider.of<GradeProvider>(context, listen: false)
.grades
.map((e) => e.subject)
.toSet()
.toList()
..sort((a, b) => a.name.compareTo(b.name));
user = Provider.of<UserProvider>(context, listen: false);
dbProvider = Provider.of<DatabaseProvider>(context, listen: false);
}
Future<Map<String, String>> fetchRenamedSubjects() async {
return await dbProvider.userQuery.renamedSubjects(userId: user.id!);
}
void showRenameDialog() {
showDialog(
context: context,
builder: (context) => StatefulBuilder(builder: (context, setS) {
return AlertDialog(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(14.0))),
title: Text("rename_subject".i18n),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
DropdownButton2(
items: subjects
.map((item) => DropdownMenuItem<String>(
value: item.id,
child: Text(
item.name,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: AppColors.of(context).text,
),
overflow: TextOverflow.ellipsis,
),
))
.toList(),
onChanged: (String? v) async {
final renamedSubs = await fetchRenamedSubjects();
setS(() {
selectedSubjectId = v;
if (renamedSubs.containsKey(selectedSubjectId)) {
_subjectName.text = renamedSubs[selectedSubjectId]!;
} else {
_subjectName.text = "";
}
});
},
iconSize: 14,
iconEnabledColor: AppColors.of(context).text,
iconDisabledColor: AppColors.of(context).text,
underline: const SizedBox(),
itemHeight: 40,
itemPadding: const EdgeInsets.only(left: 14, right: 14),
buttonWidth: 50,
dropdownWidth: 300,
dropdownPadding: null,
buttonDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
dropdownDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(14),
),
dropdownElevation: 8,
scrollbarRadius: const Radius.circular(40),
scrollbarThickness: 6,
scrollbarAlwaysShow: true,
offset: const Offset(-10, -10),
buttonSplashColor: Colors.transparent,
customButton: Container(
width: double.infinity,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 2),
borderRadius: BorderRadius.circular(12.0),
),
padding: const EdgeInsets.symmetric(
vertical: 12.0, horizontal: 8.0),
child: Text(
selectedSubjectId == null
? "select_subject".i18n
: subjects
.firstWhere(
(element) => element.id == selectedSubjectId)
.name,
style: Theme.of(context).textTheme.titleSmall!.copyWith(
fontWeight: FontWeight.w700,
color: AppColors.of(context).text.withOpacity(0.75)),
overflow: TextOverflow.ellipsis,
maxLines: 2,
textAlign: TextAlign.center,
),
),
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Icon(FeatherIcons.arrowDown, size: 32),
),
TextField(
controller: _subjectName,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide:
const BorderSide(color: Colors.grey, width: 1.5),
borderRadius: BorderRadius.circular(12.0),
),
focusedBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: Colors.grey, width: 1.5),
borderRadius: BorderRadius.circular(12.0),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
hintText: "modified_name".i18n,
suffixIcon: IconButton(
icon: const Icon(
FeatherIcons.x,
color: Colors.grey,
),
onPressed: () {
setState(() {
_subjectName.text = "";
});
},
),
),
),
],
),
actions: [
TextButton(
child: Text(
"cancel".i18n,
style: const TextStyle(fontWeight: FontWeight.w500),
),
onPressed: () {
Navigator.of(context).maybePop();
},
),
TextButton(
child: Text(
"done".i18n,
style: const TextStyle(fontWeight: FontWeight.w500),
),
onPressed: () async {
if (selectedSubjectId != null) {
final renamedSubs = await fetchRenamedSubjects();
renamedSubs[selectedSubjectId!] = _subjectName.text;
await dbProvider.userStore
.storeRenamedSubjects(renamedSubs, userId: user.id!);
await Provider.of<GradeProvider>(context, listen: false)
.convertBySettings();
await Provider.of<TimetableProvider>(context, listen: false)
.convertBySettings();
await Provider.of<AbsenceProvider>(context, listen: false)
.convertBySettings();
}
Navigator.of(context).pop(true);
setState(() {});
},
),
],
);
}),
).then((val) {
_subjectName.text = "";
selectedSubjectId = null;
});
}
@override
Widget build(BuildContext context) {
settings = Provider.of<SettingsProvider>(context);
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
leading: BackButton(color: AppColors.of(context).text),
title: Text(
"modify_subjects".i18n,
style: TextStyle(color: AppColors.of(context).text),
),
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Panel(
child: SwitchListTile(
title: Text("italics_toggle".i18n),
onChanged: (value) =>
settings.update(renamedSubjectsItalics: value),
value: settings.renamedSubjectsItalics,
),
),
const SizedBox(
height: 20,
),
InkWell(
onTap: showRenameDialog,
borderRadius: BorderRadius.circular(12.0),
child: Container(
width: double.infinity,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 2),
borderRadius: BorderRadius.circular(12.0),
),
padding: const EdgeInsets.symmetric(
vertical: 18.0, horizontal: 12.0),
child: Center(
child: Text(
"rename_new_subject".i18n,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 18,
color: AppColors.of(context).text.withOpacity(.85),
),
),
),
),
),
const SizedBox(
height: 30,
),
FutureBuilder<Map<String, String>>(
future: fetchRenamedSubjects(),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Container();
}
return Panel(
title: Text("renamed_subjects".i18n),
child: Column(
children: snapshot.data!.keys.map(
(key) {
GradeSubject? subject = subjects
.firstWhere((element) => key == element.id);
String renameTo = snapshot.data![key]!;
return RenamedSubjectItem(
subject: subject,
renamedTo: renameTo,
modifyCallback: () {
setState(() {
selectedSubjectId = subject.id;
_subjectName.text = renameTo;
});
showRenameDialog();
},
removeCallback: () {
setState(() {
Map<String, String> subs =
Map.from(snapshot.data!);
subs.remove(key);
dbProvider.userStore.storeRenamedSubjects(
subs,
userId: user.id!);
});
},
);
},
).toList(),
),
);
},
),
],
),
),
));
}
}
class RenamedSubjectItem extends StatelessWidget {
const RenamedSubjectItem({
super.key,
required this.subject,
required this.renamedTo,
required this.modifyCallback,
required this.removeCallback,
});
final GradeSubject subject;
final String renamedTo;
final void Function() modifyCallback;
final void Function() removeCallback;
@override
Widget build(BuildContext context) {
return ListTile(
minLeadingWidth: 32.0,
dense: true,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 6.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)),
visualDensity: VisualDensity.compact,
onTap: () {},
leading: Icon(
SubjectIcon.resolveVariant(subject: subject, context: context),
color: AppColors.of(context).text.withOpacity(.75)),
title: InkWell(
onTap: modifyCallback,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
subject.name.capital(),
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14,
color: AppColors.of(context).text.withOpacity(.75)),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
renamedTo,
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 16),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
trailing: InkWell(
onTap: removeCallback,
child: Icon(FeatherIcons.trash,
color: AppColors.of(context).red.withOpacity(.75)),
),
);
}
}

View File

@@ -0,0 +1,222 @@
import 'package:refilc/models/settings.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_mobile_ui/common/beta_chip.dart';
import 'package:refilc_mobile_ui/common/panel/panel.dart';
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
import 'notifications_screen.i18n.dart';
class MenuNotifications extends StatelessWidget {
const MenuNotifications({
super.key,
this.borderRadius = const BorderRadius.vertical(
top: Radius.circular(4.0), bottom: Radius.circular(4.0)),
});
final BorderRadius borderRadius;
@override
Widget build(BuildContext context) {
return PanelButton(
onPressed: () => Navigator.of(context, rootNavigator: true).push(
CupertinoPageRoute(builder: (context) => const NotificationsScreen()),
),
title: Row(
children: [
Text(
"notifications_screen".i18n,
),
const SizedBox(width: 5.0),
const BetaChip(
disabled: false,
),
],
),
leading: Icon(
FeatherIcons.messageCircle,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
trailing: Icon(
FeatherIcons.chevronRight,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
borderRadius: borderRadius,
);
}
}
class NotificationsScreen extends StatelessWidget {
const NotificationsScreen({super.key});
@override
Widget build(BuildContext context) {
SettingsProvider settings = Provider.of<SettingsProvider>(context);
return Scaffold(
appBar: AppBar(
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
leading: BackButton(color: AppColors.of(context).text),
title: Text(
"notifications_screen".i18n,
style: TextStyle(color: AppColors.of(context).text),
),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: Panel(
child: Column(
children: [
Material(
type: MaterialType.transparency,
child: SwitchListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
value: settings.notificationsGradesEnabled,
onChanged: (v) =>
settings.update(notificationsGradesEnabled: v),
title: Row(
children: [
Icon(
FeatherIcons.bookmark,
color: settings.notificationsGradesEnabled
? Theme.of(context).colorScheme.secondary
: AppColors.of(context).text.withOpacity(.25),
),
const SizedBox(width: 14.0),
Expanded(
child: Text(
"grades".i18n,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
color: AppColors.of(context).text.withOpacity(
settings.notificationsGradesEnabled
? 1.0
: .5,
),
),
),
),
],
),
),
),
Material(
type: MaterialType.transparency,
child: SwitchListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
value: settings.notificationsAbsencesEnabled,
onChanged: (v) =>
settings.update(notificationsAbsencesEnabled: v),
title: Row(
children: [
Icon(
FeatherIcons.clock,
color: settings.notificationsAbsencesEnabled
? Theme.of(context).colorScheme.secondary
: AppColors.of(context).text.withOpacity(.25),
),
const SizedBox(width: 14.0),
Expanded(
child: Text(
"absences".i18n,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
color: AppColors.of(context).text.withOpacity(
settings.notificationsAbsencesEnabled
? 1.0
: .5,
),
),
),
),
],
),
),
),
Material(
type: MaterialType.transparency,
child: SwitchListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
value: settings.notificationsMessagesEnabled,
onChanged: (v) =>
settings.update(notificationsMessagesEnabled: v),
title: Row(
children: [
Icon(
FeatherIcons.messageSquare,
color: settings.notificationsMessagesEnabled
? Theme.of(context).colorScheme.secondary
: AppColors.of(context).text.withOpacity(.25),
),
const SizedBox(width: 14.0),
Expanded(
child: Text(
"messages".i18n,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
color: AppColors.of(context).text.withOpacity(
settings.notificationsMessagesEnabled
? 1.0
: .5,
),
),
),
),
],
),
),
),
Material(
type: MaterialType.transparency,
child: SwitchListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
value: settings.notificationsLessonsEnabled,
onChanged: (v) =>
settings.update(notificationsLessonsEnabled: v),
title: Row(
children: [
Icon(
FeatherIcons.calendar,
color: settings.notificationsLessonsEnabled
? Theme.of(context).colorScheme.secondary
: AppColors.of(context).text.withOpacity(.25),
),
const SizedBox(width: 14.0),
Expanded(
child: Text(
"lessons".i18n,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
color: AppColors.of(context).text.withOpacity(
settings.notificationsLessonsEnabled
? 1.0
: .5,
),
),
),
),
],
),
),
),
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,34 @@
import 'package:i18n_extension/i18n_extension.dart';
extension SettingsLocalization on String {
static final _t = Translations.byLocale("hu_hu") +
{
"en_en": {
"notifications_screen": "Notifications",
"grades": "Grades",
"absences": "Absences",
"messages": "Messages",
"lessons": "Lessons"
},
"hu_hu": {
"notifications_screen": "Értesítések",
"grades": "Jegyek",
"absences": "Hiányzások",
"messages": "Üzenetek",
"lessons": "Órák"
},
"de_de": {
"notifications_screen": "Mitteilung",
"grades": "Noten",
"absences": "Fehlen",
"messages": "Nachrichten",
"lessons": "Unterricht"
},
};
String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t);
}

View File

@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'settings_screen.i18n.dart';
class PrivacyView extends StatelessWidget {
const PrivacyView({super.key});
static void show(BuildContext context) => showDialog(
context: context,
builder: (context) => const PrivacyView(),
barrierDismissible: true);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 100.0, horizontal: 32.0),
child: Material(
borderRadius: BorderRadius.circular(12.0),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: ListView(
physics: const BouncingScrollPhysics(),
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("privacy".i18n),
),
SelectableLinkify(
text: """
• A reFilc (továbbiakban alkalmazás) egy mobilos, asztali és webes kliensalkalmazás, segítségével az e-Kréta rendszeréből letöltheted és felhasználóbarát módon megjelenítheted az adataidat. Tanulmányi adataid csak közvetlenül az alkalmazás és a Kréta-szerverek között közlekednek, titkosított kapcsolaton keresztül.
• A reFilc fejlesztői és/vagy üzemeltetői, valamint az alkalmazás a tanulmányi és személyes adataidat semmilyen célból és semmilyen körülmények között nem másolják, nem tárolják és harmadik félnek nem továbbítják. Ezeket így az Educational Development Informatikai Zrt. kezeli, az Ő adatkezeléssel kapcsolatos tájékoztatójukat itt találod: https://tudasbazis.ekreta.hu/pages/viewpage.action?pageId=4065038
• Azok törlésével vagy módosítával kapcsolatban keresd az osztályfőnöködet vagy az iskolád rendszergazdáját.
• Az alkalmazás névtelen használati statisztikákat gyűjt, ezek alapján tudjuk meghatározni a felhasználók és a telepítések számát, valamint az eszközük platformját. Ezt a beállításokban kikapcsolhatod. Kérünk, hogy ha csak teheted, hagyd ezt a funkciót bekapcsolva, hogy pontosabb információnk legyen a felhasználóink platform-megoszlásáról.
• Amikor az alkalmazás hibába ütközik, lehetőség van hibajelentés küldésére. Ez személyes- és/vagy tanulmányi adatokat nem tartalmaz, viszont részletes információval szolgál a hibáról, annak okáról és eszközödről. A küldés előtt megjelenő képernyőn a te felelősséged átnézni a továbbításra kerülő adatsort. A hibajelentéseket a reFilc fejlesztői felületén és egy privát Discord szobában tároljuk, ezekhez csak az app fejlesztői férnek hozzá.
• Az alkalmazás (az alábbi platformokon: Android, Linux, Windows) minden egyes indításakor a reFilc API, valamint a Github API segítségével ellenőrzi, hogy elérhető-e új verzió, és kérésre innen letölti és telepíti a frissítést.
• Amennyiben az adataiddal kapcsolatban bármilyen kérdésed van (megtekintés, törlés, módosítás, adathordozás), keress minket a social@refilc.hu e-mail címen, vagy Discord szerverünkön!
• A kliensalkalmazás bármely eszközön és platformon történő használatával tudomásul vetted és elfogadod a jelen adatkezelési tájékoztatót. A reFilc csapata fenntartja a jogot a tájékoztató módosítására és a módosításokról nem köteles értesíteni a felhasználóit!
""",
onOpen: (link) => launch(link.url,
customTabsOption: CustomTabsOption(
toolbarColor: Theme.of(context).scaffoldBackgroundColor,
showPageTitle: true,
)),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,983 @@
// ignore_for_file: prefer_function_declarations_over_variables, library_private_types_in_public_api, use_build_context_synchronously
import 'dart:io';
import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/helpers/quick_actions.dart';
import 'package:refilc/icons/filc_icons.dart';
import 'package:refilc/models/settings.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc/theme/observer.dart';
import 'package:refilc_kreta_api/models/grade.dart';
import 'package:refilc_kreta_api/models/subject.dart';
import 'package:refilc_kreta_api/models/week.dart';
import 'package:refilc_kreta_api/providers/absence_provider.dart';
import 'package:refilc_kreta_api/providers/grade_provider.dart';
import 'package:refilc_kreta_api/providers/timetable_provider.dart';
import 'package:refilc_mobile_ui/common/bottom_sheet_menu/bottom_sheet_menu.dart';
import 'package:refilc_mobile_ui/common/bottom_sheet_menu/bottom_sheet_menu_item.dart';
import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart';
import 'package:refilc_mobile_ui/common/filter_bar.dart';
import 'package:refilc_mobile_ui/common/material_action_button.dart';
import 'package:refilc/ui/widgets/grade/grade_tile.dart';
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
import 'package:refilc_mobile_ui/common/system_chrome.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:i18n_extension/i18n_widget.dart';
import 'package:provider/provider.dart';
import 'package:refilc_mobile_ui/common/screens.i18n.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
import 'package:refilc/models/icon_pack.dart';
import 'package:refilc/utils/format.dart';
import 'package:refilc_mobile_ui/screens/settings/theme_screen.dart';
import 'package:refilc_plus/models/premium_scopes.dart';
import 'package:refilc_plus/providers/premium_provider.dart';
import 'package:refilc_plus/ui/mobile/premium/upsell.dart';
class SettingsHelper {
static const Map<String, String> langMap = {
"en": "🇬🇧 English",
"hu": "🇭🇺 Magyar",
"de": "🇩🇪 Deutsch"
};
static const List<String> fontList = [
"Montserrat",
"Merienda",
"M PLUS Code Latin",
"Figtree",
"Fira Code",
"Vollkorn",
];
static const Map<Pages, String> pageTitle = {
Pages.home: "home",
Pages.grades: "grades",
Pages.timetable: "timetable",
Pages.messages: "messages",
Pages.absences: "absences",
};
static Map<VibrationStrength, String> vibrationTitle = {
VibrationStrength.off: "voff",
VibrationStrength.light: "vlight",
VibrationStrength.medium: "vmedium",
VibrationStrength.strong: "vstrong",
};
static Map<Pages, String> localizedPageTitles() => pageTitle
.map((key, value) => MapEntry(key, ScreensLocalization(value).i18n));
static Map<VibrationStrength, String> localizedVibrationTitles() =>
vibrationTitle
.map((key, value) => MapEntry(key, SettingsLocalization(value).i18n));
static void language(BuildContext context) {
showBottomSheetMenu(
context,
items: List.generate(langMap.length, (index) {
String lang = langMap.keys.toList()[index];
return BottomSheetMenuItem(
onPressed: () {
Provider.of<SettingsProvider>(context, listen: false)
.update(language: lang);
I18n.of(context).locale = Locale(lang, lang.toUpperCase());
Navigator.of(context).maybePop();
if (Platform.isAndroid || Platform.isIOS) {
setupQuickActions();
}
},
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(langMap.values.toList()[index]),
if (lang == I18n.of(context).locale.languageCode)
Icon(
Icons.check_circle,
color: Theme.of(context).colorScheme.secondary,
),
],
),
);
}),
);
}
// static void uwuMode(BuildContext context, value) {
// final settings = Provider.of<SettingsProvider>(context, listen: false);
// if (value) {
// I18n.of(context).locale = const Locale('uw', 'UW');
// } else {
// I18n.of(context).locale =
// Locale(settings.language, settings.language.toUpperCase());
// }
// if (Platform.isAndroid || Platform.isIOS) {
// setupQuickActions();
// }
// }
static void fontFamily(BuildContext context) {
SettingsProvider settings =
Provider.of<SettingsProvider>(context, listen: false);
showBottomSheetMenu(
context,
items: List.generate(fontList.length, (index) {
String font = fontList[index];
return BottomSheetMenuItem(
onPressed: () {
settings.update(fontFamily: font == 'Montserrat' ? '' : font);
Provider.of<ThemeModeObserver>(context, listen: false)
.changeTheme(settings.theme, updateNavbarColor: false);
Navigator.of(context).maybePop();
},
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
font,
style: GoogleFonts.getFont(font),
),
if (font == settings.fontFamily ||
font.replaceAll('Montserrat', '') == settings.fontFamily)
Icon(
Icons.check_circle,
color: Theme.of(context).colorScheme.secondary,
),
],
),
);
}),
);
}
static void iconPack(BuildContext context) {
final settings = Provider.of<SettingsProvider>(context, listen: false);
showBottomSheetMenu(
context,
items: List.generate(IconPack.values.length, (index) {
IconPack current = IconPack.values[index];
return BottomSheetMenuItem(
onPressed: () {
settings.update(iconPack: current);
Navigator.of(context).maybePop();
},
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(current.name.capital()),
if (current == settings.iconPack)
Icon(
Icons.check_circle,
color: Theme.of(context).colorScheme.secondary,
),
],
),
);
}),
);
}
static void startPage(BuildContext context) {
Map<Pages, IconData> pageIcons = {
Pages.home: FilcIcons.home,
Pages.grades: FeatherIcons.bookmark,
Pages.timetable: FeatherIcons.calendar,
Pages.messages: FeatherIcons.messageSquare,
Pages.absences: FeatherIcons.clock,
};
showBottomSheetMenu(
context,
items: List.generate(Pages.values.length, (index) {
return BottomSheetMenuItem(
onPressed: () {
Provider.of<SettingsProvider>(context, listen: false)
.update(startPage: Pages.values[index]);
Navigator.of(context).maybePop();
},
title: Row(
children: [
Icon(pageIcons[Pages.values[index]],
size: 20.0, color: Theme.of(context).colorScheme.secondary),
const SizedBox(width: 16.0),
Text(localizedPageTitles()[Pages.values[index]] ?? ""),
const Spacer(),
if (Pages.values[index] ==
Provider.of<SettingsProvider>(context, listen: false)
.startPage)
Icon(
Icons.check_circle,
color: Theme.of(context).colorScheme.secondary,
),
],
),
);
}),
);
}
static void rounding(BuildContext context) {
showRoundedModalBottomSheet(
context,
child: const RoundingSetting(),
);
}
// new v5 roundings
static void newRoundings(BuildContext context, GradeSubject subject) {
showRoundedModalBottomSheet(
context,
child: RoundingSetting(
rounding: subject.customRounding,
subjectId: subject.id,
),
);
}
// end
static void theme(BuildContext context) {
var settings = Provider.of<SettingsProvider>(context, listen: false);
void Function(ThemeMode) setTheme = (mode) {
settings.update(theme: mode);
Provider.of<ThemeModeObserver>(context, listen: false).changeTheme(mode);
Navigator.of(context).maybePop();
};
showBottomSheetMenu(context, items: [
BottomSheetMenuItem(
onPressed: () => setTheme(ThemeMode.system),
title: Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Icon(FeatherIcons.smartphone,
size: 20.0, color: Theme.of(context).colorScheme.secondary),
),
Text(SettingsLocalization("system").i18n),
const Spacer(),
if (settings.theme == ThemeMode.system)
Icon(
Icons.check_circle,
color: Theme.of(context).colorScheme.secondary,
),
],
),
),
BottomSheetMenuItem(
onPressed: () => setTheme(ThemeMode.light),
title: Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Icon(FeatherIcons.sun,
size: 20.0, color: Theme.of(context).colorScheme.secondary),
),
Text(SettingsLocalization("light").i18n),
const Spacer(),
if (settings.theme == ThemeMode.light)
Icon(
Icons.check_circle,
color: Theme.of(context).colorScheme.secondary,
),
],
),
),
BottomSheetMenuItem(
onPressed: () => setTheme(ThemeMode.dark),
title: Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Icon(FeatherIcons.moon,
size: 20.0, color: Theme.of(context).colorScheme.secondary),
),
Text(SettingsLocalization("dark").i18n),
const Spacer(),
if (settings.theme == ThemeMode.dark)
Icon(
Icons.check_circle,
color: Theme.of(context).colorScheme.secondary,
),
],
),
)
]);
}
static void accentColor(BuildContext context) {
Navigator.of(context, rootNavigator: true).push(
PageRouteBuilder(
pageBuilder: (context, _, __) =>
const PremiumCustomAccentColorSetting(),
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
),
);
}
static void gradeColors(BuildContext context) {
showRoundedModalBottomSheet(
context,
child: const GradeColorsSetting(),
);
}
static void liveActivityColor(BuildContext context) {
showRoundedModalBottomSheet(
context,
child: const LiveActivityColorSetting(),
);
}
static void vibrate(BuildContext context) {
showBottomSheetMenu(
context,
items: List.generate(VibrationStrength.values.length, (index) {
VibrationStrength value = VibrationStrength.values[index];
return BottomSheetMenuItem(
onPressed: () {
Provider.of<SettingsProvider>(context, listen: false)
.update(vibrate: value);
Navigator.of(context).maybePop();
},
title: Row(
children: [
Container(
width: 12.0,
height: 12.0,
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.secondary
.withOpacity((index + 1) / (vibrationTitle.length + 1)),
shape: BoxShape.circle,
),
),
const SizedBox(width: 16.0),
Text(localizedVibrationTitles()[value] ?? "?"),
const Spacer(),
if (value ==
Provider.of<SettingsProvider>(context, listen: false).vibrate)
Icon(
Icons.check_circle,
color: Theme.of(context).colorScheme.secondary,
),
],
),
);
}),
);
}
static void bellDelay(BuildContext context) {
showRoundedModalBottomSheet(
context,
child: const BellDelaySetting(),
);
}
// v5 user changer
static void changeCurrentUser(BuildContext context, List<Widget> accountTiles,
int len, String addUsrLocTxt) {
showBottomSheetMenu(
context,
items: List.generate(len, (index) {
if (index == accountTiles.length) {
return const SizedBox(
height: 10.0,
);
// return Center(
// child: Container(
// margin: const EdgeInsets.only(top: 12.0, bottom: 4.0),
// height: 3.0,
// width: 175.0,
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(12.0),
// color: AppColors.of(context).text.withOpacity(.25),
// ),
// ),
// );
} else if (index == accountTiles.length + 1) {
return PanelButton(
onPressed: () {
if (!Provider.of<PremiumProvider>(context, listen: false)
.hasScope(PremiumScopes.maxTwoAccounts)) {
PremiumLockedFeatureUpsell.show(
context: context, feature: PremiumFeature.moreAccounts);
return;
}
Navigator.of(context).pushNamed("login_back").then((value) {
setSystemChrome(context);
});
},
title: Text(addUsrLocTxt),
leading: const Padding(
padding: EdgeInsets.only(left: 8.22, right: 6.9),
child: Icon(FeatherIcons.userPlus),
),
);
} else {
return accountTiles[index];
}
}),
);
}
// v5 grade rarity texts
static void surpriseGradeRarityText(
BuildContext context, {
required String title,
required String cancel,
required String done,
required List<String> rarities,
}) {
showRoundedModalBottomSheet(
context,
child: GradeRarityTextSetting(
title: title,
cancel: cancel,
done: done,
defaultRarities: rarities,
),
);
}
}
// Rounding modal
class RoundingSetting extends StatefulWidget {
const RoundingSetting({super.key, this.rounding, this.subjectId});
final double? rounding;
final String? subjectId;
@override
_RoundingSettingState createState() => _RoundingSettingState();
}
class _RoundingSettingState extends State<RoundingSetting> {
late double rounding;
@override
void initState() {
super.initState();
rounding = (widget.rounding ??
Provider.of<SettingsProvider>(context, listen: false).rounding) /
10;
}
@override
Widget build(BuildContext context) {
UserProvider userProvider =
Provider.of<UserProvider>(context, listen: false);
DatabaseProvider databaseProvider =
Provider.of<DatabaseProvider>(context, listen: false);
int roundingResult;
if (4.5 >= 4.5.floor() + rounding) {
roundingResult = 5;
} else {
roundingResult = 4;
}
return Column(children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Slider(
value: rounding,
min: 0.1,
max: 0.9,
divisions: 8,
label: rounding.toStringAsFixed(1),
activeColor: Theme.of(context).colorScheme.secondary,
thumbColor: Theme.of(context).colorScheme.secondary,
onChanged: (v) => setState(() => rounding = v),
),
),
Container(
width: 50.0,
padding: const EdgeInsets.only(right: 16.0),
child: Center(
child: Text(rounding.toStringAsFixed(1),
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 18.0,
)),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("4.5",
style: TextStyle(fontSize: 26.0, fontWeight: FontWeight.w500)),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Icon(FeatherIcons.arrowRight, color: Colors.grey),
),
GradeValueWidget(GradeValue(roundingResult, "", "", 100),
fill: true, size: 32.0),
],
),
Padding(
padding: const EdgeInsets.only(bottom: 12.0, top: 6.0),
child: MaterialActionButton(
child: Text(SettingsLocalization("done").i18n),
onPressed: () async {
if (widget.rounding == null) {
Provider.of<SettingsProvider>(context, listen: false)
.update(rounding: (rounding * 10).toInt());
} else {
Map<String, String> roundings = await databaseProvider.userQuery
.getRoundings(userId: userProvider.id!);
roundings[widget.subjectId!] = (rounding * 10).toStringAsFixed(2);
await databaseProvider.userStore
.storeRoundings(roundings, userId: userProvider.id!);
await Provider.of<GradeProvider>(context, listen: false)
.convertBySettings();
await Provider.of<TimetableProvider>(context, listen: false)
.convertBySettings();
await Provider.of<AbsenceProvider>(context, listen: false)
.convertBySettings();
}
// ik i'm like a kreta dev, but setstate isn't working, so please don't kill me bye :3
// actually it also looks good and it's kinda useful
Navigator.of(context).pop();
Navigator.of(context).pop();
// setState(() {});
},
),
),
]);
}
}
// Bell Delay Modal
class BellDelaySetting extends StatefulWidget {
const BellDelaySetting({super.key});
@override
State<BellDelaySetting> createState() => _BellDelaySettingState();
}
class _BellDelaySettingState extends State<BellDelaySetting>
with SingleTickerProviderStateMixin {
late TabController _tabController;
late Duration currentDelay;
@override
void initState() {
super.initState();
_tabController = TabController(
length: 2,
vsync: this,
initialIndex:
Provider.of<SettingsProvider>(context, listen: false).bellDelay > 0
? 1
: 0);
currentDelay = Duration(
seconds:
Provider.of<SettingsProvider>(context, listen: false).bellDelay);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
FilterBar(
scrollable: true,
tabAlignment: TabAlignment.center,
items: [
Tab(text: SettingsLocalization("delay").i18n),
Tab(text: SettingsLocalization("hurry").i18n),
],
controller: _tabController,
onTap: (i) async {
// swap current page with target page
setState(() {
currentDelay = i == 0 ? -currentDelay.abs() : currentDelay.abs();
});
},
),
SizedBox(
height: 200,
child: CupertinoTheme(
data: CupertinoThemeData(
brightness: Theme.of(context).brightness,
),
child: CupertinoTimerPicker(
key: UniqueKey(),
mode: CupertinoTimerPickerMode.ms,
initialTimerDuration: currentDelay.abs(),
onTimerDurationChanged: (Duration d) {
HapticFeedback.selectionClick();
currentDelay = _tabController.index == 0 ? -d : d;
},
),
),
),
Text(SettingsLocalization("sync_help").i18n,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w500,
color: AppColors.of(context).text.withOpacity(.75))),
Padding(
padding: const EdgeInsets.only(bottom: 12.0, top: 6.0),
child: Column(
children: [
MaterialActionButton(
backgroundColor: Theme.of(context).colorScheme.primary,
child: Text(SettingsLocalization("sync").i18n),
onPressed: () {
final lessonProvider =
Provider.of<TimetableProvider>(context, listen: false);
Duration? closest;
DateTime now = DateTime.now();
for (var lesson
in lessonProvider.getWeek(Week.current()) ?? []) {
Duration sdiff = lesson.start.difference(now);
Duration ediff = lesson.end.difference(now);
if (closest == null || sdiff.abs() < closest.abs()) {
closest = sdiff;
}
if (ediff.abs() < closest.abs()) closest = ediff;
}
if (closest != null) {
if (closest.inHours.abs() >= 1) return;
currentDelay = closest;
Provider.of<SettingsProvider>(context, listen: false)
.update(bellDelay: currentDelay.inSeconds);
_tabController.index = currentDelay.inSeconds > 0 ? 1 : 0;
setState(() {});
}
},
),
MaterialActionButton(
child: Text(SettingsLocalization("done").i18n),
onPressed: () {
//Provider.of<SettingsProvider>(context, listen: false).update(context, rounding: (r * 10).toInt());
Provider.of<SettingsProvider>(context, listen: false)
.update(bellDelay: currentDelay.inSeconds);
Navigator.of(context).maybePop();
},
),
],
),
),
],
);
}
}
class GradeColorsSetting extends StatefulWidget {
const GradeColorsSetting({super.key});
@override
_GradeColorsSettingState createState() => _GradeColorsSettingState();
}
class _GradeColorsSettingState extends State<GradeColorsSetting> {
Color currentColor = const Color(0x00000000);
late SettingsProvider settings;
@override
void initState() {
super.initState();
settings = Provider.of<SettingsProvider>(context, listen: false);
}
@override
Widget build(BuildContext context) {
return Column(children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(5, (index) {
return ClipOval(
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () {
currentColor = settings.gradeColors[index];
showRoundedModalBottomSheet(
context,
child: Column(children: [
MaterialColorPicker(
selectedColor: settings.gradeColors[index],
onColorChange: (v) {
setState(() {
currentColor = v;
});
},
allowShades: true,
elevation: 0,
physics: const NeverScrollableScrollPhysics(),
),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MaterialActionButton(
onPressed: () {
List<Color> colors =
List.castFrom(settings.gradeColors);
var defaultColors =
SettingsProvider.defaultSettings()
.gradeColors;
colors[index] = defaultColors[index];
settings.update(gradeColors: colors);
Navigator.of(context).maybePop();
},
child: Text(SettingsLocalization("reset").i18n),
),
MaterialActionButton(
onPressed: () {
List<Color> colors =
List.castFrom(settings.gradeColors);
colors[index] = currentColor.withAlpha(255);
settings.update(
gradeColors: settings.gradeColors);
Navigator.of(context).maybePop();
},
child: Text(SettingsLocalization("done").i18n),
),
],
),
),
]),
).then((value) => setState(() {}));
},
child: GradeValueWidget(GradeValue(index + 1, "", "", 0),
fill: true, size: 36.0),
),
),
);
}),
),
),
]);
}
}
class GradeRarityTextSetting extends StatefulWidget {
const GradeRarityTextSetting({
super.key,
required this.title,
required this.cancel,
required this.done,
required this.defaultRarities,
});
final String title;
final String cancel;
final String done;
final List<String> defaultRarities;
@override
_GradeRarityTextSettingState createState() => _GradeRarityTextSettingState();
}
class _GradeRarityTextSettingState extends State<GradeRarityTextSetting> {
late SettingsProvider settings;
late DatabaseProvider db;
late UserProvider user;
final _rarityText = TextEditingController();
@override
void initState() {
super.initState();
settings = Provider.of<SettingsProvider>(context, listen: false);
db = Provider.of<DatabaseProvider>(context, listen: false);
user = Provider.of<UserProvider>(context, listen: false);
}
@override
Widget build(BuildContext context) {
return Column(children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(5, (index) {
return ClipOval(
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () async {
showRenameDialog(
title: widget.title,
cancel: widget.cancel,
done: widget.done,
rarities:
await db.userQuery.getGradeRarities(userId: user.id!),
gradeIndex: (index + 1).toString(),
defaultRarities: widget.defaultRarities,
);
},
child: GradeValueWidget(GradeValue(index + 1, "", "", 0),
fill: true, size: 36.0),
),
),
);
}),
),
),
]);
}
void showRenameDialog(
{required String title,
required String cancel,
required String done,
required Map<String, String> rarities,
required String gradeIndex,
required List<String> defaultRarities,
required}) {
showDialog(
context: context,
builder: (context) => StatefulBuilder(builder: (context, setS) {
String? rr = rarities[gradeIndex];
rr ??= '';
_rarityText.text = rr;
return AlertDialog(
title: Text(title),
content: TextField(
controller: _rarityText,
autofocus: true,
decoration: InputDecoration(
border: const OutlineInputBorder(),
label: Text(defaultRarities[int.parse(gradeIndex) - 1]),
suffixIcon: IconButton(
icon: const Icon(FeatherIcons.x),
onPressed: () {
setState(() {
_rarityText.clear();
});
},
),
),
),
actions: [
TextButton(
child: Text(
cancel,
style: const TextStyle(fontWeight: FontWeight.w500),
),
onPressed: () {
Navigator.of(context).maybePop();
},
),
TextButton(
child: Text(
done,
style: const TextStyle(fontWeight: FontWeight.w500),
),
onPressed: () {
rarities[gradeIndex] = _rarityText.text;
Provider.of<DatabaseProvider>(context, listen: false)
.userStore
.storeGradeRarities(rarities, userId: user.id!);
Navigator.of(context).pop(true);
},
),
],
);
}),
).then((val) {
_rarityText.clear();
});
}
}
class LiveActivityColorSetting extends StatefulWidget {
const LiveActivityColorSetting({super.key});
@override
_LiveActivityColorSettingState createState() =>
_LiveActivityColorSettingState();
}
class _LiveActivityColorSettingState extends State<LiveActivityColorSetting> {
late SettingsProvider settings;
Color currentColor = const Color(0x00000000);
@override
void initState() {
super.initState();
settings = Provider.of<SettingsProvider>(context, listen: false);
}
@override
Widget build(BuildContext context) {
return Column(children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Material(
type: MaterialType.transparency,
child: Column(children: [
MaterialColorPicker(
allowShades: false,
selectedColor: settings.liveActivityColor,
onMainColorChange: (k) {
setState(() {
currentColor = k as Color;
settings.update(
liveActivityColor: currentColor.withAlpha(255));
Navigator.of(context).maybePop();
});
},
elevation: 0,
physics: const NeverScrollableScrollPhysics(),
),
Padding(
padding: const EdgeInsets.only(bottom: 8.0, top: 40.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MaterialActionButton(
onPressed: () {
var defaultColors =
SettingsProvider.defaultSettings().liveActivityColor;
settings.update(liveActivityColor: defaultColors);
Navigator.of(context).maybePop();
},
child: Text(SettingsLocalization("reset").i18n),
),
],
),
),
]),
),
),
]);
}
}

View File

@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
Route settingsRoute(Widget widget) {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => widget,
transitionDuration: const Duration(milliseconds: 500),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
var curve = Curves.ease;
var curveTween = CurveTween(curve: curve);
var begin = const Offset(0.0, 1.0);
var end = Offset.zero;
var tween = Tween(begin: begin, end: end).chain(curveTween);
var offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
},
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,303 @@
import 'package:i18n_extension/i18n_extension.dart';
extension SettingsLocalization on String {
static final _t = Translations.byLocale("hu_hu") +
{
"en_en": {
"personal_details": "Personal Details",
"open_dkt": "Open DCS",
"edit_nickname": "Edit Nickname",
"edit_profile_picture": "Edit Profile Picture",
"remove_profile_picture": "Remove Profile Picture",
"select_profile_picture": "to select a picture",
"click_here": "Click here",
"light": "Light",
"dark": "Dark",
"system": "System",
"add_user": "Add User",
"log_out": "Log Out",
"update_available": "Update Available",
"general": "General",
"language": "Language",
"startpage": "Startpage",
"rounding": "Rounding",
"appearance": "Appearance",
"theme": "Theme",
"color": "Color",
"grade_colors": "Grade Colors",
"live_activity_color": "Live Activity Color",
"notifications": "Notifications",
"popups": "Pop-up Alerts",
"news": "News",
"extras": "Extras",
"about": "About",
"supporters": "Supporters",
"privacy": "Privacy Policy",
"licenses": "Licenses",
"vibrate": "Vibration",
"voff": "Off",
"vlight": "Light",
"vmedium": "Medium",
"vstrong": "Strong",
"cancel": "Cancel",
"done": "Done",
"reset": "Reset",
"open": "Open",
"data_collected":
"Data collected: Platform (eg. iOS), App version (eg. 5.0.0), Unique Install Identifier",
"Analytics": "Analytics",
"Anonymous Usage Analytics": "Anonymous Usage Analytics",
"graph_class_avg": "Class average on graph",
"goodstudent": "Good student mode",
"attention": "Attention!",
"goodstudent_disclaimer":
"reFilc can not be held liable for the usage of this feature.\n\n(if your mother beats you up because you showed her fake grades, you can only blame yourself for it)",
"understand": "I understand",
"secret": "Secret Settings",
"bell_delay": "Bell Delay",
"delay": "Delay",
"hurry": "Hurry",
"sync": "Synchronize",
"sync_help": "Press the Synchronize button when the bell rings.",
"surprise_grades": "Surprise Grades",
"icon_pack": "Icon Pack",
"change_username": "Set a nickname",
"Accent Color": "Accent Color",
"Background Color": "Background Color",
"Highlight Color": "Highlight Color",
"Adaptive Theme": "Adaptive Theme",
"presentation": "Presentation mode",
"uwufymode": "UwU-fied mode (hungarian)",
"devmoretaps": "You are %s taps away from Developer Mode.",
"devactivated": "Developer Mode successfully activated.",
"devsettings": "Developer Settings",
"devmode": "Developer Mode",
"copy_jwt": "Copy JWT",
"welcome_msg": "Welcome Message",
"default": "Dynamic",
"edit_welcome_msg": "Edit welcome message",
"shadow_effect": "Shadow Effect",
"app_icon": "App Icon",
"settings": "Settings",
"personalization": "Personalization",
"edit": "Edit",
"switch_account": "Switch Account",
"subjects": "Subjects",
"select_subject": "Select Subject",
"own_paints": "Own Paints",
"dl_paint": "Redeemed",
"public_paint": "Public Paints",
"no_pub_paint": "No Public Paints",
"enter_id": "Enter Paint ID",
"paint_id": "Paint ID...",
"set_as_current": "Set as current",
"share_subj_theme": "Share Theme",
"no_name": "Untitled Paint",
"current_paint": "Current Paint",
"rename_subjects": "Rename Subjects",
"rename_teachers": "Rename Teachers",
"show_breaks": "Show Breaks",
"fonts": "Fonts",
"font_family": "Font Family",
},
"hu_hu": {
"personal_details": "Személyes információk",
"open_dkt": "DKT megnyitása",
"edit_nickname": "Becenév szerkesztése",
"edit_profile_picture": "Profil-kép szerkesztése",
"remove_profile_picture": "Profil-kép törlése",
"select_profile_picture": "a kép kiválasztásához",
"click_here": "Kattints ide",
"light": "Világos",
"dark": "Sötét",
"system": "Rendszer",
"add_user": "Felhasználó hozzáadása",
"log_out": "Kijelentkezés",
"update_available": "Frissítés elérhető",
"general": "Általános",
"language": "Nyelv",
"startpage": "Kezdőlap",
"rounding": "Kerekítés",
"appearance": "Kinézet",
"theme": "Téma",
"color": "Színek",
"grade_colors": "Jegyek színei",
"live_activity_color": "Live Activity színe",
"notifications": "Értesítések",
"popups": "Pop-up értesítések",
"news": "Hírek",
"extras": "Extrák",
"about": "Névjegy",
"supporters": "Támogatók",
"privacy": "Adatvédelmi irányelvek",
"licenses": "Licencek",
"vibrate": "Rezgés",
"voff": "Kikapcsolás",
"vlight": "Alacsony",
"vmedium": "Közepes",
"vstrong": "Erős",
"cancel": "Mégsem",
"done": "Kész",
"reset": "Visszaállítás",
"open": "Megnyitás",
"data_collected":
"Gyűjtött adat: platform (pl.: iOS), app verzió (pl.: 5.0.0), egyedi telepítési azonosító",
"Analytics": "Analitika",
"Anonymous Usage Analytics": "Névtelen használati analitika",
"graph_class_avg": "Osztályátlag a grafikonon",
"goodstudent": "Jó tanuló mód",
"attention": "Figyelem!",
"goodstudent_disclaimer":
"A reFilc minden felelősséget elhárít a funkció használatával kapcsolatban.\n\n(Értsd: ha az anyád megver, mert megtévesztő ábrákat mutattál neki, azért csakis magadat hibáztathatod.)",
"understand": "Értem",
"secret": "Titkos Beállítások",
"bell_delay": "Csengő eltolódása",
"delay": "Késleltetés",
"hurry": "Siettetés",
"sync": "Szinkronizálás",
"sync_help": "Csengetéskor nyomd meg a Szinkronizálás gombot.",
"surprise_grades": "Meglepetés jegyek",
"icon_pack": "Ikon séma",
"change_username": "Becenév beállítása",
"Accent Color": "Egyedi szín",
"Background Color": "Háttér színe",
"Highlight Color": "Panelek színe",
"Adaptive Theme": "Adaptív téma",
"presentation": "Bemutató mód",
"uwufymode": "UwU mód (magyar)",
"devmoretaps": "Még %s koppintásra vagy a Fejlesztői módtól.",
"devactivated": "Fejlesztői mód sikeresen aktiválva.",
"devsettings": "Fejlesztői Beállítások",
"devmode": "Fejlesztői mód",
"copy_jwt": "JWT másolása",
"welcome_msg": "Üdvözlő üzenet",
"default": "Dinamikus",
"edit_welcome_msg": "Üdvözlő üzenet szerkesztése",
"shadow_effect": "Árnyékhatás",
"app_icon": "Alkalmazásikon",
"settings": "Beállítások",
"personalization": "Személyre szabás",
"edit": "Szerkesztés",
"switch_account": "Fiókváltás",
"subjects": "Tantárgyak",
"select_subject": "Válassz tantárgyat",
"own_paints": "Saját témák",
"dl_paint": "Beszerzett",
"public_paint": "Nyilvános témák",
"no_pub_paint": "Nincsenek nyilvános festékek",
"enter_id": "Azonosító megadása",
"paint_id": "Téma azonosító...",
"set_as_current": "Beállítás jelenleginek",
"share_subj_theme": "Téma Megosztás",
"no_name": "Névtelen téma",
"current_paint": "Jelenlegi téma",
"rename_subjects": "Tantárgyak átnevezése",
"rename_teachers": "Tanárok átnevezése",
"show_breaks": "Szünetek megjelenítése",
"fonts": "Betűk",
"font_family": "Betűtípus",
},
"de_de": {
"personal_details": "Persönliche Angaben",
"open_dkt": "Öffnen RDZ",
"edit_nickname": "Spitznamen bearbeiten",
"edit_profile_picture": "Profilbild bearbeiten",
"remove_profile_picture": "Profilbild entfernen",
"select_profile_picture": "um ein Bild auszuwählen",
"click_here": "Klick hier",
"light": "Licht",
"dark": "Dunkel",
"system": "System",
"add_user": "Benutzer hinzufügen",
"log_out": "Abmelden",
"update_available": "Update verfügbar",
"general": "Allgemein",
"language": "Sprache",
"startpage": "Startseite",
"rounding": "Rundung",
"appearance": "Erscheinungsbild",
"theme": "Thema",
"color": "Farbe",
"grade_colors": "Grad Farben",
"live_activity_color": "Live Activity Farben",
"notifications": "Mitteilung",
"popups": "Popup-Nachrichten",
"news": "Nachrichten",
"extras": "Extras",
"about": "Informationen",
"supporters": "Unterstützer",
"privacy": "Datenschutzbestimmungen",
"licenses": "Lizenzen",
"vibrate": "Vibration",
"voff": "Aus",
"vlight": "Leicht",
"vmedium": "Mittel",
"vstrong": "Stark",
"cancel": "Abbrechen",
"done": "Fertig",
"reset": "Zurücksetzen",
"open": "Öffnen",
"data_collected":
"Erhobene Daten: Plattform (z.B. iOS), App version (z.B. 5.0.0), Eindeutige Installationskennung",
"Analytics": "Analytik",
"Anonymous Usage Analytics": "Anonyme Nutzungsanalyse",
"graph_class_avg": "Klassendurchschnitt in der Grafik",
"goodstudent": "Guter Student Modus",
"attention": "Achtung!",
"goodstudent_disclaimer":
"reFilc kann nicht für die Nutzung dieser Funktion haftbar gemacht werden.\n\n(Wenn deine Mutter dich verprügelt, weil du ihr falsche Noten gezeigt hast, kannst du dir nur die Schuld dafür geben)",
"understand": "Ich verstehe",
"secret": "Geheime Einstellungen",
"bell_delay": "Klingelverzögerung",
"delay": "Verzögern",
"hurry": "Eile",
"sync": "Synchronisieren",
"sync_help": "Drücken Sie die Sync-Taste, wenn die Glocke läutet.",
"surprise_grades": "Überraschungsnoten",
"icon_pack": "Icon-Pack",
"change_username": "Einen Spitznamen festlegen",
"Accent Color": "Accent Color",
"Background Color": "Background Color",
"Highlight Color": "Highlight Color",
"Adaptive Theme": "Adaptive Theme",
"presentation": "Präsentationsmodus",
"uwufymode": "UwU-Modus (ungarisch)",
"devmoretaps": "Sie sind %s Taps vom Entwicklermodus entfernt.",
"devactivated": "Entwicklermodus erfolgreich aktiviert.",
"devsettings": "Entwickleroptionen",
"devmode": "Entwicklermodus",
"copy_jwt": "JWT kopieren",
"welcome_msg": "Willkommensnachricht",
"default": "Dynamisch",
"edit_welcome_msg": "Begrüßungsnachricht bearbeiten",
"shadow_effect": "Schatteneffekt",
"app_icon": "App-Symbol",
"settings": "Einstellungen",
"personalization": "Personalisierung",
"edit": "Bearbeiten",
"switch_account": "Benutzer wechseln",
"subjects": "Themen",
"select_subject": "Fach auswählen",
"own_paints": "Meine Themen",
"dl_paint": "Eingelöst",
"public_paint": "Öffentliche Themen",
"no_pub_paint": "Keine öffentlichen Anstriche",
"enter_id": "Themen-ID eingeben",
"paint_id": "Themen-ID...",
"set_as_current": "Als aktuell einstellen",
"share_subj_theme": "Thema Teilen",
"no_name": "Anonymes Thema",
"current_paint": "Aktuelles Thema",
"rename_subjects": "Fächer umbenennen",
"rename_teachers": "Lehrer umbenennen",
"show_breaks": "Pausen anzeigen",
"fonts": "Schriftarten",
"font_family": "Schriftfamilie",
},
};
String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t);
}

View File

@@ -0,0 +1,398 @@
// ignore_for_file: use_build_context_synchronously
import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/models/settings.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc/utils/format.dart';
import 'package:refilc_kreta_api/models/subject.dart';
import 'package:refilc_kreta_api/models/teacher.dart';
import 'package:refilc_kreta_api/providers/absence_provider.dart';
import 'package:refilc_kreta_api/providers/grade_provider.dart';
import 'package:refilc_kreta_api/providers/timetable_provider.dart';
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
// import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
import 'edit_subject.i18n.dart';
class EditSubjectScreen extends StatefulWidget {
const EditSubjectScreen({
super.key,
required this.subject,
required this.teacher,
});
final GradeSubject subject;
final Teacher teacher;
@override
EditSubjectScreenState createState() => EditSubjectScreenState();
}
class EditSubjectScreenState extends State<EditSubjectScreen> {
late SettingsProvider settingsProvider;
late DatabaseProvider databaseProvider;
late UserProvider user;
final _subjectName = TextEditingController();
final _teacherName = TextEditingController();
@override
Widget build(BuildContext context) {
settingsProvider = Provider.of<SettingsProvider>(context);
databaseProvider = Provider.of<DatabaseProvider>(context);
user = Provider.of<UserProvider>(context);
return Scaffold(
appBar: AppBar(
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
leading: BackButton(color: AppColors.of(context).text),
title: Text(
(widget.subject.isRenamed && settingsProvider.renamedSubjectsEnabled
? widget.subject.renamedTo
: widget.subject.name.capital()) ??
'',
style: TextStyle(
color: AppColors.of(context).text,
fontStyle: settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: FontStyle.normal,
),
),
actions: [
IconButton(
onPressed: () async {
Map<String, String> subs = await databaseProvider.userQuery
.renamedSubjects(userId: user.id!);
subs.remove(widget.subject.id);
await databaseProvider.userStore
.storeRenamedSubjects(subs, userId: user.id!);
Map<String, String> teach = await databaseProvider.userQuery
.renamedTeachers(userId: user.id!);
teach.remove(widget.teacher.id);
await databaseProvider.userStore
.storeRenamedTeachers(teach, userId: user.id!);
updateProviders();
// im crying rn
Navigator.of(context).pop();
Navigator.of(context).pop();
setState(() {});
},
icon: const Icon(FeatherIcons.trash2),
),
const SizedBox(
width: 8.0,
),
],
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: Column(
children: [
// rename subject
SplittedPanel(
padding: const EdgeInsets.only(top: 8.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
onPressed: () {
showSubjectRenameDialog();
},
title: Text("rename_it".i18n),
leading: Icon(
FeatherIcons.penTool,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
// rename teacher
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
onPressed: () {
showTeacherRenameDialog();
},
title: Text("rename_te".i18n),
leading: Icon(
FeatherIcons.user,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
// edit rounding
// SplittedPanel(
// padding: const EdgeInsets.only(top: 9.0),
// cardPadding: const EdgeInsets.all(4.0),
// isSeparated: true,
// children: [
// PanelButton(
// onPressed: () {
// SettingsHelper.newRoundings(context, widget.subject);
// setState(() {});
// },
// title: Text(
// "rounding".i18n,
// style: TextStyle(
// color: AppColors.of(context).text.withOpacity(.95),
// ),
// ),
// leading: Icon(
// FeatherIcons.gitCommit,
// size: 22.0,
// color: AppColors.of(context).text.withOpacity(.95),
// ),
// trailing: Text(
// ((widget.subject.customRounding ??
// settingsProvider.rounding) /
// 10)
// .toStringAsFixed(1),
// style: const TextStyle(fontSize: 14.0),
// ),
// borderRadius: const BorderRadius.vertical(
// top: Radius.circular(12.0),
// bottom: Radius.circular(12.0),
// ),
// ),
// ],
// ),
],
),
),
),
);
}
// rename dialogs
void showSubjectRenameDialog() {
_subjectName.text = widget.subject.renamedTo ?? '';
showDialog(
context: context,
builder: (context) => StatefulBuilder(builder: (context, setS) {
return AlertDialog(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(14.0))),
title: Text("rename_it".i18n),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background,
borderRadius:
const BorderRadius.all(Radius.circular(12.0))),
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Center(
child: Text(
widget.subject.name,
style: const TextStyle(
fontWeight: FontWeight.w500, fontSize: 16.0),
),
),
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Icon(FeatherIcons.arrowDown, size: 32),
),
TextField(
controller: _subjectName,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide:
const BorderSide(color: Colors.grey, width: 1.5),
borderRadius: BorderRadius.circular(12.0),
),
focusedBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: Colors.grey, width: 1.5),
borderRadius: BorderRadius.circular(12.0),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
hintText: "modified_name".i18n,
suffixIcon: IconButton(
icon: const Icon(
FeatherIcons.x,
color: Colors.grey,
),
onPressed: () {
setState(() {
_subjectName.text = "";
});
},
),
),
),
],
),
actions: [
TextButton(
child: Text(
"cancel".i18n,
style: const TextStyle(fontWeight: FontWeight.w500),
),
onPressed: () {
Navigator.of(context).maybePop();
},
),
TextButton(
child: Text(
"done".i18n,
style: const TextStyle(fontWeight: FontWeight.w500),
),
onPressed: () async {
Map<String, String> renamedSubjs = await databaseProvider
.userQuery
.renamedSubjects(userId: user.id!);
renamedSubjs[widget.subject.id] = _subjectName.text;
await databaseProvider.userStore
.storeRenamedSubjects(renamedSubjs, userId: user.id!);
updateProviders();
Navigator.of(context).pop();
setState(() {});
},
),
],
);
}),
).then((val) {
_subjectName.text = "";
});
}
void showTeacherRenameDialog() {
_teacherName.text = widget.teacher.renamedTo ?? '';
showDialog(
context: context,
builder: (context) => StatefulBuilder(builder: (context, setS) {
return AlertDialog(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(14.0))),
title: Text("rename_te".i18n),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background,
borderRadius:
const BorderRadius.all(Radius.circular(12.0))),
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Center(
child: Text(
widget.teacher.name,
style: const TextStyle(
fontWeight: FontWeight.w500, fontSize: 16.0),
),
),
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Icon(FeatherIcons.arrowDown, size: 32),
),
TextField(
controller: _teacherName,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide:
const BorderSide(color: Colors.grey, width: 1.5),
borderRadius: BorderRadius.circular(12.0),
),
focusedBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: Colors.grey, width: 1.5),
borderRadius: BorderRadius.circular(12.0),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
hintText: "modified_name".i18n,
suffixIcon: IconButton(
icon: const Icon(
FeatherIcons.x,
color: Colors.grey,
),
onPressed: () {
setState(() {
_teacherName.text = "";
});
},
),
),
),
],
),
actions: [
TextButton(
child: Text(
"cancel".i18n,
style: const TextStyle(fontWeight: FontWeight.w500),
),
onPressed: () {
Navigator.of(context).maybePop();
},
),
TextButton(
child: Text(
"done".i18n,
style: const TextStyle(fontWeight: FontWeight.w500),
),
onPressed: () async {
Map<String, String> renamedTeach = await databaseProvider
.userQuery
.renamedTeachers(userId: user.id!);
renamedTeach[widget.teacher.id] = _teacherName.text;
await databaseProvider.userStore
.storeRenamedTeachers(renamedTeach, userId: user.id!);
updateProviders();
Navigator.of(context).pop();
setState(() {});
},
),
],
);
}),
).then((val) {
_teacherName.text = "";
});
}
void updateProviders() async {
await Provider.of<GradeProvider>(context, listen: false)
.convertBySettings();
await Provider.of<TimetableProvider>(context, listen: false)
.convertBySettings();
await Provider.of<AbsenceProvider>(context, listen: false)
.convertBySettings();
}
}

View File

@@ -0,0 +1,42 @@
import 'package:i18n_extension/i18n_extension.dart';
extension SettingsLocalization on String {
static final _t = Translations.byLocale("hu_hu") +
{
"en_en": {
"rename_it": "Rename Subject",
"rename_te": "Rename Teacher",
"rounding": "Rounding",
"gs_mode": "Good Student Mode",
"rename_subject": "Rename Subject",
"modified_name": "Modified Name",
"cancel": "Cancel",
"done": "Done",
},
"hu_hu": {
"rename_it": "Tantárgy átnevezése",
"rename_te": "Tanár átnevezése",
"rounding": "Kerekítés",
"gs_mode": "Jó tanuló mód",
"rename_subject": "Tantárgy átnevezése",
"modified_name": "Módosított név",
"cancel": "Mégse",
"done": "Kész",
},
"de_de": {
"rename_it": "Betreff umbenennen",
"rename_te": "Lehrer umbenennen",
"rounding": "Rundung",
"gs_mode": "Guter Student Modus",
"rename_subject": "Fach umbenennen",
"modified_name": "Geänderter Name",
"cancel": "Abbrechen",
"done": "Erledigt",
},
};
String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t);
}

View File

@@ -0,0 +1,156 @@
// import 'package:refilc/models/settings.dart';
import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/models/settings.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
import 'package:refilc_plus/models/premium_scopes.dart';
import 'package:refilc_plus/providers/premium_provider.dart';
import 'package:refilc_plus/ui/mobile/premium/upsell.dart';
import 'package:refilc_plus/ui/mobile/settings/welcome_message.dart';
// import 'package:provider/provider.dart';
import 'submenu_screen.i18n.dart';
class MenuExtrasSettings extends StatelessWidget {
const MenuExtrasSettings({
super.key,
this.borderRadius = const BorderRadius.vertical(
top: Radius.circular(4.0), bottom: Radius.circular(4.0)),
});
final BorderRadius borderRadius;
@override
Widget build(BuildContext context) {
return PanelButton(
onPressed: () => Navigator.of(context, rootNavigator: true).push(
CupertinoPageRoute(builder: (context) => const ExtrasSettingsScreen()),
),
title: Text("extras".i18n),
leading: Icon(
FeatherIcons.edit,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
trailing: Icon(
FeatherIcons.chevronRight,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
borderRadius: borderRadius,
);
}
}
class ExtrasSettingsScreen extends StatefulWidget {
const ExtrasSettingsScreen({super.key});
@override
ExtrasSettingsScreenState createState() => ExtrasSettingsScreenState();
}
class ExtrasSettingsScreenState extends State<ExtrasSettingsScreen> {
late SettingsProvider settingsProvider;
late UserProvider user;
@override
Widget build(BuildContext context) {
SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context);
UserProvider user = Provider.of<UserProvider>(context);
return Scaffold(
appBar: AppBar(
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
leading: BackButton(color: AppColors.of(context).text),
title: Text(
"extras".i18n,
style: TextStyle(color: AppColors.of(context).text),
),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: Column(
children: [
SplittedPanel(
padding: const EdgeInsets.only(top: 8.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
onPressed: () async {
if (!Provider.of<PremiumProvider>(context, listen: false)
.hasScope(PremiumScopes.customGradeRarities)) {
return PremiumLockedFeatureUpsell.show(
context: context,
feature: PremiumFeature.gradeRarities);
}
// settingsProvider.update(
// gradeOpeningFun: !settingsProvider.gradeOpeningFun);
SettingsHelper.surpriseGradeRarityText(
context,
title: 'rarity_title'.i18n,
cancel: 'cancel'.i18n,
done: 'done'.i18n,
rarities: [
"common".i18n,
"uncommon".i18n,
"rare".i18n,
"epic".i18n,
"legendary".i18n,
],
);
setState(() {});
},
trailingDivider: true,
title: Text(
"surprise_grades".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(
settingsProvider.gradeOpeningFun ? .95 : .25),
),
),
leading: Icon(
FeatherIcons.gift,
size: 22.0,
color: AppColors.of(context).text.withOpacity(
settingsProvider.gradeOpeningFun ? .95 : .25),
),
trailing: Switch(
onChanged: (v) async {
settingsProvider.update(gradeOpeningFun: v);
setState(() {});
},
value: settingsProvider.gradeOpeningFun,
activeColor: Theme.of(context).colorScheme.secondary,
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
WelcomeMessagePanelButton(settingsProvider, user),
],
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,371 @@
import 'package:refilc/models/settings.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc/utils/format.dart';
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
class MenuGeneralSettings extends StatelessWidget {
const MenuGeneralSettings({
super.key,
this.borderRadius = const BorderRadius.vertical(
top: Radius.circular(4.0), bottom: Radius.circular(4.0)),
});
final BorderRadius borderRadius;
@override
Widget build(BuildContext context) {
return PanelButton(
onPressed: () => Navigator.of(context, rootNavigator: true).push(
CupertinoPageRoute(builder: (context) => const GeneralSettingsScreen()),
),
title: Text("general".i18n),
leading: Icon(
FeatherIcons.settings,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
trailing: Icon(
FeatherIcons.chevronRight,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
borderRadius: borderRadius,
);
}
}
class GeneralSettingsScreen extends StatefulWidget {
const GeneralSettingsScreen({super.key});
@override
GeneralSettingsScreenState createState() => GeneralSettingsScreenState();
}
class GeneralSettingsScreenState extends State<GeneralSettingsScreen> {
@override
Widget build(BuildContext context) {
SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context);
String startPageTitle =
SettingsHelper.localizedPageTitles()[settingsProvider.startPage] ?? "?";
String languageText =
SettingsHelper.langMap[settingsProvider.language] ?? "?";
String vibrateTitle = {
VibrationStrength.off: "voff".i18n,
VibrationStrength.light: "vlight".i18n,
VibrationStrength.medium: "vmedium".i18n,
VibrationStrength.strong: "vstrong".i18n,
}[settingsProvider.vibrate] ??
"?";
return Scaffold(
appBar: AppBar(
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
leading: BackButton(color: AppColors.of(context).text),
title: Text(
"general".i18n,
style: TextStyle(color: AppColors.of(context).text),
),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: Column(
children: [
SplittedPanel(
padding: const EdgeInsets.only(top: 8.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
onPressed: () {
SettingsHelper.bellDelay(context);
setState(() {});
},
title: Text(
"bell_delay".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(
settingsProvider.bellDelayEnabled ? .95 : .25),
),
),
leading: Icon(
settingsProvider.bellDelayEnabled
? FeatherIcons.bell
: FeatherIcons.bellOff,
size: 22.0,
color: AppColors.of(context).text.withOpacity(
settingsProvider.bellDelayEnabled ? .95 : .25),
),
trailingDivider: true,
trailing: Switch(
onChanged: (v) =>
settingsProvider.update(bellDelayEnabled: v),
value: settingsProvider.bellDelayEnabled,
activeColor: Theme.of(context).colorScheme.secondary,
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
onPressed: () {
SettingsHelper.rounding(context);
setState(() {});
},
title: Text(
"rounding".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95),
),
),
leading: Icon(
FeatherIcons.gitCommit,
size: 22.0,
color: AppColors.of(context).text.withOpacity(.95),
),
trailing: Text(
(settingsProvider.rounding / 10).toStringAsFixed(1),
style: const TextStyle(fontSize: 14.0),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
onPressed: () {
settingsProvider.update(
graphClassAvg: !settingsProvider.graphClassAvg);
setState(() {});
},
title: Text(
"graph_class_avg".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(
settingsProvider.graphClassAvg ? .95 : .25),
),
),
leading: Icon(
FeatherIcons.barChart,
size: 22.0,
color: AppColors.of(context).text.withOpacity(
settingsProvider.graphClassAvg ? .95 : .25),
),
trailing: Switch(
onChanged: (v) =>
settingsProvider.update(graphClassAvg: v),
value: settingsProvider.graphClassAvg,
activeColor: Theme.of(context).colorScheme.secondary,
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
onPressed: () {
SettingsHelper.startPage(context);
setState(() {});
},
title: Text(
"startpage".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95),
),
),
leading: Icon(
FeatherIcons.play,
size: 22.0,
color: AppColors.of(context).text.withOpacity(.95),
),
trailing: Text(
startPageTitle.capital(),
style: const TextStyle(fontSize: 14.0),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
onPressed: () {
SettingsHelper.language(context);
setState(() {});
},
title: Text(
"language".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95),
),
),
leading: Icon(
FeatherIcons.globe,
size: 22.0,
color: AppColors.of(context).text.withOpacity(.95),
),
trailing: Text(
languageText,
style: const TextStyle(fontSize: 14.0),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
onPressed: () {
SettingsHelper.vibrate(context);
setState(() {});
},
title: Text(
"vibrate".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95),
),
),
leading: Icon(
FeatherIcons.radio,
size: 22.0,
color: AppColors.of(context).text.withOpacity(.95),
),
trailing: Text(
vibrateTitle,
style: const TextStyle(fontSize: 14.0),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
onPressed: () {
settingsProvider.update(
showBreaks: !settingsProvider.showBreaks);
setState(() {});
},
title: Text(
"show_breaks".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(
settingsProvider.showBreaks ? .95 : .25),
),
),
leading: Icon(
settingsProvider.showBreaks
? FeatherIcons.eye
: FeatherIcons.eyeOff,
size: 22.0,
color: AppColors.of(context)
.text
.withOpacity(settingsProvider.showBreaks ? .95 : .25),
),
trailing: Switch(
onChanged: (v) => settingsProvider.update(showBreaks: v),
value: settingsProvider.showBreaks,
activeColor: Theme.of(context).colorScheme.secondary,
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
onPressed: () {
settingsProvider.update(
newsEnabled: !settingsProvider.newsEnabled);
setState(() {});
},
title: Text(
"news".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(
settingsProvider.newsEnabled ? .95 : .25),
),
),
leading: Icon(
Icons.newspaper_outlined,
size: 22.0,
color: AppColors.of(context).text.withOpacity(
settingsProvider.newsEnabled ? .95 : .25),
),
trailing: Switch(
onChanged: (v) => settingsProvider.update(newsEnabled: v),
value: settingsProvider.newsEnabled,
activeColor: Theme.of(context).colorScheme.secondary,
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,572 @@
// ignore_for_file: use_build_context_synchronously
import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/models/settings.dart';
import 'package:refilc/models/shared_theme.dart';
import 'package:refilc/theme/colors/accent.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc/theme/observer.dart';
import 'package:refilc_kreta_api/providers/share_provider.dart';
import 'package:refilc_mobile_ui/common/custom_snack_bar.dart';
import 'package:refilc_mobile_ui/common/empty.dart';
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
import 'package:share_plus/share_plus.dart';
class MenuPaintList extends StatelessWidget {
const MenuPaintList({
super.key,
this.borderRadius = const BorderRadius.vertical(
top: Radius.circular(4.0), bottom: Radius.circular(4.0)),
});
final BorderRadius borderRadius;
@override
Widget build(BuildContext context) {
return PanelButton(
onPressed: () async {
List<SharedTheme> publicThemes =
await Provider.of<ShareProvider>(context, listen: false)
.getAllPublicThemes(context);
Navigator.of(context, rootNavigator: true).push(CupertinoPageRoute(
builder: (context) => PaintListScreen(publicThemes: publicThemes)));
},
title: Text(
"own_paints".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95),
),
),
leading: Icon(
FeatherIcons.list,
size: 22.0,
color: AppColors.of(context).text.withOpacity(.95),
),
trailing: Icon(
FeatherIcons.chevronRight,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
borderRadius: borderRadius,
);
}
}
class PaintListScreen extends StatefulWidget {
const PaintListScreen({super.key, required this.publicThemes});
final List<SharedTheme> publicThemes;
@override
PaintListScreenState createState() => PaintListScreenState();
}
class PaintListScreenState extends State<PaintListScreen>
with SingleTickerProviderStateMixin {
late SettingsProvider settingsProvider;
late UserProvider user;
late ShareProvider shareProvider;
late AnimationController _hideContainersController;
late List<Widget> tiles;
final _paintId = TextEditingController();
SharedTheme? newThemeByID;
@override
void initState() {
super.initState();
shareProvider = Provider.of<ShareProvider>(context, listen: false);
_hideContainersController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 200));
}
void buildPublicPaintTiles() async {
List<Widget> subjectTiles = [];
var added = [];
var i = 0;
for (var t in widget.publicThemes) {
if (added.contains(t.id)) continue;
Widget w = PanelButton(
onPressed: () => {
// TODO: set theme
},
title: Column(
children: [
Text(
t.displayName,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95),
),
),
Text(
t.nickname,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(.75),
),
),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: const EdgeInsets.only(left: 2.0),
width: 12.0,
height: 12.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: t.backgroundColor,
),
),
Container(
margin: const EdgeInsets.only(left: 2.0),
width: 12.0,
height: 12.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: t.panelsColor,
),
),
Container(
margin: const EdgeInsets.only(left: 2.0),
width: 12.0,
height: 12.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: t.accentColor,
),
),
],
),
borderRadius: BorderRadius.vertical(
top: Radius.circular(i == 0 ? 12.0 : 4.0),
bottom:
Radius.circular(i + 1 == widget.publicThemes.length ? 12.0 : 4.0),
),
);
i += 1;
subjectTiles.add(w);
added.add(t.id);
}
if (widget.publicThemes.isEmpty) {
subjectTiles.add(Empty(
subtitle: 'no_pub_paint'.i18n,
));
}
tiles = subjectTiles;
}
@override
Widget build(BuildContext context) {
settingsProvider = Provider.of<SettingsProvider>(context);
user = Provider.of<UserProvider>(context);
buildPublicPaintTiles();
return AnimatedBuilder(
animation: _hideContainersController,
builder: (context, child) => Opacity(
opacity: 1 - _hideContainersController.value,
child: Scaffold(
appBar: AppBar(
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
leading: BackButton(color: AppColors.of(context).text),
title: Text(
"own_paints".i18n,
style: TextStyle(color: AppColors.of(context).text),
),
),
body: SingleChildScrollView(
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: Column(
children: [
// enter id
SplittedPanel(
padding: EdgeInsets.zero,
cardPadding: const EdgeInsets.all(3.0),
hasBorder: true,
isTransparent: true,
children: [
PanelButton(
onPressed: () => showEnterIDDialog(),
title: Text(
"enter_id".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95),
),
),
leading: Icon(
FeatherIcons.plus,
size: 22.0,
color: AppColors.of(context).text.withOpacity(.95),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
const SizedBox(
height: 18.0,
),
// current paint
SplittedPanel(
title: Text('current_paint'.i18n),
padding: EdgeInsets.zero,
cardPadding: const EdgeInsets.all(4.0),
children: [
PanelButton(
onPressed: () async {
if (settingsProvider.currentThemeId != '') {
Share.share(
settingsProvider.currentThemeId,
subject: 'share_subj_theme'.i18n,
);
} else {
SharedGradeColors gradeColors = await shareProvider
.shareCurrentGradeColors(context);
SharedTheme theme =
await shareProvider.shareCurrentTheme(
context,
gradeColors: gradeColors,
);
Share.share(
theme.id,
subject: 'share_subj_theme'.i18n,
);
}
},
longPressInstead: true,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
settingsProvider.currentThemeDisplayName != ''
? settingsProvider.currentThemeDisplayName
: 'no_name'.i18n,
style: TextStyle(
color:
AppColors.of(context).text.withOpacity(.95),
),
),
Text(
settingsProvider.currentThemeCreator != ''
? settingsProvider.currentThemeCreator
: 'Anonymous',
style: TextStyle(
color:
AppColors.of(context).text.withOpacity(.65),
fontSize: 15.0,
fontWeight: FontWeight.w500,
),
),
],
),
trailing: Transform.translate(
offset: const Offset(8.0, 0.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: const EdgeInsets.only(left: 2.0),
width: 14.0,
height: 14.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color:
(settingsProvider.customBackgroundColor ??
SettingsProvider.defaultSettings()
.customBackgroundColor),
boxShadow: [
BoxShadow(
color: AppColors.of(context)
.text
.withOpacity(0.15),
offset: const Offset(1, 2),
blurRadius: 3,
),
],
),
),
Transform.translate(
offset: const Offset(-4.0, 0.0),
child: Container(
margin: const EdgeInsets.only(left: 2.0),
width: 14.0,
height: 14.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: (settingsProvider
.customHighlightColor ??
SettingsProvider.defaultSettings()
.customHighlightColor),
boxShadow: [
BoxShadow(
color: AppColors.of(context)
.text
.withOpacity(0.15),
offset: const Offset(1, 2),
blurRadius: 3,
),
],
),
),
),
Transform.translate(
offset: const Offset(-8.0, 0.0),
child: Container(
margin: const EdgeInsets.only(left: 2.0),
width: 14.0,
height: 14.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: settingsProvider.customAccentColor ??
accentColorMap[
settingsProvider.accentColor],
boxShadow: [
BoxShadow(
color: AppColors.of(context)
.text
.withOpacity(0.15),
offset: const Offset(1, 2),
blurRadius: 3,
),
],
),
),
),
],
),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12),
bottom: Radius.circular(12),
),
),
],
),
const SizedBox(
height: 18.0,
),
// own paints
SplittedPanel(
title: Text('public_paint'.i18n),
padding: EdgeInsets.zero,
cardPadding: const EdgeInsets.all(4.0),
children: tiles,
),
],
),
),
),
),
),
);
}
// enter id dialog
void showEnterIDDialog() {
_paintId.text = '';
showDialog(
context: context,
builder: (context) => StatefulBuilder(builder: (context, setS) {
return AlertDialog(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(14.0))),
title: Text("enter_id".i18n),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _paintId,
onEditingComplete: () async {
// SharedTheme? theme = await shareProvider.getThemeById(
// context,
// id: _paintId.text.replaceAll(' ', ''),
// );
// if (theme != null) {
// // set theme variable
// newThemeByID = theme;
// _paintId.clear();
// } else {
// ScaffoldMessenger.of(context).showSnackBar(
// CustomSnackBar(
// content: Text("theme_not_found".i18n,
// style: const TextStyle(color: Colors.white)),
// backgroundColor: AppColors.of(context).red,
// context: context,
// ),
// );
// }
},
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide:
const BorderSide(color: Colors.grey, width: 1.5),
borderRadius: BorderRadius.circular(12.0),
),
focusedBorder: OutlineInputBorder(
borderSide:
const BorderSide(color: Colors.grey, width: 1.5),
borderRadius: BorderRadius.circular(12.0),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
hintText: 'paint_id'.i18n,
suffixIcon: IconButton(
icon: const Icon(
FeatherIcons.x,
color: Colors.grey,
),
onPressed: () {
setState(() {
_paintId.text = '';
});
},
),
),
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Icon(FeatherIcons.arrowDown, size: 32),
),
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background,
borderRadius:
const BorderRadius.all(Radius.circular(12.0))),
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Center(
child: Text(
'set_as_current'.i18n,
style: const TextStyle(
fontWeight: FontWeight.w500, fontSize: 16.0),
),
),
),
],
),
actions: [
TextButton(
child: Text(
"cancel".i18n,
style: const TextStyle(fontWeight: FontWeight.w500),
),
onPressed: () {
Navigator.of(context).maybePop();
},
),
TextButton(
child: Text(
"done".i18n,
style: const TextStyle(fontWeight: FontWeight.w500),
),
onPressed: () async {
// get sex
SharedTheme? theme = await shareProvider.getThemeById(
context,
id: _paintId.text.replaceAll(' ', ''),
);
if (theme != null) {
// set theme variable
newThemeByID = theme;
_paintId.clear();
} else {
ScaffoldMessenger.of(context).showSnackBar(
CustomSnackBar(
content: Text("theme_not_found".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
),
);
}
// slay
setPaint();
setState(() {});
Navigator.of(context).pop();
},
),
],
);
}),
).then((val) {
_paintId.clear();
});
}
void setPaint() async {
if (newThemeByID == null) return;
// changing grade colors
List<Color> colors = [
newThemeByID!.gradeColors.oneColor,
newThemeByID!.gradeColors.twoColor,
newThemeByID!.gradeColors.threeColor,
newThemeByID!.gradeColors.fourColor,
newThemeByID!.gradeColors.fiveColor,
];
settingsProvider.update(gradeColors: colors, store: true);
// changing shadow effect
settingsProvider.update(
shadowEffect: newThemeByID!.shadowEffect, store: true);
// changing theme mode
settingsProvider.update(theme: newThemeByID!.themeMode, store: true);
// changing theme
settingsProvider.update(
customBackgroundColor: newThemeByID!.backgroundColor,
customHighlightColor: newThemeByID!.panelsColor,
customAccentColor: newThemeByID!.accentColor,
customIconColor: newThemeByID!.iconColor,
// new things
currentThemeId: newThemeByID!.id,
currentThemeDisplayName: newThemeByID!.displayName,
currentThemeCreator: newThemeByID!.nickname,
// we should store it
store: true,
);
// changing font family
settingsProvider.update(fontFamily: newThemeByID!.fontFamily, store: true);
// seems weird but it works, trust me (idk why)
// await settingsProvider.update(theme: settingsProvider.theme, store: true);
Provider.of<ThemeModeObserver>(context, listen: false)
.changeTheme(settingsProvider.theme, updateNavbarColor: true);
}
}

View File

@@ -0,0 +1,834 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:io';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/helpers/subject.dart';
import 'package:refilc/models/settings.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc/utils/format.dart';
import 'package:refilc_kreta_api/models/grade.dart';
import 'package:refilc_kreta_api/providers/absence_provider.dart';
import 'package:refilc_kreta_api/providers/grade_provider.dart';
import 'package:refilc_kreta_api/providers/timetable_provider.dart';
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart';
import 'package:refilc_mobile_ui/screens/settings/submenu/edit_subject.dart';
import 'package:refilc_mobile_ui/screens/settings/submenu/paint_list.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
import 'package:refilc_plus/models/premium_scopes.dart';
import 'package:refilc_plus/providers/premium_provider.dart';
import 'package:refilc_plus/ui/mobile/premium/upsell.dart';
import 'package:google_fonts/google_fonts.dart';
class MenuPersonalizeSettings extends StatelessWidget {
const MenuPersonalizeSettings({
super.key,
this.borderRadius = const BorderRadius.vertical(
top: Radius.circular(4.0), bottom: Radius.circular(4.0)),
});
final BorderRadius borderRadius;
@override
Widget build(BuildContext context) {
return PanelButton(
onPressed: () => Navigator.of(context, rootNavigator: true).push(
CupertinoPageRoute(
builder: (context) => const PersonalizeSettingsScreen()),
),
title: Text("personalization".i18n),
leading: Icon(
Icons.palette_outlined,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
trailing: Icon(
FeatherIcons.chevronRight,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
borderRadius: borderRadius,
);
}
}
class PersonalizeSettingsScreen extends StatefulWidget {
const PersonalizeSettingsScreen({super.key});
@override
PersonalizeSettingsScreenState createState() =>
PersonalizeSettingsScreenState();
}
class PersonalizeSettingsScreenState extends State<PersonalizeSettingsScreen>
with SingleTickerProviderStateMixin {
late SettingsProvider settingsProvider;
late UserProvider user;
late AnimationController _hideContainersController;
late List<Grade> editedShit;
late List<Grade> otherShit;
late List<Widget> tiles;
// late List<Widget> fontTiles;
@override
void initState() {
super.initState();
// editedShit = Provider.of<GradeProvider>(context, listen: false)
// .grades
// .where((e) => e.teacher.isRenamed || e.subject.isRenamed)
// // .map((e) => e.subject)
// .toSet()
// .toList()
// ..sort((a, b) => a.subject.name.compareTo(b.subject.name));
List<Grade> other = Provider.of<GradeProvider>(context, listen: false)
.grades
.where((e) => !e.teacher.isRenamed && !e.subject.isRenamed)
.toSet()
.toList()
..sort((a, b) => a.subject.name.compareTo(b.subject.name));
otherShit = [];
var addedOthers = [];
for (var e in other) {
if (addedOthers.contains(e.subject.id)) continue;
addedOthers.add(e.subject.id);
otherShit.add(e);
}
otherShit = otherShit
..sort((a, b) =>
a.subject.name.compareTo(b.subject.name)); // just cuz why not
// editedTeachers = Provider.of<GradeProvider>(context, listen: false)
// .grades
// .where((e) => e.teacher.isRenamed || e.subject.isRenamed)
// .map((e) => e.teacher)
// .toSet()
// .toList();
// // ..sort((a, b) => a.name.compareTo(b.name));
// otherTeachers = Provider.of<GradeProvider>(context, listen: false)
// .grades
// .where((e) => !e.teacher.isRenamed && !e.subject.isRenamed)
// .map((e) => e.teacher)
// .toSet()
// .toList();
// ..sort((a, b) => a.name.compareTo(b.name));
_hideContainersController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 200));
}
void buildSubjectTiles() {
List<Widget> subjectTiles = [];
var added = [];
var i = 0;
List<Grade> need = [];
for (var s in editedShit) {
if (added.contains(s.subject.id)) continue;
need.add(s);
added.add(s.subject.id);
}
for (var s in need) {
Widget widget = PanelButton(
onPressed: () async {
Navigator.of(context, rootNavigator: true).push(
CupertinoPageRoute(
builder: (context) => EditSubjectScreen(
subject: s.subject,
teacher: s.teacher, // not sure why, but it works tho
),
),
);
setState(() {});
},
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
(s.subject.isRenamed && settingsProvider.renamedSubjectsEnabled
? s.subject.renamedTo
: s.subject.name.capital()) ??
'',
style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95),
fontStyle: settingsProvider.renamedSubjectsItalics
? FontStyle.italic
: FontStyle.normal,
),
),
Text(
(s.teacher.isRenamed && settingsProvider.renamedTeachersEnabled
? s.teacher.renamedTo
: s.teacher.name.capital()) ??
'',
style: TextStyle(
color: AppColors.of(context).text.withOpacity(.85),
fontWeight: FontWeight.w400,
fontSize: 15.0,
height: 1.2,
),
),
],
),
leading: Icon(
SubjectIcon.resolveVariant(context: context, subject: s.subject),
size: 22.0,
color: AppColors.of(context).text.withOpacity(.95),
),
trailing: Icon(
FeatherIcons.chevronRight,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
borderRadius: BorderRadius.vertical(
top: Radius.circular(i == 0 ? 12.0 : 4.0),
bottom: Radius.circular(i + 1 == need.length ? 12.0 : 4.0),
),
);
i += 1;
subjectTiles.add(widget);
}
tiles = subjectTiles;
}
// void buildFontTiles() {
// List<String> fonts = [
// "Merienda",
// "M PLUS Code Latin",
// "Figtree",
// "Fira Code",
// "Vollkorn",
// ];
// List<Widget> fTiles = [];
// var added = [];
// for (var f in fonts) {
// if (added.contains(f)) continue;
// Widget widget = PanelButton(
// onPressed: () async {
// settingsProvider.update(fontFamily: f);
// setState(() {});
// },
// title: Text(
// f,
// style: GoogleFonts.getFont(
// f,
// color: AppColors.of(context).text.withOpacity(.95),
// fontStyle: settingsProvider.renamedSubjectsItalics
// ? FontStyle.italic
// : FontStyle.normal,
// ),
// ),
// trailing: settingsProvider.fontFamily == f
// ? Icon(
// FeatherIcons.chevronRight,
// size: 22.0,
// color: AppColors.of(context).text.withOpacity(0.95),
// )
// : null,
// borderRadius: BorderRadius.circular(12.0),
// );
// fTiles.add(widget);
// added.add(f);
// }
// fontTiles = fTiles;
// }
@override
Widget build(BuildContext context) {
settingsProvider = Provider.of<SettingsProvider>(context);
user = Provider.of<UserProvider>(context);
// get edited shit
editedShit = Provider.of<GradeProvider>(context, listen: false)
.grades
.where((e) => e.teacher.isRenamed || e.subject.isRenamed)
// .map((e) => e.subject)
.toSet()
.toList()
..sort((a, b) => a.subject.name.compareTo(b.subject.name));
String themeModeText = {
ThemeMode.light: "light".i18n,
ThemeMode.dark: "dark".i18n,
ThemeMode.system: "system".i18n
}[settingsProvider.theme] ??
"?";
// build da tilés
buildSubjectTiles();
// buildFontTiles();
return AnimatedBuilder(
animation: _hideContainersController,
builder: (context, child) => Opacity(
opacity: 1 - _hideContainersController.value,
child: Scaffold(
appBar: AppBar(
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
leading: BackButton(color: AppColors.of(context).text),
title: Text(
"personalization".i18n,
style: TextStyle(color: AppColors.of(context).text),
),
),
body: SingleChildScrollView(
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: Column(
children: [
// app theme
SplittedPanel(
padding: const EdgeInsets.only(top: 8.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
onPressed: () {
SettingsHelper.theme(context);
setState(() {});
},
title: Text("theme".i18n),
leading: Icon(
FeatherIcons.sun,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
trailing: Text(
themeModeText,
style: const TextStyle(fontSize: 14.0),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
// color magic shit
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: false,
children: [
PanelButton(
padding: const EdgeInsets.only(left: 14.0, right: 14.0),
onPressed: () async {
await _hideContainersController.forward();
SettingsHelper.accentColor(context);
setState(() {});
_hideContainersController.reset();
},
title: Text(
"color".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95),
),
),
leading: Icon(
FeatherIcons.droplet,
size: 22.0,
color: AppColors.of(context).text.withOpacity(.95),
),
trailing: Container(
width: 12.0,
height: 12.0,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondary,
shape: BoxShape.circle,
),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(4.0),
),
),
const MenuPaintList(
borderRadius: BorderRadius.vertical(
top: Radius.circular(4.0),
bottom: Radius.circular(12.0),
),
),
],
),
// shadow toggle
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
onPressed: () async {
settingsProvider.update(
shadowEffect: !settingsProvider.shadowEffect);
setState(() {});
},
title: Text(
"shadow_effect".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(
settingsProvider.shadowEffect ? .95 : .25),
),
),
leading: Icon(
FeatherIcons.moon,
size: 22.0,
color: AppColors.of(context).text.withOpacity(
settingsProvider.shadowEffect ? .95 : .25),
),
trailing: Switch(
onChanged: (v) async {
settingsProvider.update(shadowEffect: v);
setState(() {});
},
value: settingsProvider.shadowEffect,
activeColor: Theme.of(context).colorScheme.secondary,
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
// change subject icons
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
onPressed: () {
SettingsHelper.iconPack(context);
},
title: Text(
"icon_pack".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95),
),
),
leading: Icon(
FeatherIcons.grid,
size: 22.0,
color: AppColors.of(context).text.withOpacity(.95),
),
trailing: Text(
settingsProvider.iconPack.name.capital(),
style: const TextStyle(fontSize: 14.0),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
// grade colors
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
onPressed: () {
SettingsHelper.gradeColors(context);
setState(() {});
},
title: Text(
"grade_colors".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95),
),
),
leading: Icon(
FeatherIcons.star,
size: 22.0,
color: AppColors.of(context).text.withOpacity(.95),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
5,
(i) => Container(
margin: const EdgeInsets.only(left: 2.0),
width: 12.0,
height: 12.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: settingsProvider.gradeColors[i],
),
),
),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
// rename things
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: false,
children: [
// rename subjects
PanelButton(
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
onPressed: () async {
settingsProvider.update(
renamedSubjectsEnabled:
!settingsProvider.renamedSubjectsEnabled);
await Provider.of<GradeProvider>(context,
listen: false)
.convertBySettings();
await Provider.of<TimetableProvider>(context,
listen: false)
.convertBySettings();
await Provider.of<AbsenceProvider>(context,
listen: false)
.convertBySettings();
setState(() {});
},
title: Text(
"rename_subjects".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(
settingsProvider.renamedSubjectsEnabled
? .95
: .25),
),
),
leading: Icon(
Icons.school_outlined,
size: 22.0,
color: AppColors.of(context).text.withOpacity(
settingsProvider.renamedSubjectsEnabled
? .95
: .25),
),
trailing: Switch(
onChanged: (v) async {
settingsProvider.update(renamedSubjectsEnabled: v);
await Provider.of<GradeProvider>(context,
listen: false)
.convertBySettings();
await Provider.of<TimetableProvider>(context,
listen: false)
.convertBySettings();
await Provider.of<AbsenceProvider>(context,
listen: false)
.convertBySettings();
setState(() {});
},
value: settingsProvider.renamedSubjectsEnabled,
activeColor: Theme.of(context).colorScheme.secondary,
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(4.0),
),
),
// rename teachers
PanelButton(
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
onPressed: () async {
settingsProvider.update(
renamedTeachersEnabled:
!settingsProvider.renamedTeachersEnabled);
await Provider.of<GradeProvider>(context,
listen: false)
.convertBySettings();
await Provider.of<TimetableProvider>(context,
listen: false)
.convertBySettings();
await Provider.of<AbsenceProvider>(context,
listen: false)
.convertBySettings();
setState(() {});
},
title: Text(
"rename_teachers".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(
settingsProvider.renamedTeachersEnabled
? .95
: .25),
),
),
leading: Icon(
FeatherIcons.user,
size: 22.0,
color: AppColors.of(context).text.withOpacity(
settingsProvider.renamedTeachersEnabled
? .95
: .25),
),
trailing: Switch(
onChanged: (v) async {
settingsProvider.update(renamedTeachersEnabled: v);
await Provider.of<GradeProvider>(context,
listen: false)
.convertBySettings();
await Provider.of<TimetableProvider>(context,
listen: false)
.convertBySettings();
await Provider.of<AbsenceProvider>(context,
listen: false)
.convertBySettings();
setState(() {});
},
value: settingsProvider.renamedTeachersEnabled,
activeColor: Theme.of(context).colorScheme.secondary,
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(4.0),
bottom: Radius.circular(12.0),
),
),
],
),
// live activity color
if (Platform.isIOS)
SplittedPanel(
padding: const EdgeInsets.only(top: 9.0),
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
onPressed: () {
if (!Provider.of<PremiumProvider>(context,
listen: false)
.hasScope(PremiumScopes.liveActivityColor)) {
PremiumLockedFeatureUpsell.show(
context: context,
feature: PremiumFeature.liveActivity,
);
return;
}
SettingsHelper.liveActivityColor(context);
setState(() {});
},
title: Text(
"live_activity_color".i18n,
style: TextStyle(
color:
AppColors.of(context).text.withOpacity(.95),
),
),
leading: Icon(
FeatherIcons.activity,
size: 22.0,
color: AppColors.of(context).text.withOpacity(.95),
),
trailing: Container(
margin: const EdgeInsets.only(left: 2.0),
width: 12.0,
height: 12.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: settingsProvider.liveActivityColor,
),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
// SplittedPanel(
// padding: const EdgeInsets.only(top: 9.0),
// cardPadding: const EdgeInsets.all(4.0),
// isSeparated: true,
// children: [],
// ),
if (settingsProvider.renamedSubjectsEnabled ||
settingsProvider.renamedTeachersEnabled)
Column(
children: [
const SizedBox(
height: 18.0,
),
SplittedPanel(
title: Text('subjects'.i18n),
padding: EdgeInsets.zero,
cardPadding: const EdgeInsets.all(4.0),
children: tiles,
),
const SizedBox(
height: 9.0,
),
SplittedPanel(
padding: EdgeInsets.zero,
cardPadding: const EdgeInsets.all(3.0),
hasBorder: true,
isTransparent: true,
children: [
DropdownButton2(
items: otherShit
.map((item) => DropdownMenuItem<String>(
value: item.subject.id,
child: Text(
item.subject.name,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: AppColors.of(context).text,
),
overflow: TextOverflow.ellipsis,
),
))
.toList(),
onChanged: (String? v) async {
Navigator.of(context, rootNavigator: true).push(
CupertinoPageRoute(
builder: (context) => EditSubjectScreen(
subject: otherShit
.firstWhere((e) => e.subject.id == v)
.subject,
teacher: otherShit
.firstWhere((e) => e.subject.id == v)
.teacher,
),
),
);
setState(() {});
// _subjectName.text = "";
},
iconSize: 14,
iconEnabledColor: AppColors.of(context).text,
iconDisabledColor: AppColors.of(context).text,
underline: const SizedBox(),
itemHeight: 40,
itemPadding:
const EdgeInsets.only(left: 14, right: 14),
buttonWidth: 50,
dropdownWidth: 300,
dropdownPadding: null,
buttonDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0),
),
dropdownDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(14),
),
dropdownElevation: 8,
scrollbarRadius: const Radius.circular(40),
scrollbarThickness: 6,
scrollbarAlwaysShow: true,
offset: const Offset(-10, -10),
buttonSplashColor: Colors.transparent,
customButton: PanelButton(
title: Text(
"select_subject".i18n,
style: TextStyle(
color: AppColors.of(context)
.text
.withOpacity(.95),
),
),
leading: Icon(
FeatherIcons.plus,
size: 22.0,
color: AppColors.of(context)
.text
.withOpacity(.95),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
),
],
),
],
),
// custom fonts
const SizedBox(
height: 18.0,
),
SplittedPanel(
title: Text('fonts'.i18n),
padding: EdgeInsets.zero,
cardPadding: const EdgeInsets.all(4.0),
isSeparated: true,
children: [
PanelButton(
onPressed: () {
SettingsHelper.fontFamily(context);
setState(() {});
},
title: Text(
"font_family".i18n,
style: TextStyle(
color: AppColors.of(context).text.withOpacity(.95),
),
),
leading: Icon(
FeatherIcons.type,
size: 22.0,
color: AppColors.of(context).text.withOpacity(.95),
),
trailing: Text(
settingsProvider.fontFamily != ''
? settingsProvider.fontFamily
: 'Montserrat',
style: GoogleFonts.getFont(
settingsProvider.fontFamily != ''
? settingsProvider.fontFamily
: 'Montserrat',
fontSize: 14.0),
),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0),
bottom: Radius.circular(12.0),
),
),
],
),
],
),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,57 @@
import 'package:i18n_extension/i18n_extension.dart';
extension SettingsLocalization on String {
static final _t = Translations.byLocale("hu_hu") +
{
"en_en": {
"general": "General",
"personalization": "Personalization",
"extras": "Extras",
"surprise_grades": "Surprise Grades",
"cancel": "Cancel",
"done": "Done",
"rarity_title": "Rarity Text",
// default rarities
"common": "Common",
"uncommon": "Uncommon",
"rare": "Rare",
"epic": "Epic",
"legendary": "Legendary",
},
"hu_hu": {
"general": "Általános",
"personalization": "Személyre szabás",
"extras": "Extrák",
"surprise_grades": "Meglepetés jegyek",
"cancel": "Mégse",
"done": "Kész",
"rarity_title": "Ritkaság szövege",
// default rarities
"common": "Gyakori",
"uncommon": "Nem gyakori",
"rare": "Ritka",
"epic": "Epikus",
"legendary": "Legendás",
},
"de_de": {
"general": "Allgemeine",
"personalization": "Personalisierung",
"extras": "Extras",
"surprise_grades": "Überraschende Noten",
"cancel": "Abbrechen",
"done": "Bereit",
"rarity_title": "Text zur Seltenheit",
// default rarities
"common": "Gemeinsam",
"uncommon": "Gelegentlich",
"rare": "Selten",
"epic": "Episch",
"legendary": "Legendär",
},
};
String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t);
}

View File

@@ -0,0 +1,996 @@
// ignore_for_file: use_build_context_synchronously, deprecated_member_use
import 'package:refilc/models/settings.dart';
import 'package:refilc/models/shared_theme.dart';
import 'package:refilc/theme/colors/accent.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc/theme/observer.dart';
import 'package:refilc/ui/widgets/grade/grade_tile.dart';
import 'package:refilc/ui/widgets/message/message_tile.dart';
import 'package:refilc_kreta_api/models/grade.dart';
import 'package:refilc_kreta_api/models/homework.dart';
import 'package:refilc_kreta_api/models/message.dart';
import 'package:refilc_mobile_ui/common/action_button.dart';
import 'package:refilc_mobile_ui/common/filter_bar.dart';
import 'package:refilc_mobile_ui/common/panel/panel.dart';
import 'package:refilc_mobile_ui/common/widgets/grade/new_grades.dart';
import 'package:refilc_mobile_ui/common/widgets/homework/homework_tile.dart';
// import 'package:refilc_plus/models/premium_scopes.dart';
// import 'package:refilc_plus/providers/premium_provider.dart';
import 'package:refilc_kreta_api/providers/share_provider.dart';
import 'package:refilc/ui/flutter_colorpicker/colorpicker.dart';
// import 'package:refilc_plus/ui/mobile/premium/upsell.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart';
import 'theme_screen.i18n.dart';
import 'package:share_plus/share_plus.dart';
class PremiumCustomAccentColorSetting extends StatefulWidget {
const PremiumCustomAccentColorSetting({super.key});
@override
State<PremiumCustomAccentColorSetting> createState() =>
_PremiumCustomAccentColorSettingState();
}
enum CustomColorMode {
theme,
saved,
accent,
background,
highlight,
icon,
enterId,
}
class _PremiumCustomAccentColorSettingState
extends State<PremiumCustomAccentColorSetting>
with TickerProviderStateMixin {
late final SettingsProvider settings;
late final ShareProvider shareProvider;
bool colorSelection = false;
bool customColorMenu = false;
CustomColorMode colorMode = CustomColorMode.theme;
final customColorInput = TextEditingController();
final unknownColor = Colors.black;
late TabController _testTabController;
late TabController _colorsTabController;
late AnimationController _openAnimController;
late final Animation<double> backgroundAnimation =
Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _openAnimController,
curve: const Interval(0.2, 1.0, curve: Curves.easeInOut),
),
);
late final Animation<double> fullPageAnimation =
Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _openAnimController,
curve: const Interval(0.0, 0.6, curve: Curves.easeInOut),
),
);
late final Animation<double> backContainerAnimation =
Tween<double>(begin: 100, end: 0).animate(
CurvedAnimation(
parent: _openAnimController,
curve: const Interval(0.0, 0.9, curve: Curves.easeInOut),
),
);
late final Animation<double> backContentAnimation =
Tween<double>(begin: 100, end: 0).animate(
CurvedAnimation(
parent: _openAnimController,
curve: const Interval(0.2, 1.0, curve: Curves.easeInOut),
),
);
late final Animation<double> backContentScaleAnimation =
Tween<double>(begin: 0.8, end: 0.9).animate(
CurvedAnimation(
parent: _openAnimController,
curve: const Interval(0.45, 1.0, curve: Curves.easeInOut),
),
);
late final Animation<double> pickerContainerAnimation =
Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _openAnimController,
curve: const Interval(0.25, 0.8, curve: Curves.easeInOut),
),
);
@override
void initState() {
super.initState();
_colorsTabController = TabController(length: 4, vsync: this);
_testTabController = TabController(length: 4, vsync: this);
settings = Provider.of<SettingsProvider>(context, listen: false);
shareProvider = Provider.of<ShareProvider>(context, listen: false);
_openAnimController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 750));
_openAnimController.forward();
}
@override
void dispose() {
_openAnimController.dispose();
super.dispose();
}
void setTheme(ThemeMode mode, bool store) async {
await settings.update(theme: mode, store: store);
Provider.of<ThemeModeObserver>(context, listen: false)
.changeTheme(mode, updateNavbarColor: false);
}
dynamic getCustomColor() {
switch (colorMode) {
case CustomColorMode.theme:
return accentColorMap[settings.accentColor];
case CustomColorMode.saved:
return [
settings.customBackgroundColor,
settings.customHighlightColor,
settings.customAccentColor
];
case CustomColorMode.background:
return settings.customBackgroundColor;
case CustomColorMode.highlight:
return settings.customHighlightColor;
case CustomColorMode.accent:
return settings.customAccentColor;
case CustomColorMode.icon:
return settings.customIconColor;
case CustomColorMode.enterId:
// do nothing here lol
break;
}
}
void updateCustomColor(dynamic v, bool store,
{Color? accent, Color? background, Color? panels, Color? icon}) {
// reset custom theme id
settings.update(
currentThemeId: '',
currentThemeDisplayName: '',
currentThemeCreator: '',
store: store,
);
if (colorMode != CustomColorMode.theme) {
settings.update(accentColor: AccentColor.custom, store: store);
}
switch (colorMode) {
case CustomColorMode.theme:
settings.update(
accentColor: accentColorMap.keys.firstWhere(
(element) => accentColorMap[element] == v,
orElse: () => AccentColor.filc),
store: store);
settings.update(
customBackgroundColor: AppColors.of(context).background,
store: store);
settings.update(
customHighlightColor: AppColors.of(context).highlight,
store: store);
settings.update(customAccentColor: v, store: store);
break;
case CustomColorMode.saved:
settings.update(customBackgroundColor: v[0], store: store);
settings.update(customHighlightColor: v[1], store: store);
settings.update(customAccentColor: v[3], store: store);
break;
case CustomColorMode.background:
settings.update(customBackgroundColor: v, store: store);
break;
case CustomColorMode.highlight:
settings.update(customHighlightColor: v, store: store);
break;
case CustomColorMode.accent:
settings.update(customAccentColor: v, store: store);
break;
case CustomColorMode.icon:
settings.update(customIconColor: v, store: store);
break;
case CustomColorMode.enterId:
settings.update(customBackgroundColor: background, store: store);
settings.update(customHighlightColor: panels, store: store);
settings.update(customAccentColor: accent, store: store);
settings.update(customIconColor: icon, store: store);
break;
}
}
@override
Widget build(BuildContext context) {
// bool hasAccess = Provider.of<PremiumProvider>(context)
// .hasScope(PremiumScopes.customColors);
bool hasAccess = true;
bool isBackgroundDifferent = Theme.of(context).colorScheme.background !=
AppColors.of(context).background;
ThemeMode currentTheme = Theme.of(context).brightness == Brightness.light
? ThemeMode.light
: ThemeMode.dark;
return WillPopScope(
onWillPop: () async {
Provider.of<ThemeModeObserver>(context, listen: false)
.changeTheme(settings.theme, updateNavbarColor: true);
return true;
},
child: AnimatedBuilder(
animation: _openAnimController,
builder: (context, child) {
final backgroundGradientBottomColor = isBackgroundDifferent
? Theme.of(context).colorScheme.background
: HSVColor.fromColor(Theme.of(context).colorScheme.background)
.withValue(currentTheme == ThemeMode.dark
? 0.1 * _openAnimController.value
: 1.0 - (0.1 * _openAnimController.value))
.withAlpha(1.0)
.toColor();
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: backgroundGradientBottomColor,
));
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: const [0.0, 0.75],
colors: isBackgroundDifferent
? [
Theme.of(context).colorScheme.background.withOpacity(1 -
((currentTheme == ThemeMode.dark ? 0.65 : 0.25) *
backgroundAnimation.value)),
backgroundGradientBottomColor,
]
: [
backgroundGradientBottomColor,
backgroundGradientBottomColor
],
),
),
child: Opacity(
opacity: fullPageAnimation.value,
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
leading: BackButton(color: AppColors.of(context).text),
actions: [
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: IconButton(
focusColor: Colors.transparent,
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
onPressed: () async {
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(
// duration: Duration(milliseconds: 1000),
// content: Text(
// "Hamarosan...",
// ),
// ),
// );
showDialog(
context: context,
builder: (context) => WillPopScope(
onWillPop: () async => false,
child: AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
title: Text("attention".i18n),
content: Text("share_disclaimer".i18n),
actions: [
ActionButton(
label: "understand".i18n,
onTap: () async {
Navigator.of(context).pop();
SharedGradeColors gradeColors =
await shareProvider
.shareCurrentGradeColors(context);
SharedTheme theme =
await shareProvider.shareCurrentTheme(
context,
gradeColors: gradeColors,
);
Share.share(
theme.id,
subject: 'share_subj_theme'.i18n,
);
},
),
],
),
),
);
},
icon: const Icon(
FeatherIcons.share2,
size: 22.0,
),
),
),
],
title: Text(
"theme_prev".i18n,
style: TextStyle(color: AppColors.of(context).text),
),
backgroundColor: Colors.transparent,
elevation: 0,
),
body: Stack(
children: [
Opacity(
opacity: 1 - backContainerAnimation.value * (1 / 100),
child: Transform.translate(
offset: Offset(0, backContainerAnimation.value),
child: Container(
height: double.infinity,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
gradient: LinearGradient(
// https://discord.com/channels/1111649116020285532/1153619667848548452
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: const [
0.35,
0.75
],
colors: [
settings.customBackgroundColor ??
Theme.of(context).colorScheme.background,
isBackgroundDifferent
? HSVColor.fromColor(Theme.of(context)
.colorScheme
.background)
.withSaturation((HSVColor.fromColor(
Theme.of(context)
.colorScheme
.background)
.saturation -
0.15)
.clamp(0.0, 1.0))
.toColor()
: backgroundGradientBottomColor,
]),
),
margin: const EdgeInsets.symmetric(
vertical: 30, horizontal: 20),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: SizedBox(
width: double.infinity,
child: Padding(
padding: const EdgeInsets.only(top: 24.0),
child: Opacity(
opacity: 1 - backContentAnimation.value * (1 / 100),
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Transform.translate(
offset:
Offset(0, -24 + backContentAnimation.value),
child: Transform.scale(
scale: backContentScaleAnimation.value,
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(
left: 32.0,
right: 32.0,
top: 16.0,
bottom: 6.0,
),
child: FilterBar(
items: const [
Tab(text: "All"),
Tab(text: "Grades"),
Tab(text: "Messages"),
Tab(text: "Absences"),
],
controller: _testTabController,
padding: EdgeInsets.zero,
censored: true,
disableFading: true,
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 18.0, vertical: 8.0),
child: NewGradesSurprise(
[
Grade.fromJson(
{
"Uid": "0,Ertekeles",
"RogzitesDatuma":
"2022-01-01T23:00:00Z",
"KeszitesDatuma":
"2022-01-01T23:00:00Z",
"LattamozasDatuma": null,
"Tantargy": {
"Uid": "0",
"Nev": "reFilc szakirodalom",
"Kategoria": {
"Uid": "0,_",
"Nev": "_",
"Leiras": "Nem mondom meg"
},
"SortIndex": 2
},
"Tema":
"Kupak csomag vásárlás vizsga",
"Tipus": {
"Uid": "0,_",
"Nev": "_",
"Leiras":
"Évközi jegy/értékelés",
},
"Mod": {
"Uid": "0,_",
"Nev": "_",
"Leiras": "_ feladat",
},
"ErtekFajta": {
"Uid": "1,Osztalyzat",
"Nev": "Osztalyzat",
"Leiras":
"Elégtelen (1) és Jeles (5) között az öt alapértelmezett érték"
},
"ErtekeloTanarNeve": "Premium",
"Jelleg": "Ertekeles",
"SzamErtek": 5,
"SzovegesErtek": "Jeles(5)",
"SulySzazalekErteke": 100,
"SzovegesErtekelesRovidNev":
null,
"OsztalyCsoport": {"Uid": "0"},
"SortIndex": 2
},
),
],
censored: true,
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24.0, vertical: 6.0),
child: Panel(
child: GradeTile(
Grade.fromJson(
{
"Uid": "0,Ertekeles",
"RogzitesDatuma":
"2022-01-01T23:00:00Z",
"KeszitesDatuma":
"2022-01-01T23:00:00Z",
"LattamozasDatuma": null,
"Tantargy": {
"Uid": "0",
"Nev": "reFilc szakosztály",
"Kategoria": {
"Uid": "0,_",
"Nev": "_",
"Leiras": "Nem mondom meg"
},
"SortIndex": 2
},
"Tema":
"Kupak csomag vásárlás vizsga",
"Tipus": {
"Uid": "0,_",
"Nev": "_",
"Leiras":
"Évközi jegy/értékelés",
},
"Mod": {
"Uid": "0,_",
"Nev": "_",
"Leiras": "_ feladat",
},
"ErtekFajta": {
"Uid": "1,Osztalyzat",
"Nev": "Osztalyzat",
"Leiras":
"Elégtelen (1) és Jeles (5) között az öt alapértelmezett érték"
},
"ErtekeloTanarNeve": "Premium",
"Jelleg": "Ertekeles",
"SzamErtek": 5,
"SzovegesErtek": "Jeles(5)",
"SulySzazalekErteke": 100,
"SzovegesErtekelesRovidNev":
null,
"OsztalyCsoport": {"Uid": "0"},
"SortIndex": 2
},
),
padding: EdgeInsets.zero,
censored: true,
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24.0, vertical: 6.0),
child: Panel(
child: HomeworkTile(
Homework.fromJson(
{
"Uid": "0",
"Tantargy": {
"Uid": "0",
"Nev":
"reFilc premium előnyei",
"Kategoria": {
"Uid": "0,_",
"Nev": "_",
"Leiras":
"reFilc premium előnyei",
},
"SortIndex": 0
},
"TantargyNeve":
"reFilc premium előnyei",
"RogzitoTanarNeve":
"Kupak János",
"Szoveg":
"45 perc filctollal való rajzolás",
"FeladasDatuma":
"2022-01-01T23:00:00Z",
"HataridoDatuma":
"2022-01-01T23:00:00Z",
"RogzitesIdopontja":
"2022-01-01T23:00:00Z",
"IsTanarRogzitette": true,
"IsTanuloHaziFeladatEnabled":
false,
"IsMegoldva": false,
"IsBeadhato": false,
"OsztalyCsoport": {"Uid": "0"},
"IsCsatolasEngedelyezes": false
},
),
padding: EdgeInsets.zero,
censored: true,
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24.0, vertical: 6.0),
child: Panel(
child: MessageTile(
Message.fromJson(
{
"azonosito": 0,
"isElolvasva": true,
"isToroltElem": false,
"tipus": {
"azonosito": 1,
"kod": "BEERKEZETT",
"rovidNev":
"Beérkezett üzenet",
"nev": "Beérkezett üzenet",
"leiras": "Beérkezett üzenet"
},
"uzenet": {
"azonosito": 0,
"kuldesDatum":
"2022-01-01T23:00:00",
"feladoNev": "reFilc",
"feladoTitulus":
"Nagyon magas szintű személy",
"szoveg":
"<p>Kedves Felhasználó!</p><p><br></p><p>A prémium vásárlásakor kapott filctollal 90%-al több esély van jó jegyek szerzésére.</p>",
"targy":
"Filctoll használati útmutató",
"statusz": {
"azonosito": 2,
"kod": "KIKULDVE",
"rovidNev": "Kiküldve",
"nev": "Kiküldve",
"leiras": "Kiküldve"
},
"cimzettLista": [
{
"azonosito": 0,
"kretaAzonosito": 0,
"nev": "Tinta Józsi",
"tipus": {
"azonosito": 0,
"kod": "TANULO",
"rovidNev": "Tanuló",
"nev": "Tanuló",
"leiras": "Tanuló"
}
},
],
"csatolmanyok": [
{
"azonosito": 0,
"fajlNev": "Filctoll.doc"
}
]
}
},
),
censored: true,
padding: const EdgeInsets.only(
right: 6.0,
),
),
),
),
],
),
),
),
),
),
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Wrap(
children: [
Opacity(
opacity: pickerContainerAnimation.value,
child: SizedBox(
width: double.infinity,
child: Container(
padding: const EdgeInsets.only(bottom: 12.0),
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: backgroundGradientBottomColor,
offset: const Offset(0, -8),
blurRadius: 16,
spreadRadius: 18,
),
],
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: const [
0.0,
0.175
],
colors: [
backgroundGradientBottomColor,
backgroundGradientBottomColor,
]),
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0),
child: FilterBar(
items: [
ColorTab(
color: accentColorMap[
settings.accentColor] ??
unknownColor,
tab: Tab(
text: "colorpicker_presets"
.i18n)),
// ColorTab(
// color: unknownColor,
// tab: Tab(text: "enter_id".i18n)),
/*ColorTab(
color:
settings.customAccentColor ??
unknownColor,
tab: Tab(
text: "colorpicker_saved"
.i18n)),*/
ColorTab(
unlocked: hasAccess,
color: settings
.customBackgroundColor ??
unknownColor,
tab: Tab(
text: "colorpicker_background"
.i18n)),
ColorTab(
unlocked: hasAccess,
color: settings
.customHighlightColor ??
unknownColor,
tab: Tab(
text: "colorpicker_panels"
.i18n)),
ColorTab(
unlocked: hasAccess,
color:
settings.customAccentColor ??
unknownColor,
tab: Tab(
text: "colorpicker_accent"
.i18n)),
// ColorTab(
// unlocked: hasAccess,
// color: settings.customIconColor ??
// unknownColor,
// tab: Tab(
// text:
// "colorpicker_icon".i18n)),
],
onTap: (index) {
// if (!hasAccess) {
// index = 0;
// _colorsTabController.animateTo(0,
// duration: Duration.zero);
// PremiumLockedFeatureUpsell.show(
// context: context,
// feature: PremiumFeature
// .customcolors);
// }
switch (index) {
case 0:
setState(() {
colorMode =
CustomColorMode.theme;
});
break;
// case 1:
// setState(() {
// colorMode =
// CustomColorMode.enterId;
// });
// break;
/*case 1:
setState(() {
colorMode =
CustomColorMode.saved;
});
break;*/
case 1:
setState(() {
colorMode =
CustomColorMode.background;
});
break;
case 2:
setState(() {
colorMode =
CustomColorMode.highlight;
});
break;
case 3:
setState(() {
colorMode =
CustomColorMode.accent;
});
break;
case 4:
setState(() {
colorMode =
CustomColorMode.icon;
});
break;
}
},
controller: _colorsTabController,
padding: EdgeInsets.zero,
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12.0),
child: SafeArea(
child: FilcColorPicker(
colorMode: colorMode,
pickerColor: colorMode ==
CustomColorMode.accent
? settings.customAccentColor ??
unknownColor
: colorMode ==
CustomColorMode.background
? settings
.customBackgroundColor ??
unknownColor
: colorMode ==
CustomColorMode.theme
? (accentColorMap[settings
.accentColor] ??
AppColors.of(context)
.text) // idk what else
: colorMode ==
CustomColorMode
.highlight
? settings
.customHighlightColor ??
unknownColor
: settings
.customIconColor ??
unknownColor,
onColorChanged: (c) {
setState(() {
updateCustomColor(c, false);
});
setTheme(settings.theme, false);
},
onColorChangeEnd: (c, {adaptive}) {
setState(() {
if (adaptive == true) {
settings.update(
accentColor:
AccentColor.adaptive);
settings.update(
customBackgroundColor:
AppColors.of(context)
.background,
store: true);
settings.update(
customHighlightColor:
AppColors.of(context)
.highlight,
store: true);
settings.update(
customIconColor:
const Color(0x00000000),
store: true);
} else {
updateCustomColor(c, true);
}
});
setTheme(settings.theme, true);
},
onThemeIdProvided: (theme) {
// changing grade colors
List<Color> colors = [
theme.gradeColors.oneColor,
theme.gradeColors.twoColor,
theme.gradeColors.threeColor,
theme.gradeColors.fourColor,
theme.gradeColors.fiveColor,
];
settings.update(
gradeColors: colors);
// changing shadow effect
settings.update(
shadowEffect:
theme.shadowEffect);
// changing theme
setState(() {
updateCustomColor(
null,
true,
accent: theme.accentColor,
background:
theme.backgroundColor,
panels: theme.panelsColor,
icon: theme.iconColor,
);
});
setTheme(settings.theme, true);
},
),
),
),
],
),
),
),
),
],
),
),
],
),
),
),
);
},
),
);
}
}
class ColorTab extends StatelessWidget {
const ColorTab(
{super.key,
required this.tab,
required this.color,
this.unlocked = true});
final Tab tab;
final Color color;
final bool unlocked;
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Transform.translate(
offset: const Offset(-3, 1),
child: unlocked
? Container(
width: 15,
height: 15,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
border: Border.all(color: Colors.black, width: 2.0),
),
)
: const Padding(
padding: EdgeInsets.symmetric(horizontal: 2),
child: Icon(Icons.lock,
color: Color.fromARGB(255, 82, 82, 82), size: 18),
),
),
tab
],
);
}
}
class PremiumColorPickerItem extends StatelessWidget {
const PremiumColorPickerItem(
{super.key, required this.label, this.onTap, required this.color});
final String label;
final void Function()? onTap;
final Color color;
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
label,
style: TextStyle(
color: AppColors.of(context).text,
fontWeight: FontWeight.w500),
),
),
Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: color, shape: BoxShape.circle, border: Border.all()),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,67 @@
import 'package:i18n_extension/i18n_extension.dart';
extension SettingsLocalization on String {
static final _t = Translations.byLocale("hu_hu") +
{
"en_en": {
"theme_prev": "Preview",
"colorpicker_presets": "Presets",
"colorpicker_background": "Background",
"colorpicker_panels": "Panels",
"colorpicker_accent": "Accent",
"colorpicker_icon": "Icon",
"need_sub": "You need Kupak subscription to use modify this.",
"advanced": "Advanced",
"enter_id": "Enter ID",
"theme_id": "Theme ID...",
"theme_not_found": "Theme not found!",
"attention": "Attention!",
"share_disclaimer":
"By sharing the theme, you agree that the nickname you set and all settings of the theme will be shared publicly.",
"understand": "I understand",
"share_subj_theme": "Share Theme",
},
"hu_hu": {
"theme_prev": "Előnézet",
"colorpicker_presets": "Téma",
"colorpicker_background": "Háttér",
"colorpicker_panels": "Panelek",
"colorpicker_accent": "Színtónus",
"colorpicker_icon": "Ikon",
"need_sub": "A módosításhoz Kupak szintű támogatás szükséges.",
"advanced": "Haladó",
"enter_id": "ID megadása",
"theme_id": "Téma azonosító...",
"theme_not_found": "A téma nem található!",
"attention": "Figyelem!",
"share_disclaimer":
"A téma megosztásával elfogadod, hogy az általad beállított becenév és a téma minden beállítása nyilvánosan megosztásra kerüljön.",
"understand": "Értem",
"share_subj_theme": "Téma Megosztás",
},
"de_de": {
"theme_prev": "Vorschau",
"colorpicker_presets": "Farben",
"colorpicker_background": "Hintergrund",
"colorpicker_panels": "Tafeln",
"colorpicker_accent": "Akzent",
"colorpicker_icon": "Ikone",
"need_sub":
"Sie benötigen ein Kupak-Abonnement, um diese Funktion zu ändern.",
"advanced": "Fortschrittlich",
"enter_id": "Geben Sie die ID ein",
"theme_id": "Themen-ID...",
"theme_not_found": "Thema nicht gefunden!",
"attention": "Achtung!",
"share_disclaimer":
"Durch das Teilen des Themes erklären Sie sich damit einverstanden, dass der von Ihnen festgelegte Spitzname und alle Einstellungen des Themes öffentlich geteilt werden.",
"understand": "Ich verstehe",
"share_subj_theme": "Thema Teilen",
},
};
String get i18n => localize(this, _t);
String fill(List<Object> params) => localizeFill(this, params);
String plural(int value) => localizePlural(value, this, _t);
String version(Object modifier) => localizeVersion(modifier, this, _t);
}

View File

@@ -0,0 +1,105 @@
import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/models/user.dart';
import 'package:refilc_mobile_ui/common/bottom_sheet_menu/bottom_sheet_menu_item.dart';
// import 'package:refilc_plus/models/premium_scopes.dart';
// import 'package:refilc_plus/providers/premium_provider.dart';
// import 'package:refilc_plus/ui/mobile/premium/upsell.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
import 'package:provider/provider.dart';
// ignore: must_be_immutable
class UserMenuNickname extends StatelessWidget {
late User u;
UserMenuNickname(this.u, {super.key});
@override
Widget build(BuildContext context) {
return BottomSheetMenuItem(
onPressed: () {
// if (!Provider.of<PremiumProvider>(context, listen: false)
// .hasScope(PremiumScopes.nickname)) {
// PremiumLockedFeatureUpsell.show(
// context: context, feature: PremiumFeature.profile);
// return;
// }
showDialog(
context: context, builder: (context) => UserNicknameEditor(u));
},
icon: const Icon(FeatherIcons.edit2),
title: Text("edit_nickname".i18n),
);
}
}
// ignore: must_be_immutable
class UserNicknameEditor extends StatefulWidget {
late User u;
UserNicknameEditor(this.u, {super.key});
@override
State<UserNicknameEditor> createState() => _UserNicknameEditorState();
}
class _UserNicknameEditorState extends State<UserNicknameEditor> {
final _userName = TextEditingController();
late final UserProvider user;
@override
void initState() {
super.initState();
user = Provider.of<UserProvider>(context, listen: false);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text("change_username".i18n),
content: TextField(
controller: _userName,
autofocus: true,
decoration: InputDecoration(
border: const OutlineInputBorder(),
label: Text(widget.u.name),
suffixIcon: IconButton(
icon: const Icon(FeatherIcons.x),
onPressed: () {
setState(() {
_userName.text = "";
});
},
),
),
),
actions: [
TextButton(
child: Text(
"cancel".i18n,
style: const TextStyle(fontWeight: FontWeight.w500),
),
onPressed: () {
Navigator.of(context).maybePop();
},
),
TextButton(
child: Text(
"done".i18n,
style: const TextStyle(fontWeight: FontWeight.w500),
),
onPressed: () {
widget.u.nickname = _userName.text.trim();
Provider.of<DatabaseProvider>(context, listen: false)
.store
.storeUser(widget.u);
Provider.of<UserProvider>(context, listen: false).refresh();
Navigator.of(context).pop(true);
},
),
],
);
}
}

View File

@@ -0,0 +1,236 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/user_provider.dart';
import 'package:refilc/models/user.dart';
import 'package:refilc_mobile_ui/common/bottom_sheet_menu/bottom_sheet_menu_item.dart';
// import 'package:refilc_plus/models/premium_scopes.dart';
// import 'package:refilc_plus/providers/premium_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
import 'package:provider/provider.dart';
import 'package:image_picker/image_picker.dart';
import 'package:image_crop/image_crop.dart';
// ignore: must_be_immutable
class UserMenuProfilePic extends StatelessWidget {
late User u;
UserMenuProfilePic(this.u, {super.key});
@override
Widget build(BuildContext context) {
// if (!Provider.of<PremiumProvider>(context)
// .hasScope(PremiumScopes.nickname)) {
// return const SizedBox();
// }
return BottomSheetMenuItem(
onPressed: () {
showDialog(
context: context, builder: (context) => UserProfilePicEditor(u));
},
icon: const Icon(FeatherIcons.camera),
title: Text("edit_profile_picture".i18n),
);
}
}
// ignore: must_be_immutable
class UserProfilePicEditor extends StatefulWidget {
late User u;
UserProfilePicEditor(this.u, {super.key});
@override
State<UserProfilePicEditor> createState() => _UserProfilePicEditorState();
}
class _UserProfilePicEditorState extends State<UserProfilePicEditor> {
late final UserProvider user;
final cropKey = GlobalKey<CropState>();
File? _file;
File? _sample;
File? _lastCropped;
File? image;
Future pickImage() async {
try {
final image = await ImagePicker().pickImage(source: ImageSource.gallery);
if (image == null) return;
File imageFile = File(image.path);
final sample = await ImageCrop.sampleImage(
file: imageFile,
preferredSize: context.size!.longestSide.ceil(),
);
_sample?.delete();
_file?.delete();
setState(() {
_sample = sample;
_file = imageFile;
});
} on PlatformException catch (e) {
log('Failed to pick image: $e');
}
}
Widget cropImageWidget() {
return SizedBox(
height: 300,
child: Crop.file(
_sample!,
key: cropKey,
aspectRatio: 1.0,
),
);
}
Widget openImageWidget() {
return InkWell(
customBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14.0),
),
onTap: () => pickImage(),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(14.0),
),
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 32.0, horizontal: 8.0),
child: Column(
children: [
Text(
"click_here".i18n,
style: const TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.w600,
),
),
Text(
"select_profile_picture".i18n,
style: const TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
),
)
],
),
),
);
}
Future<void> _cropImage() async {
final scale = cropKey.currentState!.scale;
final area = cropKey.currentState!.area;
if (area == null || _file == null) {
return;
}
final sample = await ImageCrop.sampleImage(
file: _file!,
preferredSize: (2000 / scale).round(),
);
final file = await ImageCrop.cropImage(
file: sample,
area: area,
);
sample.delete();
_lastCropped?.delete();
_lastCropped = file;
List<int> imageBytes = await _lastCropped!.readAsBytes();
String base64Image = base64Encode(imageBytes);
widget.u.picture = base64Image;
Provider.of<DatabaseProvider>(context, listen: false)
.store
.storeUser(widget.u);
Provider.of<UserProvider>(context, listen: false).refresh();
debugPrint('$file');
}
@override
void initState() {
super.initState();
user = Provider.of<UserProvider>(context, listen: false);
}
@override
void dispose() {
super.dispose();
_file?.delete();
_sample?.delete();
_lastCropped?.delete();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(14.0))),
contentPadding: const EdgeInsets.only(top: 10.0),
title: Text("edit_profile_picture".i18n),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding:
const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0),
child: _sample == null ? openImageWidget() : cropImageWidget(),
),
if (widget.u.picture != "")
TextButton(
child: Text(
"remove_profile_picture".i18n,
style: const TextStyle(
fontWeight: FontWeight.w500, color: Colors.red),
),
onPressed: () {
widget.u.picture = "";
Provider.of<DatabaseProvider>(context, listen: false)
.store
.storeUser(widget.u);
Provider.of<UserProvider>(context, listen: false).refresh();
Navigator.of(context).pop(true);
},
),
],
),
actions: [
TextButton(
child: Text(
"cancel".i18n,
style: const TextStyle(fontWeight: FontWeight.w500),
),
onPressed: () {
Navigator.of(context).maybePop();
},
),
TextButton(
child: Text(
"done".i18n,
style: const TextStyle(fontWeight: FontWeight.w500),
),
onPressed: () async {
await _cropImage();
Navigator.of(context).pop(true);
},
),
],
);
}
}