= 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

View File

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

View File

@ -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

View File

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

View File

@ -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)
} }

View File

@ -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 {
) )
) )
], ],
) ));
);
} }
} }

View File

@ -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!"),
), ),
], ],
), ),

View File

@ -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) {

View File

@ -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");
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, shape: BoxShape.circle,
image: DecorationImage( image: DecorationImage(
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
image: image: NetworkImage(url)
AssetImage("assets/images/meincantor_r.png")
) )
) )
);
} 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/',
);
}
}

View File

@ -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
View 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()));
}

View File

@ -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: () {
@ -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")
], ],
), ),
), ),

View File

@ -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();
} }

View File

@ -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"))
], ],
), ),
) )))));
)
)
)
);
}, },
); );
} }

View File

@ -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);

View File

@ -1,14 +1,19 @@
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");
@ -16,6 +21,12 @@ Future<http.Response> getArticles() async {
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(
String user, String password, String otp, String devId) async { String user, String password, String otp, String devId) async {
var uri = Uri.https("mein.cantorgymnasium.de", "/login"); var uri = Uri.https("mein.cantorgymnasium.de", "/login");
@ -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),
child: ListView(
children: [card], children: [card],
)
); );
} }
return Center(child: Text('Error $statusCode')); return Center(child: Text('Error $statusCode'));

224
lib/news.dart Normal file
View 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
View File

@ -0,0 +1 @@

View File

@ -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,

View File

@ -1,16 +1,32 @@
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(
@ -28,26 +44,131 @@ class SZ extends StatelessWidget {
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) {
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("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: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => Article.fromData(element["title"], element["content"], element["author"], element["published_at"]).widget), 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"], title: Text(element["title"],
style: const TextStyle(fontWeight: FontWeight.bold)), style: const TextStyle(
fontWeight: FontWeight.bold)),
subtitle: Text( subtitle: Text(
element["summary"])), 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( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
), )),
);
}
} else {
return const LinearProgressIndicator();
}
},
); );
articleTiles.add(card); articleTiles.add(card);
} }
@ -56,13 +177,10 @@ class SZ extends StatelessWidget {
); );
} else { } else {
return (const Center( return (const Center(
child: Text("Uups... Irgendwas ist schief gelaufen") child: Text("Uups... Irgendwas ist schief gelaufen")));
));
} }
} else { } else {
return(const Center( return (const Center(child: CircularProgressIndicator()));
child: CircularProgressIndicator()
));
} }
}, },
), ),
@ -74,20 +192,20 @@ 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) {
return Article(
widget: Scaffold(
appBar: AppBar( appBar: AppBar(
title: SingleChildScrollView( title: SingleChildScrollView(
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
child: SingleChildScrollView( child: SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal, child: Text(title)),
child: Text(title)
),
), ),
centerTitle: true, centerTitle: true,
), ),
body: ListView( body: ListView(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), padding: const EdgeInsets.fromLTRB(15, 15, 15, 15),
children: [ children: [
ListTile( ListTile(
leading: const Icon(MdiIcons.accountOutline), leading: const Icon(MdiIcons.accountOutline),
@ -95,12 +213,11 @@ class Article {
), ),
ListTile( ListTile(
leading: const Icon(MdiIcons.calendarOutline), leading: const Icon(MdiIcons.calendarOutline),
title: Text("${DateTime.parse(publishDate).day.toString()}.${DateTime.parse(publishDate).month.toString()}.${DateTime.parse(publishDate).year.toString()}"), title: Text(
"${DateTime.parse(publishDate).day.toString()}.${DateTime.parse(publishDate).month.toString()}.${DateTime.parse(publishDate).year.toString()}"),
), ),
MarkdownBody(data: content) MarkdownBody(data: content)
], ],
) )));
)
);
} }
} }

View File

@ -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});
@ -61,8 +63,7 @@ class ClassTimetableBuilder {
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
children: list, children: list,
), ),
) ));
);
} }
} }
@ -104,6 +105,11 @@ class LessonsListBuilder {
style: TextStyle(color: element.fontColor)))); style: TextStyle(color: element.fontColor))));
} }
Widget card = FutureBuilder( Widget card = FutureBuilder(
future: buildBlacklist(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (!((snapshot.data as List<dynamic>).contains(element.id))) {
return FutureBuilder(
future: element.color, future: element.color,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
@ -116,10 +122,18 @@ class LessonsListBuilder {
children: cardChildren, children: cardChildren,
)); ));
} else { } else {
return (const Center(child: CircularProgressIndicator())); 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);
} }

View File

@ -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"

View File

@ -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,