tencent_cloud_chat_uikit_fl.../lib/ui/widgets/image_screen.dart

299 lines
12 KiB
Dart

import 'dart:math';
import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/gestured_image.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/image_hero.dart';
typedef DoubleClickAnimationListener = void Function();
class ImageScreen extends StatefulWidget {
const ImageScreen(
{required this.imageProvider,
required this.heroTag,
this.downloadFn,
this.messageID,
Key? key})
: super(key: key);
final ImageProvider imageProvider;
final String heroTag;
final String? messageID;
final Future<void> Function()? downloadFn;
@override
State<StatefulWidget> createState() {
return _ImageScreenState();
}
}
class _ImageScreenState extends TIMUIKitState<ImageScreen>
with TickerProviderStateMixin {
Animation<double>? _doubleClickAnimation;
late DoubleClickAnimationListener _doubleClickAnimationListener;
late AnimationController _doubleClickAnimationController;
List<double> doubleTapScales = <double>[1.0, 2.0];
double currentScale = 1.0;
double fittedScale = 1.0;
double initialScale = 1.0;
bool isLoading = false;
GlobalKey<ExtendedImageSlidePageState> slidePageKey =
GlobalKey<ExtendedImageSlidePageState>();
GlobalKey<ExtendedImageGestureState> extendedImageGestureKey =
GlobalKey<ExtendedImageGestureState>();
void close() {
slidePageKey.currentState!.popPage();
Navigator.pop(context);
}
@override
void initState() {
super.initState();
// 允许横屏
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
_doubleClickAnimationController = AnimationController(
duration: const Duration(milliseconds: 150), vsync: this);
}
@override
void dispose() {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
_doubleClickAnimationController.dispose();
super.dispose();
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
return OrientationBuilder(builder: ((context, orientation) {
if (extendedImageGestureKey.currentState != null) {
extendedImageGestureKey.currentState!.reset();
}
return Material(
color: Colors.transparent,
child: Container(
color: Colors.transparent,
constraints: BoxConstraints.expand(
height: MediaQuery.of(context).size.height,
),
child: Stack(
alignment: Alignment.center,
children: [
Positioned(
top: 0,
left: 0,
bottom: 0,
right: 0,
child: ExtendedImageSlidePage(
key: slidePageKey,
slideAxis: SlideAxis.both,
slidePageBackgroundHandler: (Offset offset, Size size) {
if (orientation == Orientation.landscape) {
return Colors.black;
}
double opacity = 0.0;
opacity = offset.distance /
(Offset(size.width, size.height).distance / 2.0);
return Colors.black
.withOpacity(min(1.0, max(1.0 - opacity, 0.0)));
},
slideType: SlideType.onlyImage,
slideEndHandler: (
Offset offset, {
ExtendedImageSlidePageState? state,
ScaleEndDetails? details,
}) {
final vy = details?.velocity.pixelsPerSecond.dy ?? 0;
final oy = offset.dy;
if (vy > 300 || oy > 100) {
return true;
}
return null;
},
child: GestureDetector(
onTap: close,
child: HeroWidget(
tag: widget.heroTag,
slidePagekey: slidePageKey,
child: ExtendedImage(
image: widget.imageProvider,
extendedImageGestureKey:
extendedImageGestureKey,
enableSlideOutPage: true,
// fit: BoxFit.scaleDown,
initGestureConfigHandler: (state) {
return GestureConfig(
minScale: 0.8,
animationMinScale: 0.6,
maxScale: 2 * fittedScale,
animationMaxScale: 2.5 * fittedScale,
speed: 1.0,
inertialSpeed: 100.0,
initialScale: initialScale,
initialAlignment:
InitialAlignment.topCenter,
hitTestBehavior: HitTestBehavior.opaque,
);
},
loadStateChanged: (ExtendedImageState state) {
switch (state.extendedImageLoadState) {
case LoadState.loading:
return Container(
color: Colors.black,
child: const Center(
child: CircularProgressIndicator(
color: Colors.white))
);
case LoadState.completed:
final screenHeight =
MediaQuery.of(context).size.height;
final screenWidth =
MediaQuery.of(context).size.width;
final imgHeight = state.extendedImageInfo
?.image.height ??
1;
final imgWidth = state
.extendedImageInfo?.image.width ??
0;
final imgRatio = imgWidth / imgHeight;
final screenRatio =
screenWidth / screenHeight;
final fitWidthScale =
screenRatio / imgRatio;
if (screenRatio > imgRatio) {
// Long Image
// initialScale = fitWidthScale;
fittedScale = fitWidthScale;
doubleTapScales[1] = fitWidthScale;
} else {
fittedScale =
1 / fitWidthScale; // fittedHeight
doubleTapScales[1] = 1 / fitWidthScale;
}
return GesturedImage(state,
key: extendedImageGestureKey);
case LoadState.failed:
break;
}
return null;
},
onDoubleTap: (ExtendedImageGestureState state) {
///you can use define pointerDownPosition as you can,
///default value is double tap pointer down position.
final Offset? pointerDownPosition =
state.pointerDownPosition;
final double? begin =
state.gestureDetails!.totalScale;
double end;
//remove old
_doubleClickAnimation?.removeListener(
_doubleClickAnimationListener);
//stop pre
_doubleClickAnimationController.stop();
//reset to use
_doubleClickAnimationController.reset();
if (begin == doubleTapScales[0]) {
end = doubleTapScales[1];
} else {
end = doubleTapScales[0];
}
_doubleClickAnimationListener = () {
//outputLogger.i(_animation.value);
state.handleDoubleTap(
scale: _doubleClickAnimation!.value,
doubleTapPosition: pointerDownPosition);
};
_doubleClickAnimation =
_doubleClickAnimationController.drive(
Tween<double>(
begin: begin, end: end));
_doubleClickAnimation!.addListener(
_doubleClickAnimationListener);
_doubleClickAnimationController.forward();
},
mode: ExtendedImageMode.gesture,
)),
),
),
),
Positioned(
left: 10,
bottom: 50,
child: SizedBox(
width: 48,
height: 48,
child: IconButton(
icon: Image.asset(
'images/close.png',
package: 'tencent_cloud_chat_uikit',
),
iconSize: 30,
onPressed: close,
),
)),
if (widget.downloadFn != null)
Positioned(
right: 10,
bottom: 50,
child: SizedBox(
width: 48,
height: 48,
child: IconButton(
icon: Image.asset(
'images/download.png',
package: 'tencent_cloud_chat_uikit',
),
iconSize: 30,
onPressed: () async {
setState(() {
isLoading = true;
});
await widget.downloadFn!();
Future.delayed(const Duration(milliseconds: 200),(){
setState(() {
isLoading = false;
});
});
},
),
),
),
if (isLoading)
Container(
child: LoadingAnimationWidget.staggeredDotsWave(
size: 35,
color: Colors.white,
),
padding: const EdgeInsets.all(30),
decoration: const BoxDecoration(
color: Color(0xB22b2b2b),
borderRadius: BorderRadius.all(Radius.circular(20)),
),
),
])),
);
}));
}
}