refactored HTTP error handling and streamlined UI components by centralizing shared logic and removing redundant parameters
This commit is contained in:
@@ -171,21 +171,18 @@ class _FileElementState extends State<FileElement>
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _delete() async {
|
||||
void _delete() {
|
||||
if (guardDemoAction(context)) return;
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => ConfirmDialog(
|
||||
title: 'Element löschen?',
|
||||
content: 'Das Element wird unwiederruflich gelöscht.',
|
||||
confirmButton: 'Löschen',
|
||||
onConfirmAsync: () async {
|
||||
final webdav = await WebdavApi.webdav;
|
||||
await webdav.delete(PathUri.parse(widget.file.path));
|
||||
widget.refetch();
|
||||
},
|
||||
),
|
||||
);
|
||||
ConfirmDialog(
|
||||
title: 'Element löschen?',
|
||||
content: 'Das Element wird unwiederruflich gelöscht.',
|
||||
confirmButton: 'Löschen',
|
||||
onConfirmAsync: () async {
|
||||
final webdav = await WebdavApi.webdav;
|
||||
await webdav.delete(PathUri.parse(widget.file.path));
|
||||
widget.refetch();
|
||||
},
|
||||
).asDialog(context);
|
||||
}
|
||||
|
||||
void _showActionSheet() {
|
||||
|
||||
@@ -86,20 +86,11 @@ class ChatBackgroundSettingsPage extends StatelessWidget {
|
||||
trailing: DropdownButton<ChatBackgroundType>(
|
||||
value: s.type,
|
||||
icon: const Icon(Icons.arrow_drop_down),
|
||||
items: ChatBackgroundType.values
|
||||
.map(
|
||||
(e) => DropdownMenuItem<ChatBackgroundType>(
|
||||
value: e,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(_typeIcon(e)),
|
||||
const SizedBox(width: 10),
|
||||
Text(_typeLabel(e)),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
items: _iconDropdownItems(
|
||||
ChatBackgroundType.values,
|
||||
_typeIcon,
|
||||
_typeLabel,
|
||||
),
|
||||
onChanged: (e) => _onTypeChanged(context, settings, e!),
|
||||
),
|
||||
),
|
||||
@@ -142,20 +133,11 @@ class ChatBackgroundSettingsPage extends StatelessWidget {
|
||||
trailing: DropdownButton<ChatBackgroundFit>(
|
||||
value: s.fit,
|
||||
icon: const Icon(Icons.arrow_drop_down),
|
||||
items: ChatBackgroundFit.values
|
||||
.map(
|
||||
(e) => DropdownMenuItem<ChatBackgroundFit>(
|
||||
value: e,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(_fitIcon(e)),
|
||||
const SizedBox(width: 10),
|
||||
Text(_fitLabel(e)),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
items: _iconDropdownItems(
|
||||
ChatBackgroundFit.values,
|
||||
_fitIcon,
|
||||
_fitLabel,
|
||||
),
|
||||
onChanged: (e) {
|
||||
Haptics.selection();
|
||||
settings.val(write: true).chatBackgroundSettings.fit =
|
||||
@@ -280,6 +262,27 @@ class ChatBackgroundSettingsPage extends StatelessWidget {
|
||||
cs.type = ChatBackgroundType.color;
|
||||
}
|
||||
|
||||
/// Dropdown menu items rendered as an icon + label row, shared by the source
|
||||
/// and fill-mode pickers.
|
||||
static List<DropdownMenuItem<T>> _iconDropdownItems<T>(
|
||||
List<T> values,
|
||||
IconData Function(T) icon,
|
||||
String Function(T) label,
|
||||
) => values
|
||||
.map(
|
||||
(e) => DropdownMenuItem<T>(
|
||||
value: e,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon(e)),
|
||||
const SizedBox(width: 10),
|
||||
Text(label(e)),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
IconData _typeIcon(ChatBackgroundType type) => switch (type) {
|
||||
ChatBackgroundType.pattern => Icons.texture_outlined,
|
||||
ChatBackgroundType.image => Icons.image_outlined,
|
||||
@@ -330,7 +333,6 @@ class _Preview extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_bubble(
|
||||
context,
|
||||
alignment: Alignment.centerLeft,
|
||||
color: remoteColor,
|
||||
text: 'Wie gefällt dir der neue Hintergrund?',
|
||||
@@ -338,7 +340,6 @@ class _Preview extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_bubble(
|
||||
context,
|
||||
alignment: Alignment.centerRight,
|
||||
color: selfColor,
|
||||
text: 'Sieht richtig gut aus! 🎉',
|
||||
@@ -352,8 +353,7 @@ class _Preview extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _bubble(
|
||||
BuildContext context, {
|
||||
Widget _bubble({
|
||||
required Alignment alignment,
|
||||
required Color color,
|
||||
required String text,
|
||||
|
||||
@@ -245,7 +245,6 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
lastDate = elementDate;
|
||||
messages.add(
|
||||
ChatBubble(
|
||||
context: context,
|
||||
isSender: false,
|
||||
bubbleData: GetChatResponseObject.getDateDummy(element.timestamp),
|
||||
chatData: widget.room,
|
||||
@@ -265,7 +264,6 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
|
||||
messages.add(
|
||||
ChatBubble(
|
||||
context: context,
|
||||
isSender:
|
||||
element.actorId == widget.selfId &&
|
||||
(element.messageType ==
|
||||
@@ -287,7 +285,6 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
messages.insert(
|
||||
0,
|
||||
ChatBubble(
|
||||
context: context,
|
||||
isSender: false,
|
||||
bubbleData: GetChatResponseObject.getTextDummy(
|
||||
'Zurzeit können in dieser App nur die letzten 200 vergangenen Nachrichten angezeigt werden. '
|
||||
|
||||
@@ -24,29 +24,23 @@ class ChatBubbleStyles {
|
||||
alignment: Alignment.center,
|
||||
);
|
||||
|
||||
BubbleStyle getRemoteStyle(bool seamless) {
|
||||
var color = AppTheme.isDarkMode(context)
|
||||
BubbleStyle getRemoteStyle() => BubbleStyle(
|
||||
nip: BubbleNip.leftTop,
|
||||
color: AppTheme.isDarkMode(context)
|
||||
? const Color(0xff202c33)
|
||||
: Colors.white;
|
||||
return BubbleStyle(
|
||||
nip: BubbleNip.leftTop,
|
||||
color: seamless ? Colors.transparent : color,
|
||||
elevation: seamless ? 0 : 1,
|
||||
margin: const BubbleEdges.only(bottom: 10, left: 10, right: 50),
|
||||
alignment: Alignment.topLeft,
|
||||
);
|
||||
}
|
||||
: Colors.white,
|
||||
elevation: 1,
|
||||
margin: const BubbleEdges.only(bottom: 10, left: 10, right: 50),
|
||||
alignment: Alignment.topLeft,
|
||||
);
|
||||
|
||||
BubbleStyle getSelfStyle(bool seamless) {
|
||||
var color = AppTheme.isDarkMode(context)
|
||||
BubbleStyle getSelfStyle() => BubbleStyle(
|
||||
nip: BubbleNip.rightBottom,
|
||||
color: AppTheme.isDarkMode(context)
|
||||
? const Color(0xff005c4b)
|
||||
: const Color(0xffd3d3d3);
|
||||
return BubbleStyle(
|
||||
nip: BubbleNip.rightBottom,
|
||||
color: seamless ? Colors.transparent : color,
|
||||
elevation: seamless ? 0 : 1,
|
||||
margin: const BubbleEdges.only(bottom: 10, right: 10, left: 50),
|
||||
alignment: Alignment.topRight,
|
||||
);
|
||||
}
|
||||
: const Color(0xffd3d3d3),
|
||||
elevation: 1,
|
||||
margin: const BubbleEdges.only(bottom: 10, right: 10, left: 50),
|
||||
alignment: Alignment.topRight,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,9 @@ import '../../../../api/marianumcloud/talk/chat/rich_object_string_processor.dar
|
||||
import '../data/chat_bubble_styles.dart';
|
||||
|
||||
class AnswerReference extends StatelessWidget {
|
||||
final BuildContext context;
|
||||
final GetChatResponseObject referenceMessage;
|
||||
final String? selfId;
|
||||
const AnswerReference({
|
||||
required this.context,
|
||||
required this.referenceMessage,
|
||||
required this.selfId,
|
||||
super.key,
|
||||
@@ -20,8 +18,8 @@ class AnswerReference extends StatelessWidget {
|
||||
final style = ChatBubbleStyles(context);
|
||||
final isSelf = referenceMessage.actorId == selfId;
|
||||
final accent = isSelf
|
||||
? style.getSelfStyle(false).color!.withGreen(200)
|
||||
: style.getRemoteStyle(false).color!.withWhite(200);
|
||||
? style.getSelfStyle().color!.withGreen(200)
|
||||
: style.getRemoteStyle().color!.withWhite(200);
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: accent.withValues(alpha: 0.2),
|
||||
|
||||
@@ -23,7 +23,6 @@ import 'highlighted_linkify.dart';
|
||||
enum SearchHighlight { none, secondary, active }
|
||||
|
||||
class ChatBubble extends StatefulWidget {
|
||||
final BuildContext context;
|
||||
final bool isSender;
|
||||
final GetChatResponseObject bubbleData;
|
||||
final GetRoomResponseObject chatData;
|
||||
@@ -40,7 +39,6 @@ class ChatBubble extends StatefulWidget {
|
||||
final SearchHighlight matchHighlight;
|
||||
|
||||
const ChatBubble({
|
||||
required this.context,
|
||||
required this.isSender,
|
||||
required this.bubbleData,
|
||||
required this.chatData,
|
||||
@@ -123,8 +121,8 @@ class _ChatBubbleState extends State<ChatBubble>
|
||||
base = styles.getSystemStyle();
|
||||
} else {
|
||||
base = widget.isSender
|
||||
? styles.getSelfStyle(false)
|
||||
: styles.getRemoteStyle(false);
|
||||
? styles.getSelfStyle()
|
||||
: styles.getRemoteStyle();
|
||||
}
|
||||
switch (widget.matchHighlight) {
|
||||
case SearchHighlight.none:
|
||||
@@ -366,7 +364,6 @@ class _BubbleContent extends StatelessWidget {
|
||||
bubbleData.messageType ==
|
||||
GetRoomResponseObjectMessageType.comment) ...[
|
||||
AnswerReference(
|
||||
context: context,
|
||||
referenceMessage: parent!,
|
||||
selfId: selfId,
|
||||
),
|
||||
|
||||
@@ -263,7 +263,6 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: AnswerReference(
|
||||
context: context,
|
||||
referenceMessage: referenceMessage,
|
||||
selfId: widget.selfId,
|
||||
),
|
||||
|
||||
@@ -78,39 +78,12 @@ class _OutsideDayColumn extends StatelessWidget {
|
||||
});
|
||||
|
||||
void _showOverflow(BuildContext context, List<Appointment> hidden) {
|
||||
showDetailsBottomSheet(
|
||||
_showAppointmentOverflowSheet(
|
||||
context,
|
||||
children: (sheetCtx) {
|
||||
final tiles = <Widget>[];
|
||||
for (var i = 0; i < hidden.length; i++) {
|
||||
if (i > 0) tiles.add(const Divider(height: 1));
|
||||
final apt = hidden[i];
|
||||
tiles.add(
|
||||
ListTile(
|
||||
leading: Container(
|
||||
width: 12,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: apt.color,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
apt.subject,
|
||||
style: isCrossedOut(apt)
|
||||
? const TextStyle(decoration: TextDecoration.lineThrough)
|
||||
: null,
|
||||
),
|
||||
subtitle: Text(_subtitleFor(apt)),
|
||||
onTap: () {
|
||||
Navigator.of(sheetCtx).pop();
|
||||
onAppointmentTap(apt);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
return tiles;
|
||||
},
|
||||
hidden,
|
||||
onAppointmentTap: onAppointmentTap,
|
||||
isCrossedOut: isCrossedOut,
|
||||
subtitle: _subtitleFor,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -252,39 +252,12 @@ class _DayColumn extends StatelessWidget {
|
||||
) {
|
||||
final sorted = [...appointments]
|
||||
..sort((a, b) => a.startTime.compareTo(b.startTime));
|
||||
showDetailsBottomSheet(
|
||||
_showAppointmentOverflowSheet(
|
||||
context,
|
||||
children: (sheetContext) {
|
||||
final tiles = <Widget>[];
|
||||
for (var i = 0; i < sorted.length; i++) {
|
||||
if (i > 0) tiles.add(const Divider(height: 1));
|
||||
final apt = sorted[i];
|
||||
tiles.add(
|
||||
ListTile(
|
||||
leading: Container(
|
||||
width: 12,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: apt.color,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
apt.subject,
|
||||
style: isCrossedOut(apt)
|
||||
? const TextStyle(decoration: TextDecoration.lineThrough)
|
||||
: null,
|
||||
),
|
||||
subtitle: Text(_overflowSubtitle(apt)),
|
||||
onTap: () {
|
||||
Navigator.of(sheetContext).pop();
|
||||
onAppointmentTap(apt);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
return tiles;
|
||||
},
|
||||
sorted,
|
||||
onAppointmentTap: onAppointmentTap,
|
||||
isCrossedOut: isCrossedOut,
|
||||
subtitle: _overflowSubtitle,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -388,6 +361,52 @@ class _DayColumn extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared bottom sheet listing hidden appointments (used by both the in-grid
|
||||
/// and outside-hours overflow cells). [appointments] is rendered in the given
|
||||
/// order — callers pre-sort as needed.
|
||||
void _showAppointmentOverflowSheet(
|
||||
BuildContext context,
|
||||
List<Appointment> appointments, {
|
||||
required void Function(Appointment) onAppointmentTap,
|
||||
required bool Function(Appointment) isCrossedOut,
|
||||
required String Function(Appointment) subtitle,
|
||||
}) {
|
||||
showDetailsBottomSheet(
|
||||
context,
|
||||
children: (sheetContext) {
|
||||
final tiles = <Widget>[];
|
||||
for (var i = 0; i < appointments.length; i++) {
|
||||
if (i > 0) tiles.add(const Divider(height: 1));
|
||||
final apt = appointments[i];
|
||||
tiles.add(
|
||||
ListTile(
|
||||
leading: Container(
|
||||
width: 12,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: apt.color,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
apt.subject,
|
||||
style: isCrossedOut(apt)
|
||||
? const TextStyle(decoration: TextDecoration.lineThrough)
|
||||
: null,
|
||||
),
|
||||
subtitle: Text(subtitle(apt)),
|
||||
onTap: () {
|
||||
Navigator.of(sheetContext).pop();
|
||||
onAppointmentTap(apt);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
return tiles;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class _CurrentTimeMarker extends StatelessWidget {
|
||||
final DateTime now;
|
||||
final PeriodLayout layout;
|
||||
|
||||
@@ -57,9 +57,6 @@ class TimetableCalendarViewState extends State<TimetableCalendarView> {
|
||||
_calendarKey.currentState?.jumpToDate(_initialDisplayDate());
|
||||
}
|
||||
|
||||
bool isOnInitialWeek() =>
|
||||
widget.state.startDate == _mondayOf(_initialDisplayDate());
|
||||
|
||||
List<Appointment> _appointments(TimetableState state) {
|
||||
final timetableSettings = context
|
||||
.watch<SettingsCubit>()
|
||||
|
||||
Reference in New Issue
Block a user