multiple runtime bugfixes for 1.5.5+65

This commit is contained in:
2026-08-28 20:42:30 +02:00
parent 65300614b1
commit 71839d0848
18 changed files with 344 additions and 159 deletions
@@ -8,7 +8,7 @@ import '../../api/errors/error_mapper.dart';
import '../../api/marianumcloud/app_password/delete_app_password.dart';
import '../../api/marianumcloud/login_flow/login_flow_api.dart';
import '../../model/account_data.dart';
import '../../routing/app_routes.dart';
import '../../utils/url_opener.dart';
import '../../widget/app_progress_indicator.dart';
/// Die beiden Durchläufe des Login Flow v2: Der erste liefert das allgemeine
@@ -79,7 +79,7 @@ class _NextcloudLoginFlowPageState extends State<NextcloudLoginFlowPage>
setState(() => _flow = flow);
_startedAt = DateTime.now();
_timer = Timer.periodic(_pollInterval, (_) => _poll());
unawaited(AppRoutes.openExternalUrl(flow.loginUrl));
unawaited(UrlOpener.openUrl(flow.loginUrl));
} catch (e) {
if (!mounted) return;
setState(() => _error = errorToUserMessage(e));
@@ -240,7 +240,7 @@ class _NextcloudLoginFlowPageState extends State<NextcloudLoginFlowPage>
FilledButton.icon(
icon: const Icon(Icons.open_in_browser),
onPressed: () =>
unawaited(AppRoutes.openExternalUrl(flow.loginUrl)),
unawaited(UrlOpener.openUrl(flow.loginUrl)),
label: Text(
isTalkStep
? 'Freigabe im Browser bestätigen'
@@ -4,10 +4,10 @@ import '../../../../api/errors/error_mapper.dart';
import '../../../../api/marianumcloud/autocomplete/autocomplete_api.dart';
import '../../../../api/marianumcloud/autocomplete/autocomplete_response.dart';
import '../../../../api/marianumcloud/files_sharing/queries/share/share.dart';
import '../../../../model/endpoint_data.dart';
import '../../../../utils/debouncer.dart';
import '../../../../utils/haptics.dart';
import '../../../../widget/app_progress_indicator.dart';
import '../../../../widget/user_avatar.dart';
/// Result of [ShareePickerPage]: a recipient to create a share for.
class ShareeRef {
@@ -209,16 +209,11 @@ class _ShareePickerPageState extends State<ShareePickerPage> {
Widget _resultTile(AutocompleteResponseObject object) {
final isGroup = shareTypeFromSource(object.source) == kShareTypeGroup;
// Nextcloud groups are not Talk rooms, so UserAvatar's group URL does not
// apply to them — they keep a plain icon.
final leading = isGroup
? const CircleAvatar(child: Icon(Icons.groups_outlined))
: CircleAvatar(
foregroundImage: Image.network(
'https://${EndpointData().nextcloud().full()}/avatar/${object.id}/128',
).image,
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
child: const Icon(Icons.person),
);
: UserAvatar(id: object.id, semanticLabel: object.label);
return ListTile(
leading: leading,
title: Text(object.label),
@@ -2,7 +2,6 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import '../../../api/errors/error_mapper.dart';
@@ -10,7 +9,6 @@ import '../../../api/marianumcloud/talk/room/get_room_response.dart';
import '../../../api/marianumcloud/talk/send_message/send_message.dart';
import '../../../api/marianumcloud/talk/send_message/send_message_params.dart';
import '../../../api/marianumcloud/talk/share_files_to_chat.dart';
import '../../../api/marianumcloud/webdav/webdav_api.dart';
import '../../../routing/app_routes.dart';
import '../../../share_intent/pending_share.dart';
import '../../../share_intent/remote_file_ref.dart';
@@ -137,12 +135,7 @@ Future<void> _externalShareFlow(
PendingShare share,
) async {
if (share.hasFiles) {
try {
final webdav = await WebdavApi.webdav;
await webdav.mkcol(PathUri.parse('/$talkShareFolder'));
} catch (_) {
// mkcol throws when the folder already exists; ignore.
}
await ensureTalkShareFolder();
if (!context.mounted) return;
await pushScreen(
context,
@@ -39,6 +39,7 @@ class _ChatInfoState extends State<ChatInfo> {
GetParticipantsCache(
chatToken: widget.room.token,
onUpdate: (GetParticipantsResponse data) {
if (!mounted) return;
setState(() {
participants = data;
});
@@ -2,14 +2,12 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import '../../../../api/marianumcloud/talk/room/get_room_response.dart';
import '../../../../api/marianumcloud/talk/send_message/send_message.dart';
import '../../../../api/marianumcloud/talk/send_message/send_message_params.dart';
import '../../../../api/marianumcloud/talk/share_files_to_chat.dart';
import '../../../../api/marianumcloud/webdav/webdav_api.dart';
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
import '../../../../state/app/modules/chat/bloc/chat_state.dart';
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
@@ -68,11 +66,7 @@ class _ChatTextfieldState extends State<ChatTextfield> {
Future<void> mediaUpload(List<String>? paths) async {
if (paths == null) return;
unawaited(
WebdavApi.webdav.then(
(webdav) => webdav.mkcol(PathUri.parse('/$talkShareFolder')),
),
);
unawaited(ensureTalkShareFolder());
if (!mounted) return;
unawaited(
@@ -113,6 +107,7 @@ class _ChatTextfieldState extends State<ChatTextfield> {
void initState() {
super.initState();
settings = context.read<SettingsCubit>();
_loadDraft();
final draftReply = settings
.val()
.talkSettings
@@ -122,6 +117,24 @@ class _ChatTextfieldState extends State<ChatTextfield> {
}
}
@override
void didUpdateWidget(covariant ChatTextfield oldWidget) {
super.didUpdateWidget(oldWidget);
// The state survives a room switch in the split view, so the draft has to
// follow the new token. Every edit persists through _setDraft, so the
// outgoing room's draft is already stored at this point.
if (oldWidget.sendToToken != widget.sendToToken) _loadDraft();
}
/// Seeds the field from the stored draft. Only ever called on mount and on a
/// room switch: `TextEditingController.text` resets selection and composing,
/// so doing this per build would drop the cursor position, any active text
/// selection and a running IME composition on every chat update.
void _loadDraft() {
_textBoxController.text =
settings.val().talkSettings.drafts[widget.sendToToken] ?? '';
}
@override
void dispose() {
// Defer to end-of-frame: resetting the shared notifier synchronously during
@@ -263,8 +276,6 @@ class _ChatTextfieldState extends State<ChatTextfield> {
@override
Widget build(BuildContext context) {
_textBoxController.text =
settings.val().talkSettings.drafts[widget.sendToToken] ?? '';
final chatBloc = context.watch<ChatBloc>();
final chatState = chatBloc.state.data;
+3 -2
View File
@@ -14,6 +14,7 @@ import '../../../state/app/infrastructure/utility_widgets/loadable_hydrated_bloc
import '../../../state/app/modules/ticker/bloc/ticker_bloc.dart';
import '../../../state/app/modules/ticker/bloc/ticker_state.dart';
import '../../../theming/app_theme.dart';
import '../../../utils/url_opener.dart';
import '../../../widget/details_bottom_sheet.dart';
import '../../../widget/placeholder_view.dart';
import '../../../widget/prosemirror/pm_json_view.dart';
@@ -247,7 +248,7 @@ class _TickerScaffoldState extends State<TickerScaffold> {
void _openRedirect(TickerNavPage page) {
final url = page.externalUrl;
if (url != null && url.isNotEmpty) {
unawaited(AppRoutes.openExternalUrl(url));
unawaited(UrlOpener.openUrl(url));
}
}
@@ -262,7 +263,7 @@ class _TickerScaffoldState extends State<TickerScaffold> {
_ensureBackEntry();
return;
}
unawaited(AppRoutes.openExternalUrl(href));
unawaited(UrlOpener.openUrl(href));
}
String? _titleForSlug(String slug) {
@@ -14,6 +14,7 @@ import '../../../../state/app/modules/ticker/bloc/ticker_page_bloc.dart';
import '../../../../state/app/modules/ticker/bloc/ticker_page_state.dart';
import '../../../../state/app/modules/ticker/repository/ticker_page_repository.dart';
import '../../../../theming/app_theme.dart';
import '../../../../utils/url_opener.dart';
import '../../../../widget/app_progress_indicator.dart';
import '../../../../widget/placeholder_view.dart';
import '../../../../widget/prosemirror/pm_json_view.dart';
@@ -87,7 +88,7 @@ class _TickerPageContentState extends State<_TickerPageContent> {
_redirected = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
if (url != null && url.isNotEmpty) AppRoutes.openExternalUrl(url);
if (url != null && url.isNotEmpty) UrlOpener.openUrl(url);
widget.onRedirect?.call();
});
}