Skip to content
5 changes: 4 additions & 1 deletion lib/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ List<String> connectWithUs = [
'Developers'
];
String softwareLicenses = 'Software Licenses';

String tryDifferentSearchSuggestion = 'Try a different search term';
String noInstrumentsFoundMessage = 'No instruments found';
String searchInstrumentsHint = 'Search instruments...';
String instrumentsTitle = 'Instruments';
String faqTitle = 'FAQs';
String launchError = 'Could not launch';
String Q = 'Q:';
Expand Down
100 changes: 77 additions & 23 deletions lib/view/instruments_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class InstrumentsScreen extends StatefulWidget {
}

class _InstrumentsScreenState extends State<InstrumentsScreen> {
List<int> _filteredIndices = <int>[];

void _onItemTapped(int index) {
switch (index) {
case 0:
Expand Down Expand Up @@ -78,50 +80,102 @@ class _InstrumentsScreenState extends State<InstrumentsScreen> {
}
}

void _filterInstruments(String query) {
setState(() {
if (query.isEmpty) {
_filteredIndices =
List<int>.generate(instrumentHeadings.length, (index) => index);
} else {
_filteredIndices = List.generate(instrumentHeadings.length, (i) => i)
.where((i) => instrumentHeadings[i]
.toLowerCase()
.contains(query.toLowerCase()))
.toList();
}
});
}

@override
void initState() {
super.initState();
_filteredIndices =
List<int>.generate(instrumentHeadings.length, (index) => index);
WidgetsBinding.instance.addPostFrameCallback((_) {
_setOrientation();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
});
super.initState();
Permission.microphone.request();
}

void _setOrientation() {
SystemChrome.setPreferredOrientations([
SystemChrome.setPreferredOrientations(<DeviceOrientation>[
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
}

@override
void dispose() {
super.dispose();
}

@override
Widget build(BuildContext context) {
return MainScaffold(
index: 0,
title: 'Instruments',
title: instrumentsTitle,
showSearch: true,
onSearchChanged: _filterInstruments,
searchHint: searchInstrumentsHint,
body: SafeArea(
child: ScrollConfiguration(
behavior: const ScrollBehavior(),
child: ListView.builder(
itemCount: instrumentHeadings.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () => _onItemTapped(index),
child: ApplicationsListItem(
heading: instrumentHeadings[index],
description: instrumentDesc[index],
instrumentIcon: instrumentIcons[index],
child: _filteredIndices.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.search_off,
size: 64,
color: Theme.of(context)
.colorScheme
.onSurface
.withAlpha(128),
),
const SizedBox(height: 16),
Text(
noInstrumentsFoundMessage,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context)
.colorScheme
.onSurface
.withAlpha(179),
),
),
const SizedBox(height: 8),
Text(
tryDifferentSearchSuggestion,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context)
.colorScheme
.onSurface
.withAlpha(128),
),
),
],
),
)
: ScrollConfiguration(
behavior: const ScrollBehavior(),
child: ListView.builder(
itemCount: _filteredIndices.length,
itemBuilder: (context, index) {
final int originalIndex = _filteredIndices[index];
return GestureDetector(
onTap: () => _onItemTapped(originalIndex),
child: ApplicationsListItem(
heading: instrumentHeadings[originalIndex],
description: instrumentDesc[originalIndex],
instrumentIcon: instrumentIcons[originalIndex],
),
);
},
),
);
},
),
),
),
),
);
}
Expand Down
193 changes: 150 additions & 43 deletions lib/view/widgets/main_scaffold_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,70 @@ class MainScaffold extends StatefulWidget {
final String icUsbDisconnected = 'assets/icons/ic_usb_disconnected.png';
final String icUsbConnected = 'assets/icons/ic_usb_connected.png';
final String icWiFiConnected = 'assets/icons/ic_wifi_connected.png';
final bool showSearch;
final Function(String)? onSearchChanged;
final String? searchHint;

const MainScaffold(
{super.key,
required this.body,
required this.title,
this.scaffoldKey,
this.actions,
required this.index});
const MainScaffold({
super.key,
required this.body,
required this.title,
this.scaffoldKey,
this.actions,
required this.index,
this.showSearch = false,
this.onSearchChanged,
this.searchHint,
});

@override
State<StatefulWidget> createState() => _MainScaffoldState();
}

class _MainScaffoldState extends State<MainScaffold> {
class _MainScaffoldState extends State<MainScaffold>
with SingleTickerProviderStateMixin {
bool _isSearching = false;
final TextEditingController _searchController = TextEditingController();
late AnimationController _animationController;

@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
}

@override
void dispose() {
_searchController.dispose();
_animationController.dispose();
super.dispose();
}

void _toggleSearch() {
setState(() {
if (_isSearching) {
_isSearching = false;
_animationController.reverse();
_searchController.clear();
if (widget.onSearchChanged != null) {
widget.onSearchChanged!('');
}
} else {
_isSearching = true;
_animationController.forward();
}
});
}

void _onSearchChanged(String query) {
if (widget.onSearchChanged != null) {
widget.onSearchChanged!(query);
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
Expand All @@ -48,43 +98,100 @@ class _MainScaffoldState extends State<MainScaffold> {
);
}),
backgroundColor: const Color(0xFFD32F2F),
title: Text(
key: widget.scaffoldKey,
widget.title,
style: const TextStyle(
color: Colors.white,
fontSize: 18,
),
title: AnimatedSwitcher(
duration: const Duration(milliseconds: 0),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: child,
);
},
child: _isSearching
? TextField(
key: const ValueKey('search_field'),
controller: _searchController,
onChanged: _onSearchChanged,
autofocus: true,
style: const TextStyle(
color: Colors.white,
fontSize: 18,
),
decoration: InputDecoration(
hintText: widget.searchHint,
hintStyle: const TextStyle(
color: Colors.white70,
fontSize: 18,
),
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
contentPadding: EdgeInsets.zero,
),
cursorColor: Colors.white,
)
: Text(
key: ValueKey('title_${widget.title}'),
widget.title,
style: const TextStyle(
color: Colors.white,
fontSize: 18,
),
),
),
actions: [
Consumer<BoardStateProvider>(
builder: (context, provider, _) {
return IconButton(
icon: Image.asset(
provider.pslabIsConnected
? (provider.scienceLabCommon.isWiFiConnected()
? widget.icWiFiConnected
: widget.icUsbConnected)
: widget.icUsbDisconnected,
width: 24,
height: 24,
actions: _isSearching
? [
IconButton(
icon: const Icon(
Icons.clear,
color: Colors.white,
),
onPressed: () {
if (_searchController.text.isNotEmpty) {
_searchController.clear();
_onSearchChanged('');
} else {
_toggleSearch();
}
},
),
onPressed: () {
/**/
},
);
},
),
IconButton(
icon: const Icon(
Icons.more_vert,
color: Colors.white,
),
onPressed: () {
/**/
},
),
],
]
: [
if (widget.showSearch)
IconButton(
icon: const Icon(
Icons.search,
color: Colors.white,
),
onPressed: _toggleSearch,
),
Consumer<BoardStateProvider>(
builder: (context, provider, _) {
return IconButton(
icon: Image.asset(
provider.pslabIsConnected
? (provider.scienceLabCommon.isWiFiConnected()
? widget.icWiFiConnected
: widget.icUsbConnected)
: widget.icUsbDisconnected,
width: 24,
height: 24,
),
onPressed: () {
/**/
},
);
},
),
IconButton(
icon: const Icon(
Icons.more_vert,
color: Colors.white,
),
onPressed: () {
/**/
},
),
],
),
body: widget.body,
drawer: NavDrawer(
Expand Down