INFRA: Set Up Project.
This commit is contained in:
225
lib/widget/story_view/widgets/story_image.dart
Normal file
225
lib/widget/story_view/widgets/story_image.dart
Normal file
@@ -0,0 +1,225 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:get/get_utils/src/extensions/internacionalization.dart';
|
||||
|
||||
import '../controller/story_controller.dart';
|
||||
import '../utils.dart';
|
||||
|
||||
/// Utitlity to load image (gif, png, jpg, etc) media just once. Resource is
|
||||
/// cached to disk with default configurations of [DefaultCacheManager].
|
||||
class ImageLoader {
|
||||
ui.Codec? frames;
|
||||
|
||||
String url;
|
||||
|
||||
Map<String, dynamic>? requestHeaders;
|
||||
|
||||
LoadState state = LoadState.loading; // by default
|
||||
|
||||
ImageLoader(this.url, {this.requestHeaders});
|
||||
|
||||
/// Load image from disk cache first, if not found then load from network.
|
||||
/// `onComplete` is called when [imageBytes] become available.
|
||||
void loadImage(VoidCallback onComplete) {
|
||||
if (frames != null) {
|
||||
state = LoadState.success;
|
||||
onComplete();
|
||||
}
|
||||
|
||||
final fileStream = DefaultCacheManager().getFileStream(url, headers: requestHeaders as Map<String, String>?);
|
||||
|
||||
fileStream.listen(
|
||||
(fileResponse) {
|
||||
if (fileResponse is! FileInfo) return;
|
||||
// the reason for this is that, when the cache manager fetches
|
||||
// the image again from network, the provided `onComplete` should
|
||||
// not be called again
|
||||
if (frames != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final imageBytes = fileResponse.file.readAsBytesSync();
|
||||
|
||||
state = LoadState.success;
|
||||
|
||||
ui.instantiateImageCodec(imageBytes).then((codec) {
|
||||
frames = codec;
|
||||
onComplete();
|
||||
}, onError: (error) {
|
||||
state = LoadState.failure;
|
||||
onComplete();
|
||||
});
|
||||
},
|
||||
onError: (error) {
|
||||
state = LoadState.failure;
|
||||
onComplete();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Widget to display animated gifs or still images. Shows a loader while image
|
||||
/// is being loaded. Listens to playback states from [controller] to pause and
|
||||
/// forward animated media.
|
||||
class StoryImage extends StatefulWidget {
|
||||
final ImageLoader imageLoader;
|
||||
|
||||
final BoxFit? fit;
|
||||
|
||||
final StoryController? controller;
|
||||
final Widget? loadingWidget;
|
||||
final Widget? errorWidget;
|
||||
|
||||
StoryImage(
|
||||
this.imageLoader, {
|
||||
Key? key,
|
||||
this.controller,
|
||||
this.fit,
|
||||
this.loadingWidget,
|
||||
this.errorWidget,
|
||||
}) : super(key: key ?? UniqueKey());
|
||||
|
||||
/// Use this shorthand to fetch images/gifs from the provided [url]
|
||||
factory StoryImage.url(
|
||||
String url, {
|
||||
StoryController? controller,
|
||||
Map<String, dynamic>? requestHeaders,
|
||||
BoxFit fit = BoxFit.fitWidth,
|
||||
Widget? loadingWidget,
|
||||
Widget? errorWidget,
|
||||
Key? key,
|
||||
}) {
|
||||
return StoryImage(
|
||||
ImageLoader(
|
||||
url,
|
||||
requestHeaders: requestHeaders,
|
||||
),
|
||||
controller: controller,
|
||||
fit: fit,
|
||||
loadingWidget: loadingWidget,
|
||||
errorWidget: errorWidget,
|
||||
key: key,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => StoryImageState();
|
||||
}
|
||||
|
||||
class StoryImageState extends State<StoryImage> {
|
||||
ui.Image? currentFrame;
|
||||
|
||||
Timer? _timer;
|
||||
|
||||
StreamSubscription<PlaybackState>? _streamSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.controller != null) {
|
||||
_streamSubscription = widget.controller!.playbackNotifier.listen((playbackState) {
|
||||
// for the case of gifs we need to pause/play
|
||||
if (widget.imageLoader.frames == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (playbackState == PlaybackState.pause) {
|
||||
_timer?.cancel();
|
||||
} else {
|
||||
forward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
widget.controller?.pause();
|
||||
|
||||
widget.imageLoader.loadImage(() async {
|
||||
if (mounted) {
|
||||
if (widget.imageLoader.state == LoadState.success) {
|
||||
widget.controller?.play();
|
||||
forward();
|
||||
} else {
|
||||
// refresh to show error
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
_streamSubscription?.cancel();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void setState(fn) {
|
||||
if (mounted) {
|
||||
super.setState(fn);
|
||||
}
|
||||
}
|
||||
|
||||
void forward() async {
|
||||
_timer?.cancel();
|
||||
|
||||
if (widget.controller != null && widget.controller!.playbackNotifier.stream.value == PlaybackState.pause) {
|
||||
return;
|
||||
}
|
||||
|
||||
final nextFrame = await widget.imageLoader.frames!.getNextFrame();
|
||||
|
||||
currentFrame = nextFrame.image;
|
||||
|
||||
if (nextFrame.duration > const Duration(milliseconds: 0)) {
|
||||
_timer = Timer(nextFrame.duration, forward);
|
||||
}
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Widget getContentView() {
|
||||
switch (widget.imageLoader.state) {
|
||||
case LoadState.success:
|
||||
return RawImage(
|
||||
image: currentFrame,
|
||||
fit: widget.fit,
|
||||
);
|
||||
case LoadState.failure:
|
||||
return Center(
|
||||
child: widget.errorWidget ??
|
||||
Text(
|
||||
"Image failed to load.".tr,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
));
|
||||
default:
|
||||
return Center(
|
||||
child: widget.loadingWidget ??
|
||||
const SizedBox(
|
||||
width: 70,
|
||||
height: 70,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
strokeWidth: 3,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
child: getContentView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user