reason

update

...@@ -51,7 +51,7 @@ android { ...@@ -51,7 +51,7 @@ android {
51 // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 51 // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
52 applicationId "pub.yiyan.parlando.Parlando" 52 applicationId "pub.yiyan.parlando.Parlando"
53 minSdkVersion 21 53 minSdkVersion 21
54 - targetSdkVersion 30 54 + targetSdkVersion 31
55 versionCode flutterVersionCode.toInteger() 55 versionCode flutterVersionCode.toInteger()
56 versionName flutterVersionName 56 versionName flutterVersionName
57 multiDexEnabled true 57 multiDexEnabled true
...@@ -78,5 +78,5 @@ flutter { ...@@ -78,5 +78,5 @@ flutter {
78 } 78 }
79 79
80 dependencies { 80 dependencies {
81 - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 81 + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
82 } 82 }
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
10 <activity 10 <activity
11 android:name=".MainActivity" 11 android:name=".MainActivity"
12 android:launchMode="singleTop" 12 android:launchMode="singleTop"
13 + android:exported="true"
13 android:theme="@style/LaunchTheme" 14 android:theme="@style/LaunchTheme"
14 android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" 15 android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
15 android:hardwareAccelerated="true" 16 android:hardwareAccelerated="true"
......
1 +import 'package:event_bus/event_bus.dart';
2 +
3 +EventBus eventBus = EventBus();
4 +
5 +class TransEvent {
6 + TransEvent();
7 +}
1 +import 'package:Parlando/events/trans_event.dart';
2 +import 'package:Parlando/poem/poem_router.dart';
3 +import 'package:Parlando/routers/fluro_navigator.dart';
4 +import 'package:Parlando/widgets/radial/flutter_radial_menu.dart';
1 import 'package:flutter/material.dart'; 5 import 'package:flutter/material.dart';
2 import 'package:Parlando/account/page/account_page.dart'; 6 import 'package:Parlando/account/page/account_page.dart';
3 import 'package:Parlando/poem/page/poem_page.dart'; 7 import 'package:Parlando/poem/page/poem_page.dart';
...@@ -13,13 +17,51 @@ class Home extends StatefulWidget { ...@@ -13,13 +17,51 @@ class Home extends StatefulWidget {
13 _HomeState createState() => _HomeState(); 17 _HomeState createState() => _HomeState();
14 } 18 }
15 19
20 +enum MenuOptions {
21 + audio,
22 + video,
23 +}
24 +
16 class _HomeState extends State<Home> with RestorationMixin { 25 class _HomeState extends State<Home> with RestorationMixin {
17 late List<Widget> _pageList; 26 late List<Widget> _pageList;
18 final PageController _pageController = PageController(); 27 final PageController _pageController = PageController();
19 28
20 HomeProvider provider = HomeProvider(); 29 HomeProvider provider = HomeProvider();
30 + final GlobalKey<RadialMenuState> _menuKey = GlobalKey<RadialMenuState>();
31 + final List<RadialMenuItem<MenuOptions>> items = <RadialMenuItem<MenuOptions>>[
32 + const RadialMenuItem<MenuOptions>(
33 + tooltip: 'audio',
34 + value: MenuOptions.audio,
35 + child: Icon(
36 + Icons.mic_none_outlined,
37 + ),
38 + iconColor: Colors.white,
39 + backgroundColor: Colors.blue,
40 + ),
41 + const RadialMenuItem<MenuOptions>(
42 + tooltip: "video",
43 + value: MenuOptions.video,
44 + child: Icon(
45 + Icons.video_call_outlined,
46 + ),
47 + iconColor: Colors.white,
48 + backgroundColor: Colors.green,
49 + ),
50 + ];
21 51
22 - List<BottomNavigationBarItem>? _list; 52 + void _onItemSelected(MenuOptions value) {
53 + if (value == MenuOptions.video) {
54 + NavigatorUtils.push(
55 + context,
56 + '${PoemRouter.poemRecordAudioPage}?id=100',
57 + );
58 + } else if (value == MenuOptions.audio) {
59 + NavigatorUtils.push(
60 + context,
61 + '${PoemRouter.poemRecordVideoPage}?data=100',
62 + );
63 + }
64 + }
23 65
24 @override 66 @override
25 void initState() { 67 void initState() {
...@@ -47,21 +89,36 @@ class _HomeState extends State<Home> with RestorationMixin { ...@@ -47,21 +89,36 @@ class _HomeState extends State<Home> with RestorationMixin {
47 child: DoubleTapBackExitApp( 89 child: DoubleTapBackExitApp(
48 child: Scaffold( 90 child: Scaffold(
49 floatingActionButton: FloatingActionButton( 91 floatingActionButton: FloatingActionButton(
50 - onPressed: () {}, 92 + onPressed: () {
93 + eventBus.fire(TransEvent());
94 + NavigatorUtils.push(
95 + context,
96 + '${PoemRouter.poemRecordVideoPage}?data=100',
97 + );
98 + },
51 tooltip: "发一言", 99 tooltip: "发一言",
52 backgroundColor: Colors.white, 100 backgroundColor: Colors.white,
53 child: const Icon( 101 child: const Icon(
54 - Icons.add, 102 + Icons.video_call_outlined,
55 color: Colors.black45, 103 color: Colors.black45,
56 ), 104 ),
57 ), 105 ),
106 + // floatingActionButton: SizedBox(
107 + // height: 60,
108 + // child: RadialMenu(
109 + // key: _menuKey,
110 + // items: items,
111 + // radius: 80.0,
112 + // onSelected: _onItemSelected,
113 + // progressAnimationDuration:const Duration(milliseconds: 1),
114 + // ),
115 + // ),
58 floatingActionButtonLocation: 116 floatingActionButtonLocation:
59 FloatingActionButtonLocation.centerDocked, 117 FloatingActionButtonLocation.centerDocked,
60 bottomNavigationBar: Consumer<HomeProvider>( 118 bottomNavigationBar: Consumer<HomeProvider>(
61 builder: (_, provider, __) { 119 builder: (_, provider, __) {
62 return BottomAppBar( 120 return BottomAppBar(
63 color: Colors.black45, 121 color: Colors.black45,
64 - shape: const CircularNotchedRectangle(),
65 child: Row( 122 child: Row(
66 mainAxisSize: MainAxisSize.max, 123 mainAxisSize: MainAxisSize.max,
67 mainAxisAlignment: MainAxisAlignment.spaceAround, 124 mainAxisAlignment: MainAxisAlignment.spaceAround,
...@@ -109,7 +166,6 @@ class _HomeState extends State<Home> with RestorationMixin { ...@@ -109,7 +166,6 @@ class _HomeState extends State<Home> with RestorationMixin {
109 ), 166 ),
110 ), 167 ),
111 ]), 168 ]),
112 - elevation: 5.0,
113 ); 169 );
114 }, 170 },
115 ), 171 ),
......
...@@ -8,7 +8,6 @@ import 'package:Parlando/poem/widgets/poem_user_comments.dart'; ...@@ -8,7 +8,6 @@ import 'package:Parlando/poem/widgets/poem_user_comments.dart';
8 import 'package:Parlando/res/gaps.dart'; 8 import 'package:Parlando/res/gaps.dart';
9 import 'package:Parlando/routers/fluro_navigator.dart'; 9 import 'package:Parlando/routers/fluro_navigator.dart';
10 import 'package:Parlando/util/image_utils.dart'; 10 import 'package:Parlando/util/image_utils.dart';
11 -import 'package:Parlando/widgets/bars/home_action_bar.dart';
12 import 'package:Parlando/widgets/bars/home_menu_bar.dart'; 11 import 'package:Parlando/widgets/bars/home_menu_bar.dart';
13 import 'package:Parlando/widgets/my_app_bar.dart'; 12 import 'package:Parlando/widgets/my_app_bar.dart';
14 13
......
1 +import 'dart:async';
2 +
3 +import 'package:Parlando/events/trans_event.dart';
1 import 'package:flutter/material.dart'; 4 import 'package:flutter/material.dart';
2 import 'package:Parlando/category/category_router.dart'; 5 import 'package:Parlando/category/category_router.dart';
3 import 'package:Parlando/poem/poem_router.dart'; 6 import 'package:Parlando/poem/poem_router.dart';
...@@ -34,6 +37,7 @@ class _PoemPageState extends State<PoemPage> with WidgetsBindingObserver { ...@@ -34,6 +37,7 @@ class _PoemPageState extends State<PoemPage> with WidgetsBindingObserver {
34 final TikTokVideoListController _videoListController = 37 final TikTokVideoListController _videoListController =
35 TikTokVideoListController(); 38 TikTokVideoListController();
36 List<UserVideo> videoDataList = []; 39 List<UserVideo> videoDataList = [];
40 + late StreamSubscription bus;
37 41
38 @override 42 @override
39 void didChangeAppLifecycleState(AppLifecycleState state) async { 43 void didChangeAppLifecycleState(AppLifecycleState state) async {
...@@ -46,6 +50,7 @@ class _PoemPageState extends State<PoemPage> with WidgetsBindingObserver { ...@@ -46,6 +50,7 @@ class _PoemPageState extends State<PoemPage> with WidgetsBindingObserver {
46 void dispose() { 50 void dispose() {
47 WidgetsBinding.instance!.removeObserver(this); 51 WidgetsBinding.instance!.removeObserver(this);
48 _videoListController.currentPlayer.pause(); 52 _videoListController.currentPlayer.pause();
53 + bus.cancel();
49 super.dispose(); 54 super.dispose();
50 } 55 }
51 56
...@@ -58,19 +63,19 @@ class _PoemPageState extends State<PoemPage> with WidgetsBindingObserver { ...@@ -58,19 +63,19 @@ class _PoemPageState extends State<PoemPage> with WidgetsBindingObserver {
58 initialList: videoDataList 63 initialList: videoDataList
59 .map( 64 .map(
60 (e) => VPVideoController( 65 (e) => VPVideoController(
61 - videoInfo: e, 66 + videoInfo: e,
62 - builder: () => VideoPlayerController.asset(e.url), 67 + builder: () => VideoPlayerController.asset(e.url),
63 - ), 68 + ),
64 - ) 69 + )
65 .toList(), 70 .toList(),
66 videoProvider: (int index, List<VPVideoController> list) async { 71 videoProvider: (int index, List<VPVideoController> list) async {
67 return videoDataList 72 return videoDataList
68 .map( 73 .map(
69 (e) => VPVideoController( 74 (e) => VPVideoController(
70 - videoInfo: e, 75 + videoInfo: e,
71 - builder: () => VideoPlayerController.asset(e.url), 76 + builder: () => VideoPlayerController.asset(e.url),
72 - ), 77 + ),
73 - ) 78 + )
74 .toList(); 79 .toList();
75 }, 80 },
76 ); 81 );
...@@ -87,6 +92,10 @@ class _PoemPageState extends State<PoemPage> with WidgetsBindingObserver { ...@@ -87,6 +92,10 @@ class _PoemPageState extends State<PoemPage> with WidgetsBindingObserver {
87 }, 92 },
88 ); 93 );
89 94
95 + bus = eventBus.on<TransEvent>().listen((event) {
96 + _videoListController.currentPlayer.pause();
97 + });
98 +
90 super.initState(); 99 super.initState();
91 } 100 }
92 101
...@@ -167,7 +176,7 @@ class _PoemPageState extends State<PoemPage> with WidgetsBindingObserver { ...@@ -167,7 +176,7 @@ class _PoemPageState extends State<PoemPage> with WidgetsBindingObserver {
167 Widget poem = TikTokVidePoem( 176 Widget poem = TikTokVidePoem(
168 title: "每日一言", 177 title: "每日一言",
169 poem: 178 poem:
170 - "清晨入古寺,初日照高林。\n曲径通幽处,禅房花木深。\n山光悦鸟性,潭影空人心。\n万籁此都寂,但余钟磬音。\n", 179 + "清晨入古寺,初日照高林。\n曲径通幽处,禅房花木深。\n山光悦鸟性,潭影空人心。\n万籁此都寂,但余钟磬音。\n",
171 author: "----《题破山寺后禅院》常建", 180 author: "----《题破山寺后禅院》常建",
172 onShowDetail: () { 181 onShowDetail: () {
173 tkController.animateToPage(TikTokPagePosition.right); 182 tkController.animateToPage(TikTokPagePosition.right);
......
1 +library flutter_radial_menu;
2 +
3 +export 'src/radial_menu.dart';
4 +export 'src/radial_menu_item.dart';
1 +import 'dart:math' as Math;
2 +
3 +import 'package:flutter/material.dart';
4 +
5 +/// Draws an [ActionIcon] and [_ArcProgressPainter] that represent an active action.
6 +/// As the provided [Animation] progresses the ActionArc grows into a full
7 +/// circle and the ActionIcon moves along it.
8 +class ArcProgressIndicator extends StatelessWidget {
9 + // required
10 + final Animation<double> controller;
11 + final double radius;
12 +
13 + // optional
14 + final double startAngle;
15 + final double? width;
16 +
17 + /// The color to use when filling the arc.
18 + ///
19 + /// Defaults to the accent color of the current theme.
20 + final Color? color;
21 + final IconData icon;
22 + final Color? iconColor;
23 + final double? iconSize;
24 +
25 + // private
26 + final Animation<double> _progress;
27 +
28 + ArcProgressIndicator({
29 + Key? key,
30 + required this.controller,
31 + required this.radius,
32 + this.startAngle = 0.0,
33 + this.width,
34 + this.color,
35 + required this.icon,
36 + this.iconColor,
37 + this.iconSize,
38 + }) : _progress = Tween(begin: 0.0, end: 1.0).animate(controller),
39 + super(key: key);
40 +
41 + @override
42 + Widget build(BuildContext context) {
43 + late TextPainter _iconPainter;
44 + final ThemeData theme = Theme.of(context);
45 + final Color? _iconColor = iconColor ?? theme.colorScheme.secondary;
46 + final double? _iconSize = iconSize ?? IconTheme.of(context).size;
47 +
48 + _iconPainter = TextPainter(
49 + textDirection: Directionality.of(context),
50 + text: TextSpan(
51 + text: String.fromCharCode(icon.codePoint),
52 + style: TextStyle(
53 + inherit: false,
54 + color: _iconColor,
55 + fontSize: _iconSize,
56 + fontFamily: icon.fontFamily,
57 + package: icon.fontPackage,
58 + ),
59 + ),
60 + )..layout();
61 +
62 + return CustomPaint(
63 + painter: _ArcProgressPainter(
64 + controller: _progress,
65 + color: color ?? theme.colorScheme.secondary,
66 + radius: radius,
67 + width: width ?? _iconSize! * 2,
68 + startAngle: startAngle,
69 + icon: _iconPainter,
70 + ),
71 + );
72 + }
73 +}
74 +
75 +class _ArcProgressPainter extends CustomPainter {
76 + // required
77 + final Animation<double> controller;
78 + final Color color;
79 + final double radius;
80 + final double width;
81 +
82 + // optional
83 + final double startAngle;
84 + final TextPainter icon;
85 +
86 + _ArcProgressPainter({
87 + required this.controller,
88 + required this.color,
89 + required this.radius,
90 + required this.width,
91 + this.startAngle = 0.0,
92 + required this.icon,
93 + }) : super(repaint: controller);
94 +
95 + @override
96 + void paint(Canvas canvas, Size size) {
97 + Paint paint = Paint()
98 + ..color = color
99 + ..strokeWidth = width
100 + ..strokeCap = StrokeCap.round
101 + ..style = PaintingStyle.stroke;
102 +
103 + final double sweepAngle = controller.value * 2 * Math.pi;
104 +
105 + canvas.drawArc(
106 + Offset.zero & size,
107 + startAngle,
108 + sweepAngle,
109 + false,
110 + paint,
111 + );
112 +
113 + double angle = startAngle + sweepAngle;
114 + Offset offset = Offset(
115 + (size.width / 2 - icon.size.width / 2) + radius * Math.cos(angle),
116 + (size.height / 2 - icon.size.height / 2) + radius * Math.sin(angle),
117 + );
118 +
119 + icon.paint(canvas, offset);
120 + }
121 +
122 + @override
123 + bool shouldRepaint(_ArcProgressPainter other) {
124 + return controller.value != other.controller.value ||
125 + color != other.color ||
126 + radius != other.radius ||
127 + width != other.width ||
128 + startAngle != other.startAngle ||
129 + icon != other.icon;
130 + }
131 +}
1 +import 'dart:async';
2 +import 'dart:math' as math;
3 +
4 +import 'package:flutter/material.dart';
5 +import 'package:Parlando/widgets/radial/src/radial_menu_button.dart';
6 +import 'package:Parlando/widgets/radial/src/radial_menu_center_button.dart';
7 +import 'package:Parlando/widgets/radial/src/radial_menu_item.dart';
8 +
9 +const double _radiansPerDegree = math.pi / 180;
10 +const double _startAngle = -120.0 * _radiansPerDegree;
11 +
12 +typedef ItemAngleCalculator = double Function(int index);
13 +
14 +/// A radial menu for selecting from a list of items.
15 +///
16 +/// A radial menu lets the user select from a number of items. It displays a
17 +/// button that opens the menu, showing its items arranged in an arc. Selecting
18 +/// an item triggers the animation of a progress bar drawn at the specified
19 +/// [radius] around the central menu button.
20 +///
21 +/// The type `T` is the type of the values the radial menu represents. All the
22 +/// entries in a given menu must represent values with consistent types.
23 +/// Typically, an enum is used. Each [RadialMenuItem] in [items] must be
24 +/// specialized with that same type argument.
25 +///
26 +/// Requires one of its ancestors to be a [Material] widget.
27 +///
28 +/// See also:
29 +///
30 +/// * [RadialMenuItem], the widget used to represent the [items].
31 +/// * [RadialMenuCenterButton], the button used to open and close the menu.
32 +class RadialMenu<T> extends StatefulWidget {
33 + /// Creates a dropdown button.
34 + ///
35 + /// The [items] must have distinct values.
36 + ///
37 + /// The [radius], [menuAnimationDuration], and [progressAnimationDuration]
38 + /// arguments must not be null (they all have defaults, so do not need to be
39 + /// specified).
40 + const RadialMenu({
41 + Key? key,
42 + required this.items,
43 + required this.onSelected,
44 + this.radius = 100.0,
45 + this.menuAnimationDuration = const Duration(milliseconds: 1000),
46 + this.progressAnimationDuration = const Duration(milliseconds: 1000),
47 + }) : super(key: key);
48 +
49 + /// The list of possible items to select among.
50 + final List<RadialMenuItem<T>> items;
51 +
52 + /// Called when the user selects an item.
53 + final Function onSelected; // TODO why Function? not ValueChanged?
54 +
55 + /// The radius of the arc used to lay out the items and draw the progress bar.
56 + ///
57 + /// Defaults to 100.0.
58 + final double radius;
59 +
60 + /// Duration of the menu opening/closing animation.
61 + ///
62 + /// Defaults to 1000 milliseconds.
63 + final Duration menuAnimationDuration;
64 +
65 + /// Duration of the action activation progress arc animation.
66 + ///
67 + /// Defaults to 1000 milliseconds.
68 + final Duration progressAnimationDuration;
69 +
70 + @override
71 + RadialMenuState createState() => RadialMenuState();
72 +}
73 +
74 +class RadialMenuState extends State<RadialMenu> with TickerProviderStateMixin {
75 + late AnimationController _menuAnimationController;
76 + late AnimationController _progressAnimationController;
77 + bool _isOpen = false;
78 + int _activeItemIndex = -1;
79 +
80 + // todo: xqwzts: allow users to pass in their own calculator as a param.
81 + // and change this to the default: radialItemAngleCalculator.
82 + double calculateItemAngle(int index) {
83 + double _itemSpacing = 120.0 / widget.items.length;
84 + return _startAngle + index * _itemSpacing * _radiansPerDegree;
85 + }
86 +
87 + @override
88 + void initState() {
89 + super.initState();
90 + _menuAnimationController = AnimationController(
91 + duration: widget.menuAnimationDuration,
92 + vsync: this,
93 + );
94 + _progressAnimationController = AnimationController(
95 + duration: widget.progressAnimationDuration,
96 + vsync: this,
97 + );
98 + }
99 +
100 + @override
101 + void dispose() {
102 + _menuAnimationController.dispose();
103 + _progressAnimationController.dispose();
104 + super.dispose();
105 + }
106 +
107 + void _openMenu() {
108 + _menuAnimationController.forward();
109 + setState(() => _isOpen = true);
110 + }
111 +
112 + void _closeMenu() {
113 + _menuAnimationController.reverse();
114 + setState(() => _isOpen = false);
115 + }
116 +
117 + Future<void> _activate(int itemIndex) async {
118 + setState(() => _activeItemIndex = itemIndex);
119 + await _progressAnimationController.forward().orCancel;
120 + widget.onSelected(widget.items[itemIndex].value);
121 + _closeMenu();
122 + }
123 +
124 + /// Resets the menu to its initial (closed) state.
125 + void reset() {
126 + _menuAnimationController.reset();
127 + _progressAnimationController.reverse();
128 + setState(() {
129 + _isOpen = false;
130 + _activeItemIndex = -1;
131 + });
132 + }
133 +
134 + Widget _buildActionButton(int index) {
135 + final RadialMenuItem item = widget.items[index];
136 +
137 + return LayoutId(
138 + id: '${_RadialMenuLayout.actionButton}$index',
139 + child: RadialMenuButton(
140 + child: item,
141 + backgroundColor: item.backgroundColor,
142 + onPressed: () => _activate(index),
143 + ),
144 + );
145 + }
146 +
147 + Widget _buildCenterButton() {
148 + return LayoutId(
149 + id: _RadialMenuLayout.menuButton,
150 + child: RadialMenuCenterButton(
151 + openCloseAnimationController: _menuAnimationController.view,
152 + activateAnimationController: _progressAnimationController.view,
153 + isOpen: _isOpen,
154 + onPressed: _isOpen ? _closeMenu : _openMenu,
155 + ),
156 + );
157 + }
158 +
159 + @override
160 + Widget build(BuildContext context) {
161 + final List<Widget> children = <Widget>[];
162 + for (int i = 0; i < widget.items.length; i++) {
163 + if (_activeItemIndex != i) {
164 + children.add(_buildActionButton(i));
165 + }
166 + }
167 + children.add(_buildCenterButton());
168 +
169 + return AnimatedBuilder(
170 + animation: _menuAnimationController,
171 + builder: (BuildContext context, Widget? child) {
172 + return CustomMultiChildLayout(
173 + delegate: _RadialMenuLayout(
174 + itemCount: widget.items.length,
175 + radius: widget.radius,
176 + calculateItemAngle: calculateItemAngle,
177 + controller: _menuAnimationController.view,
178 + ),
179 + children: children,
180 + );
181 + },
182 + );
183 + }
184 +}
185 +
186 +class _RadialMenuLayout extends MultiChildLayoutDelegate {
187 + static const String menuButton = 'menuButton';
188 + static const String actionButton = 'actionButton';
189 + static const String activeAction = 'activeAction';
190 +
191 + final int itemCount;
192 + final double radius;
193 + final ItemAngleCalculator calculateItemAngle;
194 +
195 + final Animation<double> controller;
196 +
197 + final Animation<double> _progress;
198 +
199 + _RadialMenuLayout({
200 + required this.itemCount,
201 + required this.radius,
202 + required this.calculateItemAngle,
203 + required this.controller,
204 + }) : _progress = Tween<double>(begin: 0.0, end: radius).animate(
205 + CurvedAnimation(
206 + curve: Curves.elasticOut,
207 + parent: controller,
208 + ),
209 + );
210 +
211 + late Offset center;
212 +
213 + @override
214 + void performLayout(Size size) {
215 + center = Offset(size.width / 2, size.height / 2);
216 +
217 + if (hasChild(menuButton)) {
218 + Size menuButtonSize;
219 + menuButtonSize = layoutChild(menuButton, BoxConstraints.loose(size));
220 +
221 + // place the menubutton in the center
222 + positionChild(
223 + menuButton,
224 + Offset(
225 + center.dx - menuButtonSize.width / 2,
226 + center.dy - menuButtonSize.height / 2,
227 + ),
228 + );
229 + }
230 +
231 + for (int i = 0; i < itemCount; i++) {
232 + final String actionButtonId = '$actionButton$i';
233 + final String actionArcId = '$activeAction$i';
234 + if (hasChild(actionArcId)) {
235 + final Size arcSize = layoutChild(
236 + actionArcId,
237 + BoxConstraints.expand(
238 + width: _progress.value * 2,
239 + height: _progress.value * 2,
240 + ),
241 + );
242 +
243 + positionChild(
244 + actionArcId,
245 + Offset(
246 + center.dx - arcSize.width / 2,
247 + center.dy - arcSize.height / 2,
248 + ),
249 + );
250 + }
251 +
252 + if (hasChild(actionButtonId)) {
253 + final Size buttonSize =
254 + layoutChild(actionButtonId, BoxConstraints.loose(size));
255 +
256 + final double itemAngle = calculateItemAngle(i);
257 +
258 + positionChild(
259 + actionButtonId,
260 + Offset(
261 + (center.dx - buttonSize.width / 2) +
262 + (_progress.value) * math.cos(itemAngle),
263 + (center.dy - buttonSize.height / 2) +
264 + (_progress.value) * math.sin(itemAngle),
265 + ),
266 + );
267 + }
268 + }
269 + }
270 +
271 + @override
272 + bool shouldRelayout(_RadialMenuLayout oldDelegate) =>
273 + itemCount != oldDelegate.itemCount ||
274 + radius != oldDelegate.radius ||
275 + calculateItemAngle != oldDelegate.calculateItemAngle ||
276 + controller != oldDelegate.controller ||
277 + _progress != oldDelegate._progress;
278 +}
1 +import 'package:flutter/foundation.dart';
2 +import 'package:flutter/material.dart';
3 +
4 +class RadialMenuButton extends StatelessWidget {
5 + const RadialMenuButton({
6 + Key? key,
7 + required this.child,
8 + required this.backgroundColor,
9 + required this.onPressed,
10 + }) : super(key: key);
11 +
12 + final Widget child;
13 + final Color backgroundColor;
14 + final VoidCallback onPressed;
15 +
16 + @override
17 + Widget build(BuildContext context) {
18 + final Color color = backgroundColor;
19 +
20 + return Semantics(
21 + button: true,
22 + enabled: true,
23 + child: Material(
24 + type: MaterialType.circle,
25 + color: color,
26 + child: InkWell(
27 + onTap: onPressed,
28 + child: child,
29 + ),
30 + ),
31 + );
32 + }
33 +}
1 +import 'package:flutter/foundation.dart';
2 +import 'package:flutter/material.dart';
3 +import 'package:Parlando/widgets/radial/src/radial_menu_button.dart';
4 +
5 +const double _defaultButtonSize = 48.0;
6 +
7 +/// The button at the center of a [RadialMenu] which controls its open/closed
8 +/// state.
9 +class RadialMenuCenterButton extends StatelessWidget {
10 + /// Drives the opening/closing animation of the [RadialMenu].
11 + final Animation<double> openCloseAnimationController;
12 +
13 + /// Drives the animation when an item in the [RadialMenu] is pressed.
14 + final Animation<double> activateAnimationController;
15 +
16 + /// Called when the user presses this button.
17 + final VoidCallback onPressed;
18 +
19 + /// The opened/closed state of the menu.
20 + ///
21 + /// Determines which of [closedColor] or [openedColor] should be used as the
22 + /// background color of the button.
23 + final bool isOpen;
24 +
25 + /// The color to use when painting the icon.
26 + ///
27 + /// Defaults to [Colors.black].
28 + final Color iconColor;
29 +
30 + /// Background color when it is in its closed state.
31 + ///
32 + /// Defaults to [Colors.white].
33 + final Color closedColor;
34 +
35 + /// Background color when it is in its opened state.
36 + ///
37 + /// Defaults to [Colors.grey].
38 + final Color openedColor;
39 +
40 + /// The size of the button.
41 + ///
42 + /// Defaults to 48.0.
43 + final double size;
44 +
45 + /// The animation progress for the [AnimatedIcon] in the center of the button.
46 + final Animation<double> _progress;
47 +
48 + /// The scale factor applied to the button.
49 + ///
50 + /// Animates from 1.0 to 0.0 when an an item is pressed in the menu and
51 + /// [activateAnimationController] progresses.
52 + final Animation<double> _scale;
53 +
54 + RadialMenuCenterButton({
55 + Key? key,
56 + required this.openCloseAnimationController,
57 + required this.activateAnimationController,
58 + required this.onPressed,
59 + required this.isOpen,
60 + this.iconColor = Colors.black,
61 + this.closedColor = Colors.white,
62 + this.openedColor = Colors.grey,
63 + this.size = _defaultButtonSize,
64 + }) : _progress = Tween(begin: 0.0, end: 1.0).animate(
65 + CurvedAnimation(
66 + parent: openCloseAnimationController,
67 + curve: const Interval(
68 + 0.0,
69 + 0.5,
70 + curve: Curves.ease,
71 + ),
72 + ),
73 + ),
74 + _scale = Tween(begin: 1.0, end: 0.0).animate(
75 + CurvedAnimation(
76 + parent: activateAnimationController,
77 + curve: Curves.elasticIn,
78 + ),
79 + ),
80 + super(key: key);
81 +
82 + @override
83 + Widget build(BuildContext context) {
84 + final AnimatedIcon animatedIcon = AnimatedIcon(
85 + color: iconColor,
86 + icon: AnimatedIcons.menu_close,
87 + progress: _progress,
88 + );
89 +
90 + final Widget child = SizedBox(
91 + width: size,
92 + height: size,
93 + child: Center(
94 + child: animatedIcon,
95 + ),
96 + );
97 +
98 + final Color color = isOpen ? openedColor : closedColor;
99 +
100 + return ScaleTransition(
101 + scale: _scale,
102 + child: RadialMenuButton(
103 + child: child,
104 + backgroundColor: color,
105 + onPressed: onPressed,
106 + ),
107 + );
108 + }
109 +}
1 +import 'package:flutter/foundation.dart';
2 +import 'package:flutter/material.dart';
3 +
4 +const double _defaultButtonSize = 48.0;
5 +
6 +/// An item in a [RadialMenu].
7 +///
8 +/// The type `T` is the type of the value the entry represents. All the entries
9 +/// in a given menu must represent values with consistent types.
10 +class RadialMenuItem<T> extends StatelessWidget {
11 + /// Creates a circular action button for an item in a [RadialMenu].
12 + ///
13 + /// The [child] argument is required.
14 + const RadialMenuItem({
15 + Key? key,
16 + required this.child,
17 + required this.value,
18 + required this.tooltip,
19 + this.size = _defaultButtonSize,
20 + required this.backgroundColor,
21 + required this.iconColor,
22 + this.iconSize = 24.0,
23 + }) : super(key: key);
24 +
25 + /// The widget below this widget in the tree.
26 + ///
27 + /// Typically an [Icon] widget.
28 + final Widget child;
29 +
30 + /// The value to return if the user selects this menu item.
31 + ///
32 + /// Eventually returned in a call to [RadialMenu.onSelected].
33 + final T value;
34 +
35 + /// Text that describes the action that will occur when the button is pressed.
36 + ///
37 + /// This text is displayed when the user long-presses on the button and is
38 + /// used for accessibility.
39 + final String tooltip;
40 +
41 + /// The color to use when filling the button.
42 + ///
43 + /// Defaults to the primary color of the current theme.
44 + final Color backgroundColor;
45 +
46 + /// The size of the button.
47 + ///
48 + /// Defaults to 48.0.
49 + final double size;
50 +
51 + /// The color to use when painting the child icon.
52 + ///
53 + /// Defaults to the primary icon theme color.
54 + final Color? iconColor;
55 +
56 + final double? iconSize;
57 +
58 + @override
59 + Widget build(BuildContext context) {
60 + final Color? _iconColor =
61 + iconColor ?? Theme.of(context).primaryIconTheme.color;
62 +
63 + late Widget result;
64 +
65 + result = Center(
66 + child: IconTheme.merge(
67 + data: IconThemeData(
68 + color: _iconColor,
69 + size: iconSize,
70 + ),
71 + child: child,
72 + ),
73 + );
74 +
75 + result = Tooltip(
76 + message: tooltip,
77 + child: result,
78 + );
79 +
80 + result = SizedBox(
81 + width: size,
82 + height: size,
83 + child: result,
84 + );
85 +
86 + return result;
87 + }
88 +}
...@@ -309,6 +309,13 @@ packages: ...@@ -309,6 +309,13 @@ packages:
309 url: "https://pub.flutter-io.cn" 309 url: "https://pub.flutter-io.cn"
310 source: hosted 310 source: hosted
311 version: "2.0.1" 311 version: "2.0.1"
312 + event_bus:
313 + dependency: "direct main"
314 + description:
315 + name: event_bus
316 + url: "https://pub.flutter-io.cn"
317 + source: hosted
318 + version: "2.0.0"
312 fake_async: 319 fake_async:
313 dependency: transitive 320 dependency: transitive
314 description: 321 description:
......
...@@ -111,6 +111,7 @@ dependencies: ...@@ -111,6 +111,7 @@ dependencies:
111 111
112 getwidget: ^2.0.5 112 getwidget: ^2.0.5
113 sign_in_with_apple: ^3.3.0 113 sign_in_with_apple: ^3.3.0
114 + event_bus: ^2.0.0
114 115
115 dependency_overrides: 116 dependency_overrides:
116 decimal: 1.5.0 117 decimal: 1.5.0
......