Skip to content
This repository was archived by the owner on Apr 29, 2021. It is now read-only.

Commit e30188f

Browse files
committed
[Widget] Add Material Search Widget
1 parent 734986a commit e30188f

File tree

4 files changed

+300
-2
lines changed

4 files changed

+300
-2
lines changed

Runtime/material/search.cs

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using RSG;
4+
using Unity.UIWidgets.animation;
5+
using Unity.UIWidgets.foundation;
6+
using Unity.UIWidgets.service;
7+
using Unity.UIWidgets.widgets;
8+
using UnityEngine;
9+
using Color = Unity.UIWidgets.ui.Color;
10+
11+
namespace Unity.UIWidgets.material {
12+
public class SearchUtils {
13+
public static IPromise<object> showSearch(
14+
BuildContext context,
15+
SearchDelegate del,
16+
string query = ""
17+
) {
18+
D.assert(del != null);
19+
D.assert(context != null);
20+
21+
del.query = query ?? del.query;
22+
del._currentBody = _SearchBody.suggestions;
23+
return Navigator.of(context).push(new _SearchPageRoute(
24+
del: del
25+
));
26+
}
27+
}
28+
29+
public abstract class SearchDelegate {
30+
public abstract Widget buildSuggestions(BuildContext context);
31+
public abstract Widget buildResults(BuildContext context);
32+
public abstract Widget buildLeading(BuildContext context);
33+
public abstract List<Widget> buildActions(BuildContext context);
34+
35+
public virtual ThemeData appBarTheme(BuildContext context) {
36+
D.assert(context != null);
37+
ThemeData theme = Theme.of(context);
38+
D.assert(theme != null);
39+
return theme.copyWith(
40+
primaryColor: Colors.white,
41+
primaryIconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
42+
primaryColorBrightness: Brightness.light,
43+
primaryTextTheme: theme.textTheme
44+
);
45+
}
46+
47+
public virtual string query {
48+
get { return this._queryTextController.text; }
49+
set {
50+
D.assert(this.query != null);
51+
this._queryTextController.text = value;
52+
}
53+
}
54+
55+
public virtual void showResults(BuildContext context) {
56+
this._focusNode.unfocus();
57+
this._currentBody = _SearchBody.results;
58+
}
59+
60+
public virtual void showSuggestions(BuildContext context) {
61+
FocusScope.of(context).requestFocus(this._focusNode);
62+
this._currentBody = _SearchBody.suggestions;
63+
}
64+
65+
public virtual void close(BuildContext context, object result) {
66+
this._currentBody = null;
67+
this._focusNode.unfocus();
68+
var state = Navigator.of(context);
69+
state.popUntil((Route route) => route == this._route);
70+
state.pop(result);
71+
}
72+
73+
74+
public virtual Animation<float> transitionAnimation {
75+
get { return this._proxyAnimation; }
76+
}
77+
78+
readonly internal FocusNode _focusNode = new FocusNode();
79+
80+
readonly internal TextEditingController _queryTextController = new TextEditingController();
81+
82+
readonly internal ProxyAnimation _proxyAnimation = new ProxyAnimation(Animations.kAlwaysDismissedAnimation);
83+
84+
readonly internal ValueNotifier<_SearchBody?> _currentBodyNotifier = new ValueNotifier<_SearchBody?>(null);
85+
86+
internal _SearchBody? _currentBody {
87+
get { return this._currentBodyNotifier.value; }
88+
set { this._currentBodyNotifier.value = value; }
89+
}
90+
91+
internal _SearchPageRoute _route;
92+
}
93+
94+
enum _SearchBody {
95+
suggestions,
96+
results
97+
}
98+
99+
class _SearchPageRoute : PageRoute {
100+
public _SearchPageRoute(SearchDelegate del) {
101+
D.assert(del != null);
102+
D.assert(del._route == null,
103+
() => $"The {this.del.GetType()} instance is currently used by another active " +
104+
"search. Please close that search by calling close() on the SearchDelegate " +
105+
"before openening another search with the same delegate instance."
106+
);
107+
this.del = del;
108+
this.del._route = this;
109+
}
110+
111+
public readonly SearchDelegate del;
112+
113+
public override Color barrierColor {
114+
get { return null; }
115+
}
116+
117+
public override TimeSpan transitionDuration {
118+
get { return new TimeSpan(0, 0, 0, 0, 300); }
119+
}
120+
121+
public override bool maintainState {
122+
get { return false; }
123+
}
124+
125+
public override Widget buildTransitions(
126+
BuildContext context,
127+
Animation<float> animation,
128+
Animation<float> secondaryAnimation,
129+
Widget child
130+
) {
131+
return new FadeTransition(
132+
opacity: animation,
133+
child: child
134+
);
135+
}
136+
137+
public override Animation<float> createAnimation() {
138+
Animation<float> animation = base.createAnimation();
139+
this.del._proxyAnimation.parent = animation;
140+
return animation;
141+
}
142+
143+
public override Widget buildPage(
144+
BuildContext context,
145+
Animation<float> animation,
146+
Animation<float> secondaryAnimation
147+
) {
148+
return new _SearchPage(
149+
del: this.del,
150+
animation: animation
151+
);
152+
}
153+
154+
protected internal override void didComplete(object result) {
155+
base.didComplete(result);
156+
D.assert(this.del._route == this);
157+
this.del._route = null;
158+
this.del._currentBody = null;
159+
}
160+
}
161+
162+
class _SearchPage : StatefulWidget {
163+
public _SearchPage(
164+
SearchDelegate del,
165+
Animation<float> animation
166+
) {
167+
this.del = del;
168+
this.animation = animation;
169+
}
170+
171+
public readonly SearchDelegate del;
172+
173+
public readonly Animation<float> animation;
174+
175+
public override State createState() {
176+
return new _SearchPageState();
177+
}
178+
}
179+
180+
class _SearchPageState : State<_SearchPage> {
181+
public override void initState() {
182+
base.initState();
183+
this.queryTextController.addListener(this._onQueryChanged);
184+
this.widget.animation.addStatusListener(this._onAnimationStatusChanged);
185+
this.widget.del._currentBodyNotifier.addListener(this._onSearchBodyChanged);
186+
this.widget.del._focusNode.addListener(this._onFocusChanged);
187+
}
188+
189+
public override void dispose() {
190+
base.dispose();
191+
this.queryTextController.removeListener(this._onQueryChanged);
192+
this.widget.animation.removeStatusListener(this._onAnimationStatusChanged);
193+
this.widget.del._currentBodyNotifier.removeListener(this._onSearchBodyChanged);
194+
this.widget.del._focusNode.removeListener(this._onFocusChanged);
195+
}
196+
197+
void _onAnimationStatusChanged(AnimationStatus status) {
198+
if (status != AnimationStatus.completed) {
199+
return;
200+
}
201+
202+
this.widget.animation.removeStatusListener(this._onAnimationStatusChanged);
203+
if (this.widget.del._currentBody == _SearchBody.suggestions) {
204+
FocusScope.of(this.context).requestFocus(this.widget.del._focusNode);
205+
}
206+
}
207+
208+
void _onFocusChanged() {
209+
if (this.widget.del._focusNode.hasFocus && this.widget.del._currentBody != _SearchBody.suggestions) {
210+
this.widget.del.showSuggestions(this.context);
211+
}
212+
}
213+
214+
void _onQueryChanged() {
215+
this.setState(() => { });
216+
}
217+
218+
void _onSearchBodyChanged() {
219+
this.setState(() => { });
220+
}
221+
222+
public override Widget build(BuildContext context) {
223+
MaterialD.debugCheckHasMaterialLocalizations(context);
224+
225+
ThemeData theme = this.widget.del.appBarTheme(context);
226+
string searchFieldLabel = MaterialLocalizations.of(context).searchFieldLabel;
227+
Widget body = null;
228+
switch (this.widget.del._currentBody) {
229+
case _SearchBody.suggestions:
230+
body = new KeyedSubtree(
231+
key: new ValueKey<_SearchBody>(_SearchBody.suggestions),
232+
child: this.widget.del.buildSuggestions(context)
233+
);
234+
break;
235+
case _SearchBody.results:
236+
body = new KeyedSubtree(
237+
key: new ValueKey<_SearchBody>(_SearchBody.results),
238+
child: this.widget.del.buildResults(context)
239+
);
240+
break;
241+
}
242+
243+
string routeName;
244+
switch (Theme.of(this.context).platform) {
245+
case RuntimePlatform.IPhonePlayer:
246+
routeName = "";
247+
break;
248+
case RuntimePlatform.Android:
249+
routeName = searchFieldLabel;
250+
break;
251+
}
252+
253+
return new Scaffold(
254+
appBar: new AppBar(
255+
backgroundColor: theme.primaryColor,
256+
iconTheme: theme.primaryIconTheme,
257+
textTheme: theme.primaryTextTheme,
258+
brightness: theme.primaryColorBrightness,
259+
leading: this.widget.del.buildLeading(context),
260+
title: new TextField(
261+
controller: this.queryTextController,
262+
focusNode: this.widget.del._focusNode,
263+
style: theme.textTheme.title,
264+
textInputAction: TextInputAction.search,
265+
onSubmitted: (string _) => { this.widget.del.showResults(context); },
266+
decoration: new InputDecoration(
267+
border: InputBorder.none,
268+
hintText: searchFieldLabel
269+
)
270+
),
271+
actions: this.widget.del.buildActions(context)
272+
),
273+
body: new AnimatedSwitcher(
274+
duration: new TimeSpan(0, 0, 0, 0, 300),
275+
child: body
276+
)
277+
);
278+
}
279+
280+
TextEditingController queryTextController {
281+
get { return this.widget.del._queryTextController; }
282+
}
283+
}
284+
}

Runtime/material/search.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/widgets/pages.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ namespace Unity.UIWidgets.widgets {
77
public abstract class PageRoute : ModalRoute {
88
public readonly bool fullscreenDialog;
99

10+
public PageRoute() {}
11+
1012
public PageRoute(RouteSettings settings, bool fullscreenDialog = false) : base(settings) {
1113
this.fullscreenDialog = fullscreenDialog;
1214
}

Runtime/widgets/routes.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -471,8 +471,9 @@ public override Widget build(BuildContext context) {
471471
}
472472

473473
public abstract class ModalRoute : LocalHistoryRouteTransitionRoute {
474-
protected ModalRoute(RouteSettings settings) : base(settings) {
475-
}
474+
475+
protected ModalRoute() {}
476+
protected ModalRoute(RouteSettings settings) : base(settings) { }
476477

477478
public static Color _kTransparent = new Color(0x00000000);
478479

0 commit comments

Comments
 (0)