mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 12:23:31 +00:00
Add responsive scaling to main screens and primary media controls
This commit is contained in:
parent
76da2ca390
commit
af1f513ed5
18 changed files with 310 additions and 208 deletions
|
@ -22,14 +22,14 @@ final _useMediaKit = Platform.isIOS ||
|
|||
class AVControl extends StatefulWidget {
|
||||
final String videoUrl;
|
||||
final String description;
|
||||
final double width;
|
||||
final double height;
|
||||
final double? width;
|
||||
final double? height;
|
||||
|
||||
const AVControl({
|
||||
super.key,
|
||||
required this.videoUrl,
|
||||
required this.width,
|
||||
required this.height,
|
||||
this.width,
|
||||
this.height,
|
||||
required this.description,
|
||||
});
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ import 'package:media_kit_video/media_kit_video.dart';
|
|||
|
||||
class MediaKitAvControl extends StatefulWidget {
|
||||
final String videoUrl;
|
||||
final double width;
|
||||
final double height;
|
||||
final double? width;
|
||||
final double? height;
|
||||
|
||||
const MediaKitAvControl({
|
||||
super.key,
|
||||
|
@ -70,9 +70,9 @@ class _MediaKitAvControlState extends State<MediaKitAvControl> {
|
|||
setState(() {});
|
||||
}
|
||||
|
||||
double get height => widget.height - 50;
|
||||
double? get height => widget.height == null ? null : widget.height! - 50;
|
||||
|
||||
double get width => widget.width;
|
||||
double? get width => widget.width;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -88,10 +88,12 @@ class _MediaKitAvControlState extends State<MediaKitAvControl> {
|
|||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Video(
|
||||
controller: controller,
|
||||
width: width,
|
||||
height: height,
|
||||
Expanded(
|
||||
child: Video(
|
||||
controller: controller,
|
||||
width: width,
|
||||
height: height,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
|
|
|
@ -5,8 +5,8 @@ import 'package:video_player/video_player.dart';
|
|||
|
||||
class VideoPlayerLibAvControl extends StatefulWidget {
|
||||
final String videoUrl;
|
||||
final double width;
|
||||
final double height;
|
||||
final double? width;
|
||||
final double? height;
|
||||
|
||||
const VideoPlayerLibAvControl({
|
||||
super.key,
|
||||
|
@ -56,14 +56,16 @@ class _VideoPlayerLibAvControlState extends State<VideoPlayerLibAvControl> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = videoPlayerController.value.size;
|
||||
late final double videoWidth;
|
||||
late final double videoHeight;
|
||||
double? videoWidth;
|
||||
double? videoHeight;
|
||||
if (needsToLoad) {
|
||||
videoWidth = widget.width;
|
||||
videoHeight = widget.height;
|
||||
} else {
|
||||
final horizontalScale = widget.width / size.width;
|
||||
final verticalScale = widget.height / size.height;
|
||||
final horizontalScale =
|
||||
widget.width == null ? 1 : widget.width! / size.width;
|
||||
final verticalScale =
|
||||
widget.height == null ? 1 : widget.height! / size.height;
|
||||
final scaling = min(horizontalScale, verticalScale);
|
||||
videoWidth = scaling * size.width;
|
||||
videoHeight = scaling * size.height;
|
||||
|
|
|
@ -9,11 +9,15 @@ import 'image_control.dart';
|
|||
class MediaAttachmentViewerControl extends StatefulWidget {
|
||||
final List<MediaAttachment> attachments;
|
||||
final int index;
|
||||
final double? width;
|
||||
final double? height;
|
||||
|
||||
const MediaAttachmentViewerControl({
|
||||
super.key,
|
||||
required this.attachments,
|
||||
required this.index,
|
||||
this.width,
|
||||
this.height,
|
||||
});
|
||||
|
||||
@override
|
||||
|
@ -26,13 +30,11 @@ class _MediaAttachmentViewerControlState
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final item = widget.attachments[widget.index];
|
||||
const width = 250.0;
|
||||
const height = 250.0;
|
||||
if (item.explicitType == AttachmentMediaType.video) {
|
||||
return AVControl(
|
||||
videoUrl: item.uri.toString(),
|
||||
width: width,
|
||||
height: height,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
description: item.description,
|
||||
);
|
||||
}
|
||||
|
@ -41,8 +43,8 @@ class _MediaAttachmentViewerControlState
|
|||
}
|
||||
|
||||
return ImageControl(
|
||||
width: width,
|
||||
height: height,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
imageUrl: item.thumbnailUri.toString(),
|
||||
altText: item.description,
|
||||
onTap: () async {
|
||||
|
|
26
lib/controls/responsive_max_width.dart
Normal file
26
lib/controls/responsive_max_width.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../globals.dart';
|
||||
|
||||
class ResponsiveMaxWidth extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const ResponsiveMaxWidth({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final border =
|
||||
BorderSide(color: Theme.of(context).highlightColor, width: 1);
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints.loose(
|
||||
const Size.fromWidth(maxViewPortalWidth),
|
||||
),
|
||||
child: Container(
|
||||
decoration:
|
||||
BoxDecoration(border: Border(left: border, right: border)),
|
||||
child: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import '../../services/timeline_manager.dart';
|
|||
import '../../utils/active_profile_selector.dart';
|
||||
import '../../utils/clipboard_utils.dart';
|
||||
import '../../utils/html_to_edit_text_helper.dart';
|
||||
import '../../utils/responsive_sizes_calculator.dart';
|
||||
import '../../utils/url_opening_utils.dart';
|
||||
import '../media_attachment_viewer_control.dart';
|
||||
import '../padding.dart';
|
||||
|
@ -156,13 +157,16 @@ class _StatusControlState extends State<FlattenedTreeEntryControl> {
|
|||
return const SizedBox();
|
||||
}
|
||||
return SizedBox(
|
||||
height: 300.0,
|
||||
height: ResponsiveSizesCalculator(context).maxThumbnailHeight,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (context, index) {
|
||||
return MediaAttachmentViewerControl(
|
||||
attachments: items,
|
||||
index: index,
|
||||
width: items.length > 1
|
||||
? ResponsiveSizesCalculator(context).maxThumbnailWidth
|
||||
: ResponsiveSizesCalculator(context).viewPortalWidth,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
|
|
|
@ -17,6 +17,9 @@ final useImagePicker = kIsWeb || Platform.isAndroid || Platform.isIOS;
|
|||
|
||||
const usePhpDebugging = true;
|
||||
|
||||
const maxViewPortalHeight = 750.0;
|
||||
const maxViewPortalWidth = 750.0;
|
||||
|
||||
Future<bool?> showConfirmDialog(BuildContext context, String caption) {
|
||||
return showDialog<bool>(
|
||||
context: context,
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
|
|||
import '../controls/app_bottom_nav_bar.dart';
|
||||
import '../controls/current_profile_button.dart';
|
||||
import '../controls/linear_status_indicator.dart';
|
||||
import '../controls/responsive_max_width.dart';
|
||||
import '../controls/standard_app_drawer.dart';
|
||||
import '../globals.dart';
|
||||
import '../models/connection.dart';
|
||||
|
@ -52,21 +53,23 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
|||
child: Text('No contacts'),
|
||||
));
|
||||
} else {
|
||||
body = ListView.separated(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
final contact = contacts[index];
|
||||
return ListTile(
|
||||
onTap: () {
|
||||
context.pushNamed(ScreenPaths.userProfile,
|
||||
params: {'id': contact.id});
|
||||
},
|
||||
title: Text(contact.name),
|
||||
trailing: Text(contact.status.label()),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const Divider(),
|
||||
itemCount: contacts.length);
|
||||
body = ResponsiveMaxWidth(
|
||||
child: ListView.separated(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
final contact = contacts[index];
|
||||
return ListTile(
|
||||
onTap: () {
|
||||
context.pushNamed(ScreenPaths.userProfile,
|
||||
params: {'id': contact.id});
|
||||
},
|
||||
title: Text(contact.name),
|
||||
trailing: Text(contact.status.label()),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const Divider(),
|
||||
itemCount: contacts.length),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
|
|
|
@ -7,6 +7,7 @@ import '../controls/app_bottom_nav_bar.dart';
|
|||
import '../controls/linear_status_indicator.dart';
|
||||
import '../controls/login_aware_cached_network_image.dart';
|
||||
import '../controls/padding.dart';
|
||||
import '../controls/responsive_max_width.dart';
|
||||
import '../controls/standard_app_drawer.dart';
|
||||
import '../controls/timeline/timeline_panel.dart';
|
||||
import '../globals.dart';
|
||||
|
@ -152,7 +153,9 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||
child: Column(
|
||||
children: [
|
||||
StandardLinearProgressIndicator(nss.timelineLoadingStatus),
|
||||
Expanded(child: timeline),
|
||||
Expanded(
|
||||
child: ResponsiveMaxWidth(child: timeline),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
|
|||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
import '../controls/image_control.dart';
|
||||
import '../controls/responsive_max_width.dart';
|
||||
import '../controls/standard_appbar.dart';
|
||||
import '../controls/status_and_refresh_button.dart';
|
||||
import '../globals.dart';
|
||||
|
@ -62,36 +63,38 @@ class InteractionsViewerScreen extends StatelessWidget {
|
|||
)
|
||||
]),
|
||||
body: Center(
|
||||
child: ListView.separated(
|
||||
itemCount: connections.length,
|
||||
itemBuilder: (context, index) {
|
||||
final connection = connections[index];
|
||||
return ListTile(
|
||||
onTap: () async {
|
||||
await getIt<ActiveProfileSelector<ConnectionsManager>>()
|
||||
.activeEntry
|
||||
.andThenSuccessAsync((cm) async {
|
||||
final existingData = cm.getById(connection.id);
|
||||
if (existingData.isFailure) {
|
||||
await cm.fullRefresh(connection);
|
||||
child: ResponsiveMaxWidth(
|
||||
child: ListView.separated(
|
||||
itemCount: connections.length,
|
||||
itemBuilder: (context, index) {
|
||||
final connection = connections[index];
|
||||
return ListTile(
|
||||
onTap: () async {
|
||||
await getIt<ActiveProfileSelector<ConnectionsManager>>()
|
||||
.activeEntry
|
||||
.andThenSuccessAsync((cm) async {
|
||||
final existingData = cm.getById(connection.id);
|
||||
if (existingData.isFailure) {
|
||||
await cm.fullRefresh(connection);
|
||||
}
|
||||
});
|
||||
if (context.mounted) {
|
||||
context.pushNamed(ScreenPaths.userProfile,
|
||||
params: {'id': connection.id});
|
||||
}
|
||||
});
|
||||
if (context.mounted) {
|
||||
context.pushNamed(ScreenPaths.userProfile,
|
||||
params: {'id': connection.id});
|
||||
}
|
||||
},
|
||||
leading: ImageControl(
|
||||
imageUrl: connection.avatarUrl.toString(),
|
||||
iconOverride: const Icon(Icons.person),
|
||||
width: 32.0,
|
||||
onTap: () => context.pushNamed(ScreenPaths.userProfile,
|
||||
params: {'id': connection.id}),
|
||||
),
|
||||
title: Text('${connection.name} (${connection.handle})'),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Divider(),
|
||||
},
|
||||
leading: ImageControl(
|
||||
imageUrl: connection.avatarUrl.toString(),
|
||||
iconOverride: const Icon(Icons.person),
|
||||
width: 32.0,
|
||||
onTap: () => context.pushNamed(ScreenPaths.userProfile,
|
||||
params: {'id': connection.id}),
|
||||
),
|
||||
title: Text('${connection.name} (${connection.handle})'),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Divider(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:result_monad/result_monad.dart';
|
|||
|
||||
import '../controls/image_control.dart';
|
||||
import '../controls/padding.dart';
|
||||
import '../controls/responsive_max_width.dart';
|
||||
import '../controls/standard_appbar.dart';
|
||||
import '../globals.dart';
|
||||
import '../models/direct_message_thread.dart';
|
||||
|
@ -59,56 +60,61 @@ class _MessageThreadScreenState extends State<MessageThreadScreen> {
|
|||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
itemBuilder: (context, index) {
|
||||
final m = thread.messages[index];
|
||||
final textPieces = m.text.split('...\n');
|
||||
final text =
|
||||
textPieces.length == 1 ? textPieces[0] : textPieces[1];
|
||||
final imageUrl = m.senderId == yourId
|
||||
? yourAvatarUrl
|
||||
: participants[m.senderId]?.avatarUrl ?? '';
|
||||
return ListTile(
|
||||
onTap: m.seen
|
||||
? null
|
||||
: () =>
|
||||
service.markMessageRead(widget.parentThreadId, m),
|
||||
onLongPress: () async {
|
||||
await copyToClipboard(context: context, text: m.text);
|
||||
},
|
||||
leading: ImageControl(
|
||||
imageUrl: imageUrl,
|
||||
iconOverride: const Icon(Icons.person),
|
||||
width: 32.0,
|
||||
onTap: null,
|
||||
),
|
||||
title: Text(
|
||||
text,
|
||||
style: m.seen
|
||||
child: ResponsiveMaxWidth(
|
||||
child: ListView.separated(
|
||||
itemBuilder: (context, index) {
|
||||
final m = thread.messages[index];
|
||||
final textPieces = m.text.split('...\n');
|
||||
final text = textPieces.length == 1
|
||||
? textPieces[0]
|
||||
: textPieces[1];
|
||||
final imageUrl = m.senderId == yourId
|
||||
? yourAvatarUrl
|
||||
: participants[m.senderId]?.avatarUrl ?? '';
|
||||
return ListTile(
|
||||
onTap: m.seen
|
||||
? null
|
||||
: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
trailing: Text(DateTime.fromMillisecondsSinceEpoch(
|
||||
m.createdAt * 1000)
|
||||
.toString()),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Divider(),
|
||||
itemCount: thread.messages.length),
|
||||
: () => service.markMessageRead(
|
||||
widget.parentThreadId, m),
|
||||
onLongPress: () async {
|
||||
await copyToClipboard(context: context, text: m.text);
|
||||
},
|
||||
leading: ImageControl(
|
||||
imageUrl: imageUrl,
|
||||
iconOverride: const Icon(Icons.person),
|
||||
width: 32.0,
|
||||
onTap: null,
|
||||
),
|
||||
title: Text(
|
||||
text,
|
||||
style: m.seen
|
||||
? null
|
||||
: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
trailing: Text(DateTime.fromMillisecondsSinceEpoch(
|
||||
m.createdAt * 1000)
|
||||
.toString()),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Divider(),
|
||||
itemCount: thread.messages.length),
|
||||
),
|
||||
),
|
||||
const VerticalDivider(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextFormField(
|
||||
controller: textController,
|
||||
maxLines: 4,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Reply Text',
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: ResponsiveMaxWidth(
|
||||
child: TextFormField(
|
||||
controller: textController,
|
||||
maxLines: 4,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Reply Text',
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../controls/image_control.dart';
|
||||
import '../controls/responsive_max_width.dart';
|
||||
import '../controls/standard_appbar.dart';
|
||||
import '../controls/status_and_refresh_button.dart';
|
||||
import '../globals.dart';
|
||||
|
@ -49,44 +50,47 @@ class MessagesScreen extends StatelessWidget {
|
|||
t2.messages.last.createdAt.compareTo(t1.messages.last.createdAt));
|
||||
return threads.isEmpty
|
||||
? const Text('No Direct Message Threads')
|
||||
: ListView.separated(
|
||||
itemCount: threads.length,
|
||||
itemBuilder: (context, index) {
|
||||
final thread = threads[index];
|
||||
final style = thread.allSeen
|
||||
? null
|
||||
: const TextStyle(fontWeight: FontWeight.bold);
|
||||
return ListTile(
|
||||
onTap: () => context.pushNamed(
|
||||
ScreenPaths.thread,
|
||||
queryParams: {'uri': thread.parentUri},
|
||||
),
|
||||
leading: ImageControl(
|
||||
imageUrl: thread.participants.first.avatarUrl.toString(),
|
||||
iconOverride: const Icon(Icons.person),
|
||||
width: 32.0,
|
||||
onTap: null,
|
||||
),
|
||||
title: Text(
|
||||
[
|
||||
'You',
|
||||
...thread.participants.map((p) => '${p.name}(${p.handle})')
|
||||
].join(thread.participants.length == 1 ? ' & ' : ', '),
|
||||
softWrap: true,
|
||||
style: style,
|
||||
),
|
||||
subtitle: Text(
|
||||
thread.title,
|
||||
style: style,
|
||||
),
|
||||
trailing: Text(
|
||||
ElapsedDateUtils.epochSecondsToString(
|
||||
thread.messages.last.createdAt),
|
||||
style: style,
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Divider(),
|
||||
: ResponsiveMaxWidth(
|
||||
child: ListView.separated(
|
||||
itemCount: threads.length,
|
||||
itemBuilder: (context, index) {
|
||||
final thread = threads[index];
|
||||
final style = thread.allSeen
|
||||
? null
|
||||
: const TextStyle(fontWeight: FontWeight.bold);
|
||||
return ListTile(
|
||||
onTap: () => context.pushNamed(
|
||||
ScreenPaths.thread,
|
||||
queryParams: {'uri': thread.parentUri},
|
||||
),
|
||||
leading: ImageControl(
|
||||
imageUrl: thread.participants.first.avatarUrl.toString(),
|
||||
iconOverride: const Icon(Icons.person),
|
||||
width: 32.0,
|
||||
onTap: null,
|
||||
),
|
||||
title: Text(
|
||||
[
|
||||
'You',
|
||||
...thread.participants
|
||||
.map((p) => '${p.name}(${p.handle})')
|
||||
].join(thread.participants.length == 1 ? ' & ' : ', '),
|
||||
softWrap: true,
|
||||
style: style,
|
||||
),
|
||||
subtitle: Text(
|
||||
thread.title,
|
||||
style: style,
|
||||
),
|
||||
trailing: Text(
|
||||
ElapsedDateUtils.epochSecondsToString(
|
||||
thread.messages.last.createdAt),
|
||||
style: style,
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Divider(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
|
|||
|
||||
import '../controls/app_bottom_nav_bar.dart';
|
||||
import '../controls/notifications_control.dart';
|
||||
import '../controls/responsive_max_width.dart';
|
||||
import '../controls/standard_app_drawer.dart';
|
||||
import '../controls/standard_appbar.dart';
|
||||
import '../controls/status_and_refresh_button.dart';
|
||||
|
@ -47,12 +48,18 @@ class NotificationsScreen extends StatelessWidget {
|
|||
];
|
||||
if (notifications.isEmpty) {
|
||||
title = 'Notifications';
|
||||
body = Center(
|
||||
child: Column(
|
||||
children: const [
|
||||
Center(child: Text('No notifications')),
|
||||
],
|
||||
));
|
||||
body = RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
update(manager);
|
||||
return;
|
||||
},
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: const [
|
||||
Center(child: Text('No notifications')),
|
||||
],
|
||||
)),
|
||||
);
|
||||
} else {
|
||||
final unreadCount = notifications.where((e) => !e.dismissed).length;
|
||||
title =
|
||||
|
@ -62,47 +69,49 @@ class NotificationsScreen extends StatelessWidget {
|
|||
update(manager);
|
||||
return;
|
||||
},
|
||||
child: ListView.separated(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return TextButton(
|
||||
onPressed: () async {
|
||||
final result = await manager.loadNewerNotifications();
|
||||
final noMore = result.fold(
|
||||
onSuccess: (values) => values.isEmpty,
|
||||
onError: (_) => true);
|
||||
if (context.mounted && noMore) {
|
||||
buildSnackbar(
|
||||
context, 'No newer notifications to load');
|
||||
}
|
||||
},
|
||||
child: const Text('Load newer notifications'));
|
||||
}
|
||||
if (index == notifications.length + 1) {
|
||||
return TextButton(
|
||||
onPressed: () async {
|
||||
final result = await manager.loadOlderNotifications();
|
||||
final noMore = result.fold(
|
||||
onSuccess: (values) => values.isEmpty,
|
||||
onError: (_) => true);
|
||||
if (context.mounted && noMore) {
|
||||
buildSnackbar(
|
||||
context, 'No older notifications to load');
|
||||
}
|
||||
},
|
||||
child: const Text('Load older notifications'));
|
||||
}
|
||||
return NotificationControl(
|
||||
notification: notifications[index - 1]);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const Divider(
|
||||
color: Colors.black54,
|
||||
height: 0.0,
|
||||
);
|
||||
},
|
||||
itemCount: notifications.length + 2),
|
||||
child: ResponsiveMaxWidth(
|
||||
child: ListView.separated(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return TextButton(
|
||||
onPressed: () async {
|
||||
final result = await manager.loadNewerNotifications();
|
||||
final noMore = result.fold(
|
||||
onSuccess: (values) => values.isEmpty,
|
||||
onError: (_) => true);
|
||||
if (context.mounted && noMore) {
|
||||
buildSnackbar(
|
||||
context, 'No newer notifications to load');
|
||||
}
|
||||
},
|
||||
child: const Text('Load newer notifications'));
|
||||
}
|
||||
if (index == notifications.length + 1) {
|
||||
return TextButton(
|
||||
onPressed: () async {
|
||||
final result = await manager.loadOlderNotifications();
|
||||
final noMore = result.fold(
|
||||
onSuccess: (values) => values.isEmpty,
|
||||
onError: (_) => true);
|
||||
if (context.mounted && noMore) {
|
||||
buildSnackbar(
|
||||
context, 'No older notifications to load');
|
||||
}
|
||||
},
|
||||
child: const Text('Load older notifications'));
|
||||
}
|
||||
return NotificationControl(
|
||||
notification: notifications[index - 1]);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return const Divider(
|
||||
color: Colors.black54,
|
||||
height: 0.0,
|
||||
);
|
||||
},
|
||||
itemCount: notifications.length + 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
}, onError: (error) {
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../controls/linear_status_indicator.dart';
|
||||
import '../controls/responsive_max_width.dart' show ResponsiveMaxWidth;
|
||||
import '../controls/standard_appbar.dart';
|
||||
import '../controls/timeline/post_control.dart';
|
||||
import '../globals.dart';
|
||||
|
@ -65,7 +66,7 @@ class _PostScreenState extends State<PostScreen> {
|
|||
child: Column(
|
||||
children: [
|
||||
StandardLinearProgressIndicator(nss.timelineLoadingStatus),
|
||||
Expanded(child: body),
|
||||
Expanded(child: ResponsiveMaxWidth(child: body)),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
|
|||
import '../controls/app_bottom_nav_bar.dart';
|
||||
import '../controls/current_profile_button.dart';
|
||||
import '../controls/image_control.dart';
|
||||
import '../controls/responsive_max_width.dart';
|
||||
import '../controls/search_result_status_control.dart';
|
||||
import '../controls/standard_app_drawer.dart';
|
||||
import '../friendica_client/friendica_client.dart';
|
||||
|
@ -105,7 +106,7 @@ class _SearchScreenState extends State<SearchScreen> {
|
|||
),
|
||||
);
|
||||
} else {
|
||||
body = buildResultBody(profile);
|
||||
body = ResponsiveMaxWidth(child: buildResultBody(profile));
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
|
@ -154,11 +155,11 @@ class _SearchScreenState extends State<SearchScreen> {
|
|||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
updateSearchResults(profile);
|
||||
},
|
||||
child: const Text('Search'),
|
||||
icon: const Icon(Icons.search),
|
||||
),
|
||||
PopupMenuButton<SearchTypes>(
|
||||
initialValue: searchType,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../controls/responsive_max_width.dart';
|
||||
import '../controls/standard_appbar.dart';
|
||||
import '../services/setting_service.dart';
|
||||
import '../utils/theme_mode_extensions.dart';
|
||||
|
@ -14,11 +15,13 @@ class SettingsScreen extends StatelessWidget {
|
|||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ListView(
|
||||
children: [
|
||||
buildLowBandwidthWidget(settings),
|
||||
buildThemeWidget(settings),
|
||||
],
|
||||
child: ResponsiveMaxWidth(
|
||||
child: ListView(
|
||||
children: [
|
||||
buildLowBandwidthWidget(settings),
|
||||
buildThemeWidget(settings),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
|
24
lib/utils/responsive_sizes_calculator.dart
Normal file
24
lib/utils/responsive_sizes_calculator.dart
Normal file
|
@ -0,0 +1,24 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
import '../globals.dart';
|
||||
|
||||
class ResponsiveSizesCalculator {
|
||||
final BuildContext context;
|
||||
|
||||
const ResponsiveSizesCalculator(this.context);
|
||||
|
||||
double get viewPortalWidth => min(_screenSize.width, maxViewPortalWidth);
|
||||
|
||||
double get maxThumbnailHeight =>
|
||||
min(_screenSize.height * 0.5, maxViewPortalHeight);
|
||||
|
||||
double get maxThumbnailWidth => min(
|
||||
_screenSize.width < 600
|
||||
? _screenSize.width * 0.5
|
||||
: _screenSize.width * 0.33,
|
||||
maxViewPortalHeight);
|
||||
|
||||
Size get _screenSize => MediaQuery.of(context).size;
|
||||
}
|
|
@ -32,6 +32,8 @@ PODS:
|
|||
- FMDB (>= 2.7.5)
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- window_to_front (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- desktop_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_window/macos`)
|
||||
|
@ -47,6 +49,7 @@ DEPENDENCIES:
|
|||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos`)
|
||||
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- window_to_front (from `Flutter/ephemeral/.symlinks/plugins/window_to_front/macos`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
|
@ -80,6 +83,8 @@ EXTERNAL SOURCES:
|
|||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
window_to_front:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/window_to_front/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
desktop_window: fb7c4f12c1129f947ac482296b6f14059d57a3c3
|
||||
|
@ -97,6 +102,7 @@ SPEC CHECKSUMS:
|
|||
shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472
|
||||
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
|
||||
url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451
|
||||
window_to_front: 4cdc24ddd8461ad1a55fa06286d6a79d8b29e8d8
|
||||
|
||||
PODFILE CHECKSUM: 8d40c19d3cbdb380d870685c3a564c989f1efa52
|
||||
|
||||
|
|
Loading…
Reference in a new issue