INFRA: Set Up Project.

This commit is contained in:
2025-11-28 11:10:49 +05:00
commit c798279f7d
609 changed files with 77436 additions and 0 deletions

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