mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-01-11 22:40:36 +00:00
wip: video preview
This commit is contained in:
parent
980f1e4c28
commit
4ef1345bd4
5 changed files with 293 additions and 30 deletions
|
|
@ -1054,6 +1054,7 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
||||||
tempDuration: (value) {
|
tempDuration: (value) {
|
||||||
_tempPosition.value = value;
|
_tempPosition.value = value;
|
||||||
},
|
},
|
||||||
|
video: _video.value,
|
||||||
)
|
)
|
||||||
: MobileControllerWidget(
|
: MobileControllerWidget(
|
||||||
videoController: _controller,
|
videoController: _controller,
|
||||||
|
|
@ -1061,6 +1062,7 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
||||||
videoStatekey: _key,
|
videoStatekey: _key,
|
||||||
bottomButtonBarWidget: _mobileBottomButtonBar(context),
|
bottomButtonBarWidget: _mobileBottomButtonBar(context),
|
||||||
streamController: _streamController,
|
streamController: _streamController,
|
||||||
|
video: _video.value,
|
||||||
),
|
),
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
width: context.width(1),
|
width: context.width(1),
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,19 @@ class CustomSeekBar extends StatefulWidget {
|
||||||
final Duration? delta;
|
final Duration? delta;
|
||||||
final Function(Duration)? onSeekStart;
|
final Function(Duration)? onSeekStart;
|
||||||
final Function(Duration)? onSeekEnd;
|
final Function(Duration)? onSeekEnd;
|
||||||
|
final Function(bool) isDragging;
|
||||||
|
final Function(double?) dragPosition;
|
||||||
|
final Function(Duration) onDragDuration;
|
||||||
|
|
||||||
const CustomSeekBar(
|
const CustomSeekBar(
|
||||||
{super.key,
|
{super.key,
|
||||||
this.onSeekStart,
|
this.onSeekStart,
|
||||||
this.onSeekEnd,
|
this.onSeekEnd,
|
||||||
required this.player,
|
required this.player,
|
||||||
this.delta});
|
this.delta,
|
||||||
|
required this.isDragging,
|
||||||
|
required this.dragPosition,
|
||||||
|
required this.onDragDuration});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
CustomSeekBarState createState() => CustomSeekBarState();
|
CustomSeekBarState createState() => CustomSeekBarState();
|
||||||
|
|
@ -58,6 +64,27 @@ class CustomSeekBarState extends State<CustomSeekBar> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final isDesktop = Platform.isMacOS || Platform.isWindows || Platform.isLinux;
|
final isDesktop = Platform.isMacOS || Platform.isWindows || Platform.isLinux;
|
||||||
|
final GlobalKey _sliderKey = GlobalKey();
|
||||||
|
|
||||||
|
void _onMove(PointerEvent details) {
|
||||||
|
final RenderBox box =
|
||||||
|
_sliderKey.currentContext!.findRenderObject() as RenderBox;
|
||||||
|
final Offset localOffset = box.globalToLocal(details.position);
|
||||||
|
|
||||||
|
if (localOffset.dx >= 0 && localOffset.dx <= box.size.width) {
|
||||||
|
final pourcentage = (localOffset.dx.ceil() / box.size.width.ceil()) * 100;
|
||||||
|
widget.onDragDuration.call(
|
||||||
|
Duration(seconds: ((pourcentage / 100) * duration.inSeconds).ceil()));
|
||||||
|
widget.isDragging.call(true);
|
||||||
|
widget.dragPosition.call(localOffset.dx);
|
||||||
|
Future.delayed(const Duration(milliseconds: 50))
|
||||||
|
.then((e) => widget.isDragging(false));
|
||||||
|
} else {
|
||||||
|
widget.isDragging.call(true);
|
||||||
|
widget.dragPosition.call(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
|
|
@ -78,33 +105,56 @@ class CustomSeekBarState extends State<CustomSeekBar> {
|
||||||
),
|
),
|
||||||
))),
|
))),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SliderTheme(
|
child: Listener(
|
||||||
data: SliderTheme.of(context).copyWith(
|
onPointerMove: (details) {
|
||||||
trackHeight: isDesktop ? null : 3,
|
_onMove(details);
|
||||||
overlayShape: const RoundSliderOverlayShape(overlayRadius: 5.0),
|
},
|
||||||
),
|
child: MouseRegion(
|
||||||
child: Slider(
|
onExit: (_) {
|
||||||
max: max(duration.inMilliseconds.toDouble(), 0),
|
widget.isDragging.call(false);
|
||||||
value: max(
|
widget.dragPosition.call(null);
|
||||||
(widget.delta ?? tempPosition ?? position)
|
|
||||||
.inMilliseconds
|
|
||||||
.toDouble(),
|
|
||||||
0),
|
|
||||||
secondaryTrackValue: max(buffer.inMilliseconds.toDouble(), 0),
|
|
||||||
onChanged: (value) {
|
|
||||||
widget.onSeekStart?.call(Duration(
|
|
||||||
milliseconds: value.toInt() - position.inMilliseconds));
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
tempPosition = Duration(milliseconds: value.toInt());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onChangeEnd: (value) async {
|
onHover: (details) {
|
||||||
widget.onSeekEnd?.call(Duration(
|
_onMove(details);
|
||||||
milliseconds: value.toInt() - position.inMilliseconds));
|
|
||||||
widget.player.seek(Duration(milliseconds: value.toInt()));
|
|
||||||
},
|
},
|
||||||
|
child: SliderTheme(
|
||||||
|
data: SliderTheme.of(context).copyWith(
|
||||||
|
trackHeight: isDesktop ? null : 3,
|
||||||
|
overlayShape:
|
||||||
|
const RoundSliderOverlayShape(overlayRadius: 5.0),
|
||||||
|
),
|
||||||
|
child: Slider(
|
||||||
|
key: _sliderKey,
|
||||||
|
max: max(duration.inMilliseconds.toDouble(), 0),
|
||||||
|
value: max(
|
||||||
|
(widget.delta ?? tempPosition ?? position)
|
||||||
|
.inMilliseconds
|
||||||
|
.toDouble(),
|
||||||
|
0),
|
||||||
|
secondaryTrackValue:
|
||||||
|
max(buffer.inMilliseconds.toDouble(), 0),
|
||||||
|
onChanged: (value) {
|
||||||
|
widget.onSeekStart?.call(Duration(
|
||||||
|
milliseconds:
|
||||||
|
value.toInt() - position.inMilliseconds));
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
tempPosition = Duration(milliseconds: value.toInt());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChangeStart: (value) {
|
||||||
|
widget.isDragging.call(true);
|
||||||
|
},
|
||||||
|
onChangeEnd: (value) async {
|
||||||
|
widget.isDragging.call(false);
|
||||||
|
widget.onSeekEnd?.call(Duration(
|
||||||
|
milliseconds:
|
||||||
|
value.toInt() - position.inMilliseconds));
|
||||||
|
widget.player.seek(Duration(milliseconds: value.toInt()));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import 'package:mangayomi/modules/anime/anime_player_view.dart';
|
||||||
import 'package:mangayomi/modules/anime/providers/anime_player_controller_provider.dart';
|
import 'package:mangayomi/modules/anime/providers/anime_player_controller_provider.dart';
|
||||||
import 'package:mangayomi/modules/anime/widgets/custom_seekbar.dart';
|
import 'package:mangayomi/modules/anime/widgets/custom_seekbar.dart';
|
||||||
import 'package:mangayomi/modules/anime/widgets/subtitle_view.dart';
|
import 'package:mangayomi/modules/anime/widgets/subtitle_view.dart';
|
||||||
|
import 'package:mangayomi/modules/anime/widgets/video_preview.dart';
|
||||||
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
|
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
|
||||||
import 'package:media_kit_video/media_kit_video.dart';
|
import 'package:media_kit_video/media_kit_video.dart';
|
||||||
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
|
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
|
||||||
|
|
@ -21,6 +22,7 @@ class DesktopControllerWidget extends StatefulWidget {
|
||||||
final GlobalKey<VideoState> videoStatekey;
|
final GlobalKey<VideoState> videoStatekey;
|
||||||
final Widget bottomButtonBarWidget;
|
final Widget bottomButtonBarWidget;
|
||||||
final Widget seekToWidget;
|
final Widget seekToWidget;
|
||||||
|
final VideoPrefs? video;
|
||||||
const DesktopControllerWidget(
|
const DesktopControllerWidget(
|
||||||
{super.key,
|
{super.key,
|
||||||
required this.videoController,
|
required this.videoController,
|
||||||
|
|
@ -29,7 +31,8 @@ class DesktopControllerWidget extends StatefulWidget {
|
||||||
required this.streamController,
|
required this.streamController,
|
||||||
required this.videoStatekey,
|
required this.videoStatekey,
|
||||||
required this.seekToWidget,
|
required this.seekToWidget,
|
||||||
required this.tempDuration});
|
required this.tempDuration,
|
||||||
|
required this.video});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DesktopControllerWidget> createState() =>
|
State<DesktopControllerWidget> createState() =>
|
||||||
|
|
@ -42,6 +45,9 @@ class _DesktopControllerWidgetState extends State<DesktopControllerWidget> {
|
||||||
Duration controlsTransitionDuration = const Duration(milliseconds: 300);
|
Duration controlsTransitionDuration = const Duration(milliseconds: 300);
|
||||||
Color backdropColor = const Color(0x66000000);
|
Color backdropColor = const Color(0x66000000);
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
|
final ValueNotifier<bool> _isDragging = ValueNotifier(false);
|
||||||
|
final ValueNotifier<double?> _dragPosition = ValueNotifier(null);
|
||||||
|
final ValueNotifier<Duration?> _onDragDuration = ValueNotifier(null);
|
||||||
|
|
||||||
int swipeDuration = 0; // Duration to seek in video
|
int swipeDuration = 0; // Duration to seek in video
|
||||||
bool showSwipeDuration = false; // Whether to show the seek duration overlay
|
bool showSwipeDuration = false; // Whether to show the seek duration overlay
|
||||||
|
|
@ -374,6 +380,21 @@ class _DesktopControllerWidgetState extends State<DesktopControllerWidget> {
|
||||||
widget.tempDuration(null);
|
widget.tempDuration(null);
|
||||||
},
|
},
|
||||||
player: widget.videoController.player,
|
player: widget.videoController.player,
|
||||||
|
isDragging: (value) {
|
||||||
|
setState(() {
|
||||||
|
_isDragging.value = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
dragPosition: (value) {
|
||||||
|
setState(() {
|
||||||
|
_dragPosition.value = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onDragDuration: (value) {
|
||||||
|
setState(() {
|
||||||
|
_onDragDuration.value = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -439,6 +460,11 @@ class _DesktopControllerWidgetState extends State<DesktopControllerWidget> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
VideoPreview(
|
||||||
|
isDragging: _isDragging,
|
||||||
|
dragPosition: _dragPosition,
|
||||||
|
onDragDuration: _onDragDuration,
|
||||||
|
video: widget.video!),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import 'package:mangayomi/modules/anime/providers/anime_player_controller_provid
|
||||||
import 'package:mangayomi/modules/anime/widgets/custom_seekbar.dart';
|
import 'package:mangayomi/modules/anime/widgets/custom_seekbar.dart';
|
||||||
import 'package:mangayomi/modules/anime/widgets/indicator_builder.dart';
|
import 'package:mangayomi/modules/anime/widgets/indicator_builder.dart';
|
||||||
import 'package:mangayomi/modules/anime/widgets/subtitle_view.dart';
|
import 'package:mangayomi/modules/anime/widgets/subtitle_view.dart';
|
||||||
|
import 'package:mangayomi/modules/anime/widgets/video_preview.dart';
|
||||||
import 'package:mangayomi/modules/manga/reader/providers/push_router.dart';
|
import 'package:mangayomi/modules/manga/reader/providers/push_router.dart';
|
||||||
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
|
import 'package:mangayomi/modules/more/settings/player/providers/player_state_provider.dart';
|
||||||
import 'package:volume_controller/volume_controller.dart';
|
import 'package:volume_controller/volume_controller.dart';
|
||||||
|
|
@ -21,13 +22,15 @@ class MobileControllerWidget extends ConsumerStatefulWidget {
|
||||||
final Widget topButtonBarWidget;
|
final Widget topButtonBarWidget;
|
||||||
final GlobalKey<VideoState> videoStatekey;
|
final GlobalKey<VideoState> videoStatekey;
|
||||||
final Widget bottomButtonBarWidget;
|
final Widget bottomButtonBarWidget;
|
||||||
|
final VideoPrefs? video;
|
||||||
const MobileControllerWidget(
|
const MobileControllerWidget(
|
||||||
{super.key,
|
{super.key,
|
||||||
required this.videoController,
|
required this.videoController,
|
||||||
required this.topButtonBarWidget,
|
required this.topButtonBarWidget,
|
||||||
required this.bottomButtonBarWidget,
|
required this.bottomButtonBarWidget,
|
||||||
required this.streamController,
|
required this.streamController,
|
||||||
required this.videoStatekey});
|
required this.videoStatekey,
|
||||||
|
required this.video});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<MobileControllerWidget> createState() =>
|
ConsumerState<MobileControllerWidget> createState() =>
|
||||||
|
|
@ -49,6 +52,10 @@ class _MobileControllerWidgetState
|
||||||
|
|
||||||
final ValueNotifier<double> _volumeValue = ValueNotifier(0.0);
|
final ValueNotifier<double> _volumeValue = ValueNotifier(0.0);
|
||||||
final ValueNotifier<bool> _volumeIndicator = ValueNotifier(false);
|
final ValueNotifier<bool> _volumeIndicator = ValueNotifier(false);
|
||||||
|
|
||||||
|
final ValueNotifier<bool> _isDragging = ValueNotifier(false);
|
||||||
|
final ValueNotifier<double?> _dragPosition = ValueNotifier(null);
|
||||||
|
final ValueNotifier<Duration?> _onDragDuration = ValueNotifier(null);
|
||||||
Timer? _volumeTimer;
|
Timer? _volumeTimer;
|
||||||
// The default event stream in package:volume_controller is buggy.
|
// The default event stream in package:volume_controller is buggy.
|
||||||
bool _volumeInterceptEventStream = false;
|
bool _volumeInterceptEventStream = false;
|
||||||
|
|
@ -438,6 +445,21 @@ class _MobileControllerWidgetState
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
player: widget.videoController.player,
|
player: widget.videoController.player,
|
||||||
|
isDragging: (value) {
|
||||||
|
setState(() {
|
||||||
|
_isDragging.value = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
dragPosition: (value) {
|
||||||
|
setState(() {
|
||||||
|
_dragPosition.value = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onDragDuration: (value) {
|
||||||
|
setState(() {
|
||||||
|
_onDragDuration.value = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
widget.bottomButtonBarWidget
|
widget.bottomButtonBarWidget
|
||||||
|
|
@ -463,8 +485,12 @@ class _MobileControllerWidgetState
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 10),
|
padding: const EdgeInsets.only(bottom: 10),
|
||||||
child: CustomSeekBar(
|
child: CustomSeekBar(
|
||||||
delta: _seekBarDeltaValueNotifier,
|
delta: _seekBarDeltaValueNotifier,
|
||||||
player: widget.videoController.player),
|
player: widget.videoController.player,
|
||||||
|
isDragging: (value) {},
|
||||||
|
dragPosition: (value) {},
|
||||||
|
onDragDuration: (value) {},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -662,6 +688,11 @@ class _MobileControllerWidgetState
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
VideoPreview(
|
||||||
|
isDragging: _isDragging,
|
||||||
|
dragPosition: _dragPosition,
|
||||||
|
onDragDuration: _onDragDuration,
|
||||||
|
video: widget.video!),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
154
lib/modules/anime/widgets/video_preview.dart
Normal file
154
lib/modules/anime/widgets/video_preview.dart
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:mangayomi/modules/anime/anime_player_view.dart';
|
||||||
|
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:media_kit/media_kit.dart';
|
||||||
|
import 'package:media_kit_video/media_kit_video.dart';
|
||||||
|
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
|
||||||
|
|
||||||
|
class TestTest extends StatefulWidget {
|
||||||
|
final Duration? onDragDuration;
|
||||||
|
final VideoPrefs video;
|
||||||
|
const TestTest(
|
||||||
|
{super.key, required this.onDragDuration, required this.video});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TestTest> createState() => _TestTestState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TestTestState extends State<TestTest> {
|
||||||
|
late final Player _player =
|
||||||
|
Player(configuration: const PlayerConfiguration());
|
||||||
|
late final VideoController _controller = VideoController(_player);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_player.open(
|
||||||
|
Media(widget.video.videoTrack!.id,
|
||||||
|
httpHeaders: widget.video.headers, start: widget.onDragDuration),
|
||||||
|
play: false);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_player.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Positioned.fill(
|
||||||
|
child: ValueListenableBuilder<PlatformVideoController?>(
|
||||||
|
valueListenable: _controller.notifier,
|
||||||
|
builder: (context, notifier, _) => notifier == null
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: ValueListenableBuilder<int?>(
|
||||||
|
valueListenable: notifier.id,
|
||||||
|
builder: (context, id, _) {
|
||||||
|
return ValueListenableBuilder<Rect?>(
|
||||||
|
valueListenable: notifier.rect,
|
||||||
|
builder: (context, rect, _) {
|
||||||
|
if (id != null && rect != null) {
|
||||||
|
return Texture(textureId: id);
|
||||||
|
}
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VideoPreview extends StatelessWidget {
|
||||||
|
final ValueNotifier<bool> isDragging;
|
||||||
|
final ValueNotifier<double?> dragPosition;
|
||||||
|
final ValueNotifier<Duration?> onDragDuration;
|
||||||
|
final VideoPrefs video;
|
||||||
|
const VideoPreview(
|
||||||
|
{super.key,
|
||||||
|
required this.isDragging,
|
||||||
|
required this.dragPosition,
|
||||||
|
required this.onDragDuration,
|
||||||
|
required this.video});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
|
valueListenable: isDragging,
|
||||||
|
builder: (context, isDragging, child) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
|
valueListenable: dragPosition,
|
||||||
|
builder: (context, dragPosition, child) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
|
valueListenable: onDragDuration,
|
||||||
|
builder: (context, onDragDuration, child) {
|
||||||
|
if (dragPosition != null) {
|
||||||
|
return Positioned(
|
||||||
|
bottom: 100,
|
||||||
|
left: dragPosition >= (context.width(1) - 150)
|
||||||
|
? null
|
||||||
|
: dragPosition >= 50
|
||||||
|
? dragPosition - 50
|
||||||
|
: 0,
|
||||||
|
right: dragPosition >= (context.width(1) - 150) ? 0 : null,
|
||||||
|
child: Material(
|
||||||
|
elevation: 8.0,
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
child: Container(
|
||||||
|
width: 200,
|
||||||
|
height: 100,
|
||||||
|
color: Colors.black,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
if (!isDragging)
|
||||||
|
TestTest(
|
||||||
|
onDragDuration: onDragDuration,
|
||||||
|
video: video),
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: Text(
|
||||||
|
onDragDuration!
|
||||||
|
.label(reference: onDragDuration),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 30,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
offset: Offset(-1.5, -1.5),
|
||||||
|
color: Colors.black,
|
||||||
|
blurRadius: 1.2),
|
||||||
|
Shadow(
|
||||||
|
offset: Offset(1.5, -1.5),
|
||||||
|
color: Colors.black,
|
||||||
|
blurRadius: 1.2),
|
||||||
|
Shadow(
|
||||||
|
offset: Offset(1.5, 1.5),
|
||||||
|
color: Colors.black,
|
||||||
|
blurRadius: 1.2),
|
||||||
|
Shadow(
|
||||||
|
offset: Offset(-1.5, 1.5),
|
||||||
|
color: Colors.black,
|
||||||
|
blurRadius: 1.2)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue