Add Post viewer screen and ability to open post in external browser

This commit is contained in:
Hank Grabowski 2022-11-23 15:48:09 -05:00
parent d2db2b870d
commit ff3e938f70
9 changed files with 134 additions and 25 deletions

View file

@ -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(),
),
),

View file

@ -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}');
}
}
}

View file

@ -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',

View file

@ -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(

View file

@ -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,

View 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,
));
}
}

View file

@ -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]!);

View file

@ -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');

View 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;
}