1
+ using System . Collections . Generic ;
2
+ using System . Linq ;
3
+ using Unity . UIWidgets . foundation ;
4
+ using Unity . UIWidgets . painting ;
5
+ using Unity . UIWidgets . rendering ;
6
+ using Unity . UIWidgets . ui ;
7
+ using Unity . UIWidgets . widgets ;
8
+
9
+ namespace Unity . UIWidgets . cupertino {
10
+ public class CupertinoTabScaffold : StatefulWidget {
11
+ public CupertinoTabScaffold (
12
+ Key key = null ,
13
+ CupertinoTabBar tabBar = null ,
14
+ IndexedWidgetBuilder tabBuilder = null ,
15
+ Color backgroundColor = null ,
16
+ bool resizeToAvoidBottomInset = true
17
+ ) : base ( key : key ) {
18
+ D . assert ( tabBar != null ) ;
19
+ D . assert ( tabBuilder != null ) ;
20
+ this . tabBar = tabBar ;
21
+ this . tabBuilder = tabBuilder ;
22
+ this . backgroundColor = backgroundColor ;
23
+ this . resizeToAvoidBottomInset = resizeToAvoidBottomInset ;
24
+ }
25
+
26
+
27
+ public readonly CupertinoTabBar tabBar ;
28
+
29
+ public readonly IndexedWidgetBuilder tabBuilder ;
30
+
31
+ public readonly Color backgroundColor ;
32
+
33
+ public readonly bool resizeToAvoidBottomInset ;
34
+
35
+ public override State createState ( ) {
36
+ return new _CupertinoTabScaffoldState ( ) ;
37
+ }
38
+ }
39
+
40
+ class _CupertinoTabScaffoldState : State < CupertinoTabScaffold > {
41
+ int _currentPage ;
42
+
43
+ public override void initState ( ) {
44
+ base . initState ( ) ;
45
+ this . _currentPage = this . widget . tabBar . currentIndex ;
46
+ }
47
+
48
+ public override void didUpdateWidget ( StatefulWidget _oldWidget ) {
49
+ CupertinoTabScaffold oldWidget = _oldWidget as CupertinoTabScaffold ;
50
+ base . didUpdateWidget ( oldWidget ) ;
51
+ if ( this . _currentPage >= this . widget . tabBar . items . Count ) {
52
+ this . _currentPage = this . widget . tabBar . items . Count - 1 ;
53
+ D . assert ( this . _currentPage >= 0 ,
54
+ ( ) => "CupertinoTabBar is expected to keep at least 2 tabs after updating"
55
+ ) ;
56
+ }
57
+
58
+ if ( this . widget . tabBar . currentIndex != oldWidget . tabBar . currentIndex ) {
59
+ this . _currentPage = this . widget . tabBar . currentIndex ;
60
+ }
61
+ }
62
+
63
+ public override Widget build ( BuildContext context ) {
64
+ List < Widget > stacked = new List < Widget > { } ;
65
+
66
+ MediaQueryData existingMediaQuery = MediaQuery . of ( context ) ;
67
+ MediaQueryData newMediaQuery = MediaQuery . of ( context ) ;
68
+
69
+ Widget content = new _TabSwitchingView (
70
+ currentTabIndex : this . _currentPage ,
71
+ tabNumber : this . widget . tabBar . items . Count ,
72
+ tabBuilder : this . widget . tabBuilder
73
+ ) ;
74
+ EdgeInsets contentPadding = EdgeInsets . zero ;
75
+
76
+ if ( this . widget . resizeToAvoidBottomInset ) {
77
+ newMediaQuery = newMediaQuery . removeViewInsets ( removeBottom : true ) ;
78
+ contentPadding = EdgeInsets . only ( bottom : existingMediaQuery . viewInsets . bottom ) ;
79
+ }
80
+
81
+ if ( this . widget . tabBar != null &&
82
+ ( ! this . widget . resizeToAvoidBottomInset ||
83
+ this . widget . tabBar . preferredSize . height > existingMediaQuery . viewInsets . bottom ) ) {
84
+ float bottomPadding = this . widget . tabBar . preferredSize . height + existingMediaQuery . padding . bottom ;
85
+
86
+ if ( this . widget . tabBar . opaque ( context ) ) {
87
+ contentPadding = EdgeInsets . only ( bottom : bottomPadding ) ;
88
+ }
89
+ else {
90
+ newMediaQuery = newMediaQuery . copyWith (
91
+ padding : newMediaQuery . padding . copyWith (
92
+ bottom : bottomPadding
93
+ )
94
+ ) ;
95
+ }
96
+ }
97
+
98
+ content = new MediaQuery (
99
+ data : newMediaQuery ,
100
+ child : new Padding (
101
+ padding : contentPadding ,
102
+ child : content
103
+ )
104
+ ) ;
105
+
106
+ stacked . Add ( content ) ;
107
+
108
+ if ( this . widget . tabBar != null ) {
109
+ stacked . Add ( new Align (
110
+ alignment : Alignment . bottomCenter ,
111
+ child : this . widget . tabBar . copyWith (
112
+ currentIndex : this . _currentPage ,
113
+ onTap : ( int newIndex ) => {
114
+ this . setState ( ( ) => { this . _currentPage = newIndex ; } ) ;
115
+ if ( this . widget . tabBar . onTap != null ) {
116
+ this . widget . tabBar . onTap ( newIndex ) ;
117
+ }
118
+ }
119
+ )
120
+ ) ) ;
121
+ }
122
+
123
+ return new DecoratedBox (
124
+ decoration : new BoxDecoration (
125
+ color : this . widget . backgroundColor ?? CupertinoTheme . of ( context ) . scaffoldBackgroundColor
126
+ ) ,
127
+ child : new Stack (
128
+ children : stacked
129
+ )
130
+ ) ;
131
+ }
132
+ }
133
+
134
+ class _TabSwitchingView : StatefulWidget {
135
+ public _TabSwitchingView (
136
+ int currentTabIndex ,
137
+ int tabNumber ,
138
+ IndexedWidgetBuilder tabBuilder
139
+ ) {
140
+ D . assert ( currentTabIndex != null ) ;
141
+ D . assert ( tabNumber != null && tabNumber > 0 ) ;
142
+ D . assert ( tabBuilder != null ) ;
143
+ }
144
+
145
+ public readonly int currentTabIndex ;
146
+ public readonly int tabNumber ;
147
+ public readonly IndexedWidgetBuilder tabBuilder ;
148
+
149
+ public override State createState ( ) {
150
+ return new _TabSwitchingViewState ( ) ;
151
+ }
152
+ }
153
+
154
+ class _TabSwitchingViewState : State < _TabSwitchingView > {
155
+ List < Widget > tabs ;
156
+ List < FocusScopeNode > tabFocusNodes ;
157
+
158
+ public override void initState ( ) {
159
+ base . initState ( ) ;
160
+ this . tabs = new List < Widget > ( this . widget . tabNumber ) ;
161
+ this . tabFocusNodes = Enumerable . Repeat ( new FocusScopeNode ( ) , this . widget . tabNumber ) . ToList ( ) ;
162
+ }
163
+
164
+ public override void didChangeDependencies ( ) {
165
+ base . didChangeDependencies ( ) ;
166
+ this . _focusActiveTab ( ) ;
167
+ }
168
+
169
+ public override void didUpdateWidget ( StatefulWidget _oldWidget ) {
170
+ _TabSwitchingView oldWidget = _oldWidget as _TabSwitchingView ;
171
+ base . didUpdateWidget ( oldWidget ) ;
172
+ this . _focusActiveTab ( ) ;
173
+ }
174
+
175
+ void _focusActiveTab ( ) {
176
+ FocusScope . of ( this . context ) . setFirstFocus ( this . tabFocusNodes [ this . widget . currentTabIndex ] ) ;
177
+ }
178
+
179
+ public override void dispose ( ) {
180
+ foreach ( FocusScopeNode focusScopeNode in this . tabFocusNodes ) {
181
+ focusScopeNode . detach ( ) ;
182
+ }
183
+
184
+ base . dispose ( ) ;
185
+ }
186
+
187
+ public override Widget build ( BuildContext context ) {
188
+ List < Widget > children = new List < Widget > ( ) ;
189
+ for ( int index = 0 ; index < this . widget . tabNumber ; index ++ ) {
190
+ bool active = index == this . widget . currentTabIndex ;
191
+
192
+ if ( active || this . tabs [ index ] != null ) {
193
+ this . tabs [ index ] = this . widget . tabBuilder ( context , index ) ;
194
+ }
195
+
196
+ children . Add ( new Offstage (
197
+ offstage : ! active ,
198
+ child : new TickerMode (
199
+ enabled : active ,
200
+ child : new FocusScope (
201
+ node : this . tabFocusNodes [ index ] ,
202
+ child : this . tabs [ index ] ?? new Container ( )
203
+ )
204
+ )
205
+ ) ) ;
206
+ }
207
+
208
+ return new Stack (
209
+ fit : StackFit . expand ,
210
+ children : children
211
+ ) ;
212
+ }
213
+ }
214
+ }
0 commit comments