folder restructuring
This commit is contained in:
@@ -3,8 +3,8 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
import '../../../model/accountData.dart';
|
import '../../../model/account_data.dart';
|
||||||
import '../../../model/endpointData.dart';
|
import '../../../model/endpoint_data.dart';
|
||||||
import 'autocompleteResponse.dart';
|
import 'autocompleteResponse.dart';
|
||||||
|
|
||||||
class AutocompleteApi {
|
class AutocompleteApi {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
import '../../../model/accountData.dart';
|
import '../../../model/account_data.dart';
|
||||||
import '../../../model/endpointData.dart';
|
import '../../../model/endpoint_data.dart';
|
||||||
import 'fileSharingApiParams.dart';
|
import 'fileSharingApiParams.dart';
|
||||||
|
|
||||||
class FileSharingApi {
|
class FileSharingApi {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import 'dart:developer';
|
|||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
import '../../../model/accountData.dart';
|
import '../../../model/account_data.dart';
|
||||||
import '../../../model/endpointData.dart';
|
import '../../../model/endpoint_data.dart';
|
||||||
import '../../apiError.dart';
|
import '../../apiError.dart';
|
||||||
import '../../apiParams.dart';
|
import '../../apiParams.dart';
|
||||||
import '../../apiRequest.dart';
|
import '../../apiRequest.dart';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:nextcloud/nextcloud.dart';
|
import 'package:nextcloud/nextcloud.dart';
|
||||||
|
|
||||||
import '../../../model/accountData.dart';
|
import '../../../model/account_data.dart';
|
||||||
import '../../../model/endpointData.dart';
|
import '../../../model/endpoint_data.dart';
|
||||||
import '../../apiRequest.dart';
|
import '../../apiRequest.dart';
|
||||||
import '../../apiResponse.dart';
|
import '../../apiResponse.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:device_info_plus/device_info_plus.dart';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
import '../../../../../model/accountData.dart';
|
import '../../../../../model/account_data.dart';
|
||||||
import '../../../mhslApi.dart';
|
import '../../../mhslApi.dart';
|
||||||
import 'updateUserIndexParams.dart';
|
import 'updateUserIndexParams.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import '../../../../model/accountData.dart';
|
import '../../../../model/account_data.dart';
|
||||||
import '../../webuntisApi.dart';
|
import '../../webuntisApi.dart';
|
||||||
import 'authenticateParams.dart';
|
import 'authenticateParams.dart';
|
||||||
import 'authenticateResponse.dart';
|
import 'authenticateResponse.dart';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
import '../../model/endpointData.dart';
|
import '../../model/endpoint_data.dart';
|
||||||
import '../apiParams.dart';
|
import '../apiParams.dart';
|
||||||
import '../apiRequest.dart';
|
import '../apiRequest.dart';
|
||||||
import '../apiResponse.dart';
|
import '../apiResponse.dart';
|
||||||
|
|||||||
+5
-5
@@ -11,10 +11,10 @@ import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
|||||||
import 'api/mhsl/server/userIndex/update/updateUserindex.dart';
|
import 'api/mhsl/server/userIndex/update/updateUserindex.dart';
|
||||||
import 'main.dart';
|
import 'main.dart';
|
||||||
import 'widget/breaker/breaker.dart';
|
import 'widget/breaker/breaker.dart';
|
||||||
import 'model/dataCleaner.dart';
|
import 'model/data_cleaner.dart';
|
||||||
import 'notification/notificationController.dart';
|
import 'notification/notification_controller.dart';
|
||||||
import 'notification/notificationTasks.dart';
|
import 'notification/notification_tasks.dart';
|
||||||
import 'notification/notifyUpdater.dart';
|
import 'notification/notify_updater.dart';
|
||||||
import 'state/app/modules/app_modules.dart';
|
import 'state/app/modules/app_modules.dart';
|
||||||
import 'state/app/modules/breaker/bloc/breaker_bloc.dart';
|
import 'state/app/modules/breaker/bloc/breaker_bloc.dart';
|
||||||
import 'state/app/modules/chatList/bloc/chat_list_bloc.dart';
|
import 'state/app/modules/chatList/bloc/chat_list_bloc.dart';
|
||||||
@@ -106,7 +106,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||||||
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,
|
||||||
handleAndroidBackButtonPress: false,
|
handleAndroidBackButtonPress: true,
|
||||||
screenTransitionAnimation: const ScreenTransitionAnimation(
|
screenTransitionAnimation: const ScreenTransitionAnimation(
|
||||||
curve: Curves.easeOutQuad,
|
curve: Curves.easeOutQuad,
|
||||||
duration: Duration(milliseconds: 200),
|
duration: Duration(milliseconds: 200),
|
||||||
|
|||||||
+4
-4
@@ -19,7 +19,7 @@ import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
|||||||
import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
||||||
import 'app.dart';
|
import 'app.dart';
|
||||||
import 'firebase_options.dart';
|
import 'firebase_options.dart';
|
||||||
import 'model/accountData.dart';
|
import 'model/account_data.dart';
|
||||||
import 'widget/breaker/breaker.dart';
|
import 'widget/breaker/breaker.dart';
|
||||||
import 'state/app/modules/account/bloc/account_bloc.dart';
|
import 'state/app/modules/account/bloc/account_bloc.dart';
|
||||||
import 'state/app/modules/account/bloc/account_state.dart';
|
import 'state/app/modules/account/bloc/account_state.dart';
|
||||||
@@ -29,10 +29,10 @@ import 'state/app/modules/chatList/bloc/chat_list_bloc.dart';
|
|||||||
import 'state/app/modules/settings/bloc/settings_cubit.dart';
|
import 'state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
import 'state/app/modules/timetable/bloc/timetable_bloc.dart';
|
import 'state/app/modules/timetable/bloc/timetable_bloc.dart';
|
||||||
import 'storage/base/settings.dart';
|
import 'storage/base/settings.dart';
|
||||||
import 'theming/darkAppTheme.dart';
|
import 'theming/dark_app_theme.dart';
|
||||||
import 'theming/lightAppTheme.dart';
|
import 'theming/light_app_theme.dart';
|
||||||
import 'view/login/login.dart';
|
import 'view/login/login.dart';
|
||||||
import 'widget/placeholderView.dart';
|
import 'widget/placeholder_view.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
log('MarianumMobile started');
|
log('MarianumMobile started');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import 'accountData.dart';
|
import 'account_data.dart';
|
||||||
|
|
||||||
enum EndpointMode {
|
enum EndpointMode {
|
||||||
live,
|
live,
|
||||||
+12
-4
@@ -1,9 +1,9 @@
|
|||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../widget/debug/debugTile.dart';
|
import '../widget/debug/debug_tile.dart';
|
||||||
import '../widget/debug/jsonViewer.dart';
|
import '../widget/debug/json_viewer.dart';
|
||||||
import 'notificationTasks.dart';
|
import 'notification_tasks.dart';
|
||||||
|
|
||||||
class NotificationController {
|
class NotificationController {
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
@@ -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(context);
|
NotificationTasks.navigateToTalk(context, chatToken: _extractChatToken(message));
|
||||||
NotificationTasks.updateProviders(context);
|
NotificationTasks.updateProviders(context);
|
||||||
|
|
||||||
DebugTile(context).run(() {
|
DebugTile(context).run(() {
|
||||||
@@ -60,4 +60,12 @@ class NotificationController {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String? _extractChatToken(RemoteMessage message) {
|
||||||
|
for (final key in const ['chatToken', 'token', 'roomToken']) {
|
||||||
|
final value = message.data[key];
|
||||||
|
if (value is String && value.isNotEmpty) return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,7 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import 'package:flutter_app_badge/flutter_app_badge.dart';
|
import 'package:flutter_app_badge/flutter_app_badge.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../main.dart';
|
import '../routing/app_routes.dart';
|
||||||
import '../state/app/modules/app_modules.dart';
|
|
||||||
import '../state/app/modules/chat/bloc/chat_bloc.dart';
|
import '../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||||
import '../state/app/modules/chatList/bloc/chat_list_bloc.dart';
|
import '../state/app/modules/chatList/bloc/chat_list_bloc.dart';
|
||||||
|
|
||||||
@@ -18,9 +17,14 @@ class NotificationTasks {
|
|||||||
context.read<ChatBloc>().refresh();
|
context.read<ChatBloc>().refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void navigateToTalk(BuildContext context) {
|
/// Switches to the Talk tab. If [chatToken] is provided, also schedules
|
||||||
final talkTab = AppModule.getBottomBarModules(context).map((e) => e.module).toList().indexOf(Modules.talk);
|
/// the matching chat to be opened automatically once the chat list view
|
||||||
if (talkTab == -1) return;
|
/// resolves the token (handled inside [ChatList]).
|
||||||
Main.bottomNavigator.jumpToTab(talkTab);
|
static void navigateToTalk(BuildContext context, {String? chatToken}) {
|
||||||
|
if (chatToken != null && chatToken.isNotEmpty) {
|
||||||
|
AppRoutes.openChatByToken(context, chatToken);
|
||||||
|
} else {
|
||||||
|
AppRoutes.goToTalkTab(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,9 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import '../api/mhsl/notify/register/notifyRegister.dart';
|
import '../api/mhsl/notify/register/notifyRegister.dart';
|
||||||
import '../api/mhsl/notify/register/notifyRegisterParams.dart';
|
import '../api/mhsl/notify/register/notifyRegisterParams.dart';
|
||||||
import '../model/accountData.dart';
|
import '../model/account_data.dart';
|
||||||
import '../state/app/modules/settings/bloc/settings_cubit.dart';
|
import '../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
import '../widget/confirmDialog.dart';
|
import '../widget/confirm_dialog.dart';
|
||||||
|
|
||||||
class NotifyUpdater {
|
class NotifyUpdater {
|
||||||
static ConfirmDialog enableAfterDisclaimer(SettingsCubit settings) => ConfirmDialog(
|
static ConfirmDialog enableAfterDisclaimer(SettingsCubit settings) => ConfirmDialog(
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||||
|
|
||||||
|
import '../api/marianumcloud/talk/room/getRoomResponse.dart';
|
||||||
|
import '../main.dart';
|
||||||
|
import '../model/account_data.dart';
|
||||||
|
import '../state/app/modules/app_modules.dart';
|
||||||
|
import '../state/app/modules/chatList/bloc/chat_list_bloc.dart';
|
||||||
|
import '../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||||
|
import '../state/app/modules/marianumMessage/bloc/marianum_message_state.dart';
|
||||||
|
import '../view/pages/files/files.dart';
|
||||||
|
import '../view/pages/marianum_message/marianum_message_view.dart';
|
||||||
|
import '../view/pages/more/feedback/feedback_dialog.dart';
|
||||||
|
import '../view/pages/more/roomplan/roomplan.dart';
|
||||||
|
import '../view/pages/more/share/qr_share_view.dart';
|
||||||
|
import '../view/pages/talk/chat_view.dart';
|
||||||
|
import '../view/pages/talk/details/message_reactions.dart';
|
||||||
|
import '../view/pages/talk/talk_navigator.dart';
|
||||||
|
import '../view/pages/timetable/custom_events/custom_events_view.dart';
|
||||||
|
import '../view/pages/settings/settings.dart';
|
||||||
|
import '../widget/debug/cache_view.dart';
|
||||||
|
import '../widget/file_viewer.dart';
|
||||||
|
import '../widget/user_avatar.dart';
|
||||||
|
|
||||||
|
/// Single entry point for full-page navigations across the app.
|
||||||
|
///
|
||||||
|
/// Every full-page push in modules should go through one of these methods.
|
||||||
|
/// Dialogs (`showDialog`), bottom sheets (`showStickyFlexibleBottomSheet`,
|
||||||
|
/// `showAppointmentBottomSheet`), and `Navigator.pop` for closing those
|
||||||
|
/// remain unchanged and live at the call sites.
|
||||||
|
class AppRoutes {
|
||||||
|
AppRoutes._();
|
||||||
|
|
||||||
|
/// Token of a chat that should be auto-opened in the Talk tab once
|
||||||
|
/// the chat list view picks it up. Set by [openChatByToken] (e.g. from
|
||||||
|
/// a tapped notification) and consumed by the `ChatList` widget.
|
||||||
|
static final ValueNotifier<String?> pendingChatToken = ValueNotifier(null);
|
||||||
|
|
||||||
|
// -- Files --------------------------------------------------------------
|
||||||
|
|
||||||
|
static void openFolder(BuildContext context, List<String> path) {
|
||||||
|
pushScreen(context, withNavBar: false, screen: Files(path: path));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void openFileViewer(BuildContext context, String localPath, {bool openExternal = false}) {
|
||||||
|
pushScreen(
|
||||||
|
context,
|
||||||
|
withNavBar: false,
|
||||||
|
screen: FileViewer(path: localPath, openExternal: openExternal),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Timetable ----------------------------------------------------------
|
||||||
|
|
||||||
|
static void openCustomEvents(BuildContext context) {
|
||||||
|
pushScreen(context, withNavBar: false, screen: const CustomEventsView());
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Marianum Message ---------------------------------------------------
|
||||||
|
|
||||||
|
static void openMarianumMessage(BuildContext context, String basePath, MarianumMessage message) {
|
||||||
|
pushScreen(
|
||||||
|
context,
|
||||||
|
withNavBar: false,
|
||||||
|
screen: MessageView(basePath: basePath, message: message),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Sharing / Settings / Feedback / DevTools ---------------------------
|
||||||
|
|
||||||
|
static void openQrShare(BuildContext context) {
|
||||||
|
pushScreen(context, withNavBar: false, screen: const QrShareView());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void openSettings(BuildContext context) {
|
||||||
|
pushScreen(context, withNavBar: false, screen: const Settings());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void openFeedback(BuildContext context) {
|
||||||
|
pushScreen(context, withNavBar: false, screen: const FeedbackDialog());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void openCacheView(BuildContext context) {
|
||||||
|
pushScreen(context, withNavBar: false, screen: const CacheView());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void openRoomplan(BuildContext context) {
|
||||||
|
pushScreen(context, withNavBar: false, screen: const Roomplan());
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Talk ---------------------------------------------------------------
|
||||||
|
|
||||||
|
static void openMessageReactions(BuildContext context, String token, int messageId) {
|
||||||
|
pushScreen(
|
||||||
|
context,
|
||||||
|
withNavBar: false,
|
||||||
|
screen: MessageReactions(token: token, messageId: messageId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens a chat from a known [GetRoomResponseObject]. Delegates to
|
||||||
|
/// [TalkNavigator.pushSplitView] so tablet split-view behaviour stays intact.
|
||||||
|
static void openChatView(
|
||||||
|
BuildContext context, {
|
||||||
|
required GetRoomResponseObject room,
|
||||||
|
required String selfId,
|
||||||
|
required UserAvatar avatar,
|
||||||
|
bool overrideToSingleSubScreen = true,
|
||||||
|
}) {
|
||||||
|
TalkNavigator.pushSplitView(
|
||||||
|
context,
|
||||||
|
ChatView(room: room, selfId: selfId, avatar: avatar),
|
||||||
|
overrideToSingleSubScreen: overrideToSingleSubScreen,
|
||||||
|
);
|
||||||
|
context.read<ChatBloc>().setToken(room.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Schedules a chat to be opened in the Talk tab. Use this when only the
|
||||||
|
/// token is known (e.g. from a tapped notification) — the actual push
|
||||||
|
/// happens inside the `ChatList` widget once the room is available.
|
||||||
|
static void openChatByToken(BuildContext context, String token) {
|
||||||
|
pendingChatToken.value = token;
|
||||||
|
goToTalkTab(context);
|
||||||
|
try {
|
||||||
|
context.read<ChatListBloc>().refresh();
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) debugPrint('openChatByToken: ChatListBloc refresh failed: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a pending chat token (set via [openChatByToken]) using the
|
||||||
|
/// [ChatListBloc]'s current rooms and the active [AccountData] credentials.
|
||||||
|
/// Returns `null` if the token cannot yet be matched (e.g. the room is
|
||||||
|
/// still being loaded). Callers should keep listening to [pendingChatToken]
|
||||||
|
/// and the bloc state and retry when either changes.
|
||||||
|
static ResolvedPendingChat? resolvePendingChat(BuildContext context) {
|
||||||
|
final token = pendingChatToken.value;
|
||||||
|
if (token == null) return null;
|
||||||
|
if (!AccountData().isPopulated()) return null;
|
||||||
|
|
||||||
|
final rooms = context.read<ChatListBloc>().state.data?.rooms;
|
||||||
|
final room = _findRoomByToken(rooms, token);
|
||||||
|
if (room == null) return null;
|
||||||
|
|
||||||
|
final isGroup = room.type != GetRoomResponseObjectConversationType.oneToOne;
|
||||||
|
final avatar = UserAvatar(id: isGroup ? room.token : room.name, isGroup: isGroup);
|
||||||
|
return ResolvedPendingChat(
|
||||||
|
room: room,
|
||||||
|
selfId: AccountData().getUsername(),
|
||||||
|
avatar: avatar,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GetRoomResponseObject? _findRoomByToken(GetRoomResponse? rooms, String token) {
|
||||||
|
if (rooms == null) return null;
|
||||||
|
for (final room in rooms.data) {
|
||||||
|
if (room.token == token) return room;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Module / Tab navigation -------------------------------------------
|
||||||
|
|
||||||
|
/// Opens an [AppModule]'s root view as a full screen push (used by the
|
||||||
|
/// "Mehr" tab list). Modules that live in the bottom bar are reached via
|
||||||
|
/// [goToTab] instead.
|
||||||
|
static void openModule(BuildContext context, AppModule module) {
|
||||||
|
pushScreen(context, withNavBar: false, screen: module.create());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Switches the bottom navigation to the given [module] if it is currently
|
||||||
|
/// in the bottom bar. Returns `true` if the jump happened.
|
||||||
|
static bool goToTab(BuildContext context, Modules module) {
|
||||||
|
final index = AppModule.getBottomBarModules(context)
|
||||||
|
.map((e) => e.module)
|
||||||
|
.toList()
|
||||||
|
.indexOf(module);
|
||||||
|
if (index == -1) return false;
|
||||||
|
Main.bottomNavigator.jumpToTab(index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience wrapper for the Talk tab — preserved for the notification
|
||||||
|
/// handler API which only knows about Talk.
|
||||||
|
static void goToTalkTab(BuildContext context) {
|
||||||
|
goToTab(context, Modules.talk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResolvedPendingChat {
|
||||||
|
final GetRoomResponseObject room;
|
||||||
|
final String selfId;
|
||||||
|
final UserAvatar avatar;
|
||||||
|
|
||||||
|
const ResolvedPendingChat({
|
||||||
|
required this.room,
|
||||||
|
required this.selfId,
|
||||||
|
required this.avatar,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../../../../../widget/infoDialog.dart';
|
import '../../../../../widget/info_dialog.dart';
|
||||||
import '../bloc/loadable_state_bloc.dart';
|
import '../bloc/loadable_state_bloc.dart';
|
||||||
|
|
||||||
class LoadableStateErrorBar extends StatelessWidget {
|
class LoadableStateErrorBar extends StatelessWidget {
|
||||||
|
|||||||
@@ -2,23 +2,24 @@ 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:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import 'package:badges/badges.dart' as badges;
|
||||||
|
|
||||||
import '../../../api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
import '../../../api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
||||||
import '../../../widget/breaker/breaker.dart';
|
import '../../../routing/app_routes.dart';
|
||||||
import '../../../view/pages/files/files.dart';
|
import '../../../view/pages/files/files.dart';
|
||||||
|
import '../../../view/pages/grade_averages/grade_averages_view.dart';
|
||||||
|
import '../../../view/pages/holidays/holidays_view.dart';
|
||||||
|
import '../../../view/pages/marianum_dates/marianum_dates_view.dart';
|
||||||
|
import '../../../view/pages/marianum_message/marianum_message_list_view.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/chat_list.dart';
|
||||||
import '../../../view/pages/timetable/timetable.dart';
|
import '../../../view/pages/timetable/timetable.dart';
|
||||||
import '../../../widget/centeredLeading.dart';
|
import '../../../widget/breaker/breaker.dart';
|
||||||
|
import '../../../widget/centered_leading.dart';
|
||||||
|
import '../infrastructure/loadableState/loadable_state.dart';
|
||||||
import 'chatList/bloc/chat_list_bloc.dart';
|
import 'chatList/bloc/chat_list_bloc.dart';
|
||||||
import 'chatList/bloc/chat_list_state.dart';
|
import 'chatList/bloc/chat_list_state.dart';
|
||||||
import 'settings/bloc/settings_cubit.dart';
|
import 'settings/bloc/settings_cubit.dart';
|
||||||
import '../infrastructure/loadableState/loadable_state.dart';
|
|
||||||
import 'gradeAverages/view/grade_averages_view.dart';
|
|
||||||
import 'holidays/view/holidays_view.dart';
|
|
||||||
import 'marianumDates/view/marianum_dates_view.dart';
|
|
||||||
import 'marianumMessage/view/marianum_message_list_view.dart';
|
|
||||||
|
|
||||||
import 'package:badges/badges.dart' as badges;
|
|
||||||
|
|
||||||
class AppModule {
|
class AppModule {
|
||||||
Modules module;
|
Modules module;
|
||||||
@@ -120,7 +121,7 @@ class AppModule {
|
|||||||
key: key,
|
key: key,
|
||||||
leading: CenteredLeading(icon()),
|
leading: CenteredLeading(icon()),
|
||||||
title: Text(name),
|
title: Text(name),
|
||||||
onTap: isReorder ? null : () => pushScreen(context, withNavBar: false, screen: create()),
|
onTap: isReorder ? null : () => AppRoutes.openModule(context, this),
|
||||||
trailing: isReorder
|
trailing: isReorder
|
||||||
? Row(mainAxisSize: MainAxisSize.min, children: [
|
? Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
IconButton(onPressed: onVisibleChange, icon: Icon(isVisible ? Icons.visibility_outlined : Icons.visibility_off_outlined)),
|
IconButton(onPressed: onVisibleChange, icon: Icon(isVisible ? Icons.visibility_outlined : Icons.visibility_off_outlined)),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:easy_debounce/easy_debounce.dart';
|
|||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
|
|
||||||
import '../../../../../storage/base/settings.dart';
|
import '../../../../../storage/base/settings.dart';
|
||||||
import '../../../../../view/settings/defaultSettings.dart';
|
import '../../../../../view/pages/settings/data/default_settings.dart';
|
||||||
import '../../app_modules.dart';
|
import '../../app_modules.dart';
|
||||||
|
|
||||||
class SettingsCubit extends HydratedCubit<Settings> {
|
class SettingsCubit extends HydratedCubit<Settings> {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import '../../../../../api/webuntis/queries/getTimegridUnits/getTimegridUnitsCac
|
|||||||
import '../../../../../api/webuntis/queries/getTimegridUnits/getTimegridUnitsResponse.dart';
|
import '../../../../../api/webuntis/queries/getTimegridUnits/getTimegridUnitsResponse.dart';
|
||||||
import '../../../../../api/webuntis/queries/getTimetable/getTimetableCache.dart';
|
import '../../../../../api/webuntis/queries/getTimetable/getTimetableCache.dart';
|
||||||
import '../../../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart';
|
import '../../../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart';
|
||||||
import '../../../../../model/accountData.dart';
|
import '../../../../../model/account_data.dart';
|
||||||
|
|
||||||
class TimetableDataProvider {
|
class TimetableDataProvider {
|
||||||
static final DateFormat _dateFormat = DateFormat('yyyyMMdd');
|
static final DateFormat _dateFormat = DateFormat('yyyyMMdd');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../widget/dropdownDisplay.dart';
|
import '../../widget/dropdown_display.dart';
|
||||||
|
|
||||||
enum TimetableNameMode { name, longName, alternateName }
|
enum TimetableNameMode { name, longName, alternateName }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../widget/dropdownDisplay.dart';
|
import '../widget/dropdown_display.dart';
|
||||||
|
|
||||||
class AppTheme {
|
class AppTheme {
|
||||||
static DropdownDisplay getDisplayOptions(ThemeMode theme) {
|
static DropdownDisplay getDisplayOptions(ThemeMode theme) {
|
||||||
@@ -7,7 +7,7 @@ import 'package:flutter_login/flutter_login.dart';
|
|||||||
|
|
||||||
import '../../api/marianumcloud/talk/room/getRoom.dart';
|
import '../../api/marianumcloud/talk/room/getRoom.dart';
|
||||||
import '../../api/marianumcloud/talk/room/getRoomParams.dart';
|
import '../../api/marianumcloud/talk/room/getRoomParams.dart';
|
||||||
import '../../model/accountData.dart';
|
import '../../model/account_data.dart';
|
||||||
import '../../state/app/modules/account/bloc/account_bloc.dart';
|
import '../../state/app/modules/account/bloc/account_bloc.dart';
|
||||||
import '../../state/app/modules/account/bloc/account_state.dart';
|
import '../../state/app/modules/account/bloc/account_state.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import '../../../state/app/infrastructure/utilityWidgets/bloc_module.dart';
|
|||||||
import '../../../state/app/modules/files/bloc/files_bloc.dart';
|
import '../../../state/app/modules/files/bloc/files_bloc.dart';
|
||||||
import '../../../state/app/modules/files/bloc/files_state.dart';
|
import '../../../state/app/modules/files/bloc/files_state.dart';
|
||||||
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
import '../../../widget/filePick.dart';
|
import '../../../widget/file_pick.dart';
|
||||||
import '../../../widget/placeholderView.dart';
|
import '../../../widget/placeholder_view.dart';
|
||||||
import 'fileElement.dart';
|
import 'widgets/file_element.dart';
|
||||||
import 'filesUploadDialog.dart';
|
import 'files_upload_dialog.dart';
|
||||||
|
|
||||||
class BetterSortOption {
|
class BetterSortOption {
|
||||||
String displayName;
|
String displayName;
|
||||||
|
|||||||
+2
-2
@@ -6,8 +6,8 @@ import 'package:nextcloud/nextcloud.dart';
|
|||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import '../../../api/marianumcloud/webdav/webdavApi.dart';
|
import '../../../api/marianumcloud/webdav/webdavApi.dart';
|
||||||
import '../../../widget/confirmDialog.dart';
|
import '../../../widget/confirm_dialog.dart';
|
||||||
import '../../../widget/focusBehaviour.dart';
|
import '../../../widget/focus_behaviour.dart';
|
||||||
|
|
||||||
class FilesUploadDialog extends StatefulWidget {
|
class FilesUploadDialog extends StatefulWidget {
|
||||||
final List<String> filePaths;
|
final List<String> filePaths;
|
||||||
+11
-18
@@ -7,19 +7,18 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
import 'package:open_filex/open_filex.dart';
|
import 'package:open_filex/open_filex.dart';
|
||||||
import '../../../widget/infoDialog.dart';
|
import '../../../../widget/info_dialog.dart';
|
||||||
import 'package:nextcloud/nextcloud.dart';
|
import 'package:nextcloud/nextcloud.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
import '../../../api/marianumcloud/webdav/queries/listFiles/cacheableFile.dart';
|
import '../../../../api/marianumcloud/webdav/queries/listFiles/cacheableFile.dart';
|
||||||
import '../../../api/marianumcloud/webdav/webdavApi.dart';
|
import '../../../../api/marianumcloud/webdav/webdavApi.dart';
|
||||||
import '../../../model/accountData.dart';
|
import '../../../../model/account_data.dart';
|
||||||
import '../../../model/endpointData.dart';
|
import '../../../../model/endpoint_data.dart';
|
||||||
import '../../../widget/centeredLeading.dart';
|
import '../../../../routing/app_routes.dart';
|
||||||
import '../../../widget/confirmDialog.dart';
|
import '../../../../widget/centered_leading.dart';
|
||||||
import '../../../widget/fileViewer.dart';
|
import '../../../../widget/confirm_dialog.dart';
|
||||||
import '../../../widget/unimplementedDialog.dart';
|
import '../../../../widget/unimplemented_dialog.dart';
|
||||||
import 'files.dart';
|
|
||||||
|
|
||||||
class FileElement extends StatefulWidget {
|
class FileElement extends StatefulWidget {
|
||||||
final CacheableFile file;
|
final CacheableFile file;
|
||||||
@@ -45,12 +44,8 @@ class FileElement extends StatefulWidget {
|
|||||||
deleteOnCancel: true,
|
deleteOnCancel: true,
|
||||||
client: Dio(BaseOptions(headers: AccountData().authHeaders())),
|
client: Dio(BaseOptions(headers: AccountData().authHeaders())),
|
||||||
onDone: () {
|
onDone: () {
|
||||||
//Future<OpenResult> result = OpenFile.open(local); // TODO legacy - refactor: remove onDone parameter
|
AppRoutes.openFileViewer(context, local);
|
||||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => FileViewer(path: local)));
|
|
||||||
onDone(OpenResult(message: 'File viewer opened', type: ResultType.done));
|
onDone(OpenResult(message: 'File viewer opened', type: ResultType.done));
|
||||||
// result.then((value) => {
|
|
||||||
// onDone(value)
|
|
||||||
// });
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -101,9 +96,7 @@ class _FileElementState extends State<FileElement> {
|
|||||||
trailing: Icon(widget.file.isDirectory ? Icons.arrow_right : null),
|
trailing: Icon(widget.file.isDirectory ? Icons.arrow_right : null),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if(widget.file.isDirectory) {
|
if(widget.file.isDirectory) {
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
AppRoutes.openFolder(context, widget.path.toList()..add(widget.file.name));
|
||||||
builder: (context) => Files(path: widget.path.toList()..add(widget.file.name)),
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
if(EndpointData().getEndpointMode() == EndpointMode.stage) {
|
if(EndpointData().getEndpointMode() == EndpointMode.stage) {
|
||||||
InfoDialog.show(context, 'Virtuelle Dateien im Staging Prozess können nicht heruntergeladen werden!');
|
InfoDialog.show(context, 'Virtuelle Dateien im Staging Prozess können nicht heruntergeladen werden!');
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../bloc/grade_averages_bloc.dart';
|
import '../../../state/app/modules/gradeAverages/bloc/grade_averages_bloc.dart';
|
||||||
import '../bloc/grade_averages_event.dart';
|
import '../../../state/app/modules/gradeAverages/bloc/grade_averages_event.dart';
|
||||||
|
|
||||||
class GradeAveragesListView extends StatelessWidget {
|
class GradeAveragesListView extends StatelessWidget {
|
||||||
const GradeAveragesListView({super.key});
|
const GradeAveragesListView({super.key});
|
||||||
+4
-4
@@ -1,10 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../../../../../widget/confirmDialog.dart';
|
import '../../../state/app/modules/gradeAverages/bloc/grade_averages_bloc.dart';
|
||||||
import '../bloc/grade_averages_bloc.dart';
|
import '../../../state/app/modules/gradeAverages/bloc/grade_averages_event.dart';
|
||||||
import '../bloc/grade_averages_event.dart';
|
import '../../../state/app/modules/gradeAverages/bloc/grade_averages_state.dart';
|
||||||
import '../bloc/grade_averages_state.dart';
|
import '../../../widget/confirm_dialog.dart';
|
||||||
import 'grade_averages_list_view.dart';
|
import 'grade_averages_list_view.dart';
|
||||||
|
|
||||||
class GradeAveragesView extends StatelessWidget {
|
class GradeAveragesView extends StatelessWidget {
|
||||||
+11
-11
@@ -1,17 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
|
|
||||||
import '../../../../../widget/animatedTime.dart';
|
import '../../../state/app/infrastructure/loadableState/loadable_state.dart';
|
||||||
import '../../../../../widget/list_view_util.dart';
|
import '../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
||||||
import '../../../../../widget/centeredLeading.dart';
|
import '../../../state/app/infrastructure/utilityWidgets/bloc_module.dart';
|
||||||
import '../../../../../widget/debug/debugTile.dart';
|
import '../../../state/app/modules/holidays/bloc/holidays_bloc.dart';
|
||||||
import '../../../../../widget/string_extensions.dart';
|
import '../../../state/app/modules/holidays/bloc/holidays_event.dart';
|
||||||
import '../../../infrastructure/loadableState/loadable_state.dart';
|
import '../../../state/app/modules/holidays/bloc/holidays_state.dart';
|
||||||
import '../../../infrastructure/loadableState/view/loadable_state_consumer.dart';
|
import '../../../widget/animated_time.dart';
|
||||||
import '../../../infrastructure/utilityWidgets/bloc_module.dart';
|
import '../../../widget/centered_leading.dart';
|
||||||
import '../bloc/holidays_bloc.dart';
|
import '../../../widget/debug/debug_tile.dart';
|
||||||
import '../bloc/holidays_event.dart';
|
import '../../../widget/list_view_util.dart';
|
||||||
import '../bloc/holidays_state.dart';
|
import '../../../widget/string_extensions.dart';
|
||||||
|
|
||||||
class HolidaysView extends StatelessWidget {
|
class HolidaysView extends StatelessWidget {
|
||||||
const HolidaysView({super.key});
|
const HolidaysView({super.key});
|
||||||
+11
-11
@@ -1,17 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
|
|
||||||
import '../../../../../view/pages/timetable/custom_events/custom_event_edit_dialog.dart';
|
import '../../../state/app/infrastructure/loadableState/loadable_state.dart';
|
||||||
import '../../../../../widget/animatedTime.dart';
|
import '../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
||||||
import '../../../../../widget/centeredLeading.dart';
|
import '../../../state/app/infrastructure/utilityWidgets/bloc_module.dart';
|
||||||
import '../../../../../widget/debug/debugTile.dart';
|
import '../../../state/app/modules/marianumDates/bloc/marianum_dates_bloc.dart';
|
||||||
import '../../../../../widget/list_view_util.dart';
|
import '../../../state/app/modules/marianumDates/bloc/marianum_dates_event.dart';
|
||||||
import '../../../infrastructure/loadableState/view/loadable_state_consumer.dart';
|
import '../../../state/app/modules/marianumDates/bloc/marianum_dates_state.dart';
|
||||||
import '../../../infrastructure/utilityWidgets/bloc_module.dart';
|
import '../../../widget/animated_time.dart';
|
||||||
import '../../../infrastructure/loadableState/loadable_state.dart';
|
import '../../../widget/centered_leading.dart';
|
||||||
import '../bloc/marianum_dates_bloc.dart';
|
import '../../../widget/debug/debug_tile.dart';
|
||||||
import '../bloc/marianum_dates_event.dart';
|
import '../../../widget/list_view_util.dart';
|
||||||
import '../bloc/marianum_dates_state.dart';
|
import '../timetable/custom_events/custom_event_edit_dialog.dart';
|
||||||
|
|
||||||
class MarianumDatesView extends StatelessWidget {
|
class MarianumDatesView extends StatelessWidget {
|
||||||
const MarianumDatesView({super.key});
|
const MarianumDatesView({super.key});
|
||||||
+7
-7
@@ -1,11 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'marianum_message_view.dart';
|
import '../../../routing/app_routes.dart';
|
||||||
import '../../../infrastructure/loadableState/loadable_state.dart';
|
import '../../../state/app/infrastructure/loadableState/loadable_state.dart';
|
||||||
import '../../../infrastructure/loadableState/view/loadable_state_consumer.dart';
|
import '../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
||||||
import '../../../infrastructure/utilityWidgets/bloc_module.dart';
|
import '../../../state/app/infrastructure/utilityWidgets/bloc_module.dart';
|
||||||
import '../bloc/marianum_message_bloc.dart';
|
import '../../../state/app/modules/marianumMessage/bloc/marianum_message_bloc.dart';
|
||||||
import '../bloc/marianum_message_state.dart';
|
import '../../../state/app/modules/marianumMessage/bloc/marianum_message_state.dart';
|
||||||
|
|
||||||
class MarianumMessageListView extends StatelessWidget {
|
class MarianumMessageListView extends StatelessWidget {
|
||||||
const MarianumMessageListView({super.key});
|
const MarianumMessageListView({super.key});
|
||||||
@@ -31,7 +31,7 @@ class MarianumMessageListView extends StatelessWidget {
|
|||||||
subtitle: Text('vom ${message.date}'),
|
subtitle: Text('vom ${message.date}'),
|
||||||
trailing: const Icon(Icons.arrow_right),
|
trailing: const Icon(Icons.arrow_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => MessageView(basePath: state.messageList.base, message: message)));
|
AppRoutes.openMarianumMessage(context, state.messageList.base, message);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+2
-2
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../bloc/marianum_message_state.dart';
|
import '../../../state/app/modules/marianumMessage/bloc/marianum_message_state.dart';
|
||||||
import '../../../../../widget/confirmDialog.dart';
|
import '../../../widget/confirm_dialog.dart';
|
||||||
|
|
||||||
class MessageView extends StatefulWidget {
|
class MessageView extends StatefulWidget {
|
||||||
final String basePath;
|
final String basePath;
|
||||||
+4
-4
@@ -11,11 +11,11 @@ import 'package:badges/badges.dart' as badges;
|
|||||||
|
|
||||||
import '../../../../api/mhsl/server/feedback/addFeedback.dart';
|
import '../../../../api/mhsl/server/feedback/addFeedback.dart';
|
||||||
import '../../../../api/mhsl/server/feedback/addFeedbackParams.dart';
|
import '../../../../api/mhsl/server/feedback/addFeedbackParams.dart';
|
||||||
import '../../../../model/accountData.dart';
|
import '../../../../model/account_data.dart';
|
||||||
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
import '../../../../widget/filePick.dart';
|
import '../../../../widget/file_pick.dart';
|
||||||
import '../../../../widget/focusBehaviour.dart';
|
import '../../../../widget/focus_behaviour.dart';
|
||||||
import '../../../../widget/infoDialog.dart';
|
import '../../../../widget/info_dialog.dart';
|
||||||
|
|
||||||
class FeedbackDialog extends StatefulWidget {
|
class FeedbackDialog extends StatefulWidget {
|
||||||
const FeedbackDialog({super.key});
|
const FeedbackDialog({super.key});
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:screen_brightness/screen_brightness.dart';
|
import 'package:screen_brightness/screen_brightness.dart';
|
||||||
|
|
||||||
import 'appSharePlatformView.dart';
|
import 'app_share_platform_view.dart';
|
||||||
|
|
||||||
class QrShareView extends StatefulWidget {
|
class QrShareView extends StatefulWidget {
|
||||||
const QrShareView({super.key});
|
const QrShareView({super.key});
|
||||||
+5
-5
@@ -1,8 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
import '../../../../widget/sharePositionOrigin.dart';
|
import '../../../../widget/share_position_origin.dart';
|
||||||
import 'qrShareView.dart';
|
|
||||||
|
enum ShareTargetType { qr }
|
||||||
|
|
||||||
class SelectShareTypeDialog extends StatelessWidget {
|
class SelectShareTypeDialog extends StatelessWidget {
|
||||||
const SelectShareTypeDialog({super.key});
|
const SelectShareTypeDialog({super.key});
|
||||||
@@ -14,15 +15,14 @@ class SelectShareTypeDialog extends StatelessWidget {
|
|||||||
leading: const Icon(Icons.qr_code_2_outlined),
|
leading: const Icon(Icons.qr_code_2_outlined),
|
||||||
title: const Text('Per QR-Code'),
|
title: const Text('Per QR-Code'),
|
||||||
trailing: const Icon(Icons.arrow_right),
|
trailing: const Icon(Icons.arrow_right),
|
||||||
onTap: () {
|
onTap: () => Navigator.of(context).pop(ShareTargetType.qr),
|
||||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => const QrShareView()));
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.link_outlined),
|
leading: const Icon(Icons.link_outlined),
|
||||||
title: const Text('Per Link teilen'),
|
title: const Text('Per Link teilen'),
|
||||||
trailing: const Icon(Icons.arrow_right),
|
trailing: const Icon(Icons.arrow_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
SharePlus.instance.share(ShareParams(
|
SharePlus.instance.share(ShareParams(
|
||||||
sharePositionOrigin: SharePositionOrigin.get(context),
|
sharePositionOrigin: SharePositionOrigin.get(context),
|
||||||
subject: 'App Teilen',
|
subject: 'App Teilen',
|
||||||
@@ -4,18 +4,16 @@ 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:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../../extensions/renderNotNull.dart';
|
import '../../extensions/render_not_null.dart';
|
||||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
|
||||||
|
|
||||||
|
import '../../routing/app_routes.dart';
|
||||||
import '../../state/app/modules/app_modules.dart';
|
import '../../state/app/modules/app_modules.dart';
|
||||||
import '../../state/app/modules/settings/bloc/settings_cubit.dart';
|
import '../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
import '../../storage/base/settings.dart' as model;
|
import '../../storage/base/settings.dart' as model;
|
||||||
import '../../widget/centeredLeading.dart';
|
import '../../widget/centered_leading.dart';
|
||||||
import '../../widget/infoDialog.dart';
|
import '../../widget/info_dialog.dart';
|
||||||
import '../settings/defaultSettings.dart';
|
import 'settings/data/default_settings.dart';
|
||||||
import '../settings/settings.dart';
|
import 'more/share/select_share_type_dialog.dart';
|
||||||
import 'more/feedback/feedbackDialog.dart';
|
|
||||||
import 'more/share/selectShareTypeDialog.dart';
|
|
||||||
|
|
||||||
class Overhang extends StatefulWidget {
|
class Overhang extends StatefulWidget {
|
||||||
const Overhang({super.key});
|
const Overhang({super.key});
|
||||||
@@ -41,7 +39,7 @@ class _OverhangState extends State<Overhang> {
|
|||||||
icon: Icon(Icons.undo_outlined)
|
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: () => 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)),
|
IconButton(onPressed: editMode ? null : () => AppRoutes.openSettings(context), icon: const Icon(Icons.settings)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: editMode ? _sorting() : _overhang(),
|
body: editMode ? _sorting() : _overhang(),
|
||||||
@@ -92,7 +90,14 @@ class _OverhangState extends State<Overhang> {
|
|||||||
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: () async {
|
||||||
|
final result = await showDialog<ShareTargetType>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => const SelectShareTypeDialog(),
|
||||||
|
);
|
||||||
|
if (!mounted || result != ShareTargetType.qr) return;
|
||||||
|
if (context.mounted) AppRoutes.openQrShare(context);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: InAppReview.instance.isAvailable(),
|
future: InAppReview.instance.isAvailable(),
|
||||||
@@ -130,7 +135,7 @@ class _OverhangState extends State<Overhang> {
|
|||||||
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: () => AppRoutes.openFeedback(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
+12
-12
@@ -2,18 +2,18 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../state/app/modules/app_modules.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/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';
|
||||||
import '../../storage/timetable/timetableSettings.dart';
|
import '../../../../storage/timetable/timetable_name_mode.dart';
|
||||||
import '../pages/files/files.dart';
|
import '../../../../storage/timetable/timetableSettings.dart';
|
||||||
import '../../storage/timetable/timetable_name_mode.dart';
|
import '../../files/files.dart';
|
||||||
|
|
||||||
class DefaultSettings {
|
class DefaultSettings {
|
||||||
static Settings get() => Settings(
|
static Settings get() => Settings(
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:jiffy/jiffy.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
|
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
|
import '../../../../widget/centered_leading.dart';
|
||||||
|
import '../../../../widget/confirm_dialog.dart';
|
||||||
|
import '../data/default_settings.dart';
|
||||||
|
import '../widgets/privacy_info.dart';
|
||||||
|
import 'dev_tools_section.dart';
|
||||||
|
|
||||||
|
class AboutSection extends StatelessWidget {
|
||||||
|
const AboutSection({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final settings = context.read<SettingsCubit>();
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.live_help_outlined),
|
||||||
|
title: const Text('Informationen und Lizenzen'),
|
||||||
|
onTap: () => _showAppInfo(context),
|
||||||
|
trailing: const Icon(Icons.arrow_right),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.policy_outlined),
|
||||||
|
title: const Text('Impressum & Datenschutz'),
|
||||||
|
onTap: () => _showPrivacyDialog(context),
|
||||||
|
trailing: const Icon(Icons.arrow_right),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ListTile(
|
||||||
|
leading: const CenteredLeading(Icon(Icons.code)),
|
||||||
|
title: const Text('Quellcode MarianumMobile/Client'),
|
||||||
|
subtitle: const Text('GNU GPL v3'),
|
||||||
|
onTap: () => ConfirmDialog.openBrowser(context, 'https://mhsl.eu/gitea/MarianumMobile/Client'),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.developer_mode_outlined),
|
||||||
|
title: const Text('Entwicklermodus'),
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: settings.val().devToolsEnabled,
|
||||||
|
onChanged: (state) => _toggleDeveloperMode(context, settings, state),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: settings.val().devToolsEnabled,
|
||||||
|
child: DevToolsSection(settings: settings),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showAppInfo(BuildContext context) async {
|
||||||
|
final appInfo = await PackageInfo.fromPlatform();
|
||||||
|
if (!context.mounted) return;
|
||||||
|
showAboutDialog(
|
||||||
|
context: context,
|
||||||
|
applicationIcon: const Icon(Icons.apps),
|
||||||
|
applicationName: 'MarianumMobile',
|
||||||
|
applicationVersion: '${appInfo.appName}\n\nPackage: ${appInfo.packageName}\nVersion: ${appInfo.version}\nBuild: ${appInfo.buildNumber}',
|
||||||
|
applicationLegalese: 'Dies ist ein Inoffizieller Nextcloud & Webuntis Client und wird nicht vom Marianum selbst betrieben.\n'
|
||||||
|
'Keinerlei Gewähr für Vollständigkeit, Richtigkeit und Aktualität!\n\n'
|
||||||
|
"${kReleaseMode ? "Production" : "Development"} build\n"
|
||||||
|
'Marianum Fulda 2023-${Jiffy.now().year}\nElias Müller',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showPrivacyDialog(BuildContext context) => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => SimpleDialog(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const CenteredLeading(Icon(Icons.school_outlined)),
|
||||||
|
title: const Text('Infos zum Marianum Fulda'),
|
||||||
|
subtitle: const Text('Für Talk-Chats und Dateien'),
|
||||||
|
trailing: const Icon(Icons.arrow_right),
|
||||||
|
onTap: () => PrivacyInfo(
|
||||||
|
providerText: 'Marianum',
|
||||||
|
imprintUrl: 'https://www.marianum-fulda.de/impressum',
|
||||||
|
privacyUrl: 'https://www.marianum-fulda.de/datenschutz',
|
||||||
|
).showPopup(context),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const CenteredLeading(Icon(Icons.date_range_outlined)),
|
||||||
|
title: const Text('Infos zu Web-/ Untis'),
|
||||||
|
subtitle: const Text('Für den Stundenplan'),
|
||||||
|
trailing: const Icon(Icons.arrow_right),
|
||||||
|
onTap: () => PrivacyInfo(
|
||||||
|
providerText: 'Untis',
|
||||||
|
imprintUrl: 'https://www.untis.at/impressum',
|
||||||
|
privacyUrl: 'https://www.untis.at/datenschutz-wu-apps',
|
||||||
|
).showPopup(context),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const CenteredLeading(Icon(Icons.send_time_extension_outlined)),
|
||||||
|
title: const Text('Infos zu mhsl'),
|
||||||
|
subtitle: const Text('Für Countdowns, Marianum Message und mehr'),
|
||||||
|
trailing: const Icon(Icons.arrow_right),
|
||||||
|
onTap: () => PrivacyInfo(
|
||||||
|
providerText: 'mhsl',
|
||||||
|
imprintUrl: 'https://mhsl.eu/id.html',
|
||||||
|
privacyUrl: 'https://mhsl.eu/datenschutz.html',
|
||||||
|
).showPopup(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
void _toggleDeveloperMode(BuildContext context, SettingsCubit settings, bool? state) {
|
||||||
|
void apply() {
|
||||||
|
final enabled = state ?? false;
|
||||||
|
settings.val(write: true).devToolsEnabled = enabled;
|
||||||
|
if (!enabled) settings.val(write: true).devToolsSettings = DefaultSettings.get().devToolsSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state!) {
|
||||||
|
apply();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmDialog(
|
||||||
|
title: 'Entwicklermodus',
|
||||||
|
content: 'Die Entwickleransicht bietet erweiterte Funktionen, die für den üblichen Gebrauch nicht benötigt werden.\n\n'
|
||||||
|
'Die Verwendung der Tools kann darüber hinaus bei falscher Verwendung zu Fehlern führen.\n\n'
|
||||||
|
'Aktivieren auf eigene Verantwortung.',
|
||||||
|
confirmButton: 'Ja, ich verstehe das Risiko',
|
||||||
|
cancelButton: 'Nein, zurück zur App',
|
||||||
|
onConfirm: apply,
|
||||||
|
).asDialog(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../../../../model/account_data.dart';
|
||||||
|
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
|
import '../../../../widget/centered_leading.dart';
|
||||||
|
import '../../../../widget/confirm_dialog.dart';
|
||||||
|
import '../../../../widget/debug/cache_view.dart';
|
||||||
|
|
||||||
|
class AccountSection extends StatelessWidget {
|
||||||
|
const AccountSection({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => ListTile(
|
||||||
|
leading: const CenteredLeading(Icon(Icons.logout_outlined)),
|
||||||
|
title: const Text('Konto abmelden'),
|
||||||
|
subtitle: Text('Angemeldet als ${AccountData().getUsername()}'),
|
||||||
|
onTap: () => _showLogoutDialog(context),
|
||||||
|
);
|
||||||
|
|
||||||
|
void _showLogoutDialog(BuildContext context) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ConfirmDialog(
|
||||||
|
title: 'Abmelden?',
|
||||||
|
content: 'Möchtest du dich wirklich abmelden?',
|
||||||
|
confirmButton: 'Abmelden',
|
||||||
|
onConfirm: () async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.clear();
|
||||||
|
PaintingBinding.instance.imageCache.clear();
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.read<SettingsCubit>().reset();
|
||||||
|
const CacheView().clear();
|
||||||
|
AccountData().removeData(context: context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
|
import '../../../../theming/app_theme.dart';
|
||||||
|
|
||||||
|
class AppearanceSection extends StatelessWidget {
|
||||||
|
const AppearanceSection({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final settings = context.read<SettingsCubit>();
|
||||||
|
return ListTile(
|
||||||
|
leading: const Icon(Icons.dark_mode_outlined),
|
||||||
|
title: const Text('Farbgebung'),
|
||||||
|
trailing: DropdownButton<ThemeMode>(
|
||||||
|
value: settings.val().appTheme,
|
||||||
|
icon: const Icon(Icons.arrow_drop_down),
|
||||||
|
items: ThemeMode.values
|
||||||
|
.map((e) => DropdownMenuItem<ThemeMode>(
|
||||||
|
value: e,
|
||||||
|
enabled: e != settings.val().appTheme,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(AppTheme.getDisplayOptions(e).icon),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(AppTheme.getDisplayOptions(e).displayName),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (e) => settings.val(write: true).appTheme = e!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-12
@@ -4,21 +4,22 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../../state/app/modules/settings/bloc/settings_cubit.dart';
|
import '../../../../routing/app_routes.dart';
|
||||||
import '../../widget/centeredLeading.dart';
|
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
import '../../widget/confirmDialog.dart';
|
import '../../../../widget/centered_leading.dart';
|
||||||
import '../../widget/debug/cacheView.dart';
|
import '../../../../widget/confirm_dialog.dart';
|
||||||
import '../../widget/debug/jsonViewer.dart';
|
import '../../../../widget/debug/cache_view.dart';
|
||||||
|
import '../../../../widget/debug/json_viewer.dart';
|
||||||
|
|
||||||
class DevToolsSettings extends StatefulWidget {
|
class DevToolsSection extends StatefulWidget {
|
||||||
final SettingsCubit settings;
|
final SettingsCubit settings;
|
||||||
const DevToolsSettings({required this.settings, super.key});
|
const DevToolsSection({required this.settings, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DevToolsSettings> createState() => _DevToolsSettingsState();
|
State<DevToolsSection> createState() => _DevToolsSectionState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DevToolsSettingsState extends State<DevToolsSettings> {
|
class _DevToolsSectionState extends State<DevToolsSection> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Column(
|
Widget build(BuildContext context) => Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -96,9 +97,7 @@ class _DevToolsSettingsState extends State<DevToolsSettings> {
|
|||||||
future: const CacheView().totalSize(),
|
future: const CacheView().totalSize(),
|
||||||
builder: (context, snapshot) => Text("etwa ${snapshot.hasError ? "?" : snapshot.hasData ? filesize(snapshot.data) : "..."}\nLange tippen um zu löschen"),
|
builder: (context, snapshot) => Text("etwa ${snapshot.hasError ? "?" : snapshot.hasData ? filesize(snapshot.data) : "..."}\nLange tippen um zu löschen"),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () => AppRoutes.openCacheView(context),
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => const CacheView()));
|
|
||||||
},
|
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
ConfirmDialog(
|
ConfirmDialog(
|
||||||
title: 'App-Cache löschen',
|
title: 'App-Cache löschen',
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
|
|
||||||
|
class FilesSection extends StatelessWidget {
|
||||||
|
const FilesSection({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final settings = context.read<SettingsCubit>();
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
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!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../../../../notification/notify_updater.dart';
|
||||||
|
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
|
import '../../../../widget/centered_leading.dart';
|
||||||
|
|
||||||
|
class TalkSection extends StatelessWidget {
|
||||||
|
const TalkSection({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final settings = context.read<SettingsCubit>();
|
||||||
|
final talkSettings = settings.val().talkSettings;
|
||||||
|
final notificationSettings = settings.val().notificationSettings;
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.star_border),
|
||||||
|
title: const Text('Favoriten im Talk nach oben sortieren'),
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: talkSettings.sortFavoritesToTop,
|
||||||
|
onChanged: (e) => settings.val(write: true).talkSettings.sortFavoritesToTop = e!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.mark_email_unread_outlined),
|
||||||
|
title: const Text('Ungelesene Chats nach oben sortieren'),
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: talkSettings.sortUnreadToTop,
|
||||||
|
onChanged: (e) => settings.val(write: true).talkSettings.sortUnreadToTop = e!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const CenteredLeading(Icon(Icons.notifications_active_outlined)),
|
||||||
|
title: const Text('Push-Benachrichtigungen aktivieren'),
|
||||||
|
subtitle: const Text('Lange tippen für mehr Informationen'),
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: notificationSettings.enabled,
|
||||||
|
onChanged: (e) {
|
||||||
|
if (e!) {
|
||||||
|
NotifyUpdater.enableAfterDisclaimer(settings).asDialog(context);
|
||||||
|
} else {
|
||||||
|
settings.val(write: true).notificationSettings.enabled = e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onLongPress: () => _showInfoDialog(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showInfoDialog(BuildContext context) => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Info über Push'),
|
||||||
|
content: const SingleChildScrollView(
|
||||||
|
child: Text(
|
||||||
|
"Aufgrund technischer Limitationen müssen Push-Nachrichten über einen externen Server - hier 'mhsl.eu' (Author dieser App) - erfolgen.\n\n"
|
||||||
|
'Wenn Push aktiviert wird, werden deine Zugangsdaten und ein Token verschlüsselt an den Betreiber gesendet und von ihm unverschlüsselt gespeichert.\n\n'
|
||||||
|
'Der extene Server verwendet die Zugangsdaten um sich maschinell in Nextcloud Talk anzumelden und via Websockets auf neue Nachrichten zu warten.\n\n'
|
||||||
|
'Wenn eine neue Nachricht eintrifft wird dein Telefon via FBC-Messaging (Google Firebase Push) vom externen Server benachrichtigt.\n\n'
|
||||||
|
'Behalte im Hinterkopf, dass deine Zugangsdaten auf einem externen Server gespeichert werden und dies trotz bester Absichten ein Sicherheitsrisiko sein kann!',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Zurück')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
|
import '../../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
||||||
|
import '../../../../storage/timetable/timetable_name_mode.dart';
|
||||||
|
|
||||||
|
class TimetableSection extends StatelessWidget {
|
||||||
|
const TimetableSection({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final settings = context.read<SettingsCubit>();
|
||||||
|
final timetableBloc = context.read<TimetableBloc>();
|
||||||
|
final timetableSettings = settings.val().timetableSettings;
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.abc_outlined),
|
||||||
|
title: const Text('Fachbezeichnung'),
|
||||||
|
trailing: DropdownButton<TimetableNameMode>(
|
||||||
|
value: timetableSettings.timetableNameMode,
|
||||||
|
icon: const Icon(Icons.arrow_drop_down),
|
||||||
|
items: TimetableNameMode.values
|
||||||
|
.map((e) => DropdownMenuItem(
|
||||||
|
value: e,
|
||||||
|
enabled: e != timetableSettings.timetableNameMode,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(TimetableNameModes.getDisplayOptions(e).icon),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(TimetableNameModes.getDisplayOptions(e).displayName),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
settings.val(write: true).timetableSettings.timetableNameMode = value!;
|
||||||
|
timetableBloc.refresh();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.calendar_view_day_outlined),
|
||||||
|
title: const Text('Doppelstunden zusammenhängend anzeigen'),
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: timetableSettings.connectDoubleLessons,
|
||||||
|
onChanged: (e) {
|
||||||
|
settings.val(write: true).timetableSettings.connectDoubleLessons = e!;
|
||||||
|
timetableBloc.refresh();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
|
import '../../../storage/base/settings.dart' as model;
|
||||||
|
import 'sections/about_section.dart';
|
||||||
|
import 'sections/account_section.dart';
|
||||||
|
import 'sections/appearance_section.dart';
|
||||||
|
import 'sections/files_section.dart';
|
||||||
|
import 'sections/talk_section.dart';
|
||||||
|
import 'sections/timetable_section.dart';
|
||||||
|
|
||||||
|
class Settings extends StatelessWidget {
|
||||||
|
const Settings({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => BlocBuilder<SettingsCubit, model.Settings>(
|
||||||
|
builder: (context, _) => Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Einstellungen')),
|
||||||
|
body: ListView(
|
||||||
|
children: const [
|
||||||
|
AccountSection(),
|
||||||
|
Divider(),
|
||||||
|
AppearanceSection(),
|
||||||
|
Divider(),
|
||||||
|
TimetableSection(),
|
||||||
|
Divider(),
|
||||||
|
TalkSection(),
|
||||||
|
Divider(),
|
||||||
|
FilesSection(),
|
||||||
|
Divider(),
|
||||||
|
AboutSection(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../widget/centeredLeading.dart';
|
import '../../../../widget/centered_leading.dart';
|
||||||
import '../../widget/confirmDialog.dart';
|
import '../../../../widget/confirm_dialog.dart';
|
||||||
|
|
||||||
class PrivacyInfo {
|
class PrivacyInfo {
|
||||||
String providerText;
|
String providerText;
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:flutter_split_view/flutter_split_view.dart';
|
|
||||||
|
|
||||||
import '../../../state/app/infrastructure/loadableState/loadable_state.dart';
|
|
||||||
import '../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
|
||||||
import '../../../state/app/infrastructure/utilityWidgets/bloc_module.dart';
|
|
||||||
import '../../../state/app/modules/chatList/bloc/chat_list_bloc.dart';
|
|
||||||
import '../../../state/app/modules/chatList/bloc/chat_list_state.dart';
|
|
||||||
import '../../../notification/notifyUpdater.dart';
|
|
||||||
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
|
||||||
import '../../../widget/confirmDialog.dart';
|
|
||||||
import 'components/chatTile.dart';
|
|
||||||
import 'components/splitViewPlaceholder.dart';
|
|
||||||
import 'joinChat.dart';
|
|
||||||
import 'searchChat.dart';
|
|
||||||
|
|
||||||
class ChatList extends StatelessWidget {
|
|
||||||
const ChatList({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => BlocModule<ChatListBloc, LoadableState<ChatListState>>(
|
|
||||||
create: (_) => ChatListBloc(),
|
|
||||||
child: (context, bloc, _) => const _ChatListView(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ChatListView extends StatefulWidget {
|
|
||||||
const _ChatListView();
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<_ChatListView> createState() => _ChatListViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ChatListViewState extends State<_ChatListView> {
|
|
||||||
late final SettingsCubit _settings;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_settings = context.read<SettingsCubit>();
|
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _maybeAskForNotificationPermission());
|
|
||||||
}
|
|
||||||
|
|
||||||
void _maybeAskForNotificationPermission() {
|
|
||||||
final notificationSettings = _settings.val().notificationSettings;
|
|
||||||
if (notificationSettings.enabled || notificationSettings.askUsageDismissed) return;
|
|
||||||
|
|
||||||
_settings.val(write: true).notificationSettings.askUsageDismissed = true;
|
|
||||||
ConfirmDialog(
|
|
||||||
icon: Icons.notifications_active_outlined,
|
|
||||||
title: 'Benachrichtigungen aktivieren',
|
|
||||||
content: 'Auf wunsch kannst du Push-Benachrichtigungen aktivieren. Deine Einstellungen kannst du jederzeit ändern.',
|
|
||||||
confirmButton: 'Weiter',
|
|
||||||
onConfirm: () {
|
|
||||||
FirebaseMessaging.instance.requestPermission(provisional: false).then((value) {
|
|
||||||
if (!mounted) return;
|
|
||||||
switch (value.authorizationStatus) {
|
|
||||||
case AuthorizationStatus.authorized:
|
|
||||||
NotifyUpdater.enableAfterDisclaimer(_settings).asDialog(context);
|
|
||||||
break;
|
|
||||||
case AuthorizationStatus.denied:
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const AlertDialog(
|
|
||||||
content: Text('Du kannst die Benachrichtigungen später jederzeit in den App-Einstellungen aktivieren.'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
).asDialog(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final bloc = context.read<ChatListBloc>();
|
|
||||||
return SplitView.material(
|
|
||||||
placeholder: const SplitViewPlaceholder(),
|
|
||||||
breakpoint: 1000,
|
|
||||||
child: Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Talk'),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.search),
|
|
||||||
onPressed: () {
|
|
||||||
final rooms = bloc.state.data?.rooms;
|
|
||||||
if (rooms == null) return;
|
|
||||||
showSearch(context: context, delegate: SearchChat(rooms.data.toList()));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
heroTag: 'createChat',
|
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
|
||||||
onPressed: () {
|
|
||||||
showSearch(context: context, delegate: JoinChat()).then((username) {
|
|
||||||
if (username == null || !context.mounted) return;
|
|
||||||
ConfirmDialog(
|
|
||||||
title: 'Chat starten',
|
|
||||||
content: "Möchtest du einen Chat mit Nutzer '$username' starten?",
|
|
||||||
confirmButton: 'Chat starten',
|
|
||||||
onConfirm: () {
|
|
||||||
bloc.createDirectChat(username);
|
|
||||||
},
|
|
||||||
).asDialog(context);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: const Icon(Icons.add_comment_outlined),
|
|
||||||
),
|
|
||||||
body: LoadableStateConsumer<ChatListBloc, ChatListState>(
|
|
||||||
child: (state, _) {
|
|
||||||
final rooms = state.rooms;
|
|
||||||
if (rooms == null) return const SizedBox.shrink();
|
|
||||||
|
|
||||||
final talkSettings = context.watch<SettingsCubit>().val().talkSettings;
|
|
||||||
final sorted = rooms.sortBy(
|
|
||||||
lastActivity: true,
|
|
||||||
favoritesToTop: talkSettings.sortFavoritesToTop,
|
|
||||||
unreadToTop: talkSettings.sortUnreadToTop,
|
|
||||||
);
|
|
||||||
|
|
||||||
return ListView(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
children: sorted.map((room) {
|
|
||||||
final hasDraft = _settings.val().talkSettings.drafts.containsKey(room.token);
|
|
||||||
return ChatTile(data: room, hasDraft: hasDraft);
|
|
||||||
}).toList(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_split_view/flutter_split_view.dart';
|
||||||
|
|
||||||
|
import '../../../routing/app_routes.dart';
|
||||||
|
import '../../../state/app/infrastructure/loadableState/loadable_state.dart';
|
||||||
|
import '../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
||||||
|
import '../../../state/app/infrastructure/utilityWidgets/bloc_module.dart';
|
||||||
|
import '../../../state/app/modules/chatList/bloc/chat_list_bloc.dart';
|
||||||
|
import '../../../state/app/modules/chatList/bloc/chat_list_state.dart';
|
||||||
|
import '../../../notification/notify_updater.dart';
|
||||||
|
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
|
import '../../../widget/confirm_dialog.dart';
|
||||||
|
import 'widgets/chat_tile.dart';
|
||||||
|
import 'widgets/split_view_placeholder.dart';
|
||||||
|
import 'join_chat.dart';
|
||||||
|
import 'search_chat.dart';
|
||||||
|
|
||||||
|
class ChatList extends StatelessWidget {
|
||||||
|
const ChatList({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => BlocModule<ChatListBloc, LoadableState<ChatListState>>(
|
||||||
|
create: (_) => ChatListBloc(),
|
||||||
|
child: (context, bloc, _) => const _ChatListView(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatListView extends StatefulWidget {
|
||||||
|
const _ChatListView();
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_ChatListView> createState() => _ChatListViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatListViewState extends State<_ChatListView> {
|
||||||
|
late final SettingsCubit _settings;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_settings = context.read<SettingsCubit>();
|
||||||
|
|
||||||
|
AppRoutes.pendingChatToken.addListener(_maybeOpenPendingChat);
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
_maybeAskForNotificationPermission();
|
||||||
|
_maybeOpenPendingChat();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
AppRoutes.pendingChatToken.removeListener(_maybeOpenPendingChat);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _maybeOpenPendingChat() {
|
||||||
|
if (!mounted) return;
|
||||||
|
final resolved = AppRoutes.resolvePendingChat(context);
|
||||||
|
if (resolved == null) return;
|
||||||
|
AppRoutes.pendingChatToken.value = null;
|
||||||
|
|
||||||
|
// Replace any chat already pushed on top of the chat list so a freshly
|
||||||
|
// tapped notification doesn't stack indefinitely on previous chats.
|
||||||
|
final navigator = Navigator.of(context);
|
||||||
|
if (navigator.canPop()) {
|
||||||
|
navigator.popUntil((route) => route.isFirst);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppRoutes.openChatView(
|
||||||
|
context,
|
||||||
|
room: resolved.room,
|
||||||
|
selfId: resolved.selfId,
|
||||||
|
avatar: resolved.avatar,
|
||||||
|
overrideToSingleSubScreen: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _maybeAskForNotificationPermission() {
|
||||||
|
final notificationSettings = _settings.val().notificationSettings;
|
||||||
|
if (notificationSettings.enabled || notificationSettings.askUsageDismissed) return;
|
||||||
|
|
||||||
|
_settings.val(write: true).notificationSettings.askUsageDismissed = true;
|
||||||
|
ConfirmDialog(
|
||||||
|
icon: Icons.notifications_active_outlined,
|
||||||
|
title: 'Benachrichtigungen aktivieren',
|
||||||
|
content: 'Auf wunsch kannst du Push-Benachrichtigungen aktivieren. Deine Einstellungen kannst du jederzeit ändern.',
|
||||||
|
confirmButton: 'Weiter',
|
||||||
|
onConfirm: () {
|
||||||
|
FirebaseMessaging.instance.requestPermission(provisional: false).then((value) {
|
||||||
|
if (!mounted) return;
|
||||||
|
switch (value.authorizationStatus) {
|
||||||
|
case AuthorizationStatus.authorized:
|
||||||
|
NotifyUpdater.enableAfterDisclaimer(_settings).asDialog(context);
|
||||||
|
break;
|
||||||
|
case AuthorizationStatus.denied:
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => const AlertDialog(
|
||||||
|
content: Text('Du kannst die Benachrichtigungen später jederzeit in den App-Einstellungen aktivieren.'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
).asDialog(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final bloc = context.read<ChatListBloc>();
|
||||||
|
return SplitView.material(
|
||||||
|
placeholder: const SplitViewPlaceholder(),
|
||||||
|
breakpoint: 1000,
|
||||||
|
child: BlocListener<ChatListBloc, LoadableState<ChatListState>>(
|
||||||
|
listener: (_, _) => _maybeOpenPendingChat(),
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Talk'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.search),
|
||||||
|
onPressed: () {
|
||||||
|
final rooms = bloc.state.data?.rooms;
|
||||||
|
if (rooms == null) return;
|
||||||
|
showSearch(context: context, delegate: SearchChat(rooms.data.toList()));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
heroTag: 'createChat',
|
||||||
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
|
onPressed: () {
|
||||||
|
showSearch(context: context, delegate: JoinChat()).then((username) {
|
||||||
|
if (username == null || !context.mounted) return;
|
||||||
|
ConfirmDialog(
|
||||||
|
title: 'Chat starten',
|
||||||
|
content: "Möchtest du einen Chat mit Nutzer '$username' starten?",
|
||||||
|
confirmButton: 'Chat starten',
|
||||||
|
onConfirm: () {
|
||||||
|
bloc.createDirectChat(username);
|
||||||
|
},
|
||||||
|
).asDialog(context);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Icon(Icons.add_comment_outlined),
|
||||||
|
),
|
||||||
|
body: LoadableStateConsumer<ChatListBloc, ChatListState>(
|
||||||
|
child: (state, _) {
|
||||||
|
final rooms = state.rooms;
|
||||||
|
if (rooms == null) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
final talkSettings = context.watch<SettingsCubit>().val().talkSettings;
|
||||||
|
final sorted = rooms.sortBy(
|
||||||
|
lastActivity: true,
|
||||||
|
favoritesToTop: talkSettings.sortFavoritesToTop,
|
||||||
|
unreadToTop: talkSettings.sortUnreadToTop,
|
||||||
|
);
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
children: sorted.map((room) {
|
||||||
|
final hasDraft = _settings.val().talkSettings.drafts.containsKey(room.token);
|
||||||
|
return ChatTile(data: room, hasDraft: hasDraft);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,17 +3,17 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
|
|
||||||
import '../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
import '../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||||
import '../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
import '../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
||||||
import '../../../extensions/dateTime.dart';
|
import '../../../extensions/date_time.dart';
|
||||||
import '../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
import '../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
||||||
import '../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
import '../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||||
import '../../../state/app/modules/chat/bloc/chat_state.dart';
|
import '../../../state/app/modules/chat/bloc/chat_state.dart';
|
||||||
import '../../../theming/appTheme.dart';
|
import '../../../theming/app_theme.dart';
|
||||||
import '../../../widget/clickableAppBar.dart';
|
import '../../../widget/clickable_app_bar.dart';
|
||||||
import '../../../widget/userAvatar.dart';
|
import '../../../widget/user_avatar.dart';
|
||||||
import 'chatDetails/chatInfo.dart';
|
import 'details/chat_info.dart';
|
||||||
import 'components/chatBubble.dart';
|
import 'widgets/chat_bubble.dart';
|
||||||
import 'components/chatTextfield.dart';
|
import 'widgets/chat_textfield.dart';
|
||||||
import 'talkNavigator.dart';
|
import 'talk_navigator.dart';
|
||||||
|
|
||||||
class ChatView extends StatefulWidget {
|
class ChatView extends StatefulWidget {
|
||||||
final GetRoomResponseObject room;
|
final GetRoomResponseObject room;
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import 'package:bubble/bubble.dart';
|
import 'package:bubble/bubble.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../../../theming/appTheme.dart';
|
import '../../../../theming/app_theme.dart';
|
||||||
|
|
||||||
extension ColorExtensions on Color {
|
extension ColorExtensions on Color {
|
||||||
Color invert() {
|
Color invert() {
|
||||||
+3
-3
@@ -5,9 +5,9 @@ import 'package:flutter_linkify/flutter_linkify.dart';
|
|||||||
|
|
||||||
import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||||
import '../../../../api/marianumcloud/talk/chat/richObjectStringProcessor.dart';
|
import '../../../../api/marianumcloud/talk/chat/richObjectStringProcessor.dart';
|
||||||
import '../../../../model/accountData.dart';
|
import '../../../../model/account_data.dart';
|
||||||
import '../../../../model/endpointData.dart';
|
import '../../../../model/endpoint_data.dart';
|
||||||
import '../../../../utils/UrlOpener.dart';
|
import '../../../../utils/url_opener.dart';
|
||||||
|
|
||||||
class ChatMessage {
|
class ChatMessage {
|
||||||
String originalMessage;
|
String originalMessage;
|
||||||
+5
-5
@@ -3,11 +3,11 @@ import 'package:flutter/material.dart';
|
|||||||
import '../../../../api/marianumcloud/talk/getParticipants/getParticipantsCache.dart';
|
import '../../../../api/marianumcloud/talk/getParticipants/getParticipantsCache.dart';
|
||||||
import '../../../../api/marianumcloud/talk/getParticipants/getParticipantsResponse.dart';
|
import '../../../../api/marianumcloud/talk/getParticipants/getParticipantsResponse.dart';
|
||||||
import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
||||||
import '../../../../widget/largeProfilePictureView.dart';
|
import '../../../../widget/large_profile_picture_view.dart';
|
||||||
import '../../../../widget/loadingSpinner.dart';
|
import '../../../../widget/loading_spinner.dart';
|
||||||
import '../../../../widget/userAvatar.dart';
|
import '../../../../widget/user_avatar.dart';
|
||||||
import '../talkNavigator.dart';
|
import '../talk_navigator.dart';
|
||||||
import 'participants/participantsListView.dart';
|
import 'participants_list_view.dart';
|
||||||
|
|
||||||
class ChatInfo extends StatefulWidget {
|
class ChatInfo extends StatefulWidget {
|
||||||
final GetRoomResponseObject room;
|
final GetRoomResponseObject room;
|
||||||
+8
-8
@@ -1,14 +1,14 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../../api/marianumcloud/talk/getReactions/getReactions.dart';
|
import '../../../../api/marianumcloud/talk/getReactions/getReactions.dart';
|
||||||
import '../../../api/marianumcloud/talk/getReactions/getReactionsResponse.dart';
|
import '../../../../api/marianumcloud/talk/getReactions/getReactionsResponse.dart';
|
||||||
import '../../../model/accountData.dart';
|
import '../../../../model/account_data.dart';
|
||||||
import '../../../widget/centeredLeading.dart';
|
import '../../../../widget/centered_leading.dart';
|
||||||
import '../../../widget/loadingSpinner.dart';
|
import '../../../../widget/loading_spinner.dart';
|
||||||
import '../../../widget/placeholderView.dart';
|
import '../../../../widget/placeholder_view.dart';
|
||||||
import '../../../widget/unimplementedDialog.dart';
|
import '../../../../widget/unimplemented_dialog.dart';
|
||||||
import '../../../widget/userAvatar.dart';
|
import '../../../../widget/user_avatar.dart';
|
||||||
|
|
||||||
class MessageReactions extends StatefulWidget {
|
class MessageReactions extends StatefulWidget {
|
||||||
final String token;
|
final String token;
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../../../../api/marianumcloud/talk/getParticipants/getParticipantsResponse.dart';
|
import '../../../../api/marianumcloud/talk/getParticipants/getParticipantsResponse.dart';
|
||||||
import '../../../../../widget/userAvatar.dart';
|
import '../../../../widget/user_avatar.dart';
|
||||||
|
|
||||||
class ParticipantsListView extends StatelessWidget {
|
class ParticipantsListView extends StatelessWidget {
|
||||||
final GetParticipantsResponse participantsResponse;
|
final GetParticipantsResponse participantsResponse;
|
||||||
@@ -4,8 +4,8 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import '../../../api/marianumcloud/autocomplete/autocompleteApi.dart';
|
import '../../../api/marianumcloud/autocomplete/autocompleteApi.dart';
|
||||||
import '../../../api/marianumcloud/autocomplete/autocompleteResponse.dart';
|
import '../../../api/marianumcloud/autocomplete/autocompleteResponse.dart';
|
||||||
import '../../../model/endpointData.dart';
|
import '../../../model/endpoint_data.dart';
|
||||||
import '../../../widget/placeholderView.dart';
|
import '../../../widget/placeholder_view.dart';
|
||||||
|
|
||||||
class JoinChat extends SearchDelegate<String> {
|
class JoinChat extends SearchDelegate<String> {
|
||||||
CancelableOperation<AutocompleteResponse>? future;
|
CancelableOperation<AutocompleteResponse>? future;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
import '../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
||||||
import 'components/chatTile.dart';
|
import 'widgets/chat_tile.dart';
|
||||||
|
|
||||||
class SearchChat extends SearchDelegate {
|
class SearchChat extends SearchDelegate {
|
||||||
List<GetRoomResponseObject> chats;
|
List<GetRoomResponseObject> chats;
|
||||||
+1
-1
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||||
import '../../../../api/marianumcloud/talk/chat/richObjectStringProcessor.dart';
|
import '../../../../api/marianumcloud/talk/chat/richObjectStringProcessor.dart';
|
||||||
import 'chatBubbleStyles.dart';
|
import '../data/chat_bubble_styles.dart';
|
||||||
|
|
||||||
class AnswerReference extends StatelessWidget {
|
class AnswerReference extends StatelessWidget {
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
+17
-185
@@ -1,31 +1,26 @@
|
|||||||
import 'package:bubble/bubble.dart';
|
import 'package:bubble/bubble.dart';
|
||||||
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart' as emojis;
|
|
||||||
import 'package:flowder/flowder.dart';
|
import 'package:flowder/flowder.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
import 'package:open_filex/open_filex.dart';
|
import 'package:open_filex/open_filex.dart';
|
||||||
import '../../../../api/marianumcloud/talk/getPoll/getPollState.dart';
|
|
||||||
import '../../../../extensions/text.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||||
import '../../../../api/marianumcloud/talk/deleteMessage/deleteMessage.dart';
|
|
||||||
import '../../../../api/marianumcloud/talk/deleteReactMessage/deleteReactMessage.dart';
|
import '../../../../api/marianumcloud/talk/deleteReactMessage/deleteReactMessage.dart';
|
||||||
import '../../../../api/marianumcloud/talk/deleteReactMessage/deleteReactMessageParams.dart';
|
import '../../../../api/marianumcloud/talk/deleteReactMessage/deleteReactMessageParams.dart';
|
||||||
|
import '../../../../api/marianumcloud/talk/getPoll/getPollState.dart';
|
||||||
import '../../../../api/marianumcloud/talk/reactMessage/reactMessage.dart';
|
import '../../../../api/marianumcloud/talk/reactMessage/reactMessage.dart';
|
||||||
import '../../../../api/marianumcloud/talk/reactMessage/reactMessageParams.dart';
|
import '../../../../api/marianumcloud/talk/reactMessage/reactMessageParams.dart';
|
||||||
import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
||||||
|
import '../../../../extensions/text.dart';
|
||||||
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||||
import '../../../../widget/debug/debugTile.dart';
|
import '../../../../widget/loading_spinner.dart';
|
||||||
import '../../../../widget/loadingSpinner.dart';
|
import '../../files/widgets/file_element.dart';
|
||||||
import '../../files/fileElement.dart';
|
import '../data/chat_bubble_styles.dart';
|
||||||
import 'answerReference.dart';
|
import '../data/chat_message.dart';
|
||||||
import 'chatBubbleStyles.dart';
|
import 'answer_reference.dart';
|
||||||
import 'chatMessage.dart';
|
import 'chat_message_options_dialog.dart';
|
||||||
import '../messageReactions.dart';
|
import 'poll_options_list.dart';
|
||||||
import 'pollOptionsList.dart';
|
|
||||||
|
|
||||||
class ChatBubble extends StatefulWidget {
|
class ChatBubble extends StatefulWidget {
|
||||||
final BuildContext context;
|
final BuildContext context;
|
||||||
@@ -77,176 +72,13 @@ class _ChatBubbleState extends State<ChatBubble> with SingleTickerProviderStateM
|
|||||||
}
|
}
|
||||||
|
|
||||||
void showOptionsDialog() {
|
void showOptionsDialog() {
|
||||||
showDialog(context: context, builder: (context) {
|
showChatMessageOptionsDialog(
|
||||||
var commonReactions = <String>['👍', '👎', '😆', '❤️', '👀'];
|
context,
|
||||||
var canReact = widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment;
|
chatData: widget.chatData,
|
||||||
return SimpleDialog(
|
bubbleData: widget.bubbleData,
|
||||||
children: [
|
isSender: widget.isSender,
|
||||||
Visibility(
|
onRefetch: widget.refetch,
|
||||||
visible: canReact,
|
);
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Wrap(
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
children: [
|
|
||||||
...commonReactions.map((e) => TextButton(
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
||||||
minimumSize: const Size(40, 40)
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
ReactMessage(
|
|
||||||
chatToken: widget.chatData.token,
|
|
||||||
messageId: widget.bubbleData.id,
|
|
||||||
params: ReactMessageParams(e),
|
|
||||||
).run().then((value) => widget.refetch(renew: true));
|
|
||||||
},
|
|
||||||
child: Text(e),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
showDialog(context: context, builder: (context) => AlertDialog(
|
|
||||||
contentPadding: const EdgeInsets.all(15),
|
|
||||||
titlePadding: const EdgeInsets.only(left: 6, top: 15),
|
|
||||||
title: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
const Text('Reagieren'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
content: SizedBox(
|
|
||||||
width: 256,
|
|
||||||
height: 270,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
emojis.EmojiPicker(
|
|
||||||
config: emojis.Config(
|
|
||||||
height: 256,
|
|
||||||
// swapCategoryAndBottomBar: true, // TODO this property is no longer supported, need to find an replacement
|
|
||||||
emojiViewConfig: emojis.EmojiViewConfig(
|
|
||||||
backgroundColor: Theme.of(context).canvasColor,
|
|
||||||
recentsLimit: 67,
|
|
||||||
emojiSizeMax: 25,
|
|
||||||
noRecents: const Text('Keine zuletzt verwendeten Emojis'),
|
|
||||||
columns: 7,
|
|
||||||
),
|
|
||||||
bottomActionBarConfig: const emojis.BottomActionBarConfig(
|
|
||||||
enabled: false,
|
|
||||||
),
|
|
||||||
categoryViewConfig: emojis.CategoryViewConfig(
|
|
||||||
backgroundColor: Theme.of(context).hoverColor,
|
|
||||||
iconColorSelected: Theme.of(context).primaryColor,
|
|
||||||
indicatorColor: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
searchViewConfig: emojis.SearchViewConfig(
|
|
||||||
backgroundColor: Theme.of(context).dividerColor,
|
|
||||||
// buttonColor: Theme.of(context).dividerColor, // TODO property no longer supported
|
|
||||||
hintText: 'Suchen',
|
|
||||||
buttonIconColor: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onEmojiSelected: (emojis.Category? category, emojis.Emoji emoji) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
ReactMessage(
|
|
||||||
chatToken: widget.chatData.token,
|
|
||||||
messageId: widget.bubbleData.id,
|
|
||||||
params: ReactMessageParams(emoji.emoji),
|
|
||||||
).run().then((value) => widget.refetch(renew: true));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
style: IconButton.styleFrom(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
||||||
minimumSize: const Size(40, 40),
|
|
||||||
),
|
|
||||||
icon: const Icon(Icons.add_circle_outline_outlined),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Visibility(
|
|
||||||
visible: widget.bubbleData.isReplyable,
|
|
||||||
child: ListTile(
|
|
||||||
leading: const Icon(Icons.reply_outlined),
|
|
||||||
title: const Text('Antworten'),
|
|
||||||
onTap: () {
|
|
||||||
context.read<ChatBloc>().setReferenceMessageId(widget.bubbleData.id);
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Visibility(
|
|
||||||
visible: canReact,
|
|
||||||
child: ListTile(
|
|
||||||
leading: const Icon(Icons.emoji_emotions_outlined),
|
|
||||||
title: const Text('Reaktionen'),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => MessageReactions(
|
|
||||||
token: widget.chatData.token,
|
|
||||||
messageId: widget.bubbleData.id,
|
|
||||||
)));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Visibility(
|
|
||||||
visible: widget.bubbleData.message != '{file}',
|
|
||||||
child: ListTile(
|
|
||||||
leading: const Icon(Icons.copy),
|
|
||||||
title: const Text('Nachricht kopieren'),
|
|
||||||
onTap: () => {
|
|
||||||
Clipboard.setData(ClipboardData(text: widget.bubbleData.message)),
|
|
||||||
Navigator.of(context).pop(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Visibility(
|
|
||||||
visible: !kReleaseMode && !widget.isSender && widget.chatData.type != GetRoomResponseObjectConversationType.oneToOne,
|
|
||||||
child: ListTile(
|
|
||||||
leading: const Icon(Icons.sms_outlined),
|
|
||||||
title: Text("Private Nachricht an '${widget.bubbleData.actorDisplayName}'"),
|
|
||||||
onTap: () => {
|
|
||||||
Navigator.of(context).pop()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Visibility(
|
|
||||||
visible: widget.isSender && DateTime.fromMillisecondsSinceEpoch(widget.bubbleData.timestamp * 1000).add(const Duration(hours: 6)).isAfter(DateTime.now()),
|
|
||||||
child: ListTile(
|
|
||||||
leading: const Icon(Icons.delete_outline),
|
|
||||||
title: const Text('Nachricht löschen'),
|
|
||||||
onTap: () {
|
|
||||||
DeleteMessage(widget.chatData.token, widget.bubbleData.id).run().then((value) {
|
|
||||||
if (!context.mounted) return;
|
|
||||||
context.read<ChatBloc>().refresh();
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DebugTile(context).jsonData(widget.bubbleData.toJson()),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart' as emojis;
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||||
|
import '../../../../api/marianumcloud/talk/deleteMessage/deleteMessage.dart';
|
||||||
|
import '../../../../api/marianumcloud/talk/reactMessage/reactMessage.dart';
|
||||||
|
import '../../../../api/marianumcloud/talk/reactMessage/reactMessageParams.dart';
|
||||||
|
import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
||||||
|
import '../../../../routing/app_routes.dart';
|
||||||
|
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||||
|
import '../../../../widget/debug/debug_tile.dart';
|
||||||
|
|
||||||
|
const _commonReactions = <String>['👍', '👎', '😆', '❤️', '👀'];
|
||||||
|
|
||||||
|
/// Long-press / double-tap options dialog for a single chat message bubble.
|
||||||
|
/// The hosting [ChatBubble] keeps responsibility for rendering the bubble;
|
||||||
|
/// this file owns the modal interactions (react, reply, copy, delete, ...).
|
||||||
|
Future<void> showChatMessageOptionsDialog(
|
||||||
|
BuildContext context, {
|
||||||
|
required GetRoomResponseObject chatData,
|
||||||
|
required GetChatResponseObject bubbleData,
|
||||||
|
required bool isSender,
|
||||||
|
required void Function({bool renew}) onRefetch,
|
||||||
|
}) {
|
||||||
|
final parentContext = context;
|
||||||
|
final canReact = bubbleData.messageType == GetRoomResponseObjectMessageType.comment;
|
||||||
|
final canDelete = isSender &&
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(bubbleData.timestamp * 1000)
|
||||||
|
.add(const Duration(hours: 6))
|
||||||
|
.isAfter(DateTime.now());
|
||||||
|
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (dialogCtx) => SimpleDialog(
|
||||||
|
children: [
|
||||||
|
if (canReact)
|
||||||
|
_ReactionsRow(
|
||||||
|
chatToken: chatData.token,
|
||||||
|
messageId: bubbleData.id,
|
||||||
|
onRefetch: onRefetch,
|
||||||
|
dialogContext: dialogCtx,
|
||||||
|
),
|
||||||
|
if (bubbleData.isReplyable)
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.reply_outlined),
|
||||||
|
title: const Text('Antworten'),
|
||||||
|
onTap: () {
|
||||||
|
dialogCtx.read<ChatBloc>().setReferenceMessageId(bubbleData.id);
|
||||||
|
Navigator.of(dialogCtx).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (canReact)
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.emoji_emotions_outlined),
|
||||||
|
title: const Text('Reaktionen'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(dialogCtx).pop();
|
||||||
|
if (!parentContext.mounted) return;
|
||||||
|
AppRoutes.openMessageReactions(parentContext, chatData.token, bubbleData.id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (bubbleData.message != '{file}')
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.copy),
|
||||||
|
title: const Text('Nachricht kopieren'),
|
||||||
|
onTap: () {
|
||||||
|
Clipboard.setData(ClipboardData(text: bubbleData.message));
|
||||||
|
Navigator.of(dialogCtx).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (!kReleaseMode && !isSender && chatData.type != GetRoomResponseObjectConversationType.oneToOne)
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.sms_outlined),
|
||||||
|
title: Text("Private Nachricht an '${bubbleData.actorDisplayName}'"),
|
||||||
|
onTap: () => Navigator.of(dialogCtx).pop(),
|
||||||
|
),
|
||||||
|
if (canDelete)
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.delete_outline),
|
||||||
|
title: const Text('Nachricht löschen'),
|
||||||
|
onTap: () async {
|
||||||
|
await DeleteMessage(chatData.token, bubbleData.id).run();
|
||||||
|
if (!dialogCtx.mounted) return;
|
||||||
|
dialogCtx.read<ChatBloc>().refresh();
|
||||||
|
Navigator.of(dialogCtx).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DebugTile(dialogCtx).jsonData(bubbleData.toJson()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ReactionsRow extends StatelessWidget {
|
||||||
|
final String chatToken;
|
||||||
|
final int messageId;
|
||||||
|
final void Function({bool renew}) onRefetch;
|
||||||
|
final BuildContext dialogContext;
|
||||||
|
|
||||||
|
const _ReactionsRow({
|
||||||
|
required this.chatToken,
|
||||||
|
required this.messageId,
|
||||||
|
required this.onRefetch,
|
||||||
|
required this.dialogContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
void _react(String emoji) {
|
||||||
|
Navigator.of(dialogContext).pop();
|
||||||
|
ReactMessage(
|
||||||
|
chatToken: chatToken,
|
||||||
|
messageId: messageId,
|
||||||
|
params: ReactMessageParams(emoji),
|
||||||
|
).run().then((_) => onRefetch(renew: true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Wrap(
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
children: [
|
||||||
|
..._commonReactions.map(
|
||||||
|
(emoji) => TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
minimumSize: const Size(40, 40),
|
||||||
|
),
|
||||||
|
onPressed: () => _react(emoji),
|
||||||
|
child: Text(emoji),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _showEmojiPicker(context),
|
||||||
|
style: IconButton.styleFrom(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
minimumSize: const Size(40, 40),
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.add_circle_outline_outlined),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
void _showEmojiPicker(BuildContext rowContext) {
|
||||||
|
showDialog(
|
||||||
|
context: rowContext,
|
||||||
|
builder: (pickerCtx) => AlertDialog(
|
||||||
|
contentPadding: const EdgeInsets.all(15),
|
||||||
|
titlePadding: const EdgeInsets.only(left: 6, top: 15),
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Navigator.of(pickerCtx).pop(),
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
const Text('Reagieren'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
content: SizedBox(
|
||||||
|
width: 256,
|
||||||
|
height: 270,
|
||||||
|
child: emojis.EmojiPicker(
|
||||||
|
config: emojis.Config(
|
||||||
|
height: 256,
|
||||||
|
emojiViewConfig: emojis.EmojiViewConfig(
|
||||||
|
backgroundColor: Theme.of(pickerCtx).canvasColor,
|
||||||
|
recentsLimit: 67,
|
||||||
|
emojiSizeMax: 25,
|
||||||
|
noRecents: const Text('Keine zuletzt verwendeten Emojis'),
|
||||||
|
columns: 7,
|
||||||
|
),
|
||||||
|
bottomActionBarConfig: const emojis.BottomActionBarConfig(enabled: false),
|
||||||
|
categoryViewConfig: emojis.CategoryViewConfig(
|
||||||
|
backgroundColor: Theme.of(pickerCtx).hoverColor,
|
||||||
|
iconColorSelected: Theme.of(pickerCtx).primaryColor,
|
||||||
|
indicatorColor: Theme.of(pickerCtx).primaryColor,
|
||||||
|
),
|
||||||
|
searchViewConfig: emojis.SearchViewConfig(
|
||||||
|
backgroundColor: Theme.of(pickerCtx).dividerColor,
|
||||||
|
hintText: 'Suchen',
|
||||||
|
buttonIconColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onEmojiSelected: (_, emoji) {
|
||||||
|
Navigator.of(pickerCtx).pop();
|
||||||
|
_react(emoji.emoji);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-4
@@ -12,10 +12,10 @@ import '../../../../api/marianumcloud/talk/sendMessage/sendMessageParams.dart';
|
|||||||
import '../../../../api/marianumcloud/webdav/webdavApi.dart';
|
import '../../../../api/marianumcloud/webdav/webdavApi.dart';
|
||||||
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||||
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
import '../../../../widget/filePick.dart';
|
import '../../../../widget/file_pick.dart';
|
||||||
import '../../../../widget/focusBehaviour.dart';
|
import '../../../../widget/focus_behaviour.dart';
|
||||||
import '../../files/filesUploadDialog.dart';
|
import '../../files/files_upload_dialog.dart';
|
||||||
import 'answerReference.dart';
|
import 'answer_reference.dart';
|
||||||
|
|
||||||
class ChatTextfield extends StatefulWidget {
|
class ChatTextfield extends StatefulWidget {
|
||||||
final String sendToToken;
|
final String sendToToken;
|
||||||
+6
-6
@@ -9,14 +9,14 @@ import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
|||||||
import '../../../../api/marianumcloud/talk/setFavorite/setFavorite.dart';
|
import '../../../../api/marianumcloud/talk/setFavorite/setFavorite.dart';
|
||||||
import '../../../../api/marianumcloud/talk/setReadMarker/setReadMarker.dart';
|
import '../../../../api/marianumcloud/talk/setReadMarker/setReadMarker.dart';
|
||||||
import '../../../../api/marianumcloud/talk/setReadMarker/setReadMarkerParams.dart';
|
import '../../../../api/marianumcloud/talk/setReadMarker/setReadMarkerParams.dart';
|
||||||
import '../../../../model/accountData.dart';
|
import '../../../../model/account_data.dart';
|
||||||
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||||
import '../../../../state/app/modules/chatList/bloc/chat_list_bloc.dart';
|
import '../../../../state/app/modules/chatList/bloc/chat_list_bloc.dart';
|
||||||
import '../../../../widget/confirmDialog.dart';
|
import '../../../../widget/confirm_dialog.dart';
|
||||||
import '../../../../widget/debug/debugTile.dart';
|
import '../../../../widget/debug/debug_tile.dart';
|
||||||
import '../../../../widget/userAvatar.dart';
|
import '../../../../widget/user_avatar.dart';
|
||||||
import '../chatView.dart';
|
import '../chat_view.dart';
|
||||||
import '../talkNavigator.dart';
|
import '../talk_navigator.dart';
|
||||||
|
|
||||||
class ChatTile extends StatefulWidget {
|
class ChatTile extends StatefulWidget {
|
||||||
final GetRoomResponseObject data;
|
final GetRoomResponseObject data;
|
||||||
+1
-1
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||||
|
|
||||||
import '../../../../api/marianumcloud/talk/getPoll/getPollStateResponse.dart';
|
import '../../../../api/marianumcloud/talk/getPoll/getPollStateResponse.dart';
|
||||||
import '../../../../utils/UrlOpener.dart';
|
import '../../../../utils/url_opener.dart';
|
||||||
|
|
||||||
class PollOptionsList extends StatefulWidget {
|
class PollOptionsList extends StatefulWidget {
|
||||||
final GetPollStateResponseObject pollData;
|
final GetPollStateResponseObject pollData;
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../../../theming/appTheme.dart';
|
import '../../../../theming/app_theme.dart';
|
||||||
|
|
||||||
class SplitViewPlaceholder extends StatelessWidget {
|
class SplitViewPlaceholder extends StatelessWidget {
|
||||||
const SplitViewPlaceholder({super.key});
|
const SplitViewPlaceholder({super.key});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../../../theming/darkAppTheme.dart';
|
import '../../../../theming/dark_app_theme.dart';
|
||||||
|
|
||||||
enum CustomTimetableColors { orange, red, green, blue }
|
enum CustomTimetableColors { orange, red, green, blue }
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import 'package:rrule_generator/rrule_generator.dart';
|
|||||||
import 'package:time_range_picker/time_range_picker.dart';
|
import 'package:time_range_picker/time_range_picker.dart';
|
||||||
|
|
||||||
import '../../../../api/mhsl/customTimetableEvent/customTimetableEvent.dart';
|
import '../../../../api/mhsl/customTimetableEvent/customTimetableEvent.dart';
|
||||||
import '../../../../extensions/dateTime.dart';
|
import '../../../../extensions/date_time.dart';
|
||||||
import '../../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
import '../../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
||||||
import '../../../../widget/focusBehaviour.dart';
|
import '../../../../widget/focus_behaviour.dart';
|
||||||
import '../../../../widget/infoDialog.dart';
|
import '../../../../widget/info_dialog.dart';
|
||||||
import 'custom_event_colors.dart';
|
import 'custom_event_colors.dart';
|
||||||
|
|
||||||
class CustomEventEditDialog extends StatefulWidget {
|
class CustomEventEditDialog extends StatefulWidget {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import 'package:jiffy/jiffy.dart';
|
|||||||
import '../../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
import '../../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
||||||
import '../../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
import '../../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
||||||
import '../../../../state/app/modules/timetable/bloc/timetable_state.dart';
|
import '../../../../state/app/modules/timetable/bloc/timetable_state.dart';
|
||||||
import '../../../../widget/centeredLeading.dart';
|
import '../../../../widget/centered_leading.dart';
|
||||||
import '../../../../widget/placeholderView.dart';
|
import '../../../../widget/placeholder_view.dart';
|
||||||
import '../details/delete_custom_event.dart';
|
import '../details/delete_custom_event.dart';
|
||||||
import 'custom_event_edit_dialog.dart';
|
import 'custom_event_edit_dialog.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import 'package:jiffy/jiffy.dart';
|
|||||||
import 'package:rrule/rrule.dart';
|
import 'package:rrule/rrule.dart';
|
||||||
|
|
||||||
import '../../../../api/mhsl/customTimetableEvent/customTimetableEvent.dart';
|
import '../../../../api/mhsl/customTimetableEvent/customTimetableEvent.dart';
|
||||||
import '../../../../widget/centeredLeading.dart';
|
import '../../../../widget/centered_leading.dart';
|
||||||
import '../../../../widget/debug/debugTile.dart';
|
import '../../../../widget/debug/debug_tile.dart';
|
||||||
import '../custom_events/custom_event_edit_dialog.dart';
|
import '../custom_events/custom_event_edit_dialog.dart';
|
||||||
import '_bottom_sheet.dart';
|
import '_bottom_sheet.dart';
|
||||||
import 'delete_custom_event.dart';
|
import 'delete_custom_event.dart';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
|
|
||||||
import '../../../../api/mhsl/customTimetableEvent/customTimetableEvent.dart';
|
import '../../../../api/mhsl/customTimetableEvent/customTimetableEvent.dart';
|
||||||
import '../../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
import '../../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
||||||
import '../../../../widget/confirmDialog.dart';
|
import '../../../../widget/confirm_dialog.dart';
|
||||||
|
|
||||||
Completer<void> showDeleteCustomEventDialog(BuildContext context, CustomTimetableEvent event) {
|
Completer<void> showDeleteCustomEventDialog(BuildContext context, CustomTimetableEvent event) {
|
||||||
final completer = Completer<void>();
|
final completer = Completer<void>();
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
|
||||||
import 'package:syncfusion_flutter_calendar/calendar.dart';
|
import 'package:syncfusion_flutter_calendar/calendar.dart';
|
||||||
|
|
||||||
import '../../../../api/webuntis/queries/getRooms/getRoomsResponse.dart';
|
import '../../../../api/webuntis/queries/getRooms/getRoomsResponse.dart';
|
||||||
import '../../../../api/webuntis/queries/getSubjects/getSubjectsResponse.dart';
|
import '../../../../api/webuntis/queries/getSubjects/getSubjectsResponse.dart';
|
||||||
import '../../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart';
|
import '../../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart';
|
||||||
|
import '../../../../routing/app_routes.dart';
|
||||||
import '../../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
import '../../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
||||||
import '../../../../state/app/modules/timetable/bloc/timetable_state.dart';
|
import '../../../../state/app/modules/timetable/bloc/timetable_state.dart';
|
||||||
import '../../../../widget/debug/debugTile.dart';
|
import '../../../../widget/debug/debug_tile.dart';
|
||||||
import '../../../../widget/unimplementedDialog.dart';
|
import '../../../../widget/unimplemented_dialog.dart';
|
||||||
import '../../more/roomplan/roomplan.dart';
|
|
||||||
import '_bottom_sheet.dart';
|
import '_bottom_sheet.dart';
|
||||||
|
|
||||||
class WebuntisLessonSheet {
|
class WebuntisLessonSheet {
|
||||||
@@ -54,7 +53,7 @@ class WebuntisLessonSheet {
|
|||||||
title: Text('Raum: ${room.name} (${room.longName})'),
|
title: Text('Raum: ${room.name} (${room.longName})'),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: const Icon(Icons.house_outlined),
|
icon: const Icon(Icons.house_outlined),
|
||||||
onPressed: () => pushScreen(context, withNavBar: false, screen: const Roomplan()),
|
onPressed: () => AppRoutes.openRoomplan(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncfusion_flutter_calendar/calendar.dart';
|
import 'package:syncfusion_flutter_calendar/calendar.dart';
|
||||||
|
|
||||||
import '../../../extensions/dateTime.dart';
|
import '../../../extensions/date_time.dart';
|
||||||
|
import '../../../routing/app_routes.dart';
|
||||||
import '../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
import '../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart';
|
||||||
import '../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
import '../../../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
||||||
import '../../../state/app/modules/timetable/bloc/timetable_state.dart';
|
import '../../../state/app/modules/timetable/bloc/timetable_state.dart';
|
||||||
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
import 'custom_events/custom_event_edit_dialog.dart';
|
import 'custom_events/custom_event_edit_dialog.dart';
|
||||||
import 'custom_events/custom_events_view.dart';
|
|
||||||
import 'data/arbitrary_appointment.dart';
|
import 'data/arbitrary_appointment.dart';
|
||||||
import 'data/lesson_period_schedule.dart';
|
import 'data/lesson_period_schedule.dart';
|
||||||
import 'data/timetable_appointment_factory.dart';
|
import 'data/timetable_appointment_factory.dart';
|
||||||
@@ -46,7 +46,7 @@ class _TimetableState extends State<Timetable> {
|
|||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
);
|
);
|
||||||
case _CalendarAction.viewEvents:
|
case _CalendarAction.viewEvents:
|
||||||
Navigator.of(context).push(MaterialPageRoute(builder: (_) => const CustomEventsView()));
|
AppRoutes.openCustomEvents(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:syncfusion_flutter_calendar/calendar.dart';
|
import 'package:syncfusion_flutter_calendar/calendar.dart';
|
||||||
|
|
||||||
import '../../../../api/webuntis/queries/getHolidays/getHolidaysResponse.dart';
|
import '../../../../api/webuntis/queries/getHolidays/getHolidaysResponse.dart';
|
||||||
import '../../../../extensions/dateTime.dart';
|
import '../../../../extensions/date_time.dart';
|
||||||
import '../data/calendar_layout.dart';
|
import '../data/calendar_layout.dart';
|
||||||
import '../data/lesson_period_schedule.dart';
|
import '../data/lesson_period_schedule.dart';
|
||||||
import '../data/webuntis_time.dart';
|
import '../data/webuntis_time.dart';
|
||||||
|
|||||||
@@ -1,318 +0,0 @@
|
|||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:jiffy/jiffy.dart';
|
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
|
|
||||||
import '../../model/accountData.dart';
|
|
||||||
import '../../notification/notifyUpdater.dart';
|
|
||||||
import '../../state/app/modules/settings/bloc/settings_cubit.dart';
|
|
||||||
import '../../state/app/modules/timetable/bloc/timetable_bloc.dart';
|
|
||||||
import '../../storage/base/settings.dart' as model;
|
|
||||||
import '../../theming/appTheme.dart';
|
|
||||||
import '../../widget/centeredLeading.dart';
|
|
||||||
import '../../widget/confirmDialog.dart';
|
|
||||||
import '../../widget/debug/cacheView.dart';
|
|
||||||
import '../../storage/timetable/timetable_name_mode.dart';
|
|
||||||
import 'defaultSettings.dart';
|
|
||||||
import 'devToolsSettings.dart';
|
|
||||||
import 'privacyInfo.dart';
|
|
||||||
|
|
||||||
class Settings extends StatefulWidget {
|
|
||||||
const Settings({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<Settings> createState() => _SettingsState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SettingsState extends State<Settings> {
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool developerMode = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => BlocBuilder<SettingsCubit, model.Settings>(builder: (context, _) {
|
|
||||||
final settings = context.read<SettingsCubit>();
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Einstellungen'),
|
|
||||||
),
|
|
||||||
body: ListView(
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
leading: const CenteredLeading(Icon(Icons.logout_outlined)),
|
|
||||||
title: const Text('Konto abmelden'),
|
|
||||||
subtitle: Text('Angemeldet als ${AccountData().getUsername()}'),
|
|
||||||
onTap: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => ConfirmDialog(
|
|
||||||
title: 'Abmelden?',
|
|
||||||
content: 'Möchtest du dich wirklich abmelden?',
|
|
||||||
confirmButton: 'Abmelden',
|
|
||||||
onConfirm: () {
|
|
||||||
SharedPreferences.getInstance().then((value) => {
|
|
||||||
value.clear(),
|
|
||||||
}).then((value) async {
|
|
||||||
PaintingBinding.instance.imageCache.clear();
|
|
||||||
if (!context.mounted) return;
|
|
||||||
context.read<SettingsCubit>().reset();
|
|
||||||
const CacheView().clear();
|
|
||||||
AccountData().removeData(context: context);
|
|
||||||
Navigator.popUntil(context, (route) => !Navigator.canPop(context));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
const Divider(),
|
|
||||||
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.dark_mode_outlined),
|
|
||||||
title: const Text('Farbgebung'),
|
|
||||||
trailing: DropdownButton<ThemeMode>(
|
|
||||||
value: settings.val().appTheme,
|
|
||||||
icon: const Icon(Icons.arrow_drop_down),
|
|
||||||
items: ThemeMode.values.map((e) => DropdownMenuItem<ThemeMode>(
|
|
||||||
value: e,
|
|
||||||
enabled: e != settings.val().appTheme,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(AppTheme.getDisplayOptions(e).icon),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Text(AppTheme.getDisplayOptions(e).displayName),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)).toList(),
|
|
||||||
onChanged: (e) {
|
|
||||||
settings.val(write: true).appTheme = e!;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const Divider(),
|
|
||||||
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.abc_outlined),
|
|
||||||
title: const Text('Fachbezeichnung'),
|
|
||||||
trailing: DropdownButton<TimetableNameMode>(
|
|
||||||
value: settings.val().timetableSettings.timetableNameMode,
|
|
||||||
icon: Icon(Icons.arrow_drop_down),
|
|
||||||
items: TimetableNameMode.values.map((e) => DropdownMenuItem(
|
|
||||||
value: e,
|
|
||||||
enabled: e != settings.val().timetableSettings.timetableNameMode,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(TimetableNameModes.getDisplayOptions(e).icon),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Text(TimetableNameModes.getDisplayOptions(e).displayName),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)).toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
settings.val(write: true).timetableSettings.timetableNameMode = value!;
|
|
||||||
context.read<TimetableBloc>().refresh();
|
|
||||||
},
|
|
||||||
)
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.calendar_view_day_outlined),
|
|
||||||
title: const Text('Doppelstunden zusammenhängend anzeigen'),
|
|
||||||
trailing: Checkbox(
|
|
||||||
value: settings.val().timetableSettings.connectDoubleLessons,
|
|
||||||
onChanged: (e) {
|
|
||||||
settings.val(write: true).timetableSettings.connectDoubleLessons = e!;
|
|
||||||
context.read<TimetableBloc>().refresh();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const Divider(),
|
|
||||||
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.star_border),
|
|
||||||
title: const Text('Favoriten im Talk nach oben sortieren'),
|
|
||||||
trailing: Checkbox(
|
|
||||||
value: settings.val().talkSettings.sortFavoritesToTop,
|
|
||||||
onChanged: (e) {
|
|
||||||
settings.val(write: true).talkSettings.sortFavoritesToTop = e!;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.mark_email_unread_outlined),
|
|
||||||
title: const Text('Ungelesene Chats nach oben sortieren'),
|
|
||||||
trailing: Checkbox(
|
|
||||||
value: settings.val().talkSettings.sortUnreadToTop,
|
|
||||||
onChanged: (e) {
|
|
||||||
settings.val(write: true).talkSettings.sortUnreadToTop = e!;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
ListTile(
|
|
||||||
leading: const CenteredLeading(Icon(Icons.notifications_active_outlined)),
|
|
||||||
title: const Text('Push-Benachrichtigungen aktivieren'),
|
|
||||||
subtitle: const Text('Lange tippen für mehr Informationen'),
|
|
||||||
trailing: Checkbox(
|
|
||||||
value: settings.val().notificationSettings.enabled,
|
|
||||||
onChanged: (e) {
|
|
||||||
if(e!) {
|
|
||||||
NotifyUpdater.enableAfterDisclaimer(settings).asDialog(context);
|
|
||||||
} else {
|
|
||||||
settings.val(write: true).notificationSettings.enabled = e;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
onLongPress: () => showDialog(context: context, builder: (context) => AlertDialog(
|
|
||||||
title: const Text('Info über Push'),
|
|
||||||
content: const SingleChildScrollView(child: Text(''
|
|
||||||
"Aufgrund technischer Limitationen müssen Push-Nachrichten über einen externen Server - hier 'mhsl.eu' (Author dieser App) - erfolgen.\n\n"
|
|
||||||
'Wenn Push aktiviert wird, werden deine Zugangsdaten und ein Token verschlüsselt an den Betreiber gesendet und von ihm unverschlüsselt gespeichert.\n\n'
|
|
||||||
'Der extene Server verwendet die Zugangsdaten um sich maschinell in Nextcloud Talk anzumelden und via Websockets auf neue Nachrichten zu warten.\n\n'
|
|
||||||
'Wenn eine neue Nachricht eintrifft wird dein Telefon via FBC-Messaging (Google Firebase Push) vom externen Server benachrichtigt.\n\n'
|
|
||||||
'Behalte im Hinterkopf, dass deine Zugangsdaten auf einem externen Server gespeichert werden und dies trotz bester Absichten ein Sicherheitsrisiko sein kann!'
|
|
||||||
)),
|
|
||||||
actions: [
|
|
||||||
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Zurück'))
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
|
|
||||||
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(
|
|
||||||
leading: const Icon(Icons.live_help_outlined),
|
|
||||||
title: const Text('Informationen und Lizenzen'),
|
|
||||||
onTap: () {
|
|
||||||
PackageInfo.fromPlatform().then((appInfo) {
|
|
||||||
if (!context.mounted) return;
|
|
||||||
showAboutDialog(
|
|
||||||
context: context,
|
|
||||||
applicationIcon: const Icon(Icons.apps),
|
|
||||||
applicationName: 'MarianumMobile',
|
|
||||||
applicationVersion: '${appInfo.appName}\n\nPackage: ${appInfo.packageName}\nVersion: ${appInfo.version}\nBuild: ${appInfo.buildNumber}',
|
|
||||||
applicationLegalese: 'Dies ist ein Inoffizieller Nextcloud & Webuntis Client und wird nicht vom Marianum selbst betrieben.\n'
|
|
||||||
'Keinerlei Gewähr für Vollständigkeit, Richtigkeit und Aktualität!\n\n'
|
|
||||||
"${kReleaseMode ? "Production" : "Development"} build\n"
|
|
||||||
'Marianum Fulda 2023-${Jiffy.now().year}\nElias Müller',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
trailing: const Icon(Icons.arrow_right),
|
|
||||||
),
|
|
||||||
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.policy_outlined),
|
|
||||||
title: const Text('Impressum & Datenschutz'),
|
|
||||||
onTap: () {
|
|
||||||
showDialog(context: context, builder: (context) => SimpleDialog(
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
leading: const CenteredLeading(Icon(Icons.school_outlined)),
|
|
||||||
title: const Text('Infos zum Marianum Fulda'),
|
|
||||||
subtitle: const Text('Für Talk-Chats und Dateien'),
|
|
||||||
trailing: const Icon(Icons.arrow_right),
|
|
||||||
onTap: () => PrivacyInfo(providerText: 'Marianum', imprintUrl: 'https://www.marianum-fulda.de/impressum', privacyUrl: 'https://www.marianum-fulda.de/datenschutz').showPopup(context)
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const CenteredLeading(Icon(Icons.date_range_outlined)),
|
|
||||||
title: const Text('Infos zu Web-/ Untis'),
|
|
||||||
subtitle: const Text('Für den Stundenplan'),
|
|
||||||
trailing: const Icon(Icons.arrow_right),
|
|
||||||
onTap: () => PrivacyInfo(providerText: 'Untis', imprintUrl: 'https://www.untis.at/impressum', privacyUrl: 'https://www.untis.at/datenschutz-wu-apps').showPopup(context)
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const CenteredLeading(Icon(Icons.send_time_extension_outlined)),
|
|
||||||
title: const Text('Infos zu mhsl'),
|
|
||||||
subtitle: const Text('Für Countdowns, Marianum Message und mehr'),
|
|
||||||
trailing: const Icon(Icons.arrow_right),
|
|
||||||
onTap: () => PrivacyInfo(providerText: 'mhsl', imprintUrl: 'https://mhsl.eu/id.html', privacyUrl: 'https://mhsl.eu/datenschutz.html').showPopup(context),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
},
|
|
||||||
trailing: const Icon(Icons.arrow_right),
|
|
||||||
),
|
|
||||||
|
|
||||||
const Divider(),
|
|
||||||
|
|
||||||
ListTile(
|
|
||||||
leading: const CenteredLeading(Icon(Icons.code)),
|
|
||||||
title: const Text('Quellcode MarianumMobile/Client'),
|
|
||||||
subtitle: const Text('GNU GPL v3'),
|
|
||||||
onTap: () => ConfirmDialog.openBrowser(context, 'https://mhsl.eu/gitea/MarianumMobile/Client'),
|
|
||||||
),
|
|
||||||
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.developer_mode_outlined),
|
|
||||||
title: const Text('Entwicklermodus'),
|
|
||||||
trailing: Checkbox(
|
|
||||||
value: settings.val().devToolsEnabled,
|
|
||||||
onChanged: (state) {
|
|
||||||
changeView() {
|
|
||||||
var enabled = state ?? false;
|
|
||||||
settings.val(write: true).devToolsEnabled = enabled;
|
|
||||||
if(!enabled) settings.val(write: true).devToolsSettings = DefaultSettings.get().devToolsSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!state!) {
|
|
||||||
changeView();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfirmDialog(
|
|
||||||
title: 'Entwicklermodus',
|
|
||||||
content: ''
|
|
||||||
'Die Entwickleransicht bietet erweiterte Funktionen, die für den üblichen Gebrauch nicht benötigt werden.\n\nDie Verwendung der Tools kann darüber hinaus bei falscher Verwendung zu Fehlern führen.\n\n'
|
|
||||||
'Aktivieren auf eigene Verantwortung.',
|
|
||||||
confirmButton: 'Ja, ich verstehe das Risiko',
|
|
||||||
cancelButton: 'Nein, zurück zur App',
|
|
||||||
onConfirm: changeView,
|
|
||||||
).asDialog(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
Visibility(
|
|
||||||
visible: settings.val().devToolsEnabled,
|
|
||||||
child: DevToolsSettings(settings: settings),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
|
|
||||||
import '../../api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
import '../../api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
||||||
import '../../state/app/modules/breaker/bloc/breaker_bloc.dart';
|
import '../../state/app/modules/breaker/bloc/breaker_bloc.dart';
|
||||||
import '../../widget/placeholderView.dart';
|
import '../../widget/placeholder_view.dart';
|
||||||
|
|
||||||
class Breaker extends StatelessWidget {
|
class Breaker extends StatelessWidget {
|
||||||
final BreakerArea breaker;
|
final BreakerArea breaker;
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
import 'package:localstore/localstore.dart';
|
import 'package:localstore/localstore.dart';
|
||||||
|
|
||||||
import '../../../widget/placeholderView.dart';
|
import '../../../widget/placeholder_view.dart';
|
||||||
import '../../api/requestCache.dart';
|
import '../../api/requestCache.dart';
|
||||||
import 'jsonViewer.dart';
|
import 'json_viewer.dart';
|
||||||
|
|
||||||
class CacheView extends StatefulWidget {
|
class CacheView extends StatefulWidget {
|
||||||
const CacheView({super.key});
|
const CacheView({super.key});
|
||||||
@@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../../state/app/modules/settings/bloc/settings_cubit.dart';
|
import '../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
import '../centeredLeading.dart';
|
import '../centered_leading.dart';
|
||||||
import 'jsonViewer.dart';
|
import 'json_viewer.dart';
|
||||||
|
|
||||||
class DebugTile {
|
class DebugTile {
|
||||||
BuildContext context;
|
BuildContext context;
|
||||||
@@ -8,11 +8,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
||||||
|
|
||||||
|
import '../routing/app_routes.dart';
|
||||||
import '../state/app/modules/settings/bloc/settings_cubit.dart';
|
import '../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
import '../utils/FileSaver.dart';
|
import '../utils/file_saver.dart';
|
||||||
import 'infoDialog.dart';
|
import 'info_dialog.dart';
|
||||||
import 'placeholderView.dart';
|
import 'placeholder_view.dart';
|
||||||
import 'sharePositionOrigin.dart';
|
import 'share_position_origin.dart';
|
||||||
|
|
||||||
class FileViewer extends StatefulWidget {
|
class FileViewer extends StatefulWidget {
|
||||||
final String path;
|
final String path;
|
||||||
@@ -51,9 +52,7 @@ class _FileViewerState extends State<FileViewer> {
|
|||||||
onSelected: (value) async {
|
onSelected: (value) async {
|
||||||
switch(value) {
|
switch(value) {
|
||||||
case FileViewingActions.openExternal:
|
case FileViewingActions.openExternal:
|
||||||
Navigator.of(context).push(
|
AppRoutes.openFileViewer(context, widget.path, openExternal: true);
|
||||||
MaterialPageRoute(builder: (context) => FileViewer(path: widget.path, openExternal: true))
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case FileViewingActions.share:
|
case FileViewingActions.share:
|
||||||
SharePlus.instance.share(
|
SharePlus.instance.share(
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
|
|
||||||
import '../model/endpointData.dart';
|
import '../model/endpoint_data.dart';
|
||||||
|
|
||||||
class LargeProfilePictureView extends StatelessWidget {
|
class LargeProfilePictureView extends StatelessWidget {
|
||||||
final String username;
|
final String username;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user