Skip to content

Commit 8699777

Browse files
committed
Add carousel
1 parent fed1208 commit 8699777

File tree

5 files changed

+187
-7
lines changed

5 files changed

+187
-7
lines changed

app/lib/pages/home/page.dart

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:phosphor_flutter/phosphor_flutter.dart';
44
import 'package:vulpine/api/settings.dart';
55
import 'package:vulpine/cubits/settings.dart';
66
import 'package:vulpine/main.dart';
7+
import 'package:vulpine/widgets/carousel.dart';
78

89
class HomePage extends StatefulWidget {
910
const HomePage({super.key});
@@ -13,6 +14,14 @@ class HomePage extends StatefulWidget {
1314
}
1415

1516
class _HomePageState extends State<HomePage> {
17+
final CarouselController _carouselController = CarouselController();
18+
19+
@override
20+
void dispose() {
21+
super.dispose();
22+
_carouselController.dispose();
23+
}
24+
1625
@override
1726
Widget build(BuildContext context) {
1827
return Scaffold(
@@ -25,6 +34,45 @@ class _HomePageState extends State<HomePage> {
2534
),
2635
],
2736
),
37+
body: ListView(
38+
children: [
39+
VulpineCarouselView(
40+
children: List.generate(
41+
10,
42+
(index) =>
43+
UncontainedLayoutCard(index: index, label: 'Item $index'),
44+
),
45+
),
46+
],
47+
),
48+
);
49+
}
50+
}
51+
52+
class UncontainedLayoutCard extends StatelessWidget {
53+
const UncontainedLayoutCard({
54+
super.key,
55+
required this.index,
56+
required this.label,
57+
});
58+
59+
final int index;
60+
final String label;
61+
62+
@override
63+
Widget build(BuildContext context) {
64+
return ColoredBox(
65+
color: Colors.primaries[index % Colors.primaries.length].withValues(
66+
alpha: 0.5,
67+
),
68+
child: Center(
69+
child: Text(
70+
label,
71+
style: const TextStyle(color: Colors.white, fontSize: 20),
72+
overflow: TextOverflow.clip,
73+
softWrap: false,
74+
),
75+
),
2876
);
2977
}
3078
}

app/lib/pages/settings/data.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ class DataSettingsPage extends StatelessWidget {
1919
title: Text(AppLocalizations.of(context).data),
2020
),
2121
body: BlocBuilder<SettingsCubit, VulpineSettings>(
22-
builder: (context, state) => ListView(children: [
23-
],
24-
),
22+
builder: (context, state) => ListView(children: []),
2523
),
2624
);
2725
}

app/lib/widgets/carousel.dart

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import 'dart:ui';
2+
3+
import 'package:flutter/gestures.dart';
4+
import 'package:flutter/material.dart';
5+
import 'package:phosphor_flutter/phosphor_flutter.dart';
6+
7+
final class CarouselScrollBehavior extends ScrollBehavior {
8+
final double itemExtent;
9+
const CarouselScrollBehavior({required this.itemExtent});
10+
// Override behavior methods and getters like dragDevices
11+
@override
12+
Set<PointerDeviceKind> get dragDevices => {
13+
PointerDeviceKind.touch,
14+
PointerDeviceKind.mouse,
15+
// etc.
16+
};
17+
}
18+
19+
class VulpineCarouselView extends StatefulWidget {
20+
final List<Widget> children;
21+
final double itemExtent;
22+
final double shrinkExtent;
23+
24+
const VulpineCarouselView({
25+
super.key,
26+
required this.children,
27+
this.itemExtent = 330,
28+
this.shrinkExtent = 200,
29+
});
30+
31+
@override
32+
State<VulpineCarouselView> createState() => _VulpineCarouselViewState();
33+
}
34+
35+
class _VulpineCarouselViewState extends State<VulpineCarouselView> {
36+
final CarouselController _carouselController = CarouselController();
37+
double _nextOffset = 0;
38+
double _maxOffset = 0;
39+
40+
@override
41+
void initState() {
42+
super.initState();
43+
_updateMaxPosition();
44+
}
45+
46+
@override
47+
void dispose() {
48+
super.dispose();
49+
_carouselController.dispose();
50+
}
51+
52+
void _updateMaxPosition() {
53+
_maxOffset =
54+
widget.itemExtent +
55+
(widget.shrinkExtent * (widget.children.length - 1));
56+
}
57+
58+
void _animateToPrevious() =>
59+
_animateTo(_carouselController.offset - widget.itemExtent);
60+
void _animateToNext() =>
61+
_animateTo(_carouselController.offset + widget.itemExtent);
62+
63+
void animateToNext(int index) {
64+
double to = 0;
65+
if (index > 1) {
66+
to = widget.itemExtent + (widget.shrinkExtent * (index - 1));
67+
} else if (index == 1) {
68+
to = widget.itemExtent;
69+
}
70+
_animateTo(to);
71+
}
72+
73+
void _animateTo(double to) {
74+
_carouselController.animateTo(
75+
to,
76+
duration: const Duration(milliseconds: 300),
77+
curve: Curves.easeInOut,
78+
);
79+
setState(() {
80+
_nextOffset = to;
81+
});
82+
}
83+
84+
@override
85+
Widget build(BuildContext context) {
86+
return ConstrainedBox(
87+
constraints: const BoxConstraints(maxHeight: 200),
88+
child: Stack(
89+
children: [
90+
ScrollConfiguration(
91+
behavior: CarouselScrollBehavior(itemExtent: widget.itemExtent),
92+
child: CarouselView(
93+
itemSnapping: false,
94+
itemExtent: widget.itemExtent,
95+
shrinkExtent: widget.shrinkExtent,
96+
controller: _carouselController,
97+
children: widget.children,
98+
),
99+
),
100+
Align(
101+
alignment: Alignment.centerLeft,
102+
child: AnimatedScale(
103+
alignment: Alignment.centerLeft,
104+
scale: _nextOffset > 0 ? 1 : 0,
105+
duration: const Duration(milliseconds: 300),
106+
child: Padding(
107+
padding: const EdgeInsets.all(8.0),
108+
child: IconButton(
109+
icon: const Icon(PhosphorIconsLight.arrowLeft),
110+
onPressed: _animateToPrevious,
111+
),
112+
),
113+
),
114+
),
115+
Align(
116+
alignment: Alignment.centerRight,
117+
child: AnimatedScale(
118+
alignment: Alignment.centerRight,
119+
scale: _nextOffset < _maxOffset ? 1 : 0,
120+
duration: const Duration(milliseconds: 300),
121+
child: Padding(
122+
padding: const EdgeInsets.all(8.0),
123+
child: IconButton(
124+
icon: const Icon(PhosphorIconsLight.arrowRight),
125+
onPressed: _animateToNext,
126+
),
127+
),
128+
),
129+
),
130+
],
131+
),
132+
);
133+
}
134+
}

app/windows/runner/Runner.rc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ BEGIN
9090
BLOCK "040904e4"
9191
BEGIN
9292
VALUE "CompanyName", "dev.linwood" "\0"
93-
VALUE "FileDescription", "vulpine" "\0"
93+
VALUE "FileDescription", "Linwood Vulpine" "\0"
9494
VALUE "FileVersion", VERSION_AS_STRING "\0"
95-
VALUE "InternalName", "vulpine" "\0"
96-
VALUE "LegalCopyright", "Copyright (C) 2025 dev.linwood. All rights reserved." "\0"
95+
VALUE "InternalName", "Linwood Vulpine" "\0"
96+
VALUE "LegalCopyright", "Copyright (C) 2022 Linwood. Licensed under AGPL-3.0-only." "\0"
9797
VALUE "OriginalFilename", "vulpine.exe" "\0"
98-
VALUE "ProductName", "vulpine" "\0"
98+
VALUE "ProductName", "Linwood Vulpine" "\0"
9999
VALUE "ProductVersion", VERSION_AS_STRING "\0"
100100
END
101101
END

0 commit comments

Comments
 (0)