Compare commits
16 Commits
9ad0f624de
...
develop-fi
Author | SHA1 | Date | |
---|---|---|---|
f3de0bc165 | |||
8000475c1f | |||
da772f17cc | |||
9d0cf8e313 | |||
f0009dad88 | |||
905206f242 | |||
769fbc1b6a | |||
8daf57bcee | |||
33d488946a | |||
41a5e021c5 | |||
8f58893553 | |||
626d3d5564 | |||
d833cdb733 | |||
8868914a76 | |||
70e6f82b10 | |||
6651613331 |
@@ -28,6 +28,7 @@ android {
|
|||||||
ndkVersion "27.0.12077973"
|
ndkVersion "27.0.12077973"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
@@ -64,5 +65,5 @@ flutter {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.android.support:multidex:2.0.1'
|
implementation 'com.android.support:multidex:2.0.1'
|
||||||
implementation 'com.android.tools:desugar_jdk_libs:2.0.3'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||||
}
|
}
|
||||||
|
0
android/app/proguard-rules.pro
vendored
Normal file
0
android/app/proguard-rules.pro
vendored
Normal file
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
@@ -19,7 +19,7 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version '8.1.4' apply false
|
id "com.android.application" version '8.7.3' apply false
|
||||||
id "org.jetbrains.kotlin.android" version "1.8.10" apply false
|
id "org.jetbrains.kotlin.android" version "1.8.10" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,5 +5,5 @@ abstract class ApiResponse {
|
|||||||
late http.Response rawResponse;
|
late http.Response rawResponse;
|
||||||
|
|
||||||
@JsonKey(includeIfNull: false)
|
@JsonKey(includeIfNull: false)
|
||||||
late Map<String, String>? headers;
|
Map<String, String>? headers;
|
||||||
}
|
}
|
||||||
|
@@ -110,6 +110,7 @@ enum GetRoomResponseObjectConversationType {
|
|||||||
@JsonValue(3) public,
|
@JsonValue(3) public,
|
||||||
@JsonValue(4) changelog,
|
@JsonValue(4) changelog,
|
||||||
@JsonValue(5) deleted,
|
@JsonValue(5) deleted,
|
||||||
|
@JsonValue(6) noteToSelf,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum GetRoomResponseObjectParticipantNotificationLevel {
|
enum GetRoomResponseObjectParticipantNotificationLevel {
|
||||||
|
@@ -102,6 +102,7 @@ const _$GetRoomResponseObjectConversationTypeEnumMap = {
|
|||||||
GetRoomResponseObjectConversationType.public: 3,
|
GetRoomResponseObjectConversationType.public: 3,
|
||||||
GetRoomResponseObjectConversationType.changelog: 4,
|
GetRoomResponseObjectConversationType.changelog: 4,
|
||||||
GetRoomResponseObjectConversationType.deleted: 5,
|
GetRoomResponseObjectConversationType.deleted: 5,
|
||||||
|
GetRoomResponseObjectConversationType.noteToSelf: 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
const _$GetRoomResponseObjectParticipantNotificationLevelEnumMap = {
|
const _$GetRoomResponseObjectParticipantNotificationLevelEnumMap = {
|
||||||
|
@@ -58,11 +58,9 @@ abstract class TalkApi<T extends ApiResponse?> extends ApiRequest {
|
|||||||
assembled?.headers = data.headers;
|
assembled?.headers = data.headers;
|
||||||
return assembled;
|
return assembled;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO report error
|
var message = 'Error assembling Talk API ${T.toString()} message: ${e.toString()} response with request body: $body and request headers: ${headers.toString()}';
|
||||||
log('Error assembling Talk API ${T.toString()} message: ${e.toString()} response on ${endpoint.path} with request body: $body and request headers: ${headers.toString()}');
|
log(message);
|
||||||
|
throw Exception(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw Exception('Error assembling Talk API response');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@ import 'dart:convert';
|
|||||||
import 'package:localstore/localstore.dart';
|
import 'package:localstore/localstore.dart';
|
||||||
|
|
||||||
import 'apiResponse.dart';
|
import 'apiResponse.dart';
|
||||||
import 'webuntis/webuntisError.dart';
|
|
||||||
|
|
||||||
abstract class RequestCache<T extends ApiResponse?> {
|
abstract class RequestCache<T extends ApiResponse?> {
|
||||||
static const int cacheNothing = 0;
|
static const int cacheNothing = 0;
|
||||||
@@ -40,7 +39,7 @@ abstract class RequestCache<T extends ApiResponse?> {
|
|||||||
'json': jsonEncode(newValue),
|
'json': jsonEncode(newValue),
|
||||||
'lastupdate': DateTime.now().millisecondsSinceEpoch
|
'lastupdate': DateTime.now().millisecondsSinceEpoch
|
||||||
});
|
});
|
||||||
} on WebuntisError catch(e) {
|
} on Exception catch(e) {
|
||||||
onError(e);
|
onError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
29
lib/app.dart
29
lib/app.dart
@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'state/app/modules/app_modules.dart';
|
import 'state/app/modules/app_modules.dart';
|
||||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:badges/badges.dart' as badges;
|
|
||||||
|
|
||||||
import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
||||||
import 'api/mhsl/server/userIndex/update/updateUserindex.dart';
|
import 'api/mhsl/server/userIndex/update/updateUserindex.dart';
|
||||||
@@ -93,7 +92,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => PersistentTabView(
|
Widget build(BuildContext context) => Consumer<SettingsProvider>(builder: (context, settings, child) => PersistentTabView(
|
||||||
controller: Main.bottomNavigator,
|
controller: Main.bottomNavigator,
|
||||||
navBarOverlap: const NavBarOverlap.none(),
|
navBarOverlap: const NavBarOverlap.none(),
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
@@ -101,29 +100,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
screenTransitionAnimation: const ScreenTransitionAnimation(curve: Curves.easeOutQuad, duration: Duration(milliseconds: 200)),
|
screenTransitionAnimation: const ScreenTransitionAnimation(curve: Curves.easeOutQuad, duration: Duration(milliseconds: 200)),
|
||||||
tabs: [
|
tabs: [
|
||||||
AppModule.getModule(Modules.timetable).toBottomTab(context),
|
...AppModule.getBottomBarModules(context).map((e) => e.toBottomTab(context)),
|
||||||
AppModule.getModule(Modules.talk).toBottomTab(
|
|
||||||
context,
|
|
||||||
itemBuilder: (icon) => Consumer<ChatListProps>(
|
|
||||||
builder: (context, value, child) {
|
|
||||||
if(value.primaryLoading()) return Icon(icon);
|
|
||||||
var messages = value.getRoomsResponse.data.map((e) => e.unreadMessages).reduce((a, b) => a+b);
|
|
||||||
return badges.Badge(
|
|
||||||
showBadge: messages > 0,
|
|
||||||
position: badges.BadgePosition.topEnd(top: -3, end: -3),
|
|
||||||
stackFit: StackFit.loose,
|
|
||||||
badgeStyle: badges.BadgeStyle(
|
|
||||||
padding: const EdgeInsets.all(3),
|
|
||||||
badgeColor: Theme.of(context).primaryColor,
|
|
||||||
elevation: 1,
|
|
||||||
),
|
|
||||||
badgeContent: Text('$messages', style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
|
|
||||||
child: Icon(icon),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AppModule.getModule(Modules.files).toBottomTab(context),
|
|
||||||
|
|
||||||
PersistentTabConfig(
|
PersistentTabConfig(
|
||||||
screen: const Breaker(breaker: BreakerArea.more, child: Overhang()),
|
screen: const Breaker(breaker: BreakerArea.more, child: Overhang()),
|
||||||
@@ -142,7 +119,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
@@ -23,7 +23,11 @@ class _BreakerState extends State<Breaker> {
|
|||||||
builder: (context, value, child) {
|
builder: (context, value, child) {
|
||||||
var blocked = value.isBlocked(widget.breaker);
|
var blocked = value.isBlocked(widget.breaker);
|
||||||
if(blocked != null) {
|
if(blocked != null) {
|
||||||
return PlaceholderView(icon: Icons.security_outlined, text: "Die App/ Dieser Bereich wurde als Schutzmaßnahme deaktiviert!\n\n${blocked.isEmpty ? "Es wurde vom Server kein Grund übermittelt." : blocked}");
|
return PlaceholderView(
|
||||||
|
icon: Icons.app_blocking_outlined,
|
||||||
|
text: 'Die App / Dieser Bereich ist zurzeit nicht verfügbar!\n\n'
|
||||||
|
"${blocked.isEmpty ? "Es wurde vom Server kein Grund übermittelt.\nAktualisiere die App und versuche es später erneut" : blocked}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return widget.child;
|
return widget.child;
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
import '../../api/apiResponse.dart';
|
import '../../api/apiResponse.dart';
|
||||||
@@ -12,6 +13,8 @@ class BreakerProps extends DataHolder {
|
|||||||
PackageInfo? packageInfo;
|
PackageInfo? packageInfo;
|
||||||
|
|
||||||
String? isBlocked(BreakerArea? type) {
|
String? isBlocked(BreakerArea? type) {
|
||||||
|
if(kDebugMode) return null;
|
||||||
|
|
||||||
if(packageInfo == null) {
|
if(packageInfo == null) {
|
||||||
PackageInfo.fromPlatform().then((value) => packageInfo = value);
|
PackageInfo.fromPlatform().then((value) => packageInfo = value);
|
||||||
return null;
|
return null;
|
||||||
|
@@ -44,7 +44,7 @@ class NotificationController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> onAppOpenedByNotification(RemoteMessage message, BuildContext context) async {
|
static Future<void> onAppOpenedByNotification(RemoteMessage message, BuildContext context) async {
|
||||||
NotificationTasks.navigateToTalk();
|
NotificationTasks.navigateToTalk(context);
|
||||||
NotificationTasks.updateProviders(context);
|
NotificationTasks.updateProviders(context);
|
||||||
|
|
||||||
DebugTile(context).run(() {
|
DebugTile(context).run(() {
|
||||||
|
@@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
|
|||||||
import '../main.dart';
|
import '../main.dart';
|
||||||
import '../model/chatList/chatListProps.dart';
|
import '../model/chatList/chatListProps.dart';
|
||||||
import '../model/chatList/chatProps.dart';
|
import '../model/chatList/chatProps.dart';
|
||||||
|
import '../state/app/modules/app_modules.dart';
|
||||||
|
|
||||||
class NotificationTasks {
|
class NotificationTasks {
|
||||||
static void updateBadgeCount(RemoteMessage notification) {
|
static void updateBadgeCount(RemoteMessage notification) {
|
||||||
@@ -17,7 +18,9 @@ class NotificationTasks {
|
|||||||
Provider.of<ChatProps>(context, listen: false).run();
|
Provider.of<ChatProps>(context, listen: false).run();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void navigateToTalk() {
|
static void navigateToTalk(BuildContext context) {
|
||||||
Main.bottomNavigator.jumpToTab(1);
|
var talkTab = AppModule.getBottomBarModules(context).map((e) => e.module).toList().indexOf(Modules.talk);
|
||||||
|
if(talkTab == -1) return;
|
||||||
|
Main.bottomNavigator.jumpToTab(talkTab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../../../api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
import '../../../api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
||||||
import '../../../model/breakers/Breaker.dart';
|
import '../../../model/breakers/Breaker.dart';
|
||||||
|
import '../../../model/chatList/chatListProps.dart';
|
||||||
|
import '../../../storage/base/settingsProvider.dart';
|
||||||
import '../../../view/pages/files/files.dart';
|
import '../../../view/pages/files/files.dart';
|
||||||
import '../../../view/pages/more/roomplan/roomplan.dart';
|
import '../../../view/pages/more/roomplan/roomplan.dart';
|
||||||
import '../../../view/pages/talk/chatList.dart';
|
import '../../../view/pages/talk/chatList.dart';
|
||||||
@@ -12,38 +15,115 @@ import 'gradeAverages/view/grade_averages_view.dart';
|
|||||||
import 'holidays/view/holidays_view.dart';
|
import 'holidays/view/holidays_view.dart';
|
||||||
import 'marianumMessage/view/marianum_message_list_view.dart';
|
import 'marianumMessage/view/marianum_message_list_view.dart';
|
||||||
|
|
||||||
|
import 'package:badges/badges.dart' as badges;
|
||||||
|
|
||||||
class AppModule {
|
class AppModule {
|
||||||
|
Modules module;
|
||||||
String name;
|
String name;
|
||||||
IconData icon;
|
Widget Function() icon;
|
||||||
|
BreakerArea breakerArea;
|
||||||
Widget Function() create;
|
Widget Function() create;
|
||||||
|
|
||||||
AppModule(this.name, this.icon, this.create);
|
AppModule(this.module, {required this.name, required this.icon, this.breakerArea = BreakerArea.global, required this.create});
|
||||||
|
|
||||||
static Map<Modules, AppModule> modules() => {
|
static Map<Modules, AppModule> modules(BuildContext context, { showFiltered = false }) {
|
||||||
Modules.timetable: AppModule('Vertretung', Icons.calendar_month, Timetable.new),
|
var settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||||
Modules.talk: AppModule('Talk', Icons.chat, ChatList.new),
|
var available = {
|
||||||
Modules.files: AppModule('Files', Icons.folder, Files.new),
|
Modules.timetable: AppModule(
|
||||||
Modules.marianumMessage: AppModule('Marianum Message', Icons.newspaper, MarianumMessageListView.new),
|
Modules.timetable,
|
||||||
Modules.roomPlan: AppModule('Raumplan', Icons.location_pin, Roomplan.new),
|
name: 'Vertretung',
|
||||||
Modules.gradeAveragesCalculator: AppModule('Notendurschnittsrechner', Icons.calculate, GradeAveragesView.new),
|
icon: () => Icon(Icons.calendar_month),
|
||||||
Modules.holidays: AppModule('Schulferien', Icons.flight, HolidaysView.new),
|
breakerArea: BreakerArea.timetable,
|
||||||
};
|
create: Timetable.new,
|
||||||
|
),
|
||||||
|
Modules.talk: AppModule(
|
||||||
|
Modules.talk,
|
||||||
|
name: 'Talk',
|
||||||
|
icon: () => Consumer<ChatListProps>(
|
||||||
|
builder: (context, value, child) {
|
||||||
|
if(value.primaryLoading()) return Icon(Icons.chat);
|
||||||
|
var messages = value.getRoomsResponse.data.map((e) => e.unreadMessages).reduce((a, b) => a+b);
|
||||||
|
return badges.Badge(
|
||||||
|
showBadge: messages > 0,
|
||||||
|
position: badges.BadgePosition.topEnd(top: -3, end: -3),
|
||||||
|
stackFit: StackFit.loose,
|
||||||
|
badgeStyle: badges.BadgeStyle(
|
||||||
|
padding: const EdgeInsets.all(3),
|
||||||
|
badgeColor: Theme.of(context).primaryColor,
|
||||||
|
elevation: 1,
|
||||||
|
),
|
||||||
|
badgeContent: Text('$messages', style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
|
||||||
|
child: Icon(Icons.chat),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
breakerArea: BreakerArea.talk,
|
||||||
|
create: ChatList.new,
|
||||||
|
),
|
||||||
|
Modules.files: AppModule(
|
||||||
|
Modules.files,
|
||||||
|
name: 'Dateien',
|
||||||
|
icon: () => Icon(Icons.folder),
|
||||||
|
breakerArea: BreakerArea.files,
|
||||||
|
create: Files.new,
|
||||||
|
),
|
||||||
|
Modules.marianumMessage: AppModule(
|
||||||
|
Modules.marianumMessage,
|
||||||
|
name: 'Marianum Message',
|
||||||
|
icon: () => Icon(Icons.newspaper),
|
||||||
|
breakerArea: BreakerArea.more,
|
||||||
|
create: MarianumMessageListView.new,
|
||||||
|
),
|
||||||
|
Modules.roomPlan: AppModule(
|
||||||
|
Modules.roomPlan,
|
||||||
|
name: 'Raumplan',
|
||||||
|
icon: () => Icon(Icons.location_pin),
|
||||||
|
breakerArea: BreakerArea.more,
|
||||||
|
create: Roomplan.new,
|
||||||
|
),
|
||||||
|
Modules.gradeAveragesCalculator: AppModule(
|
||||||
|
Modules.gradeAveragesCalculator,
|
||||||
|
name: 'Notendurschnittsrechner',
|
||||||
|
icon: () => Icon(Icons.calculate),
|
||||||
|
breakerArea: BreakerArea.more,
|
||||||
|
create: GradeAveragesView.new,
|
||||||
|
),
|
||||||
|
Modules.holidays: AppModule(
|
||||||
|
Modules.holidays,
|
||||||
|
name: 'Schulferien',
|
||||||
|
icon: () => Icon(Icons.flight),
|
||||||
|
breakerArea: BreakerArea.more,
|
||||||
|
create: HolidaysView.new,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
static AppModule getModule(Modules module) => modules()[module]!;
|
if(!showFiltered) available.removeWhere((key, value) => settings.val().modulesSettings.hiddenModules.contains(key));
|
||||||
|
|
||||||
Widget toListTile(BuildContext context) => ListTile(
|
return { for (var element in settings.val().modulesSettings.moduleOrder.where((element) => available.containsKey(element))) element : available[element]! };
|
||||||
leading: CenteredLeading(Icon(icon)),
|
}
|
||||||
|
|
||||||
|
static List<AppModule> getBottomBarModules(BuildContext context) => modules(context).values.toList().getRange(0, 3).toList();
|
||||||
|
static List<AppModule> getOverhangModules(BuildContext context) => modules(context).values.skip(3).toList();
|
||||||
|
|
||||||
|
Widget toListTile(BuildContext context, {Key? key, bool isReorder = false, Function()? onVisibleChange, bool isVisible = true}) => ListTile(
|
||||||
|
key: key,
|
||||||
|
leading: CenteredLeading(icon()),
|
||||||
title: Text(name),
|
title: Text(name),
|
||||||
onTap: () => pushScreen(context, withNavBar: false, screen: create()),
|
onTap: isReorder ? null : () => pushScreen(context, withNavBar: false, screen: create()),
|
||||||
trailing: const Icon(Icons.arrow_right),
|
trailing: isReorder
|
||||||
|
? Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
IconButton(onPressed: onVisibleChange, icon: Icon(isVisible ? Icons.visibility_outlined : Icons.visibility_off_outlined)),
|
||||||
|
Icon(Icons.drag_handle_outlined)
|
||||||
|
])
|
||||||
|
: const Icon(Icons.arrow_right),
|
||||||
);
|
);
|
||||||
|
|
||||||
PersistentTabConfig toBottomTab(BuildContext context, {Widget Function(IconData icon)? itemBuilder}) => PersistentTabConfig(
|
PersistentTabConfig toBottomTab(BuildContext context, {Widget Function(IconData icon)? iconBuilder}) => PersistentTabConfig(
|
||||||
screen: Breaker(breaker: BreakerArea.global, child: create()),
|
screen: Breaker(breaker: breakerArea, child: create()),
|
||||||
item: ItemConfig(
|
item: ItemConfig(
|
||||||
activeForegroundColor: Theme.of(context).primaryColor,
|
activeForegroundColor: Theme.of(context).primaryColor,
|
||||||
inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
|
inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
|
||||||
icon: itemBuilder == null ? Icon(icon) : itemBuilder(icon),
|
icon: icon(),
|
||||||
title: name
|
title: name
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@@ -4,6 +4,7 @@ import 'package:json_annotation/json_annotation.dart';
|
|||||||
import '../devTools/devToolsSettings.dart';
|
import '../devTools/devToolsSettings.dart';
|
||||||
import '../file/fileSettings.dart';
|
import '../file/fileSettings.dart';
|
||||||
import '../fileView/fileViewSettings.dart';
|
import '../fileView/fileViewSettings.dart';
|
||||||
|
import '../general/modulesSettings.dart';
|
||||||
import '../holidays/holidaysSettings.dart';
|
import '../holidays/holidaysSettings.dart';
|
||||||
import '../notification/notificationSettings.dart';
|
import '../notification/notificationSettings.dart';
|
||||||
import '../talk/talkSettings.dart';
|
import '../talk/talkSettings.dart';
|
||||||
@@ -20,6 +21,7 @@ class Settings {
|
|||||||
ThemeMode appTheme;
|
ThemeMode appTheme;
|
||||||
bool devToolsEnabled;
|
bool devToolsEnabled;
|
||||||
|
|
||||||
|
ModulesSettings modulesSettings;
|
||||||
TimetableSettings timetableSettings;
|
TimetableSettings timetableSettings;
|
||||||
TalkSettings talkSettings;
|
TalkSettings talkSettings;
|
||||||
FileSettings fileSettings;
|
FileSettings fileSettings;
|
||||||
@@ -31,6 +33,7 @@ class Settings {
|
|||||||
Settings({
|
Settings({
|
||||||
required this.appTheme,
|
required this.appTheme,
|
||||||
required this.devToolsEnabled,
|
required this.devToolsEnabled,
|
||||||
|
required this.modulesSettings,
|
||||||
required this.timetableSettings,
|
required this.timetableSettings,
|
||||||
required this.talkSettings,
|
required this.talkSettings,
|
||||||
required this.fileSettings,
|
required this.fileSettings,
|
||||||
|
@@ -9,6 +9,8 @@ part of 'settings.dart';
|
|||||||
Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings(
|
Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings(
|
||||||
appTheme: Settings._themeFromJson(json['appTheme'] as String),
|
appTheme: Settings._themeFromJson(json['appTheme'] as String),
|
||||||
devToolsEnabled: json['devToolsEnabled'] as bool,
|
devToolsEnabled: json['devToolsEnabled'] as bool,
|
||||||
|
modulesSettings: ModulesSettings.fromJson(
|
||||||
|
json['modulesSettings'] as Map<String, dynamic>),
|
||||||
timetableSettings: TimetableSettings.fromJson(
|
timetableSettings: TimetableSettings.fromJson(
|
||||||
json['timetableSettings'] as Map<String, dynamic>),
|
json['timetableSettings'] as Map<String, dynamic>),
|
||||||
talkSettings:
|
talkSettings:
|
||||||
@@ -28,6 +30,7 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings(
|
|||||||
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
||||||
'appTheme': Settings._themeToJson(instance.appTheme),
|
'appTheme': Settings._themeToJson(instance.appTheme),
|
||||||
'devToolsEnabled': instance.devToolsEnabled,
|
'devToolsEnabled': instance.devToolsEnabled,
|
||||||
|
'modulesSettings': instance.modulesSettings.toJson(),
|
||||||
'timetableSettings': instance.timetableSettings.toJson(),
|
'timetableSettings': instance.timetableSettings.toJson(),
|
||||||
'talkSettings': instance.talkSettings.toJson(),
|
'talkSettings': instance.talkSettings.toJson(),
|
||||||
'fileSettings': instance.fileSettings.toJson(),
|
'fileSettings': instance.fileSettings.toJson(),
|
||||||
|
19
lib/storage/general/modulesSettings.dart
Normal file
19
lib/storage/general/modulesSettings.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
import '../../state/app/modules/app_modules.dart';
|
||||||
|
|
||||||
|
part 'modulesSettings.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class ModulesSettings {
|
||||||
|
List<Modules> moduleOrder;
|
||||||
|
List<Modules> hiddenModules;
|
||||||
|
|
||||||
|
ModulesSettings({
|
||||||
|
required this.moduleOrder,
|
||||||
|
required this.hiddenModules
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ModulesSettings.fromJson(Map<String, dynamic> json) => _$ModulesSettingsFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$ModulesSettingsToJson(this);
|
||||||
|
}
|
35
lib/storage/general/modulesSettings.g.dart
Normal file
35
lib/storage/general/modulesSettings.g.dart
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'modulesSettings.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
ModulesSettings _$ModulesSettingsFromJson(Map<String, dynamic> json) =>
|
||||||
|
ModulesSettings(
|
||||||
|
moduleOrder: (json['moduleOrder'] as List<dynamic>)
|
||||||
|
.map((e) => $enumDecode(_$ModulesEnumMap, e))
|
||||||
|
.toList(),
|
||||||
|
hiddenModules: (json['hiddenModules'] as List<dynamic>)
|
||||||
|
.map((e) => $enumDecode(_$ModulesEnumMap, e))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ModulesSettingsToJson(ModulesSettings instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'moduleOrder':
|
||||||
|
instance.moduleOrder.map((e) => _$ModulesEnumMap[e]!).toList(),
|
||||||
|
'hiddenModules':
|
||||||
|
instance.hiddenModules.map((e) => _$ModulesEnumMap[e]!).toList(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$ModulesEnumMap = {
|
||||||
|
Modules.timetable: 'timetable',
|
||||||
|
Modules.talk: 'talk',
|
||||||
|
Modules.files: 'files',
|
||||||
|
Modules.marianumMessage: 'marianumMessage',
|
||||||
|
Modules.roomPlan: 'roomPlan',
|
||||||
|
Modules.gradeAveragesCalculator: 'gradeAveragesCalculator',
|
||||||
|
Modules.holidays: 'holidays',
|
||||||
|
};
|
@@ -3,76 +3,124 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:in_app_review/in_app_review.dart';
|
import 'package:in_app_review/in_app_review.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import '../../extensions/renderNotNull.dart';
|
import '../../extensions/renderNotNull.dart';
|
||||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||||
|
|
||||||
import '../../state/app/modules/app_modules.dart';
|
import '../../state/app/modules/app_modules.dart';
|
||||||
|
import '../../storage/base/settingsProvider.dart';
|
||||||
import '../../widget/centeredLeading.dart';
|
import '../../widget/centeredLeading.dart';
|
||||||
import '../../widget/infoDialog.dart';
|
import '../../widget/infoDialog.dart';
|
||||||
|
import '../settings/defaultSettings.dart';
|
||||||
import '../settings/settings.dart';
|
import '../settings/settings.dart';
|
||||||
import 'more/feedback/feedbackDialog.dart';
|
import 'more/feedback/feedbackDialog.dart';
|
||||||
import 'more/share/selectShareTypeDialog.dart';
|
import 'more/share/selectShareTypeDialog.dart';
|
||||||
|
|
||||||
class Overhang extends StatelessWidget {
|
class Overhang extends StatefulWidget {
|
||||||
const Overhang({super.key});
|
const Overhang({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Scaffold(
|
State<Overhang> createState() => _OverhangState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OverhangState extends State<Overhang> {
|
||||||
|
bool editMode = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Consumer<SettingsProvider>(builder: (context, settings, child) => Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Mehr'),
|
title: const Text('Mehr'),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(onPressed: () => pushScreen(context, screen: const Settings(), withNavBar: false), icon: const Icon(Icons.settings))
|
if(editMode) IconButton(
|
||||||
|
onPressed: settings.val().modulesSettings.toJson().toString() != DefaultSettings.get().modulesSettings.toJson().toString()
|
||||||
|
? () => settings.val(write: true).modulesSettings = DefaultSettings.get().modulesSettings
|
||||||
|
: null,
|
||||||
|
icon: Icon(Icons.undo_outlined)
|
||||||
|
),
|
||||||
|
IconButton(onPressed: () => setState(() => editMode = !editMode), icon: Icon(Icons.edit_note_outlined), color: editMode ? Theme.of(context).primaryColor : null),
|
||||||
|
IconButton(onPressed: editMode ? null : () => pushScreen(context, screen: const Settings(), withNavBar: false), icon: const Icon(Icons.settings)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: editMode ? _sorting() : _overhang(),
|
||||||
children: [
|
));
|
||||||
AppModule.getModule(Modules.marianumMessage).toListTile(context),
|
|
||||||
AppModule.getModule(Modules.roomPlan).toListTile(context),
|
|
||||||
AppModule.getModule(Modules.gradeAveragesCalculator).toListTile(context),
|
|
||||||
AppModule.getModule(Modules.holidays).toListTile(context),
|
|
||||||
|
|
||||||
const Divider(),
|
Widget _sorting() => Consumer<SettingsProvider>(builder: (context, settings, child) {
|
||||||
|
void changeVisibility(Modules module) {
|
||||||
|
var hidden = settings.val(write: true).modulesSettings.hiddenModules;
|
||||||
|
hidden.contains(module) ? hidden.remove(module) : (hidden.length < 3 ? hidden.add(module) : null);
|
||||||
|
}
|
||||||
|
|
||||||
ListTile(
|
return ReorderableListView(
|
||||||
|
header: const Center(
|
||||||
|
heightFactor: 2,
|
||||||
|
child: Text('Halte und ziehe einen Eintrag, um ihn zu verschieben.\nEs können 3 Bereiche ausgeblendet werden.', textAlign: TextAlign.center)
|
||||||
|
),
|
||||||
|
children: AppModule.modules(context, showFiltered: true)
|
||||||
|
.map((key, value) => MapEntry(key, value.toListTile(
|
||||||
|
context,
|
||||||
|
key: Key(key.name),
|
||||||
|
isReorder: true,
|
||||||
|
onVisibleChange: () => changeVisibility(key),
|
||||||
|
isVisible: !settings.val().modulesSettings.hiddenModules.contains(key)
|
||||||
|
)))
|
||||||
|
.values
|
||||||
|
.toList(),
|
||||||
|
onReorder: (oldIndex, newIndex) {
|
||||||
|
if (newIndex > oldIndex) newIndex -= 1;
|
||||||
|
|
||||||
|
var order = settings.val().modulesSettings.moduleOrder.toList();
|
||||||
|
final movedModule = order.removeAt(oldIndex);
|
||||||
|
order.insert(newIndex, movedModule);
|
||||||
|
settings.val(write: true).modulesSettings.moduleOrder = order;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Widget _overhang() => ListView(
|
||||||
|
children: [
|
||||||
|
...AppModule.getOverhangModules(context).map((e) => e.toListTile(context)),
|
||||||
|
|
||||||
|
const Divider(),
|
||||||
|
|
||||||
|
ListTile(
|
||||||
leading: const Icon(Icons.share_outlined),
|
leading: const Icon(Icons.share_outlined),
|
||||||
title: const Text('Teile die App'),
|
title: const Text('Teile die App'),
|
||||||
subtitle: const Text('Mit Freunden und deiner Klasse teilen'),
|
subtitle: const Text('Mit Freunden und deiner Klasse teilen'),
|
||||||
trailing: const Icon(Icons.arrow_right),
|
trailing: const Icon(Icons.arrow_right),
|
||||||
onTap: () => showDialog(context: context, builder: (context) => const SelectShareTypeDialog())
|
onTap: () => showDialog(context: context, builder: (context) => const SelectShareTypeDialog())
|
||||||
),
|
),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: InAppReview.instance.isAvailable(),
|
future: InAppReview.instance.isAvailable(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if(!snapshot.hasData) return const SizedBox.shrink();
|
if(!snapshot.hasData) return const SizedBox.shrink();
|
||||||
|
|
||||||
String? getPlatformStoreName() {
|
String? getPlatformStoreName() {
|
||||||
if(Platform.isAndroid) return 'Play store';
|
if(Platform.isAndroid) return 'Play store';
|
||||||
if(Platform.isIOS) return 'App store';
|
if(Platform.isIOS) return 'App store';
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: const CenteredLeading(Icon(Icons.star_rate_outlined)),
|
leading: const CenteredLeading(Icon(Icons.star_rate_outlined)),
|
||||||
title: const Text('App bewerten'),
|
title: const Text('App bewerten'),
|
||||||
subtitle: getPlatformStoreName().wrapNullable((data) => Text('Im $data')),
|
subtitle: getPlatformStoreName().wrapNullable((data) => Text('Im $data')),
|
||||||
trailing: const Icon(Icons.arrow_right),
|
trailing: const Icon(Icons.arrow_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
InAppReview.instance.openStoreListing(appStoreId: '6458789560').then(
|
InAppReview.instance.openStoreListing(appStoreId: '6458789560').then(
|
||||||
(value) => InfoDialog.show(context, 'Vielen Dank!'),
|
(value) => InfoDialog.show(context, 'Vielen Dank!'),
|
||||||
onError: (error) => InfoDialog.show(context, error.toString())
|
onError: (error) => InfoDialog.show(context, error.toString())
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const CenteredLeading(Icon(Icons.feedback_outlined)),
|
leading: const CenteredLeading(Icon(Icons.feedback_outlined)),
|
||||||
title: const Text('Du hast eine Idee?'),
|
title: const Text('Du hast eine Idee?'),
|
||||||
subtitle: const Text('Fehler und Verbessungsvorschläge'),
|
subtitle: const Text('Fehler und Verbessungsvorschläge'),
|
||||||
trailing: const Icon(Icons.arrow_right),
|
trailing: const Icon(Icons.arrow_right),
|
||||||
onTap: () => pushScreen(context, withNavBar: false, screen: const FeedbackDialog()),
|
onTap: () => pushScreen(context, withNavBar: false, screen: const FeedbackDialog()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -206,7 +206,7 @@ class _ChatBubbleState extends State<ChatBubble> with SingleTickerProviderStateM
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: !message.containsFile,
|
visible: widget.bubbleData.message != '{file}',
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: const Icon(Icons.copy),
|
leading: const Icon(Icons.copy),
|
||||||
title: const Text('Nachricht kopieren'),
|
title: const Text('Nachricht kopieren'),
|
||||||
@@ -282,14 +282,15 @@ class _ChatBubbleState extends State<ChatBubble> with SingleTickerProviderStateM
|
|||||||
if(!widget.bubbleData.isReplyable) return;
|
if(!widget.bubbleData.isReplyable) return;
|
||||||
var dx = details.delta.dx - _dragStartPosition.dx;
|
var dx = details.delta.dx - _dragStartPosition.dx;
|
||||||
setState(() {
|
setState(() {
|
||||||
_position = (_position.dx + dx).abs() > 30 ? Offset(_position.dx, 0) : Offset(_position.dx + dx, 0);
|
_position = (_position.dx + dx).abs() > 60 ? Offset(_position.dx, 0) : Offset(_position.dx + dx, 0);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onHorizontalDragEnd: (DragEndDetails details) {
|
onHorizontalDragEnd: (DragEndDetails details) {
|
||||||
|
var isAction = _position.dx.abs() > 50;
|
||||||
setState(() {
|
setState(() {
|
||||||
_position = const Offset(0, 0);
|
_position = const Offset(0, 0);
|
||||||
});
|
});
|
||||||
if(widget.bubbleData.isReplyable) {
|
if(widget.bubbleData.isReplyable && isAction) {
|
||||||
Provider.of<ChatProps>(context, listen: false).setReferenceMessageId(widget.bubbleData.id, context, widget.chatData.token);
|
Provider.of<ChatProps>(context, listen: false).setReferenceMessageId(widget.bubbleData.id, context, widget.chatData.token);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -21,38 +21,52 @@ class ChatMessage {
|
|||||||
ChatMessage({required this.originalMessage, this.originalData}) {
|
ChatMessage({required this.originalMessage, this.originalData}) {
|
||||||
if(originalData?.containsKey('file') ?? false) {
|
if(originalData?.containsKey('file') ?? false) {
|
||||||
file = originalData?['file'];
|
file = originalData?['file'];
|
||||||
content = file?.name ?? 'Datei';
|
|
||||||
} else {
|
|
||||||
content = RichObjectStringProcessor.parseToString(originalMessage, originalData);
|
|
||||||
}
|
}
|
||||||
|
content = RichObjectStringProcessor.parseToString(originalMessage, originalData);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget getWidget() {
|
Widget getWidget() {
|
||||||
|
|
||||||
if(file == null) {
|
var contentWidget = Linkify(
|
||||||
return Linkify(
|
text: content,
|
||||||
text: content,
|
onOpen: onOpen,
|
||||||
onOpen: onOpen,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return CachedNetworkImage(
|
if(file == null) return contentWidget;
|
||||||
errorWidget: (context, url, error) => Padding(padding: const EdgeInsets.only(top: 10), child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
return Padding(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
padding: const EdgeInsets.only(top: 5),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.file_open_outlined, size: 35),
|
Row(
|
||||||
const SizedBox(width: 10),
|
mainAxisSize: MainAxisSize.max,
|
||||||
Flexible(child: Text(file!.name, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontWeight: FontWeight.bold))),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
const SizedBox(width: 10),
|
children: [
|
||||||
|
CachedNetworkImage(
|
||||||
|
errorWidget: (context, url, error) => Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.file_open_outlined, size: 35),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Flexible(child: Text(file!.name, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontWeight: FontWeight.bold))),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
placeholder: (context, url) => const Padding(padding: EdgeInsets.all(15), child: SizedBox(width: 50, child: LinearProgressIndicator())),
|
||||||
|
fadeInDuration: Duration.zero,
|
||||||
|
fadeOutDuration: Duration.zero,
|
||||||
|
errorListener: (value) {},
|
||||||
|
imageUrl: 'https://${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().full()}/index.php/core/preview?fileId=${file!.id}&x=100&y=-1&a=1',
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
contentWidget
|
||||||
],
|
],
|
||||||
)),
|
)
|
||||||
alignment: Alignment.center,
|
|
||||||
placeholder: (context, url) => const Padding(padding: EdgeInsets.all(10), child: CircularProgressIndicator()),
|
|
||||||
fadeInDuration: Duration.zero,
|
|
||||||
fadeOutDuration: Duration.zero,
|
|
||||||
errorListener: (value) {},
|
|
||||||
imageUrl: 'https://${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().full()}/index.php/core/preview?fileId=${file!.id}&x=100&y=-1&a=1',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -123,6 +123,7 @@ class _TimetableState extends State<Timetable> {
|
|||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
child: SfCalendar(
|
child: SfCalendar(
|
||||||
|
timeZone: 'W. Europe Standard Time',
|
||||||
view: CalendarView.workWeek,
|
view: CalendarView.workWeek,
|
||||||
dataSource: _buildTableEvents(value),
|
dataSource: _buildTableEvents(value),
|
||||||
|
|
||||||
@@ -367,6 +368,9 @@ class _TimetableState extends State<Timetable> {
|
|||||||
// Any changes or no teacher at this element
|
// Any changes or no teacher at this element
|
||||||
if(webuntisElement.code == 'irregular' || webuntisElement.te.first.id == 0) return const Color(0xff8F19B3).withAlpha(alpha);
|
if(webuntisElement.code == 'irregular' || webuntisElement.te.first.id == 0) return const Color(0xff8F19B3).withAlpha(alpha);
|
||||||
|
|
||||||
|
// Teacher has changed
|
||||||
|
if(webuntisElement.te.any((element) => element.orgname != null)) return const Color(0xFF29639B).withAlpha(alpha);
|
||||||
|
|
||||||
// Event was in the past
|
// Event was in the past
|
||||||
if(endTime.isBefore(DateTime.now())) return Theme.of(context).primaryColor.withAlpha(alpha);
|
if(endTime.isBefore(DateTime.now())) return Theme.of(context).primaryColor.withAlpha(alpha);
|
||||||
|
|
||||||
|
@@ -2,10 +2,12 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../state/app/modules/app_modules.dart';
|
||||||
import '../../storage/base/settings.dart';
|
import '../../storage/base/settings.dart';
|
||||||
import '../../storage/devTools/devToolsSettings.dart';
|
import '../../storage/devTools/devToolsSettings.dart';
|
||||||
import '../../storage/file/fileSettings.dart';
|
import '../../storage/file/fileSettings.dart';
|
||||||
import '../../storage/fileView/fileViewSettings.dart';
|
import '../../storage/fileView/fileViewSettings.dart';
|
||||||
|
import '../../storage/general/modulesSettings.dart';
|
||||||
import '../../storage/holidays/holidaysSettings.dart';
|
import '../../storage/holidays/holidaysSettings.dart';
|
||||||
import '../../storage/notification/notificationSettings.dart';
|
import '../../storage/notification/notificationSettings.dart';
|
||||||
import '../../storage/talk/talkSettings.dart';
|
import '../../storage/talk/talkSettings.dart';
|
||||||
@@ -17,6 +19,18 @@ class DefaultSettings {
|
|||||||
static Settings get() => Settings(
|
static Settings get() => Settings(
|
||||||
appTheme: ThemeMode.system,
|
appTheme: ThemeMode.system,
|
||||||
devToolsEnabled: false,
|
devToolsEnabled: false,
|
||||||
|
modulesSettings: ModulesSettings(
|
||||||
|
moduleOrder: [
|
||||||
|
Modules.timetable,
|
||||||
|
Modules.talk,
|
||||||
|
Modules.files,
|
||||||
|
Modules.marianumMessage,
|
||||||
|
Modules.roomPlan,
|
||||||
|
Modules.gradeAveragesCalculator,
|
||||||
|
Modules.holidays
|
||||||
|
],
|
||||||
|
hiddenModules: [],
|
||||||
|
),
|
||||||
timetableSettings: TimetableSettings(
|
timetableSettings: TimetableSettings(
|
||||||
connectDoubleLessons: false,
|
connectDoubleLessons: false,
|
||||||
timetableNameMode: TimetableNameMode.name
|
timetableNameMode: TimetableNameMode.name
|
||||||
|
@@ -10,15 +10,15 @@ import '../../widget/confirmDialog.dart';
|
|||||||
import '../../widget/debug/cacheView.dart';
|
import '../../widget/debug/cacheView.dart';
|
||||||
import '../../widget/debug/jsonViewer.dart';
|
import '../../widget/debug/jsonViewer.dart';
|
||||||
|
|
||||||
class DevToolsSettingsDialog extends StatefulWidget {
|
class DevToolsSettings extends StatefulWidget {
|
||||||
final SettingsProvider settings;
|
final SettingsProvider settings;
|
||||||
const DevToolsSettingsDialog({required this.settings, super.key});
|
const DevToolsSettings({required this.settings, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DevToolsSettingsDialog> createState() => _DevToolsSettingsDialogState();
|
State<DevToolsSettings> createState() => _DevToolsSettingsState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
|
class _DevToolsSettingsState extends State<DevToolsSettings> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Column(
|
Widget build(BuildContext context) => Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -59,9 +59,9 @@ class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const CenteredLeading(Icon(Icons.image_outlined)),
|
leading: const CenteredLeading(Icon(Icons.image_outlined)),
|
||||||
title: const Text('Cached Thumbnails löschen'),
|
title: const Text('Thumb-storage'),
|
||||||
subtitle: Text('etwa ${filesize(PaintingBinding.instance.imageCache.currentSizeBytes)}'),
|
subtitle: Text('etwa ${filesize(PaintingBinding.instance.imageCache.currentSizeBytes)}\nLange tippen um zu löschen'),
|
||||||
onTap: () {
|
onLongPress: () {
|
||||||
ConfirmDialog(
|
ConfirmDialog(
|
||||||
title: 'Thumbs cache löschen',
|
title: 'Thumbs cache löschen',
|
||||||
content: 'Alle zwischengespeicherten Bilder werden gelöscht.',
|
content: 'Alle zwischengespeicherten Bilder werden gelöscht.',
|
||||||
@@ -69,7 +69,6 @@ class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
|
|||||||
onConfirm: () => PaintingBinding.instance.imageCache.clear(),
|
onConfirm: () => PaintingBinding.instance.imageCache.clear(),
|
||||||
).asDialog(context);
|
).asDialog(context);
|
||||||
},
|
},
|
||||||
trailing: const Icon(Icons.arrow_right),
|
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const CenteredLeading(Icon(Icons.settings_applications_outlined)),
|
leading: const CenteredLeading(Icon(Icons.settings_applications_outlined)),
|
||||||
@@ -80,7 +79,7 @@ class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
|
|||||||
},
|
},
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
ConfirmDialog(
|
ConfirmDialog(
|
||||||
title: 'App-Speicher löschen',
|
title: 'Einstellungen löschen',
|
||||||
content: 'Alle Einstellungen gehen verloren! Accountdaten sowie App-Daten sind nicht betroffen.',
|
content: 'Alle Einstellungen gehen verloren! Accountdaten sowie App-Daten sind nicht betroffen.',
|
||||||
confirmButton: 'Unwiederruflich Löschen',
|
confirmButton: 'Unwiederruflich Löschen',
|
||||||
onConfirm: () {
|
onConfirm: () {
|
||||||
@@ -112,7 +111,7 @@ class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const CenteredLeading(Icon(Icons.data_object)),
|
leading: const CenteredLeading(Icon(Icons.data_object)),
|
||||||
title: const Text('BLOC State cache'),
|
title: const Text('BLOC-storage state cache'),
|
||||||
subtitle: const Text('Lange tippen um zu löschen'),
|
subtitle: const Text('Lange tippen um zu löschen'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Navigator.push(context, MaterialPageRoute(builder: (context) => const CacheView()));
|
// Navigator.push(context, MaterialPageRoute(builder: (context) => const CacheView()));
|
||||||
@@ -125,7 +124,6 @@ class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
|
|||||||
onConfirm: () => HydratedBloc.storage.clear(),
|
onConfirm: () => HydratedBloc.storage.clear(),
|
||||||
).asDialog(context);
|
).asDialog(context);
|
||||||
},
|
},
|
||||||
trailing: const Icon(Icons.arrow_right),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
@@ -16,7 +16,7 @@ import '../../widget/confirmDialog.dart';
|
|||||||
import '../../widget/debug/cacheView.dart';
|
import '../../widget/debug/cacheView.dart';
|
||||||
import '../pages/timetable/timetableNameMode.dart';
|
import '../pages/timetable/timetableNameMode.dart';
|
||||||
import 'defaultSettings.dart';
|
import 'defaultSettings.dart';
|
||||||
import 'devToolsSettingsDialog.dart';
|
import 'devToolsSettings.dart';
|
||||||
import 'privacyInfo.dart';
|
import 'privacyInfo.dart';
|
||||||
|
|
||||||
class Settings extends StatefulWidget {
|
class Settings extends StatefulWidget {
|
||||||
@@ -155,34 +155,6 @@ class _SettingsState extends State<Settings> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const Divider(),
|
|
||||||
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.drive_folder_upload_outlined),
|
|
||||||
title: const Text('Ordner in Dateien nach oben sortieren'),
|
|
||||||
trailing: Checkbox(
|
|
||||||
value: settings.val().fileSettings.sortFoldersToTop,
|
|
||||||
onChanged: (e) {
|
|
||||||
settings.val(write: true).fileSettings.sortFoldersToTop = e!;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const Divider(),
|
|
||||||
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.open_in_new_outlined),
|
|
||||||
title: const Text('Dateien immer mit Systemdialog öffnen'),
|
|
||||||
trailing: Checkbox(
|
|
||||||
value: settings.val().fileViewSettings.alwaysOpenExternally,
|
|
||||||
onChanged: (e) {
|
|
||||||
settings.val(write: true).fileViewSettings.alwaysOpenExternally = e!;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const Divider(),
|
|
||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const CenteredLeading(Icon(Icons.notifications_active_outlined)),
|
leading: const CenteredLeading(Icon(Icons.notifications_active_outlined)),
|
||||||
title: const Text('Push-Benachrichtigungen aktivieren'),
|
title: const Text('Push-Benachrichtigungen aktivieren'),
|
||||||
@@ -214,6 +186,30 @@ class _SettingsState extends State<Settings> {
|
|||||||
|
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.drive_folder_upload_outlined),
|
||||||
|
title: const Text('Ordner in Dateien nach oben sortieren'),
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: settings.val().fileSettings.sortFoldersToTop,
|
||||||
|
onChanged: (e) {
|
||||||
|
settings.val(write: true).fileSettings.sortFoldersToTop = e!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.open_in_new_outlined),
|
||||||
|
title: const Text('Dateien immer mit Systemdialog öffnen'),
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: settings.val().fileViewSettings.alwaysOpenExternally,
|
||||||
|
onChanged: (e) {
|
||||||
|
settings.val(write: true).fileViewSettings.alwaysOpenExternally = e!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const Divider(),
|
||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.live_help_outlined),
|
leading: const Icon(Icons.live_help_outlined),
|
||||||
title: const Text('Informationen und Lizenzen'),
|
title: const Text('Informationen und Lizenzen'),
|
||||||
@@ -269,14 +265,11 @@ class _SettingsState extends State<Settings> {
|
|||||||
|
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
|
||||||
Visibility(
|
ListTile(
|
||||||
visible: !kReleaseMode,
|
leading: const CenteredLeading(Icon(Icons.code)),
|
||||||
child: ListTile(
|
title: const Text('Quellcode MarianumMobile/Client'),
|
||||||
leading: const CenteredLeading(Icon(Icons.code)),
|
subtitle: const Text('GNU GPL v3'),
|
||||||
title: const Text('Quellcode MarianumMobile/Client'),
|
onTap: () => ConfirmDialog.openBrowser(context, 'https://mhsl.eu/gitea/MarianumMobile/Client'),
|
||||||
subtitle: const Text('GNU GPL v3'),
|
|
||||||
onTap: () => ConfirmDialog.openBrowser(context, 'https://mhsl.eu/gitea/MarianumMobile/Client'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -311,7 +304,7 @@ class _SettingsState extends State<Settings> {
|
|||||||
|
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: settings.val().devToolsEnabled,
|
visible: settings.val().devToolsEnabled,
|
||||||
child: DevToolsSettingsDialog(settings: settings),
|
child: DevToolsSettings(settings: settings),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@@ -3,7 +3,6 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:filesize/filesize.dart';
|
import 'package:filesize/filesize.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
import 'package:localstore/localstore.dart';
|
import 'package:localstore/localstore.dart';
|
||||||
|
|
||||||
@@ -41,7 +40,7 @@ class _CacheViewState extends State<CacheView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Scaffold(
|
Widget build(BuildContext context) => Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Lokaler cache'),
|
title: const Text('Cache storage'),
|
||||||
),
|
),
|
||||||
body: FutureBuilder(
|
body: FutureBuilder(
|
||||||
future: files,
|
future: files,
|
||||||
@@ -58,27 +57,7 @@ class _CacheViewState extends State<CacheView> {
|
|||||||
title: Text(filename),
|
title: Text(filename),
|
||||||
subtitle: Text("${filesize(jsonEncode(element).length * 8)}, ${Jiffy.parseFromMillisecondsSinceEpoch(element['lastupdate']).fromNow()}"),
|
subtitle: Text("${filesize(jsonEncode(element).length * 8)}, ${Jiffy.parseFromMillisecondsSinceEpoch(element['lastupdate']).fromNow()}"),
|
||||||
trailing: const Icon(Icons.arrow_right),
|
trailing: const Icon(Icons.arrow_right),
|
||||||
onTap: () {
|
onTap: () => JsonViewer.asDialog(context, jsonDecode(element['json'])),
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => JsonViewer(title: filename, data: jsonDecode(element['json'])),));
|
|
||||||
},
|
|
||||||
onLongPress: () {
|
|
||||||
showDialog(context: context, builder: (context) => SimpleDialog(
|
|
||||||
children: [
|
|
||||||
const ListTile(
|
|
||||||
leading: Icon(Icons.delete_forever),
|
|
||||||
title: Text('Diese Datei löschen'),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.copy),
|
|
||||||
title: const Text('Dateitext kopieren'),
|
|
||||||
onTap: () {
|
|
||||||
Clipboard.setData(ClipboardData(text: jsonEncode(element)));
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
));
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@@ -7,29 +7,26 @@ class PlaceholderView extends StatelessWidget {
|
|||||||
const PlaceholderView({super.key, required this.icon, required this.text, this.button});
|
const PlaceholderView({super.key, required this.icon, required this.text, this.button});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => DefaultTextStyle(
|
Widget build(BuildContext context) => Scaffold(
|
||||||
style: const TextStyle(),
|
body: Center(
|
||||||
child: Center(
|
child: Container(
|
||||||
child: Container(
|
margin: const EdgeInsets.only(top: 100, left: 20, right: 20),
|
||||||
margin: const EdgeInsets.only(top: 100, left: 20, right: 20),
|
child: Column(
|
||||||
child: Column(
|
children: [
|
||||||
children: [
|
Container(
|
||||||
Container(
|
margin: const EdgeInsets.all(30),
|
||||||
margin: const EdgeInsets.all(30),
|
child: Icon(icon, size: 60),
|
||||||
child: Icon(icon, color: Colors.grey, size: 60),
|
),
|
||||||
),
|
Text(
|
||||||
Text(text,
|
text,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 20,),
|
||||||
fontSize: 20,
|
textAlign: TextAlign.center,
|
||||||
color: Colors.grey,
|
),
|
||||||
),
|
const SizedBox(height: 30),
|
||||||
textAlign: TextAlign.center,
|
if(button != null) button!,
|
||||||
),
|
],
|
||||||
const SizedBox(height: 30),
|
|
||||||
if(button != null) button!,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# 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
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 0.1.0+36
|
version: 0.1.3+41
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>3.0.0'
|
sdk: '>3.0.0'
|
||||||
|
Reference in New Issue
Block a user