From 4d64705e59a30e4bf5112a5874960daa73b797a7 Mon Sep 17 00:00:00 2001 From: zypherift Date: Thu, 22 Aug 2024 22:46:11 +0200 Subject: [PATCH 01/22] fix for padding --- refilc_mobile_ui/lib/screens/login/login_screen.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/refilc_mobile_ui/lib/screens/login/login_screen.dart b/refilc_mobile_ui/lib/screens/login/login_screen.dart index 9de9d59..3690fae 100644 --- a/refilc_mobile_ui/lib/screens/login/login_screen.dart +++ b/refilc_mobile_ui/lib/screens/login/login_screen.dart @@ -1,6 +1,7 @@ // import 'dart:async'; import 'package:refilc/api/client.dart'; +import 'dart:io' show Platform; import 'package:refilc/api/login.dart'; import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart'; @@ -79,13 +80,19 @@ class LoginScreenState extends State { }); } + double paddingTop = 0; @override Widget build(BuildContext context) { precacheImage(const AssetImage('assets/images/showcase1.png'), context); precacheImage(const AssetImage('assets/images/showcase2.png'), context); precacheImage(const AssetImage('assets/images/showcase3.png'), context); precacheImage(const AssetImage('assets/images/showcase4.png'), context); - bool selected = false; + + if (Platform.isIOS) { + paddingTop = 0; + } else if (Platform.isAndroid) { + paddingTop = 20; + } return Scaffold( body: Container( @@ -102,7 +109,7 @@ class LoginScreenState extends State { children: [ // app icon Padding( - padding: const EdgeInsets.only(left: 24, top: 20), + padding: EdgeInsets.only(left: 24, top: paddingTop), child: Row( children: [ Image.asset( From 9ecee0bb01879b5187472868fb156a4f6a172036 Mon Sep 17 00:00:00 2001 From: Kima Date: Fri, 27 Sep 2024 20:51:01 +0200 Subject: [PATCH 02/22] hide "ads" if user has plus --- refilc/lib/ui/filter/widgets.dart | 3 ++- refilc/lib/ui/filter/widgets/ads.dart | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/refilc/lib/ui/filter/widgets.dart b/refilc/lib/ui/filter/widgets.dart index 5142a88..a3e59d9 100644 --- a/refilc/lib/ui/filter/widgets.dart +++ b/refilc/lib/ui/filter/widgets.dart @@ -178,7 +178,8 @@ Future> getFilterWidgets(FilterType activeData, // Ads case FilterType.ads: if (adProvider.available) { - items = ad_filter.getWidgets(adProvider.ads); + items = ad_filter.getWidgets( + adProvider.ads, Provider.of(context).hasPremium); } break; } diff --git a/refilc/lib/ui/filter/widgets/ads.dart b/refilc/lib/ui/filter/widgets/ads.dart index 6be93f1..c1ae632 100644 --- a/refilc/lib/ui/filter/widgets/ads.dart +++ b/refilc/lib/ui/filter/widgets/ads.dart @@ -2,13 +2,14 @@ import 'package:refilc/models/ad.dart'; import 'package:refilc/ui/date_widget.dart'; import 'package:refilc_mobile_ui/common/widgets/ad/ad_viewable.dart' as mobile; -List getWidgets(List providerAds) { +List getWidgets(List providerAds, bool hasPlus) { List items = []; if (providerAds.isNotEmpty) { for (var ad in providerAds) { if (ad.date.isBefore(DateTime.now()) && - ad.expireDate.isAfter(DateTime.now())) { + ad.expireDate.isAfter(DateTime.now()) && + (!hasPlus || ad.overridePremium)) { providerAds.sort((a, b) => -a.date.compareTo(b.date)); items.add(DateWidget( From a3694b59ec5959749c29d7f3c25b91cdcf3e9a76 Mon Sep 17 00:00:00 2001 From: Kima Date: Fri, 27 Sep 2024 21:28:07 +0200 Subject: [PATCH 03/22] doing something with ads --- refilc/lib/ui/filter/widgets.dart | 2 +- refilc/lib/ui/filter/widgets/ads.dart | 63 ++++++++++++++++--- .../lib/common/widgets/ad/ad_tile.dart | 7 ++- 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/refilc/lib/ui/filter/widgets.dart b/refilc/lib/ui/filter/widgets.dart index a3e59d9..b9bcec1 100644 --- a/refilc/lib/ui/filter/widgets.dart +++ b/refilc/lib/ui/filter/widgets.dart @@ -179,7 +179,7 @@ Future> getFilterWidgets(FilterType activeData, case FilterType.ads: if (adProvider.available) { items = ad_filter.getWidgets( - adProvider.ads, Provider.of(context).hasPremium); + adProvider.ads, context); } break; } diff --git a/refilc/lib/ui/filter/widgets/ads.dart b/refilc/lib/ui/filter/widgets/ads.dart index c1ae632..6e064a2 100644 --- a/refilc/lib/ui/filter/widgets/ads.dart +++ b/refilc/lib/ui/filter/widgets/ads.dart @@ -1,24 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:refilc/models/ad.dart'; import 'package:refilc/ui/date_widget.dart'; +import 'package:refilc_mobile_ui/common/widgets/ad/ad_tile.dart'; import 'package:refilc_mobile_ui/common/widgets/ad/ad_viewable.dart' as mobile; +import 'package:refilc_mobile_ui/plus/plus_screen.dart'; +import 'package:refilc_plus/providers/plus_provider.dart'; +import 'package:uuid/uuid.dart'; -List getWidgets(List providerAds, bool hasPlus) { +List getWidgets(List providerAds, BuildContext context) { List items = []; + bool hasPlus = Provider.of(context).hasPremium; + + DateWidget plusWidget = DateWidget( + key: const Uuid().v4(), + date: DateTime.now(), + widget: AdTile( + Ad( + title: 'reFilc+', + description: + 'Fizess elő reFilc+-ra, rejtsd el a hirdetéseket és támogasd az app működését!', + author: '', + logoUrl: Uri.parse('https://refilc.hu/image/brand/logo.png'), + overridePremium: false, + date: DateTime(2007, 6, 29, 9, 41), + expireDate: DateTime.now().add(const Duration(days: 11)), + launchUrl: Uri.parse('https://refilc.hu/plus'), + ), + onTap: () => Navigator.of(context, rootNavigator: true) + .push(MaterialPageRoute(builder: (context) { + return const PlusScreen(); + })), + padding: const EdgeInsets.symmetric(horizontal: 5.0), + showExternalIcon: false, + ), + ); + if (providerAds.isNotEmpty) { for (var ad in providerAds) { if (ad.date.isBefore(DateTime.now()) && - ad.expireDate.isAfter(DateTime.now()) && - (!hasPlus || ad.overridePremium)) { - providerAds.sort((a, b) => -a.date.compareTo(b.date)); + ad.expireDate.isAfter(DateTime.now())) { + if (!hasPlus || ad.overridePremium) { + providerAds.sort((a, b) => -a.date.compareTo(b.date)); - items.add(DateWidget( - key: ad.description, - date: ad.date, - widget: mobile.AdViewable(ad), - )); + items.add(DateWidget( + key: ad.description, + date: ad.date, + widget: mobile.AdViewable(ad), + )); + } + } else { + if (DateTime.now().weekday == DateTime.saturday && + items.isEmpty && + !hasPlus) { + items.add(plusWidget); + } } } + } else { + if (DateTime.now().weekday == DateTime.saturday && + items.isEmpty && + !hasPlus) { + items.add(plusWidget); + } } return items; diff --git a/refilc_mobile_ui/lib/common/widgets/ad/ad_tile.dart b/refilc_mobile_ui/lib/common/widgets/ad/ad_tile.dart index ddde59a..73b0cfb 100644 --- a/refilc_mobile_ui/lib/common/widgets/ad/ad_tile.dart +++ b/refilc_mobile_ui/lib/common/widgets/ad/ad_tile.dart @@ -5,11 +5,13 @@ import 'package:refilc_mobile_ui/common/panel/panel_button.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart'; class AdTile extends StatelessWidget { - const AdTile(this.ad, {super.key, this.onTap, this.padding}); + const AdTile(this.ad, + {super.key, this.onTap, this.padding, this.showExternalIcon = true}); final Ad ad; final Function()? onTap; final EdgeInsetsGeometry? padding; + final bool showExternalIcon; @override Widget build(BuildContext context) { @@ -46,7 +48,8 @@ class AdTile extends StatelessWidget { ), ) : null, - trailing: const Icon(FeatherIcons.externalLink), + trailing: + showExternalIcon ? const Icon(FeatherIcons.externalLink) : null, ), ); } From aa10f0672e430a3f8fbcef8df44cc1c9e8456b4b Mon Sep 17 00:00:00 2001 From: Kima Date: Fri, 27 Sep 2024 21:30:32 +0200 Subject: [PATCH 04/22] show ads only in even hours --- refilc/lib/ui/filter/widgets/ads.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/refilc/lib/ui/filter/widgets/ads.dart b/refilc/lib/ui/filter/widgets/ads.dart index 6e064a2..3c8b9a8 100644 --- a/refilc/lib/ui/filter/widgets/ads.dart +++ b/refilc/lib/ui/filter/widgets/ads.dart @@ -40,7 +40,8 @@ List getWidgets(List providerAds, BuildContext context) { if (providerAds.isNotEmpty) { for (var ad in providerAds) { if (ad.date.isBefore(DateTime.now()) && - ad.expireDate.isAfter(DateTime.now())) { + ad.expireDate.isAfter(DateTime.now()) && + DateTime.now().hour.isEven) { if (!hasPlus || ad.overridePremium) { providerAds.sort((a, b) => -a.date.compareTo(b.date)); From 63fd37c31f5f764f0cb61fe8df845171c4c048ef Mon Sep 17 00:00:00 2001 From: Kima Date: Fri, 27 Sep 2024 21:37:06 +0200 Subject: [PATCH 05/22] the ads got more acceptable --- refilc/lib/ui/filter/widgets/ads.dart | 2 +- refilc_mobile_ui/lib/common/widgets/ad/ad_tile.dart | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/refilc/lib/ui/filter/widgets/ads.dart b/refilc/lib/ui/filter/widgets/ads.dart index 3c8b9a8..c4a1660 100644 --- a/refilc/lib/ui/filter/widgets/ads.dart +++ b/refilc/lib/ui/filter/widgets/ads.dart @@ -41,7 +41,7 @@ List getWidgets(List providerAds, BuildContext context) { for (var ad in providerAds) { if (ad.date.isBefore(DateTime.now()) && ad.expireDate.isAfter(DateTime.now()) && - DateTime.now().hour.isEven) { + DateTime.now().hour.isOdd) { if (!hasPlus || ad.overridePremium) { providerAds.sort((a, b) => -a.date.compareTo(b.date)); diff --git a/refilc_mobile_ui/lib/common/widgets/ad/ad_tile.dart b/refilc_mobile_ui/lib/common/widgets/ad/ad_tile.dart index 73b0cfb..f325975 100644 --- a/refilc_mobile_ui/lib/common/widgets/ad/ad_tile.dart +++ b/refilc_mobile_ui/lib/common/widgets/ad/ad_tile.dart @@ -30,6 +30,7 @@ class AdTile extends StatelessWidget { Text( ad.description, style: TextStyle( + fontSize: 14.5, fontWeight: FontWeight.w500, color: AppColors.of(context).text.withOpacity(0.7), ), @@ -40,6 +41,8 @@ class AdTile extends StatelessWidget { ? ClipRRect( borderRadius: BorderRadius.circular(50.0), child: Image.network( + width: 45.0, + height: 45.0, ad.logoUrl.toString(), errorBuilder: (context, error, stackTrace) { ad.logoUrl = null; @@ -48,8 +51,12 @@ class AdTile extends StatelessWidget { ), ) : null, - trailing: - showExternalIcon ? const Icon(FeatherIcons.externalLink) : null, + trailing: showExternalIcon + ? const Icon( + FeatherIcons.externalLink, + size: 20.0, + ) + : null, ), ); } From 92fe3b7dcd1657942cd69e75159fc34999190d89 Mon Sep 17 00:00:00 2001 From: Kima Date: Fri, 27 Sep 2024 22:02:31 +0200 Subject: [PATCH 06/22] fixed yellow lines at profile image grade streak indicator --- refilc/assets/images/apple_fire_emoji.png | Bin 0 -> 21481 bytes .../lib/common/profile_image/profile_image.dart | 16 ++++++++++------ .../lib/screens/settings/settings_screen.dart | 10 +++++++--- 3 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 refilc/assets/images/apple_fire_emoji.png diff --git a/refilc/assets/images/apple_fire_emoji.png b/refilc/assets/images/apple_fire_emoji.png new file mode 100644 index 0000000000000000000000000000000000000000..7ea6be86af9870002d3ae10d545e7ae780b6e065 GIT binary patch literal 21481 zcmV)bK&iipP)005u}1^@s6i_d2*00004XF*Lt006O% z3;baP0000WV@Og>004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00007 zbV*G`2jmL@5-J^?qH?bQ000JJOGiWi{{a60|De66lK=ox?MXyIRCwC$eRsShRh9qu z+*?)M{qlR012ZrT8AVYL5fl&wK`|i6PgfKbbzKvyi?F(a7+75}hZRvwtE>nrqF?}7 zBu5ED8eo{5(+mB&yQ=Q_{c-QDI#utD%Pc(sc(tFmj4yS+>TjQW&i8!J_Yfwor+jU= zEY#^m_W`W$!Q>H$D&l`l)jO+&OVq zo+bdz^PWF(7oOs!@4UHE1(06*=i};&mmI85c*A*g!t1^RAep#3|3?5SttalnQ@qTS z{*6Wfl^N4OOCFs(@|6p|j?J47j*d9;Yyit9?$G}QfJ>K8+=ZujC6lKvNdcf*Lv+*} zsO8Hr^po%YL+h_MJ}>y>r~VnhbrW}KYmZ>V6L;aM-mE1{R;INo%y1p5e-?sW7`;sQqykN+H<^ro)@h$rsWQ@p1%b;`tDc&bP69{a53&Zw1c-WaLIEMUqM zsM$bI#~KVjvbd*l*>}Gb#*G(8haYh!fW;Gc>?s|=D< z1G*om41l@`-FvNpn$$r3k3Tq>Z@*=q;FIT_0pQw+yY{~TFs;@m?!r^Ox_ee)VA{$Q zD+Vw$3~)aXRe@R`kN}!A0oV`%9^&r8n#_^+xjf755^ttWpKKss^rwsZu~ojq|E zp5hf=`=?=3?Ov4t`*D2))M^2`1_V{0)(aS*nydrOsbhHE;_k+;zJ5WZl6`~Y-f%X6 zH4}GmJOB>XC+@;iz4kbu!MohHngN(*1G+H^RI7jrfe=8Ifs|1hRL}z>80t*Xyy_e8 z!riw%kIwy%e*|#X#7!(8!NCL%xK-DzS*rlR#4YGX0|*&V3ZM`OYCsKuQ3!?z-Qh4s z<}#Wq?>I<*`U6)+r=IaI0Dd-c6J-Q^Y~jRR*qTas=G?UmAWaRzMiZzdKo9^#fDVAD z1{eSa22pjn}FbO@?HMLik`Mz8t7SrPFb;cMUcQA~7C5CnHj zq$OJgz%}bPn~n|`9s(LIpcMmw0Z0STfFeM_f{+OSL4nXQIs{cqFtSUE=I<^(5%=G_ zTlDG^-iPP7pdp~U?w&x^R$S@c`)o!vXc~hGr!gpQ0Vx9ndkjNc0BQk3CJ<><>w{3b zg^}HX=JGoZG*_(qP57w`J__Kn=WY?Q@4geL+KMabq+zP)nChsoF`zgBQe#0!HsXO? z6x0Mn?sH0kNTE_61f>ns=K#&Mi)NaC{l_0wk9*rZz1yz;;ScWT))BmXLd3CE5pdr< z8l;1S3E(6JlGFk)a8VE*aDn?b=b$9}`DkPls5J^TnbFw1rZ)P^Z~lArSZ))G3vMd;zQA=t8>GX-}XTOOP@0U)DJv3fvT;EfYYZm5LFt?02>BO3K+H@Dubzr zeA?!N8327GR0!${9gShAz@R>d5&!8&C(vUHXIEc##Jd6f?YVIT|IhUksM?ww@SX2S z5QIsFF=GHWA_7tl!VHJ5Fku1$_jUJunhCYWFi54S&j%VyZ$1#0FTE@{?}F0-JnLVa zAUWWG2~=&(Wl{ngf^Q%hwjea@K*)KNlSi`Ad4%-2BHKu)0)^`6Fer^}ak6abeDj5W zy)^jz*FFH?if1(dO=sr>sJ12t+_brZv{gYQPFmys%TC
    Y$xW0&)s(El96@rDFN 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 UFeF2j(EtDd07*qoM6N<$g8P`$%m4rY literal 0 HcmV?d00001 diff --git a/refilc_mobile_ui/lib/common/profile_image/profile_image.dart b/refilc_mobile_ui/lib/common/profile_image/profile_image.dart index 6024a7f..69abe4b 100644 --- a/refilc_mobile_ui/lib/common/profile_image/profile_image.dart +++ b/refilc_mobile_ui/lib/common/profile_image/profile_image.dart @@ -266,12 +266,16 @@ class _ProfileImageState extends State { child: Transform.translate( offset: Offset(-widget.radius / 4, -widget.radius / 4), child: Container( - alignment: Alignment.topLeft, - child: Text( - '🔥', - style: TextStyle(fontSize: widget.radius * 0.8), - ), - ), + alignment: Alignment.topLeft, + child: Image.asset( + 'assets/images/apple_fire_emoji.png', + width: widget.radius, + ) + // Text( + // '🔥', + // style: TextStyle(fontSize: widget.radius * 0.8), + // ), + ), ), ), ), diff --git a/refilc_mobile_ui/lib/screens/settings/settings_screen.dart b/refilc_mobile_ui/lib/screens/settings/settings_screen.dart index cb6b2bb..52db5dc 100644 --- a/refilc_mobile_ui/lib/screens/settings/settings_screen.dart +++ b/refilc_mobile_ui/lib/screens/settings/settings_screen.dart @@ -782,10 +782,14 @@ class SettingsScreenState extends State color: AppColors.of(context).text.withOpacity(0.75), ), ), - leading: const Text( - "🔥", - style: TextStyle(fontSize: 22.0), + leading: Image.asset( + 'assets/images/apple_fire_emoji.png', + width: 24.0, ), + // leading: const Text( + // "🔥", + // style: TextStyle(fontSize: 22.0), + // ), trailing: Text( "${user.gradeStreak}", style: TextStyle( From 7919d0e2841f09f9f5a4b6f859558855e399db44 Mon Sep 17 00:00:00 2001 From: Kima Date: Fri, 27 Sep 2024 23:07:18 +0200 Subject: [PATCH 07/22] added grade delay to details and other small shit --- refilc/lib/api/login.dart | 1 + refilc/lib/api/providers/sync.dart | 6 +++--- refilc/lib/models/user.dart | 1 + refilc_kreta_api/lib/models/student.dart | 8 ++++++++ refilc_mobile_ui/lib/screens/login/login_screen.dart | 6 +----- .../lib/screens/settings/accounts/account_view.dart | 10 ++++++++++ .../screens/settings/accounts/account_view.i18n.dart | 6 ++++++ 7 files changed, 30 insertions(+), 8 deletions(-) diff --git a/refilc/lib/api/login.dart b/refilc/lib/api/login.dart index 6db0229..8f44bce 100644 --- a/refilc/lib/api/login.dart +++ b/refilc/lib/api/login.dart @@ -65,6 +65,7 @@ Future loginAPI({ parents: ['Teszt András', 'Teszt Linda'], json: {"a": "b"}, address: '1117 Budapest, Gábor Dénes utca 4.', + gradeDelay: 0, ), role: Role.parent, refreshToken: '', diff --git a/refilc/lib/api/providers/sync.dart b/refilc/lib/api/providers/sync.dart index d6be0b1..5e44466 100644 --- a/refilc/lib/api/providers/sync.dart +++ b/refilc/lib/api/providers/sync.dart @@ -71,6 +71,8 @@ Future syncAll(BuildContext context) { if (studentJson == null) return; Student student = Student.fromJson(studentJson); + // print(studentJson); + user.user?.name = student.name; // Store user @@ -89,13 +91,11 @@ Future syncAll(BuildContext context) { return false; } - - return Future.wait(tasks).then((value) { // Unlock lock = false; - if(Platform.isIOS && LiveCardProvider.hasActivityStarted == true){ + if (Platform.isIOS && LiveCardProvider.hasActivityStarted == true) { PlatformChannel.endLiveActivity(); LiveCardProvider.hasActivityStarted = false; } diff --git a/refilc/lib/models/user.dart b/refilc/lib/models/user.dart index 1b76ec2..0f4ae6c 100644 --- a/refilc/lib/models/user.dart +++ b/refilc/lib/models/user.dart @@ -59,6 +59,7 @@ class User { birth: DateTime.now(), yearId: '1', parents: [], + gradeDelay: 0, ), role: Role.values[map["role"] ?? 0], nickname: map["nickname"] ?? "", diff --git a/refilc_kreta_api/lib/models/student.dart b/refilc_kreta_api/lib/models/student.dart index 31aa35e..e4b46bf 100644 --- a/refilc_kreta_api/lib/models/student.dart +++ b/refilc_kreta_api/lib/models/student.dart @@ -11,6 +11,8 @@ class Student { String? address; String? groupId; List parents; + int gradeDelay; + String? bankAccount; // List parentsPhone; String? className; @@ -22,6 +24,8 @@ class Student { required this.yearId, this.address, required this.parents, + required this.gradeDelay, + this.bankAccount, // required this.parentsPhone, this.json, }); @@ -57,6 +61,10 @@ class Student { : null : null, parents: parents, + gradeDelay: json["Intezmeny"]["TestreszabasBeallitasok"] + ["ErtekelesekMegjelenitesenekKesleltetesenekMerteke"] ?? + 0, + bankAccount: json["Bankszamla"]["BankszamlaSzam"], json: json, ); } diff --git a/refilc_mobile_ui/lib/screens/login/login_screen.dart b/refilc_mobile_ui/lib/screens/login/login_screen.dart index 3690fae..9d20c7e 100644 --- a/refilc_mobile_ui/lib/screens/login/login_screen.dart +++ b/refilc_mobile_ui/lib/screens/login/login_screen.dart @@ -4,19 +4,14 @@ import 'package:refilc/api/client.dart'; import 'dart:io' show Platform; import 'package:refilc/api/login.dart'; import 'package:refilc/theme/colors/colors.dart'; -import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart'; import 'package:refilc_mobile_ui/common/custom_snack_bar.dart'; import 'package:refilc_mobile_ui/common/system_chrome.dart'; -import 'package:refilc_mobile_ui/common/widgets/absence/absence_display.dart'; -import 'package:refilc_mobile_ui/screens/login/login_button.dart'; -import 'package:refilc_mobile_ui/screens/login/login_input.dart'; import 'package:refilc_mobile_ui/screens/login/school_input/school_input.dart'; import 'package:refilc_mobile_ui/screens/settings/privacy_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'login_screen.i18n.dart'; import 'package:carousel_slider/carousel_slider.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:refilc_mobile_ui/screens/login/kreten_login.dart'; //new library for new web login class LoginScreen extends StatefulWidget { @@ -454,6 +449,7 @@ class LoginScreenState extends State { // }), // ); // } + // ignore: non_constant_identifier_names void _NewLoginAPI({required BuildContext context}) { String code = codeController.text; diff --git a/refilc_mobile_ui/lib/screens/settings/accounts/account_view.dart b/refilc_mobile_ui/lib/screens/settings/accounts/account_view.dart index 7f4eadd..598b2ca 100644 --- a/refilc_mobile_ui/lib/screens/settings/accounts/account_view.dart +++ b/refilc_mobile_ui/lib/screens/settings/accounts/account_view.dart @@ -56,6 +56,16 @@ class AccountView extends StatelessWidget { Detail( title: "parents".plural(user.student.parents.length), description: user.student.parents.join(", ")), + if (user.student.gradeDelay > 0) + Detail( + title: "grade_delay".i18n, + description: "hrs".i18n.fill([user.student.gradeDelay]), + ), + // if ((user.student.bankAccount ?? "").isNotEmpty) + // Detail( + // title: "bank_account".i18n, + // description: (user.student.bankAccount ?? "not_provided".i18n), + // ), const SizedBox( height: 10.0, ), diff --git a/refilc_mobile_ui/lib/screens/settings/accounts/account_view.i18n.dart b/refilc_mobile_ui/lib/screens/settings/accounts/account_view.i18n.dart index 5e38fae..1e8c115 100644 --- a/refilc_mobile_ui/lib/screens/settings/accounts/account_view.i18n.dart +++ b/refilc_mobile_ui/lib/screens/settings/accounts/account_view.i18n.dart @@ -10,6 +10,8 @@ extension Localization on String { "address": "Home address", "parents": "Parent(s)", "parents_phone": "Parents' phone number: ", + "grade_delay": "Grade visibility delay", + "hrs": "%s hour(s)", }, "hu_hu": { "birthdate": "Születési dátum", @@ -17,6 +19,8 @@ extension Localization on String { "class": "Osztály", "address": "Lakcím", "parents": "Szülő(k)", + "grade_delay": "Jegy megjelenítési késleltetés", + "hrs": "%s óra", }, "de_de": { "birthdate": "Geburtsdatum", @@ -24,6 +28,8 @@ extension Localization on String { "class": "Klasse", "address": "Wohnanschrift", "parents": "Elter(n)", + "grade_delay": "Notenverzögerung", + "hrs": "%s Stunde(n)", }, }; From a2cbe5d90bb9707f6e5960d4920a3155a483cd07 Mon Sep 17 00:00:00 2001 From: Kima Date: Sat, 28 Sep 2024 17:04:28 +0200 Subject: [PATCH 08/22] changed version number --- refilc/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/refilc/pubspec.yaml b/refilc/pubspec.yaml index a7fa3f0..9f81ba2 100644 --- a/refilc/pubspec.yaml +++ b/refilc/pubspec.yaml @@ -3,7 +3,7 @@ description: "Egy nem hivatalos e-KRÉTA kliens, diákoktól diákoknak." homepage: https://refilc.hu publish_to: "none" -version: 5.0.4+274 +version: 5.0.5+275 environment: sdk: ">=3.3.2 <=3.4.3" From d7741ca1c46cb2f056850b86611731b4c0fb8e34 Mon Sep 17 00:00:00 2001 From: Kima Date: Sat, 28 Sep 2024 17:33:57 +0200 Subject: [PATCH 09/22] tried testing sync bug and fixed ads even more --- refilc_kreta_api/lib/client/client.dart | 9 +++++---- refilc_mobile_ui/lib/common/widgets/ad/ad_tile.dart | 4 ++-- refilc_mobile_ui/lib/common/widgets/ad/ad_viewable.dart | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/refilc_kreta_api/lib/client/client.dart b/refilc_kreta_api/lib/client/client.dart index e9ff255..0f93cda 100644 --- a/refilc_kreta_api/lib/client/client.dart +++ b/refilc_kreta_api/lib/client/client.dart @@ -85,6 +85,7 @@ class KretaClient { if (res.statusCode == 401) { headerMap.remove("authorization"); + print("DEBUG: 401 error, refreshing login"); await refreshLogin(); } else { break; @@ -257,8 +258,8 @@ class KretaClient { refreshToken ??= loginUser.refreshToken; - // print("REFRESH TOKEN BELOW"); - // print(refreshToken); + print("REFRESH TOKEN BELOW"); + print(refreshToken); if (refreshToken != null) { // print("REFRESHING LOGIN"); @@ -268,8 +269,8 @@ class KretaClient { refreshToken: loginUser.refreshToken, instituteCode: loginUser.instituteCode, )); - // print("REFRESH RESPONSE BELOW"); - // print(res); + print("REFRESH RESPONSE BELOW"); + print(res); if (res != null) { if (res.containsKey("error")) { // remove user if refresh token expired diff --git a/refilc_mobile_ui/lib/common/widgets/ad/ad_tile.dart b/refilc_mobile_ui/lib/common/widgets/ad/ad_tile.dart index f325975..2ae7e44 100644 --- a/refilc_mobile_ui/lib/common/widgets/ad/ad_tile.dart +++ b/refilc_mobile_ui/lib/common/widgets/ad/ad_tile.dart @@ -41,8 +41,8 @@ class AdTile extends StatelessWidget { ? ClipRRect( borderRadius: BorderRadius.circular(50.0), child: Image.network( - width: 45.0, - height: 45.0, + width: 42.0, + height: 42.0, ad.logoUrl.toString(), errorBuilder: (context, error, stackTrace) { ad.logoUrl = null; diff --git a/refilc_mobile_ui/lib/common/widgets/ad/ad_viewable.dart b/refilc_mobile_ui/lib/common/widgets/ad/ad_viewable.dart index 1e1d103..ccff405 100644 --- a/refilc_mobile_ui/lib/common/widgets/ad/ad_viewable.dart +++ b/refilc_mobile_ui/lib/common/widgets/ad/ad_viewable.dart @@ -12,6 +12,7 @@ class AdViewable extends StatelessWidget { @override Widget build(BuildContext context) { return AdTile( + padding: const EdgeInsets.symmetric(horizontal: 5.0), ad, onTap: () => launchUrl( ad.launchUrl, From 816ddf58a2929aff9be414bf3c1208ed2e0a8fea Mon Sep 17 00:00:00 2001 From: Kima Date: Wed, 2 Oct 2024 21:06:01 +0200 Subject: [PATCH 10/22] changed how analytics work --- refilc/lib/api/client.dart | 16 ++++++---- refilc/lib/models/settings.dart | 11 +++++++ .../lib/plus/components/plan_card.dart | 27 ++++++++++------- .../lib/screens/settings/settings_screen.dart | 29 ++++++++++--------- 4 files changed, 53 insertions(+), 30 deletions(-) diff --git a/refilc/lib/api/client.dart b/refilc/lib/api/client.dart index 0387e84..c7a0dd0 100644 --- a/refilc/lib/api/client.dart +++ b/refilc/lib/api/client.dart @@ -18,7 +18,7 @@ import 'package:connectivity_plus/connectivity_plus.dart'; class FilcAPI { // API base - static const baseUrl = "https://api.refilc.hu"; + static const baseUrl = "https://api.refilcapp.hu"; // Public API static const schoolList = "$baseUrl/v3/public/school-list"; @@ -51,7 +51,7 @@ class FilcAPI { static const gradeColorsByID = "$gradeColorsGet/"; // Payment API - static const payment = "$baseUrl/v3/payment"; + static const payment = "$baseUrl/v4/payment"; static const stripeSheet = "$payment/stripe-sheet"; static Future checkConnectivity() async => @@ -93,10 +93,14 @@ class FilcAPI { "x-filc-id": settings.xFilcId, "user-agent": userAgent, // platform things - "rf-platform": Platform.operatingSystem, - "rf-platform-version": Platform.operatingSystemVersion, - "rf-app-version": - const String.fromEnvironment("APPVER", defaultValue: "?"), + "rf-platform": + settings.analyticsEnabled ? Platform.operatingSystem : "unknown", + "rf-platform-version": settings.analyticsEnabled + ? Platform.operatingSystemVersion + : "unknown", + "rf-app-version": settings.analyticsEnabled + ? const String.fromEnvironment("APPVER", defaultValue: "?") + : "unknown", "rf-uinid": settings.xFilcId, }; diff --git a/refilc/lib/models/settings.dart b/refilc/lib/models/settings.dart index 6bde6b2..1863941 100644 --- a/refilc/lib/models/settings.dart +++ b/refilc/lib/models/settings.dart @@ -60,6 +60,7 @@ class SettingsProvider extends ChangeNotifier { UpdateChannel _updateChannel; Config _config; String _xFilcId; + bool _analyticsEnabled; bool _graphClassAvg; bool _goodStudent; bool _presentationMode; @@ -137,6 +138,7 @@ class SettingsProvider extends ChangeNotifier { required UpdateChannel updateChannel, required Config config, required String xFilcId, + required bool analyticsEnabled, required bool graphClassAvg, required bool goodStudent, required bool presentationMode, @@ -208,6 +210,7 @@ class SettingsProvider extends ChangeNotifier { _updateChannel = updateChannel, _config = config, _xFilcId = xFilcId, + _analyticsEnabled = analyticsEnabled, _graphClassAvg = graphClassAvg, _goodStudent = goodStudent, _presentationMode = presentationMode, @@ -297,6 +300,7 @@ class SettingsProvider extends ChangeNotifier { updateChannel: UpdateChannel.values[map["update_channel"]], config: Config.fromJson(configMap ?? {}), xFilcId: map["x_filc_id"], + analyticsEnabled: map["analytics_enabled"] == 1, graphClassAvg: map["graph_class_avg"] == 1, goodStudent: false, presentationMode: map["presentation_mode"] == 1, @@ -377,6 +381,7 @@ class SettingsProvider extends ChangeNotifier { "notification_poll_interval": _notificationPollInterval, "config": jsonEncode(config.json), "x_filc_id": _xFilcId, + "analytics_enabled": _analyticsEnabled ? 1 : 0, "graph_class_avg": _graphClassAvg ? 1 : 0, "presentation_mode": _presentationMode ? 1 : 0, "bell_delay_enabled": _bellDelayEnabled ? 1 : 0, @@ -458,6 +463,7 @@ class SettingsProvider extends ChangeNotifier { updateChannel: UpdateChannel.stable, config: Config.fromJson({}), xFilcId: const Uuid().v4(), + analyticsEnabled: true, graphClassAvg: false, goodStudent: false, presentationMode: false, @@ -532,6 +538,7 @@ class SettingsProvider extends ChangeNotifier { UpdateChannel get updateChannel => _updateChannel; Config get config => _config; String get xFilcId => _xFilcId; + bool get analyticsEnabled => _analyticsEnabled; bool get graphClassAvg => _graphClassAvg; bool get goodStudent => _goodStudent; bool get presentationMode => _presentationMode; @@ -604,6 +611,7 @@ class SettingsProvider extends ChangeNotifier { UpdateChannel? updateChannel, Config? config, String? xFilcId, + bool? analyticsEnabled, bool? graphClassAvg, bool? goodStudent, bool? presentationMode, @@ -708,6 +716,9 @@ class SettingsProvider extends ChangeNotifier { } if (config != null && config != _config) _config = config; if (xFilcId != null && xFilcId != _xFilcId) _xFilcId = xFilcId; + if (analyticsEnabled != null && analyticsEnabled != _analyticsEnabled) { + _analyticsEnabled = analyticsEnabled; + } if (graphClassAvg != null && graphClassAvg != _graphClassAvg) { _graphClassAvg = graphClassAvg; } diff --git a/refilc_mobile_ui/lib/plus/components/plan_card.dart b/refilc_mobile_ui/lib/plus/components/plan_card.dart index 18985b7..f590468 100644 --- a/refilc_mobile_ui/lib/plus/components/plan_card.dart +++ b/refilc_mobile_ui/lib/plus/components/plan_card.dart @@ -5,6 +5,7 @@ import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/ui/mobile/plus/activation_view/activation_view.dart'; import 'package:refilc_mobile_ui/plus/plus_screen.i18n.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:uuid/uuid.dart'; class PlusPlanCard extends StatelessWidget { const PlusPlanCard({ @@ -53,18 +54,24 @@ class PlusPlanCard extends StatelessWidget { if (Provider.of(context, listen: false).xFilcId == "none") { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text( - "Be kell kapcsolnod a Névtelen Analitikát a beállítások főoldalán, mielőtt reFilc+ előfizetést vásárolnál!", - style: - TextStyle(color: Colors.black, fontWeight: FontWeight.bold), - ), - backgroundColor: Colors.white, - )); - - return; + Provider.of(context, listen: false) + .update(xFilcId: const Uuid().v4(), store: true); } + // if (Provider.of(context, listen: false).xFilcId == + // "none") { + // ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + // content: Text( + // "Be kell kapcsolnod a Névtelen Analitikát a beállítások főoldalán, mielőtt reFilc+ előfizetést vásárolnál!", + // style: + // TextStyle(color: Colors.black, fontWeight: FontWeight.bold), + // ), + // backgroundColor: Colors.white, + // )); + + // return; + // } + if (Provider.of(context, listen: false).hasPremium) { if (!active) { launchUrl( diff --git a/refilc_mobile_ui/lib/screens/settings/settings_screen.dart b/refilc_mobile_ui/lib/screens/settings/settings_screen.dart index 52db5dc..8bf78c4 100644 --- a/refilc_mobile_ui/lib/screens/settings/settings_screen.dart +++ b/refilc_mobile_ui/lib/screens/settings/settings_screen.dart @@ -1177,7 +1177,7 @@ class SettingsScreenState extends State secondary: Icon( FeatherIcons.barChart2, size: 22.0, - color: settings.xFilcId != "none" + color: settings.analyticsEnabled ? AppColors.of(context).text.withOpacity(0.95) : AppColors.of(context).text.withOpacity(.25), ), @@ -1187,28 +1187,29 @@ class SettingsScreenState extends State fontWeight: FontWeight.w600, fontSize: 16.0, color: AppColors.of(context).text.withOpacity( - settings.xFilcId != "none" ? 1.0 : .5), + settings.analyticsEnabled ? 1.0 : .5), ), ), subtitle: Text( "Anonymous Usage Analytics".i18n, style: TextStyle( - color: AppColors.of(context).text.withOpacity( - settings.xFilcId != "none" ? .5 : .2), + color: AppColors.of(context) + .text + .withOpacity(settings.analyticsEnabled ? .5 : .2), ), ), onChanged: (v) { - String newId; - if (v == false) { - newId = "none"; - } else if (settings.xFilcId == "none") { - newId = SettingsProvider.defaultSettings().xFilcId; - } else { - newId = settings.xFilcId; - } - settings.update(xFilcId: newId); + // String newId; + // if (v == false) { + // newId = "none"; + // } else if (settings.xFilcId == "none") { + // newId = SettingsProvider.defaultSettings().xFilcId; + // } else { + // newId = settings.xFilcId; + // } + settings.update(analyticsEnabled: v); }, - value: settings.xFilcId != "none", + value: settings.analyticsEnabled, activeColor: Theme.of(context).colorScheme.secondary, ), ), From 6634010b970a316bef9131ffa87f471042958dc3 Mon Sep 17 00:00:00 2001 From: Kima Date: Wed, 2 Oct 2024 21:17:03 +0200 Subject: [PATCH 11/22] made re-activation easier --- refilc_mobile_ui/lib/plus/plus_screen.dart | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/refilc_mobile_ui/lib/plus/plus_screen.dart b/refilc_mobile_ui/lib/plus/plus_screen.dart index d6d1bd3..a5ca2ff 100644 --- a/refilc_mobile_ui/lib/plus/plus_screen.dart +++ b/refilc_mobile_ui/lib/plus/plus_screen.dart @@ -1,5 +1,6 @@ // ignore_for_file: use_build_context_synchronously +import 'package:flutter/services.dart'; import 'package:refilc_mobile_ui/plus/plus_screen.i18n.dart'; import 'package:refilc_mobile_ui/plus/components/plan_card.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart'; @@ -335,6 +336,46 @@ class PlusScreenState extends State { contentPadding: const EdgeInsets.only(left: 15.0, right: 10.0), onTap: () async { + // try clipboard re-activation + final data = await Clipboard.getData("text/plain"); + if (data != null && + data.text != null && + data.text != "") { + // activate using clipboard data + final result = await context + .read() + .auth + .finishAuth(data.text!); + + if (!result && mounted) { + ScaffoldMessenger.of(context) + .showSnackBar(const SnackBar( + content: Text( + "Sikertelen aktiválás. Kérlek próbáld újra később!", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold), + ), + backgroundColor: Colors.red, + )); + } else { + ScaffoldMessenger.of(context) + .showSnackBar(const SnackBar( + content: Text( + "Sikeres aktiválás!", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold), + ), + backgroundColor: Colors.green, + )); + + Future.delayed(const Duration(seconds: 2), + () => Navigator.of(context).pop()); + } + } + + // try re-activation using refresh final result = await context .read() .auth From 0ec33f86316dd450c3801c82b0a68ed332795bfa Mon Sep 17 00:00:00 2001 From: Kima Date: Sun, 6 Oct 2024 23:53:24 +0200 Subject: [PATCH 12/22] changed subscription document acceptance --- .../lib/plus/components/plan_card.dart | 49 +++++++++---- refilc_mobile_ui/lib/plus/plus_screen.dart | 70 +++++++++---------- .../lib/plus/plus_screen.i18n.dart | 20 +++++- 3 files changed, 88 insertions(+), 51 deletions(-) diff --git a/refilc_mobile_ui/lib/plus/components/plan_card.dart b/refilc_mobile_ui/lib/plus/components/plan_card.dart index f590468..f8aba19 100644 --- a/refilc_mobile_ui/lib/plus/components/plan_card.dart +++ b/refilc_mobile_ui/lib/plus/components/plan_card.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:refilc/models/settings.dart'; +import 'package:refilc_mobile_ui/common/action_button.dart'; import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/ui/mobile/plus/activation_view/activation_view.dart'; import 'package:refilc_mobile_ui/plus/plus_screen.i18n.dart'; @@ -39,18 +40,18 @@ class PlusPlanCard extends StatelessWidget { Widget build(BuildContext context) { return GestureDetector( onTap: () { - if (!docsAccepted) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text( - "El kell fogadnod az ÁSZF-et és az Adatkezelési Tájékoztatót!", - style: - TextStyle(color: Colors.black, fontWeight: FontWeight.bold), - ), - backgroundColor: Colors.white, - )); + // if (!docsAccepted) { + // ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + // content: Text( + // "El kell fogadnod az ÁSZF-et és az Adatkezelési Tájékoztatót!", + // style: + // TextStyle(color: Colors.black, fontWeight: FontWeight.bold), + // ), + // backgroundColor: Colors.white, + // )); - return; - } + // return; + // } if (Provider.of(context, listen: false).xFilcId == "none") { @@ -84,9 +85,29 @@ class PlusPlanCard extends StatelessWidget { return; } - Navigator.of(context).push(MaterialPageRoute(builder: (context) { - return PremiumActivationView(product: id); - })); + showDialog( + context: context, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0)), + title: Text('docs'.i18n), + content: Text('docs_acceptance'.i18n), + actions: [ + ActionButton( + label: "next".i18n, + onTap: () { + // pop dialog + Navigator.of(context).pop(); + // start payment process + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) { + return PremiumActivationView(product: id); + })); + }, + ), + ], + ), + ); }, child: Container( decoration: BoxDecoration( diff --git a/refilc_mobile_ui/lib/plus/plus_screen.dart b/refilc_mobile_ui/lib/plus/plus_screen.dart index a5ca2ff..e2db8b1 100644 --- a/refilc_mobile_ui/lib/plus/plus_screen.dart +++ b/refilc_mobile_ui/lib/plus/plus_screen.dart @@ -420,41 +420,41 @@ class PlusScreenState extends State { ), ), // aszf warning - const SizedBox( - height: 18.0, - ), - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16.0), - border: Border.all( - color: Colors.black.withOpacity(0.2), - ), - ), - child: CheckboxListTile( - side: - const BorderSide(color: Colors.black, width: 2.0), - contentPadding: - const EdgeInsets.only(left: 15.0, right: 10.0), - value: docsAccepted, - onChanged: (value) { - setState(() { - docsAccepted = !docsAccepted; - }); - }, - // title: Text( - // 'show_lifetime'.i18n, - // style: const TextStyle( - // color: Colors.black, - // fontWeight: FontWeight.w500, - // ), - // ), - subtitle: const Text( - '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)?', - textAlign: TextAlign.start, - style: TextStyle(color: Colors.black), - ), - ), - ), + // const SizedBox( + // height: 18.0, + // ), + // Container( + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(16.0), + // border: Border.all( + // color: Colors.black.withOpacity(0.2), + // ), + // ), + // child: CheckboxListTile( + // side: + // const BorderSide(color: Colors.black, width: 2.0), + // contentPadding: + // const EdgeInsets.only(left: 15.0, right: 10.0), + // value: docsAccepted, + // onChanged: (value) { + // setState(() { + // docsAccepted = !docsAccepted; + // }); + // }, + // // title: Text( + // // 'show_lifetime'.i18n, + // // style: const TextStyle( + // // color: Colors.black, + // // fontWeight: FontWeight.w500, + // // ), + // // ), + // subtitle: const Text( + // '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)?', + // textAlign: TextAlign.start, + // style: TextStyle(color: Colors.black), + // ), + // ), + // ), // CheckboxListTile(value: false, onChanged: onChanged) // Padding( // padding: const EdgeInsets.symmetric(horizontal: 12.0), diff --git a/refilc_mobile_ui/lib/plus/plus_screen.i18n.dart b/refilc_mobile_ui/lib/plus/plus_screen.i18n.dart index 5c9b29b..902ba64 100644 --- a/refilc_mobile_ui/lib/plus/plus_screen.i18n.dart +++ b/refilc_mobile_ui/lib/plus/plus_screen.i18n.dart @@ -47,13 +47,19 @@ 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": "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", // other "and": " and ", "every": "Every ", "benefit": " benefit", "show_lifetime": "Show Lifetime Plans", "more_soon": "More coming soon...", - "faq_dc": "To redeem your benefits, contact us on Discord in DMs!", + "faq_dc": + "To redeem your Discord-related benefits, contact us on Discord in DMs!", "reactivate": "Reactivate Existing Subscription", }, "hu_hu": { @@ -100,6 +106,11 @@ 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": "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", // other "and": " és ", "every": "Minden ", @@ -107,7 +118,7 @@ extension SettingsLocalization on String { "show_lifetime": "Örökre szóló csomagok", "more_soon": "Hamarosan mégtöbb finomság...", "faq_dc": - "Az előnyök beváltásához írj nekünk Discord-on privát üzenetet!", + "A Discord-al kapcsolatos előnyök beváltásához írj nekünk Discord-on privát üzenetet!", "reactivate": "Meglévő előfizetés újraaktiválása", }, "de_de": { @@ -156,6 +167,11 @@ extension SettingsLocalization on String { "rfp_16": "Private Leaks und Informationen über kommende Funktionen", "rfp_17": "Notenexport", "rfp_18": "Anzeigen exportierter Noten", + // docs 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", // other "and": " und ", "every": "Jeder ", From fe3ed3183085327b20a63de5ce235e598f921e9f Mon Sep 17 00:00:00 2001 From: Kima Date: Sun, 6 Oct 2024 23:56:56 +0200 Subject: [PATCH 13/22] added new analytics option to db --- refilc/lib/database/init.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/refilc/lib/database/init.dart b/refilc/lib/database/init.dart index cde1c14..f6a744a 100644 --- a/refilc/lib/database/init.dart +++ b/refilc/lib/database/init.dart @@ -27,7 +27,8 @@ const settingsDB = DatabaseStruct("settings", { "notifications_absences": int, "notifications_messages": int, "notifications_lessons": int, // notifications - "x_filc_id": String, "graph_class_avg": int, "presentation_mode": int, + "x_filc_id": String, "graph_class_avg": int, + "analytics_enabled": int, "presentation_mode": int, "bell_delay": int, "bell_delay_enabled": int, "grade_opening_fun": int, "icon_pack": String, "premium_scopes": String, "premium_token": String, "premium_login": String, From a50f449f7ced0bf350e61c5ad78cde2f493268ce Mon Sep 17 00:00:00 2001 From: Kima Date: Mon, 7 Oct 2024 22:33:52 +0200 Subject: [PATCH 14/22] added extra fields in news objects --- refilc/lib/api/client.dart | 2 +- refilc/lib/models/news.dart | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/refilc/lib/api/client.dart b/refilc/lib/api/client.dart index c7a0dd0..db1ae6f 100644 --- a/refilc/lib/api/client.dart +++ b/refilc/lib/api/client.dart @@ -22,7 +22,7 @@ class FilcAPI { // Public API static const schoolList = "$baseUrl/v3/public/school-list"; - static const news = "$baseUrl/v3/public/news"; + static const news = "$baseUrl/v4/public/news"; static const supporters = "$baseUrl/v3/public/supporters"; // Private API diff --git a/refilc/lib/models/news.dart b/refilc/lib/models/news.dart index 05431d7..c2dd928 100644 --- a/refilc/lib/models/news.dart +++ b/refilc/lib/models/news.dart @@ -7,6 +7,8 @@ class News { String platform; bool emergency; DateTime expireDate; + List? appVersions; + String? specificAppId; Map? json; News({ @@ -18,6 +20,8 @@ class News { required this.platform, required this.emergency, required this.expireDate, + this.appVersions, + this.specificAppId, this.json, }); @@ -31,6 +35,10 @@ class News { platform: json["platform"] ?? "", emergency: json["emergency"] ?? false, expireDate: DateTime.parse(json["expire_date"] ?? ''), + appVersions: json["app_versions"] != null + ? List.from(json["app_versions"]) + : null, + specificAppId: json["specific_app_id"], json: json, ); } From f1ba5230fc01ff7ed801023ca83d8d4c60c2142d Mon Sep 17 00:00:00 2001 From: Kima Date: Thu, 10 Oct 2024 18:11:41 +0200 Subject: [PATCH 15/22] added theme share error handling for ratelimit response --- refilc/lib/api/client.dart | 12 ++++++++---- .../lib/providers/share_provider.dart | 10 +++++++--- .../settings/submenu/share_theme_popup.dart | 15 ++++++++++++++- .../settings/submenu/submenu_screen.i18n.dart | 6 ++++++ 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/refilc/lib/api/client.dart b/refilc/lib/api/client.dart index db1ae6f..9c76de8 100644 --- a/refilc/lib/api/client.dart +++ b/refilc/lib/api/client.dart @@ -235,7 +235,7 @@ class FilcAPI { } // sharing - static Future addSharedTheme(SharedTheme theme) async { + static Future addSharedTheme(SharedTheme theme) async { try { theme.json.remove('json'); theme.json['is_public'] = theme.isPublic.toString(); @@ -267,13 +267,17 @@ class FilcAPI { headers: {'Content-Type': 'application/x-www-form-urlencoded'}, ); - if (res.statusCode != 201) { - throw "HTTP ${res.statusCode}: ${res.body}"; - } + // if (res.statusCode != 201) { + // throw "HTTP ${res.statusCode}: ${res.body}"; + // } log('Shared theme successfully with ID: ${theme.id}'); + + return res.statusCode; } on Exception catch (error, stacktrace) { log("ERROR: FilcAPI.addSharedTheme: $error $stacktrace"); + + return 696; } } diff --git a/refilc_kreta_api/lib/providers/share_provider.dart b/refilc_kreta_api/lib/providers/share_provider.dart index 1df105c..5e01fb0 100644 --- a/refilc_kreta_api/lib/providers/share_provider.dart +++ b/refilc_kreta_api/lib/providers/share_provider.dart @@ -19,7 +19,7 @@ class ShareProvider extends ChangeNotifier { // } // themes - Future shareCurrentTheme( + Future shareCurrentTheme( BuildContext context, { bool isPublic = false, bool shareNick = true, @@ -56,9 +56,13 @@ class ShareProvider extends ChangeNotifier { }; SharedTheme theme = SharedTheme.fromJson(themeJson, gradeColors); - FilcAPI.addSharedTheme(theme); + int shareResult = await FilcAPI.addSharedTheme(theme); - return theme; + if (shareResult == 200) { + return theme; + } else { + return null; + } } Future getThemeById(BuildContext context, diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart b/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart index 92da234..abe3e10 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart @@ -5,8 +5,10 @@ import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:provider/provider.dart'; // import 'package:refilc/models/settings.dart'; import 'package:refilc/models/shared_theme.dart'; +import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc_kreta_api/providers/share_provider.dart'; import 'package:refilc_mobile_ui/common/action_button.dart'; +import 'package:refilc_mobile_ui/common/custom_snack_bar.dart'; import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart'; import 'package:share_plus/share_plus.dart'; import 'submenu_screen.i18n.dart'; @@ -133,13 +135,24 @@ class ShareThemeDialogState extends State { // share the fucking theme SharedGradeColors gradeColors = await shareProvider.shareCurrentGradeColors(context); - SharedTheme theme = await shareProvider.shareCurrentTheme( + SharedTheme? theme = await shareProvider.shareCurrentTheme( context, gradeColors: gradeColors, isPublic: isPublic, displayName: _title.text, ); + if (theme == null) { + ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( + content: Text("theme_share_failed".i18n, + style: const TextStyle(color: Colors.white)), + backgroundColor: AppColors.of(context).red, + context: context, + )); + + return; + } + // save theme id in settings // Provider.of(context, listen: false) // .update(currentThemeId: theme.id); diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart b/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart index ce76ee7..d90bb5a 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart @@ -30,6 +30,8 @@ extension SettingsLocalization on String { "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", + "theme_share_failed": + "An error occurred while sharing the theme. Wait 1 minute and try again.", }, "hu_hu": { "general": "Általános", @@ -58,6 +60,8 @@ extension SettingsLocalization on String { "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", + "theme_share_failed": + "Hiba történt a téma megosztása közben. Várj 1 percet, majd próbáld újra.", }, "de_de": { "general": "Allgemeine", @@ -86,6 +90,8 @@ extension SettingsLocalization on String { "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", + "theme_share_failed": + "Beim Teilen des Themas ist ein Fehler aufgetreten. Warten Sie 1 Minute und versuchen Sie es erneut.", }, }; From 939761695fdab5f8c6cbaae8e5321c511b2a6135 Mon Sep 17 00:00:00 2001 From: Kima Date: Thu, 10 Oct 2024 20:48:21 +0200 Subject: [PATCH 16/22] working error handling for theme sharing --- refilc/lib/api/client.dart | 19 ++++--- .../lib/providers/share_provider.dart | 18 ++++--- .../settings/submenu/share_theme_popup.dart | 51 +++++++++++++++---- .../settings/submenu/submenu_screen.i18n.dart | 11 ++-- 4 files changed, 72 insertions(+), 27 deletions(-) diff --git a/refilc/lib/api/client.dart b/refilc/lib/api/client.dart index 9c76de8..ed02aa1 100644 --- a/refilc/lib/api/client.dart +++ b/refilc/lib/api/client.dart @@ -271,7 +271,9 @@ class FilcAPI { // throw "HTTP ${res.statusCode}: ${res.body}"; // } - log('Shared theme successfully with ID: ${theme.id}'); + if (res.statusCode == 201) { + log('Shared theme successfully with ID: ${theme.id}'); + } return res.statusCode; } on Exception catch (error, stacktrace) { @@ -311,8 +313,7 @@ class FilcAPI { return null; } - static Future addSharedGradeColors( - SharedGradeColors gradeColors) async { + static Future addSharedGradeColors(SharedGradeColors gradeColors) async { try { gradeColors.json.remove('json'); gradeColors.json['is_public'] = gradeColors.isPublic.toString(); @@ -328,13 +329,19 @@ class FilcAPI { headers: {'Content-Type': 'application/x-www-form-urlencoded'}, ); - if (res.statusCode != 201) { - throw "HTTP ${res.statusCode}: ${res.body}"; + // if (res.statusCode != 201) { + // throw "HTTP ${res.statusCode}: ${res.body}"; + // } + + if (res.statusCode == 201) { + log('Shared grade colors successfully with ID: ${gradeColors.id}'); } - log('Shared grade colors successfully with ID: ${gradeColors.id}'); + return res.statusCode; } on Exception catch (error, stacktrace) { log("ERROR: FilcAPI.addSharedGradeColors: $error $stacktrace"); + + return 696; } } diff --git a/refilc_kreta_api/lib/providers/share_provider.dart b/refilc_kreta_api/lib/providers/share_provider.dart index 5e01fb0..c45c228 100644 --- a/refilc_kreta_api/lib/providers/share_provider.dart +++ b/refilc_kreta_api/lib/providers/share_provider.dart @@ -19,7 +19,7 @@ class ShareProvider extends ChangeNotifier { // } // themes - Future shareCurrentTheme( + Future<(SharedTheme?, int)> shareCurrentTheme( BuildContext context, { bool isPublic = false, bool shareNick = true, @@ -58,10 +58,10 @@ class ShareProvider extends ChangeNotifier { SharedTheme theme = SharedTheme.fromJson(themeJson, gradeColors); int shareResult = await FilcAPI.addSharedTheme(theme); - if (shareResult == 200) { - return theme; + if (shareResult == 201) { + return (theme, 201); } else { - return null; + return (null, shareResult); } } @@ -146,7 +146,7 @@ class ShareProvider extends ChangeNotifier { } // grade colors - Future shareCurrentGradeColors( + Future<(SharedGradeColors?, int)> shareCurrentGradeColors( BuildContext context, { bool isPublic = false, bool shareNick = true, @@ -166,9 +166,13 @@ class ShareProvider extends ChangeNotifier { }; SharedGradeColors gradeColors = SharedGradeColors.fromJson(gradeColorsJson); - FilcAPI.addSharedGradeColors(gradeColors); + int shareResult = await FilcAPI.addSharedGradeColors(gradeColors); - return gradeColors; + if (shareResult == 201) { + return (gradeColors, 201); + } else { + return (null, shareResult); + } } Future getGradeColorsById(BuildContext context, diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart b/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart index abe3e10..9c6b50b 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart @@ -133,16 +133,19 @@ class ShareThemeDialogState extends State { ), onPressed: () async { // share the fucking theme - SharedGradeColors gradeColors = + var (gradeColors, gradeColorsStatus) = await shareProvider.shareCurrentGradeColors(context); - SharedTheme? theme = await shareProvider.shareCurrentTheme( - context, - gradeColors: gradeColors, - isPublic: isPublic, - displayName: _title.text, - ); - if (theme == null) { + if (gradeColorsStatus == 429) { + ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( + content: Text("theme_share_ratelimit".i18n, + style: const TextStyle(color: Colors.white)), + backgroundColor: AppColors.of(context).red, + context: context, + )); + + return; + } else if (gradeColorsStatus != 201) { ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( content: Text("theme_share_failed".i18n, style: const TextStyle(color: Colors.white)), @@ -153,6 +156,36 @@ class ShareThemeDialogState extends State { return; } + var (theme, themeStatus) = await shareProvider.shareCurrentTheme( + context, + gradeColors: gradeColors!, + isPublic: isPublic, + displayName: _title.text, + ); + + if (themeStatus == 429) { + ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( + content: Text("theme_share_ratelimit".i18n, + style: const TextStyle(color: Colors.white)), + backgroundColor: AppColors.of(context).red, + context: context, + )); + + return; + } else if (themeStatus != 201) { + ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( + content: Text("theme_share_failed".i18n, + style: const TextStyle(color: Colors.white)), + backgroundColor: AppColors.of(context).red, + context: context, + )); + + return; + } + + print(theme); + print(themeStatus); + // save theme id in settings // Provider.of(context, listen: false) // .update(currentThemeId: theme.id); @@ -162,7 +195,7 @@ class ShareThemeDialogState extends State { // show the share popup Share.share( - theme.id, + theme!.id, subject: 'share_subj_theme'.i18n, ); }, diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart b/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart index d90bb5a..35c63b1 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/submenu_screen.i18n.dart @@ -30,8 +30,8 @@ extension SettingsLocalization on String { "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", - "theme_share_failed": - "An error occurred while sharing the theme. Wait 1 minute and try again.", + "theme_share_failed": "An error occurred while sharing the theme.", + "theme_share_ratelimit": "You can only share 1 theme per minute.", }, "hu_hu": { "general": "Általános", @@ -60,8 +60,8 @@ extension SettingsLocalization on String { "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", - "theme_share_failed": - "Hiba történt a téma megosztása közben. Várj 1 percet, majd próbáld újra.", + "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.", }, "de_de": { "general": "Allgemeine", @@ -91,7 +91,8 @@ extension SettingsLocalization on String { "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", "theme_share_failed": - "Beim Teilen des Themas ist ein Fehler aufgetreten. Warten Sie 1 Minute und versuchen Sie es erneut.", + "Beim Teilen des Themas ist ein Fehler aufgetreten.", + "theme_share_ratelimit": "Sie können nur 1 Thema pro Minute teilen.", }, }; From 2dafe5ed028df3b49c136f7c1662a63296682d5f Mon Sep 17 00:00:00 2001 From: Kima Date: Tue, 12 Nov 2024 23:27:14 +0100 Subject: [PATCH 17/22] updated packages, did things and maybe finally fixed login issue --- refilc/lib/api/login.dart | 16 +++ refilc/lib/api/providers/sync.dart | 25 ++++ refilc/lib/database/init.dart | 3 + refilc/lib/models/user.dart | 9 ++ refilc/pubspec.yaml | 2 +- refilc_kreta_api/lib/client/client.dart | 52 +++++-- .../lib/screens/settings/settings_screen.dart | 28 +++- .../settings/settings_screen.i18n.dart | 3 + .../settings/submenu/code_scanner.dart | 136 ++++++++++++++++++ .../settings/submenu/share_theme_popup.dart | 1 - refilc_mobile_ui/pubspec.yaml | 3 +- refilc_plus | 2 +- 12 files changed, 258 insertions(+), 22 deletions(-) create mode 100644 refilc_mobile_ui/lib/screens/settings/submenu/code_scanner.dart diff --git a/refilc/lib/api/login.dart b/refilc/lib/api/login.dart index 8f44bce..3895afc 100644 --- a/refilc/lib/api/login.dart +++ b/refilc/lib/api/login.dart @@ -68,6 +68,8 @@ Future loginAPI({ gradeDelay: 0, ), role: Role.parent, + accessToken: '', + accessTokenExpire: DateTime.now(), refreshToken: '', ); @@ -154,6 +156,8 @@ Future loginAPI({ name: student.name, student: student, role: JwtUtils.getRoleFromJWT(res["access_token"])!, + accessToken: res["access_token"], + accessTokenExpire: DateTime.now(), refreshToken: '', ); @@ -235,6 +239,15 @@ Future newLoginAPI({ if (res != null) { if (kDebugMode) { print(res); + + // const splitSize = 1000; + // RegExp exp = RegExp(r"\w{" "$splitSize" "}"); + // // String str = "0102031522"; + // Iterable matches = exp.allMatches(res.toString()); + // var list = matches.map((m) => m.group(0)); + // list.forEach((e) { + // print(e); + // }); } if (res.containsKey("error")) { @@ -267,6 +280,9 @@ Future newLoginAPI({ name: student.name, student: student, role: role, + accessToken: res["access_token"], + accessTokenExpire: + DateTime.now().add(Duration(seconds: (res["expires_in"] - 30))), refreshToken: res["refresh_token"], ); diff --git a/refilc/lib/api/providers/sync.dart b/refilc/lib/api/providers/sync.dart index 5e44466..cb8c474 100644 --- a/refilc/lib/api/providers/sync.dart +++ b/refilc/lib/api/providers/sync.dart @@ -40,6 +40,12 @@ Future syncAll(BuildContext context) { StatusProvider statusProvider = Provider.of(context, listen: false); + // check if access token isn't expired + // if (user.user?.accessToken == null) { + // lock = false; + // return Future.value(); + // } + List> tasks = []; int taski = 0; @@ -50,6 +56,25 @@ Future syncAll(BuildContext context) { } tasks = [ + // refresh login + syncStatus(() async { + print(user.user?.accessTokenExpire); + + if (user.user == null) return; + if (user.user!.accessTokenExpire.isBefore(DateTime.now())) { + String authRes = await Provider.of(context, listen: false) + .refreshLogin() ?? + ''; + if (authRes != 'success') { + print('ERROR: failed to refresh login'); + lock = false; + return Future.value(); + } + } else { + print('INFO: access token is not expired'); + } + }()), + syncStatus(Provider.of(context, listen: false).fetch()), syncStatus(Provider.of(context, listen: false) .fetch(week: Week.current())), diff --git a/refilc/lib/database/init.dart b/refilc/lib/database/init.dart index f6a744a..2850814 100644 --- a/refilc/lib/database/init.dart +++ b/refilc/lib/database/init.dart @@ -67,6 +67,7 @@ const usersDB = DatabaseStruct("users", { "institute_code": String, "student": String, "role": int, "nickname": String, "picture": String, // premium only (it's now plus btw) "grade_streak": int, + "access_token": String, "access_token_expire": String, "refresh_token": String, }); const userDataDB = DatabaseStruct("user_data", { @@ -141,6 +142,8 @@ Future initDB(DatabaseProvider database) async { "nickname": "", "picture": "", "grade_streak": 0, + "access_token": "", + "access_token_expire": "", "refresh_token": "", }, ); diff --git a/refilc/lib/models/user.dart b/refilc/lib/models/user.dart index 0f4ae6c..eab3624 100644 --- a/refilc/lib/models/user.dart +++ b/refilc/lib/models/user.dart @@ -18,6 +18,8 @@ class User { String picture; int gradeStreak; // new login method + String accessToken; + DateTime accessTokenExpire; String refreshToken; String get displayName => nickname != '' ? nickname : name; @@ -34,6 +36,8 @@ class User { this.nickname = "", this.picture = "", this.gradeStreak = 0, + required this.accessToken, + required this.accessTokenExpire, required this.refreshToken, }) { if (id != null) { @@ -65,6 +69,9 @@ class User { nickname: map["nickname"] ?? "", picture: map["picture"] ?? "", gradeStreak: map["grade_streak"] ?? 0, + accessToken: map["access_token"] ?? "", + accessTokenExpire: DateTime.parse( + map["access_token_expire"] ?? DateTime.now().toIso8601String()), refreshToken: map["refresh_token"] ?? "", ); } @@ -81,6 +88,8 @@ class User { "nickname": nickname, "picture": picture, "grade_streak": gradeStreak, + "access_token": accessToken, + "access_token_expire": accessTokenExpire.toIso8601String(), "refresh_token": refreshToken, }; } diff --git a/refilc/pubspec.yaml b/refilc/pubspec.yaml index 9f81ba2..fd4a2b7 100644 --- a/refilc/pubspec.yaml +++ b/refilc/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: # ref: master path_provider: ^2.0.2 permission_handler: ^11.0.1 - share_plus: ^9.0.0 + share_plus: ^10.0.3 connectivity_plus: ^6.0.3 flutter_displaymode: ^0.6.0 quick_actions: ^1.0.1 diff --git a/refilc_kreta_api/lib/client/client.dart b/refilc_kreta_api/lib/client/client.dart index 0f93cda..1cd5964 100644 --- a/refilc_kreta_api/lib/client/client.dart +++ b/refilc_kreta_api/lib/client/client.dart @@ -28,7 +28,7 @@ class KretaClient { late final DatabaseProvider _database; late final StatusProvider _status; - bool _loginRefreshing = false; + // bool _loginRefreshing = false; KretaClient({ this.accessToken, @@ -67,10 +67,14 @@ class KretaClient { headerMap = {}; } + if (accessToken == null || accessToken == '') { + accessToken = _user.user?.accessToken; + } + try { http.Response? res; - for (int i = 0; i < 3; i++) { + for (int i = 0; i < 2; i++) { if (autoHeader) { if (!headerMap.containsKey("authorization") && accessToken != null) { headerMap["authorization"] = "Bearer $accessToken"; @@ -86,13 +90,14 @@ class KretaClient { if (res.statusCode == 401) { headerMap.remove("authorization"); print("DEBUG: 401 error, refreshing login"); - await refreshLogin(); + print("DEBUG: 401 error, URL: $url"); + // await refreshLogin(); } else { break; } // Wait before retrying - await Future.delayed(const Duration(milliseconds: 500)); + await Future.delayed(const Duration(milliseconds: 1500)); } if (res == null) throw "Login error"; @@ -130,10 +135,14 @@ class KretaClient { headerMap = {}; } + if (accessToken == null || accessToken == '') { + accessToken = _user.user?.accessToken; + } + try { http.Response? res; - for (int i = 0; i < 3; i++) { + for (int i = 0; i < 2; i++) { if (autoHeader) { if (!headerMap.containsKey("authorization") && accessToken != null) { headerMap["authorization"] = "Bearer $accessToken"; @@ -151,11 +160,14 @@ class KretaClient { res = await client.post(Uri.parse(url), headers: headerMap, body: body); if (res.statusCode == 401) { - await refreshLogin(); + // await refreshLogin(); headerMap.remove("authorization"); } else { break; } + + // Wait before retrying + await Future.delayed(const Duration(milliseconds: 1500)); } if (res == null) throw "Login error"; @@ -188,6 +200,10 @@ class KretaClient { headerMap = {}; } + if (accessToken == null || accessToken == '') { + accessToken = _user.user?.accessToken; + } + try { http.StreamedResponse? res; @@ -218,7 +234,7 @@ class KretaClient { if (res.statusCode == 401) { headerMap.remove("authorization"); - await refreshLogin(); + // await refreshLogin(); } else { break; } @@ -238,8 +254,8 @@ class KretaClient { } Future refreshLogin() async { - if (_loginRefreshing) return null; - _loginRefreshing = true; + // if (_loginRefreshing) return null; + // _loginRefreshing = true; User? loginUser = _user.user; if (loginUser == null) return null; @@ -288,6 +304,11 @@ class KretaClient { if (res.containsKey("access_token")) { accessToken = res["access_token"]; + loginUser.accessToken = res["refresh_token"]; + loginUser.accessTokenExpire = + DateTime.now().add(Duration(seconds: (res["expires_in"] - 30))); + _database.store.storeUser(loginUser); + _user.refresh(); } if (res.containsKey("refresh_token")) { refreshToken = res["refresh_token"]; @@ -298,15 +319,20 @@ class KretaClient { if (res.containsKey("id_token")) { idToken = res["id_token"]; } - _loginRefreshing = false; + // _loginRefreshing = false; + print('successful refresh'); + + return 'success'; } else { - _loginRefreshing = false; + // _loginRefreshing = false; + return null; } } else { - _loginRefreshing = false; + // _loginRefreshing = false; + return null; } - return null; + // return null; } Future logout() async { diff --git a/refilc_mobile_ui/lib/screens/settings/settings_screen.dart b/refilc_mobile_ui/lib/screens/settings/settings_screen.dart index 8bf78c4..6868474 100644 --- a/refilc_mobile_ui/lib/screens/settings/settings_screen.dart +++ b/refilc_mobile_ui/lib/screens/settings/settings_screen.dart @@ -42,6 +42,7 @@ import 'package:refilc_mobile_ui/screens/settings/accounts/account_view.dart'; import 'package:refilc_mobile_ui/screens/settings/notifications_screen.dart'; import 'package:refilc_mobile_ui/screens/settings/privacy_view.dart'; import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart'; +import 'package:refilc_mobile_ui/screens/settings/submenu/code_scanner.dart'; import 'package:refilc_mobile_ui/screens/settings/submenu/extras_screen.dart'; import 'package:refilc_mobile_ui/screens/settings/submenu/personalize_screen.dart'; import 'package:flutter/foundation.dart'; @@ -1012,14 +1013,15 @@ class SettingsScreenState extends State children: [ PanelButton( leading: Icon( - FeatherIcons.map, + Icons.qr_code, size: 22.0, color: AppColors.of(context).text.withOpacity(0.95), ), - title: Text("stickermap".i18n), - onPressed: () => launchUrl( - Uri.parse("https://stickermap.refilc.hu"), - mode: LaunchMode.inAppBrowserView, + title: Text("qr_scanner".i18n), + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const CodeScannerScreen(), + ), ), borderRadius: const BorderRadius.vertical( top: Radius.circular(12.0), @@ -1034,6 +1036,22 @@ class SettingsScreenState extends State ), title: Text("news".i18n), onPressed: () => _openNews(context), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(4.0), + bottom: Radius.circular(4.0), + ), + ), + PanelButton( + leading: Icon( + FeatherIcons.map, + size: 22.0, + color: AppColors.of(context).text.withOpacity(0.95), + ), + title: Text("stickermap".i18n), + onPressed: () => launchUrl( + Uri.parse("https://stickermap.refilc.hu"), + mode: LaunchMode.inAppBrowserView, + ), borderRadius: const BorderRadius.vertical( top: Radius.circular(4.0), bottom: Radius.circular(12.0), diff --git a/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart b/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart index 2c4387c..26a9cf1 100644 --- a/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart +++ b/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart @@ -131,6 +131,7 @@ extension SettingsLocalization on String { "feedback": "Feedback", "other": "Other", "stickermap": "Sticker Map", + "qr_scanner": "QR Scanner", }, "hu_hu": { "heads_up": "Figyelem!", @@ -260,6 +261,7 @@ extension SettingsLocalization on String { "feedback": "Visszajelzés", "other": "Egyéb", "stickermap": "Matrica térkép", + "qr_scanner": "QR Kódolvasó", }, "de_de": { "heads_up": "Achtung!", @@ -389,6 +391,7 @@ extension SettingsLocalization on String { "feedback": "Feedback", "other": "Sonstiges", "stickermap": "Sticker Map", + "qr_scanner": "QR-Scanner", }, }; diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/code_scanner.dart b/refilc_mobile_ui/lib/screens/settings/submenu/code_scanner.dart new file mode 100644 index 0000000..5aec597 --- /dev/null +++ b/refilc_mobile_ui/lib/screens/settings/submenu/code_scanner.dart @@ -0,0 +1,136 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; +import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart'; + +class CodeScannerScreen extends StatefulWidget { + const CodeScannerScreen({super.key}); + + @override + State createState() => _CodeScannerScreenState(); +} + +class _CodeScannerScreenState extends State { + Barcode? result; + QRViewController? controller; + final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); + + // In order to get hot reload to work we need to pause the camera if the platform + // is android, or resume the camera if the platform is iOS. + @override + void reassemble() { + super.reassemble(); + if (Platform.isAndroid) { + controller!.pauseCamera(); + } + controller!.resumeCamera(); + } + + // @override + // void initState() { + // super.initState(); + + // controller!.resumeCamera(); + // } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + title: Text('qr_scanner'.i18n), + leading: const BackButton(), + actions: [ + IconButton( + icon: FutureBuilder( + future: controller?.getFlashStatus(), + builder: (context, snapshot) { + return Icon( + snapshot.data == true + ? FeatherIcons.zapOff + : FeatherIcons.zap, + ); + }, + ), + onPressed: () async { + await controller?.toggleFlash(); + setState(() {}); + }, + ), + ], + ), + body: _buildQrView(context), + // body: Column( + // children: [ + // Expanded(flex: 4, child: _buildQrView(context)), + // // Expanded( + // // flex: 1, + // // child: FittedBox( + // // fit: BoxFit.contain, + // // child: Column( + // // mainAxisAlignment: MainAxisAlignment.spaceEvenly, + // // children: [ + // // if (result != null) + // // Text( + // // 'Barcode Type: ${describeEnum(result!.format)} Data: ${result!.code}') + // // else + // // const Text('Scan a code'), + // // ], + // // ), + // // ), + // // ) + // ], + // ), + ); + } + + Widget _buildQrView(BuildContext context) { + // For this example we check how width or tall the device is and change the scanArea and overlay accordingly. + var scanArea = (MediaQuery.of(context).size.width < 400 || + MediaQuery.of(context).size.height < 400) + ? 150.0 + : 280.0; + // To ensure the Scanner view is properly sizes after rotation + // we need to listen for Flutter SizeChanged notification and update controller + return QRView( + key: qrKey, + onQRViewCreated: _onQRViewCreated, + overlay: QrScannerOverlayShape( + borderColor: Theme.of(context).primaryColor, + borderRadius: 10, + borderLength: 30, + borderWidth: 10, + cutOutSize: scanArea, + ), + onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p), + ); + } + + void _onQRViewCreated(QRViewController controller) { + setState(() { + this.controller = controller; + }); + controller.scannedDataStream.listen((scanData) { + setState(() { + result = scanData; + }); + }); + } + + void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) { + // log('${DateTime.now().toIso8601String()}_onPermissionSet $p'); + if (!p) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('no Permission')), + ); + } + } + + @override + void dispose() { + controller?.dispose(); + super.dispose(); + } +} diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart b/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart index 9c6b50b..d6d8572 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:provider/provider.dart'; // import 'package:refilc/models/settings.dart'; -import 'package:refilc/models/shared_theme.dart'; import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc_kreta_api/providers/share_provider.dart'; import 'package:refilc_mobile_ui/common/action_button.dart'; diff --git a/refilc_mobile_ui/pubspec.yaml b/refilc_mobile_ui/pubspec.yaml index 53f2629..7c050a9 100644 --- a/refilc_mobile_ui/pubspec.yaml +++ b/refilc_mobile_ui/pubspec.yaml @@ -58,7 +58,7 @@ dependencies: auto_size_text: ^3.0.0 connectivity_plus: ^6.0.3 collection: ^1.18.0 - share_plus: ^9.0.0 + share_plus: ^10.0.3 image_picker: ^1.0.7 path_provider: ^2.1.2 image_crop: @@ -77,6 +77,7 @@ dependencies: webview_flutter: ^4.8.0 file_picker: ^8.0.5 shake_flutter: ^17.0.0 + qr_code_scanner_plus: ^2.0.6 dev_dependencies: flutter_lints: ^4.0.0 diff --git a/refilc_plus b/refilc_plus index 26cd3fc..6abc4ed 160000 --- a/refilc_plus +++ b/refilc_plus @@ -1 +1 @@ -Subproject commit 26cd3fc163d72ddb849edfeb7fdb7b64c7df44bc +Subproject commit 6abc4edf70deeaffea8b8a7dd95acebecc5a520b From 38d9b5f3b2c7afbb35752a6501b0a4200260b9ce Mon Sep 17 00:00:00 2001 From: Kima Date: Wed, 13 Nov 2024 19:10:48 +0100 Subject: [PATCH 18/22] fixed login finally --- refilc/lib/api/providers/sync.dart | 10 +++++++--- refilc_kreta_api/lib/client/client.dart | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/refilc/lib/api/providers/sync.dart b/refilc/lib/api/providers/sync.dart index cb8c474..81fca47 100644 --- a/refilc/lib/api/providers/sync.dart +++ b/refilc/lib/api/providers/sync.dart @@ -2,6 +2,7 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/status_provider.dart'; import 'package:refilc/api/providers/user_provider.dart'; @@ -58,7 +59,8 @@ Future syncAll(BuildContext context) { tasks = [ // refresh login syncStatus(() async { - print(user.user?.accessTokenExpire); + // print(user.user?.accessTokenExpire); + // print('${user.user?.accessToken ?? "no token"} - ACCESS TOKEN'); if (user.user == null) return; if (user.user!.accessTokenExpire.isBefore(DateTime.now())) { @@ -66,12 +68,14 @@ Future syncAll(BuildContext context) { .refreshLogin() ?? ''; if (authRes != 'success') { - print('ERROR: failed to refresh login'); + if (kDebugMode) print('ERROR: failed to refresh login'); lock = false; return Future.value(); + } else { + if (kDebugMode) print('INFO: access token refreshed'); } } else { - print('INFO: access token is not expired'); + if (kDebugMode) print('INFO: access token is not expired'); } }()), diff --git a/refilc_kreta_api/lib/client/client.dart b/refilc_kreta_api/lib/client/client.dart index 1cd5964..e383ac1 100644 --- a/refilc_kreta_api/lib/client/client.dart +++ b/refilc_kreta_api/lib/client/client.dart @@ -304,7 +304,7 @@ class KretaClient { if (res.containsKey("access_token")) { accessToken = res["access_token"]; - loginUser.accessToken = res["refresh_token"]; + loginUser.accessToken = res["access_token"]; loginUser.accessTokenExpire = DateTime.now().add(Duration(seconds: (res["expires_in"] - 30))); _database.store.storeUser(loginUser); From d391448870b21fa5a315a135ee701f7923397b26 Mon Sep 17 00:00:00 2001 From: Kima Date: Wed, 13 Nov 2024 19:11:08 +0100 Subject: [PATCH 19/22] remove prints --- .../lib/screens/settings/submenu/share_theme_popup.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart b/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart index d6d8572..ac34212 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/share_theme_popup.dart @@ -182,8 +182,8 @@ class ShareThemeDialogState extends State { return; } - print(theme); - print(themeStatus); + // print(theme); + // print(themeStatus); // save theme id in settings // Provider.of(context, listen: false) From 986b13de68bd262e7c566556701f1497615dab99 Mon Sep 17 00:00:00 2001 From: Kima Date: Wed, 13 Nov 2024 19:12:09 +0100 Subject: [PATCH 20/22] changed version number --- refilc/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/refilc/pubspec.yaml b/refilc/pubspec.yaml index fd4a2b7..9482d1f 100644 --- a/refilc/pubspec.yaml +++ b/refilc/pubspec.yaml @@ -3,7 +3,7 @@ description: "Egy nem hivatalos e-KRÉTA kliens, diákoktól diákoknak." homepage: https://refilc.hu publish_to: "none" -version: 5.0.5+275 +version: 5.0.6+276 environment: sdk: ">=3.3.2 <=3.4.3" From 3708b917c4febbf6161acb5dd97a7544e9127078 Mon Sep 17 00:00:00 2001 From: Kima Date: Wed, 13 Nov 2024 20:25:13 +0100 Subject: [PATCH 21/22] finished qr scanner (test) --- refilc/lib/api/providers/sync.dart | 4 +- .../settings/settings_screen.i18n.dart | 9 ++++ .../settings/submenu/code_scanner.dart | 42 +++++++++++++++++-- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/refilc/lib/api/providers/sync.dart b/refilc/lib/api/providers/sync.dart index 81fca47..94b28d4 100644 --- a/refilc/lib/api/providers/sync.dart +++ b/refilc/lib/api/providers/sync.dart @@ -59,8 +59,8 @@ Future syncAll(BuildContext context) { tasks = [ // refresh login syncStatus(() async { - // print(user.user?.accessTokenExpire); - // print('${user.user?.accessToken ?? "no token"} - ACCESS TOKEN'); + print(user.user?.accessTokenExpire); + print('${user.user?.accessToken ?? "no token"} - ACCESS TOKEN'); if (user.user == null) return; if (user.user!.accessTokenExpire.isBefore(DateTime.now())) { diff --git a/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart b/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart index 26a9cf1..a2a5e8f 100644 --- a/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart +++ b/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart @@ -132,6 +132,9 @@ extension SettingsLocalization on String { "other": "Other", "stickermap": "Sticker Map", "qr_scanner": "QR Scanner", + "camera_perm_error": + "Camera permission is required to scan QR codes.", + "invalid_qr_code": "Invalid QR code!", }, "hu_hu": { "heads_up": "Figyelem!", @@ -262,6 +265,9 @@ extension SettingsLocalization on String { "other": "Egyéb", "stickermap": "Matrica térkép", "qr_scanner": "QR Kódolvasó", + "camera_perm_error": + "A kamera engedély szükséges a QR kódok beolvasásához.", + "invalid_qr_code": "Érvénytelen QR kód!", }, "de_de": { "heads_up": "Achtung!", @@ -392,6 +398,9 @@ extension SettingsLocalization on String { "other": "Sonstiges", "stickermap": "Sticker Map", "qr_scanner": "QR-Scanner", + "camera_perm_error": + "Kameraberechtigung ist erforderlich, um QR-Codes zu scannen.", + "invalid_qr_code": "Ungültiger QR-Code!", }, }; diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/code_scanner.dart b/refilc_mobile_ui/lib/screens/settings/submenu/code_scanner.dart index 5aec597..132f9d1 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/code_scanner.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/code_scanner.dart @@ -3,7 +3,10 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; +import 'package:refilc/theme/colors/colors.dart'; +import 'package:refilc_mobile_ui/common/custom_snack_bar.dart'; import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart'; +import 'package:url_launcher/url_launcher.dart'; class CodeScannerScreen extends StatefulWidget { const CodeScannerScreen({super.key}); @@ -113,18 +116,51 @@ class _CodeScannerScreenState extends State { this.controller = controller; }); controller.scannedDataStream.listen((scanData) { + controller.pauseCamera(); + setState(() { result = scanData; }); + + Navigator.of(context).pop(); + + if (scanData.code != null) { + if (scanData.code!.startsWith('qw://')) { + // String data = scanData.code!.replaceFirst('qw://', ''); + // check the qr id from api + // TODO: this qr shit + } else if (scanData.code!.startsWith('https://') || + scanData.code!.startsWith('http://')) { + Uri uri = Uri.parse(scanData.code!.replaceFirst('http', 'https')); + + if (uri.host.contains('refilc.hu') || + uri.host.contains('refilcapp.hu') || + uri.host.contains('filc.one')) { + launchUrl(uri, mode: LaunchMode.inAppBrowserView); + } else { + ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( + content: Text("invalid_qr_code".i18n, + style: const TextStyle(color: Colors.white)), + backgroundColor: AppColors.of(context).red, + context: context, + )); + } + } + // log(scanData.code); + // Provider.of(context, listen: false).syncAll(context); + } }); } void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) { // log('${DateTime.now().toIso8601String()}_onPermissionSet $p'); if (!p) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('no Permission')), - ); + ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( + content: Text("camera_perm_error".i18n, + style: const TextStyle(color: Colors.white)), + backgroundColor: AppColors.of(context).red, + context: context, + )); } } From 414755c777effd28e312d3e63c958c19aa1d8156 Mon Sep 17 00:00:00 2001 From: Kima Date: Wed, 13 Nov 2024 20:51:24 +0100 Subject: [PATCH 22/22] okay that's it bye --- refilc/lib/api/providers/sync.dart | 4 +- .../settings/settings_screen.i18n.dart | 3 + .../settings/submenu/code_scanner.dart | 68 +++++++++---------- 3 files changed, 39 insertions(+), 36 deletions(-) diff --git a/refilc/lib/api/providers/sync.dart b/refilc/lib/api/providers/sync.dart index 94b28d4..81fca47 100644 --- a/refilc/lib/api/providers/sync.dart +++ b/refilc/lib/api/providers/sync.dart @@ -59,8 +59,8 @@ Future syncAll(BuildContext context) { tasks = [ // refresh login syncStatus(() async { - print(user.user?.accessTokenExpire); - print('${user.user?.accessToken ?? "no token"} - ACCESS TOKEN'); + // print(user.user?.accessTokenExpire); + // print('${user.user?.accessToken ?? "no token"} - ACCESS TOKEN'); if (user.user == null) return; if (user.user!.accessTokenExpire.isBefore(DateTime.now())) { diff --git a/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart b/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart index a2a5e8f..928d576 100644 --- a/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart +++ b/refilc_mobile_ui/lib/screens/settings/settings_screen.i18n.dart @@ -135,6 +135,7 @@ extension SettingsLocalization on String { "camera_perm_error": "Camera permission is required to scan QR codes.", "invalid_qr_code": "Invalid QR code!", + "success": "Success!", }, "hu_hu": { "heads_up": "Figyelem!", @@ -268,6 +269,7 @@ extension SettingsLocalization on String { "camera_perm_error": "A kamera engedély szükséges a QR kódok beolvasásához.", "invalid_qr_code": "Érvénytelen QR kód!", + "success": "Siker!", }, "de_de": { "heads_up": "Achtung!", @@ -401,6 +403,7 @@ extension SettingsLocalization on String { "camera_perm_error": "Kameraberechtigung ist erforderlich, um QR-Codes zu scannen.", "invalid_qr_code": "Ungültiger QR-Code!", + "success": "Erfolg!", }, }; diff --git a/refilc_mobile_ui/lib/screens/settings/submenu/code_scanner.dart b/refilc_mobile_ui/lib/screens/settings/submenu/code_scanner.dart index 132f9d1..ebae250 100644 --- a/refilc_mobile_ui/lib/screens/settings/submenu/code_scanner.dart +++ b/refilc_mobile_ui/lib/screens/settings/submenu/code_scanner.dart @@ -20,8 +20,6 @@ class _CodeScannerScreenState extends State { QRViewController? controller; final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); - // In order to get hot reload to work we need to pause the camera if the platform - // is android, or resume the camera if the platform is iOS. @override void reassemble() { super.reassemble(); @@ -65,38 +63,15 @@ class _CodeScannerScreenState extends State { ], ), body: _buildQrView(context), - // body: Column( - // children: [ - // Expanded(flex: 4, child: _buildQrView(context)), - // // Expanded( - // // flex: 1, - // // child: FittedBox( - // // fit: BoxFit.contain, - // // child: Column( - // // mainAxisAlignment: MainAxisAlignment.spaceEvenly, - // // children: [ - // // if (result != null) - // // Text( - // // 'Barcode Type: ${describeEnum(result!.format)} Data: ${result!.code}') - // // else - // // const Text('Scan a code'), - // // ], - // // ), - // // ), - // // ) - // ], - // ), ); } Widget _buildQrView(BuildContext context) { - // For this example we check how width or tall the device is and change the scanArea and overlay accordingly. var scanArea = (MediaQuery.of(context).size.width < 400 || MediaQuery.of(context).size.height < 400) ? 150.0 : 280.0; - // To ensure the Scanner view is properly sizes after rotation - // we need to listen for Flutter SizeChanged notification and update controller + return QRView( key: qrKey, onQRViewCreated: _onQRViewCreated, @@ -116,14 +91,13 @@ class _CodeScannerScreenState extends State { this.controller = controller; }); controller.scannedDataStream.listen((scanData) { - controller.pauseCamera(); + // controller.pauseCamera(); + if (result?.code == scanData.code) return; setState(() { result = scanData; }); - Navigator.of(context).pop(); - if (scanData.code != null) { if (scanData.code!.startsWith('qw://')) { // String data = scanData.code!.replaceFirst('qw://', ''); @@ -131,29 +105,55 @@ class _CodeScannerScreenState extends State { // TODO: this qr shit } else if (scanData.code!.startsWith('https://') || scanData.code!.startsWith('http://')) { - Uri uri = Uri.parse(scanData.code!.replaceFirst('http', 'https')); + Uri uri = + Uri.parse(scanData.code!.replaceFirst('http://', 'https://')); + + // print(uri); if (uri.host.contains('refilc.hu') || uri.host.contains('refilcapp.hu') || uri.host.contains('filc.one')) { - launchUrl(uri, mode: LaunchMode.inAppBrowserView); + ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( + content: Text("success".i18n, + style: const TextStyle(color: Colors.white)), + backgroundColor: const Color(0xFF00A900), + context: context, + )); + + // launch refilc url + Future.delayed(const Duration(seconds: 1), () { + Navigator.of(context).pop(); + launchUrl(uri, mode: LaunchMode.inAppBrowserView); + }); } else { + // show invalid code error + // Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( content: Text("invalid_qr_code".i18n, style: const TextStyle(color: Colors.white)), backgroundColor: AppColors.of(context).red, context: context, )); + + controller.resumeCamera(); } + } else { + // show invalid code error + // Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( + content: Text("invalid_qr_code".i18n, + style: const TextStyle(color: Colors.white)), + backgroundColor: AppColors.of(context).red, + context: context, + )); + + controller.resumeCamera(); } - // log(scanData.code); - // Provider.of(context, listen: false).syncAll(context); } }); } void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) { - // log('${DateTime.now().toIso8601String()}_onPermissionSet $p'); if (!p) { ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( content: Text("camera_perm_error".i18n,