mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 15:53:32 +00:00
Add Post viewer screen and ability to open post in external browser
This commit is contained in:
parent
d2db2b870d
commit
ff3e938f70
9 changed files with 134 additions and 25 deletions
|
@ -15,14 +15,17 @@ import '../../services/connections_manager.dart';
|
|||
import '../../services/timeline_manager.dart';
|
||||
import '../../utils/dateutils.dart';
|
||||
import '../../utils/snackbar_builder.dart';
|
||||
import '../../utils/url_opening_utils.dart';
|
||||
import '../padding.dart';
|
||||
import 'interactions_bar_control.dart';
|
||||
import 'status_header_control.dart';
|
||||
|
||||
class StatusControl extends StatefulWidget {
|
||||
final EntryTreeItem originalItem;
|
||||
final bool showActionBar;
|
||||
|
||||
const StatusControl({super.key, required this.originalItem});
|
||||
const StatusControl(
|
||||
{super.key, required this.originalItem, required this.showActionBar});
|
||||
|
||||
@override
|
||||
State<StatusControl> createState() => _StatusControlState();
|
||||
|
@ -58,7 +61,10 @@ class _StatusControlState extends State<StatusControl> {
|
|||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
StatusHeaderControl(entry: entry),
|
||||
StatusHeaderControl(
|
||||
entry: entry,
|
||||
showActionBar: widget.showActionBar,
|
||||
),
|
||||
const VerticalPadding(
|
||||
height: 5,
|
||||
),
|
||||
|
@ -151,22 +157,7 @@ class _StatusControlState extends State<StatusControl> {
|
|||
HtmlWidget(
|
||||
entry.body,
|
||||
onTapUrl: (url) async {
|
||||
final uri = Uri.tryParse(url);
|
||||
if (uri == null) {
|
||||
buildSnackbar(context, 'Bad link: $url');
|
||||
return false;
|
||||
}
|
||||
if (await canLaunchUrl(uri)) {
|
||||
buildSnackbar(
|
||||
context,
|
||||
'Attempting to launch video: $url',
|
||||
);
|
||||
await launchUrl(uri);
|
||||
} else {
|
||||
buildSnackbar(context, 'Unable to launch video: $url');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return await openUrlStringInSystembrowser(context, url, 'video');
|
||||
},
|
||||
onTapImage: (imageMetadata) {
|
||||
print(imageMetadata);
|
||||
|
@ -246,7 +237,10 @@ class _StatusControlState extends State<StatusControl> {
|
|||
Expanded(
|
||||
child: Column(
|
||||
children: comments
|
||||
.map((c) => StatusControl(originalItem: c))
|
||||
.map((c) => StatusControl(
|
||||
originalItem: c,
|
||||
showActionBar: false,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_portal/models/timeline_entry.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../globals.dart';
|
||||
import '../../models/connection.dart';
|
||||
import '../../services/connections_manager.dart';
|
||||
import '../../utils/dateutils.dart';
|
||||
import '../../utils/url_opening_utils.dart';
|
||||
import '../padding.dart';
|
||||
|
||||
class StatusHeaderControl extends StatelessWidget {
|
||||
final TimelineEntry entry;
|
||||
final bool showActionBar;
|
||||
|
||||
const StatusHeaderControl({super.key, required this.entry});
|
||||
const StatusHeaderControl(
|
||||
{super.key, required this.entry, required this.showActionBar});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -50,12 +54,39 @@ class StatusHeaderControl extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
entry.id,
|
||||
),
|
||||
// Text(
|
||||
// entry.id,
|
||||
// ),
|
||||
],
|
||||
),
|
||||
if (showActionBar) ...[
|
||||
Expanded(child: SizedBox()),
|
||||
buildActionBar(context),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildActionBar(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await _openAction(context);
|
||||
},
|
||||
icon: Icon(Icons.launch),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openAction(BuildContext context) async {
|
||||
final openInBrowser =
|
||||
await showYesNoDialog(context, 'Open in browser instead of app?');
|
||||
if (openInBrowser == true) {
|
||||
await openUrlStringInSystembrowser(context, entry.externalLink, 'Post');
|
||||
} else {
|
||||
context.push('/post/view/${entry.id}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'globals.dart';
|
|||
import 'screens/editor.dart';
|
||||
import 'screens/home.dart';
|
||||
import 'screens/notifications_screen.dart';
|
||||
import 'screens/post_screen.dart';
|
||||
import 'screens/profile_screen.dart';
|
||||
import 'screens/sign_in.dart';
|
||||
import 'screens/splash.dart';
|
||||
|
@ -100,6 +101,11 @@ final appRouter = GoRouter(
|
|||
builder: (context, state) =>
|
||||
EditorScreen(id: state.params['id'] ?? 'Not Found'),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'view/:id',
|
||||
builder: (context, state) =>
|
||||
PostScreen(id: state.params['id'] ?? 'Not Found'),
|
||||
),
|
||||
]),
|
||||
GoRoute(
|
||||
path: '/comment',
|
||||
|
|
|
@ -151,7 +151,10 @@ class _EditorScreenState extends State<EditorScreen> {
|
|||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
StatusHeaderControl(entry: entry),
|
||||
StatusHeaderControl(
|
||||
entry: entry,
|
||||
showActionBar: false,
|
||||
),
|
||||
const VerticalPadding(height: 3),
|
||||
if (entry.spoilerText.isNotEmpty) ...[
|
||||
Text(
|
||||
|
|
|
@ -98,7 +98,10 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||
final item = items[itemIndex];
|
||||
_logger.finest(
|
||||
'Building item: $itemIndex: ${item.entry.toShortString()}');
|
||||
return StatusControl(originalItem: item);
|
||||
return StatusControl(
|
||||
originalItem: item,
|
||||
showActionBar: true,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => Divider(),
|
||||
itemCount: items.length + 2,
|
||||
|
|
29
lib/screens/post_screen.dart
Normal file
29
lib/screens/post_screen.dart
Normal file
|
@ -0,0 +1,29 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../controls/timeline/status_control.dart';
|
||||
import '../services/timeline_manager.dart';
|
||||
|
||||
class PostScreen extends StatelessWidget {
|
||||
final String id;
|
||||
|
||||
const PostScreen({super.key, required this.id});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final manager = context.watch<TimelineManager>();
|
||||
final body = manager.getPostTreeEntryBy(id).fold(
|
||||
onSuccess: (post) => StatusControl(
|
||||
originalItem: post,
|
||||
showActionBar: false,
|
||||
),
|
||||
onError: (error) => Text('Error getting post: $error'));
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('View Post'),
|
||||
),
|
||||
body: Center(
|
||||
child: body,
|
||||
));
|
||||
}
|
||||
}
|
|
@ -21,6 +21,20 @@ class EntryManagerService extends ChangeNotifier {
|
|||
_parentPostIds.clear();
|
||||
}
|
||||
|
||||
Result<EntryTreeItem, ExecError> getPostTreeEntryBy(String id) {
|
||||
_logger.finest('Getting post: $id');
|
||||
final auth = getIt<AuthService>();
|
||||
final postNode = _postNodes[id];
|
||||
if (postNode == null) {
|
||||
return Result.error(ExecError(
|
||||
type: ErrorType.notFound,
|
||||
message: 'Unknown post id: $id',
|
||||
));
|
||||
}
|
||||
|
||||
return Result.ok(_nodeToTreeItem(postNode, auth.currentId));
|
||||
}
|
||||
|
||||
Result<TimelineEntry, ExecError> getEntryById(String id) {
|
||||
if (_entries.containsKey(id)) {
|
||||
return Result.ok(_entries[id]!);
|
||||
|
|
|
@ -46,6 +46,11 @@ class TimelineManager extends ChangeNotifier {
|
|||
return getIt<EntryManagerService>().getEntryById(id);
|
||||
}
|
||||
|
||||
Result<EntryTreeItem, ExecError> getPostTreeEntryBy(String id) {
|
||||
_logger.finest('Getting post for $id');
|
||||
return getIt<EntryManagerService>().getPostTreeEntryBy(id);
|
||||
}
|
||||
|
||||
// refresh timeline gets statuses newer than the newest in that timeline
|
||||
Result<List<EntryTreeItem>, ExecError> getTimeline(TimelineIdentifiers type) {
|
||||
_logger.finest('Getting timeline $type');
|
||||
|
|
24
lib/utils/url_opening_utils.dart
Normal file
24
lib/utils/url_opening_utils.dart
Normal file
|
@ -0,0 +1,24 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'snackbar_builder.dart';
|
||||
|
||||
Future<bool> openUrlStringInSystembrowser(
|
||||
BuildContext context, String url, String label) async {
|
||||
final uri = Uri.tryParse(url);
|
||||
if (uri == null) {
|
||||
buildSnackbar(context, 'Bad link: $url');
|
||||
return false;
|
||||
}
|
||||
if (await canLaunchUrl(uri)) {
|
||||
buildSnackbar(
|
||||
context,
|
||||
'Attempting to launch $label: $url',
|
||||
);
|
||||
await launchUrl(uri);
|
||||
} else {
|
||||
buildSnackbar(context, 'Unable to launch $label: $url');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
Loading…
Reference in a new issue