diff --git a/filcnaplo/ios/Flutter/Generated 2.xcconfig b/filcnaplo/ios/Flutter/Generated 2.xcconfig new file mode 100644 index 0000000..194436b --- /dev/null +++ b/filcnaplo/ios/Flutter/Generated 2.xcconfig @@ -0,0 +1,14 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=/Users/kima/src/flutter +FLUTTER_APPLICATION_PATH=/Users/kima/Documents/refilc/app/naplo/filcnaplo +COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_TARGET=lib/main.dart +FLUTTER_BUILD_DIR=build +FLUTTER_BUILD_NAME=4.2.0 +FLUTTER_BUILD_NUMBER=220 +EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386 +EXCLUDED_ARCHS[sdk=iphoneos*]=armv7 +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=true +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=.dart_tool/package_config.json diff --git a/filcnaplo/ios/Flutter/flutter_export_environment 3.sh b/filcnaplo/ios/Flutter/flutter_export_environment 3.sh new file mode 100755 index 0000000..276363d --- /dev/null +++ b/filcnaplo/ios/Flutter/flutter_export_environment 3.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=/Users/kima/src/flutter" +export "FLUTTER_APPLICATION_PATH=/Users/kima/Documents/refilc/app/naplo/filcnaplo" +export "COCOAPODS_PARALLEL_CODE_SIGN=true" +export "FLUTTER_TARGET=lib/main.dart" +export "FLUTTER_BUILD_DIR=build" +export "FLUTTER_BUILD_NAME=4.2.0" +export "FLUTTER_BUILD_NUMBER=220" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=true" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.dart_tool/package_config.json" diff --git a/filcnaplo/lib/api/client.dart b/filcnaplo/lib/api/client.dart index a098548..66ee094 100644 --- a/filcnaplo/lib/api/client.dart +++ b/filcnaplo/lib/api/client.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:developer'; +import 'package:filcnaplo/models/ad.dart'; import 'package:filcnaplo/models/config.dart'; import 'package:filcnaplo/models/news.dart'; import 'package:filcnaplo/models/release.dart'; @@ -19,6 +20,7 @@ class FilcAPI { // Private API static const config = "https://api.refilc.hu/v1/private/config"; + static const ads = "https://api.refilc.hu/v1/private/ads"; static const reportApi = "https://api.refilc.hu/v1/private/crash-report"; static const premiumApi = "https://api.filcnaplo.hu/premium/activate"; // static const premiumScopesApi = "https://api.filcnaplo.hu/premium/scopes"; @@ -117,6 +119,24 @@ class FilcAPI { return null; } + static Future?> getAds() async { + try { + http.Response res = await http.get(Uri.parse(ads)); + + if (res.statusCode == 200) { + return (jsonDecode(res.body) as List) + .cast() + .map((e) => Ad.fromJson(e)) + .toList(); + } else { + throw "HTTP ${res.statusCode}: ${res.body}"; + } + } on Exception catch (error, stacktrace) { + log("ERROR: FilcAPI.getAds: $error $stacktrace"); + } + return null; + } + static Future?> getReleases() async { try { http.Response res = await http.get(Uri.parse(releases)); diff --git a/filcnaplo/lib/api/providers/ad_provider.dart b/filcnaplo/lib/api/providers/ad_provider.dart new file mode 100644 index 0000000..a479935 --- /dev/null +++ b/filcnaplo/lib/api/providers/ad_provider.dart @@ -0,0 +1,29 @@ +import 'package:filcnaplo/api/client.dart'; +import 'package:filcnaplo/models/ad.dart'; +import 'package:flutter/material.dart'; + +class AdProvider extends ChangeNotifier { + // private + late List _ads; + bool get available => _ads.isNotEmpty; + + // public + List get ads => _ads; + + AdProvider({ + List initialAds = const [], + required BuildContext context, + }) { + _ads = List.castFrom(initialAds); + } + + Future fetch() async { + _ads = await FilcAPI.getAds() ?? []; + _ads.sort((a, b) => -a.date.compareTo(b.date)); + + // check for new ads + if (_ads.isNotEmpty) { + notifyListeners(); + } + } +} diff --git a/filcnaplo/lib/models/ad.dart b/filcnaplo/lib/models/ad.dart new file mode 100644 index 0000000..48ea5bf --- /dev/null +++ b/filcnaplo/lib/models/ad.dart @@ -0,0 +1,29 @@ +class Ad { + String title; + String description; + String author; + Uri? logoUrl; + bool overridePremium; + DateTime date; + + Ad({ + required this.title, + required this.description, + required this.author, + required this.logoUrl, + this.overridePremium = false, + required this.date, + }); + + factory Ad.fromJson(Map json) { + return Ad( + title: json['title'] ?? 'Ad', + description: json['description'] ?? '', + author: json['author'] ?? 'reFilc', + logoUrl: json['logo_url'] != null ? Uri.parse(json['logo_url']) : null, + overridePremium: json['override_premium'] ?? false, + date: + json['date'] != null ? DateTime.parse(json['date']) : DateTime.now(), + ); + } +} diff --git a/filcnaplo/lib/ui/filter/widgets.dart b/filcnaplo/lib/ui/filter/widgets.dart index 54589d7..ec354a4 100644 --- a/filcnaplo/lib/ui/filter/widgets.dart +++ b/filcnaplo/lib/ui/filter/widgets.dart @@ -1,3 +1,4 @@ +import 'package:filcnaplo/api/providers/ad_provider.dart'; import 'package:filcnaplo/api/providers/update_provider.dart'; import 'package:filcnaplo/models/settings.dart'; import 'package:filcnaplo/ui/date_widget.dart'; @@ -14,6 +15,7 @@ import 'package:filcnaplo/ui/filter/widgets/lessons.dart' as lesson_filter; import 'package:filcnaplo/ui/filter/widgets/update.dart' as update_filter; import 'package:filcnaplo/ui/filter/widgets/missed_exams.dart' as missed_exam_filter; +import 'package:filcnaplo/ui/filter/widgets/ads.dart' as ad_filter; import 'package:filcnaplo_kreta_api/models/week.dart'; import 'package:filcnaplo_kreta_api/providers/absence_provider.dart'; import 'package:filcnaplo_kreta_api/providers/event_provider.dart'; @@ -50,7 +52,8 @@ enum FilterType { lessons, updates, certifications, - missedExams + missedExams, + ads, } Future> getFilterWidgets(FilterType activeData, @@ -65,6 +68,7 @@ Future> getFilterWidgets(FilterType activeData, final eventProvider = Provider.of(context); final updateProvider = Provider.of(context); final settingsProvider = Provider.of(context); + final adProvider = Provider.of(context); List items = []; @@ -82,6 +86,7 @@ Future> getFilterWidgets(FilterType activeData, getFilterWidgets(FilterType.updates, context: context), getFilterWidgets(FilterType.certifications, context: context), getFilterWidgets(FilterType.missedExams, context: context), + getFilterWidgets(FilterType.ads, context: context), ]); items = all.expand((x) => x).toList(); @@ -89,9 +94,9 @@ Future> getFilterWidgets(FilterType activeData, // Grades case FilterType.grades: - if(!settingsProvider.gradeOpeningFun) { - gradeProvider.seenAll(); - } + if (!settingsProvider.gradeOpeningFun) { + gradeProvider.seenAll(); + } items = grade_filter.getWidgets( gradeProvider.grades, gradeProvider.lastSeenDate); if (settingsProvider.gradeOpeningFun) { @@ -164,6 +169,13 @@ Future> getFilterWidgets(FilterType activeData, items = missed_exam_filter .getWidgets(timetableProvider.getWeek(Week.current()) ?? []); break; + + // Ads + case FilterType.ads: + if (adProvider.available) { + items = ad_filter.getWidgets(adProvider.ads); + } + break; } return items; } diff --git a/filcnaplo/lib/ui/filter/widgets/ads.dart b/filcnaplo/lib/ui/filter/widgets/ads.dart new file mode 100644 index 0000000..2cd2c7a --- /dev/null +++ b/filcnaplo/lib/ui/filter/widgets/ads.dart @@ -0,0 +1,24 @@ +import 'package:filcnaplo/models/ad.dart'; +import 'package:filcnaplo/ui/date_widget.dart'; +import 'package:filcnaplo_mobile_ui/common/widgets/ad/ad_viewable.dart' + as mobile; + +List getWidgets(List providerAds) { + List items = []; + + if (providerAds.isNotEmpty) { + for (var ad in providerAds) { + if (ad.date.isAfter(DateTime.now())) { + providerAds.sort((a, b) => -a.date.compareTo(b.date)); + + items.add(DateWidget( + key: ad.description, + date: ad.date, + widget: mobile.AdViewable(ad), + )); + } + } + } + + return items; +} diff --git a/filcnaplo_kreta_api/lib/client/api.dart b/filcnaplo_kreta_api/lib/client/api.dart index 4060961..f3c18d0 100644 --- a/filcnaplo_kreta_api/lib/client/api.dart +++ b/filcnaplo_kreta_api/lib/client/api.dart @@ -24,7 +24,7 @@ class KretaAPI { KretaApiEndpoints.groupAverages + "?oktatasiNevelesiFeladatUid=" + uid; - static String averages(String iss, String uid) => + static String averages(String iss, String uid) => BaseKreta.kreta(iss) + KretaApiEndpoints.averages + "?oktatasiNevelesiFeladatUid=" + @@ -95,8 +95,7 @@ class KretaApiEndpoints { static const groups = "/ellenorzo/V3/Sajat/OsztalyCsoportok"; static const groupAverages = "/ellenorzo/V3/Sajat/Ertekelesek/Atlagok/OsztalyAtlagok"; - static const averages = - "/ellenorzo/V3/Sajat/Ertekelesek/Atlagok/TantargyiAtlagok"; + static const averages = "/ellenorzo/V3/idk"; static const timetable = "/ellenorzo/V3/Sajat/OrarendElemek"; static const exams = "/ellenorzo/V3/Sajat/BejelentettSzamonkeresek"; static const homework = "/ellenorzo/V3/Sajat/HaziFeladatok"; diff --git a/filcnaplo_mobile_ui/lib/common/widgets/ad/ad_tile.dart b/filcnaplo_mobile_ui/lib/common/widgets/ad/ad_tile.dart new file mode 100644 index 0000000..90b9efd --- /dev/null +++ b/filcnaplo_mobile_ui/lib/common/widgets/ad/ad_tile.dart @@ -0,0 +1,30 @@ +import 'package:filcnaplo/models/ad.dart'; +import 'package:flutter/material.dart'; +import 'package:filcnaplo_mobile_ui/common/panel/panel_button.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; + +class AdTile extends StatelessWidget { + const AdTile(this.ad, {Key? key, this.onTap, this.padding}) : super(key: key); + + final Ad ad; + final Function()? onTap; + final EdgeInsetsGeometry? padding; + + @override + Widget build(BuildContext context) { + return Padding( + padding: padding ?? const EdgeInsets.symmetric(horizontal: 8.0), + child: PanelButton( + onPressed: onTap, + title: Column( + children: [ + Text(ad.title), + Text(ad.description), + ], + ), + leading: Image.network(ad.logoUrl.toString()), + trailing: const Icon(FeatherIcons.externalLink), + ), + ); + } +} diff --git a/filcnaplo_mobile_ui/lib/common/widgets/ad/ad_viewable.dart b/filcnaplo_mobile_ui/lib/common/widgets/ad/ad_viewable.dart new file mode 100644 index 0000000..9676728 --- /dev/null +++ b/filcnaplo_mobile_ui/lib/common/widgets/ad/ad_viewable.dart @@ -0,0 +1,18 @@ +import 'package:filcnaplo/models/ad.dart'; +import 'package:flutter/material.dart'; + +import 'ad_tile.dart'; + +class AdViewable extends StatelessWidget { + const AdViewable(this.ad, {Key? key}) : super(key: key); + + final Ad ad; + + @override + Widget build(BuildContext context) { + return AdTile( + ad, + onTap: () => [], + ); + } +} diff --git a/filcnaplo_mobile_ui/lib/pages/grades/grade_subject_view.dart b/filcnaplo_mobile_ui/lib/pages/grades/grade_subject_view.dart index 0c0c152..21949b2 100755 --- a/filcnaplo_mobile_ui/lib/pages/grades/grade_subject_view.dart +++ b/filcnaplo_mobile_ui/lib/pages/grades/grade_subject_view.dart @@ -5,6 +5,8 @@ import 'package:filcnaplo/api/providers/database_provider.dart'; import 'package:filcnaplo/api/providers/user_provider.dart'; import 'package:filcnaplo/models/settings.dart'; import 'package:filcnaplo/utils/format.dart'; +// import 'package:filcnaplo_kreta_api/client/api.dart'; +// import 'package:filcnaplo_kreta_api/client/client.dart'; import 'package:filcnaplo_kreta_api/providers/grade_provider.dart'; import 'package:filcnaplo/helpers/average_helper.dart'; import 'package:filcnaplo/helpers/subject.dart'; diff --git a/filcnaplo_mobile_ui/lib/pages/grades/grades_page.dart b/filcnaplo_mobile_ui/lib/pages/grades/grades_page.dart index 2261899..8f0f6ca 100755 --- a/filcnaplo_mobile_ui/lib/pages/grades/grades_page.dart +++ b/filcnaplo_mobile_ui/lib/pages/grades/grades_page.dart @@ -2,6 +2,8 @@ import 'dart:math'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:filcnaplo/api/providers/update_provider.dart'; +// import 'package:filcnaplo_kreta_api/client/api.dart'; +// import 'package:filcnaplo_kreta_api/client/client.dart'; import 'package:filcnaplo_kreta_api/providers/grade_provider.dart'; import 'package:filcnaplo/api/providers/user_provider.dart'; import 'package:filcnaplo/theme/colors/colors.dart'; @@ -112,6 +114,18 @@ class _GradesPageState extends State { } else { tiles.insert( 0, + // TextButton( + // onPressed: () async { + // var url = KretaAPI.averages( + // user.instituteCode!, + // user.id!, + // ); + // print(url); + // var res = await Provider.of(context, listen: false) + // .getAPI(url); + // print(res); + // }, + // child: Text('test')), Padding( padding: const EdgeInsets.only(top: 24.0), child: Empty(subtitle: "empty".i18n), diff --git a/filcnaplo_mobile_ui/lib/screens/navigation/navigation_screen.dart b/filcnaplo_mobile_ui/lib/screens/navigation/navigation_screen.dart index 4c2ca14..f5017de 100755 --- a/filcnaplo_mobile_ui/lib/screens/navigation/navigation_screen.dart +++ b/filcnaplo_mobile_ui/lib/screens/navigation/navigation_screen.dart @@ -26,6 +26,7 @@ import 'package:home_widget/home_widget.dart'; import 'package:wtf_sliding_sheet/wtf_sliding_sheet.dart'; import 'package:background_fetch/background_fetch.dart'; import 'package:filcnaplo_premium/providers/goal_provider.dart'; +import 'package:filcnaplo/api/providers/ad_provider.dart'; class NavigationScreen extends StatefulWidget { const NavigationScreen({Key? key}) : super(key: key); @@ -48,6 +49,7 @@ class NavigationScreenState extends State late GoalProvider goalProvider; late UpdateProvider updateProvider; late GradeProvider gradeProvicer; + late AdProvider adProvider; NavigatorState? get navigator => _navigatorState.currentState; @@ -176,6 +178,10 @@ class NavigationScreenState extends State updateProvider = Provider.of(context, listen: false); updateProvider.fetch(); + // get advertisements + adProvider = Provider.of(context, listen: false); + adProvider.fetch(); + // initial sync syncAll(context); setupQuickActions();