So, I am making the sport application with the bookmark articles functionality. The bookmark article widget has an animated icon with a simple size animation. After clicking on the icon that removes the article, the icons in other widgets refresh in a few moments. I put the link with the screen video below.
https://firebasestorage.googleapis.com/v0/b/footballapp-2fb40.appspot.com/o/Screen_Recording_20210727-122200.mp4?alt=media&token=794fb85b-659a-4127-a6cb-e784bde7ff72
class BookmarkArticleIcon extends StatefulWidget {
final Color iconColor;
final double iconSize;
final Article article;
final ValueKey key;
BookmarkArticleIcon({this.iconColor, this.iconSize, this.article, this.key})
: super(key: key);
#override
_BookmarkArticleIconState createState() => _BookmarkArticleIconState();
}
class _BookmarkArticleIconState extends State<BookmarkArticleIcon>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _animation;
#override
void initState() {
super.initState();
BlocProvider.of<BookmarkArticleBloc>(context)
.add(FetchInitialArticleState(article: widget.article));
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 100))
..addListener(() {
if (_animationController.isCompleted) {
_animationController.reverse();
}
});
_animation =
Tween<double>(begin: 1.0, end: 1.2).animate(_animationController);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<BookmarkArticleBloc, BookmarkArticleState>(
buildWhen: _buildWhenFunction,
builder: (context, state) {
if (state is BookmarkArticleResult) {
return AnimatedBuilder(
animation: _animationController,
builder: (context, _) {
return Transform.scale(
scale: _animation.value,
child: InkWell(
child: Icon(
(state.isBookmarked)
? Icons.bookmark
: Icons.bookmark_outline,
color: widget.iconColor,
size: widget.iconSize,
),
onTap: () {
_onTapFunction(state);
}),
);
});
} else {
return LoadingWidget(
iconColor: widget.iconColor,
);
}
},
);
}
void _onTapFunction(BookmarkArticleResult state) {
final BookmarkArticleBloc _bookmarkArticleBloc =
BlocProvider.of<BookmarkArticleBloc>(context);
_animationController.forward();
if (state.isBookmarked) {
_bookmarkArticleBloc
.add(RemoveBookmarkedArticle(article: widget.article));
} else {
_bookmarkArticleBloc.add(AddBookmarkArticle(article: widget.article));
}
}
bool _buildWhenFunction(previous, current) {
if ((current is BookmarkArticleResult &&
current.articleID == widget.article.id) ||
(current is LoadingBookmarkArticle &&
current.articleID == widget.article.id)) {
return true;
} else {
return false;
}
}
}
class LoadingWidget extends StatelessWidget {
final Color iconColor;
LoadingWidget({this.iconColor});
#override
Widget build(BuildContext context) {
return Container(
key: UniqueKey(),
margin: EdgeInsets.all(4),
width: 16,
height: 16,
child: CircularProgressIndicator(
color: iconColor,
strokeWidth: 2.0,
),
);
}
}
Related
i have a CupertinoModalBottomSheet that is closing by scrolling down. And in this page i have image that i can pinch by two fingers. Is there any way to change priority to pinching when two fingers on screen. Because when i put two fingers on screen i can still close the page by scrolling down until i start pinching.
class PinchZoom extends StatefulWidget {
final Widget child;
PinchZoom({
Key? key,
required this.child,
}) : super(key: key);
#override
State<PinchZoom> createState() => _PinchZoomState();
}
class _PinchZoomState extends State<PinchZoom>
with SingleTickerProviderStateMixin {
late TransformationController controller;
late AnimationController animationController;
Animation<Matrix4>? animation;
Map<int, Offset> touchPositions = <int, Offset>{};
final double minScale = 1;
final double maxScale = 2;
double scale = 1;
OverlayEntry? entry;
#override
void initState() {
super.initState();
controller = TransformationController();
animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 200),
)
..addListener(() => controller.value = animation!.value)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
removeOverlay();
}
});
}
#override
void dispose() {
controller.dispose();
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) =>
BlocBuilder<DishInfoCubit, DishInfoState>(
builder: (_, _state) {
final dishCubit = BlocProvider.of<DishInfoCubit>(context);
return Builder(
builder: (context) => Listener(
onPointerMove: (opm) {
savePointerPosition(opm.pointer, opm.position);
if (touchPositions.length > 1) {
dishCubit.setIsPinchInProgress(true);
}
},
onPointerDown: (opm) {
savePointerPosition(opm.pointer, opm.position);
if (touchPositions.length > 1) {
dishCubit.setIsPinchInProgress(true);
}
},
onPointerCancel: (opc) {
clearPointerPosition(opc.pointer);
if (touchPositions.length < 2) {
dishCubit.setIsPinchInProgress(false);
}
},
onPointerUp: (opc) {
clearPointerPosition(opc.pointer);
if (touchPositions.length < 2) {
dishCubit.setIsPinchInProgress(false);
}
},
child: InteractiveViewer(
transformationController: controller,
clipBehavior: Clip.none,
panEnabled: false,
minScale: minScale,
maxScale: maxScale,
onInteractionStart: (details) {
print("trying to start anim");
if (details.pointerCount < 2) return;
animationController.stop();
},
onInteractionEnd: (details) {
if (details.pointerCount != 1) return;
resetAnimation();
dishCubit.setIsPinchInProgress(false);
},
child: AspectRatio(
aspectRatio: 1,
child: widget.child,
),
),
),
);
},
);
void removeOverlay() {
entry?.remove();
entry = null;
}
void resetAnimation() {
animation = Matrix4Tween(
begin: controller.value,
end: Matrix4.identity(),
).animate(
CurvedAnimation(parent: animationController, curve: Curves.easeInOut),
);
animationController.forward(from: 0);
}
void savePointerPosition(int index, Offset position) {
setState(() {
touchPositions[index] = position;
});
}
void clearPointerPosition(int index) {
setState(() {
touchPositions.remove(index);
});
}
}
When I build the application, I encounter such an error.
lib/input_page/pacman_slider.dart:41:7: Error: A value of type 'Animation<BorderRadius?>' can't be assigned to a variable of type 'Animation' because 'BorderRadius?' is nullable and 'BorderRadius' isn't.
'Animation' is from 'package:flutter/src/animation/animation.dart' ('../../Dev/flutter/packages/flutter/lib/src/animation/animation.dart').
'BorderRadius' is from 'package:flutter/src/painting/border_radius.dart' ('../../Dev/flutter/packages/flutter/lib/src/painting/border_radius.dart').
).animate(CurvedAnimation(
^
Error Code:
void initState() {
super.initState();
pacmanMovementController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400));
_bordersAnimation = BorderRadiusTween(
begin: BorderRadius.circular(8.0),
end: BorderRadius.circular(50.0),
).animate(CurvedAnimation(
parent: widget.submitAnimationController,
curve: Interval(0.0, 0.07),
));
}
Full Code:
const double _pacmanWidth = 21.0;
const double _sliderHorizontalMargin = 24.0;
const double _dotsLeftMargin = 8.0;
class PacmanSlider extends StatefulWidget {
final VoidCallback onSubmit;
final AnimationController submitAnimationController;
const PacmanSlider(
{Key? key,
required this.onSubmit,
required this.submitAnimationController})
: super(key: key);
#override
_PacmanSliderState createState() => _PacmanSliderState();
}
class _PacmanSliderState extends State<PacmanSlider>
with TickerProviderStateMixin {
double _pacmanPosition = 24.0;
late Animation<BorderRadius> _bordersAnimation;
late Animation<double> _submitWidthAnimation;
late AnimationController pacmanMovementController;
late Animation<double> pacmanAnimation;
#override
void initState() {
super.initState();
pacmanMovementController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400));
_bordersAnimation = BorderRadiusTween(
begin: BorderRadius.circular(8.0),
end: BorderRadius.circular(50.0),
).animate(CurvedAnimation(
parent: widget.submitAnimationController,
curve: Interval(0.0, 0.07),
));
}
#override
void dispose() {
pacmanMovementController.dispose();
super.dispose();
}
// double get width => _submitWidthAnimation?.value ?? 0.0;
double get width => _submitWidthAnimation.value;
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
_submitWidthAnimation = Tween<double>(
begin: constraints.maxWidth,
end: screenAwareSize(52.0, context),
).animate(CurvedAnimation(
parent: widget.submitAnimationController,
curve: Interval(0.05, 0.15),
));
return AnimatedBuilder(
animation: widget.submitAnimationController,
builder: (context, child) {
Decoration decoration = BoxDecoration(
borderRadius: _bordersAnimation.value,
color: Theme.of(context).primaryColor,
);
return Center(
child: Container(
height: screenAwareSize(52.0, context),
width: width,
decoration: decoration,
child: _submitWidthAnimation.isDismissed
? GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => _animatePacmanToEnd(),
child: Stack(
alignment: Alignment.centerRight,
children: <Widget>[
AnimatedDots(),
_drawDotCurtain(decoration),
_drawPacman(),
],
),
)
: Container(),
),
);
},
);
},
);
}
Widget _drawDotCurtain(Decoration decoration) {
if (width == 0.0) {
return Container();
}
double marginRight =
width - _pacmanPosition - screenAwareSize(_pacmanWidth / 2, context);
return Positioned.fill(
right: marginRight,
child: Container(decoration: decoration),
);
}
Widget _drawPacman() {
pacmanAnimation = _initPacmanAnimation();
return Positioned(
left: _pacmanPosition,
child: GestureDetector(
onHorizontalDragUpdate: (details) => _onPacmanDrag(width, details),
onHorizontalDragEnd: (details) => _onPacmanEnd(width, details),
child: PacmanIcon(),
),
);
}
Animation<double> _initPacmanAnimation() {
Animation<double> animation = Tween(
begin: _pacmanMinPosition(),
end: _pacmanMaxPosition(width),
).animate(pacmanMovementController);
animation.addListener(() {
setState(() {
_pacmanPosition = animation.value;
});
if (animation.status == AnimationStatus.completed) {
_onPacmanSubmit();
}
});
return animation;
}
_onPacmanSubmit() {
widget.onSubmit();
Future.delayed(Duration(seconds: 1), () => _resetPacman());
}
_onPacmanDrag(double width, DragUpdateDetails details) {
setState(() {
_pacmanPosition += details.delta.dx;
_pacmanPosition = math.max(_pacmanMinPosition(),
math.min(_pacmanMaxPosition(width), _pacmanPosition));
});
}
_onPacmanEnd(double width, DragEndDetails details) {
bool isOverHalf =
_pacmanPosition + screenAwareSize(_pacmanWidth / 2, context) >
0.5 * width;
if (isOverHalf) {
_animatePacmanToEnd();
} else {
_resetPacman();
}
}
_animatePacmanToEnd() {
pacmanMovementController.forward(
from: _pacmanPosition / _pacmanMaxPosition(width));
}
_resetPacman() {
if (this.mounted) {
setState(() => _pacmanPosition = _pacmanMinPosition());
}
}
double _pacmanMinPosition() =>
screenAwareSize(_sliderHorizontalMargin, context);
double _pacmanMaxPosition(double sliderWidth) =>
sliderWidth -
screenAwareSize(_sliderHorizontalMargin / 2 + _pacmanWidth, context);
}
class AnimatedDots extends StatefulWidget {
#override
_AnimatedDotsState createState() => _AnimatedDotsState();
}
class _AnimatedDotsState extends State<AnimatedDots>
with TickerProviderStateMixin {
final int numberOfDots = 10;
final double minOpacity = 0.1;
final double maxOpacity = 0.5;
late AnimationController hintAnimationController;
#override
void initState() {
super.initState();
_initHintAnimationController();
hintAnimationController.forward();
}
#override
void dispose() {
hintAnimationController.dispose();
super.dispose();
}
void _initHintAnimationController() {
hintAnimationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 800),
);
hintAnimationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
Future.delayed(Duration(milliseconds: 800), () {
if (this.mounted) {
hintAnimationController.forward(from: 0.0);
}
});
}
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
left: screenAwareSize(
_sliderHorizontalMargin + _pacmanWidth + _dotsLeftMargin,
context),
right: screenAwareSize(_sliderHorizontalMargin, context)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(numberOfDots, _generateDot)
..add(Opacity(
opacity: maxOpacity,
child: Dot(size: 14.0),
)),
),
);
}
Widget _generateDot(int dotNumber) {
Animation animation = _initDotAnimation(dotNumber);
return AnimatedBuilder(
animation: animation,
builder: (context, child) => Opacity(
opacity: animation.value,
child: child,
),
child: Dot(size: 9.0),
);
}
Animation<double> _initDotAnimation(int dotNumber) {
double lastDotStartTime = 0.4;
double dotAnimationDuration = 0.5;
double begin = lastDotStartTime * dotNumber / numberOfDots;
double end = begin + dotAnimationDuration;
return SinusoidalAnimation(min: minOpacity, max: maxOpacity).animate(
CurvedAnimation(
parent: hintAnimationController,
curve: Interval(begin, end),
),
);
}
}
class SinusoidalAnimation extends Animatable<double> {
SinusoidalAnimation({required this.min, required this.max});
final double min;
final double max;
#override
double transform(double t) {
return min + (max - min) * math.sin(math.pi * t);
}
}
class Dot extends StatelessWidget {
final double size;
const Dot({Key? key, required this.size}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: screenAwareSize(size, context),
width: screenAwareSize(size, context),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
);
}
}
class PacmanIcon extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
right: screenAwareSize(16.0, context),
),
child: SvgPicture.asset(
'images/pacman.svg',
height: screenAwareSize(25.0, context),
width: screenAwareSize(21.0, context),
),
);
}
}
As you see in the attached capture, BorderRadiusTween has a nullable value of type BorderRadius?
I'd suggest to either declare _bordersAnimation as Animation<BorderRadius?>or just using an Animation<double> instead
Does anyone know how to do Flutter Video Caching in List for 4 to 6 seconds? (For next 5 videos) like Instagram reels.
Is there any way to do it?
I had taken PageView.builder to show a video with a full page.
I have tried one cached_video_player but it's loading full video.
Here is what I have done.
VideoWidget:
typedef OnVideoCompleted = void Function();
class VideoWidget extends StatefulWidget {
//final bool? play;
final bool? isDuetVideo;
final String? url;
final OnVideoCompleted? onVideoCompleted;
final HomeWidgetBloc? homeWidgetBloc;
const VideoWidget(
{Key? key,
this.onVideoCompleted,
required this.url,
required this.homeWidgetBloc,
required this.isDuetVideo})
: super(key: key);
#override
_VideoWidgetState createState() => _VideoWidgetState();
}
class _VideoWidgetState extends State<VideoWidget> {
late VideoPlayerController videoPlayerController;
late Future<void> _initializeVideoPlayerFuture;
final _controllerStateStream = BehaviorSubject<int>.seeded(0);
VoidCallback? _listener;
StreamSubscription? _playPauseSubscription;
#override
void initState() {
super.initState();
videoPlayerController = new VideoPlayerController.network(widget.url!);
videoPlayerController.initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
_controllerStateStream.add(1);
_observeForPlayPause();
_observerForSeekDuration();
_listener = () {
final value =
widget.homeWidgetBloc?.videoEndedStream.valueWrapper?.value;
print('duration -----value--- ${value}');
if (videoPlayerController.value.duration.inSeconds > 0 &&
videoPlayerController.value.position.inMilliseconds ==
videoPlayerController.value.duration.inMilliseconds &&
(videoPlayerController.value.position.inMilliseconds ==
videoPlayerController.value.duration.inMilliseconds)) {
// FOR AUTO PLAY NEXT VIDEO...
widget.onVideoCompleted?.call();
print(
'duration -----addListener--- ${videoPlayerController.value.duration}');
}
};
videoPlayerController.addListener(_listener!);
});
} // This closing tag was missing
#override
void dispose() {
super.dispose();
_controllerStateStream.close();
_playPauseSubscription?.cancel();
try {
if (_listener != null) {
videoPlayerController.removeListener(_listener!);
}
videoPlayerController.dispose();
} catch (e) {
print(e.toString());
}
}
#override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: _controllerStateStream,
builder: (context, snapshot) {
final isReady = (snapshot.data ?? 0) == 1;
if (!isReady) {
return _progressWidget();
}
return new Stack(children: <Widget>[
Container(
child: Center(
child: (widget.isDuetVideo! ||
videoPlayerController.value.size.width >
videoPlayerController.value.size.height)
? AspectRatio(
child: VideoPlayer(
videoPlayerController,
),
aspectRatio: videoPlayerController.value.aspectRatio,
)
: VideoPlayer(
videoPlayerController,
),
widthFactor: double.maxFinite,
heightFactor: double.maxFinite,
),
),
Visibility(
visible: !widget.isDuetVideo!,
child: VideoPlayerCustomControlsWidget(
controller: videoPlayerController,
),
),
]);
},
);
}
Center _progressWidget() {
return Center(
child: CircularProgressIndicator(
color: AppStyles.primary500Color,
),
);
}
void _observeForPlayPause() {
_playPauseSubscription =
widget.homeWidgetBloc?.videoPlayPauseStream.listen((value) {
if (value == PLAY)
videoPlayerController.play();
else
videoPlayerController.pause();
});
}
void _observerForSeekDuration() {
_playPauseSubscription =
widget.homeWidgetBloc?.duetVideoSeekDurationZeroStream.listen((value) {
if (value == true) videoPlayerController.seekTo(Duration.zero);
widget.homeWidgetBloc?.duetVideoSeekDurationZeroStream.add(false);
});
}
}
Update:
I found many answers (like this) but that all answers are only for caching the current video, not the next/prev videos from the list. I want it, especially for the list.
this is what I used in my app, preload_page_view, it preloads a specific count of pre/next pages:
#override
Widget build(BuildContext context) {
return new PreloadPageView.builder(
itemCount: ...,
itemBuilder: ...,
onPageChanged: (int position) {...},
.....
preloadPagesCount: 3,
controller: PreloadPageController(),
);
}
I made this code based on this solution https://stackoverflow.com/a/56218422/5956942
Works very well, using any widget on child page.
But if i try put a webview widgt on new page, i got following errors when add the first tab. Second and another pages not fire this errors.
E/flutter (20419): [ERROR:flutter/lib/ui/ui_dart_state.cc(199)]
Unhandled Exception: setState() called after dispose():
_PlatformViewLinkState#a7b8e(lifecycle state: defunct, not mounted)
E/flutter (20419): This error happens if you call setState() on a
State object for a widget that no longer appears in the widget tree
(e.g., whose parent widget no longer includes the widget in its
build). This error can occur when code calls setState() from a timer
or an animation callback.
E/flutter (20419): The preferred solution is
to cancel the timer or stop listening to the animation in the
dispose() callback. Another solution is to check the "mounted"
property of this object before calling setState() to ensure the object
is still in the tree.
E/flutter (20419): This error might indicate a
memory leak if setState() is being called because another object is
retaining a reference to this State object after it has been removed
from the tree. To avoid memory leaks, consider breaking the reference
to this object during dispose().
This is my code:
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'dart:async';
import 'dart:io';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Home(),
debugShowCheckedModeBanner: false,
);
}
}
class Home extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<Home> {
List<Widget> data = [Container()];
int initPosition = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: CustomTabView(
initPosition: initPosition,
itemCount: data.length,
tabBuilder: (context, index) {
if (index == 0) {
return Tab(icon: Icon(Icons.home));
} else {
return Tab(text: index.toString());
}
},
pageBuilder: (context, index) {
if (index == 0) {
return Center(
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
data.add(Page(
url: 'https://www.google.com',
key: ValueKey('$index'),
));
});
}));
} else {
return Center(child: (data[index]));
}
},
onPositionChange: (index) {
print('current position: $index');
initPosition = index;
},
onScroll: (position) => print('$position'),
),
),
);
}
}
/// Implementation
class CustomTabView extends StatefulWidget {
final int? itemCount;
final IndexedWidgetBuilder? tabBuilder;
final IndexedWidgetBuilder? pageBuilder;
final Widget? stub;
final ValueChanged<int>? onPositionChange;
final ValueChanged<double>? onScroll;
final int? initPosition;
CustomTabView({
#required this.itemCount,
#required this.tabBuilder,
#required this.pageBuilder,
this.stub,
this.onPositionChange,
this.onScroll,
this.initPosition,
});
#override
_CustomTabsState createState() => _CustomTabsState();
}
class _CustomTabsState extends State<CustomTabView>
with TickerProviderStateMixin {
TabController? controller;
int? _currentCount;
int? _currentPosition;
#override
void initState() {
_currentPosition = widget.initPosition ?? 0;
controller = TabController(
length: widget.itemCount!,
vsync: this,
initialIndex: _currentPosition!,
);
controller!.addListener(onPositionChange);
controller!.animation!.addListener(onScroll);
_currentCount = widget.itemCount;
super.initState();
}
#override
void didUpdateWidget(CustomTabView oldWidget) {
print('current position: $_currentPosition');
if (_currentCount != widget.itemCount) {
print('_currentCount != widget.itemCount');
controller!.animation!.removeListener(onScroll);
controller!.removeListener(onPositionChange);
controller!.dispose();
if (widget.initPosition != null) {
print('widget.initPosition != null');
_currentPosition = widget.initPosition;
}
if (_currentPosition! > widget.itemCount! - 1) {
print('_currentPosition > widget.itemCount - 1');
_currentPosition = widget.itemCount! - 1;
_currentPosition = _currentPosition! < 0 ? 0 : _currentPosition;
if (widget.onPositionChange is ValueChanged<int>) {
WidgetsBinding.instance!.addPostFrameCallback((_) {
if (mounted) {
widget.onPositionChange!(_currentPosition!);
}
});
}
}
_currentCount = widget.itemCount;
setState(() {
controller = TabController(
length: widget.itemCount!,
vsync: this,
initialIndex: _currentPosition!,
);
controller!.addListener(onPositionChange);
controller!.animation!.addListener(onScroll);
});
controller!.animateTo(widget.itemCount! - 1);
} else if (widget.initPosition != null) {
controller!.animateTo(widget.initPosition!);
}
super.didUpdateWidget(oldWidget);
}
#override
void dispose() {
controller!.animation!.removeListener(onScroll);
controller!.removeListener(onPositionChange);
controller!.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (widget.itemCount! < 1) return widget.stub ?? Container();
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: controller,
children: List.generate(
widget.itemCount!,
(index) => widget.pageBuilder!(context, index),
),
),
),
Container(
alignment: Alignment.centerLeft,
child: TabBar(
isScrollable: true,
controller: controller,
labelColor: Theme.of(context).primaryColor,
unselectedLabelColor: Theme.of(context).hintColor,
indicator: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).primaryColor,
width: 2,
),
),
),
tabs: List.generate(
widget.itemCount!,
(index) => widget.tabBuilder!(context, index),
),
),
),
],
);
}
onPositionChange() {
if (!controller!.indexIsChanging) {
_currentPosition = controller!.index;
if (widget.onPositionChange is ValueChanged<int>) {
widget.onPositionChange!(_currentPosition!);
}
}
}
onScroll() {
if (widget.onScroll is ValueChanged<double>) {
widget.onScroll!(controller!.animation!.value);
}
}
}
class Page extends StatefulWidget {
const Page({key, this.url}) : super(key: key);
final String? url;
#override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> with AutomaticKeepAliveClientMixin<Page> {
final Completer<WebViewController> _controller =
Completer<WebViewController>();
late final url = widget.url!;
#override
bool get wantKeepAlive => true;
#override
void initState() {
super.initState();
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
#override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
body: WebView(
key: widget.key,
initialUrl: url,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
debugPrint("WebView is created");
_controller.complete(webViewController);
},
onProgress: (int progress) {
debugPrint("WebView is loading (progress : $progress%)");
},
onPageStarted: (String url) {
debugPrint('Page started loading: $url');
},
onPageFinished: (String url) {
debugPrint('Page finished loading: $url');
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Page finished loading: $url'),
duration: Duration(seconds: 5),
));
},
));
}
}
I wanted to Download a Image with its progress and message. I wanted to show it in a dialog. When ever I click Download Button the Image gets downloaded and the Container pops up, but it does not Show any value.
The below code uses Image_downloader package. Raised button downloads the image and display the Blank Container without any value;
import 'dart:async';
import 'dart:io';
import 'Download.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_downloader/image_downloader.dart';
void main() => runApp(HomePage());
class HomePage extends StatefulWidget {
#override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
String message = "";
String path = "";
int _progress = 0;
#override
void initState() {
super.initState();
ImageDownloader.callback(onProgressUpdate: (String imageId, int progress) {
setState(() {
_progress = progress;
});
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RaisedButton(
onPressed: () {
_downloadImage(
"https://images.unsplash.com/photo-1503023345310-bd7c1de61c7d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80",
);
showDialog(
context: context,
builder: (_) => FunkyOverlay(progress: _progress, message: message,),
);
},
child: Text("default destination"),
),
],
),
),
),
);
}
Future<void> _downloadImage(String url,
{AndroidDestinationType destination, bool whenError = false}) async {
String fileName;
String path;
try {
String imageId;
if (whenError) {
imageId = await ImageDownloader.downloadImage(url).catchError((error) {
if (error is PlatformException) {
var path = "";
if (error.code == "404") {
print("Not Found Error.");
} else if (error.code == "unsupported_file") {
print("UnSupported FIle Error.");
path = error.details["unsupported_file_path"];
}
setState(() {
message = error.toString();
path = path;
});
}
print(error);
}).timeout(Duration(seconds: 10), onTimeout: () {
print("timeout");
});
} else {
if (destination == null) {
imageId = await ImageDownloader.downloadImage(url);
} else {
imageId = await ImageDownloader.downloadImage(
url,
destination: destination,
);
}
}
if (imageId == null) {
return;
}
fileName = await ImageDownloader.findName(imageId);
path = await ImageDownloader.findPath(imageId);
} on PlatformException catch (error) {
setState(() {
message = error.message;
});
return;
}
if (!mounted) return;
setState(() {
message = 'Image Downloaded';
});
}
}
This is the Pop up Container Part
import 'package:flutter/material.dart';
class FunkyOverlay extends StatefulWidget {
String message;
int progress;
FunkyOverlay({#required this.message, #required this.progress});
#override
State<StatefulWidget> createState() => FunkyOverlayState(message, progress);
}
class FunkyOverlayState extends State<FunkyOverlay>
with SingleTickerProviderStateMixin {
String message;
int progress;
FunkyOverlayState(this.message, this.progress);
AnimationController controller;
Animation<double> scaleAnimation;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 450));
scaleAnimation =
CurvedAnimation(parent: controller, curve: Curves.elasticInOut);
controller.addListener(() {
setState(() {});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
return Center(
child: Material(
color: Colors.transparent,
child: ScaleTransition(
scale: scaleAnimation,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
),
child: Padding(
padding: const EdgeInsets.all(50.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Downloaded: $progress'),
Text(message)
],
),
),
),
),
),
);
}
}
You can copy paste run full code below
You can use StreamBuilder to receive progress from onProgressUpdate
class HomePageState extends State<HomePage> {
...
#override
void initState() {
super.initState();
_events = new StreamController<int>.broadcast();;
_events.add(0);
ImageDownloader.callback(onProgressUpdate: (String imageId, int progress) {
setState(() {
print("progress $progress");
_progress = progress;
_events.add(progress);
});
return StreamBuilder<int>(
stream: _events.stream,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return Center(
...
children: <Widget>[
Text('Downloaded: ${snapshot.data.toString()}'),
Text(message)
working demo
full code
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:image_downloader/image_downloader.dart';
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
HomePageState createState() => HomePageState();
}
StreamController<int> _events;
class HomePageState extends State<HomePage> {
String message = "";
String path = "";
int _progress = 0;
#override
void initState() {
super.initState();
_events = new StreamController<int>.broadcast();
;
_events.add(0);
ImageDownloader.callback(onProgressUpdate: (String imageId, int progress) {
setState(() {
print("progress $progress");
_progress = progress;
_events.add(progress);
if (progress == 100) {
Navigator.pop(context);
}
});
});
}
#override
void dispose() {
// TODO: implement dispose
_events.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RaisedButton(
onPressed: () {
_events.add(0);
_downloadImage(
"https://images.unsplash.com/photo-1503023345310-bd7c1de61c7d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80",
);
showDialog(
context: context,
builder: (_) => FunkyOverlay(
progress: _progress,
message: message,
),
);
},
child: Text("default destination"),
),
],
),
),
);
}
Future<void> _downloadImage(String url,
{AndroidDestinationType destination, bool whenError = false}) async {
String fileName;
String path;
try {
String imageId;
if (whenError) {
imageId = await ImageDownloader.downloadImage(url).catchError((error) {
if (error is PlatformException) {
var path = "";
if (error.code == "404") {
print("Not Found Error.");
} else if (error.code == "unsupported_file") {
print("UnSupported FIle Error.");
path = error.details["unsupported_file_path"];
}
setState(() {
message = error.toString();
path = path;
});
}
print(error);
}).timeout(Duration(seconds: 10), onTimeout: () {
print("timeout");
});
} else {
if (destination == null) {
imageId = await ImageDownloader.downloadImage(url);
} else {
imageId = await ImageDownloader.downloadImage(
url,
destination: destination,
);
}
}
if (imageId == null) {
print("imageId is null");
return;
}
fileName = await ImageDownloader.findName(imageId);
path = await ImageDownloader.findPath(imageId);
} on PlatformException catch (error) {
setState(() {
message = error.message;
});
return;
}
if (!mounted) return;
setState(() {
message = 'Image Downloaded';
});
}
}
class FunkyOverlay extends StatefulWidget {
String message;
int progress;
FunkyOverlay({#required this.message, #required this.progress});
#override
State<StatefulWidget> createState() => FunkyOverlayState(message, progress);
}
class FunkyOverlayState extends State<FunkyOverlay>
with SingleTickerProviderStateMixin {
String message;
int progress;
FunkyOverlayState(this.message, this.progress);
AnimationController controller;
Animation<double> scaleAnimation;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 450));
scaleAnimation =
CurvedAnimation(parent: controller, curve: Curves.elasticInOut);
controller.addListener(() {
setState(() {});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
print("StreamBuilder build");
return StreamBuilder<int>(
stream: _events.stream,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
print("snapshot.data ${snapshot.data.toString()}");
return Center(
child: Material(
color: Colors.transparent,
child: ScaleTransition(
scale: scaleAnimation,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
),
child: Padding(
padding: const EdgeInsets.all(50.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Downloaded: ${snapshot.data.toString()}'),
Text(message)
],
),
),
),
),
),
);
});
}
}