Created
August 14, 2018 14:50
-
-
Save rodydavis/17efd68e7109c3008979ea337368da91 to your computer and use it in GitHub Desktop.
Very Fast Search for Flutter with Custom Object
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'package:cloud_firestore/cloud_firestore.dart'; | |
import 'package:date_format/date_format.dart'; | |
import 'package:firebase_analytics/firebase_analytics.dart'; | |
import 'package:firebase_analytics/observer.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:native_widgets/native_widgets.dart'; | |
import 'package:songlist_pro/util/globals.dart' as globals; | |
import 'package:tonic/tonic.dart'; | |
import '../models/song.dart'; | |
// import 'package:material_search/material_search.dart'; | |
class SearchList extends StatefulWidget { | |
SearchList({ | |
Key key, | |
@required this.name, | |
@required this.songs, | |
@required this.firestore, | |
@required this.observer, | |
@required this.analytics, | |
}) : super(key: key); | |
final Firestore firestore; | |
final FirebaseAnalytics analytics; | |
final FirebaseAnalyticsObserver observer; | |
final String name; | |
final Query songs; | |
@override | |
_SearchListState createState() => new _SearchListState(); | |
} | |
class _SearchListState extends State<SearchList> { | |
// Icon actionIcon = new Icon( | |
// Icons.search, | |
// color: Colors.white, | |
// ); | |
final key = new GlobalKey<ScaffoldState>(); | |
final TextEditingController _searchQuery = new TextEditingController(); | |
// List<String> _list; | |
bool isSearching; | |
String _searchText = ""; | |
List<Song> _list; | |
_SearchListState() { | |
_searchQuery.addListener(() { | |
if (_searchQuery.text.isEmpty) { | |
setState(() { | |
isSearching = false; | |
_searchText = ""; | |
}); | |
} else { | |
setState(() { | |
isSearching = true; | |
_searchText = _searchQuery.text; | |
}); | |
} | |
}); | |
} | |
@override | |
void initState() { | |
super.initState(); | |
isSearching = false; | |
_list = List(); | |
_getData().then((data) { | |
try { | |
data.sort((a, b) { | |
// if (a.dateModified != null && b.dateModified != null) | |
// return a.dateModified.compareTo(b.dateModified); | |
// if (a.dateModified != null && b.dateCreated != null) | |
// return a.dateModified.compareTo(b.dateCreated); | |
// if (a.dateCreated != null && b.dateModified != null) | |
// return a.dateCreated.compareTo(b.dateModified); | |
return a.dateCreated.compareTo(b.dateCreated); | |
}); | |
setState(() => _list = data); | |
print("Length: ${_list.length}"); | |
} catch (e) { | |
setState(() => _list = data); | |
} | |
}); | |
} | |
Future<List<Song>> _getData() async { | |
var data = await widget.songs.getDocuments(); | |
List<Song> _items = []; | |
for (var document in data.documents) { | |
Song song = Song.fromSnapshot(document); | |
song.source = widget.name; | |
_items.add(song); | |
} | |
return _items; | |
} | |
@override | |
Widget build(BuildContext context) { | |
return new Scaffold( | |
key: key, | |
appBar: buildBar(context), | |
body: _list == null || _list.length == 0 | |
? NativeLoadingIndicator( | |
text: Text("Loading..."), | |
) | |
: ListView( | |
padding: new EdgeInsets.symmetric(vertical: 8.0), | |
children: isSearching ? _buildSearchList() : _buildList(), | |
), | |
); | |
} | |
List<ChildItem> _buildList() { | |
return _list.map((song) => new ChildItem(song)).toList(); | |
} | |
List<ChildItem> _buildSearchList() { | |
if (_searchText.isEmpty) | |
return _list.map((song) => new ChildItem(song)).toList(); | |
List<Song> _searchList = []; | |
for (int i = 0; i < _list.length; i++) | |
if (isMatch(i, _searchText.toLowerCase().trim())) | |
_searchList.add(_list.elementAt(i)); | |
return _searchList.map((song) => new ChildItem(song)).toList(); | |
} | |
bool isMatch(int index, String search) { | |
var _song = _list.elementAt(index); | |
if (_song.name.toLowerCase().contains(search)) return true; | |
if (_song.number.toLowerCase().contains(search)) return true; | |
if (_song.notation.scale.toLowerCase().contains(search)) return true; | |
if (_song.notation.step.toLowerCase().contains(search)) return true; | |
var _value = int.tryParse(_song.notation.pitch.toString()) ?? null; | |
String _pitch = _value != null | |
? Pitch.fromMidiNumber(_song.notation.pitch).pitchClass.toString() | |
: ""; | |
if (_pitch.toLowerCase().contains(search)) return true; | |
bool _sharp = _pitch.contains("♯") || | |
_song.notation.scale.toLowerCase().contains("♯"); | |
bool _flat = _pitch.contains("♭") || | |
_song.notation.scale.toLowerCase().contains("♭"); | |
if (_sharp && (search.contains("#") || search.contains("sharp"))) | |
return true; | |
if (_flat && (search.contains("b") || search.contains("flat"))) return true; | |
if (_song.source.toLowerCase().contains(search)) return true; | |
if (_song.timeSignature.toLowerCase().contains(search)) return true; | |
if (_song.trebleClef && search.contains("treble")) return true; | |
if (_song.coda && search.contains("coda")) return true; | |
if (_song.chorus && search.contains("chorus")) return true; | |
if (_song.verses.toString().contains(search)) return true; | |
if (_song.notes.toLowerCase().contains(search)) return true; | |
return false; // No Match Found | |
} | |
Widget buildBar(BuildContext context) { | |
return AppBar( | |
elevation: 1.0, | |
// centerTitle: true, | |
title: TextField( | |
controller: _searchQuery, | |
autofocus: true, | |
autocorrect: false, | |
style: new TextStyle( | |
color: globals.isDark ? Colors.white : Colors.black, | |
), | |
decoration: new InputDecoration( | |
// prefixIcon: new Icon(Icons.search, color: Colors.white), | |
hintText: "Search...", | |
hintStyle: | |
new TextStyle(color: globals.isDark ? Colors.white : Colors.grey), | |
), | |
), | |
// foregroundColor: globals.isDark ? Colors.white : Colors.black, | |
backgroundColor: globals.isDark ? null : Colors.white, | |
actions: <Widget>[ | |
IconButton( | |
icon: Icon(Icons.help), | |
onPressed: () => showAlertPopup(context, "Info", "${widget.name}"), | |
) | |
], | |
); | |
} | |
void showAlertPopup(BuildContext context, String title, String detail) { | |
void showDemoDialog<T>({BuildContext context, Widget child}) { | |
showDialog<T>( | |
context: context, | |
barrierDismissible: false, | |
builder: (BuildContext context) => child, | |
); | |
} | |
return showDemoDialog( | |
context: context, | |
child: NativeDialog( | |
title: title, | |
content: detail, | |
actions: <NativeDialogAction>[ | |
NativeDialogAction( | |
text: 'OK', | |
isDestructive: false, | |
onPressed: () { | |
Navigator.pop(context); | |
}), | |
], | |
)); | |
} | |
} | |
class ChildItem extends StatelessWidget { | |
final Song song; | |
ChildItem(this.song); | |
@override | |
Widget build(BuildContext context) { | |
return Container( | |
// color: globals.isDark ? Colors.black : Colors.white, | |
decoration: BoxDecoration( | |
border: Border(bottom: BorderSide(color: Colors.grey[300]))), | |
child: ListTile( | |
leading: new Text(this.song.number), | |
title: this.song.number == null ? null : new Text(this.song.name), | |
subtitle: this.song.notation == null | |
? null | |
: new Text(this.song.notation.toString()), | |
trailing: this.song.dateModified == null && | |
this.song.dateCreated == null | |
? null | |
: new Text(formatDate( | |
this.song.dateModified ?? this.song.dateCreated, [M, ' ', dd])), | |
onTap: () { | |
Navigator.pop(context, song); | |
}, | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment