= 0.8.0-dev =
- code cleanup - caching - black-/whitelist - sz/news fixes - added settings options ...
This commit is contained in:
parent
649e226c66
commit
e86c1bad1d
@ -44,7 +44,7 @@ android {
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "de.cantorgymnasium.meincantor"
|
||||
minSdkVersion 18
|
||||
minSdkVersion 20
|
||||
targetSdkVersion 30
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
23
android/app/proguard-rules.pro
vendored
Normal file
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:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
android:showWhenLocked="true"
|
||||
android:turnScreenOn="true"
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
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
|
||||
gap between the end of Android's launch screen and the painting of
|
||||
Flutter's first frame. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||
<!--meta-data
|
||||
<android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||
android:resource="@drawable/launch_background"
|
||||
/>
|
||||
/>-->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
BIN
android/app/src/main/res/drawable-hdpi/app_icon.png
Normal file
BIN
android/app/src/main/res/drawable-hdpi/app_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/drawable-mdpi/app_icon.png
Normal file
BIN
android/app/src/main/res/drawable-mdpi/app_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
android/app/src/main/res/drawable-xhdpi/app_icon.png
Normal file
BIN
android/app/src/main/res/drawable-xhdpi/app_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/app_icon.png
Normal file
BIN
android/app/src/main/res/drawable-xxhdpi/app_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/app_icon.png
Normal file
BIN
android/app/src/main/res/drawable-xxxhdpi/app_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.8 KiB |
3
android/app/src/main/res/raw/keep.xml
Normal file
3
android/app/src/main/res/raw/keep.xml
Normal file
@ -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,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
if #available(iOS 10.0, *) {
|
||||
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
|
||||
}
|
||||
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
@ -34,9 +34,7 @@ class DevSettings extends StatelessWidget {
|
||||
content:
|
||||
Text('Neuer API-Schlüssel gesetzt: $apiKey'));
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
)
|
||||
),
|
||||
})),
|
||||
const Divider(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
|
||||
@ -51,7 +49,6 @@ class DevSettings extends StatelessWidget {
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,11 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:MeinCantor/const.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:meincantor/const.dart';
|
||||
import 'package:meincantor/main.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class InfoSettings extends StatelessWidget {
|
||||
const InfoSettings({Key? key}) : super(key: key);
|
||||
@ -19,9 +24,31 @@ class InfoSettings extends StatelessWidget {
|
||||
leading: Icon(Icons.info_outlined),
|
||||
title: Text("Version"),
|
||||
subtitle: Text(version)),
|
||||
ListTile(
|
||||
leading: Icon(Icons.person_outlined),
|
||||
title: Text("Autor"),
|
||||
subtitle: Text(author),
|
||||
onTap: () => launch("https://git.cantorgymnasium.de/denyskon"),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.source_outlined),
|
||||
title: const Text("Quellcode"),
|
||||
subtitle: Linkify(
|
||||
onOpen: (link) async {
|
||||
if (await canLaunch(link.url)) {
|
||||
await launch(link.url);
|
||||
} else {
|
||||
throw 'Could not launch $link';
|
||||
}
|
||||
},
|
||||
text: "https://git.cantorgymnasium.de/cantortechnik/meincantor-app",
|
||||
linkStyle: const TextStyle(color: Palette.accent),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings_backup_restore_outlined),
|
||||
title: const Text("Änderungsverlauf"),
|
||||
subtitle: const Text("Was ist neu?"),
|
||||
onTap: () {
|
||||
showModalBottomSheet<void>(
|
||||
isScrollControlled: true,
|
||||
@ -30,14 +57,13 @@ class InfoSettings extends StatelessWidget {
|
||||
return SizedBox(
|
||||
height: 400,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
AppBar(
|
||||
title: const Text("Änderungsverlauf"),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: Text(""),
|
||||
child: Text("1.0 --\nErste Release-Version!"),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -1,51 +1,20 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:MeinCantor/main.dart';
|
||||
import 'package:meincantor/main.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cyclop/cyclop.dart';
|
||||
|
||||
import 'package:MeinCantor/networking.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:MeinCantor/presets/colors.dart';
|
||||
import 'package:MeinCantor/presets/subjects.dart';
|
||||
import 'package:meincantor/presets/colors.dart';
|
||||
import 'package:meincantor/presets/subjects.dart';
|
||||
|
||||
import 'package:MeinCantor/presets/teachers.dart';
|
||||
import 'package:meincantor/presets/teachers.dart';
|
||||
|
||||
class PlanSettings extends StatefulWidget {
|
||||
class PlanSettings extends StatelessWidget {
|
||||
const PlanSettings({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -56,10 +25,202 @@ class _PlanSettingsState extends State<PlanSettings> {
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(5, 5, 5, 5),
|
||||
children: [
|
||||
const ListTile(
|
||||
title: Text("Kurse/Fächer"),
|
||||
leading: Icon(Icons.list_alt_outlined),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.list_alt_outlined, color: Colors.red),
|
||||
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||
title: const Text("Kurse"),
|
||||
subtitle: const Text("Konfiguration der Kurse (Whitelist)"),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const WhitelistSettings()),
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading:
|
||||
const Icon(Icons.color_lens_outlined, color: Colors.teal),
|
||||
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||
title: const Text("Farben"),
|
||||
subtitle:
|
||||
const Text("Konfiguration der Farben für die Plankacheln"),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const PlanColorSettings()),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class WhitelistSettings extends StatefulWidget {
|
||||
const WhitelistSettings({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<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(
|
||||
future: buildLessonsList(),
|
||||
builder: (context, snapshot) {
|
||||
|
@ -1,7 +1,14 @@
|
||||
import 'package:cyclop/cyclop.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:MeinCantor/networking.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
import 'package:meincantor/Settings/Pages/plan_settings.dart';
|
||||
import 'package:meincantor/networking.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import '../../const.dart';
|
||||
|
||||
Future<String> getSettingsString(String key) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
@ -28,17 +35,39 @@ class UserSettings extends StatelessWidget {
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
|
||||
child: Container(
|
||||
width: 128.0,
|
||||
height: 128.0,
|
||||
decoration: const BoxDecoration(
|
||||
child: FutureBuilder(
|
||||
future: Future.sync(() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? user = prefs.getString("user");
|
||||
if (user == null || user.isEmpty) {
|
||||
user = "";
|
||||
}
|
||||
String? name = prefs.getString("name");
|
||||
if (name == null || name.isEmpty) {
|
||||
name = "";
|
||||
}
|
||||
Map data = {"user": user, "name": name };
|
||||
return data;
|
||||
}),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
// .svg?text=${(snapshot.data! as Map)['name'][0]}
|
||||
String url = "$avatarUrl/${(snapshot.data! as Map)['user']}";
|
||||
return Container(
|
||||
width: 120.0,
|
||||
height: 120.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.scaleDown,
|
||||
image:
|
||||
AssetImage("assets/images/meincantor_r.png")
|
||||
image: NetworkImage(url)
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
FutureBuilder(
|
||||
@ -61,8 +90,44 @@ class UserSettings extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(5, 20, 5, 5),
|
||||
child: buildClassesChooser()),
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.accountSettingsOutline),
|
||||
trailing: const Icon(Icons.link, size: 16),
|
||||
title: const Text("Account-Konsole"),
|
||||
subtitle: const Text("Konto-Einstellungen öffnen"),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AccountConsole()),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AccountConsole extends StatefulWidget {
|
||||
@override
|
||||
AccountConsoleState createState() => AccountConsoleState();
|
||||
}
|
||||
|
||||
class AccountConsoleState extends State<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/service_settings.dart';
|
||||
import 'package:meincantor/Settings/Pages/appearance_settings.dart';
|
||||
import 'package:meincantor/Settings/Pages/service_settings.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
|
20
lib/cache_manager.dart
Normal file
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:MeinCantor/schulbibliothek.dart';
|
||||
import 'package:MeinCantor/schulcomputer.dart';
|
||||
import 'package:MeinCantor/schuelerzeitung.dart';
|
||||
import 'package:MeinCantor/Settings/dashboard.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:meincantor/const.dart';
|
||||
import 'package:meincantor/raumuebersicht.dart';
|
||||
import 'package:meincantor/schulbibliothek.dart';
|
||||
import 'package:meincantor/schulcomputer.dart';
|
||||
import 'package:meincantor/schuelerzeitung.dart';
|
||||
import 'package:meincantor/Settings/dashboard.dart';
|
||||
|
||||
import 'package:MeinCantor/main.dart';
|
||||
import 'package:MeinCantor/networking.dart';
|
||||
import 'package:MeinCantor/login.dart';
|
||||
import 'package:meincantor/main.dart';
|
||||
import 'package:meincantor/networking.dart';
|
||||
import 'package:meincantor/login.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
|
||||
import 'news.dart';
|
||||
|
||||
class Dashboard extends StatefulWidget {
|
||||
const Dashboard({Key? key, this.restorationId}) : super(key: key);
|
||||
|
||||
@ -53,8 +58,39 @@ class _DashboardState extends State<Dashboard> with RestorationMixin {
|
||||
UserAccountsDrawerHeader(
|
||||
accountName: buildSettingsString('name', const TextStyle()),
|
||||
accountEmail: buildSettingsString('user', const TextStyle()),
|
||||
currentAccountPicture:
|
||||
Image.asset("assets/images/meincantor_r.png")),
|
||||
currentAccountPicture: FutureBuilder(
|
||||
future: Future.sync(() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? user = prefs.getString("user");
|
||||
if (user == null || user.isEmpty) {
|
||||
user = "";
|
||||
}
|
||||
String? name = prefs.getString("name");
|
||||
if (name == null || name.isEmpty) {
|
||||
name = "";
|
||||
}
|
||||
Map data = {"user": user, "name": name };
|
||||
return data;
|
||||
}),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
// .svg?text=${(snapshot.data! as Map)['name'][0]}
|
||||
String url = "$avatarUrl/${(snapshot.data! as Map)['user']}";
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.scaleDown,
|
||||
image: NetworkImage(url)
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
},
|
||||
)
|
||||
),
|
||||
ListTile(
|
||||
title: const Text("Einstellungen"),
|
||||
onTap: () {
|
||||
@ -271,10 +307,10 @@ class _DashboardBottomNavView extends StatelessWidget {
|
||||
),
|
||||
)),
|
||||
),
|
||||
width: 175,
|
||||
width: 170,
|
||||
),
|
||||
SizedBox(
|
||||
width: 175,
|
||||
width: 170,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
Navigator.push(
|
||||
@ -310,7 +346,7 @@ class _DashboardBottomNavView extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 175,
|
||||
width: 170,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
Navigator.push(
|
||||
@ -346,7 +382,7 @@ class _DashboardBottomNavView extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 175,
|
||||
width: 170,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
Navigator.push(
|
||||
@ -381,6 +417,42 @@ class _DashboardBottomNavView extends StatelessWidget {
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 170,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const News()),
|
||||
);
|
||||
},
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: ListTile(
|
||||
title: Padding(
|
||||
padding: EdgeInsets.fromLTRB(0, 0, 0, 10),
|
||||
child: Icon(
|
||||
MdiIcons.newspaperVariantOutline,
|
||||
color: Palette.accent,
|
||||
size: 48,
|
||||
),
|
||||
),
|
||||
subtitle: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(0, 10, 0, 0),
|
||||
child: Text(
|
||||
'Aktuelles',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
@ -439,9 +511,16 @@ class _DashboardBottomNavView extends StatelessWidget {
|
||||
),
|
||||
body: TabBarView(
|
||||
children: <Widget>[
|
||||
buildTodayClassTimetable(),
|
||||
buildTomorrowClassTimetable(),
|
||||
buildClassTimetable(),
|
||||
buildTimetable(
|
||||
fetchClassTimetable(
|
||||
"/${DateFormat("yyyyMMdd").format(DateTime.now())}"),
|
||||
"Vertretungsplan für heute"),
|
||||
buildTimetable(
|
||||
fetchClassTimetable(
|
||||
"/${DateFormat("yyyyMMdd").format(DateTime.now().add(const Duration(days: 1)))}"),
|
||||
"Vertretungsplan für morgen"),
|
||||
buildTimetable(fetchClassTimetable("/latest"),
|
||||
"aktueller Vertretungsplan")
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -5,14 +5,14 @@
|
||||
// ignore_for_file: directives_ordering
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
import 'package:fluttertoast/fluttertoast_web.dart';
|
||||
import 'package:shared_preferences_web/shared_preferences_web.dart';
|
||||
import 'package:url_launcher_web/url_launcher_web.dart';
|
||||
|
||||
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
||||
|
||||
// ignore: public_member_api_docs
|
||||
void registerPlugins(Registrar registrar) {
|
||||
FluttertoastWebPlugin.registerWith(registrar);
|
||||
SharedPreferencesPlugin.registerWith(registrar);
|
||||
UrlLauncherPlugin.registerWith(registrar);
|
||||
registrar.registerMessageHandler();
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ Future<bool> checkKey() async {
|
||||
}
|
||||
|
||||
class Login extends StatelessWidget {
|
||||
|
||||
final userController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
final otpController = TextEditingController();
|
||||
@ -148,11 +149,7 @@ class Login extends StatelessWidget {
|
||||
child: const Text("Anmelden"))
|
||||
],
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
)))));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +1,28 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'dashboard.dart';
|
||||
import 'login.dart';
|
||||
import 'dart:math';
|
||||
|
||||
void main() => runApp(const App());
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project
|
||||
const AndroidInitializationSettings initializationSettingsAndroid =
|
||||
AndroidInitializationSettings('app_icon');
|
||||
final IOSInitializationSettings initializationSettingsIOS =
|
||||
IOSInitializationSettings();
|
||||
final MacOSInitializationSettings initializationSettingsMacOS =
|
||||
MacOSInitializationSettings();
|
||||
final InitializationSettings initializationSettings = InitializationSettings(
|
||||
android: initializationSettingsAndroid,
|
||||
iOS: initializationSettingsIOS,
|
||||
macOS: initializationSettingsMacOS);
|
||||
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
||||
runApp(const App());
|
||||
}
|
||||
|
||||
class App extends StatelessWidget {
|
||||
const App({Key? key}) : super(key: key);
|
||||
|
@ -1,14 +1,19 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:meincantor/cache_manager.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:MeinCantor/const.dart';
|
||||
import 'package:MeinCantor/timetable.dart';
|
||||
import 'package:MeinCantor/login.dart';
|
||||
import 'package:MeinCantor/main.dart';
|
||||
import 'package:meincantor/const.dart';
|
||||
import 'package:meincantor/timetable.dart';
|
||||
import 'package:meincantor/login.dart';
|
||||
import 'package:meincantor/main.dart';
|
||||
|
||||
Future<http.Response> getArticles() async {
|
||||
var uri = Uri.https(szUrl["url"]!, "/articles");
|
||||
@ -16,6 +21,12 @@ Future<http.Response> getArticles() async {
|
||||
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(
|
||||
String user, String password, String otp, String devId) async {
|
||||
var uri = Uri.https("mein.cantorgymnasium.de", "/login");
|
||||
@ -40,7 +51,28 @@ Future<String> getUserInfo(
|
||||
}
|
||||
|
||||
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;
|
||||
if (prefs.getString('class_num') != null) {
|
||||
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) {
|
||||
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 {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String classNum;
|
||||
@ -114,18 +110,6 @@ fetchLessonList() async {
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildClassTimetable() {
|
||||
return buildTimetable(fetchClassTimetable(""), "aktueller Vertretungsplan");
|
||||
}
|
||||
|
||||
Widget buildTodayClassTimetable() {
|
||||
return buildTimetable(fetchClassTimetable("/today"), "Vertretungsplan für heute");
|
||||
}
|
||||
|
||||
Widget buildTomorrowClassTimetable() {
|
||||
return buildTimetable(fetchClassTimetable("/tomorrow"), "Vertretungsplan für morgen");
|
||||
}
|
||||
|
||||
Widget buildTimetable(Future<http.Response> future, String info) {
|
||||
return FutureBuilder<http.Response>(
|
||||
future: future,
|
||||
@ -134,7 +118,7 @@ Widget buildTimetable(Future<http.Response> future, String info) {
|
||||
int statusCode = snapshot.data!.statusCode;
|
||||
if (statusCode == 200) {
|
||||
Widget timetableView = ClassTimetableBuilder.buildView(
|
||||
jsonDecode(utf8.decode(snapshot.data!.bodyBytes)), context)
|
||||
jsonDecode(snapshot.data!.body), context)
|
||||
.view
|
||||
.child;
|
||||
return timetableView;
|
||||
@ -142,8 +126,7 @@ Widget buildTimetable(Future<http.Response> future, String info) {
|
||||
Navigator.push(
|
||||
context, MaterialPageRoute(builder: (context) => Login()));
|
||||
} else if (statusCode == 500) {
|
||||
var chars = Runes(
|
||||
'Es konnte kein $info gefunden werden. \u{1F937}');
|
||||
var chars = Runes('Es konnte kein $info gefunden werden. \u{1F937}');
|
||||
List<Widget> cardChildren = [];
|
||||
cardChildren.add(ListTile(
|
||||
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),
|
||||
child: ListView(
|
||||
children: [card],
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
return Center(child: Text('Error $statusCode'));
|
||||
} else if (snapshot.hasError) {
|
||||
@ -197,13 +179,14 @@ Widget buildTimetable(Future<http.Response> future, String info) {
|
||||
|
||||
Widget buildTodayClassTimetableLesson(int count) {
|
||||
return FutureBuilder<http.Response>(
|
||||
future: fetchClassTimetable("/today"),
|
||||
future: fetchClassTimetable(
|
||||
"/${DateFormat("yyyyMMdd").format(DateTime.now())}"),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
int statusCode = snapshot.data!.statusCode;
|
||||
if (statusCode == 200) {
|
||||
List<Widget> lessons = LessonsListBuilder.buildList(
|
||||
jsonDecode(utf8.decode(snapshot.data!.bodyBytes)),
|
||||
jsonDecode(snapshot.data!.body),
|
||||
count: count)
|
||||
.lessons;
|
||||
if (lessons.isNotEmpty) {
|
||||
@ -223,7 +206,7 @@ Widget buildTodayClassTimetableLesson(int count) {
|
||||
child: Column(
|
||||
children: cardChildren,
|
||||
));
|
||||
return card;
|
||||
return Column(children: [card]);
|
||||
}
|
||||
} else if (statusCode == 400) {
|
||||
Future.delayed(Duration.zero, () {
|
||||
@ -264,11 +247,8 @@ Widget buildTodayClassTimetableLesson(int count) {
|
||||
child: Column(
|
||||
children: cardChildren,
|
||||
));
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 20),
|
||||
child: ListView(
|
||||
return Column(
|
||||
children: [card],
|
||||
)
|
||||
);
|
||||
}
|
||||
return Center(child: Text('Error $statusCode'));
|
||||
|
224
lib/news.dart
Normal file
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
1
lib/notifications.dart
Normal file
@ -0,0 +1 @@
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
dynamic colors = {
|
||||
Map colors = {
|
||||
'Bio': Colors.green,
|
||||
'Mat': Colors.indigo,
|
||||
'matL1': Colors.indigo,
|
||||
|
@ -1,16 +1,32 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:MeinCantor/networking.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:meincantor/networking.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class SZ extends StatelessWidget {
|
||||
Future<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);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _SZState();
|
||||
}
|
||||
|
||||
class _SZState extends State<SZ> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -28,26 +44,131 @@ class SZ extends StatelessWidget {
|
||||
List articles = jsonDecode(data);
|
||||
List<Widget> articleTiles = [];
|
||||
for (var element in articles) {
|
||||
Card card = Card(
|
||||
child: Column(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
|
||||
child: ListTile(
|
||||
Color color = Colors.white70;
|
||||
Widget card = FutureBuilder(
|
||||
future: getSZread(),
|
||||
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("SZread", jsonEncode(readList));
|
||||
setState(() {
|
||||
color = Colors.transparent;
|
||||
});
|
||||
},
|
||||
child: Card(
|
||||
color: color,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(10, 10, 10, 10),
|
||||
child: FutureBuilder(
|
||||
future: Future.delayed(
|
||||
const Duration(seconds: 0)),
|
||||
builder: (context, snapshot) {
|
||||
if (element["summary"] != null &&
|
||||
(element["summary"] as String)
|
||||
.isNotEmpty) {
|
||||
return ListTile(
|
||||
title: Text(element["title"],
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold)),
|
||||
subtitle: Text(
|
||||
element["summary"],
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
),
|
||||
trailing: Text(
|
||||
"${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"),
|
||||
);
|
||||
} else {
|
||||
return ListTile(
|
||||
title: Text(element["title"],
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold)),
|
||||
trailing: Text(
|
||||
"${DateTime.parse(element["published_at"]).day.toString()}.${DateTime.parse(element["published_at"]).month.toString()}.${DateTime.parse(element["published_at"]).year.toString()}"),
|
||||
);
|
||||
}
|
||||
},
|
||||
)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
)),
|
||||
);
|
||||
} else {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => Article.fromData(element["title"], element["content"], element["author"], element["published_at"]).widget),
|
||||
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)),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold)),
|
||||
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(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const LinearProgressIndicator();
|
||||
}
|
||||
},
|
||||
);
|
||||
articleTiles.add(card);
|
||||
}
|
||||
@ -55,14 +176,11 @@ class SZ extends StatelessWidget {
|
||||
children: articleTiles.reversed.toList(),
|
||||
);
|
||||
} else {
|
||||
return(const Center(
|
||||
child: Text("Uups... Irgendwas ist schief gelaufen")
|
||||
));
|
||||
return (const Center(
|
||||
child: Text("Uups... Irgendwas ist schief gelaufen")));
|
||||
}
|
||||
} else {
|
||||
return(const Center(
|
||||
child: CircularProgressIndicator()
|
||||
));
|
||||
return (const Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -74,20 +192,20 @@ 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(
|
||||
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)
|
||||
),
|
||||
scrollDirection: Axis.horizontal, child: Text(title)),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 20),
|
||||
padding: const EdgeInsets.fromLTRB(15, 15, 15, 15),
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(MdiIcons.accountOutline),
|
||||
@ -95,12 +213,11 @@ class Article {
|
||||
),
|
||||
ListTile(
|
||||
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)
|
||||
],
|
||||
)
|
||||
)
|
||||
);
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
import 'package:MeinCantor/presets/teachers.dart';
|
||||
import 'package:MeinCantor/presets/subjects.dart';
|
||||
import 'package:MeinCantor/presets/colors.dart';
|
||||
import 'package:meincantor/presets/teachers.dart';
|
||||
import 'package:meincantor/presets/subjects.dart';
|
||||
import 'package:meincantor/presets/colors.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'Settings/Pages/plan_settings.dart';
|
||||
|
||||
class ClassTimetableBuilder {
|
||||
final RefreshIndicator view;
|
||||
ClassTimetableBuilder({required this.view});
|
||||
@ -61,8 +63,7 @@ class ClassTimetableBuilder {
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: list,
|
||||
),
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,6 +105,11 @@ class LessonsListBuilder {
|
||||
style: TextStyle(color: element.fontColor))));
|
||||
}
|
||||
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,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
@ -116,10 +122,18 @@ class LessonsListBuilder {
|
||||
children: cardChildren,
|
||||
));
|
||||
} else {
|
||||
return (const Center(child: CircularProgressIndicator()));
|
||||
return (const Center(
|
||||
child: CircularProgressIndicator()));
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
} else {
|
||||
return const LinearProgressIndicator();
|
||||
}
|
||||
});
|
||||
children.add(card);
|
||||
}
|
||||
}
|
||||
@ -187,6 +201,7 @@ class ClassTimetable {
|
||||
|
||||
lessons.add(TimetableLesson(
|
||||
value['St'],
|
||||
value["Nr"],
|
||||
subjects[subject] ?? subject.toString(),
|
||||
teachers[teacher] ?? teacher.toString(),
|
||||
room.toString(),
|
||||
@ -202,6 +217,7 @@ class ClassTimetable {
|
||||
|
||||
class TimetableLesson {
|
||||
final int count;
|
||||
final int id;
|
||||
final String name;
|
||||
final String teacher;
|
||||
final String room;
|
||||
@ -209,6 +225,6 @@ class TimetableLesson {
|
||||
final Future<Color> color;
|
||||
final Color fontColor;
|
||||
final String info;
|
||||
const TimetableLesson(this.count, this.name, this.teacher, this.room,
|
||||
const TimetableLesson(this.count, this.id, this.name, this.teacher, this.room,
|
||||
this.comment, this.color, this.fontColor, this.info);
|
||||
}
|
||||
|
14
pubspec.yaml
14
pubspec.yaml
@ -1,4 +1,4 @@
|
||||
name: MeinCantor
|
||||
name: meincantor
|
||||
description: Die Schulplatform für Cantorianer.
|
||||
|
||||
# The following line prevents the package from being accidentally published to
|
||||
@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 0.7.5-beta1.nightly2021-11-16
|
||||
version: 0.8.0-dev
|
||||
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
@ -37,11 +37,17 @@ dependencies:
|
||||
google_fonts: ^2.1.0
|
||||
time: ^2.0.0
|
||||
flutter_launcher_icons: ^0.9.1
|
||||
fluttertoast: ^8.0.8
|
||||
flutter_colorpicker: ^0.6.0
|
||||
material_design_icons_flutter: ^5.0.5955-rc.1
|
||||
cyclop: ^0.5.2
|
||||
flutter_markdown: ^0.6.8
|
||||
flutter_cache_manager: ^3.2.0
|
||||
intl: ^0.17.0
|
||||
url_launcher: ^6.0.17
|
||||
flutter_linkify: ^5.0.2
|
||||
flutter_svg: ^1.0.0
|
||||
webview_flutter: ^3.0.0
|
||||
flutter_local_notifications: ^10.0.0-dev.1
|
||||
background_fetch: ^1.0.3
|
||||
|
||||
flutter_icons:
|
||||
# image_path: "assets/images/icon-128x128.png"
|
||||
|
@ -3,8 +3,8 @@
|
||||
"short_name": "MeinCantor",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#0175C2",
|
||||
"theme_color": "#0175C2",
|
||||
"background_color": "#1a1a37",
|
||||
"theme_color": "#ffbc3b",
|
||||
"description": "Die Schulplatform für Cantorianer.",
|
||||
"orientation": "portrait-primary",
|
||||
"prefer_related_applications": false,
|
||||
|
Reference in New Issue
Block a user