= 0.8.0-dev =

- code cleanup
- caching
- black-/whitelist
- sz/news fixes
- added settings options
...
This commit is contained in:
Denys Konovalov 2021-12-13 13:39:06 +01:00
parent 649e226c66
commit e86c1bad1d
28 changed files with 1017 additions and 279 deletions

@ -44,7 +44,7 @@ android {
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "de.cantorgymnasium.meincantor" applicationId "de.cantorgymnasium.meincantor"
minSdkVersion 18 minSdkVersion 20
targetSdkVersion 30 targetSdkVersion 30
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName

23
android/app/proguard-rules.pro vendored Normal file

@ -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 <fields>;
}

@ -10,6 +10,8 @@
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
android:showWhenLocked="true"
android:turnScreenOn="true"
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues
@ -23,10 +25,10 @@
screen fades out. A splash screen is useful to avoid any visual screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of gap between the end of Android's launch screen and the painting of
Flutter's first frame. --> Flutter's first frame. -->
<meta-data <!--meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable" <android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" android:resource="@drawable/launch_background"
/> />-->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@drawable/*" />

@ -7,6 +7,10 @@ import Flutter
_ application: UIApplication, _ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
GeneratedPluginRegistrant.register(with: self) GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }

@ -34,9 +34,7 @@ class DevSettings extends StatelessWidget {
content: content:
Text('Neuer API-Schlüssel gesetzt: $apiKey')); Text('Neuer API-Schlüssel gesetzt: $apiKey'));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
} })),
)
),
const Divider(), const Divider(),
Padding( Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
@ -51,7 +49,6 @@ class DevSettings extends StatelessWidget {
) )
) )
], ],
) ));
);
} }
} }

@ -1,6 +1,11 @@
import 'dart:io';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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 { class InfoSettings extends StatelessWidget {
const InfoSettings({Key? key}) : super(key: key); const InfoSettings({Key? key}) : super(key: key);
@ -19,9 +24,31 @@ class InfoSettings extends StatelessWidget {
leading: Icon(Icons.info_outlined), leading: Icon(Icons.info_outlined),
title: Text("Version"), title: Text("Version"),
subtitle: 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( ListTile(
leading: const Icon(Icons.settings_backup_restore_outlined), leading: const Icon(Icons.settings_backup_restore_outlined),
title: const Text("Änderungsverlauf"), title: const Text("Änderungsverlauf"),
subtitle: const Text("Was ist neu?"),
onTap: () { onTap: () {
showModalBottomSheet<void>( showModalBottomSheet<void>(
isScrollControlled: true, isScrollControlled: true,
@ -30,14 +57,13 @@ class InfoSettings extends StatelessWidget {
return SizedBox( return SizedBox(
height: 400, height: 400,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
AppBar( AppBar(
title: const Text("Änderungsverlauf"), title: const Text("Änderungsverlauf"),
), ),
const Padding( const Padding(
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),
child: Text(""), child: Text("1.0 --\nErste Release-Version!"),
), ),
], ],
), ),

@ -1,51 +1,20 @@
import 'dart:convert'; import 'dart:convert';
import 'package:MeinCantor/main.dart'; import 'package:meincantor/main.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cyclop/cyclop.dart'; import 'package:cyclop/cyclop.dart';
import 'package:MeinCantor/networking.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:MeinCantor/presets/colors.dart'; import 'package:meincantor/presets/colors.dart';
import 'package:MeinCantor/presets/subjects.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); const PlanSettings({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _PlanSettingsState();
}
Future<Color> 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<List<dynamic>> buildLessonsList() async {
await fetchLessonList();
SharedPreferences prefs = await SharedPreferences.getInstance();
String lessonsJson = prefs.getString("lessons")!;
List<dynamic> lessons = jsonDecode(lessonsJson);
return lessons;
}
class _PlanSettingsState extends State<PlanSettings> {
Set<Color> swatches = {
...Colors.primaries,
...Colors.accents,
Palette.accent,
Palette.primary
};
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -56,10 +25,202 @@ class _PlanSettingsState extends State<PlanSettings> {
body: ListView( body: ListView(
padding: const EdgeInsets.fromLTRB(5, 5, 5, 5), padding: const EdgeInsets.fromLTRB(5, 5, 5, 5),
children: [ children: [
const ListTile( ListTile(
title: Text("Kurse/Fächer"), leading: const Icon(Icons.list_alt_outlined, color: Colors.red),
leading: Icon(Icons.list_alt_outlined), 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<StatefulWidget> createState() => _WhitelistSettingsState();
}
class _WhitelistSettingsState extends State<WhitelistSettings> {
Set<Color> 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<Widget> children = [];
for (var element in (snapshot.data as List<dynamic>)) {
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<dynamic>;
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<StatefulWidget> createState() => _PlanColorSettingsState();
}
Future<Color> 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<List<dynamic>> buildLessonsList() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String lessonsJson = prefs.getString("lessons")!;
List<dynamic> lessons = jsonDecode(lessonsJson);
return lessons;
}
Future<List> buildBlacklist() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey("blacklist") ||
jsonDecode(prefs.getString("blacklist")!).isEmpty) {
return <int>[];
}
String blacklistJson = prefs.getString("blacklist")!;
List blacklist = jsonDecode(blacklistJson);
return blacklist;
}
class _PlanColorSettingsState extends State<PlanColorSettings> {
Set<Color> 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( FutureBuilder(
future: buildLessonsList(), future: buildLessonsList(),
builder: (context, snapshot) { builder: (context, snapshot) {

@ -1,7 +1,14 @@
import 'package:cyclop/cyclop.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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:shared_preferences/shared_preferences.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'dart:io' show Platform;
import '../../const.dart';
Future<String> getSettingsString(String key) async { Future<String> getSettingsString(String key) async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
@ -28,17 +35,39 @@ class UserSettings extends StatelessWidget {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
child: Container( child: FutureBuilder(
width: 128.0, future: Future.sync(() async {
height: 128.0, SharedPreferences prefs = await SharedPreferences.getInstance();
decoration: const BoxDecoration( String? user = prefs.getString("user");
shape: BoxShape.circle, if (user == null || user.isEmpty) {
image: DecorationImage( user = "";
fit: BoxFit.scaleDown, }
image: String? name = prefs.getString("name");
AssetImage("assets/images/meincantor_r.png") 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( FutureBuilder(
@ -61,8 +90,44 @@ class UserSettings extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.fromLTRB(5, 20, 5, 5), padding: const EdgeInsets.fromLTRB(5, 20, 5, 5),
child: buildClassesChooser()), 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<AccountConsole> {
@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/',
);
}
}

@ -1,5 +1,5 @@
import 'package:MeinCantor/Settings/Pages/appearance_settings.dart'; import 'package:meincantor/Settings/Pages/appearance_settings.dart';
import 'package:MeinCantor/Settings/Pages/service_settings.dart'; import 'package:meincantor/Settings/Pages/service_settings.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';

20
lib/cache_manager.dart Normal file

@ -0,0 +1,20 @@
import 'dart:convert';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:shared_preferences/shared_preferences.dart';
Future<String> 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()));
}

@ -1,19 +1,24 @@
import 'package:MeinCantor/raumuebersicht.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:MeinCantor/schulbibliothek.dart'; import 'package:meincantor/const.dart';
import 'package:MeinCantor/schulcomputer.dart'; import 'package:meincantor/raumuebersicht.dart';
import 'package:MeinCantor/schuelerzeitung.dart'; import 'package:meincantor/schulbibliothek.dart';
import 'package:MeinCantor/Settings/dashboard.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/main.dart';
import 'package:MeinCantor/networking.dart'; import 'package:meincantor/networking.dart';
import 'package:MeinCantor/login.dart'; import 'package:meincantor/login.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'news.dart';
class Dashboard extends StatefulWidget { class Dashboard extends StatefulWidget {
const Dashboard({Key? key, this.restorationId}) : super(key: key); const Dashboard({Key? key, this.restorationId}) : super(key: key);
@ -53,8 +58,39 @@ class _DashboardState extends State<Dashboard> with RestorationMixin {
UserAccountsDrawerHeader( UserAccountsDrawerHeader(
accountName: buildSettingsString('name', const TextStyle()), accountName: buildSettingsString('name', const TextStyle()),
accountEmail: buildSettingsString('user', const TextStyle()), accountEmail: buildSettingsString('user', const TextStyle()),
currentAccountPicture: currentAccountPicture: FutureBuilder(
Image.asset("assets/images/meincantor_r.png")), 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( ListTile(
title: const Text("Einstellungen"), title: const Text("Einstellungen"),
onTap: () { onTap: () {
@ -175,7 +211,7 @@ class _DashboardBottomNavView extends StatelessWidget {
_timeOfDayToDouble(const TimeOfDay(hour: 15, minute: 10))) { _timeOfDayToDouble(const TimeOfDay(hour: 15, minute: 10))) {
lessonCount = 9; lessonCount = 9;
} else if (_timeOfDayToDouble(TimeOfDay.now()) > } else if (_timeOfDayToDouble(TimeOfDay.now()) >
_timeOfDayToDouble(const TimeOfDay(hour: 15, minute: 10)) && _timeOfDayToDouble(const TimeOfDay(hour: 15, minute: 10)) &&
_timeOfDayToDouble(TimeOfDay.now()) <= _timeOfDayToDouble(TimeOfDay.now()) <=
_timeOfDayToDouble(const TimeOfDay(hour: 16, minute: 00))) { _timeOfDayToDouble(const TimeOfDay(hour: 16, minute: 00))) {
lessonCount = 10; lessonCount = 10;
@ -271,10 +307,10 @@ class _DashboardBottomNavView extends StatelessWidget {
), ),
)), )),
), ),
width: 175, width: 170,
), ),
SizedBox( SizedBox(
width: 175, width: 170,
child: GestureDetector( child: GestureDetector(
onTap: () async { onTap: () async {
Navigator.push( Navigator.push(
@ -310,7 +346,7 @@ class _DashboardBottomNavView extends StatelessWidget {
), ),
), ),
SizedBox( SizedBox(
width: 175, width: 170,
child: GestureDetector( child: GestureDetector(
onTap: () async { onTap: () async {
Navigator.push( Navigator.push(
@ -346,7 +382,7 @@ class _DashboardBottomNavView extends StatelessWidget {
), ),
), ),
SizedBox( SizedBox(
width: 175, width: 170,
child: GestureDetector( child: GestureDetector(
onTap: () async { onTap: () async {
Navigator.push( 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( body: TabBarView(
children: <Widget>[ children: <Widget>[
buildTodayClassTimetable(), buildTimetable(
buildTomorrowClassTimetable(), fetchClassTimetable(
buildClassTimetable(), "/${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")
], ],
), ),
), ),

@ -5,14 +5,14 @@
// ignore_for_file: directives_ordering // ignore_for_file: directives_ordering
// ignore_for_file: lines_longer_than_80_chars // 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: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'; import 'package:flutter_web_plugins/flutter_web_plugins.dart';
// ignore: public_member_api_docs // ignore: public_member_api_docs
void registerPlugins(Registrar registrar) { void registerPlugins(Registrar registrar) {
FluttertoastWebPlugin.registerWith(registrar);
SharedPreferencesPlugin.registerWith(registrar); SharedPreferencesPlugin.registerWith(registrar);
UrlLauncherPlugin.registerWith(registrar);
registrar.registerMessageHandler(); registrar.registerMessageHandler();
} }

@ -14,6 +14,7 @@ Future<bool> checkKey() async {
} }
class Login extends StatelessWidget { class Login extends StatelessWidget {
final userController = TextEditingController(); final userController = TextEditingController();
final passwordController = TextEditingController(); final passwordController = TextEditingController();
final otpController = TextEditingController(); final otpController = TextEditingController();
@ -148,11 +149,7 @@ class Login extends StatelessWidget {
child: const Text("Anmelden")) child: const Text("Anmelden"))
], ],
), ),
) )))));
)
)
)
);
}, },
); );
} }

@ -1,11 +1,28 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'dashboard.dart'; import 'dashboard.dart';
import 'login.dart'; import 'login.dart';
import 'dart:math'; 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 { class App extends StatelessWidget {
const App({Key? key}) : super(key: key); const App({Key? key}) : super(key: key);

@ -1,19 +1,30 @@
import 'dart:convert'; 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/cupertino.dart';
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:MeinCantor/const.dart'; import 'package:meincantor/const.dart';
import 'package:MeinCantor/timetable.dart'; import 'package:meincantor/timetable.dart';
import 'package:MeinCantor/login.dart'; import 'package:meincantor/login.dart';
import 'package:MeinCantor/main.dart'; import 'package:meincantor/main.dart';
Future<http.Response> getArticles() async { Future<http.Response> getArticles() async {
var uri = Uri.https(szUrl["url"]!, "/articles"); var uri = Uri.https(szUrl["url"]!, "/articles");
final response = await http.get(uri); final response = await http.get(uri);
return (response); return (response);
}
Future<http.Response> getNews() async {
var uri = Uri.https(szUrl["url"]!, "/aktuelles");
final response = await http.get(uri);
return (response);
} }
Future<http.Response> getToken( Future<http.Response> getToken(
@ -40,7 +51,28 @@ Future<String> getUserInfo(
} }
Future<http.Response> fetchClassTimetable(String ext) async { Future<http.Response> 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; String classNum;
if (prefs.getString('class_num') != null) { if (prefs.getString('class_num') != null) {
classNum = prefs.getString('class_num')!.replaceAll("/", "_"); classNum = prefs.getString('class_num')!.replaceAll("/", "_");
@ -53,45 +85,9 @@ Future<http.Response> fetchClassTimetable(String ext) async {
final response = http.get(uri, headers: headers).onError((error, stackTrace) { final response = http.get(uri, headers: headers).onError((error, stackTrace) {
return (http.Response("", 404)); return (http.Response("", 404));
}); });
return response; return response;*/
} }
/*Future<http.Response> 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<http.Response> 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 { fetchLessonList() async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
String classNum; 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<http.Response> future, String info) { Widget buildTimetable(Future<http.Response> future, String info) {
return FutureBuilder<http.Response>( return FutureBuilder<http.Response>(
future: future, future: future,
@ -134,7 +118,7 @@ Widget buildTimetable(Future<http.Response> future, String info) {
int statusCode = snapshot.data!.statusCode; int statusCode = snapshot.data!.statusCode;
if (statusCode == 200) { if (statusCode == 200) {
Widget timetableView = ClassTimetableBuilder.buildView( Widget timetableView = ClassTimetableBuilder.buildView(
jsonDecode(utf8.decode(snapshot.data!.bodyBytes)), context) jsonDecode(snapshot.data!.body), context)
.view .view
.child; .child;
return timetableView; return timetableView;
@ -142,8 +126,7 @@ Widget buildTimetable(Future<http.Response> future, String info) {
Navigator.push( Navigator.push(
context, MaterialPageRoute(builder: (context) => Login())); context, MaterialPageRoute(builder: (context) => Login()));
} else if (statusCode == 500) { } else if (statusCode == 500) {
var chars = Runes( var chars = Runes('Es konnte kein $info gefunden werden. \u{1F937}');
'Es konnte kein $info gefunden werden. \u{1F937}');
List<Widget> cardChildren = []; List<Widget> cardChildren = [];
cardChildren.add(ListTile( cardChildren.add(ListTile(
title: Text(String.fromCharCodes(chars), title: Text(String.fromCharCodes(chars),
@ -182,8 +165,7 @@ Widget buildTimetable(Future<http.Response> future, String info) {
padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), padding: const EdgeInsets.fromLTRB(20, 20, 20, 20),
child: ListView( child: ListView(
children: [card], children: [card],
) ));
);
} }
return Center(child: Text('Error $statusCode')); return Center(child: Text('Error $statusCode'));
} else if (snapshot.hasError) { } else if (snapshot.hasError) {
@ -197,13 +179,14 @@ Widget buildTimetable(Future<http.Response> future, String info) {
Widget buildTodayClassTimetableLesson(int count) { Widget buildTodayClassTimetableLesson(int count) {
return FutureBuilder<http.Response>( return FutureBuilder<http.Response>(
future: fetchClassTimetable("/today"), future: fetchClassTimetable(
"/${DateFormat("yyyyMMdd").format(DateTime.now())}"),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
int statusCode = snapshot.data!.statusCode; int statusCode = snapshot.data!.statusCode;
if (statusCode == 200) { if (statusCode == 200) {
List<Widget> lessons = LessonsListBuilder.buildList( List<Widget> lessons = LessonsListBuilder.buildList(
jsonDecode(utf8.decode(snapshot.data!.bodyBytes)), jsonDecode(snapshot.data!.body),
count: count) count: count)
.lessons; .lessons;
if (lessons.isNotEmpty) { if (lessons.isNotEmpty) {
@ -223,7 +206,7 @@ Widget buildTodayClassTimetableLesson(int count) {
child: Column( child: Column(
children: cardChildren, children: cardChildren,
)); ));
return card; return Column(children: [card]);
} }
} else if (statusCode == 400) { } else if (statusCode == 400) {
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
@ -264,11 +247,8 @@ Widget buildTodayClassTimetableLesson(int count) {
child: Column( child: Column(
children: cardChildren, children: cardChildren,
)); ));
return Padding( return Column(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), children: [card],
child: ListView(
children: [card],
)
); );
} }
return Center(child: Text('Error $statusCode')); return Center(child: Text('Error $statusCode'));

224
lib/news.dart Normal file

@ -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<List> getNewsRead() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? newsReadString = prefs.getString("newsRead");
List<dynamic> newsRead;
if (newsReadString == null ||
(jsonDecode(newsReadString) as List<dynamic>).isEmpty) {
newsRead = [];
} else {
newsRead = jsonDecode(newsReadString) as List<dynamic>;
}
return newsRead;
}
class News extends StatefulWidget {
const News({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _NewsState();
}
class _NewsState extends State<News> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Aktuelles"),
centerTitle: true,
),
body: FutureBuilder<http.Response>(
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<Widget> articleTiles = [];
for (var element in articles) {
Color color = Colors.white70;
Widget card = FutureBuilder(
future: getNewsRead(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<dynamic> readList = snapshot.data! as List<dynamic>;
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)
],
)));
}
}

1
lib/notifications.dart Normal file

@ -0,0 +1 @@

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
dynamic colors = { Map colors = {
'Bio': Colors.green, 'Bio': Colors.green,
'Mat': Colors.indigo, 'Mat': Colors.indigo,
'matL1': Colors.indigo, 'matL1': Colors.indigo,

@ -1,72 +1,190 @@
import 'dart:convert'; import 'dart:convert';
import 'package:meincantor/networking.dart';
import 'package:MeinCantor/networking.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SZ extends StatelessWidget { Future<List> getSZread() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? szReadString = prefs.getString("SZread");
List<dynamic> szRead;
if (szReadString == null ||
(jsonDecode(szReadString) as List<dynamic>).isEmpty) {
szRead = [];
} else {
szRead = jsonDecode(szReadString) as List<dynamic>;
}
return szRead;
}
class SZ extends StatefulWidget {
const SZ({Key? key}) : super(key: key); const SZ({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _SZState();
}
class _SZState extends State<SZ> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text("Schülerzeitung"), title: const Text("Schülerzeitung"),
centerTitle: true, centerTitle: true,
), ),
body: FutureBuilder<http.Response>( body: FutureBuilder<http.Response>(
future: getArticles(), future: getArticles(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
int statusCode = snapshot.data!.statusCode; int statusCode = snapshot.data!.statusCode;
if (statusCode == 200) { if (statusCode == 200) {
String data = utf8.decode(snapshot.data!.bodyBytes); String data = utf8.decode(snapshot.data!.bodyBytes);
List articles = jsonDecode(data); List articles = jsonDecode(data);
List<Widget> articleTiles = []; List<Widget> articleTiles = [];
for (var element in articles) { for (var element in articles) {
Card card = Card( Color color = Colors.white70;
child: Column(children: [ Widget card = FutureBuilder(
Padding( future: getSZread(),
padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), builder: (context, snapshot) {
child: ListTile( if (snapshot.hasData) {
onTap: () { List<dynamic> readList = snapshot.data! as List<dynamic>;
Navigator.push( if (!readList.contains(element["id"])) {
context, return GestureDetector(
MaterialPageRoute(builder: (context) => Article.fromData(element["title"], element["content"], element["author"], element["published_at"]).widget), onTap: () async {
); SharedPreferences prefs =
}, await SharedPreferences.getInstance();
title: Text(element["title"], Navigator.push(
style: const TextStyle(fontWeight: FontWeight.bold)), context,
subtitle: Text( MaterialPageRoute(
element["summary"])), builder: (context) => Article.fromData(
) element["title"],
]), element["content"],
shape: RoundedRectangleBorder( element["author"],
borderRadius: BorderRadius.circular(15), element["published_at"])
), .widget),
); );
articleTiles.add(card); readList.add(element["id"]);
} prefs.setString("SZread", jsonEncode(readList));
return ListView( setState(() {
children: articleTiles.reversed.toList(), 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 { articleTiles.add(card);
return(const Center(
child: Text("Uups... Irgendwas ist schief gelaufen")
));
} }
return ListView(
children: articleTiles.reversed.toList(),
);
} else { } else {
return(const Center( return (const Center(
child: CircularProgressIndicator() child: Text("Uups... Irgendwas ist schief gelaufen")));
));
} }
}, } else {
), return (const Center(child: CircularProgressIndicator()));
); }
},
),
);
} }
} }
@ -74,33 +192,32 @@ class Article {
Widget widget; Widget widget;
//const Article({Key? key}) : super(key: key); //const Article({Key? key}) : super(key: key);
Article({required this.widget}); Article({required this.widget});
factory Article.fromData(String title, String content, String author, String publishDate, ) { factory Article.fromData(
return Article(widget: Scaffold( String title, String content, String author, String publishDate) {
appBar: AppBar( return Article(
title: SingleChildScrollView( widget: Scaffold(
scrollDirection: Axis.vertical, appBar: AppBar(
child: SingleChildScrollView( title: SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.vertical,
child: Text(title) child: SingleChildScrollView(
scrollDirection: Axis.horizontal, child: Text(title)),
),
centerTitle: true,
), ),
), body: ListView(
centerTitle: true, padding: const EdgeInsets.fromLTRB(15, 15, 15, 15),
), children: [
body: ListView( ListTile(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), leading: const Icon(MdiIcons.accountOutline),
children: [ title: Text(author),
ListTile( ),
leading: const Icon(MdiIcons.accountOutline), ListTile(
title: Text(author), leading: const Icon(MdiIcons.calendarOutline),
), title: Text(
ListTile( "${DateTime.parse(publishDate).day.toString()}.${DateTime.parse(publishDate).month.toString()}.${DateTime.parse(publishDate).year.toString()}"),
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)
), ],
MarkdownBody(data: content) )));
],
)
)
);
} }
} }

@ -1,12 +1,14 @@
import 'package:MeinCantor/presets/teachers.dart'; import 'package:meincantor/presets/teachers.dart';
import 'package:MeinCantor/presets/subjects.dart'; import 'package:meincantor/presets/subjects.dart';
import 'package:MeinCantor/presets/colors.dart'; import 'package:meincantor/presets/colors.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'Settings/Pages/plan_settings.dart';
class ClassTimetableBuilder { class ClassTimetableBuilder {
final RefreshIndicator view; final RefreshIndicator view;
ClassTimetableBuilder({required this.view}); ClassTimetableBuilder({required this.view});
@ -54,15 +56,14 @@ class ClassTimetableBuilder {
} }
return ClassTimetableBuilder( return ClassTimetableBuilder(
view: RefreshIndicator( view: RefreshIndicator(
onRefresh: () { onRefresh: () {
return Future.delayed(const Duration(seconds: 1)); return Future.delayed(const Duration(seconds: 1));
}, },
child: ListView( child: ListView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
children: list, children: list,
), ),
) ));
);
} }
} }
@ -104,22 +105,35 @@ class LessonsListBuilder {
style: TextStyle(color: element.fontColor)))); style: TextStyle(color: element.fontColor))));
} }
Widget card = FutureBuilder( Widget card = FutureBuilder(
future: element.color, future: buildBlacklist(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
return Card( if (!((snapshot.data as List<dynamic>).contains(element.id))) {
shape: RoundedRectangleBorder( return FutureBuilder(
borderRadius: BorderRadius.circular(15.0), future: element.color,
), builder: (context, snapshot) {
color: snapshot.data as Color, if (snapshot.hasData) {
child: Column( return Card(
children: cardChildren, shape: RoundedRectangleBorder(
)); borderRadius: BorderRadius.circular(15.0),
} else { ),
return (const Center(child: CircularProgressIndicator())); 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); children.add(card);
} }
} }
@ -187,6 +201,7 @@ class ClassTimetable {
lessons.add(TimetableLesson( lessons.add(TimetableLesson(
value['St'], value['St'],
value["Nr"],
subjects[subject] ?? subject.toString(), subjects[subject] ?? subject.toString(),
teachers[teacher] ?? teacher.toString(), teachers[teacher] ?? teacher.toString(),
room.toString(), room.toString(),
@ -202,6 +217,7 @@ class ClassTimetable {
class TimetableLesson { class TimetableLesson {
final int count; final int count;
final int id;
final String name; final String name;
final String teacher; final String teacher;
final String room; final String room;
@ -209,6 +225,6 @@ class TimetableLesson {
final Future<Color> color; final Future<Color> color;
final Color fontColor; final Color fontColor;
final String info; 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); this.comment, this.color, this.fontColor, this.info);
} }

@ -1,4 +1,4 @@
name: MeinCantor name: meincantor
description: Die Schulplatform für Cantorianer. description: Die Schulplatform für Cantorianer.
# The following line prevents the package from being accidentally published to # 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. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # 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: environment:
sdk: ">=2.12.0 <3.0.0" sdk: ">=2.12.0 <3.0.0"
@ -37,11 +37,17 @@ dependencies:
google_fonts: ^2.1.0 google_fonts: ^2.1.0
time: ^2.0.0 time: ^2.0.0
flutter_launcher_icons: ^0.9.1 flutter_launcher_icons: ^0.9.1
fluttertoast: ^8.0.8
flutter_colorpicker: ^0.6.0
material_design_icons_flutter: ^5.0.5955-rc.1 material_design_icons_flutter: ^5.0.5955-rc.1
cyclop: ^0.5.2 cyclop: ^0.5.2
flutter_markdown: ^0.6.8 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: flutter_icons:
# image_path: "assets/images/icon-128x128.png" # image_path: "assets/images/icon-128x128.png"

@ -3,8 +3,8 @@
"short_name": "MeinCantor", "short_name": "MeinCantor",
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"background_color": "#0175C2", "background_color": "#1a1a37",
"theme_color": "#0175C2", "theme_color": "#ffbc3b",
"description": "Die Schulplatform für Cantorianer.", "description": "Die Schulplatform für Cantorianer.",
"orientation": "portrait-primary", "orientation": "portrait-primary",
"prefer_related_applications": false, "prefer_related_applications": false,