Commit 95e3448ceddfa0198073048461d7467e875572a3
1 parent
7912b3f7
feat:听音选图/选字
Showing
14 changed files
with
457 additions
and
2 deletions
assets/images/road_bg.png
0 → 100644
353 KB
assets/images/voice.png
0 → 100644
8.3 KB
lib/home/home_page.dart
... | ... | @@ -35,7 +35,9 @@ class _HomePageView extends StatelessWidget { |
35 | 35 | } else { |
36 | 36 | // Navigator.of(AppRouter.context).pushNamed(AppRouteName.topicPic); |
37 | 37 | // Navigator.of(AppRouter.context).pushNamed(AppRouteName.topicWord); |
38 | - Navigator.of(AppRouter.context).pushNamed(AppRouteName.lookVideo); | |
38 | + // Navigator.of(AppRouter.context).pushNamed(AppRouteName.lookVideo); | |
39 | + // Navigator.of(AppRouter.context).pushNamed(AppRouteName.voicePic); | |
40 | + Navigator.of(AppRouter.context).pushNamed(AppRouteName.voiceWord); | |
39 | 41 | } |
40 | 42 | } |
41 | 43 | ... | ... |
lib/practice/voicetopic/voicepicture/bloc/voice_pic_bloc.dart
0 → 100644
1 | +import 'package:flutter/cupertino.dart'; | |
2 | +import 'package:flutter_bloc/flutter_bloc.dart'; | |
3 | + | |
4 | +part 'voice_pic_event.dart'; | |
5 | +part 'voice_pic_state.dart'; | |
6 | + | |
7 | +class VoicePicBloc extends Bloc<VoicePicEvent, VoicePicState> { | |
8 | + final PageController pageController; | |
9 | + | |
10 | + final int modelCount; | |
11 | + | |
12 | + int _currentPage = 0; | |
13 | + | |
14 | + int _selectItem = 0; | |
15 | + | |
16 | + int get currentPage => _currentPage + 1; | |
17 | + | |
18 | + int get selectItem => _selectItem; | |
19 | + VoicePicBloc(this.pageController, this.modelCount) : super(VoicePicInitial()) { | |
20 | + on<CurrentPageIndexChangeEvent>(_pageControllerChange); | |
21 | + on<SelectItemEvent>(_selectItemLoad); | |
22 | + } | |
23 | + | |
24 | + @override | |
25 | + Future<void> close() { | |
26 | + pageController.dispose(); | |
27 | + return super.close(); | |
28 | + } | |
29 | + | |
30 | + void _pageControllerChange(CurrentPageIndexChangeEvent event,Emitter<VoicePicState> emitter) async { | |
31 | + _currentPage = event.pageIndex; | |
32 | + emitter(CurrentPageIndexState()); | |
33 | + } | |
34 | + | |
35 | + void _selectItemLoad(SelectItemEvent event,Emitter<VoicePicState> emitter) async { | |
36 | + _selectItem = event.selectIndex; | |
37 | + emitter(SelectItemChangeState()); | |
38 | + } | |
39 | +} | ... | ... |
lib/practice/voicetopic/voicepicture/bloc/voice_pic_event.dart
0 → 100644
1 | +part of 'voice_pic_bloc.dart'; | |
2 | + | |
3 | +@immutable | |
4 | +abstract class VoicePicEvent {} | |
5 | + | |
6 | +class CurrentPageIndexChangeEvent extends VoicePicEvent { | |
7 | + final int pageIndex; | |
8 | + CurrentPageIndexChangeEvent(this.pageIndex); | |
9 | +} | |
10 | + | |
11 | +class SelectItemEvent extends VoicePicEvent { | |
12 | + final int selectIndex; | |
13 | + SelectItemEvent(this.selectIndex); | |
14 | +} | |
0 | 15 | \ No newline at end of file | ... | ... |
lib/practice/voicetopic/voicepicture/bloc/voice_pic_state.dart
0 → 100644
1 | +part of 'voice_pic_bloc.dart'; | |
2 | + | |
3 | +@immutable | |
4 | +abstract class VoicePicState {} | |
5 | + | |
6 | +class VoicePicInitial extends VoicePicState {} | |
7 | + | |
8 | +class CurrentPageIndexState extends VoicePicState {} | |
9 | + | |
10 | +class SelectItemChangeState extends VoicePicState {} | |
0 | 11 | \ No newline at end of file | ... | ... |
lib/practice/voicetopic/voicepicture/voice_pic_page.dart
0 → 100644
1 | +import 'package:flutter/material.dart'; | |
2 | +import 'package:flutter_bloc/flutter_bloc.dart'; | |
3 | +import 'package:flutter_screenutil/flutter_screenutil.dart'; | |
4 | +import 'package:wow_english/common/extension/string_extension.dart'; | |
5 | +import 'package:wow_english/practice/voicetopic/voicepicture/bloc/voice_pic_bloc.dart'; | |
6 | +import 'package:wow_english/practice/widgets/practice_header_widget.dart'; | |
7 | + | |
8 | +class VoicePicPage extends StatelessWidget { | |
9 | + const VoicePicPage({super.key}); | |
10 | + | |
11 | + @override | |
12 | + Widget build(BuildContext context) { | |
13 | + return BlocProvider( | |
14 | + create: (context) => VoicePicBloc(PageController(),4), | |
15 | + child: _VoicePicPage(), | |
16 | + ); | |
17 | + } | |
18 | +} | |
19 | + | |
20 | +class _VoicePicPage extends StatelessWidget { | |
21 | + @override | |
22 | + Widget build(BuildContext context) { | |
23 | + return BlocListener<VoicePicBloc, VoicePicState>( | |
24 | + listener: (context, state){}, | |
25 | + child: _voicePicView(), | |
26 | + ); | |
27 | + } | |
28 | + | |
29 | + Widget _voicePicView() => BlocBuilder<VoicePicBloc, VoicePicState>( | |
30 | + builder: (context, state){ | |
31 | + return _voicePictureView(); | |
32 | + }); | |
33 | + | |
34 | + Widget _voicePictureView() => BlocBuilder<VoicePicBloc, VoicePicState>( | |
35 | + buildWhen: (_,s) => s is CurrentPageIndexState, | |
36 | + builder: (context,state){ | |
37 | + final bloc = BlocProvider.of<VoicePicBloc>(context); | |
38 | + return Container( | |
39 | + color: Colors.white, | |
40 | + child: Stack( | |
41 | + children: [ | |
42 | + Image.asset( | |
43 | + 'road_bg'.assetPng, | |
44 | + height: double.infinity, | |
45 | + width: double.infinity | |
46 | + ), | |
47 | + Column( | |
48 | + children: [ | |
49 | + PracticeHeaderWidget( | |
50 | + title: '${bloc.currentPage}/8', | |
51 | + onTap: (){Navigator.pop(context);}, | |
52 | + ), | |
53 | + Row( | |
54 | + mainAxisAlignment: MainAxisAlignment.center, | |
55 | + children: [ | |
56 | + Image.asset('voice'.assetPng,height: 33.h,width: 30.w,), | |
57 | + 10.horizontalSpace, | |
58 | + Text( | |
59 | + 'yellow', | |
60 | + style: TextStyle( | |
61 | + fontSize: 20.sp, | |
62 | + color: const Color(0xFF333333) | |
63 | + ) | |
64 | + ) | |
65 | + ], | |
66 | + ), | |
67 | + 26.verticalSpace, | |
68 | + Expanded( | |
69 | + child: PageView.builder( | |
70 | + itemCount: 8, | |
71 | + scrollDirection: Axis.horizontal, | |
72 | + controller: bloc.pageController, | |
73 | + onPageChanged: (int index) { | |
74 | + bloc.add(CurrentPageIndexChangeEvent(index)); | |
75 | + }, | |
76 | + itemBuilder: (BuildContext context,int index){ | |
77 | + return _pageViewItemWidget(); | |
78 | + }), | |
79 | + ) | |
80 | + ], | |
81 | + ) | |
82 | + ], | |
83 | + ), | |
84 | + ); | |
85 | + }); | |
86 | + | |
87 | + Widget _pageViewItemWidget() => BlocBuilder<VoicePicBloc, VoicePicState>( | |
88 | + builder: (context, state){ | |
89 | + final bloc = BlocProvider.of<VoicePicBloc>(context); | |
90 | + return SafeArea( | |
91 | + child: Column( | |
92 | + children: [ | |
93 | + Row( | |
94 | + mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
95 | + children: [ | |
96 | + Offstage( | |
97 | + offstage: (bloc.modelCount < 1), | |
98 | + child: _decodeImageWidget(1), | |
99 | + ), | |
100 | + Offstage( | |
101 | + offstage: (bloc.modelCount < 2), | |
102 | + child: _decodeImageWidget(2), | |
103 | + ), | |
104 | + Offstage( | |
105 | + offstage: (bloc.modelCount < 3), | |
106 | + child: _decodeImageWidget(3), | |
107 | + ), | |
108 | + Offstage( | |
109 | + offstage: (bloc.modelCount < 4), | |
110 | + child: _decodeImageWidget(4), | |
111 | + ) | |
112 | + ], | |
113 | + ) | |
114 | + ], | |
115 | + ), | |
116 | + ); | |
117 | + }); | |
118 | + | |
119 | + Widget _decodeImageWidget(int index) => BlocBuilder<VoicePicBloc, VoicePicState>( | |
120 | + buildWhen: (_, s) => s is SelectItemChangeState, | |
121 | + builder: (context,state){ | |
122 | + final bloc = BlocProvider.of<VoicePicBloc>(context); | |
123 | + return GestureDetector( | |
124 | + onTap: () => bloc.add(SelectItemEvent(index)), | |
125 | + child: Container( | |
126 | + padding: const EdgeInsets.all(4.5), | |
127 | + decoration: BoxDecoration( | |
128 | + color: bloc.selectItem == index?const Color(0xFF00B6F1):Colors.white, | |
129 | + borderRadius: BorderRadius.circular(15), | |
130 | + ), | |
131 | + height: 143.h, | |
132 | + width: 143.w, | |
133 | + child: Container( | |
134 | + decoration: BoxDecoration( | |
135 | + color: Colors.white, | |
136 | + borderRadius: BorderRadius.circular(15), | |
137 | + border: Border.all( | |
138 | + width: 1.0, | |
139 | + color: const Color(0xFF140C10) | |
140 | + ), | |
141 | + image: const DecorationImage( | |
142 | + fit: BoxFit.fitWidth, | |
143 | + image: NetworkImage('https://img1.baidu.com/it/u=3392591833,1640391553&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=714') | |
144 | + ) | |
145 | + ), | |
146 | + ), | |
147 | + ), | |
148 | + ); | |
149 | + }); | |
150 | +} | |
0 | 151 | \ No newline at end of file | ... | ... |
lib/practice/voicetopic/voiceword/bloc/voice_word_bloc.dart
0 → 100644
1 | + | |
2 | +import 'package:flutter/cupertino.dart'; | |
3 | +import 'package:flutter_bloc/flutter_bloc.dart'; | |
4 | + | |
5 | +part 'voice_word_event.dart'; | |
6 | +part 'voice_word_state.dart'; | |
7 | + | |
8 | +class VoiceWordBloc extends Bloc<VoiceWordEvent, VoiceWordState> { | |
9 | + final PageController pageController; | |
10 | + | |
11 | + final int modelCount; | |
12 | + | |
13 | + int _currentPage = 0; | |
14 | + | |
15 | + int _selectItem = 0; | |
16 | + | |
17 | + int get currentPage => _currentPage + 1; | |
18 | + | |
19 | + int get selectItem => _selectItem; | |
20 | + VoiceWordBloc(this.pageController, this.modelCount) : super(VoiceWordInitial()) { | |
21 | + on<CurrentPageIndexChangeEvent>(_pageControllerChange); | |
22 | + on<SelectItemEvent>(_selectItemLoad); | |
23 | + } | |
24 | + | |
25 | + @override | |
26 | + Future<void> close() { | |
27 | + pageController.dispose(); | |
28 | + return super.close(); | |
29 | + } | |
30 | + | |
31 | + void _pageControllerChange(CurrentPageIndexChangeEvent event,Emitter<VoiceWordState> emitter) async { | |
32 | + _currentPage = event.pageIndex; | |
33 | + emitter(CurrentPageIndexState()); | |
34 | + } | |
35 | + | |
36 | + void _selectItemLoad(SelectItemEvent event,Emitter<VoiceWordState> emitter) async { | |
37 | + _selectItem = event.selectIndex; | |
38 | + emitter(SelectItemChangeState()); | |
39 | + } | |
40 | +} | |
41 | + | ... | ... |
lib/practice/voicetopic/voiceword/bloc/voice_word_event.dart
0 → 100644
1 | +part of 'voice_word_bloc.dart'; | |
2 | + | |
3 | +@immutable | |
4 | +abstract class VoiceWordEvent {} | |
5 | + | |
6 | +class CurrentPageIndexChangeEvent extends VoiceWordEvent { | |
7 | + final int pageIndex; | |
8 | + CurrentPageIndexChangeEvent(this.pageIndex); | |
9 | +} | |
10 | + | |
11 | +class SelectItemEvent extends VoiceWordEvent { | |
12 | + final int selectIndex; | |
13 | + SelectItemEvent(this.selectIndex); | |
14 | +} | |
0 | 15 | \ No newline at end of file | ... | ... |
lib/practice/voicetopic/voiceword/bloc/voice_word_state.dart
0 → 100644
lib/practice/voicetopic/voiceword/voice_word_page.dart
0 → 100644
1 | +import 'package:flutter/material.dart'; | |
2 | +import 'package:flutter_bloc/flutter_bloc.dart'; | |
3 | +import 'package:flutter_screenutil/flutter_screenutil.dart'; | |
4 | +import 'package:wow_english/common/extension/string_extension.dart'; | |
5 | +import 'package:wow_english/practice/voicetopic/voiceword/bloc/voice_word_bloc.dart'; | |
6 | +import 'package:wow_english/practice/widgets/practice_header_widget.dart'; | |
7 | + | |
8 | +class VoiceWordPage extends StatelessWidget { | |
9 | + const VoiceWordPage({super.key}); | |
10 | + | |
11 | + | |
12 | + @override | |
13 | + Widget build(BuildContext context) { | |
14 | + return BlocProvider( | |
15 | + create: (context) => VoiceWordBloc(PageController(),4), | |
16 | + child: _VoiceWordPage(), | |
17 | + ); | |
18 | + } | |
19 | +} | |
20 | + | |
21 | +class _VoiceWordPage extends StatelessWidget { | |
22 | + @override | |
23 | + Widget build(BuildContext context) { | |
24 | + return BlocListener<VoiceWordBloc, VoiceWordState>( | |
25 | + listener: (context, state){}, | |
26 | + child: _voiceWorView(), | |
27 | + ); | |
28 | + } | |
29 | + | |
30 | + Widget _voiceWorView() => BlocBuilder<VoiceWordBloc, VoiceWordState>( | |
31 | + builder: (context, state){ | |
32 | + return _voiceWordView(); | |
33 | + }); | |
34 | + | |
35 | + Widget _voiceWordView() => BlocBuilder<VoiceWordBloc, VoiceWordState>( | |
36 | + buildWhen: (_,s) => s is CurrentPageIndexState, | |
37 | + builder: (context,state){ | |
38 | + final bloc = BlocProvider.of<VoiceWordBloc>(context); | |
39 | + return Container( | |
40 | + color: Colors.white, | |
41 | + child: Stack( | |
42 | + children: [ | |
43 | + Image.asset( | |
44 | + 'road_bg'.assetPng, | |
45 | + height: double.infinity, | |
46 | + width: double.infinity | |
47 | + ), | |
48 | + Column( | |
49 | + children: [ | |
50 | + PracticeHeaderWidget( | |
51 | + title: '${bloc.currentPage}/8', | |
52 | + onTap: (){Navigator.pop(context);}, | |
53 | + ), | |
54 | + Image.asset('voice'.assetPng,height: 33.h,width: 30.w,), | |
55 | + 26.verticalSpace, | |
56 | + Expanded( | |
57 | + child: PageView.builder( | |
58 | + itemCount: 8, | |
59 | + scrollDirection: Axis.horizontal, | |
60 | + controller: bloc.pageController, | |
61 | + onPageChanged: (int index) { | |
62 | + bloc.add(CurrentPageIndexChangeEvent(index)); | |
63 | + }, | |
64 | + itemBuilder: (BuildContext context,int index){ | |
65 | + return _pageViewItemWidget(); | |
66 | + }), | |
67 | + ) | |
68 | + ], | |
69 | + ) | |
70 | + ], | |
71 | + ), | |
72 | + ); | |
73 | + }); | |
74 | + | |
75 | + Widget _pageViewItemWidget() => BlocBuilder<VoiceWordBloc, VoiceWordState>( | |
76 | + builder: (context, state){ | |
77 | + final bloc = BlocProvider.of<VoiceWordBloc>(context); | |
78 | + return SafeArea( | |
79 | + child: Column( | |
80 | + children: [ | |
81 | + Row( | |
82 | + mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
83 | + children: [ | |
84 | + Offstage( | |
85 | + offstage: (bloc.modelCount < 1), | |
86 | + child: _decodeImageWidget(1), | |
87 | + ), | |
88 | + Offstage( | |
89 | + offstage: (bloc.modelCount < 2), | |
90 | + child: _decodeImageWidget(2), | |
91 | + ), | |
92 | + Offstage( | |
93 | + offstage: (bloc.modelCount < 3), | |
94 | + child: _decodeImageWidget(3), | |
95 | + ), | |
96 | + Offstage( | |
97 | + offstage: (bloc.modelCount < 4), | |
98 | + child: _decodeImageWidget(4), | |
99 | + ) | |
100 | + ], | |
101 | + ) | |
102 | + ], | |
103 | + ), | |
104 | + ); | |
105 | + }); | |
106 | + | |
107 | + Widget _decodeImageWidget(int index) => BlocBuilder<VoiceWordBloc,VoiceWordState>( | |
108 | + buildWhen: (_, s) => s is SelectItemChangeState, | |
109 | + builder: (context,state){ | |
110 | + final bloc = BlocProvider.of<VoiceWordBloc>(context); | |
111 | + return GestureDetector( | |
112 | + onTap: () => bloc.add(SelectItemEvent(index)), | |
113 | + child: Container( | |
114 | + width: 143.w, | |
115 | + height: 143.h, | |
116 | + padding: EdgeInsets.only(left: 13.w,right: 13.w,top: 13.h,bottom: 13.h), | |
117 | + decoration: BoxDecoration( | |
118 | + color: Colors.white, | |
119 | + borderRadius: BorderRadius.circular(15), | |
120 | + border: Border.all( | |
121 | + width: 1.0, | |
122 | + color: const Color(0xFF140C10) | |
123 | + ), | |
124 | + ), | |
125 | + child: Column( | |
126 | + mainAxisAlignment: MainAxisAlignment.end, | |
127 | + children: [ | |
128 | + Expanded( | |
129 | + child: Container( | |
130 | + alignment: Alignment.center, | |
131 | + child: Text( | |
132 | + 'yellow', | |
133 | + style: TextStyle( | |
134 | + fontSize: 20.sp, | |
135 | + color: const Color(0xFF333333) | |
136 | + ) | |
137 | + ), | |
138 | + ), | |
139 | + ), | |
140 | + Container( | |
141 | + height: 30.h, | |
142 | + width: double.infinity, | |
143 | + decoration: BoxDecoration( | |
144 | + color: bloc.selectItem == index?const Color(0xFF00B6F1):Colors.white, | |
145 | + borderRadius: BorderRadius.circular(15.r), | |
146 | + border: Border.all( | |
147 | + width: 1.5, | |
148 | + color: const Color(0xFF140C10) | |
149 | + ), | |
150 | + ), | |
151 | + alignment: Alignment.center, | |
152 | + child: Image.asset('choose'.assetPng), | |
153 | + ) | |
154 | + ], | |
155 | + ), | |
156 | + ), | |
157 | + ); | |
158 | + }); | |
159 | +} | |
0 | 160 | \ No newline at end of file | ... | ... |
lib/route/route.dart
... | ... | @@ -10,6 +10,8 @@ import 'package:wow_english/login/loginpage/login_page.dart'; |
10 | 10 | import 'package:wow_english/login/setpwd/set_pwd_page.dart'; |
11 | 11 | import 'package:wow_english/practice/chosetopic/topicpicture/topic_picture_page.dart'; |
12 | 12 | import 'package:wow_english/practice/chosetopic/topicword/topic_word_page.dart'; |
13 | +import 'package:wow_english/practice/voicetopic/voicepicture/voice_pic_page.dart'; | |
14 | +import 'package:wow_english/practice/voicetopic/voiceword/voice_word_page.dart'; | |
13 | 15 | import 'package:wow_english/repeatafter/repeat_after_page.dart'; |
14 | 16 | import 'package:wow_english/shop/exchane/exchange_lesson_page.dart'; |
15 | 17 | import 'package:wow_english/shop/exchangelist/exchange_lesson_list_page.dart'; |
... | ... | @@ -34,6 +36,8 @@ class AppRouteName { |
34 | 36 | static const String reAfter = 'reAfter'; |
35 | 37 | static const String topicPic = 'topicPic'; |
36 | 38 | static const String topicWord = 'topicWord'; |
39 | + static const String voicePic = 'voicePic'; | |
40 | + static const String voiceWord = 'voiceWord'; | |
37 | 41 | static const String user = 'user'; |
38 | 42 | static const String lookVideo = 'lookVideo'; |
39 | 43 | static const String tab = '/'; |
... | ... | @@ -76,6 +80,10 @@ class AppRouter { |
76 | 80 | return CupertinoPageRoute(builder: (_) => const TopicPicturePage()); |
77 | 81 | case AppRouteName.topicWord: |
78 | 82 | return CupertinoPageRoute(builder: (_) => const TopicWordPage()); |
83 | + case AppRouteName.voicePic: | |
84 | + return CupertinoPageRoute(builder: (_) => const VoicePicPage()); | |
85 | + case AppRouteName.voiceWord: | |
86 | + return CupertinoPageRoute(builder: (_) => const VoiceWordPage()); | |
79 | 87 | case AppRouteName.lookVideo: |
80 | 88 | return CupertinoPageRoute(builder: (_) => const LookVideoPage()); |
81 | 89 | case AppRouteName.setPwd: | ... | ... |
lib/video/lookvideo/widgets/video_opera_widget.dart
... | ... | @@ -19,6 +19,7 @@ class VideoOperaWidget extends StatefulWidget { |
19 | 19 | this.totalTime = '00:00', |
20 | 20 | this.degree = 0.0, |
21 | 21 | this.actionEvent, |
22 | + this.sliderChangeEvent, | |
22 | 23 | this.isPlay = true |
23 | 24 | }); |
24 | 25 | //当前播放时间 |
... | ... | @@ -28,6 +29,7 @@ class VideoOperaWidget extends StatefulWidget { |
28 | 29 | final double degree; |
29 | 30 | final bool isPlay; |
30 | 31 | final Function(OperationType type)? actionEvent; |
32 | + final Function(double degree)? sliderChangeEvent; | |
31 | 33 | |
32 | 34 | @override |
33 | 35 | State<StatefulWidget> createState() { |
... | ... | @@ -154,6 +156,7 @@ class _VideoOperaWidgetState extends State<VideoOperaWidget> { |
154 | 156 | setState(() { |
155 | 157 | isSlider = false; |
156 | 158 | }); |
159 | + widget.sliderChangeEvent?.call(value); | |
157 | 160 | }, |
158 | 161 | onChanged: (value) { |
159 | 162 | setState(() { | ... | ... |
lib/video/lookvideo/widgets/video_widget.dart
... | ... | @@ -154,6 +154,11 @@ class _VideoWidgetState extends State<VideoWidget> { |
154 | 154 | actionEvent: (OperationType type) { |
155 | 155 | actionType(type); |
156 | 156 | }, |
157 | + sliderChangeEvent: (double degree) { | |
158 | + int totalSecond = _controller!.value.duration.inMinutes.remainder(60)*60+_controller!.value.duration.inSeconds.remainder(60); | |
159 | + int positionSecond = (totalSecond * degree).toInt(); | |
160 | + _controller!.seekTo(Duration(seconds: positionSecond)); | |
161 | + }, | |
157 | 162 | ), |
158 | 163 | ), |
159 | 164 | Offstage( |
... | ... | @@ -171,7 +176,7 @@ class _VideoWidgetState extends State<VideoWidget> { |
171 | 176 | ) |
172 | 177 | ], |
173 | 178 | ): Container( |
174 | - color: Colors.black, | |
179 | + color: Colors.white, | |
175 | 180 | ), |
176 | 181 | ), |
177 | 182 | ); | ... | ... |