183 lines
5.0 KiB
Dart
183 lines
5.0 KiB
Dart
import 'dart:async';
|
|
import 'dart:ui';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:sheetless/edit_bottom_sheet.dart';
|
|
import 'package:sheetless/storage_helper.dart';
|
|
import 'package:sheetless/upload_queue.dart';
|
|
|
|
class Sheet {
|
|
final String uuid;
|
|
String name;
|
|
String composerUuid;
|
|
String composerName;
|
|
|
|
Sheet({
|
|
required this.uuid,
|
|
required this.name,
|
|
required this.composerUuid,
|
|
required this.composerName,
|
|
});
|
|
|
|
// Factory constructor for creating a Sheet from JSON
|
|
factory Sheet.fromJson(Map<String, dynamic> json) {
|
|
return Sheet(
|
|
uuid: json['uuid'],
|
|
name: json['sheet_name'],
|
|
composerUuid: json['composer_uuid'],
|
|
composerName: json['composer_name'],
|
|
);
|
|
}
|
|
}
|
|
|
|
class SheetsWidget extends StatefulWidget {
|
|
final List<Sheet> sheets;
|
|
final ValueSetter<Sheet> onSheetOpenRequest;
|
|
|
|
const SheetsWidget({
|
|
super.key,
|
|
required this.sheets,
|
|
required this.onSheetOpenRequest,
|
|
});
|
|
|
|
@override
|
|
State<SheetsWidget> createState() => _SheetsWidgetState();
|
|
}
|
|
|
|
class _SheetsWidgetState extends State<SheetsWidget> {
|
|
late List<Sheet> filteredSheets;
|
|
final StorageHelper storageHelper = StorageHelper();
|
|
final TextEditingController _searchController = TextEditingController();
|
|
Timer? _debounce;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
filteredSheets = widget.sheets;
|
|
_searchController.addListener(_onSearchChanged);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_searchController.removeListener(_onSearchChanged);
|
|
_searchController.dispose();
|
|
_debounce?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
void _onSearchChanged() {
|
|
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
|
|
|
_debounce = Timer(const Duration(milliseconds: 500), () {
|
|
_filterSheets();
|
|
});
|
|
}
|
|
|
|
void _filterSheets() {
|
|
setState(() {
|
|
String query = _searchController.text.toLowerCase().trim();
|
|
List<String> terms = query.split(RegExp(r'\s+')); // Split by whitespace
|
|
|
|
filteredSheets = widget.sheets.where((sheet) {
|
|
String name = sheet.name.toLowerCase();
|
|
String composer = sheet.composerName.toLowerCase();
|
|
|
|
// Each term must be found in either the name or composer
|
|
return terms.every(
|
|
(term) => name.contains(term) || composer.contains(term),
|
|
);
|
|
}).toList();
|
|
});
|
|
}
|
|
|
|
void _clearSearch() {
|
|
_searchController.clear();
|
|
}
|
|
|
|
void _openEditSheet(BuildContext context, Sheet sheet) {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
builder: (_) => EditItemBottomSheet(
|
|
sheet: sheet,
|
|
onSheetEdited: (String newName, String newComposer) {
|
|
if (newName != sheet.name) {
|
|
storageHelper.writeChange(
|
|
Change(
|
|
type: ChangeType.sheetNameChange,
|
|
sheetUuid: sheet.uuid,
|
|
value: newName,
|
|
),
|
|
);
|
|
}
|
|
if (newComposer != sheet.composerName) {
|
|
storageHelper.writeChange(
|
|
Change(
|
|
type: ChangeType.composerNameChange,
|
|
sheetUuid: sheet.uuid,
|
|
value: newComposer,
|
|
),
|
|
);
|
|
}
|
|
setState(() {
|
|
sheet.name = newName;
|
|
sheet.composerName = newComposer;
|
|
});
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: TextField(
|
|
controller: _searchController,
|
|
decoration: InputDecoration(
|
|
hintText: 'Search...',
|
|
prefixIcon: const Icon(Icons.search),
|
|
suffixIcon: _searchController.text.isNotEmpty
|
|
? IconButton(
|
|
icon: const Icon(Icons.clear),
|
|
onPressed: _clearSearch,
|
|
)
|
|
: null,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8.0),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
// Fixes scroll on web
|
|
child: ScrollConfiguration(
|
|
behavior: ScrollConfiguration.of(context).copyWith(
|
|
dragDevices: {PointerDeviceKind.touch, PointerDeviceKind.mouse},
|
|
),
|
|
child: ListView.builder(
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
itemCount: filteredSheets.length,
|
|
itemBuilder: (context, index) {
|
|
var sheet = filteredSheets[index];
|
|
return ListTile(
|
|
title: Text(sheet.name),
|
|
subtitle: Text(sheet.composerName),
|
|
onTap: () => setState(() {
|
|
widget.onSheetOpenRequest(sheet);
|
|
widget.sheets.remove(sheet);
|
|
widget.sheets.insert(0, sheet);
|
|
}),
|
|
onLongPress: () => _openEditSheet(context, sheet),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|