kopia lustrzana https://gitlab.com/mysocialportal/relatica
Remove carousel from image viewer screen (renamed media viewer) to fix bad zoom behaviors
rodzic
6a5345953a
commit
581b4bf224
|
@ -4,7 +4,7 @@ import '../../globals.dart';
|
||||||
import '../../models/image_entry.dart';
|
import '../../models/image_entry.dart';
|
||||||
import '../../models/visibility.dart';
|
import '../../models/visibility.dart';
|
||||||
import '../../screens/existing_image_selector_screen.dart';
|
import '../../screens/existing_image_selector_screen.dart';
|
||||||
import '../../screens/image_viewer_screen.dart';
|
import '../../screens/media_viewer_screen.dart';
|
||||||
import '../../serializers/friendica/image_entry_friendica_extensions.dart';
|
import '../../serializers/friendica/image_entry_friendica_extensions.dart';
|
||||||
import '../login_aware_cached_network_image.dart';
|
import '../login_aware_cached_network_image.dart';
|
||||||
import '../padding.dart';
|
import '../padding.dart';
|
||||||
|
@ -66,7 +66,7 @@ class _GallerySelectorControlState extends State<GallerySelectorControl> {
|
||||||
onDoubleTap: () {
|
onDoubleTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => ImageViewerScreen(
|
builder: (context) => MediaViewerScreen(
|
||||||
attachments: widget.entries
|
attachments: widget.entries
|
||||||
.map((i) => i.toMediaAttachment())
|
.map((i) => i.toMediaAttachment())
|
||||||
.toList(),
|
.toList(),
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../models/attachment_media_type_enum.dart';
|
import '../models/attachment_media_type_enum.dart';
|
||||||
import '../models/media_attachment.dart';
|
import '../models/media_attachment.dart';
|
||||||
import '../screens/image_viewer_screen.dart';
|
import '../screens/media_viewer_screen.dart';
|
||||||
import 'audio_video/av_control.dart';
|
import 'audio_video/av_control.dart';
|
||||||
import 'image_control.dart';
|
import 'image_control.dart';
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class _MediaAttachmentViewerControlState
|
||||||
altText: item.description,
|
altText: item.description,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) {
|
Navigator.push(context, MaterialPageRoute(builder: (context) {
|
||||||
return ImageViewerScreen(
|
return MediaViewerScreen(
|
||||||
attachments: widget.attachments,
|
attachments: widget.attachments,
|
||||||
initialIndex: widget.index,
|
initialIndex: widget.index,
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,7 +29,7 @@ void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
MediaKit.ensureInitialized();
|
MediaKit.ensureInitialized();
|
||||||
// await dotenv.load(fileName: '.env');
|
// await dotenv.load(fileName: '.env');
|
||||||
const enablePreview = false;
|
const enablePreview = true;
|
||||||
Logger.root.level = Level.FINER;
|
Logger.root.level = Level.FINER;
|
||||||
Logger.root.onRecord.listen((event) {
|
Logger.root.onRecord.listen((event) {
|
||||||
final logName = event.loggerName.isEmpty ? 'ROOT' : event.loggerName;
|
final logName = event.loggerName.isEmpty ? 'ROOT' : event.loggerName;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import '../models/visibility.dart';
|
||||||
import '../serializers/friendica/image_entry_friendica_extensions.dart';
|
import '../serializers/friendica/image_entry_friendica_extensions.dart';
|
||||||
import '../services/gallery_service.dart';
|
import '../services/gallery_service.dart';
|
||||||
import '../utils/active_profile_selector.dart';
|
import '../utils/active_profile_selector.dart';
|
||||||
import 'image_viewer_screen.dart';
|
import 'media_viewer_screen.dart';
|
||||||
|
|
||||||
class ExistingImageSelectorScreen extends StatefulWidget {
|
class ExistingImageSelectorScreen extends StatefulWidget {
|
||||||
final Visibility? visibilityFilter;
|
final Visibility? visibilityFilter;
|
||||||
|
@ -171,7 +171,7 @@ class _ExistingImageSelectorScreenState
|
||||||
onDoubleTap: () {
|
onDoubleTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => ImageViewerScreen(
|
builder: (context) => MediaViewerScreen(
|
||||||
attachments: [image.toMediaAttachment()],
|
attachments: [image.toMediaAttachment()],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -11,7 +11,7 @@ import '../serializers/friendica/image_entry_friendica_extensions.dart';
|
||||||
import '../services/gallery_service.dart';
|
import '../services/gallery_service.dart';
|
||||||
import '../services/network_status_service.dart';
|
import '../services/network_status_service.dart';
|
||||||
import '../utils/active_profile_selector.dart';
|
import '../utils/active_profile_selector.dart';
|
||||||
import 'image_viewer_screen.dart';
|
import 'media_viewer_screen.dart';
|
||||||
|
|
||||||
class GalleryScreen extends StatelessWidget {
|
class GalleryScreen extends StatelessWidget {
|
||||||
static const thumbnailDimension = 350.0;
|
static const thumbnailDimension = 350.0;
|
||||||
|
@ -117,7 +117,7 @@ class GalleryScreen extends StatelessWidget {
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) {
|
Navigator.push(context, MaterialPageRoute(builder: (context) {
|
||||||
return ImageViewerScreen(
|
return MediaViewerScreen(
|
||||||
attachments: attachments,
|
attachments: attachments,
|
||||||
initialIndex: index,
|
initialIndex: index,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,153 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:carousel_slider/carousel_slider.dart';
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
|
|
||||||
import '../controls/login_aware_cached_network_image.dart';
|
|
||||||
import '../friendica_client/friendica_client.dart';
|
|
||||||
import '../globals.dart';
|
|
||||||
import '../models/media_attachment.dart';
|
|
||||||
import '../services/auth_service.dart';
|
|
||||||
import '../utils/clipboard_utils.dart';
|
|
||||||
import '../utils/snackbar_builder.dart';
|
|
||||||
|
|
||||||
class ImageViewerScreen extends StatefulWidget {
|
|
||||||
final List<MediaAttachment> attachments;
|
|
||||||
final int initialIndex;
|
|
||||||
|
|
||||||
const ImageViewerScreen({
|
|
||||||
super.key,
|
|
||||||
required this.attachments,
|
|
||||||
this.initialIndex = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ImageViewerScreen> createState() => _ImageViewerScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ImageViewerScreenState extends State<ImageViewerScreen> {
|
|
||||||
MediaAttachment? currentAttachment;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
currentAttachment = widget.attachments[widget.initialIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> saveImage(
|
|
||||||
BuildContext context,
|
|
||||||
MediaAttachment attachment,
|
|
||||||
) async {
|
|
||||||
buildSnackbar(context, 'Downloading full image to save locally');
|
|
||||||
final appsDir = await getApplicationDocumentsDirectory();
|
|
||||||
final filename = p.basename(attachment.fullFileUri.path);
|
|
||||||
final bytesResult =
|
|
||||||
await RemoteFileClient(getIt<AccountsService>().currentProfile)
|
|
||||||
.getFileBytes(attachment.uri);
|
|
||||||
if (bytesResult.isFailure && mounted) {
|
|
||||||
buildSnackbar(context,
|
|
||||||
'Error getting full size version of file: ${bytesResult.error}');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Platform.isAndroid || Platform.isIOS) {
|
|
||||||
final params = SaveFileDialogParams(
|
|
||||||
data: bytesResult.value,
|
|
||||||
fileName: filename,
|
|
||||||
);
|
|
||||||
await FlutterFileDialog.saveFile(params: params);
|
|
||||||
} else {
|
|
||||||
final location = await FilePicker.platform.saveFile(
|
|
||||||
dialogTitle: 'Save Image',
|
|
||||||
fileName: filename,
|
|
||||||
initialDirectory: appsDir.path,
|
|
||||||
);
|
|
||||||
if (location != null) {
|
|
||||||
await File(location).writeAsBytes(bytesResult.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final width = MediaQuery.of(context).size.width;
|
|
||||||
final height = MediaQuery.of(context).size.height;
|
|
||||||
final carouselHeight =
|
|
||||||
widget.attachments.length == 1 ? height * 0.9 : height * 0.8;
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: currentAttachment == null
|
|
||||||
? null
|
|
||||||
: () => saveImage(context, currentAttachment!),
|
|
||||||
icon: const Icon(Icons.download))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
CarouselSlider.builder(
|
|
||||||
disableGesture: true,
|
|
||||||
itemCount: widget.attachments.length,
|
|
||||||
itemBuilder: (context, index, realIndex) {
|
|
||||||
return SizedBox(
|
|
||||||
width: width,
|
|
||||||
height: carouselHeight,
|
|
||||||
child: InteractiveViewer(
|
|
||||||
maxScale: 10.0,
|
|
||||||
scaleFactor: 400,
|
|
||||||
child: LoginAwareCachedNetworkImage(
|
|
||||||
imageUrl: widget.attachments[index].uri.toString()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
options: CarouselOptions(
|
|
||||||
height: carouselHeight,
|
|
||||||
initialPage: widget.initialIndex,
|
|
||||||
enableInfiniteScroll: false,
|
|
||||||
enlargeCenterPage: true,
|
|
||||||
viewportFraction: 0.95,
|
|
||||||
onPageChanged: (index, reason) {
|
|
||||||
setState(() {
|
|
||||||
currentAttachment = widget.attachments[index];
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
if (currentAttachment != null) buildTextArea(currentAttachment!),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildTextArea(MediaAttachment attachment) {
|
|
||||||
return Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Text(attachment.description),
|
|
||||||
)),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await copyToClipboard(
|
|
||||||
context: context,
|
|
||||||
text: attachment.description,
|
|
||||||
message: 'Image description copied to clipboard');
|
|
||||||
},
|
|
||||||
icon: Icon(Icons.copy),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
import '../controls/login_aware_cached_network_image.dart';
|
||||||
|
import '../friendica_client/friendica_client.dart';
|
||||||
|
import '../globals.dart';
|
||||||
|
import '../models/media_attachment.dart';
|
||||||
|
import '../services/auth_service.dart';
|
||||||
|
import '../utils/clipboard_utils.dart';
|
||||||
|
import '../utils/snackbar_builder.dart';
|
||||||
|
|
||||||
|
class MediaViewerScreen extends StatefulWidget {
|
||||||
|
final List<MediaAttachment> attachments;
|
||||||
|
final int initialIndex;
|
||||||
|
|
||||||
|
const MediaViewerScreen({
|
||||||
|
super.key,
|
||||||
|
required this.attachments,
|
||||||
|
this.initialIndex = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MediaViewerScreen> createState() => _MediaViewerScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MediaViewerScreenState extends State<MediaViewerScreen> {
|
||||||
|
var currentIndex = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
currentIndex = widget.initialIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> saveImage(
|
||||||
|
BuildContext context,
|
||||||
|
MediaAttachment attachment,
|
||||||
|
) async {
|
||||||
|
buildSnackbar(context, 'Downloading full image to save locally');
|
||||||
|
final appsDir = await getApplicationDocumentsDirectory();
|
||||||
|
final filename = p.basename(attachment.fullFileUri.path);
|
||||||
|
final bytesResult =
|
||||||
|
await RemoteFileClient(getIt<AccountsService>().currentProfile)
|
||||||
|
.getFileBytes(attachment.uri);
|
||||||
|
if (bytesResult.isFailure && mounted) {
|
||||||
|
buildSnackbar(context,
|
||||||
|
'Error getting full size version of file: ${bytesResult.error}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Platform.isAndroid || Platform.isIOS) {
|
||||||
|
final params = SaveFileDialogParams(
|
||||||
|
data: bytesResult.value,
|
||||||
|
fileName: filename,
|
||||||
|
);
|
||||||
|
await FlutterFileDialog.saveFile(params: params);
|
||||||
|
} else {
|
||||||
|
final location = await FilePicker.platform.saveFile(
|
||||||
|
dialogTitle: 'Save Image',
|
||||||
|
fileName: filename,
|
||||||
|
initialDirectory: appsDir.path,
|
||||||
|
);
|
||||||
|
if (location != null) {
|
||||||
|
await File(location).writeAsBytes(bytesResult.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final currentAttachment = widget.attachments[currentIndex];
|
||||||
|
final height = MediaQuery.of(context).size.height;
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => saveImage(context, currentAttachment),
|
||||||
|
icon: const Icon(Icons.download))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: InteractiveViewer(
|
||||||
|
maxScale: 10.0,
|
||||||
|
scaleFactor: 400,
|
||||||
|
child: LoginAwareCachedNetworkImage(
|
||||||
|
imageUrl:
|
||||||
|
widget.attachments[currentIndex].uri.toString()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (currentAttachment.description.isNotEmpty)
|
||||||
|
buildTextArea(currentAttachment),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (widget.attachments.length > 1) ...[
|
||||||
|
Positioned(
|
||||||
|
bottom: height * 0.5,
|
||||||
|
child: Opacity(
|
||||||
|
opacity: 0.8,
|
||||||
|
child: currentIndex < 1
|
||||||
|
? null
|
||||||
|
: Container(
|
||||||
|
color: Colors.black,
|
||||||
|
child: IconButton(
|
||||||
|
color: Colors.white,
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
currentIndex--;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.chevron_left),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: height * 0.5,
|
||||||
|
right: 0.0,
|
||||||
|
child: Opacity(
|
||||||
|
opacity: 0.8,
|
||||||
|
child: currentIndex >= widget.attachments.length - 1
|
||||||
|
? null
|
||||||
|
: Container(
|
||||||
|
color: Colors.black,
|
||||||
|
child: IconButton(
|
||||||
|
color: Colors.white,
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
currentIndex++;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.chevron_right),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildTextArea(MediaAttachment attachment) {
|
||||||
|
final height = MediaQuery.of(context).size.height * 0.1;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: SizedBox(
|
||||||
|
height: height,
|
||||||
|
child: attachment.description.isEmpty
|
||||||
|
? null
|
||||||
|
: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Text(attachment.description),
|
||||||
|
)),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await copyToClipboard(
|
||||||
|
context: context,
|
||||||
|
text: attachment.description,
|
||||||
|
message: 'Image description copied to clipboard');
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.copy),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Ładowanie…
Reference in New Issue