diff --git a/.gitignore b/.gitignore index 4b56298..b97fcfb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # See https://www.dartlang.org/guides/libraries/private-files termek.txt +.DS_Store # Files and directories created by pub .dart_tool/ diff --git a/filcnaplo/android/app/src/main/AndroidManifest.xml b/filcnaplo/android/app/src/main/AndroidManifest.xml index ed9a9b7..ad93e0a 100644 --- a/filcnaplo/android/app/src/main/AndroidManifest.xml +++ b/filcnaplo/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,16 @@ + + + + + + + diff --git a/filcnaplo/assets/fonts/FilcIcons.ttf b/filcnaplo/assets/fonts/FilcIcons.ttf index 9a3f33a..77d2759 100644 Binary files a/filcnaplo/assets/fonts/FilcIcons.ttf and b/filcnaplo/assets/fonts/FilcIcons.ttf differ diff --git a/filcnaplo/ios/Runner/Runner.entitlements b/filcnaplo/ios/Runner/Runner.entitlements index 903def2..da95ee4 100644 --- a/filcnaplo/ios/Runner/Runner.entitlements +++ b/filcnaplo/ios/Runner/Runner.entitlements @@ -4,5 +4,9 @@ aps-environment development + com.apple.developer.associated-domains + + applinks:api.filcnaplo.hu + diff --git a/filcnaplo/lib/api/client.dart b/filcnaplo/lib/api/client.dart index dcab1eb..b4f9fbb 100644 --- a/filcnaplo/lib/api/client.dart +++ b/filcnaplo/lib/api/client.dart @@ -21,6 +21,8 @@ class FilcAPI { // Private API static const config = "https://api.filcnaplo.hu/config"; static const reportApi = "https://api.filcnaplo.hu/report"; + static const premiumApi = "https://api.filcnaplo.hu/premium/activate"; + static const premiumScopesApi = "https://api.filcnaplo.hu/premium/scopes"; // Updates static const repo = "filc/naplo"; diff --git a/filcnaplo/lib/api/providers/news_provider.dart b/filcnaplo/lib/api/providers/news_provider.dart index e0636b6..e1eba3b 100644 --- a/filcnaplo/lib/api/providers/news_provider.dart +++ b/filcnaplo/lib/api/providers/news_provider.dart @@ -41,7 +41,7 @@ class NewsProvider extends ChangeNotifier { } _state = state_; - Provider.of(_context, listen: false).update(_context, newsState: _state); + Provider.of(_context, listen: false).update(newsState: _state); } Future fetch() async { @@ -53,7 +53,7 @@ class NewsProvider extends ChangeNotifier { if (_fresh < 0) { _state = news_.length; - Provider.of(_context, listen: false).update(_context, newsState: _state); + Provider.of(_context, listen: false).update(newsState: _state); } _fresh = max(_fresh, 0); @@ -72,7 +72,7 @@ class NewsProvider extends ChangeNotifier { _fresh--; _state++; - Provider.of(_context, listen: false).update(_context, newsState: _state); + Provider.of(_context, listen: false).update(newsState: _state); if (_fresh > 0) { show = true; diff --git a/filcnaplo/lib/app.dart b/filcnaplo/lib/app.dart index 3a9e2a3..91a823f 100644 --- a/filcnaplo/lib/app.dart +++ b/filcnaplo/lib/app.dart @@ -45,6 +45,7 @@ import 'package:filcnaplo/api/providers/user_provider.dart'; import 'package:filcnaplo/api/providers/update_provider.dart'; import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_provider.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; +import 'package:filcnaplo_premium/providers/premium_provider.dart'; class App extends StatelessWidget { final SettingsProvider settings; @@ -62,20 +63,23 @@ class App extends StatelessWidget { // Set high refresh mode #28 if (Platform.isAndroid) FlutterDisplayMode.setHighRefreshRate(); - WidgetsBinding.instance.addPostFrameCallback((_) { - FilcAPI.getConfig(settings).then((Config? config) { - if (config != null) settings.update(context, database: database, config: config); - }); - }); - CorePalette? corePalette; final status = StatusProvider(); final kreta = KretaClient(user: user, settings: settings, status: status); final timetable = TimetableProvider(user: user, database: database, kreta: kreta); + final premium = PremiumProvider(settings: settings); + + WidgetsBinding.instance.addPostFrameCallback((_) { + FilcAPI.getConfig(settings).then((Config? config) { + if (config != null) settings.update(config: config); + }); + premium.activate(); + }); return MultiProvider( providers: [ + ChangeNotifierProvider(create: (_) => premium), ChangeNotifierProvider(create: (_) => settings), ChangeNotifierProvider(create: (_) => user), ChangeNotifierProvider(create: (_) => status), diff --git a/filcnaplo/lib/database/init.dart b/filcnaplo/lib/database/init.dart index d7bdabf..44eeba9 100644 --- a/filcnaplo/lib/database/init.dart +++ b/filcnaplo/lib/database/init.dart @@ -2,6 +2,7 @@ import 'dart:io'; +import 'package:filcnaplo/api/providers/database_provider.dart'; import 'package:filcnaplo/database/struct.dart'; import 'package:filcnaplo/models/settings.dart'; import 'package:sqflite/sqflite.dart'; @@ -15,7 +16,7 @@ const settingsDB = DatabaseStruct("settings", { "vibration_strength": int, "ab_weeks": int, "swap_ab_weeks": int, "notifications": int, "notifications_bitfield": int, "notification_poll_interval": int, // notifications "x_filc_id": String, "graph_class_avg": int, "presentation_mode": int, "bell_delay": int, "bell_delay_enabled": int, - "grade_opening_fun": int, "icon_pack": String, + "grade_opening_fun": int, "icon_pack": String, "premium_scopes": String, "premium_token": String, }); const usersDB = DatabaseStruct("users", { "id": String, "name": String, "username": String, "password": String, "institute_code": String, "student": String, "role": int, @@ -30,7 +31,7 @@ const userDataDB = DatabaseStruct("user_data", { Future createTable(Database db, DatabaseStruct struct) => db.execute("CREATE TABLE IF NOT EXISTS ${struct.table} ($struct)"); -Future initDB() async { +Future initDB(DatabaseProvider database) async { Database db; if (Platform.isLinux || Platform.isWindows) { @@ -46,7 +47,7 @@ Future initDB() async { if ((await db.rawQuery("SELECT COUNT(*) FROM settings"))[0].values.first == 0) { // Set default values for table Settings - await db.insert("settings", SettingsProvider.defaultSettings().toMap()); + await db.insert("settings", SettingsProvider.defaultSettings(database: database).toMap()); } // Migrate Databases @@ -54,7 +55,7 @@ Future initDB() async { await migrateDB( db, struct: settingsDB, - defaultValues: SettingsProvider.defaultSettings().toMap(), + defaultValues: SettingsProvider.defaultSettings(database: database).toMap(), ); await migrateDB( db, diff --git a/filcnaplo/lib/database/query.dart b/filcnaplo/lib/database/query.dart index ab19331..797d58a 100644 --- a/filcnaplo/lib/database/query.dart +++ b/filcnaplo/lib/database/query.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:filcnaplo/api/providers/database_provider.dart'; import 'package:filcnaplo/models/subject_lesson_count.dart'; import 'package:filcnaplo/models/user.dart'; // ignore: depend_on_referenced_packages @@ -22,9 +23,9 @@ class DatabaseQuery { final Database db; - Future getSettings() async { + Future getSettings(DatabaseProvider database) async { Map settingsMap = (await db.query("settings")).elementAt(0); - SettingsProvider settings = SettingsProvider.fromMap(settingsMap); + SettingsProvider settings = SettingsProvider.fromMap(settingsMap, database: database); return settings; } diff --git a/filcnaplo/lib/database/store.dart b/filcnaplo/lib/database/store.dart index 1046e7e..10d455a 100644 --- a/filcnaplo/lib/database/store.dart +++ b/filcnaplo/lib/database/store.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:developer'; import 'package:filcnaplo/models/subject_lesson_count.dart'; // ignore: depend_on_referenced_packages import 'package:sqflite_common/sqlite_api.dart'; diff --git a/filcnaplo/lib/helpers/share_helper.dart b/filcnaplo/lib/helpers/share_helper.dart index 5242980..f917e84 100644 --- a/filcnaplo/lib/helpers/share_helper.dart +++ b/filcnaplo/lib/helpers/share_helper.dart @@ -5,6 +5,7 @@ import 'package:share_plus/share_plus.dart'; class ShareHelper { static Future shareText(String text, {String? subject}) => Share.share(text, subject: subject); + // ignore: deprecated_member_use static Future shareFile(String path, {String? text, String? subject}) => Share.shareFiles([path], text: text, subject: subject); static Future shareAttachment(Attachment attachment, {required BuildContext context}) async { diff --git a/filcnaplo/lib/icons/filc_icons.dart b/filcnaplo/lib/icons/filc_icons.dart index e0e1458..e1528fb 100644 --- a/filcnaplo/lib/icons/filc_icons.dart +++ b/filcnaplo/lib/icons/filc_icons.dart @@ -16,4 +16,7 @@ class FilcIcons { /// downstairs static const IconData downstairs = IconData(0x03, fontFamily: iconFontFamily); + + /// premium + static const IconData premium = IconData(0x04, fontFamily: iconFontFamily); } diff --git a/filcnaplo/lib/main.dart b/filcnaplo/lib/main.dart index 918eab0..3de1311 100644 --- a/filcnaplo/lib/main.dart +++ b/filcnaplo/lib/main.dart @@ -32,11 +32,11 @@ class Startup { late DatabaseProvider database; Future start() async { - var db = await initDB(); - await db.close(); database = DatabaseProvider(); + var db = await initDB(database); + await db.close(); await database.init(); - settings = await database.query.getSettings(); + settings = await database.query.getSettings(database); user = await database.query.getUsers(); } } diff --git a/filcnaplo/lib/models/settings.dart b/filcnaplo/lib/models/settings.dart index 5985e21..48c1f59 100644 --- a/filcnaplo/lib/models/settings.dart +++ b/filcnaplo/lib/models/settings.dart @@ -7,7 +7,6 @@ import 'package:filcnaplo/models/icon_pack.dart'; import 'package:filcnaplo/theme/colors/accent.dart'; import 'package:filcnaplo/theme/colors/dark_mobile.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:uuid/uuid.dart'; enum Pages { home, grades, timetable, messages, absences } @@ -17,6 +16,8 @@ enum UpdateChannel { stable, beta, dev } enum VibrationStrength { off, light, medium, strong } class SettingsProvider extends ChangeNotifier { + final DatabaseProvider? _database; + // en_en, hu_hu, de_de String _language; Pages _startPage; @@ -61,8 +62,11 @@ class SettingsProvider extends ChangeNotifier { Color _customAccentColor; Color _customBackgroundColor; Color _customHighlightColor; + List _premiumScopes; + String _premiumAccessToken; SettingsProvider({ + DatabaseProvider? database, required String language, required Pages startPage, required int rounding, @@ -91,7 +95,10 @@ class SettingsProvider extends ChangeNotifier { required Color customAccentColor, required Color customBackgroundColor, required Color customHighlightColor, - }) : _language = language, + required List premiumScopes, + required String premiumAccessToken, + }) : _database = database, + _language = language, _startPage = startPage, _rounding = rounding, _theme = theme, @@ -118,9 +125,11 @@ class SettingsProvider extends ChangeNotifier { _iconPack = iconPack, _customAccentColor = customAccentColor, _customBackgroundColor = customBackgroundColor, - _customHighlightColor = customHighlightColor; + _customHighlightColor = customHighlightColor, + _premiumScopes = premiumScopes, + _premiumAccessToken = premiumAccessToken; - factory SettingsProvider.fromMap(Map map) { + factory SettingsProvider.fromMap(Map map, {required DatabaseProvider database}) { Map? configMap; try { @@ -130,6 +139,7 @@ class SettingsProvider extends ChangeNotifier { } return SettingsProvider( + database: database, language: map["language"], startPage: Pages.values[map["start_page"]], rounding: map["rounding"], @@ -164,6 +174,8 @@ class SettingsProvider extends ChangeNotifier { customAccentColor: Color(map["custom_accent_color"]), customBackgroundColor: Color(map["custom_background_color"]), customHighlightColor: Color(map["custom_highlight_color"]), + premiumScopes: jsonDecode(map["premium_scopes"]).cast(), + premiumAccessToken: map["premium_token"], ); } @@ -200,11 +212,14 @@ class SettingsProvider extends ChangeNotifier { "custom_accent_color": _customAccentColor.value, "custom_background_color": _customBackgroundColor.value, "custom_highlight_color": _customHighlightColor.value, + "premium_scopes": jsonEncode(_premiumScopes), + "premium_token": _premiumAccessToken, }; } - factory SettingsProvider.defaultSettings() { + factory SettingsProvider.defaultSettings({DatabaseProvider? database}) { return SettingsProvider( + database: database, language: "hu", startPage: Pages.home, rounding: 5, @@ -239,6 +254,8 @@ class SettingsProvider extends ChangeNotifier { customAccentColor: const Color(0xff20AC9B), customBackgroundColor: const Color(0xff000000), customHighlightColor: const Color(0xff222222), + premiumScopes: [], + premiumAccessToken: "", ); } @@ -271,10 +288,10 @@ class SettingsProvider extends ChangeNotifier { Color? get customAccentColor => _customAccentColor == accentColorMap[AccentColor.custom] ? null : _customAccentColor; Color? get customBackgroundColor => _customBackgroundColor; Color? get customHighlightColor => _customHighlightColor; + List get premiumScopes => _premiumScopes; + String get premiumAccessToken => _premiumAccessToken; - Future update( - BuildContext context, { - DatabaseProvider? database, + Future update({ bool store = true, String? language, Pages? startPage, @@ -304,6 +321,8 @@ class SettingsProvider extends ChangeNotifier { Color? customAccentColor, Color? customBackgroundColor, Color? customHighlightColor, + List? premiumScopes, + String? premiumAccessToken, }) async { if (language != null && language != _language) _language = language; if (startPage != null && startPage != _startPage) _startPage = startPage; @@ -335,9 +354,10 @@ class SettingsProvider extends ChangeNotifier { if (customAccentColor != null && customAccentColor != _customAccentColor) _customAccentColor = customAccentColor; if (customBackgroundColor != null && customBackgroundColor != _customBackgroundColor) _customBackgroundColor = customBackgroundColor; if (customHighlightColor != null && customHighlightColor != _customHighlightColor) _customHighlightColor = customHighlightColor; + if (premiumScopes != null && premiumScopes != _premiumScopes) _premiumScopes = premiumScopes; + if (premiumAccessToken != null && premiumAccessToken != _premiumAccessToken) _premiumAccessToken = premiumAccessToken; - database ??= Provider.of(context, listen: false); - if (store) await database.store.storeSettings(this); + if (store) await _database?.store.storeSettings(this); notifyListeners(); } } diff --git a/filcnaplo/lib/ui/filter/widgets.dart b/filcnaplo/lib/ui/filter/widgets.dart index 6e44e97..4cdb60a 100644 --- a/filcnaplo/lib/ui/filter/widgets.dart +++ b/filcnaplo/lib/ui/filter/widgets.dart @@ -12,6 +12,7 @@ import 'package:filcnaplo/ui/filter/widgets/events.dart' as event_filter; 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/premium.dart' as premium_filter; import 'package:filcnaplo_kreta_api/providers/absence_provider.dart'; import 'package:filcnaplo_kreta_api/providers/event_provider.dart'; import 'package:filcnaplo_kreta_api/providers/exam_provider.dart'; @@ -20,6 +21,7 @@ import 'package:filcnaplo_kreta_api/providers/homework_provider.dart'; import 'package:filcnaplo_kreta_api/providers/message_provider.dart'; import 'package:filcnaplo_kreta_api/providers/note_provider.dart'; import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart'; +import 'package:filcnaplo_premium/providers/premium_provider.dart'; import 'package:filcnaplo_mobile_ui/common/panel/panel.dart'; import 'package:flutter/material.dart'; import 'package:implicitly_animated_reorderable_list/transitions.dart'; @@ -27,7 +29,7 @@ import 'package:provider/provider.dart'; const List homeFilters = [FilterType.all, FilterType.grades, FilterType.messages, FilterType.absences]; -enum FilterType { all, grades, newGrades, messages, absences, homework, exams, notes, events, lessons, updates, certifications, missedExams } +enum FilterType { all, grades, newGrades, messages, absences, homework, exams, notes, events, lessons, updates, certifications, missedExams, premium } Future> getFilterWidgets(FilterType activeData, {bool absencesNoExcused = false, required BuildContext context}) async { final gradeProvider = Provider.of(context); @@ -40,6 +42,7 @@ Future> getFilterWidgets(FilterType activeData, {bool absencesN final eventProvider = Provider.of(context); final updateProvider = Provider.of(context); final settingsProvider = Provider.of(context); + final premiumProvider = Provider.of(context); List items = []; @@ -56,6 +59,7 @@ Future> getFilterWidgets(FilterType activeData, {bool absencesN getFilterWidgets(FilterType.updates, context: context), getFilterWidgets(FilterType.certifications, context: context), getFilterWidgets(FilterType.missedExams, context: context), + getFilterWidgets(FilterType.premium, context: context), ]); items = all.expand((x) => x).toList(); @@ -127,6 +131,12 @@ Future> getFilterWidgets(FilterType activeData, {bool absencesN case FilterType.missedExams: items = missed_exam_filter.getWidgets(timetableProvider.lessons); break; + + case FilterType.premium: + final now = DateTime.now(); + final isWeekend = now.weekday == DateTime.saturday || now.weekday == DateTime.sunday; + items = [if (!premiumProvider.hasPremium && isWeekend) premium_filter.getWidget()]; + break; } return items; } diff --git a/filcnaplo/lib/ui/filter/widgets/premium.dart b/filcnaplo/lib/ui/filter/widgets/premium.dart new file mode 100644 index 0000000..b1e5336 --- /dev/null +++ b/filcnaplo/lib/ui/filter/widgets/premium.dart @@ -0,0 +1,16 @@ +import 'package:filcnaplo/ui/date_widget.dart'; +import 'package:filcnaplo_premium/ui/mobile/premium/premium_banner_button.dart'; +import 'package:flutter/widgets.dart'; + +DateWidget getWidget() { + return DateWidget( + date: DateTime.now().add(const Duration(minutes: 1)), + widget: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(14.0), + child: const PremiumBannerButton(), + ), + ), + ); +} diff --git a/filcnaplo/pubspec.yaml b/filcnaplo/pubspec.yaml index 92e27bb..55ddf79 100644 --- a/filcnaplo/pubspec.yaml +++ b/filcnaplo/pubspec.yaml @@ -57,6 +57,8 @@ dependencies: git: url: https://github.com/filc/flutter_expandable_fab ref: master + uni_links: ^0.5.1 + url_launcher: ^6.1.6 dev_dependencies: flutter_lints: ^2.0.1 diff --git a/filcnaplo_mobile_ui b/filcnaplo_mobile_ui index beac629..8dca7bf 160000 --- a/filcnaplo_mobile_ui +++ b/filcnaplo_mobile_ui @@ -1 +1 @@ -Subproject commit beac629f88a4d7cb9b5edf696e1fe067aef84ff0 +Subproject commit 8dca7bf10124b2eae89441f9defc506a7127755c diff --git a/filcnaplo_premium b/filcnaplo_premium index f659db4..ea41ee1 160000 --- a/filcnaplo_premium +++ b/filcnaplo_premium @@ -1 +1 @@ -Subproject commit f659db4e98390df3a46d82357b8880f2c91e379b +Subproject commit ea41ee168fc3b1cac9d3c760edc4e5fd7c391d1f