Skip to content

Instantly share code, notes, and snippets.

@rodydavis
Created August 14, 2018 14:50
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rodydavis/17efd68e7109c3008979ea337368da91 to your computer and use it in GitHub Desktop.
Save rodydavis/17efd68e7109c3008979ea337368da91 to your computer and use it in GitHub Desktop.
Very Fast Search for Flutter with Custom Object
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