Skip to content

Commit

Permalink
Add mixed search page. Fixed #7
Browse files Browse the repository at this point in the history
  • Loading branch information
guojiex committed Mar 12, 2019
1 parent 2c92fd0 commit eeb2352
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 33 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ samples, guidance on mobile development, and a full API reference.

### watch demo on youtube.

[![demo_on_youtube](https://img.youtube.com/vi/80D_cu3ex1g/hqdefault.jpg)](https://youtu.be/80D_cu3ex1g)
[![demo_on_youtube](https://img.youtube.com/vi/80D_cu3ex1g/hqdefault.jpg)](https://youtu.be/wkUx-FkA9LA)
49 changes: 32 additions & 17 deletions lib/custom_search_search_delegate.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_app_cse/search_data_source.dart';
import 'ui/web_search_result_page.dart';

import 'ui/mix_search_result_page.dart';
import 'ui/web_search_result_page.dart';
import 'ui/no_result_card.dart';
import 'ui/web_search_result_card.dart';
import 'ui/image_search_result_card.dart';
Expand All @@ -19,30 +20,30 @@ import 'shared_constant.dart';
class CustomSearchSearchDelegate extends SearchDelegate<SearchResult> {
SearchDataSource dataSource;
AutoCompleteDataSource autoCompleteDataSource;
SearchType searchType;
SearchResultPageType searchResultPageType;

CustomSearchSearchDelegate(
{this.dataSource,
this.autoCompleteDataSource =
const CommonEnglishWordAutoCompleteDataSource(),
this.searchType = SearchType.web});
this.searchResultPageType = SearchResultPageType.web});

CustomSearchSearchDelegate.imageSearch(
{this.dataSource,
this.autoCompleteDataSource =
const CommonEnglishWordAutoCompleteDataSource(),
this.searchType = SearchType.image});
this.searchResultPageType = SearchResultPageType.image});

CustomSearchSearchDelegate.fakeStaticSource() {
this.dataSource = FakeSearchDataSource();
this.searchType = SearchType.web;
this.searchResultPageType = SearchResultPageType.web;
this.autoCompleteDataSource =
const CommonEnglishWordAutoCompleteDataSource();
}

CustomSearchSearchDelegate.fakeStaticSourceImageSearch() {
this.dataSource = FakeSearchDataSource();
this.searchType = SearchType.image;
this.searchResultPageType = SearchResultPageType.image;
this.autoCompleteDataSource =
const CommonEnglishWordAutoCompleteDataSource();
}
Expand Down Expand Up @@ -117,8 +118,8 @@ class CustomSearchSearchDelegate extends SearchDelegate<SearchResult> {
if (searchResults.searchResults.isEmpty) {
return noResultCardInContainer(context);
}
switch (this.searchType) {
case SearchType.image:
switch (this.searchResultPageType) {
case SearchResultPageType.image:
return GridView.count(
crossAxisCount: 1,
mainAxisSpacing: 4.0,
Expand All @@ -142,7 +143,7 @@ class CustomSearchSearchDelegate extends SearchDelegate<SearchResult> {
searchResult: searchResults.searchResults[index]);
}));

case SearchType.web:
case SearchResultPageType.web:
return ListView.builder(
itemCount: searchResults.searchResults.length + 2,
itemBuilder: (BuildContext context, int index) {
Expand Down Expand Up @@ -188,27 +189,37 @@ class CustomSearchSearchDelegate extends SearchDelegate<SearchResult> {
return this.buildResultsFromQuery(
context,
SearchQuery(query, dataSource.cx,
searchType: this.searchType == SearchType.web ? null : 'image'));
searchType: this.searchResultPageType == SearchResultPageType.image
? 'image'
: null));
}
}

/// A SearchDelegate that will render the result page as infinite scroll view.
class CustomSearchInfiniteSearchDelegate extends CustomSearchSearchDelegate {
CustomSearchInfiniteSearchDelegate({dataSource,
autoCompleteDataSource = const CommonEnglishWordAutoCompleteDataSource(),
searchType = SearchType.web})
searchType = SearchResultPageType.web})
: super(
dataSource: dataSource,
autoCompleteDataSource: autoCompleteDataSource,
searchType: searchType);
searchResultPageType: searchType);

CustomSearchInfiniteSearchDelegate.imageSearch({dataSource,
autoCompleteDataSource = const CommonEnglishWordAutoCompleteDataSource(),
searchType = SearchType.image})
searchType = SearchResultPageType.image})
: super.imageSearch(
dataSource: dataSource,
autoCompleteDataSource: autoCompleteDataSource,
searchType: searchType);
searchResultPageType: searchType);

CustomSearchInfiniteSearchDelegate.mixSearch({dataSource,
autoCompleteDataSource = const CommonEnglishWordAutoCompleteDataSource(),
searchType = SearchResultPageType.mix})
: super(
dataSource: dataSource,
autoCompleteDataSource: autoCompleteDataSource,
searchResultPageType: searchType);

CustomSearchInfiniteSearchDelegate.fakeStaticSource()
: super.fakeStaticSource();
Expand All @@ -217,6 +228,7 @@ class CustomSearchInfiniteSearchDelegate extends CustomSearchSearchDelegate {
: super.fakeStaticSourceImageSearch();

Widget _buildSearchResultPage(BuildContext context, SearchQuery searchQuery) {
print(searchQuery);
return FutureBuilder(
future: dataSource.search(searchQuery),
builder: (context, snapshot) {
Expand All @@ -233,13 +245,16 @@ class CustomSearchInfiniteSearchDelegate extends CustomSearchSearchDelegate {
if (snapshot.data.searchResults.isEmpty) {
return noResultCardInContainer(context);
}
switch (this.searchType) {
case SearchType.image:
switch (this.searchResultPageType) {
case SearchResultPageType.image:
return ImageSearchResultPage(
dataSource, snapshot.data, searchQuery);
case SearchType.web:
case SearchResultPageType.web:
return WebSearchResultPage(
dataSource, snapshot.data, searchQuery);
case SearchResultPageType.mix:
return MixSearchResultPage(
dataSource, snapshot.data, searchQuery);
}
}
},
Expand Down
2 changes: 2 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class SearchDemoApp extends StatelessWidget {
CustomSearchDemoPage(CustomSearchDemoType.imageSearch),
'/promotionwebsearch': (context) =>
CustomSearchDemoPage(CustomSearchDemoType.promotionWebSearch),
'/mixsearch': (context) =>
CustomSearchDemoPage(CustomSearchDemoType.mixSearch),
},
title: 'Custom Search Engine Flutter Demo');
}
Expand Down
6 changes: 4 additions & 2 deletions lib/search_data_source.dart
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,12 @@ class SearchResults {
SearchResults.empty();

SearchResults(customsearch.Search search) {
if (int.parse(search.searchInformation.totalResults) == 0) {
return;
}
var results = new List<SearchResult>();
search.items.forEach(
(item) => results.add(SearchResult.escapeLineBreakInSnippet(item)));
(item) => results.add(SearchResult.escapeLineBreakInSnippet(item)));
// Deduplicate search result.
this.searchResults = Set<SearchResult>.from(results).toList();
if (search.context.facets != null) {
Expand All @@ -144,7 +147,6 @@ class SearchResults {
String toString() {
return 'SearchResults{searchResults: $searchResults, promotions: $promotions, refinements: $refinements, nextPage: $nextPage}';
}

}

/// A wrapper class for search request, to make caching search request possible.
Expand Down
1 change: 1 addition & 0 deletions lib/shared_constant.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
enum SearchType { web, image }
enum SearchResultPageType { web, image, mix }
enum WebSearchLayout { simple, CSE }
enum PaginationTabType { nextPage, previousPage }
29 changes: 26 additions & 3 deletions lib/ui/custom_search_demo_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ enum CustomSearchDemoType {

/// Search for image.
imageSearch,

promotionWebSearch,

// Search for image or web.
mixSearch,
}

class CustomSearchDemoPage extends StatefulWidget {
Expand All @@ -37,6 +41,8 @@ class CustomSearchDemoPage extends StatefulWidget {
return _CustomSearchDemoPageState.customImageSearch();
case CustomSearchDemoType.promotionWebSearch:
return _CustomSearchDemoPageState.customPromotionWebSearch();
case CustomSearchDemoType.mixSearch:
return _CustomSearchDemoPageState.customMixSearch();
default:
return null;
}
Expand Down Expand Up @@ -79,7 +85,8 @@ class _CustomSearchDemoPageState extends State<CustomSearchDemoPage> {
otherRoutes = [
Tuple2<String, String>('Custom Web Search Demo', '/websearch'),
Tuple2<String, String>(
'Custom Web Search Promotion Demo', '/promotionwebsearch')
'Custom Web Search Promotion Demo', '/promotionwebsearch'),
Tuple2<String, String>('Custom Mix Search Demo', '/mixsearch')
];
}

Expand All @@ -92,7 +99,8 @@ class _CustomSearchDemoPageState extends State<CustomSearchDemoPage> {
otherRoutes = [
Tuple2<String, String>('Custom Image Search Demo', '/imagesearch'),
Tuple2<String, String>(
'Custom Web Search Promotion Demo', '/promotionwebsearch')
'Custom Web Search Promotion Demo', '/promotionwebsearch'),
Tuple2<String, String>('Custom Mix Search Demo', '/mixsearch')
];
}

Expand All @@ -104,7 +112,22 @@ class _CustomSearchDemoPageState extends State<CustomSearchDemoPage> {
this.hintText = 'Google Custom Web Search with Promotion';
otherRoutes = [
Tuple2<String, String>('Custom Web Search Demo', '/websearch'),
Tuple2<String, String>('Custom Image Search Demo', '/imagesearch')
Tuple2<String, String>('Custom Image Search Demo', '/imagesearch'),
Tuple2<String, String>('Custom Mix Search Demo', '/mixsearch')
];
}

_CustomSearchDemoPageState.customMixSearch() {
// Pokemon db with refinement.
this.delegate = new CustomSearchInfiniteSearchDelegate.mixSearch(
dataSource: CustomSearchDataSource(
cx: '013098254965507895640:g-r0nurxf2g', apiKey: APIKEY));
this.hintText = 'Google Custom Mix Search';
otherRoutes = [
Tuple2<String, String>('Custom Web Search Demo', '/websearch'),
Tuple2<String, String>('Custom Image Search Demo', '/imagesearch'),
Tuple2<String, String>(
'Custom Web Search Promotion Demo', '/promotionwebsearch')
];
}

Expand Down
2 changes: 1 addition & 1 deletion lib/ui/image_search_result_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:http/http.dart' as http;
import 'dart:typed_data';

/// Result card for iamge search. Will try to load image. If failed, fall back to
/// Result card for image search. Will try to load image. If failed, fall back to
/// try thumbnail image.
class ImageSearchResultCard extends StatelessWidget {
ImageSearchResultCard({@required this.searchResult});
Expand Down
2 changes: 1 addition & 1 deletion lib/ui/image_search_result_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class _ImageSearchResultPageState extends State<ImageSearchResultPage>
currentSearchResults = null;
this.currentSearchResults = null;
this._refinementTabs.clear();
this.currentResultLength = 0;
_tabController?.dispose();
super.dispose();
}
Expand Down Expand Up @@ -120,7 +121,6 @@ class _ImageSearchResultPageState extends State<ImageSearchResultPage>
.firstWhere((element) => element.label == tab.text);
final query =
widget.searchQuery.q + " " + currentRefinement.labelWithOp;
print(query);
return FutureBuilder(
future: widget.dataSource
.search(widget.searchQuery.copyWith(q: query)),
Expand Down
91 changes: 91 additions & 0 deletions lib/ui/mix_search_result_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import 'package:flutter/material.dart';

import '../search_data_source.dart';
import 'web_search_result_page.dart';
import 'image_search_result_page.dart';
import 'loading_progress_indicator.dart';

class MixSearchResultPage extends StatefulWidget {
final SearchDataSource dataSource;
final SearchResults initialSearchResult;
final SearchQuery searchQuery;

MixSearchResultPage(
this.dataSource, this.initialSearchResult, this.searchQuery);

@override
State createState() => new _MixSearchResultPageState(initialSearchResult);
}

class _MixSearchResultPageState extends State<MixSearchResultPage>
with SingleTickerProviderStateMixin {
final SearchResults initialSearchResult;
List<Tab> _searchTypeTabs = new List<Tab>();
TabController _tabController;

_MixSearchResultPageState(this.initialSearchResult) {
_searchTypeTabs.add(Tab(text: 'Web'));
_searchTypeTabs.add(Tab(text: 'Image'));
_tabController = TabController(vsync: this, length: _searchTypeTabs.length);
}

@override
void dispose() {
this._searchTypeTabs.clear();
_tabController?.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: new PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight - 8),
child: new Container(
color: Colors.blue,
child: new SafeArea(
child: Column(
children: <Widget>[
new TabBar(
isScrollable: true,
controller: _tabController,
tabs: _searchTypeTabs,
),
],
),
),
),
),
body: TabBarView(
controller: _tabController,
children: _searchTypeTabs.map((Tab tab) {
if (tab.text == 'Web') {
return WebSearchResultPage(widget.dataSource,
widget.initialSearchResult, widget.searchQuery);
} else {
final imageQuery =
widget.searchQuery.copyWith(searchType: 'image');
return FutureBuilder<SearchResults>(
future: widget.dataSource.search(imageQuery),
builder: (BuildContext context,
AsyncSnapshot<SearchResults> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Press button to start.');
case ConnectionState.active:
case ConnectionState.waiting:
return loadingProgressIndicator(context);
case ConnectionState.done:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
return ImageSearchResultPage(
widget.dataSource, snapshot.data, imageQuery);
}
return null; // unreachable
},
);
}
}).toList(),
));
}
}
15 changes: 7 additions & 8 deletions lib/ui/web_search_result_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ class WebSearchResultCard extends StatelessWidget {
final ThemeData theme = Theme.of(context);
bool haveThumbnail = this.searchResult.result.pagemap != null &&
(this.searchResult.result.pagemap['thumbnail'] != null ||
this.searchResult.result.pagemap['cse_thumbnail'] != null);
this.searchResult.result.pagemap['cse_thumbnail'] != null ||
this.searchResult.result.pagemap['cse_image'] != null);
if (!haveThumbnail) {
return Container(
padding: const EdgeInsets.only(
Expand All @@ -50,19 +51,17 @@ class WebSearchResultCard extends StatelessWidget {
)),
);
} else {
var imageLink = this.searchResult.result.pagemap['thumbnail'] != null
? this.searchResult.result.pagemap['thumbnail'][0]['src']
: this.searchResult.result.pagemap['cse_thumbnail'][0]['src'];
imageLink ??= this.searchResult.result.pagemap['cse_image'][0]['src'];
return Container(
padding: const EdgeInsets.only(
left: 15.0,
bottom: 8.0,
),
child: new Row(children: [
Expanded(
flex: 1,
child: Image.network(
this.searchResult.result.pagemap['thumbnail'] != null
? this.searchResult.result.pagemap['thumbnail'][0]['src']
: this.searchResult.result.pagemap['cse_thumbnail'][0]
['src'])),
Expanded(flex: 1, child: Image.network(imageLink)),
Expanded(
flex: 4,
child: Container(
Expand Down

0 comments on commit eeb2352

Please sign in to comment.