diff --git a/android/app/build.gradle b/android/app/build.gradle index 26c5fa5..cfa383a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -44,7 +44,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "de.cantorgymnasium.meincantor" - minSdkVersion 18 + minSdkVersion 20 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..2d6b4a9 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,23 @@ +## Gson rules +# Gson uses generic type information stored in a class file when working with fields. Proguard +# removes such information by default, so configure it to keep all of it. +-keepattributes Signature + +# For using GSON @Expose annotation +-keepattributes *Annotation* + +# Gson specific classes +-dontwarn sun.misc.** +#-keep class com.google.gson.stream.** { *; } + +# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory, +# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) +-keep class * extends com.google.gson.TypeAdapter +-keep class * implements com.google.gson.TypeAdapterFactory +-keep class * implements com.google.gson.JsonSerializer +-keep class * implements com.google.gson.JsonDeserializer + +# Prevent R8 from leaving Data object members always null +-keepclassmembers,allowobfuscation class * { + @com.google.gson.annotations.SerializedName ; +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 336c0b8..729401f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -10,6 +10,8 @@ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> + android:showWhenLocked="true" + android:turnScreenOn="true" - + />--> diff --git a/android/app/src/main/res/drawable-hdpi/app_icon.png b/android/app/src/main/res/drawable-hdpi/app_icon.png new file mode 100644 index 0000000..975f367 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/app_icon.png differ diff --git a/android/app/src/main/res/drawable-mdpi/app_icon.png b/android/app/src/main/res/drawable-mdpi/app_icon.png new file mode 100644 index 0000000..c320649 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/app_icon.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/app_icon.png b/android/app/src/main/res/drawable-xhdpi/app_icon.png new file mode 100644 index 0000000..fb72cd7 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/app_icon.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/app_icon.png b/android/app/src/main/res/drawable-xxhdpi/app_icon.png new file mode 100644 index 0000000..d97a4a1 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/app_icon.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/app_icon.png b/android/app/src/main/res/drawable-xxxhdpi/app_icon.png new file mode 100644 index 0000000..31cfe90 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/app_icon.png differ diff --git a/android/app/src/main/res/raw/keep.xml b/android/app/src/main/res/raw/keep.xml new file mode 100644 index 0000000..c721bb9 --- /dev/null +++ b/android/app/src/main/res/raw/keep.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4..44f2c4f 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -7,6 +7,10 @@ import Flutter _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate + } + GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } diff --git a/lib/Settings/Pages/dev_settings.dart b/lib/Settings/Pages/dev_settings.dart index b243c35..4e40844 100644 --- a/lib/Settings/Pages/dev_settings.dart +++ b/lib/Settings/Pages/dev_settings.dart @@ -34,9 +34,7 @@ class DevSettings extends StatelessWidget { content: Text('Neuer API-Schlüssel gesetzt: $apiKey')); ScaffoldMessenger.of(context).showSnackBar(snackBar); - } - ) - ), + })), const Divider(), Padding( padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), @@ -51,7 +49,6 @@ class DevSettings extends StatelessWidget { ) ) ], - ) - ); + )); } } diff --git a/lib/Settings/Pages/info_settings.dart b/lib/Settings/Pages/info_settings.dart index 80715a7..f82c1f8 100644 --- a/lib/Settings/Pages/info_settings.dart +++ b/lib/Settings/Pages/info_settings.dart @@ -1,6 +1,11 @@ +import 'dart:io'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:MeinCantor/const.dart'; +import 'package:flutter_linkify/flutter_linkify.dart'; +import 'package:meincantor/const.dart'; +import 'package:meincantor/main.dart'; +import 'package:url_launcher/url_launcher.dart'; class InfoSettings extends StatelessWidget { const InfoSettings({Key? key}) : super(key: key); @@ -19,9 +24,31 @@ class InfoSettings extends StatelessWidget { leading: Icon(Icons.info_outlined), title: Text("Version"), subtitle: Text(version)), + ListTile( + leading: Icon(Icons.person_outlined), + title: Text("Autor"), + subtitle: Text(author), + onTap: () => launch("https://git.cantorgymnasium.de/denyskon"), + ), + ListTile( + leading: const Icon(Icons.source_outlined), + title: const Text("Quellcode"), + subtitle: Linkify( + onOpen: (link) async { + if (await canLaunch(link.url)) { + await launch(link.url); + } else { + throw 'Could not launch $link'; + } + }, + text: "https://git.cantorgymnasium.de/cantortechnik/meincantor-app", + linkStyle: const TextStyle(color: Palette.accent), + ), + ), ListTile( leading: const Icon(Icons.settings_backup_restore_outlined), title: const Text("Änderungsverlauf"), + subtitle: const Text("Was ist neu?"), onTap: () { showModalBottomSheet( isScrollControlled: true, @@ -30,14 +57,13 @@ class InfoSettings extends StatelessWidget { return SizedBox( height: 400, child: Column( - mainAxisSize: MainAxisSize.min, children: [ AppBar( title: const Text("Änderungsverlauf"), ), const Padding( padding: EdgeInsets.all(10), - child: Text(""), + child: Text("1.0 --\nErste Release-Version!"), ), ], ), diff --git a/lib/Settings/Pages/plan_settings.dart b/lib/Settings/Pages/plan_settings.dart index 54feb7a..0e34b7b 100644 --- a/lib/Settings/Pages/plan_settings.dart +++ b/lib/Settings/Pages/plan_settings.dart @@ -1,51 +1,20 @@ import 'dart:convert'; -import 'package:MeinCantor/main.dart'; +import 'package:meincantor/main.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cyclop/cyclop.dart'; -import 'package:MeinCantor/networking.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:MeinCantor/presets/colors.dart'; -import 'package:MeinCantor/presets/subjects.dart'; +import 'package:meincantor/presets/colors.dart'; +import 'package:meincantor/presets/subjects.dart'; -import 'package:MeinCantor/presets/teachers.dart'; +import 'package:meincantor/presets/teachers.dart'; -class PlanSettings extends StatefulWidget { +class PlanSettings extends StatelessWidget { const PlanSettings({Key? key}) : super(key: key); - @override - State createState() => _PlanSettingsState(); -} - -Future buildPlanColors(lesson) async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - if (!prefs.containsKey("color$lesson")) { - prefs.setInt("color$lesson", colors[lesson].value ?? Colors.grey.value); - } - await fetchLessonList(); - Color colorDeu = Color(prefs.getInt("color$lesson")!); - return colorDeu; -} - -Future> buildLessonsList() async { - await fetchLessonList(); - SharedPreferences prefs = await SharedPreferences.getInstance(); - String lessonsJson = prefs.getString("lessons")!; - List lessons = jsonDecode(lessonsJson); - return lessons; -} - -class _PlanSettingsState extends State { - Set swatches = { - ...Colors.primaries, - ...Colors.accents, - Palette.accent, - Palette.primary - }; - @override Widget build(BuildContext context) { return Scaffold( @@ -56,10 +25,202 @@ class _PlanSettingsState extends State { body: ListView( padding: const EdgeInsets.fromLTRB(5, 5, 5, 5), children: [ - const ListTile( - title: Text("Kurse/Fächer"), - leading: Icon(Icons.list_alt_outlined), + ListTile( + leading: const Icon(Icons.list_alt_outlined, color: Colors.red), + trailing: const Icon(Icons.arrow_forward_ios, size: 16), + title: const Text("Kurse"), + subtitle: const Text("Konfiguration der Kurse (Whitelist)"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const WhitelistSettings()), + ); + }, ), + ListTile( + leading: + const Icon(Icons.color_lens_outlined, color: Colors.teal), + trailing: const Icon(Icons.arrow_forward_ios, size: 16), + title: const Text("Farben"), + subtitle: + const Text("Konfiguration der Farben für die Plankacheln"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const PlanColorSettings()), + ); + }, + ), + ], + )); + } +} + +class WhitelistSettings extends StatefulWidget { + const WhitelistSettings({Key? key}) : super(key: key); + + @override + State createState() => _WhitelistSettingsState(); +} + +class _WhitelistSettingsState extends State { + Set swatches = { + ...Colors.primaries, + ...Colors.accents, + Palette.accent, + Palette.primary + }; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Farben"), + centerTitle: true, + ), + body: ListView( + padding: const EdgeInsets.fromLTRB(5, 5, 5, 5), + children: [ + FutureBuilder( + future: buildLessonsList(), + builder: (context, snapshot) { + if (snapshot.hasData) { + List children = []; + for (var element in (snapshot.data as List)) { + String subject = element['subject']; + String teacher = element['teacher']; + int id = element['id']; + children.add( + FutureBuilder( + future: buildPlanColors(subject), + builder: (context, snapshot) { + if (snapshot.hasData) { + Color color = snapshot.data as Color; + return FutureBuilder( + future: buildBlacklist(), + builder: (context, snapshot) { + if (snapshot.hasData) { + final _blacklist = + snapshot.data! as List; + final _blacklisted = + _blacklist.contains(id); + return ListTile( + leading: Checkbox( + value: + _blacklisted ? false : true, + onChanged: (state) async { + SharedPreferences prefs = + await SharedPreferences + .getInstance(); + setState(() { + _blacklisted + ? _blacklist.remove(id) + : _blacklist.add(id); + }); + prefs.setString("blacklist", + jsonEncode(_blacklist)); + }, + activeColor: color), + title: Text(subjects[subject] ?? ""), + subtitle: + Text(teachers[teacher] ?? ""), + onTap: () async { + SharedPreferences prefs = + await SharedPreferences + .getInstance(); + setState(() { + _blacklisted + ? _blacklist.remove(id) + : _blacklist.add(id); + }); + prefs.setString("blacklist", + jsonEncode(_blacklist)); + }, + ); + } else { + return const LinearProgressIndicator(); + } + }); + } else { + return (const LinearProgressIndicator()); + } + }), + ); + } + return Column( + children: children, + ); + } else { + return (const Center(child: CircularProgressIndicator())); + } + }), + ], + )); + } +} + +class PlanColorSettings extends StatefulWidget { + const PlanColorSettings({Key? key}) : super(key: key); + + @override + State createState() => _PlanColorSettingsState(); +} + +Future buildPlanColors(String lesson) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String key = "color$lesson"; + if (prefs.containsKey(key) == false) { + int colorValue; + if (colors.containsKey(lesson)) { + colorValue = colors[lesson].value; + } else { + colorValue = Colors.grey.value; + } + prefs.setInt(key, colorValue); + } + //await fetchLessonList(); + Color color = Color(prefs.getInt(key)!); + return color; +} + +Future> buildLessonsList() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String lessonsJson = prefs.getString("lessons")!; + List lessons = jsonDecode(lessonsJson); + return lessons; +} + +Future buildBlacklist() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + if (!prefs.containsKey("blacklist") || + jsonDecode(prefs.getString("blacklist")!).isEmpty) { + return []; + } + String blacklistJson = prefs.getString("blacklist")!; + List blacklist = jsonDecode(blacklistJson); + return blacklist; +} + +class _PlanColorSettingsState extends State { + Set swatches = { + ...Colors.primaries, + ...Colors.accents, + Palette.accent, + Palette.primary + }; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Farben"), + centerTitle: true, + ), + body: ListView( + padding: const EdgeInsets.fromLTRB(5, 5, 5, 5), + children: [ FutureBuilder( future: buildLessonsList(), builder: (context, snapshot) { diff --git a/lib/Settings/Pages/user_settings.dart b/lib/Settings/Pages/user_settings.dart index 63465dc..3407fbf 100644 --- a/lib/Settings/Pages/user_settings.dart +++ b/lib/Settings/Pages/user_settings.dart @@ -1,7 +1,14 @@ +import 'package:cyclop/cyclop.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:MeinCantor/networking.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:meincantor/Settings/Pages/plan_settings.dart'; +import 'package:meincantor/networking.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'dart:io' show Platform; + +import '../../const.dart'; Future getSettingsString(String key) async { SharedPreferences prefs = await SharedPreferences.getInstance(); @@ -28,17 +35,39 @@ class UserSettings extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), - child: Container( - width: 128.0, - height: 128.0, - decoration: const BoxDecoration( - shape: BoxShape.circle, - image: DecorationImage( - fit: BoxFit.scaleDown, - image: - AssetImage("assets/images/meincantor_r.png") - ) - ) + child: FutureBuilder( + future: Future.sync(() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? user = prefs.getString("user"); + if (user == null || user.isEmpty) { + user = ""; + } + String? name = prefs.getString("name"); + if (name == null || name.isEmpty) { + name = ""; + } + Map data = {"user": user, "name": name }; + return data; + }), + builder: (context, snapshot) { + if (snapshot.hasData) { + // .svg?text=${(snapshot.data! as Map)['name'][0]} + String url = "$avatarUrl/${(snapshot.data! as Map)['user']}"; + return Container( + width: 120.0, + height: 120.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + fit: BoxFit.scaleDown, + image: NetworkImage(url) + ) + ) + ); + } else { + return const CircularProgressIndicator(); + } + }, ), ), FutureBuilder( @@ -61,8 +90,44 @@ class UserSettings extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(5, 20, 5, 5), child: buildClassesChooser()), + ListTile( + leading: const Icon(MdiIcons.accountSettingsOutline), + trailing: const Icon(Icons.link, size: 16), + title: const Text("Account-Konsole"), + subtitle: const Text("Konto-Einstellungen öffnen"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AccountConsole()), + ); + }, + ), ], ) ); } } + + +class AccountConsole extends StatefulWidget { + @override + AccountConsoleState createState() => AccountConsoleState(); +} + +class AccountConsoleState extends State { + @override + void initState() { + super.initState(); + // Enable virtual display. + if (Platform.isAndroid) WebView.platform = AndroidWebView(); + } + + @override + Widget build(BuildContext context) { + return const WebView( + initialUrl: 'https://mein.cantorgymnasium.de/auth/realms/GCG.MeinCantor/account/', + ); + } +} + diff --git a/lib/Settings/dashboard.dart b/lib/Settings/dashboard.dart index fa176df..09672ac 100644 --- a/lib/Settings/dashboard.dart +++ b/lib/Settings/dashboard.dart @@ -1,5 +1,5 @@ -import 'package:MeinCantor/Settings/Pages/appearance_settings.dart'; -import 'package:MeinCantor/Settings/Pages/service_settings.dart'; +import 'package:meincantor/Settings/Pages/appearance_settings.dart'; +import 'package:meincantor/Settings/Pages/service_settings.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; diff --git a/lib/cache_manager.dart b/lib/cache_manager.dart new file mode 100644 index 0000000..4f384b9 --- /dev/null +++ b/lib/cache_manager.dart @@ -0,0 +1,20 @@ +import 'dart:convert'; + +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +Future getCachedTimetable(String ext) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String classNum; + if (prefs.getString('class_num') != null) { + classNum = prefs.getString('class_num')!.replaceAll("/", "_"); + } else { + classNum = '05_1'; + } + var apiKey = prefs.getString('api_key'); + var headers = {"x-api-key": "$apiKey"}; + var file = await DefaultCacheManager().getSingleFile( + "https://mein.cantorgymnasium.de/api/timetable/$ext/$classNum", + headers: headers); + return (utf8.decode(await file.readAsBytes())); +} diff --git a/lib/dashboard.dart b/lib/dashboard.dart index 7d5dddb..486d737 100644 --- a/lib/dashboard.dart +++ b/lib/dashboard.dart @@ -1,19 +1,24 @@ -import 'package:MeinCantor/raumuebersicht.dart'; -import 'package:MeinCantor/schulbibliothek.dart'; -import 'package:MeinCantor/schulcomputer.dart'; -import 'package:MeinCantor/schuelerzeitung.dart'; -import 'package:MeinCantor/Settings/dashboard.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:meincantor/const.dart'; +import 'package:meincantor/raumuebersicht.dart'; +import 'package:meincantor/schulbibliothek.dart'; +import 'package:meincantor/schulcomputer.dart'; +import 'package:meincantor/schuelerzeitung.dart'; +import 'package:meincantor/Settings/dashboard.dart'; -import 'package:MeinCantor/main.dart'; -import 'package:MeinCantor/networking.dart'; -import 'package:MeinCantor/login.dart'; +import 'package:meincantor/main.dart'; +import 'package:meincantor/networking.dart'; +import 'package:meincantor/login.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'news.dart'; + class Dashboard extends StatefulWidget { const Dashboard({Key? key, this.restorationId}) : super(key: key); @@ -53,8 +58,39 @@ class _DashboardState extends State with RestorationMixin { UserAccountsDrawerHeader( accountName: buildSettingsString('name', const TextStyle()), accountEmail: buildSettingsString('user', const TextStyle()), - currentAccountPicture: - Image.asset("assets/images/meincantor_r.png")), + currentAccountPicture: FutureBuilder( + future: Future.sync(() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? user = prefs.getString("user"); + if (user == null || user.isEmpty) { + user = ""; + } + String? name = prefs.getString("name"); + if (name == null || name.isEmpty) { + name = ""; + } + Map data = {"user": user, "name": name }; + return data; + }), + builder: (context, snapshot) { + if (snapshot.hasData) { + // .svg?text=${(snapshot.data! as Map)['name'][0]} + String url = "$avatarUrl/${(snapshot.data! as Map)['user']}"; + return Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + fit: BoxFit.scaleDown, + image: NetworkImage(url) + ) + ) + ); + } else { + return const CircularProgressIndicator(); + } + }, + ) + ), ListTile( title: const Text("Einstellungen"), onTap: () { @@ -175,7 +211,7 @@ class _DashboardBottomNavView extends StatelessWidget { _timeOfDayToDouble(const TimeOfDay(hour: 15, minute: 10))) { lessonCount = 9; } else if (_timeOfDayToDouble(TimeOfDay.now()) > - _timeOfDayToDouble(const TimeOfDay(hour: 15, minute: 10)) && + _timeOfDayToDouble(const TimeOfDay(hour: 15, minute: 10)) && _timeOfDayToDouble(TimeOfDay.now()) <= _timeOfDayToDouble(const TimeOfDay(hour: 16, minute: 00))) { lessonCount = 10; @@ -271,10 +307,10 @@ class _DashboardBottomNavView extends StatelessWidget { ), )), ), - width: 175, + width: 170, ), SizedBox( - width: 175, + width: 170, child: GestureDetector( onTap: () async { Navigator.push( @@ -310,7 +346,7 @@ class _DashboardBottomNavView extends StatelessWidget { ), ), SizedBox( - width: 175, + width: 170, child: GestureDetector( onTap: () async { Navigator.push( @@ -346,7 +382,7 @@ class _DashboardBottomNavView extends StatelessWidget { ), ), SizedBox( - width: 175, + width: 170, child: GestureDetector( onTap: () async { Navigator.push( @@ -381,6 +417,42 @@ class _DashboardBottomNavView extends StatelessWidget { ), )), ), + ), + SizedBox( + width: 170, + child: GestureDetector( + onTap: () async { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const News()), + ); + }, + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + child: const Padding( + padding: EdgeInsets.all(10), + child: ListTile( + title: Padding( + padding: EdgeInsets.fromLTRB(0, 0, 0, 10), + child: Icon( + MdiIcons.newspaperVariantOutline, + color: Palette.accent, + size: 48, + ), + ), + subtitle: Center( + child: Padding( + padding: EdgeInsets.fromLTRB(0, 10, 0, 0), + child: Text( + 'Aktuelles', + ), + ), + ), + ), + )), + ), ) ], ), @@ -439,9 +511,16 @@ class _DashboardBottomNavView extends StatelessWidget { ), body: TabBarView( children: [ - buildTodayClassTimetable(), - buildTomorrowClassTimetable(), - buildClassTimetable(), + buildTimetable( + fetchClassTimetable( + "/${DateFormat("yyyyMMdd").format(DateTime.now())}"), + "Vertretungsplan für heute"), + buildTimetable( + fetchClassTimetable( + "/${DateFormat("yyyyMMdd").format(DateTime.now().add(const Duration(days: 1)))}"), + "Vertretungsplan für morgen"), + buildTimetable(fetchClassTimetable("/latest"), + "aktueller Vertretungsplan") ], ), ), diff --git a/lib/generated_plugin_registrant.dart b/lib/generated_plugin_registrant.dart index 7933e85..849a492 100644 --- a/lib/generated_plugin_registrant.dart +++ b/lib/generated_plugin_registrant.dart @@ -5,14 +5,14 @@ // ignore_for_file: directives_ordering // ignore_for_file: lines_longer_than_80_chars -import 'package:fluttertoast/fluttertoast_web.dart'; import 'package:shared_preferences_web/shared_preferences_web.dart'; +import 'package:url_launcher_web/url_launcher_web.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; // ignore: public_member_api_docs void registerPlugins(Registrar registrar) { - FluttertoastWebPlugin.registerWith(registrar); SharedPreferencesPlugin.registerWith(registrar); + UrlLauncherPlugin.registerWith(registrar); registrar.registerMessageHandler(); } diff --git a/lib/login.dart b/lib/login.dart index 00701d7..434e9f7 100644 --- a/lib/login.dart +++ b/lib/login.dart @@ -14,6 +14,7 @@ Future checkKey() async { } class Login extends StatelessWidget { + final userController = TextEditingController(); final passwordController = TextEditingController(); final otpController = TextEditingController(); @@ -148,11 +149,7 @@ class Login extends StatelessWidget { child: const Text("Anmelden")) ], ), - ) - ) - ) - ) - ); + ))))); }, ); } diff --git a/lib/main.dart b/lib/main.dart index b99e9d9..261669d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,11 +1,28 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dashboard.dart'; import 'login.dart'; import 'dart:math'; -void main() => runApp(const App()); +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + // initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings('app_icon'); + final IOSInitializationSettings initializationSettingsIOS = + IOSInitializationSettings(); + final MacOSInitializationSettings initializationSettingsMacOS = + MacOSInitializationSettings(); + final InitializationSettings initializationSettings = InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsIOS, + macOS: initializationSettingsMacOS); + await flutterLocalNotificationsPlugin.initialize(initializationSettings); + runApp(const App()); +} class App extends StatelessWidget { const App({Key? key}) : super(key: key); diff --git a/lib/networking.dart b/lib/networking.dart index 60c9a4a..fff30a9 100644 --- a/lib/networking.dart +++ b/lib/networking.dart @@ -1,19 +1,30 @@ import 'dart:convert'; +import 'dart:io'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:meincantor/cache_manager.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/painting.dart'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:MeinCantor/const.dart'; -import 'package:MeinCantor/timetable.dart'; -import 'package:MeinCantor/login.dart'; -import 'package:MeinCantor/main.dart'; +import 'package:meincantor/const.dart'; +import 'package:meincantor/timetable.dart'; +import 'package:meincantor/login.dart'; +import 'package:meincantor/main.dart'; Future getArticles() async { - var uri = Uri.https(szUrl["url"]!, "/articles"); - final response = await http.get(uri); - return (response); + var uri = Uri.https(szUrl["url"]!, "/articles"); + final response = await http.get(uri); + return (response); +} + +Future getNews() async { + var uri = Uri.https(szUrl["url"]!, "/aktuelles"); + final response = await http.get(uri); + return (response); } Future getToken( @@ -40,7 +51,28 @@ Future getUserInfo( } Future fetchClassTimetable(String ext) async { - SharedPreferences prefs = await SharedPreferences.getInstance(); + FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + const AndroidNotificationDetails androidPlatformChannelSpecifics = + AndroidNotificationDetails('de.cantorgymnasium.meincantor', 'GCG.MeinCantor', + channelDescription: '', + importance: Importance.max, + priority: Priority.high, + ticker: 'ticker'); + const NotificationDetails platformChannelSpecifics = + NotificationDetails(android: androidPlatformChannelSpecifics); + await flutterLocalNotificationsPlugin.show( + 0, 'Neuer Vertretungsplan geladen!', 'Du hast folgende Vertretungen:\nSt. 8 Deutsch Frau Rinke, Raum 203\nSt. 4 Biologie Frau Borchert, Raum 107', platformChannelSpecifics, + payload: 'item x'); + try { + return (http.Response(await getCachedTimetable(ext), 200)); + } on HttpExceptionWithStatus catch (e) { + return http.Response(e.message, e.statusCode); + } on HttpException catch (e) { + return http.Response(e.message, 500); + } on SocketException catch (e) { + return http.Response(e.message, 404); + } + /*SharedPreferences prefs = await SharedPreferences.getInstance(); String classNum; if (prefs.getString('class_num') != null) { classNum = prefs.getString('class_num')!.replaceAll("/", "_"); @@ -53,45 +85,9 @@ Future fetchClassTimetable(String ext) async { final response = http.get(uri, headers: headers).onError((error, stackTrace) { return (http.Response("", 404)); }); - return response; + return response;*/ } -/*Future fetchTodayClassTimetable() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - String classNum; - if (prefs.getString('class_num') != null) { - classNum = prefs.getString('class_num')!.replaceAll("/", "_"); - } else { - classNum = '05_1'; - } - var apiKey = prefs.getString('api_key'); - var uri = - Uri.https("mein.cantorgymnasium.de", "/api/timetable/$classNum/today"); - var headers = {"x-api-key": "$apiKey"}; - final response = http.get(uri, headers: headers).onError((error, stackTrace) { - return (http.Response("", 404)); - }); - return response; -} - -Future fetchTomorrowClassTimetable() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - String classNum; - if (prefs.getString('class_num') != null) { - classNum = prefs.getString('class_num')!.replaceAll("/", "_"); - } else { - classNum = '05_1'; - } - var apiKey = prefs.getString('api_key'); - var uri = - Uri.https("mein.cantorgymnasium.de", "/api/timetable/$classNum/tomorrow"); - var headers = {"x-api-key": "$apiKey"}; - final response = http.get(uri, headers: headers).onError((error, stackTrace) { - return (http.Response("", 404)); - }); - return response; -}*/ - fetchLessonList() async { SharedPreferences prefs = await SharedPreferences.getInstance(); String classNum; @@ -114,18 +110,6 @@ fetchLessonList() async { } } -Widget buildClassTimetable() { - return buildTimetable(fetchClassTimetable(""), "aktueller Vertretungsplan"); -} - -Widget buildTodayClassTimetable() { - return buildTimetable(fetchClassTimetable("/today"), "Vertretungsplan für heute"); -} - -Widget buildTomorrowClassTimetable() { - return buildTimetable(fetchClassTimetable("/tomorrow"), "Vertretungsplan für morgen"); -} - Widget buildTimetable(Future future, String info) { return FutureBuilder( future: future, @@ -134,7 +118,7 @@ Widget buildTimetable(Future future, String info) { int statusCode = snapshot.data!.statusCode; if (statusCode == 200) { Widget timetableView = ClassTimetableBuilder.buildView( - jsonDecode(utf8.decode(snapshot.data!.bodyBytes)), context) + jsonDecode(snapshot.data!.body), context) .view .child; return timetableView; @@ -142,8 +126,7 @@ Widget buildTimetable(Future future, String info) { Navigator.push( context, MaterialPageRoute(builder: (context) => Login())); } else if (statusCode == 500) { - var chars = Runes( - 'Es konnte kein $info gefunden werden. \u{1F937}'); + var chars = Runes('Es konnte kein $info gefunden werden. \u{1F937}'); List cardChildren = []; cardChildren.add(ListTile( title: Text(String.fromCharCodes(chars), @@ -182,8 +165,7 @@ Widget buildTimetable(Future future, String info) { padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), child: ListView( children: [card], - ) - ); + )); } return Center(child: Text('Error $statusCode')); } else if (snapshot.hasError) { @@ -197,13 +179,14 @@ Widget buildTimetable(Future future, String info) { Widget buildTodayClassTimetableLesson(int count) { return FutureBuilder( - future: fetchClassTimetable("/today"), + future: fetchClassTimetable( + "/${DateFormat("yyyyMMdd").format(DateTime.now())}"), builder: (context, snapshot) { if (snapshot.hasData) { int statusCode = snapshot.data!.statusCode; if (statusCode == 200) { List lessons = LessonsListBuilder.buildList( - jsonDecode(utf8.decode(snapshot.data!.bodyBytes)), + jsonDecode(snapshot.data!.body), count: count) .lessons; if (lessons.isNotEmpty) { @@ -223,7 +206,7 @@ Widget buildTodayClassTimetableLesson(int count) { child: Column( children: cardChildren, )); - return card; + return Column(children: [card]); } } else if (statusCode == 400) { Future.delayed(Duration.zero, () { @@ -264,11 +247,8 @@ Widget buildTodayClassTimetableLesson(int count) { child: Column( children: cardChildren, )); - return Padding( - padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), - child: ListView( - children: [card], - ) + return Column( + children: [card], ); } return Center(child: Text('Error $statusCode')); diff --git a/lib/news.dart b/lib/news.dart new file mode 100644 index 0000000..0e3d141 --- /dev/null +++ b/lib/news.dart @@ -0,0 +1,224 @@ +import 'dart:convert'; +import 'package:meincantor/networking.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:http/http.dart' as http; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +Future getNewsRead() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? newsReadString = prefs.getString("newsRead"); + List newsRead; + if (newsReadString == null || + (jsonDecode(newsReadString) as List).isEmpty) { + newsRead = []; + } else { + newsRead = jsonDecode(newsReadString) as List; + } + return newsRead; +} + +class News extends StatefulWidget { + const News({Key? key}) : super(key: key); + + @override + State createState() => _NewsState(); +} + +class _NewsState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Aktuelles"), + centerTitle: true, + ), + body: FutureBuilder( + future: getNews(), + builder: (context, snapshot) { + if (snapshot.hasData) { + int statusCode = snapshot.data!.statusCode; + if (statusCode == 200) { + String data = utf8.decode(snapshot.data!.bodyBytes); + List articles = jsonDecode(data); + List articleTiles = []; + for (var element in articles) { + Color color = Colors.white70; + Widget card = FutureBuilder( + future: getNewsRead(), + builder: (context, snapshot) { + if (snapshot.hasData) { + List readList = snapshot.data! as List; + if (!readList.contains(element["id"])) { + return GestureDetector( + onTap: () async { + SharedPreferences prefs = + await SharedPreferences.getInstance(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Article.fromData( + element["title"], + element["content"], + element["author"], + element["published_at"]) + .widget), + ); + readList.add(element["id"]); + prefs.setString("newsRead", jsonEncode(readList)); + setState(() { + color = Colors.transparent; + }); + }, + child: Card( + color: color, + child: Padding( + padding: + const EdgeInsets.fromLTRB(10, 10, 10, 10), + child: FutureBuilder( + future: Future.delayed( + const Duration(seconds: 0)), + builder: (context, snapshot) { + if (element["summary"] != null && + (element["summary"] as String) + .isNotEmpty) { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold)), + subtitle: Text( + element["summary"], + overflow: TextOverflow.ellipsis, + maxLines: 2, + softWrap: true, + ), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } else { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold)), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } + }, + )), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + )), + ); + } else { + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Article.fromData( + element["title"], + element["content"], + element["author"], + element["published_at"]) + .widget), + ); + }, + child: Card( + child: Padding( + padding: + const EdgeInsets.fromLTRB(10, 10, 10, 10), + child: FutureBuilder( + future: Future.delayed( + const Duration(seconds: 0)), + builder: (context, snapshot) { + if (element["summary"] != null && + (element["summary"] as String) + .isNotEmpty) { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold)), + subtitle: Text( + element["summary"], + overflow: TextOverflow.ellipsis, + maxLines: 2, + softWrap: true, + ), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } else { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold) + ), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } + }, + )), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + )), + ); + } + } else { + return const LinearProgressIndicator(); + } + }, + ); + articleTiles.add(card); + } + return ListView( + children: articleTiles.reversed.toList(), + ); + } else { + return (const Center( + child: Text("Uups... Irgendwas ist schief gelaufen"))); + } + } else { + return (const Center(child: CircularProgressIndicator())); + } + }, + ), + ); + } +} + +class Article { + Widget widget; + //const Article({Key? key}) : super(key: key); + Article({required this.widget}); + factory Article.fromData( + String title, String content, String author, String publishDate) { + return Article( + widget: Scaffold( + appBar: AppBar( + title: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, child: Text(title)), + ), + centerTitle: true, + ), + body: ListView( + padding: const EdgeInsets.fromLTRB(15, 15, 15, 15), + children: [ + ListTile( + leading: const Icon(MdiIcons.accountOutline), + title: Text(author), + ), + ListTile( + leading: const Icon(MdiIcons.calendarOutline), + title: Text( + "${DateTime.parse(publishDate).day.toString()}.${DateTime.parse(publishDate).month.toString()}.${DateTime.parse(publishDate).year.toString()}"), + ), + MarkdownBody(data: content) + ], + ))); + } +} diff --git a/lib/notifications.dart b/lib/notifications.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/notifications.dart @@ -0,0 +1 @@ + diff --git a/lib/presets/colors.dart b/lib/presets/colors.dart index eaba1c2..8704493 100644 --- a/lib/presets/colors.dart +++ b/lib/presets/colors.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -dynamic colors = { +Map colors = { 'Bio': Colors.green, 'Mat': Colors.indigo, 'matL1': Colors.indigo, diff --git a/lib/schuelerzeitung.dart b/lib/schuelerzeitung.dart index 102fe25..e211464 100644 --- a/lib/schuelerzeitung.dart +++ b/lib/schuelerzeitung.dart @@ -1,72 +1,190 @@ import 'dart:convert'; - -import 'package:MeinCantor/networking.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:meincantor/networking.dart'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; - import 'package:http/http.dart' as http; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:shared_preferences/shared_preferences.dart'; -class SZ extends StatelessWidget { +Future getSZread() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? szReadString = prefs.getString("SZread"); + List szRead; + if (szReadString == null || + (jsonDecode(szReadString) as List).isEmpty) { + szRead = []; + } else { + szRead = jsonDecode(szReadString) as List; + } + return szRead; +} + +class SZ extends StatefulWidget { const SZ({Key? key}) : super(key: key); + @override + State createState() => _SZState(); +} + +class _SZState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text("Schülerzeitung"), - centerTitle: true, - ), - body: FutureBuilder( - future: getArticles(), - builder: (context, snapshot) { - if (snapshot.hasData) { - int statusCode = snapshot.data!.statusCode; - if (statusCode == 200) { - String data = utf8.decode(snapshot.data!.bodyBytes); - List articles = jsonDecode(data); - List articleTiles = []; - for (var element in articles) { - Card card = Card( - child: Column(children: [ - Padding( - padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), - child: ListTile( - onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => Article.fromData(element["title"], element["content"], element["author"], element["published_at"]).widget), - ); - }, - title: Text(element["title"], - style: const TextStyle(fontWeight: FontWeight.bold)), - subtitle: Text( - element["summary"])), - ) - ]), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15), - ), - ); - articleTiles.add(card); - } - return ListView( - children: articleTiles.reversed.toList(), + appBar: AppBar( + title: const Text("Schülerzeitung"), + centerTitle: true, + ), + body: FutureBuilder( + future: getArticles(), + builder: (context, snapshot) { + if (snapshot.hasData) { + int statusCode = snapshot.data!.statusCode; + if (statusCode == 200) { + String data = utf8.decode(snapshot.data!.bodyBytes); + List articles = jsonDecode(data); + List articleTiles = []; + for (var element in articles) { + Color color = Colors.white70; + Widget card = FutureBuilder( + future: getSZread(), + builder: (context, snapshot) { + if (snapshot.hasData) { + List readList = snapshot.data! as List; + if (!readList.contains(element["id"])) { + return GestureDetector( + onTap: () async { + SharedPreferences prefs = + await SharedPreferences.getInstance(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Article.fromData( + element["title"], + element["content"], + element["author"], + element["published_at"]) + .widget), + ); + readList.add(element["id"]); + prefs.setString("SZread", jsonEncode(readList)); + setState(() { + color = Colors.transparent; + }); + }, + child: Card( + color: color, + child: Padding( + padding: + const EdgeInsets.fromLTRB(10, 10, 10, 10), + child: FutureBuilder( + future: Future.delayed( + const Duration(seconds: 0)), + builder: (context, snapshot) { + if (element["summary"] != null && + (element["summary"] as String) + .isNotEmpty) { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold)), + subtitle: Text( + element["summary"], + overflow: TextOverflow.ellipsis, + maxLines: 2, + softWrap: true, + ), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } else { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold)), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } + }, + )), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + )), + ); + } else { + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Article.fromData( + element["title"], + element["content"], + element["author"], + element["published_at"]) + .widget), + ); + }, + child: Card( + child: Padding( + padding: + const EdgeInsets.fromLTRB(10, 10, 10, 10), + child: FutureBuilder( + future: Future.delayed( + const Duration(seconds: 0)), + builder: (context, snapshot) { + if (element["summary"] != null && + (element["summary"] as String) + .isNotEmpty) { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold)), + subtitle: Text( + element["summary"], + overflow: TextOverflow.ellipsis, + maxLines: 2, + softWrap: true, + ), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } else { + return ListTile( + title: Text(element["title"], + style: const TextStyle( + fontWeight: FontWeight.bold)), + trailing: Text( + "${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"), + ); + } + }, + )), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + )), + ); + } + } else { + return const LinearProgressIndicator(); + } + }, ); - } else { - return(const Center( - child: Text("Uups... Irgendwas ist schief gelaufen") - )); + articleTiles.add(card); } + return ListView( + children: articleTiles.reversed.toList(), + ); } else { - return(const Center( - child: CircularProgressIndicator() - )); + return (const Center( + child: Text("Uups... Irgendwas ist schief gelaufen"))); } - }, - ), - ); + } else { + return (const Center(child: CircularProgressIndicator())); + } + }, + ), + ); } } @@ -74,33 +192,32 @@ class Article { Widget widget; //const Article({Key? key}) : super(key: key); Article({required this.widget}); - factory Article.fromData(String title, String content, String author, String publishDate, ) { - return Article(widget: Scaffold( - appBar: AppBar( - title: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Text(title) + factory Article.fromData( + String title, String content, String author, String publishDate) { + return Article( + widget: Scaffold( + appBar: AppBar( + title: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, child: Text(title)), + ), + centerTitle: true, ), - ), - centerTitle: true, - ), - body: ListView( - padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), - children: [ - ListTile( - leading: const Icon(MdiIcons.accountOutline), - title: Text(author), - ), - ListTile( - leading: const Icon(MdiIcons.calendarOutline), - title: Text("${DateTime.parse(publishDate).day.toString()}.${DateTime.parse(publishDate).month.toString()}.${DateTime.parse(publishDate).year.toString()}"), - ), - MarkdownBody(data: content) - ], - ) - ) - ); + body: ListView( + padding: const EdgeInsets.fromLTRB(15, 15, 15, 15), + children: [ + ListTile( + leading: const Icon(MdiIcons.accountOutline), + title: Text(author), + ), + ListTile( + leading: const Icon(MdiIcons.calendarOutline), + title: Text( + "${DateTime.parse(publishDate).day.toString()}.${DateTime.parse(publishDate).month.toString()}.${DateTime.parse(publishDate).year.toString()}"), + ), + MarkdownBody(data: content) + ], + ))); } } diff --git a/lib/timetable.dart b/lib/timetable.dart index bbfcc5e..681c00b 100644 --- a/lib/timetable.dart +++ b/lib/timetable.dart @@ -1,12 +1,14 @@ -import 'package:MeinCantor/presets/teachers.dart'; -import 'package:MeinCantor/presets/subjects.dart'; -import 'package:MeinCantor/presets/colors.dart'; +import 'package:meincantor/presets/teachers.dart'; +import 'package:meincantor/presets/subjects.dart'; +import 'package:meincantor/presets/colors.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'Settings/Pages/plan_settings.dart'; + class ClassTimetableBuilder { final RefreshIndicator view; ClassTimetableBuilder({required this.view}); @@ -54,15 +56,14 @@ class ClassTimetableBuilder { } return ClassTimetableBuilder( view: RefreshIndicator( - onRefresh: () { - return Future.delayed(const Duration(seconds: 1)); - }, - child: ListView( - physics: const AlwaysScrollableScrollPhysics(), - children: list, - ), - ) - ); + onRefresh: () { + return Future.delayed(const Duration(seconds: 1)); + }, + child: ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: list, + ), + )); } } @@ -104,22 +105,35 @@ class LessonsListBuilder { style: TextStyle(color: element.fontColor)))); } Widget card = FutureBuilder( - future: element.color, - builder: (context, snapshot) { - if (snapshot.hasData) { - return Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15.0), - ), - color: snapshot.data as Color, - child: Column( - children: cardChildren, - )); - } else { - return (const Center(child: CircularProgressIndicator())); - } - }, - ); + future: buildBlacklist(), + builder: (context, snapshot) { + if (snapshot.hasData) { + if (!((snapshot.data as List).contains(element.id))) { + return FutureBuilder( + future: element.color, + builder: (context, snapshot) { + if (snapshot.hasData) { + return Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15.0), + ), + color: snapshot.data as Color, + child: Column( + children: cardChildren, + )); + } else { + return (const Center( + child: CircularProgressIndicator())); + } + }, + ); + } else { + return const SizedBox.shrink(); + } + } else { + return const LinearProgressIndicator(); + } + }); children.add(card); } } @@ -187,6 +201,7 @@ class ClassTimetable { lessons.add(TimetableLesson( value['St'], + value["Nr"], subjects[subject] ?? subject.toString(), teachers[teacher] ?? teacher.toString(), room.toString(), @@ -202,6 +217,7 @@ class ClassTimetable { class TimetableLesson { final int count; + final int id; final String name; final String teacher; final String room; @@ -209,6 +225,6 @@ class TimetableLesson { final Future color; final Color fontColor; final String info; - const TimetableLesson(this.count, this.name, this.teacher, this.room, + const TimetableLesson(this.count, this.id, this.name, this.teacher, this.room, this.comment, this.color, this.fontColor, this.info); } diff --git a/pubspec.yaml b/pubspec.yaml index 6ffed1f..4bd95bc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,4 +1,4 @@ -name: MeinCantor +name: meincantor description: Die Schulplatform für Cantorianer. # The following line prevents the package from being accidentally published to @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.7.5-beta1.nightly2021-11-16 +version: 0.8.0-dev environment: sdk: ">=2.12.0 <3.0.0" @@ -37,11 +37,17 @@ dependencies: google_fonts: ^2.1.0 time: ^2.0.0 flutter_launcher_icons: ^0.9.1 - fluttertoast: ^8.0.8 - flutter_colorpicker: ^0.6.0 material_design_icons_flutter: ^5.0.5955-rc.1 cyclop: ^0.5.2 flutter_markdown: ^0.6.8 + flutter_cache_manager: ^3.2.0 + intl: ^0.17.0 + url_launcher: ^6.0.17 + flutter_linkify: ^5.0.2 + flutter_svg: ^1.0.0 + webview_flutter: ^3.0.0 + flutter_local_notifications: ^10.0.0-dev.1 + background_fetch: ^1.0.3 flutter_icons: # image_path: "assets/images/icon-128x128.png" diff --git a/web/manifest.json b/web/manifest.json index 0426ae5..8feaa1a 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -3,8 +3,8 @@ "short_name": "MeinCantor", "start_url": ".", "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", + "background_color": "#1a1a37", + "theme_color": "#ffbc3b", "description": "Die Schulplatform für Cantorianer.", "orientation": "portrait-primary", "prefer_related_applications": false,