Merge branch 'dev' into master
This commit is contained in:
@@ -75,7 +75,7 @@ class _FSTimetableState extends State<FSTimetable> {
|
||||
body: ListView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 24.0),
|
||||
itemCount: maxLessonCount + 1,
|
||||
itemCount: maxLessonCount + 2,
|
||||
itemBuilder: (context, index) {
|
||||
List<Widget> columns = [];
|
||||
for (int dayIndex = -1; dayIndex < days.length; dayIndex++) {
|
||||
@@ -119,10 +119,8 @@ class _FSTimetableState extends State<FSTimetable> {
|
||||
|
||||
if (lessons.isEmpty) continue;
|
||||
|
||||
int lsnIndx = int.tryParse(lessons.first.lessonIndex) ?? 1;
|
||||
final dayOffset = lsnIndx == 0 ? 1 : lsnIndx;
|
||||
|
||||
if (index == 0 && dayIndex >= 0) {
|
||||
// if (index == 0 || dayIndex >=0) {
|
||||
columns.add(
|
||||
SizedBox(
|
||||
width: colw,
|
||||
@@ -141,16 +139,10 @@ class _FSTimetableState extends State<FSTimetable> {
|
||||
continue;
|
||||
}
|
||||
|
||||
final lessonIndex = index - dayOffset;
|
||||
|
||||
Lesson? lsn = lessons.firstWhereOrNull(
|
||||
(e) => e.lessonIndex == (index - 1).toString());
|
||||
|
||||
if (lessonIndex < 0 ||
|
||||
lessonIndex > lessons.length ||
|
||||
(index == 1 && lsnIndx != 0) ||
|
||||
(lsnIndx != 0 && lessonIndex - 1 == -1) ||
|
||||
lsn == null) {
|
||||
if (lsn == null) {
|
||||
columns.add(SizedBox(width: colw));
|
||||
continue;
|
||||
}
|
||||
@@ -259,4 +251,4 @@ class _FSTimetableState extends State<FSTimetable> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,11 +98,48 @@ class PlusPlanCard extends StatelessWidget {
|
||||
onTap: () {
|
||||
// pop dialog
|
||||
Navigator.of(context).pop();
|
||||
// start payment process
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) {
|
||||
return PremiumActivationView(product: id);
|
||||
}));
|
||||
// show payment option selector
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0)),
|
||||
title: Text('payment_method'.i18n),
|
||||
content: Text('select_payment_method'.i18n),
|
||||
actions: [
|
||||
ActionButton(
|
||||
label: "stripe".i18n,
|
||||
onTap: () {
|
||||
// pop dialog
|
||||
Navigator.of(context).pop();
|
||||
// start payment process
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) {
|
||||
return PremiumActivationView(
|
||||
product: id,
|
||||
paymentProvider: "stripe",
|
||||
);
|
||||
}));
|
||||
},
|
||||
),
|
||||
ActionButton(
|
||||
label: "paypal".i18n,
|
||||
onTap: () {
|
||||
// pop dialog
|
||||
Navigator.of(context).pop();
|
||||
// start payment process
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) {
|
||||
return PremiumActivationView(
|
||||
product: id,
|
||||
paymentProvider: "paypal",
|
||||
);
|
||||
}));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -47,11 +47,16 @@ extension SettingsLocalization on String {
|
||||
"rfp_16": "Private leaks and informations about upcoming features",
|
||||
"rfp_17": "Grade exporting",
|
||||
"rfp_18": "Viewing exported grades",
|
||||
// docs popup
|
||||
// docs and payment method popup
|
||||
"docs": "Documents",
|
||||
"docs_acceptance":
|
||||
"By pressing the \"Next\" button, you accept reFilc's Terms and Conditions for subscriptions (available at the following link: filc.one/pay-terms) and our Privacy Policy (available at the following link: filc.one/pay-privacy).",
|
||||
"next": "Next",
|
||||
"payment_method": "Payment Method",
|
||||
"select_payment_method":
|
||||
"Please select a preferred payment method! Credit card payments are handled by Stripe, which also supports Apple Pay, Google Pay and Revolut Pay.",
|
||||
"stripe": "Credit Card",
|
||||
"paypal": "PayPal",
|
||||
// other
|
||||
"and": " and ",
|
||||
"every": "Every ",
|
||||
@@ -106,11 +111,16 @@ extension SettingsLocalization on String {
|
||||
"rfp_16": "Privát betekintések és információk közelgő újításokról",
|
||||
"rfp_17": "Jegy exportálás",
|
||||
"rfp_18": "Exportált jegyek megtekintése",
|
||||
// docs popup
|
||||
// docs and payment method popup
|
||||
"docs": "Dokumentumok",
|
||||
"docs_acceptance":
|
||||
"A \"Tovább\" gombra kattintva elfogadod a reFilc előfizetésekkel kapcsolatos Általános Szerződési Feltételeit (elérhető az alábbi link-en: filc.one/pay-terms), valamint Adatkezelési Tájékoztatónkat (elérhető az alábbi link-en: filc.one/pay-privacy).",
|
||||
"next": "Tovább",
|
||||
"payment_method": "Fizetési mód",
|
||||
"select_payment_method":
|
||||
"Kérlek válassz egy fizetési módot! A bankkártyás fizetést a Stripe biztosítja, mely támogat Apple Pay-t, Google Pay-t és Revolut Pay-t is.",
|
||||
"stripe": "Bankkártya",
|
||||
"paypal": "PayPal",
|
||||
// other
|
||||
"and": " és ",
|
||||
"every": "Minden ",
|
||||
@@ -167,11 +177,16 @@ extension SettingsLocalization on String {
|
||||
"rfp_16": "Private Leaks und Informationen über kommende Funktionen",
|
||||
"rfp_17": "Notenexport",
|
||||
"rfp_18": "Anzeigen exportierter Noten",
|
||||
// docs popup
|
||||
// docs and payment method popup
|
||||
"docs": "Dokumente",
|
||||
"docs_acceptance":
|
||||
"Durch Drücken der Schaltfläche \"Weiter\" akzeptieren Sie die Allgemeinen Geschäftsbedingungen von reFilc für Abonnements (verfügbar unter folgendem Link: filc.one/pay-terms) und unsere Datenschutzrichtlinie (verfügbar unter folgendem Link: filc.one/pay-privacy).",
|
||||
"next": "Weiter",
|
||||
"payment_method": "Zahlungsmethode",
|
||||
"select_payment_method":
|
||||
"Bitte wählen Sie eine bevorzugte Zahlungsmethode aus! Kreditkartenzahlungen werden von Stripe abgewickelt, der auch Apple Pay, Google Pay und Revolut Pay unterstützt.",
|
||||
"stripe": "Kreditkarte",
|
||||
"paypal": "PayPal",
|
||||
// other
|
||||
"and": " und ",
|
||||
"every": "Jeder ",
|
||||
|
||||
170
refilc_mobile_ui/lib/screens/login/qwid_login.dart
Normal file
170
refilc_mobile_ui/lib/screens/login/qwid_login.dart
Normal file
@@ -0,0 +1,170 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class QwIDLoginWidget extends StatefulWidget {
|
||||
const QwIDLoginWidget({super.key, required this.onLogin});
|
||||
|
||||
// final String selectedSchool;
|
||||
final void Function(String code) onLogin;
|
||||
|
||||
@override
|
||||
State<QwIDLoginWidget> createState() => _QwIDLoginWidgetState();
|
||||
}
|
||||
|
||||
class _QwIDLoginWidgetState extends State<QwIDLoginWidget>
|
||||
with TickerProviderStateMixin {
|
||||
late final WebViewController controller;
|
||||
late AnimationController _animationController;
|
||||
var loadingPercentage = 0;
|
||||
var currentUrl = '';
|
||||
bool _hasFadedIn = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_animationController = AnimationController(
|
||||
vsync: this, // Use the TickerProviderStateMixin
|
||||
duration: const Duration(milliseconds: 350),
|
||||
);
|
||||
|
||||
controller = WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setNavigationDelegate(NavigationDelegate(
|
||||
onNavigationRequest: (n) async {
|
||||
if (n.url.startsWith('refilc://oauth2-callback/qwid')) {
|
||||
setState(() {
|
||||
loadingPercentage = 0;
|
||||
currentUrl = n.url;
|
||||
});
|
||||
|
||||
// final String instituteCode = widget.selectedSchool;
|
||||
// if (!n.url.startsWith(
|
||||
// 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=')) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
String longLivedToken = n.url
|
||||
.replaceAll('refilc://oauth2-callback/qwid?access_token=', '');
|
||||
|
||||
widget.onLogin(longLivedToken);
|
||||
// Future.delayed(const Duration(milliseconds: 500), () {
|
||||
// Navigator.of(context).pop();
|
||||
// });
|
||||
// Navigator.of(context).pop();
|
||||
|
||||
return NavigationDecision.prevent;
|
||||
} else {
|
||||
return NavigationDecision.navigate;
|
||||
}
|
||||
},
|
||||
onPageStarted: (url) async {
|
||||
// setState(() {
|
||||
// loadingPercentage = 0;
|
||||
// currentUrl = url;
|
||||
// });
|
||||
|
||||
// // final String instituteCode = widget.selectedSchool;
|
||||
// if (!url.startsWith(
|
||||
// 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=')) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// List<String> requiredThings = url
|
||||
// .replaceAll(
|
||||
// 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=',
|
||||
// '')
|
||||
// .replaceAll(
|
||||
// '&scope=openid%20email%20offline_access%20kreta-ellenorzo-webapi.public%20kreta-eugyintezes-webapi.public%20kreta-fileservice-webapi.public%20kreta-mobile-global-webapi.public%20kreta-dkt-webapi.public%20kreta-ier-webapi.public&state=refilc_student_mobile&session_state=',
|
||||
// ':')
|
||||
// .split(':');
|
||||
|
||||
// String code = requiredThings[0];
|
||||
// // String sessionState = requiredThings[1];
|
||||
|
||||
// widget.onLogin(code);
|
||||
// // Future.delayed(const Duration(milliseconds: 500), () {
|
||||
// // Navigator.of(context).pop();
|
||||
// // });
|
||||
// // Navigator.of(context).pop();
|
||||
},
|
||||
onProgress: (progress) {
|
||||
setState(() {
|
||||
loadingPercentage = progress;
|
||||
});
|
||||
},
|
||||
onPageFinished: (url) {
|
||||
setState(() {
|
||||
loadingPercentage = 100;
|
||||
});
|
||||
},
|
||||
))
|
||||
..loadRequest(
|
||||
Uri.parse(
|
||||
'https://qwid.qwit.dev/oauth2/authorize?client_id=99aa103a-0bd7-43e0-8421-3bb0b2f6adb1&scope=*&redirect_uri=https://api.refilc.hu/v4/oauth2/callback/app/qwid&response_type=code'), // &institute_code=${widget.selectedSchool}
|
||||
);
|
||||
}
|
||||
|
||||
// Future<void> loadLoginUrl() async {
|
||||
// String nonceStr = await Provider.of<KretaClient>(context, listen: false)
|
||||
// .getAPI(KretaAPI.nonce, json: false);
|
||||
|
||||
// Nonce nonce = getNonce(nonceStr, );
|
||||
// }
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Step 3: Dispose of the animation controller
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Trigger the fade-in animation only once when loading reaches 100%
|
||||
if (loadingPercentage == 100 && !_hasFadedIn) {
|
||||
_animationController.forward(); // Play the animation
|
||||
_hasFadedIn =
|
||||
true; // Set the flag to true, so the animation is not replayed
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
// Webview that will be displayed only when the loading is 100%
|
||||
if (loadingPercentage == 100)
|
||||
FadeTransition(
|
||||
opacity: Tween<double>(begin: 0, end: 1).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Curves.easeIn,
|
||||
),
|
||||
),
|
||||
child: WebViewWidget(
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
|
||||
// Show the CircularProgressIndicator while loading is not 100%
|
||||
if (loadingPercentage < 100)
|
||||
Center(
|
||||
child: TweenAnimationBuilder(
|
||||
tween: Tween<double>(begin: 0, end: loadingPercentage / 100.0),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
builder: (context, double value, child) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
value: value, // Smoothly animates the progress
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@ import 'package:refilc_mobile_ui/screens/settings/user/profile_pic.dart';
|
||||
// import 'package:refilc_plus/ui/mobile/settings/welcome_message.dart';
|
||||
// import 'package:refilc_mobile_ui/screens/error_screen.dart';
|
||||
import 'package:refilc_mobile_ui/screens/error_report_screen.dart';
|
||||
import 'submenu/cloud_sync_screen.dart';
|
||||
import 'submenu/general_screen.dart';
|
||||
import 'package:refilc_plus/ui/mobile/plus/settings_inline.dart';
|
||||
|
||||
@@ -428,6 +429,13 @@ class SettingsScreenState extends State<SettingsScreen>
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0), bottom: Radius.circular(4.0)),
|
||||
),
|
||||
// cloud-sync
|
||||
const MenuCloudSyncSettings(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(4.0),
|
||||
bottom: Radius.circular(4.0),
|
||||
),
|
||||
),
|
||||
// open dcs (digital collaboration space)
|
||||
PanelButton(
|
||||
onPressed: () => _openDKT(user.user!),
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
// import 'package:refilc/models/settings.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:refilc/api/client.dart';
|
||||
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_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
|
||||
import 'package:refilc_mobile_ui/screens/login/qwid_login.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/models/cloud_sync_data.dart';
|
||||
// import 'package:provider/provider.dart';
|
||||
import 'submenu_screen.i18n.dart';
|
||||
|
||||
class MenuCloudSyncSettings extends StatelessWidget {
|
||||
const MenuCloudSyncSettings({
|
||||
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 CloudSyncSettingsScreen()),
|
||||
),
|
||||
title: Text("cloud_sync".i18n),
|
||||
leading: Icon(
|
||||
FeatherIcons.uploadCloud,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
borderRadius: borderRadius,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CloudSyncSettingsScreen extends StatefulWidget {
|
||||
const CloudSyncSettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
CloudSyncSettingsScreenState createState() => CloudSyncSettingsScreenState();
|
||||
}
|
||||
|
||||
class CloudSyncSettingsScreenState extends State<CloudSyncSettingsScreen> {
|
||||
late SettingsProvider settingsProvider;
|
||||
late UserProvider user;
|
||||
|
||||
String longLivedToken = '';
|
||||
|
||||
@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(
|
||||
"cloud_sync".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 {
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
isScrollControlled:
|
||||
true, // This ensures the modal accommodates input fields properly
|
||||
builder: (BuildContext context) {
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height * 0.9 +
|
||||
MediaQuery.of(context).viewInsets.bottom,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFDAE4F7),
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(24.0),
|
||||
topLeft: Radius.circular(24.0),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 18),
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFB9C8E5),
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(2.0),
|
||||
topLeft: Radius.circular(2.0),
|
||||
),
|
||||
),
|
||||
width: 40,
|
||||
height: 4,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 14, left: 14, bottom: 24),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(16),
|
||||
),
|
||||
child: QwIDLoginWidget(
|
||||
onLogin: (String token) {
|
||||
setState(() {
|
||||
longLivedToken = token;
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
).then((value) {
|
||||
// After closing the modal bottom sheet, check if the code is set
|
||||
if (longLivedToken.isNotEmpty) {
|
||||
// Call your API after retrieving the code
|
||||
settingsProvider.update(
|
||||
cloudSyncToken: longLivedToken,
|
||||
store: true,
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('login_successful'.i18n)));
|
||||
}
|
||||
});
|
||||
},
|
||||
trailingDivider: true,
|
||||
title: Text(
|
||||
"qwit_sign_in".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.gradeOpeningFun ? .95 : .25),
|
||||
),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: settingsProvider.cloudSyncEnabled,
|
||||
onChanged: (value) {
|
||||
settingsProvider.update(
|
||||
cloudSyncEnabled: value,
|
||||
store: true,
|
||||
);
|
||||
},
|
||||
title: Text("cloud_sync_enabled".i18n),
|
||||
),
|
||||
PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
|
||||
onPressed: () async {
|
||||
if (settingsProvider.cloudSyncToken.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('sign_in_first'.i18n),
|
||||
),
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
FilcAPI.cloudSync(
|
||||
{
|
||||
"settings": jsonEncode(settingsProvider.toMap()),
|
||||
// "device_ids": [
|
||||
// settingsProvider.xFilcId,
|
||||
// ],
|
||||
// "refilc_plus_id": settingsProvider.plusSessionId,
|
||||
},
|
||||
settingsProvider.cloudSyncToken,
|
||||
).then((response) {
|
||||
if (response == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('sync_failed'.i18n),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
CloudSyncData cloudSyncData = CloudSyncData.fromJson(
|
||||
response['data']['cloud_sync_data']);
|
||||
|
||||
settingsProvider.updateFromMap(
|
||||
map: cloudSyncData.settings,
|
||||
store: true,
|
||||
);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('sync_successful'.i18n),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
trailingDivider: true,
|
||||
title: Text(
|
||||
"sync_now".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.gradeOpeningFun ? .95 : .25),
|
||||
),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,8 @@ extension SettingsLocalization on String {
|
||||
"understand": "I understand",
|
||||
"theme_share_failed": "An error occurred while sharing the theme.",
|
||||
"theme_share_ratelimit": "You can only share 1 theme per minute.",
|
||||
// cloud sync
|
||||
"cloud_sync": "Cloud Sync",
|
||||
},
|
||||
"hu_hu": {
|
||||
"general": "Általános",
|
||||
@@ -62,6 +64,8 @@ extension SettingsLocalization on String {
|
||||
"understand": "Értem",
|
||||
"theme_share_failed": "Hiba történt a téma megosztása közben.",
|
||||
"theme_share_ratelimit": "Csak 1 témát oszthatsz meg percenként.",
|
||||
// cloud sync
|
||||
"cloud_sync": "Felhő szinkronizálás",
|
||||
},
|
||||
"de_de": {
|
||||
"general": "Allgemeine",
|
||||
@@ -93,6 +97,8 @@ extension SettingsLocalization on String {
|
||||
"theme_share_failed":
|
||||
"Beim Teilen des Themas ist ein Fehler aufgetreten.",
|
||||
"theme_share_ratelimit": "Sie können nur 1 Thema pro Minute teilen.",
|
||||
// cloud sync
|
||||
"cloud_sync": "Cloud-Synchronisierung",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ dependencies:
|
||||
animations: ^2.0.11
|
||||
animated_list_plus: ^0.5.0
|
||||
confetti: ^0.7.0
|
||||
live_activities: ^1.9.1+1
|
||||
# live_activities: ^1.9.1+1
|
||||
animated_flip_counter: ^0.3.4
|
||||
lottie: ^3.1.0
|
||||
rive: ^0.12.4
|
||||
@@ -51,7 +51,7 @@ dependencies:
|
||||
rounded_expansion_tile:
|
||||
git:
|
||||
url: https://github.com/kimaah/rounded_expansion_tile.git
|
||||
go_router: ^14.2.0
|
||||
# go_router: ^14.2.0
|
||||
flutter_expandable_fab: ^2.0.0
|
||||
intl: ^0.19.0
|
||||
i18n_extension: ^12.0.1
|
||||
@@ -67,7 +67,7 @@ dependencies:
|
||||
uuid: ^4.3.3
|
||||
maps_launcher: ^2.2.0
|
||||
google_fonts: ^6.1.0
|
||||
flutter_any_logo: ^1.1.1
|
||||
# flutter_any_logo: ^1.1.1
|
||||
custom_sliding_segmented_control: ^1.8.1
|
||||
get_it: ^7.6.7
|
||||
xml: ^6.5.0
|
||||
|
||||
Reference in New Issue
Block a user