kopia lustrzana https://gitlab.com/mysocialportal/relatica
Add Post viewer screen and ability to open post in external browser
rodzic
d2db2b870d
commit
ff3e938f70
|
@ -15,14 +15,17 @@ import '../../services/connections_manager.dart';
|
||||||
import '../../services/timeline_manager.dart';
|
import '../../services/timeline_manager.dart';
|
||||||
import '../../utils/dateutils.dart';
|
import '../../utils/dateutils.dart';
|
||||||
import '../../utils/snackbar_builder.dart';
|
import '../../utils/snackbar_builder.dart';
|
||||||
|
import '../../utils/url_opening_utils.dart';
|
||||||
import '../padding.dart';
|
import '../padding.dart';
|
||||||
import 'interactions_bar_control.dart';
|
import 'interactions_bar_control.dart';
|
||||||
import 'status_header_control.dart';
|
import 'status_header_control.dart';
|
||||||
|
|
||||||
class StatusControl extends StatefulWidget {
|
class StatusControl extends StatefulWidget {
|
||||||
final EntryTreeItem originalItem;
|
final EntryTreeItem originalItem;
|
||||||
|
final bool showActionBar;
|
||||||
|
|
||||||
const StatusControl({super.key, required this.originalItem});
|
const StatusControl(
|
||||||
|
{super.key, required this.originalItem, required this.showActionBar});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatusControl> createState() => _StatusControlState();
|
State<StatusControl> createState() => _StatusControlState();
|
||||||
|
@ -58,7 +61,10 @@ class _StatusControlState extends State<StatusControl> {
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
StatusHeaderControl(entry: entry),
|
StatusHeaderControl(
|
||||||
|
entry: entry,
|
||||||
|
showActionBar: widget.showActionBar,
|
||||||
|
),
|
||||||
const VerticalPadding(
|
const VerticalPadding(
|
||||||
height: 5,
|
height: 5,
|
||||||
),
|
),
|
||||||
|
@ -151,22 +157,7 @@ class _StatusControlState extends State<StatusControl> {
|
||||||
HtmlWidget(
|
HtmlWidget(
|
||||||
entry.body,
|
entry.body,
|
||||||
onTapUrl: (url) async {
|
onTapUrl: (url) async {
|
||||||
final uri = Uri.tryParse(url);
|
return await openUrlStringInSystembrowser(context, url, 'video');
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
onTapImage: (imageMetadata) {
|
onTapImage: (imageMetadata) {
|
||||||
print(imageMetadata);
|
print(imageMetadata);
|
||||||
|
@ -246,7 +237,10 @@ class _StatusControlState extends State<StatusControl> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: comments
|
children: comments
|
||||||
.map((c) => StatusControl(originalItem: c))
|
.map((c) => StatusControl(
|
||||||
|
originalItem: c,
|
||||||
|
showActionBar: false,
|
||||||
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_portal/models/timeline_entry.dart';
|
import 'package:flutter_portal/models/timeline_entry.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import '../../globals.dart';
|
import '../../globals.dart';
|
||||||
import '../../models/connection.dart';
|
import '../../models/connection.dart';
|
||||||
import '../../services/connections_manager.dart';
|
import '../../services/connections_manager.dart';
|
||||||
import '../../utils/dateutils.dart';
|
import '../../utils/dateutils.dart';
|
||||||
|
import '../../utils/url_opening_utils.dart';
|
||||||
import '../padding.dart';
|
import '../padding.dart';
|
||||||
|
|
||||||
class StatusHeaderControl extends StatelessWidget {
|
class StatusHeaderControl extends StatelessWidget {
|
||||||
final TimelineEntry entry;
|
final TimelineEntry entry;
|
||||||
|
final bool showActionBar;
|
||||||
|
|
||||||
const StatusHeaderControl({super.key, required this.entry});
|
const StatusHeaderControl(
|
||||||
|
{super.key, required this.entry, required this.showActionBar});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -50,12 +54,39 @@ class StatusHeaderControl extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
// Text(
|
||||||
entry.id,
|
// 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/editor.dart';
|
||||||
import 'screens/home.dart';
|
import 'screens/home.dart';
|
||||||
import 'screens/notifications_screen.dart';
|
import 'screens/notifications_screen.dart';
|
||||||
|
import 'screens/post_screen.dart';
|
||||||
import 'screens/profile_screen.dart';
|
import 'screens/profile_screen.dart';
|
||||||
import 'screens/sign_in.dart';
|
import 'screens/sign_in.dart';
|
||||||
import 'screens/splash.dart';
|
import 'screens/splash.dart';
|
||||||
|
@ -100,6 +101,11 @@ final appRouter = GoRouter(
|
||||||
builder: (context, state) =>
|
builder: (context, state) =>
|
||||||
EditorScreen(id: state.params['id'] ?? 'Not Found'),
|
EditorScreen(id: state.params['id'] ?? 'Not Found'),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: 'view/:id',
|
||||||
|
builder: (context, state) =>
|
||||||
|
PostScreen(id: state.params['id'] ?? 'Not Found'),
|
||||||
|
),
|
||||||
]),
|
]),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/comment',
|
path: '/comment',
|
||||||
|
|
|
@ -151,7 +151,10 @@ class _EditorScreenState extends State<EditorScreen> {
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
StatusHeaderControl(entry: entry),
|
StatusHeaderControl(
|
||||||
|
entry: entry,
|
||||||
|
showActionBar: false,
|
||||||
|
),
|
||||||
const VerticalPadding(height: 3),
|
const VerticalPadding(height: 3),
|
||||||
if (entry.spoilerText.isNotEmpty) ...[
|
if (entry.spoilerText.isNotEmpty) ...[
|
||||||
Text(
|
Text(
|
||||||
|
|
|
@ -98,7 +98,10 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
final item = items[itemIndex];
|
final item = items[itemIndex];
|
||||||
_logger.finest(
|
_logger.finest(
|
||||||
'Building item: $itemIndex: ${item.entry.toShortString()}');
|
'Building item: $itemIndex: ${item.entry.toShortString()}');
|
||||||
return StatusControl(originalItem: item);
|
return StatusControl(
|
||||||
|
originalItem: item,
|
||||||
|
showActionBar: true,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) => Divider(),
|
separatorBuilder: (context, index) => Divider(),
|
||||||
itemCount: items.length + 2,
|
itemCount: items.length + 2,
|
||||||
|
|
|
@ -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();
|
_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) {
|
Result<TimelineEntry, ExecError> getEntryById(String id) {
|
||||||
if (_entries.containsKey(id)) {
|
if (_entries.containsKey(id)) {
|
||||||
return Result.ok(_entries[id]!);
|
return Result.ok(_entries[id]!);
|
||||||
|
|
|
@ -46,6 +46,11 @@ class TimelineManager extends ChangeNotifier {
|
||||||
return getIt<EntryManagerService>().getEntryById(id);
|
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
|
// refresh timeline gets statuses newer than the newest in that timeline
|
||||||
Result<List<EntryTreeItem>, ExecError> getTimeline(TimelineIdentifiers type) {
|
Result<List<EntryTreeItem>, ExecError> getTimeline(TimelineIdentifiers type) {
|
||||||
_logger.finest('Getting timeline $type');
|
_logger.finest('Getting timeline $type');
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
Ładowanie…
Reference in New Issue