/
book_cover.dart
171 lines (155 loc) · 5.91 KB
/
book_cover.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import 'package:animate_do/animate_do.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_folio/_utils/input_utils.dart';
import 'package:flutter_folio/_widgets/animated/animated_scale.dart';
import 'package:flutter_folio/_widgets/gradient_container.dart';
import 'package:flutter_folio/_widgets/rounded_card.dart';
import 'package:flutter_folio/core_packages.dart';
import 'package:flutter_folio/data/book_data.dart';
import 'package:flutter_folio/views/home_page/book_cover/book_cover_large.dart';
import 'package:flutter_folio/views/home_page/book_cover/book_cover_small.dart';
import 'package:provider/provider.dart';
/// Represents the Cover for one ScrapBook.
/// Supports 2 modes, and holds the shared elements between the modes, like the imageBg, gradients etc
/// `bool largeMode` toggles between 2 different child widgets (cover_small, cover_large)
/// This is the main card used in [CoversFlowList]], for both the list items, and the primary content area
class BookCoverWidget extends StatefulWidget {
const BookCoverWidget(
this.data, {
Key? key,
this.isSelected = false,
this.onPressed,
this.largeMode = false,
this.topTitle = false,
}) : super(key: key);
final ScrapBookData data;
final bool isSelected;
final bool largeMode;
final bool topTitle;
final void Function(Offset globalPos)? onPressed;
@override
_BookCoverWidgetState createState() => _BookCoverWidgetState();
}
class _BookCoverWidgetState extends State<BookCoverWidget> {
FocusNode _focusNode = FocusNode();
bool _isMouseOver = false;
set isOver(bool value) {
if (value == _isMouseOver) return;
setState(() => _isMouseOver = value);
}
@override
void initState() {
super.initState();
_focusNode.addListener(() => setState(() {}));
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
AppTheme theme = Provider.of(context);
bool isClickable = _isMouseOver && widget.onPressed != null;
double overlayOpacity = 0;
// 'clickable' cards fade out their overlay when mouse is over
if (widget.onPressed != null) {
overlayOpacity = _isMouseOver ? 0 : .3;
}
return AnimatedSwitcher(
duration: Duration(milliseconds: widget.isSelected ? 0 : 200),
child: widget.isSelected
? Container(color: Colors.white.withOpacity(.2))
: MouseRegion(
opaque: false,
cursor: isClickable ? SystemMouseCursors.click : MouseCursor.defer,
onEnter: (_) => isOver = true,
onHover: (_) => isOver = true,
onExit: (_) => isOver = false,
child: Stack(fit: StackFit.expand, children: [
/// /////////////////////////////
/// Background Image
// Animated scale for when we mouse-over
AnimatedScale(
duration: Times.slow,
begin: 1,
end: isClickable ? 1.1 : 1,
child: BookCoverImage(widget.data),
),
/// Black overlay, fades out on mouseOver
AnimatedContainer(duration: Times.slow, color: Colors.black.withOpacity(overlayOpacity)),
/// When in large mode, show some gradients, should sit under the Text elements
if (widget.largeMode) ...[
FadeInLeft(
duration: Times.slower,
child: _SideGradient(Colors.black),
),
FadeInUp(child: _BottomGradientLg(Colors.black))
] else ...[
FadeInUp(child: _BottomGradientSm(Colors.black)),
],
/// Sit under the text content, and unfocus when tapped.
GestureDetector(behavior: HitTestBehavior.translucent, onTap: InputUtils.unFocus),
/// BookContent, shows either the Large cover or Small
Align(
alignment: widget.topTitle ? Alignment.topLeft : Alignment.bottomLeft,
// Tween the padding depending on which mode we're in
child: AnimatedContainer(
duration: Times.slow,
padding: EdgeInsets.all(widget.largeMode ? Insets.offset : Insets.sm),
child: (widget.largeMode)
? LargeBookCover(widget.data)
: SmallBookCover(widget.data, topTitle: widget.topTitle),
),
),
/// Mouse-over effect
if (isClickable) ...[
Positioned.fill(child: FadeIn(child: RoundedBorder(color: theme.accent1, ignorePointer: false))),
],
]),
),
);
}
}
class _SideGradient extends StatelessWidget {
const _SideGradient(this.color, {Key? key}) : super(key: key);
final Color color;
@override
Widget build(BuildContext context) => HzGradient(
[color.withOpacity(.8), color.withOpacity(0)],
[.15, .8],
);
}
class _BottomGradientLg extends StatelessWidget {
const _BottomGradientLg(this.color, {Key? key}) : super(key: key);
final Color color;
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.bottomCenter,
child: GradientContainer(
[color.withOpacity(1), color.withOpacity(1), color.withOpacity(0)],
[.2, .55, 1],
height: 300,
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
),
);
}
}
class _BottomGradientSm extends StatelessWidget {
const _BottomGradientSm(this.color, {Key? key}) : super(key: key);
final Color color;
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.bottomCenter,
child: VtGradient(
[color.withOpacity(0), color.withOpacity(.8)],
[0, 1],
height: 60,
),
);
}
}