Showing
15 changed files
with
456 additions
and
37 deletions
1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | 1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
2 | + xmlns:tools="http://schemas.android.com/tools" | ||
2 | package="com.mofunsky.one_poem"> | 3 | package="com.mofunsky.one_poem"> |
3 | <application | 4 | <application |
5 | + android:requestLegacyExternalStorage="true" | ||
6 | + tools:replace="android:label" | ||
4 | android:label="一言" | 7 | android:label="一言" |
5 | android:icon="@mipmap/ic_launcher"> | 8 | android:icon="@mipmap/ic_launcher"> |
6 | <activity | 9 | <activity | ... | ... |
... | @@ -41,5 +41,12 @@ | ... | @@ -41,5 +41,12 @@ |
41 | </array> | 41 | </array> |
42 | <key>UIViewControllerBasedStatusBarAppearance</key> | 42 | <key>UIViewControllerBasedStatusBarAppearance</key> |
43 | <false/> | 43 | <false/> |
44 | + <key>NSMicrophoneUsageDescription</key> | ||
45 | + <string>打开话筒</string> | ||
46 | + <key>NSAppTransportSecurity</key> | ||
47 | + <dict> | ||
48 | + <key>NSAllowsArbitraryLoads</key> | ||
49 | + <true/> | ||
50 | + </dict> | ||
44 | </dict> | 51 | </dict> |
45 | </plist> | 52 | </plist> | ... | ... |
... | @@ -73,7 +73,7 @@ class CategoryItem extends StatelessWidget { | ... | @@ -73,7 +73,7 @@ class CategoryItem extends StatelessWidget { |
73 | ], | 73 | ], |
74 | ), | 74 | ), |
75 | const Text( | 75 | const Text( |
76 | - "京", | 76 | + "冬", |
77 | style: TextStyle( | 77 | style: TextStyle( |
78 | fontSize: 30, fontFamily: "ZhiMangXing"), | 78 | fontSize: 30, fontFamily: "ZhiMangXing"), |
79 | ), | 79 | ), | ... | ... |
lib/extension/double_extension.dart
0 → 100644
lib/extension/int_extension.dart
0 → 100644
lib/extension/shared/size_fit.dart
0 → 100644
1 | +import 'package:flutter/material.dart'; | ||
2 | + | ||
3 | +class HYSizeFit { | ||
4 | + static late MediaQueryData _mediaQueryData; | ||
5 | + static late double screenWidth; | ||
6 | + static late double screenHeight; | ||
7 | + static late double rpx; | ||
8 | + static late double px; | ||
9 | + | ||
10 | + static void initialize(BuildContext context, {double standardWidth = 750}) { | ||
11 | + _mediaQueryData = MediaQuery.of(context); | ||
12 | + screenWidth = _mediaQueryData.size.width; | ||
13 | + screenHeight = _mediaQueryData.size.height; | ||
14 | + rpx = screenWidth / standardWidth; | ||
15 | + px = screenWidth / standardWidth * 2; | ||
16 | + } | ||
17 | + | ||
18 | + // 按照像素来设置 | ||
19 | + static double setPx(double size) { | ||
20 | + return HYSizeFit.rpx * size * 2; | ||
21 | + } | ||
22 | + | ||
23 | + // 按照rxp来设置 | ||
24 | + static double setRpx(double size) { | ||
25 | + return HYSizeFit.rpx * size; | ||
26 | + } | ||
27 | +} |
... | @@ -2,6 +2,7 @@ import 'dart:async'; | ... | @@ -2,6 +2,7 @@ import 'dart:async'; |
2 | 2 | ||
3 | import 'package:flutter/material.dart'; | 3 | import 'package:flutter/material.dart'; |
4 | import 'package:flutter_swiper_null_safety/flutter_swiper_null_safety.dart'; | 4 | import 'package:flutter_swiper_null_safety/flutter_swiper_null_safety.dart'; |
5 | +import 'package:one_poem/extension/shared/size_fit.dart'; | ||
5 | import 'package:one_poem/login/login_router.dart'; | 6 | import 'package:one_poem/login/login_router.dart'; |
6 | import 'package:one_poem/res/constant.dart'; | 7 | import 'package:one_poem/res/constant.dart'; |
7 | import 'package:one_poem/routers/fluro_navigator.dart'; | 8 | import 'package:one_poem/routers/fluro_navigator.dart'; |
... | @@ -83,6 +84,7 @@ class _SplashPageState extends State<SplashPage> { | ... | @@ -83,6 +84,7 @@ class _SplashPageState extends State<SplashPage> { |
83 | 84 | ||
84 | @override | 85 | @override |
85 | Widget build(BuildContext context) { | 86 | Widget build(BuildContext context) { |
87 | + HYSizeFit.initialize(context); | ||
86 | return Material( | 88 | return Material( |
87 | color: context.backgroundColor, | 89 | color: context.backgroundColor, |
88 | child: _status == 0 ? | 90 | child: _status == 0 ? | ... | ... |
... | @@ -84,6 +84,7 @@ class _LoginPageState extends State<LoginPage> with ChangeNotifierMixin<LoginPag | ... | @@ -84,6 +84,7 @@ class _LoginPageState extends State<LoginPage> with ChangeNotifierMixin<LoginPag |
84 | return Scaffold( | 84 | return Scaffold( |
85 | appBar: MyAppBar( | 85 | appBar: MyAppBar( |
86 | isBack: false, | 86 | isBack: false, |
87 | + isTransparent: true, | ||
87 | onPressed: () { | 88 | onPressed: () { |
88 | NavigatorUtils.push(context, LoginRouter.smsLoginPage); | 89 | NavigatorUtils.push(context, LoginRouter.smsLoginPage); |
89 | }, | 90 | }, | ... | ... |
... | @@ -11,6 +11,8 @@ import 'package:one_poem/widgets/bars/home_action_bar.dart'; | ... | @@ -11,6 +11,8 @@ import 'package:one_poem/widgets/bars/home_action_bar.dart'; |
11 | import 'package:one_poem/widgets/bars/home_menu_bar.dart'; | 11 | import 'package:one_poem/widgets/bars/home_menu_bar.dart'; |
12 | import 'package:one_poem/widgets/my_app_bar.dart'; | 12 | import 'package:one_poem/widgets/my_app_bar.dart'; |
13 | 13 | ||
14 | +import 'package:one_poem/extension/int_extension.dart'; | ||
15 | + | ||
14 | import '../poem_router.dart'; | 16 | import '../poem_router.dart'; |
15 | 17 | ||
16 | enum PoemContentSwitch { | 18 | enum PoemContentSwitch { |
... | @@ -52,8 +54,9 @@ class _PoemDetailPageState extends State<PoemDetailPage> { | ... | @@ -52,8 +54,9 @@ class _PoemDetailPageState extends State<PoemDetailPage> { |
52 | contentSwitch = PoemContentSwitch.comment; | 54 | contentSwitch = PoemContentSwitch.comment; |
53 | setState(() {}); | 55 | setState(() {}); |
54 | }, | 56 | }, |
55 | - funcRight: (){ | 57 | + funcRight: () { |
56 | - NavigatorUtils.push(context, '${PoemRouter.poemRecordAudioPage}?id=100'); | 58 | + NavigatorUtils.push( |
59 | + context, '${PoemRouter.poemRecordAudioPage}?id=100'); | ||
57 | }, | 60 | }, |
58 | ), | 61 | ), |
59 | homeActionWidgets: HomeActionWidgets( | 62 | homeActionWidgets: HomeActionWidgets( |
... | @@ -75,10 +78,10 @@ class _PoemDetailPageState extends State<PoemDetailPage> { | ... | @@ -75,10 +78,10 @@ class _PoemDetailPageState extends State<PoemDetailPage> { |
75 | crossAxisAlignment: CrossAxisAlignment.start, | 78 | crossAxisAlignment: CrossAxisAlignment.start, |
76 | children: [ | 79 | children: [ |
77 | Container( | 80 | Container( |
78 | - margin: const EdgeInsets.symmetric( | 81 | + margin: |
79 | - vertical: 30.0, horizontal: 20.0), | 82 | + EdgeInsets.symmetric(vertical: 30.px, horizontal: 20.px), |
80 | height: MediaQuery.of(context).size.height - | 83 | height: MediaQuery.of(context).size.height - |
81 | - 140 - | 84 | + 140.px - |
82 | widget.poemPanelHeight, | 85 | widget.poemPanelHeight, |
83 | width: double.infinity, | 86 | width: double.infinity, |
84 | decoration: BoxDecoration( | 87 | decoration: BoxDecoration( |
... | @@ -97,7 +100,7 @@ class _PoemDetailPageState extends State<PoemDetailPage> { | ... | @@ -97,7 +100,7 @@ class _PoemDetailPageState extends State<PoemDetailPage> { |
97 | color: Colors.grey.shade200.withOpacity(0.1), | 100 | color: Colors.grey.shade200.withOpacity(0.1), |
98 | ), | 101 | ), |
99 | child: Padding( | 102 | child: Padding( |
100 | - padding: const EdgeInsets.all(10.0), | 103 | + padding: EdgeInsets.all(10.px), |
101 | child: Flex( | 104 | child: Flex( |
102 | direction: Axis.vertical, | 105 | direction: Axis.vertical, |
103 | children: [ | 106 | children: [ |
... | @@ -109,7 +112,9 @@ class _PoemDetailPageState extends State<PoemDetailPage> { | ... | @@ -109,7 +112,9 @@ class _PoemDetailPageState extends State<PoemDetailPage> { |
109 | Expanded( | 112 | Expanded( |
110 | flex: 1, | 113 | flex: 1, |
111 | child: contentSwitch == PoemContentSwitch.audio | 114 | child: contentSwitch == PoemContentSwitch.audio |
112 | - ? const PoemUserAudio() | 115 | + ? PoemUserAudio( |
116 | + poemPanelHeight: widget.poemPanelHeight, | ||
117 | + ) | ||
113 | : const PoemUserComments( | 118 | : const PoemUserComments( |
114 | author: "老魔取西经", | 119 | author: "老魔取西经", |
115 | comments: | 120 | comments: |
... | @@ -124,17 +129,17 @@ class _PoemDetailPageState extends State<PoemDetailPage> { | ... | @@ -124,17 +129,17 @@ class _PoemDetailPageState extends State<PoemDetailPage> { |
124 | mainAxisSize: MainAxisSize.min, | 129 | mainAxisSize: MainAxisSize.min, |
125 | children: [ | 130 | children: [ |
126 | IconButton( | 131 | IconButton( |
127 | - icon: const Icon( | 132 | + icon: Icon( |
128 | Icons.mic_none, | 133 | Icons.mic_none, |
129 | - size: 36.0, | 134 | + size: 36.px, |
130 | ), | 135 | ), |
131 | onPressed: () {}, | 136 | onPressed: () {}, |
132 | ), | 137 | ), |
133 | Gaps.hGap16, | 138 | Gaps.hGap16, |
134 | IconButton( | 139 | IconButton( |
135 | - icon: const Icon( | 140 | + icon: Icon( |
136 | Icons.camera_alt_outlined, | 141 | Icons.camera_alt_outlined, |
137 | - size: 36.0, | 142 | + size: 36.px, |
138 | ), | 143 | ), |
139 | onPressed: () {}, | 144 | onPressed: () {}, |
140 | ) | 145 | ) | ... | ... |
... | @@ -3,11 +3,13 @@ import 'dart:ui'; | ... | @@ -3,11 +3,13 @@ import 'dart:ui'; |
3 | import 'package:flutter/cupertino.dart'; | 3 | import 'package:flutter/cupertino.dart'; |
4 | import 'package:flutter/material.dart'; | 4 | import 'package:flutter/material.dart'; |
5 | import 'package:one_poem/poem/widgets/poem_content.dart'; | 5 | import 'package:one_poem/poem/widgets/poem_content.dart'; |
6 | -import 'package:one_poem/res/resources.dart'; | 6 | +import 'package:one_poem/recorder/widgets/poem_voice_widget.dart'; |
7 | import 'package:one_poem/widgets/bars/home_action_bar.dart'; | 7 | import 'package:one_poem/widgets/bars/home_action_bar.dart'; |
8 | import 'package:one_poem/widgets/bars/home_menu_bar.dart'; | 8 | import 'package:one_poem/widgets/bars/home_menu_bar.dart'; |
9 | import 'package:one_poem/widgets/my_app_bar.dart'; | 9 | import 'package:one_poem/widgets/my_app_bar.dart'; |
10 | 10 | ||
11 | +import 'package:one_poem/extension/int_extension.dart'; | ||
12 | + | ||
11 | class PoemRecordAudioPage extends StatefulWidget { | 13 | class PoemRecordAudioPage extends StatefulWidget { |
12 | @override | 14 | @override |
13 | State<StatefulWidget> createState() => _PoemRecordAudioPageState(); | 15 | State<StatefulWidget> createState() => _PoemRecordAudioPageState(); |
... | @@ -23,6 +25,16 @@ class PoemRecordAudioPage extends StatefulWidget { | ... | @@ -23,6 +25,16 @@ class PoemRecordAudioPage extends StatefulWidget { |
23 | } | 25 | } |
24 | 26 | ||
25 | class _PoemRecordAudioPageState extends State<PoemRecordAudioPage> { | 27 | class _PoemRecordAudioPageState extends State<PoemRecordAudioPage> { |
28 | + startRecord() { | ||
29 | + print("开始录制"); | ||
30 | + } | ||
31 | + | ||
32 | + stopRecord(String path, double audioTimeLength) { | ||
33 | + print("结束束录制"); | ||
34 | + print("音频文件位置" + path); | ||
35 | + print("音频录制时长" + audioTimeLength.toString()); | ||
36 | + } | ||
37 | + | ||
26 | @override | 38 | @override |
27 | Widget build(BuildContext context) { | 39 | Widget build(BuildContext context) { |
28 | const poemStr = | 40 | const poemStr = |
... | @@ -59,10 +71,10 @@ class _PoemRecordAudioPageState extends State<PoemRecordAudioPage> { | ... | @@ -59,10 +71,10 @@ class _PoemRecordAudioPageState extends State<PoemRecordAudioPage> { |
59 | crossAxisAlignment: CrossAxisAlignment.start, | 71 | crossAxisAlignment: CrossAxisAlignment.start, |
60 | children: [ | 72 | children: [ |
61 | Container( | 73 | Container( |
62 | - margin: const EdgeInsets.symmetric( | 74 | + margin: |
63 | - vertical: 30.0, horizontal: 20.0), | 75 | + EdgeInsets.symmetric(vertical: 20.px, horizontal: 20.px), |
64 | height: MediaQuery.of(context).size.height - | 76 | height: MediaQuery.of(context).size.height - |
65 | - 140 - | 77 | + 110.px - |
66 | widget.poemPanelHeight, | 78 | widget.poemPanelHeight, |
67 | width: double.infinity, | 79 | width: double.infinity, |
68 | decoration: BoxDecoration( | 80 | decoration: BoxDecoration( |
... | @@ -81,39 +93,37 @@ class _PoemRecordAudioPageState extends State<PoemRecordAudioPage> { | ... | @@ -81,39 +93,37 @@ class _PoemRecordAudioPageState extends State<PoemRecordAudioPage> { |
81 | color: Colors.grey.shade200.withOpacity(0.1), | 93 | color: Colors.grey.shade200.withOpacity(0.1), |
82 | ), | 94 | ), |
83 | child: Padding( | 95 | child: Padding( |
84 | - padding: const EdgeInsets.all(10.0), | 96 | + padding: EdgeInsets.all(10.px), |
85 | child: Flex( | 97 | child: Flex( |
86 | direction: Axis.vertical, | 98 | direction: Axis.vertical, |
87 | children: [ | 99 | children: [ |
88 | - const PoemContent( | 100 | + PoemContent( |
89 | title: "题破山寺后禅院", | 101 | title: "题破山寺后禅院", |
90 | author: "常建", | 102 | author: "常建", |
91 | poemStr: poemStr, | 103 | poemStr: poemStr, |
92 | - fontSize: 22.0, | 104 | + fontSize: 20.px, |
93 | ), | 105 | ), |
94 | Stack( | 106 | Stack( |
95 | alignment: Alignment.center, | 107 | alignment: Alignment.center, |
96 | children: [ | 108 | children: [ |
97 | Positioned( | 109 | Positioned( |
98 | - left: 10.0, | 110 | + left: 10.px, |
99 | child: IconButton( | 111 | child: IconButton( |
100 | - icon: const Icon( | 112 | + icon: Icon( |
101 | Icons.camera_alt_outlined, | 113 | Icons.camera_alt_outlined, |
102 | - size: 28.0, | 114 | + size: 28.px, |
103 | ), | 115 | ), |
104 | onPressed: () {}, | 116 | onPressed: () {}, |
105 | ), | 117 | ), |
106 | ), | 118 | ), |
107 | SizedBox( | 119 | SizedBox( |
108 | width: double.infinity, | 120 | width: double.infinity, |
109 | - height: 90.0, | 121 | + height: 80.px, |
110 | - child: IconButton( | 122 | + child: PoemVoiceWidget( |
111 | - icon: const Icon( | 123 | + startRecord: startRecord, |
112 | - Icons.mic_none, | 124 | + stopRecord: stopRecord, |
113 | - size: 70.0, | 125 | + // 加入定制化Container的相关属性 |
114 | - color: Colors.green, | 126 | + height: 40.px, |
115 | - ), | ||
116 | - onPressed: () {}, | ||
117 | ), | 127 | ), |
118 | ), | 128 | ), |
119 | ], | 129 | ], | ... | ... |
1 | import 'package:flutter/cupertino.dart'; | 1 | import 'package:flutter/cupertino.dart'; |
2 | import 'package:flutter/material.dart'; | 2 | import 'package:flutter/material.dart'; |
3 | 3 | ||
4 | +import 'package:one_poem/extension/int_extension.dart'; | ||
5 | + | ||
4 | class PoemUserAudio extends StatelessWidget { | 6 | class PoemUserAudio extends StatelessWidget { |
5 | const PoemUserAudio({ | 7 | const PoemUserAudio({ |
6 | Key? key, | 8 | Key? key, |
7 | this.audio, //TODO 传入数据 | 9 | this.audio, //TODO 传入数据 |
8 | this.desc, | 10 | this.desc, |
11 | + this.poemPanelHeight = 0, | ||
9 | }) : super(key: key); | 12 | }) : super(key: key); |
10 | 13 | ||
11 | final List<Map<String, String>>? audio; | 14 | final List<Map<String, String>>? audio; |
12 | final String? desc; | 15 | final String? desc; |
16 | + final int poemPanelHeight; | ||
13 | @override | 17 | @override |
14 | Widget build(BuildContext context) { | 18 | Widget build(BuildContext context) { |
15 | return Container( | 19 | return Container( |
16 | - padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), | 20 | + padding: EdgeInsets.symmetric(vertical: 5.px, horizontal: 10.px), |
17 | width: double.infinity, | 21 | width: double.infinity, |
18 | child: Column( | 22 | child: Column( |
19 | children: <Widget>[ | 23 | children: <Widget>[ |
20 | ListTile( | 24 | ListTile( |
21 | title: Text( | 25 | title: Text( |
22 | desc ?? "一大波用户朗读录制提交了“临境”", | 26 | desc ?? "一大波用户朗读录制提交了“临境”", |
23 | - style: const TextStyle(color: Colors.black54, fontSize: 15.0), | 27 | + style: TextStyle(color: Colors.black54, fontSize: 15.px), |
24 | ), | 28 | ), |
25 | ), | 29 | ), |
26 | SizedBox( | 30 | SizedBox( |
27 | width: double.infinity, | 31 | width: double.infinity, |
28 | - height: 200.0, | 32 | + height: 200.px - poemPanelHeight, |
29 | child: ListView.builder( | 33 | child: ListView.builder( |
30 | itemBuilder: (BuildContext context, int index) { | 34 | itemBuilder: (BuildContext context, int index) { |
31 | return Wrap( | 35 | return Wrap( |
32 | - spacing: 5.0, | 36 | + spacing: 5.px, |
33 | crossAxisAlignment: WrapCrossAlignment.center, | 37 | crossAxisAlignment: WrapCrossAlignment.center, |
34 | - children: const [ | 38 | + children: [ |
35 | Icon( | 39 | Icon( |
36 | Icons.play_circle_outline, | 40 | Icons.play_circle_outline, |
37 | - size: 16.0, | 41 | + size: 16.px, |
38 | color: Colors.black45, | 42 | color: Colors.black45, |
39 | ), | 43 | ), |
40 | - Text( | 44 | + const Text( |
41 | "普通话", | 45 | "普通话", |
42 | style: TextStyle(color: Colors.black45, fontSize: 16.0), | 46 | style: TextStyle(color: Colors.black45, fontSize: 16.0), |
43 | ) | 47 | ) | ... | ... |
lib/recorder/widgets/custom_overlay.dart
0 → 100644
1 | +import 'package:flutter/material.dart'; | ||
2 | + | ||
3 | +class CustomOverlay extends StatelessWidget { | ||
4 | + final Widget? icon; | ||
5 | + final BoxDecoration decoration; | ||
6 | + final double width; | ||
7 | + final double height; | ||
8 | + const CustomOverlay({ | ||
9 | + Key? key, | ||
10 | + this.icon, | ||
11 | + this.decoration = const BoxDecoration( | ||
12 | + color: Color(0xff77797A), | ||
13 | + borderRadius: BorderRadius.all(Radius.circular(20.0)), | ||
14 | + ), | ||
15 | + this.width = 160, | ||
16 | + this.height = 160, | ||
17 | + }) : super(key: key); | ||
18 | + | ||
19 | + @override | ||
20 | + Widget build(BuildContext context) { | ||
21 | + return Positioned( | ||
22 | + top: MediaQuery.of(context).size.height * 0.5 - width / 2, | ||
23 | + left: MediaQuery.of(context).size.width * 0.5 - height / 2, | ||
24 | + child: Material( | ||
25 | + type: MaterialType.transparency, | ||
26 | + child: Center( | ||
27 | + child: Opacity( | ||
28 | + opacity: 0.8, | ||
29 | + child: Container( | ||
30 | + width: width, | ||
31 | + height: height, | ||
32 | + decoration: decoration, | ||
33 | + child: icon, | ||
34 | + ), | ||
35 | + ), | ||
36 | + ), | ||
37 | + ), | ||
38 | + ); | ||
39 | + } | ||
40 | +} |
lib/recorder/widgets/poem_voice_widget.dart
0 → 100644
1 | +import 'dart:async'; | ||
2 | + | ||
3 | +import 'package:flutter/material.dart'; | ||
4 | +import 'package:flutter_plugin_record/flutter_plugin_record.dart'; | ||
5 | +import 'package:flutter_plugin_record/utils/common_toast.dart'; | ||
6 | + | ||
7 | +import 'custom_overlay.dart'; | ||
8 | + | ||
9 | +typedef StartRecord = Future Function(); | ||
10 | +typedef StopRecord = Future Function(); | ||
11 | + | ||
12 | +class PoemVoiceWidget extends StatefulWidget { | ||
13 | + final Function? startRecord; | ||
14 | + final Function? stopRecord; | ||
15 | + final double? height; | ||
16 | + final EdgeInsets? margin; | ||
17 | + final Decoration? decoration; | ||
18 | + | ||
19 | + /// startRecord 开始录制回调 stopRecord回调 | ||
20 | + const PoemVoiceWidget( | ||
21 | + {Key? key, | ||
22 | + this.startRecord, | ||
23 | + this.stopRecord, | ||
24 | + this.height, | ||
25 | + this.decoration, | ||
26 | + this.margin}) | ||
27 | + : super(key: key); | ||
28 | + | ||
29 | + @override | ||
30 | + _PoemVoiceWidgetState createState() => _PoemVoiceWidgetState(); | ||
31 | +} | ||
32 | + | ||
33 | +class _PoemVoiceWidgetState extends State<PoemVoiceWidget> { | ||
34 | + // 倒计时总时长 | ||
35 | + final int _countTotal = 12; | ||
36 | + double starty = 0.0; | ||
37 | + double offset = 0.0; | ||
38 | + bool isUp = false; | ||
39 | + String textShow = "按住说话"; | ||
40 | + String toastShow = "手指上滑,取消发送"; | ||
41 | + String voiceIco = "images/voice_volume_1.png"; | ||
42 | + | ||
43 | + ///默认隐藏状态 | ||
44 | + bool voiceState = true; | ||
45 | + FlutterPluginRecord? recordPlugin; | ||
46 | + Timer? _timer; | ||
47 | + int _count = 0; | ||
48 | + OverlayEntry? overlayEntry; | ||
49 | + | ||
50 | + String audioFilePath = ""; | ||
51 | + | ||
52 | + @override | ||
53 | + void initState() { | ||
54 | + super.initState(); | ||
55 | + recordPlugin = FlutterPluginRecord(); | ||
56 | + | ||
57 | + _init(); | ||
58 | + | ||
59 | + ///初始化方法的监听 | ||
60 | + recordPlugin?.responseFromInit.listen((data) { | ||
61 | + // if (data) { | ||
62 | + // print("初始化成功"); | ||
63 | + // } else { | ||
64 | + // print("初始化失败"); | ||
65 | + // } | ||
66 | + }); | ||
67 | + | ||
68 | + /// 开始录制或结束录制的监听 | ||
69 | + recordPlugin?.response.listen((data) { | ||
70 | + if (data.msg == "onStop") { | ||
71 | + ///结束录制时会返回录制文件的地址方便上传服务器 | ||
72 | + // print("onStop " + data.path!); | ||
73 | + if (widget.stopRecord != null) { | ||
74 | + audioFilePath = data.path!; | ||
75 | + widget.stopRecord!(data.path, data.audioTimeLength); | ||
76 | + } | ||
77 | + } else if (data.msg == "onStart") { | ||
78 | + if (widget.startRecord != null) widget.startRecord!(); | ||
79 | + } | ||
80 | + }); | ||
81 | + | ||
82 | + ///录制过程监听录制的声音的大小 方便做语音动画显示图片的样式 | ||
83 | + recordPlugin!.responseFromAmplitude.listen((data) { | ||
84 | + var voiceData = double.parse(data.msg ?? ''); | ||
85 | + setState(() { | ||
86 | + if (voiceData > 0 && voiceData < 0.1) { | ||
87 | + voiceIco = "images/voice_volume_2.png"; | ||
88 | + } else if (voiceData > 0.2 && voiceData < 0.3) { | ||
89 | + voiceIco = "images/voice_volume_3.png"; | ||
90 | + } else if (voiceData > 0.3 && voiceData < 0.4) { | ||
91 | + voiceIco = "images/voice_volume_4.png"; | ||
92 | + } else if (voiceData > 0.4 && voiceData < 0.5) { | ||
93 | + voiceIco = "images/voice_volume_5.png"; | ||
94 | + } else if (voiceData > 0.5 && voiceData < 0.6) { | ||
95 | + voiceIco = "images/voice_volume_6.png"; | ||
96 | + } else if (voiceData > 0.6 && voiceData < 0.7) { | ||
97 | + voiceIco = "images/voice_volume_7.png"; | ||
98 | + } else if (voiceData > 0.7 && voiceData < 1) { | ||
99 | + voiceIco = "images/voice_volume_7.png"; | ||
100 | + } else { | ||
101 | + voiceIco = "images/voice_volume_1.png"; | ||
102 | + } | ||
103 | + if (overlayEntry != null) { | ||
104 | + overlayEntry!.markNeedsBuild(); | ||
105 | + } | ||
106 | + }); | ||
107 | + | ||
108 | + // print("振幅大小 " + voiceData.toString() + " " + voiceIco); | ||
109 | + }); | ||
110 | + } | ||
111 | + | ||
112 | + ///显示录音悬浮布局 | ||
113 | + buildOverLayView(BuildContext context) { | ||
114 | + if (overlayEntry == null) { | ||
115 | + overlayEntry = OverlayEntry(builder: (content) { | ||
116 | + return CustomOverlay( | ||
117 | + icon: Column( | ||
118 | + children: <Widget>[ | ||
119 | + Container( | ||
120 | + margin: const EdgeInsets.only(top: 10), | ||
121 | + child: _countTotal - _count < 11 | ||
122 | + ? Center( | ||
123 | + child: Padding( | ||
124 | + padding: const EdgeInsets.only(bottom: 15.0), | ||
125 | + child: Text( | ||
126 | + (_countTotal - _count).toString(), | ||
127 | + style: const TextStyle( | ||
128 | + fontSize: 70.0, | ||
129 | + color: Colors.white, | ||
130 | + ), | ||
131 | + ), | ||
132 | + ), | ||
133 | + ) | ||
134 | + : Image.asset( | ||
135 | + voiceIco, | ||
136 | + width: 100, | ||
137 | + height: 100, | ||
138 | + package: 'flutter_plugin_record', | ||
139 | + ), | ||
140 | + ), | ||
141 | + Text( | ||
142 | + toastShow, | ||
143 | + style: const TextStyle( | ||
144 | + fontStyle: FontStyle.normal, | ||
145 | + color: Colors.white, | ||
146 | + fontSize: 14, | ||
147 | + ), | ||
148 | + ) | ||
149 | + ], | ||
150 | + ), | ||
151 | + ); | ||
152 | + }); | ||
153 | + Overlay.of(context)!.insert(overlayEntry!); | ||
154 | + } | ||
155 | + } | ||
156 | + | ||
157 | + showVoiceView() { | ||
158 | + setState(() { | ||
159 | + textShow = "松开结束"; | ||
160 | + voiceState = false; | ||
161 | + }); | ||
162 | + | ||
163 | + ///显示录音悬浮布局 | ||
164 | + buildOverLayView(context); | ||
165 | + | ||
166 | + start(); | ||
167 | + } | ||
168 | + | ||
169 | + hideVoiceView() { | ||
170 | + if (_timer!.isActive) { | ||
171 | + if (_count < 1) { | ||
172 | + CommonToast.showView( | ||
173 | + context: context, | ||
174 | + msg: '说话时间太短', | ||
175 | + icon: const Text( | ||
176 | + '!', | ||
177 | + style: TextStyle(fontSize: 80, color: Colors.white), | ||
178 | + )); | ||
179 | + isUp = true; | ||
180 | + } | ||
181 | + _timer?.cancel(); | ||
182 | + _count = 0; | ||
183 | + } | ||
184 | + | ||
185 | + setState(() { | ||
186 | + textShow = "按住说话"; | ||
187 | + voiceState = true; | ||
188 | + }); | ||
189 | + | ||
190 | + stop(); | ||
191 | + if (overlayEntry != null) { | ||
192 | + overlayEntry?.remove(); | ||
193 | + overlayEntry = null; | ||
194 | + } | ||
195 | + | ||
196 | + // if (isUp) { | ||
197 | + // print("取消发送"); | ||
198 | + // } else { | ||
199 | + // print("进行发送"); | ||
200 | + // } | ||
201 | + } | ||
202 | + | ||
203 | + moveVoiceView() { | ||
204 | + setState(() { | ||
205 | + isUp = starty - offset > 100 ? true : false; | ||
206 | + if (isUp) { | ||
207 | + textShow = "松开手指,取消发送"; | ||
208 | + toastShow = textShow; | ||
209 | + } else { | ||
210 | + textShow = "松开结束"; | ||
211 | + toastShow = "手指上滑,取消发送"; | ||
212 | + } | ||
213 | + }); | ||
214 | + } | ||
215 | + | ||
216 | + ///初始化语音录制的方法 | ||
217 | + void _init() async { | ||
218 | + recordPlugin?.initRecordMp3(); | ||
219 | + } | ||
220 | + | ||
221 | + ///开始语音录制的方法 | ||
222 | + void start() async { | ||
223 | + recordPlugin?.start(); | ||
224 | + } | ||
225 | + | ||
226 | + ///停止语音录制的方法 | ||
227 | + void stop() { | ||
228 | + recordPlugin?.stop(); | ||
229 | + } | ||
230 | + | ||
231 | + @override | ||
232 | + Widget build(BuildContext context) { | ||
233 | + return Padding( | ||
234 | + padding: const EdgeInsets.only(right: 10.0), | ||
235 | + child: Row( | ||
236 | + crossAxisAlignment: CrossAxisAlignment.center, | ||
237 | + mainAxisAlignment: MainAxisAlignment.end, | ||
238 | + children: [ | ||
239 | + GestureDetector( | ||
240 | + onLongPressStart: (details) { | ||
241 | + starty = details.globalPosition.dy; | ||
242 | + _timer = Timer.periodic(const Duration(milliseconds: 1000), (t) { | ||
243 | + _count++; | ||
244 | + if (_count == _countTotal) { | ||
245 | + hideVoiceView(); | ||
246 | + } | ||
247 | + }); | ||
248 | + showVoiceView(); | ||
249 | + }, | ||
250 | + onLongPressEnd: (details) { | ||
251 | + hideVoiceView(); | ||
252 | + }, | ||
253 | + onLongPressMoveUpdate: (details) { | ||
254 | + offset = details.globalPosition.dy; | ||
255 | + moveVoiceView(); | ||
256 | + }, | ||
257 | + child: Container( | ||
258 | + height: widget.height ?? 60, | ||
259 | + margin: widget.margin ?? const EdgeInsets.fromLTRB(50, 0, 50, 20), | ||
260 | + child: const Icon( | ||
261 | + Icons.mic_none, | ||
262 | + size: 70.0, | ||
263 | + color: Colors.green, | ||
264 | + ), | ||
265 | + ), | ||
266 | + ), | ||
267 | + IconButton( | ||
268 | + icon: const Icon( | ||
269 | + Icons.play_circle_outline, | ||
270 | + size: 28.0, | ||
271 | + ), | ||
272 | + onPressed: () { | ||
273 | + print("######:" + audioFilePath); | ||
274 | + recordPlugin!.playByPath(audioFilePath, "file"); | ||
275 | + }, | ||
276 | + ), | ||
277 | + ], | ||
278 | + ), | ||
279 | + ); | ||
280 | + } | ||
281 | + | ||
282 | + @override | ||
283 | + void dispose() { | ||
284 | + recordPlugin?.dispose(); | ||
285 | + _timer?.cancel(); | ||
286 | + super.dispose(); | ||
287 | + } | ||
288 | +} |
... | @@ -366,6 +366,13 @@ packages: | ... | @@ -366,6 +366,13 @@ packages: |
366 | url: "https://pub.dartlang.org" | 366 | url: "https://pub.dartlang.org" |
367 | source: hosted | 367 | source: hosted |
368 | version: "2.0.5" | 368 | version: "2.0.5" |
369 | + flutter_plugin_record: | ||
370 | + dependency: "direct main" | ||
371 | + description: | ||
372 | + name: flutter_plugin_record | ||
373 | + url: "https://pub.dartlang.org" | ||
374 | + source: hosted | ||
375 | + version: "1.0.1" | ||
369 | flutter_slidable: | 376 | flutter_slidable: |
370 | dependency: "direct main" | 377 | dependency: "direct main" |
371 | description: | 378 | description: | ... | ... |
... | @@ -91,6 +91,7 @@ dependencies: | ... | @@ -91,6 +91,7 @@ dependencies: |
91 | flutter_spinkit: ^5.0.0 | 91 | flutter_spinkit: ^5.0.0 |
92 | 92 | ||
93 | json_annotation: ^4.4.0 | 93 | json_annotation: ^4.4.0 |
94 | + flutter_plugin_record: ^1.0.1 | ||
94 | 95 | ||
95 | dependency_overrides: | 96 | dependency_overrides: |
96 | decimal: 1.5.0 | 97 | decimal: 1.5.0 | ... | ... |
-
Please register or login to post a comment