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

[Widget] Add Material Search Widget #385

Merged
merged 1 commit into from
Nov 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 284 additions & 0 deletions Runtime/material/search.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
using System;
using System.Collections.Generic;
using RSG;
using Unity.UIWidgets.animation;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.service;
using Unity.UIWidgets.widgets;
using UnityEngine;
using Color = Unity.UIWidgets.ui.Color;

namespace Unity.UIWidgets.material {
public class SearchUtils {
public static IPromise<object> showSearch(
BuildContext context,
SearchDelegate del,
string query = ""
) {
D.assert(del != null);
D.assert(context != null);

del.query = query ?? del.query;
del._currentBody = _SearchBody.suggestions;
return Navigator.of(context).push(new _SearchPageRoute(
del: del
));
}
}

public abstract class SearchDelegate {
public abstract Widget buildSuggestions(BuildContext context);
public abstract Widget buildResults(BuildContext context);
public abstract Widget buildLeading(BuildContext context);
public abstract List<Widget> buildActions(BuildContext context);

public virtual ThemeData appBarTheme(BuildContext context) {
D.assert(context != null);
ThemeData theme = Theme.of(context);
D.assert(theme != null);
return theme.copyWith(
primaryColor: Colors.white,
primaryIconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
primaryColorBrightness: Brightness.light,
primaryTextTheme: theme.textTheme
);
}

public virtual string query {
get { return this._queryTextController.text; }
set {
D.assert(this.query != null);
this._queryTextController.text = value;
}
}

public virtual void showResults(BuildContext context) {
this._focusNode.unfocus();
this._currentBody = _SearchBody.results;
}

public virtual void showSuggestions(BuildContext context) {
FocusScope.of(context).requestFocus(this._focusNode);
this._currentBody = _SearchBody.suggestions;
}

public virtual void close(BuildContext context, object result) {
this._currentBody = null;
this._focusNode.unfocus();
var state = Navigator.of(context);
state.popUntil((Route route) => route == this._route);
state.pop(result);
}


public virtual Animation<float> transitionAnimation {
get { return this._proxyAnimation; }
}

readonly internal FocusNode _focusNode = new FocusNode();

readonly internal TextEditingController _queryTextController = new TextEditingController();

readonly internal ProxyAnimation _proxyAnimation = new ProxyAnimation(Animations.kAlwaysDismissedAnimation);

readonly internal ValueNotifier<_SearchBody?> _currentBodyNotifier = new ValueNotifier<_SearchBody?>(null);

internal _SearchBody? _currentBody {
get { return this._currentBodyNotifier.value; }
set { this._currentBodyNotifier.value = value; }
}

internal _SearchPageRoute _route;
}

enum _SearchBody {
suggestions,
results
}

class _SearchPageRoute : PageRoute {
public _SearchPageRoute(SearchDelegate del) {
D.assert(del != null);
D.assert(del._route == null,
() => $"The {this.del.GetType()} instance is currently used by another active " +
"search. Please close that search by calling close() on the SearchDelegate " +
"before openening another search with the same delegate instance."
);
this.del = del;
this.del._route = this;
}

public readonly SearchDelegate del;

public override Color barrierColor {
get { return null; }
}

public override TimeSpan transitionDuration {
get { return new TimeSpan(0, 0, 0, 0, 300); }
}

public override bool maintainState {
get { return false; }
}

public override Widget buildTransitions(
BuildContext context,
Animation<float> animation,
Animation<float> secondaryAnimation,
Widget child
) {
return new FadeTransition(
opacity: animation,
child: child
);
}

public override Animation<float> createAnimation() {
Animation<float> animation = base.createAnimation();
this.del._proxyAnimation.parent = animation;
return animation;
}

public override Widget buildPage(
BuildContext context,
Animation<float> animation,
Animation<float> secondaryAnimation
) {
return new _SearchPage(
del: this.del,
animation: animation
);
}

protected internal override void didComplete(object result) {
base.didComplete(result);
D.assert(this.del._route == this);
this.del._route = null;
this.del._currentBody = null;
}
}

class _SearchPage : StatefulWidget {
public _SearchPage(
SearchDelegate del,
Animation<float> animation
) {
this.del = del;
this.animation = animation;
}

public readonly SearchDelegate del;

public readonly Animation<float> animation;

public override State createState() {
return new _SearchPageState();
}
}

class _SearchPageState : State<_SearchPage> {
public override void initState() {
base.initState();
this.queryTextController.addListener(this._onQueryChanged);
this.widget.animation.addStatusListener(this._onAnimationStatusChanged);
this.widget.del._currentBodyNotifier.addListener(this._onSearchBodyChanged);
this.widget.del._focusNode.addListener(this._onFocusChanged);
}

public override void dispose() {
base.dispose();
this.queryTextController.removeListener(this._onQueryChanged);
this.widget.animation.removeStatusListener(this._onAnimationStatusChanged);
this.widget.del._currentBodyNotifier.removeListener(this._onSearchBodyChanged);
this.widget.del._focusNode.removeListener(this._onFocusChanged);
}

void _onAnimationStatusChanged(AnimationStatus status) {
if (status != AnimationStatus.completed) {
return;
}

this.widget.animation.removeStatusListener(this._onAnimationStatusChanged);
if (this.widget.del._currentBody == _SearchBody.suggestions) {
FocusScope.of(this.context).requestFocus(this.widget.del._focusNode);
}
}

void _onFocusChanged() {
if (this.widget.del._focusNode.hasFocus && this.widget.del._currentBody != _SearchBody.suggestions) {
this.widget.del.showSuggestions(this.context);
}
}

void _onQueryChanged() {
this.setState(() => { });
}

void _onSearchBodyChanged() {
this.setState(() => { });
}

public override Widget build(BuildContext context) {
MaterialD.debugCheckHasMaterialLocalizations(context);

ThemeData theme = this.widget.del.appBarTheme(context);
string searchFieldLabel = MaterialLocalizations.of(context).searchFieldLabel;
Widget body = null;
switch (this.widget.del._currentBody) {
case _SearchBody.suggestions:
body = new KeyedSubtree(
key: new ValueKey<_SearchBody>(_SearchBody.suggestions),
child: this.widget.del.buildSuggestions(context)
);
break;
case _SearchBody.results:
body = new KeyedSubtree(
key: new ValueKey<_SearchBody>(_SearchBody.results),
child: this.widget.del.buildResults(context)
);
break;
}

string routeName;
switch (Theme.of(this.context).platform) {
case RuntimePlatform.IPhonePlayer:
routeName = "";
break;
case RuntimePlatform.Android:
routeName = searchFieldLabel;
break;
}

return new Scaffold(
appBar: new AppBar(
backgroundColor: theme.primaryColor,
iconTheme: theme.primaryIconTheme,
textTheme: theme.primaryTextTheme,
brightness: theme.primaryColorBrightness,
leading: this.widget.del.buildLeading(context),
title: new TextField(
controller: this.queryTextController,
focusNode: this.widget.del._focusNode,
style: theme.textTheme.title,
textInputAction: TextInputAction.search,
onSubmitted: (string _) => { this.widget.del.showResults(context); },
decoration: new InputDecoration(
border: InputBorder.none,
hintText: searchFieldLabel
)
),
actions: this.widget.del.buildActions(context)
),
body: new AnimatedSwitcher(
duration: new TimeSpan(0, 0, 0, 0, 300),
child: body
)
);
}

TextEditingController queryTextController {
get { return this.widget.del._queryTextController; }
}
}
}
11 changes: 11 additions & 0 deletions Runtime/material/search.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Runtime/widgets/pages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ namespace Unity.UIWidgets.widgets {
public abstract class PageRoute : ModalRoute {
public readonly bool fullscreenDialog;

public PageRoute() {}

public PageRoute(RouteSettings settings, bool fullscreenDialog = false) : base(settings) {
this.fullscreenDialog = fullscreenDialog;
}
Expand Down
5 changes: 3 additions & 2 deletions Runtime/widgets/routes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,9 @@ public override Widget build(BuildContext context) {
}

public abstract class ModalRoute : LocalHistoryRouteTransitionRoute {
protected ModalRoute(RouteSettings settings) : base(settings) {
}

protected ModalRoute() {}
protected ModalRoute(RouteSettings settings) : base(settings) { }

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

Expand Down