mirror of
https://github.com/JGeek00/adguard-home-manager.git
synced 2025-04-19 21:39:16 +00:00
Changed design top items screen
This commit is contained in:
parent
f9149056fd
commit
9eb200f5da
3 changed files with 339 additions and 313 deletions
|
@ -9,6 +9,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/widgets/options_menu.dart';
|
import 'package:adguard_home_manager/widgets/options_menu.dart';
|
||||||
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
import 'package:adguard_home_manager/widgets/custom_list_tile.dart';
|
||||||
|
import 'package:adguard_home_manager/widgets/floating_search_bar.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/models/menu_option.dart';
|
import 'package:adguard_home_manager/models/menu_option.dart';
|
||||||
import 'package:adguard_home_manager/constants/enums.dart';
|
import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
|
@ -16,6 +17,7 @@ import 'package:adguard_home_manager/functions/number_format.dart';
|
||||||
import 'package:adguard_home_manager/providers/status_provider.dart';
|
import 'package:adguard_home_manager/providers/status_provider.dart';
|
||||||
|
|
||||||
enum _SortingOptions { highestToLowest, lowestToHighest }
|
enum _SortingOptions { highestToLowest, lowestToHighest }
|
||||||
|
final GlobalKey _searchButtonKey = GlobalKey();
|
||||||
|
|
||||||
class TopItemsScreen extends StatefulWidget {
|
class TopItemsScreen extends StatefulWidget {
|
||||||
final HomeTopItems type;
|
final HomeTopItems type;
|
||||||
|
@ -47,15 +49,18 @@ class TopItemsScreen extends StatefulWidget {
|
||||||
|
|
||||||
class _TopItemsScreenState extends State<TopItemsScreen> {
|
class _TopItemsScreenState extends State<TopItemsScreen> {
|
||||||
_SortingOptions _sortingOptions = _SortingOptions.highestToLowest;
|
_SortingOptions _sortingOptions = _SortingOptions.highestToLowest;
|
||||||
bool searchActive = false;
|
|
||||||
final TextEditingController searchController = TextEditingController();
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
String? _currentSearchValue = "";
|
||||||
|
|
||||||
List<Map<String, dynamic>> data = [];
|
List<Map<String, dynamic>> data = [];
|
||||||
List<Map<String, dynamic>> screenData = [];
|
List<Map<String, dynamic>> screenData = [];
|
||||||
|
|
||||||
void search(String value) {
|
void search(String value) {
|
||||||
List<Map<String, dynamic>> newValues = widget.data.where((item) => item.keys.toList()[0].contains(value)).toList();
|
List<Map<String, dynamic>> newValues = widget.data.where((item) => item.keys.toList()[0].contains(value)).toList();
|
||||||
setState(() => screenData = newValues);
|
setState(() {
|
||||||
|
screenData = newValues;
|
||||||
|
_currentSearchValue = searchController.text;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -75,118 +80,129 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
|
||||||
final sortedValues = _sortingOptions == _SortingOptions.lowestToHighest
|
final sortedValues = _sortingOptions == _SortingOptions.lowestToHighest
|
||||||
? screenData.reversed.toList()
|
? screenData.reversed.toList()
|
||||||
: screenData.toList();
|
: screenData.toList();
|
||||||
|
|
||||||
|
void showSearchDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => FloatingSearchBar(
|
||||||
|
existingSearchValue: _currentSearchValue,
|
||||||
|
searchButtonRenderBox: _searchButtonKey.currentContext?.findRenderObject() as RenderBox?,
|
||||||
|
onSearchCompleted: (v) {
|
||||||
|
List<Map<String, dynamic>> newValues = widget.data.where((item) => item.keys.toList()[0].contains(v)).toList();
|
||||||
|
setState(() {
|
||||||
|
screenData = newValues;
|
||||||
|
_currentSearchValue = v;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (widget.isFullscreen == true) {
|
if (widget.isFullscreen == true) {
|
||||||
return Dialog.fullscreen(
|
return Scaffold(
|
||||||
child: Scaffold(
|
body: NestedScrollView(
|
||||||
appBar: AppBar(
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
title: searchActive == true
|
SliverOverlapAbsorber(
|
||||||
? Padding(
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
padding: const EdgeInsets.only(bottom: 3),
|
sliver: SliverAppBar.large(
|
||||||
child: TextFormField(
|
title: Text(widget.title),
|
||||||
controller: searchController,
|
actions: [
|
||||||
onChanged: search,
|
IconButton(
|
||||||
decoration: InputDecoration(
|
key: _searchButtonKey,
|
||||||
hintText: AppLocalizations.of(context)!.search,
|
onPressed: showSearchDialog,
|
||||||
hintStyle: const TextStyle(
|
icon: const Icon(Icons.search_rounded),
|
||||||
fontWeight: FontWeight.normal,
|
tooltip: AppLocalizations.of(context)!.search,
|
||||||
fontSize: 18
|
),
|
||||||
|
PopupMenuButton(
|
||||||
|
icon: const Icon(Icons.sort_rounded),
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () => setState(() => _sortingOptions = _SortingOptions.highestToLowest),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.arrow_downward_rounded),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(AppLocalizations.of(context)!.fromHighestToLowest)
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Icon(
|
||||||
|
_sortingOptions == _SortingOptions.highestToLowest
|
||||||
|
? Icons.radio_button_checked_rounded
|
||||||
|
: Icons.radio_button_unchecked_rounded,
|
||||||
|
color: _sortingOptions == _SortingOptions.highestToLowest
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
),
|
),
|
||||||
border: InputBorder.none,
|
PopupMenuItem(
|
||||||
),
|
onTap: () => setState(() => _sortingOptions = _SortingOptions.lowestToHighest),
|
||||||
style: const TextStyle(
|
child: Row(
|
||||||
fontWeight: FontWeight.normal,
|
children: [
|
||||||
fontSize: 18
|
const Icon(Icons.arrow_upward_rounded),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
autofocus: true,
|
Expanded(
|
||||||
),
|
child: Text(AppLocalizations.of(context)!.fromLowestToHighest)
|
||||||
)
|
),
|
||||||
: Text(widget.title),
|
const SizedBox(width: 16),
|
||||||
leading: searchActive == true ?
|
Icon(
|
||||||
IconButton(
|
_sortingOptions == _SortingOptions.lowestToHighest
|
||||||
onPressed: () => setState(() {
|
? Icons.radio_button_checked_rounded
|
||||||
searchActive = false;
|
: Icons.radio_button_unchecked_rounded,
|
||||||
searchController.text = '';
|
color: _sortingOptions == _SortingOptions.lowestToHighest
|
||||||
screenData = data;
|
? Theme.of(context).colorScheme.primary
|
||||||
}),
|
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
icon: const Icon(Icons.arrow_back),
|
)
|
||||||
tooltip: AppLocalizations.of(context)!.exitSearch,
|
],
|
||||||
) : null,
|
|
||||||
actions: [
|
|
||||||
if (searchActive == false) IconButton(
|
|
||||||
onPressed: () => setState(() => searchActive = true),
|
|
||||||
icon: const Icon(Icons.search),
|
|
||||||
tooltip: AppLocalizations.of(context)!.search,
|
|
||||||
),
|
|
||||||
if (searchActive == true) IconButton(
|
|
||||||
onPressed: () => setState(() {
|
|
||||||
searchController.text = '';
|
|
||||||
screenData = data;
|
|
||||||
}),
|
|
||||||
icon: const Icon(Icons.clear_rounded),
|
|
||||||
tooltip: AppLocalizations.of(context)!.clearSearch,
|
|
||||||
),
|
|
||||||
PopupMenuButton(
|
|
||||||
icon: const Icon(Icons.sort_rounded),
|
|
||||||
itemBuilder: (context) => [
|
|
||||||
PopupMenuItem(
|
|
||||||
onTap: () => setState(() => _sortingOptions = _SortingOptions.highestToLowest),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.arrow_downward_rounded),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: Text(AppLocalizations.of(context)!.fromHighestToLowest)
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Icon(
|
|
||||||
_sortingOptions == _SortingOptions.highestToLowest
|
|
||||||
? Icons.radio_button_checked_rounded
|
|
||||||
: Icons.radio_button_unchecked_rounded,
|
|
||||||
color: _sortingOptions == _SortingOptions.highestToLowest
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
)
|
)
|
||||||
],
|
),
|
||||||
)
|
],
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
const SizedBox(width: 8)
|
||||||
onTap: () => setState(() => _sortingOptions = _SortingOptions.lowestToHighest),
|
],
|
||||||
child: Row(
|
)
|
||||||
children: [
|
)
|
||||||
const Icon(Icons.arrow_upward_rounded),
|
],
|
||||||
const SizedBox(width: 8),
|
body: SafeArea(
|
||||||
Expanded(
|
top: false,
|
||||||
child: Text(AppLocalizations.of(context)!.fromLowestToHighest)
|
bottom: false,
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) => CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
),
|
||||||
|
if (sortedValues.isEmpty) Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.noItemsSearch,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
),
|
||||||
Icon(
|
),
|
||||||
_sortingOptions == _SortingOptions.lowestToHighest
|
),
|
||||||
? Icons.radio_button_checked_rounded
|
if (sortedValues.isNotEmpty) SliverList.builder(
|
||||||
: Icons.radio_button_unchecked_rounded,
|
itemCount: sortedValues.length,
|
||||||
color: _sortingOptions == _SortingOptions.lowestToHighest
|
itemBuilder: (context, index) => _Item(
|
||||||
? Theme.of(context).colorScheme.primary
|
data: sortedValues[index],
|
||||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
isClient: widget.isClient,
|
||||||
)
|
options: widget.options,
|
||||||
],
|
total: total,
|
||||||
)
|
withProgressBar: widget.withProgressBar,
|
||||||
|
onTapEntry: widget.onTapEntry,
|
||||||
|
buildValue: widget.buildValue,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8)
|
)
|
||||||
],
|
)
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: _Content(
|
|
||||||
buildValue: widget.buildValue,
|
|
||||||
isClient: widget.isClient,
|
|
||||||
onTapEntry: widget.onTapEntry,
|
|
||||||
options: widget.options,
|
|
||||||
screenData: sortedValues,
|
|
||||||
total: total,
|
|
||||||
withProgressBar: widget.withProgressBar,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -242,14 +258,29 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _Content(
|
child: sortedValues.isNotEmpty ? ListView.builder(
|
||||||
buildValue: widget.buildValue,
|
itemCount: sortedValues.length,
|
||||||
isClient: widget.isClient,
|
itemBuilder: (context, index) => _Item(
|
||||||
onTapEntry: widget.onTapEntry,
|
data: sortedValues[index],
|
||||||
options: widget.options,
|
isClient: widget.isClient,
|
||||||
screenData: sortedValues,
|
options: widget.options,
|
||||||
total: total,
|
withProgressBar: widget.withProgressBar,
|
||||||
withProgressBar: widget.withProgressBar,
|
onTapEntry: widget.onTapEntry,
|
||||||
|
buildValue: widget.buildValue,
|
||||||
|
total: total,
|
||||||
|
),
|
||||||
|
) : Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.noItemsSearch,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -260,8 +291,8 @@ class _TopItemsScreenState extends State<TopItemsScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Content extends StatelessWidget {
|
class _Item extends StatelessWidget {
|
||||||
final List<Map<String, dynamic>> screenData;
|
final dynamic data;
|
||||||
final bool? isClient;
|
final bool? isClient;
|
||||||
final List<MenuOption> Function(dynamic) options;
|
final List<MenuOption> Function(dynamic) options;
|
||||||
final bool withProgressBar;
|
final bool withProgressBar;
|
||||||
|
@ -269,8 +300,8 @@ class _Content extends StatelessWidget {
|
||||||
final String Function(dynamic) buildValue;
|
final String Function(dynamic) buildValue;
|
||||||
final double total;
|
final double total;
|
||||||
|
|
||||||
const _Content({
|
const _Item({
|
||||||
required this.screenData,
|
required this.data,
|
||||||
required this.isClient,
|
required this.isClient,
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.withProgressBar,
|
required this.withProgressBar,
|
||||||
|
@ -283,98 +314,75 @@ class _Content extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final statusProvider = Provider.of<StatusProvider>(context);
|
final statusProvider = Provider.of<StatusProvider>(context);
|
||||||
|
|
||||||
if (screenData.isNotEmpty) {
|
String? name;
|
||||||
return ListView.builder(
|
if (isClient != null && isClient == true) {
|
||||||
padding: const EdgeInsets.only(top: 0),
|
try {
|
||||||
itemCount: screenData.length,
|
name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(data.keys.toList()[0])).name;
|
||||||
itemBuilder: (context, index) {
|
} catch (e) {
|
||||||
String? name;
|
// ---- //
|
||||||
if (isClient != null && isClient == true) {
|
}
|
||||||
try {
|
|
||||||
name = statusProvider.serverStatus!.clients.firstWhere((c) => c.ids.contains(screenData[index].keys.toList()[0])).name;
|
|
||||||
} catch (e) {
|
|
||||||
// ---- //
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return OptionsMenu(
|
|
||||||
options: options,
|
|
||||||
value: screenData[index].keys.toList()[0],
|
|
||||||
onTap: onTapEntry != null
|
|
||||||
? (v) {
|
|
||||||
onTapEntry!(v);
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
child: CustomListTile(
|
|
||||||
title: screenData[index].keys.toList()[0],
|
|
||||||
trailing: Text(
|
|
||||||
buildValue(screenData[index].values.toList()[0]),
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitleWidget: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (name != null) ...[
|
|
||||||
Text(
|
|
||||||
name,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
],
|
|
||||||
if (withProgressBar == true) Row(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 50,
|
|
||||||
child: Text(
|
|
||||||
"${doubleFormat((screenData[index].values.toList()[0]/total*100), Platform.localeName)}%",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).listTileTheme.textColor
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Flexible(
|
|
||||||
child: LinearPercentIndicator(
|
|
||||||
animation: true,
|
|
||||||
lineHeight: 4,
|
|
||||||
animationDuration: 500,
|
|
||||||
curve: Curves.easeOut,
|
|
||||||
percent: screenData[index].values.toList()[0]/total,
|
|
||||||
barRadius: const Radius.circular(5),
|
|
||||||
progressColor: Theme.of(context).colorScheme.primary,
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return Center(
|
return OptionsMenu(
|
||||||
child: Padding(
|
options: options,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
value: data.keys.toList()[0],
|
||||||
child: Text(
|
onTap: onTapEntry != null
|
||||||
AppLocalizations.of(context)!.noItemsSearch,
|
? (v) {
|
||||||
textAlign: TextAlign.center,
|
onTapEntry!(v);
|
||||||
style: TextStyle(
|
Navigator.pop(context);
|
||||||
fontSize: 22,
|
}
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
: null,
|
||||||
),
|
child: CustomListTile(
|
||||||
|
title: data.keys.toList()[0],
|
||||||
|
trailing: Text(
|
||||||
|
buildValue(data.values.toList()[0]),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
subtitleWidget: Column(
|
||||||
}
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (name != null) ...[
|
||||||
|
Text(
|
||||||
|
name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
],
|
||||||
|
if (withProgressBar == true) Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 50,
|
||||||
|
child: Text(
|
||||||
|
"${doubleFormat((data.values.toList()[0]/total*100), Platform.localeName)}%",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).listTileTheme.textColor
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Flexible(
|
||||||
|
child: LinearPercentIndicator(
|
||||||
|
animation: true,
|
||||||
|
lineHeight: 4,
|
||||||
|
animationDuration: 500,
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
percent: data.values.toList()[0]/total,
|
||||||
|
barRadius: const Radius.circular(5),
|
||||||
|
progressColor: Theme.of(context).colorScheme.primary,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,8 +8,8 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/screens/logs/live/live_logs_screen.dart';
|
import 'package:adguard_home_manager/screens/logs/live/live_logs_screen.dart';
|
||||||
import 'package:adguard_home_manager/screens/logs/filters/logs_filters_modal.dart';
|
import 'package:adguard_home_manager/screens/logs/filters/logs_filters_modal.dart';
|
||||||
|
import 'package:adguard_home_manager/widgets/floating_search_bar.dart';
|
||||||
|
|
||||||
import 'package:adguard_home_manager/config/globals.dart';
|
|
||||||
import 'package:adguard_home_manager/constants/enums.dart';
|
import 'package:adguard_home_manager/constants/enums.dart';
|
||||||
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
import 'package:adguard_home_manager/functions/desktop_mode.dart';
|
||||||
import 'package:adguard_home_manager/providers/live_logs_provider.dart';
|
import 'package:adguard_home_manager/providers/live_logs_provider.dart';
|
||||||
|
@ -61,9 +61,10 @@ class LogsListAppBar extends StatelessWidget {
|
||||||
void showSearchDialog() {
|
void showSearchDialog() {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _Search(
|
builder: (context) => FloatingSearchBar(
|
||||||
|
existingSearchValue: logsProvider.appliedFilters.searchText ?? "",
|
||||||
searchButtonRenderBox: _searchButtonKey.currentContext?.findRenderObject() as RenderBox?,
|
searchButtonRenderBox: _searchButtonKey.currentContext?.findRenderObject() as RenderBox?,
|
||||||
onSearch: (v) {
|
onSearchCompleted: (v) {
|
||||||
logsProvider.setAppliedFilters(
|
logsProvider.setAppliedFilters(
|
||||||
AppliedFiters(
|
AppliedFiters(
|
||||||
selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus,
|
selectedResultStatus: logsProvider.appliedFilters.selectedResultStatus,
|
||||||
|
@ -73,6 +74,16 @@ class LogsListAppBar extends StatelessWidget {
|
||||||
);
|
);
|
||||||
logsProvider.filterLogs();
|
logsProvider.filterLogs();
|
||||||
},
|
},
|
||||||
|
onSearchFieldUpdated: (v) {
|
||||||
|
if (v == "") {
|
||||||
|
logsProvider.setSearchText(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logsProvider.setSearchText(v);
|
||||||
|
},
|
||||||
|
onSearchFieldCleared: () {
|
||||||
|
logsProvider.setSearchText(null);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -279,106 +290,3 @@ class LogsListAppBar extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Search extends StatefulWidget {
|
|
||||||
final void Function(String) onSearch;
|
|
||||||
final RenderBox? searchButtonRenderBox;
|
|
||||||
|
|
||||||
const _Search({
|
|
||||||
required this.onSearch,
|
|
||||||
required this.searchButtonRenderBox,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<_Search> createState() => _SearchState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SearchState extends State<_Search> {
|
|
||||||
final _searchController = TextEditingController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
final logsProvider = Provider.of<LogsProvider>(context, listen: false);
|
|
||||||
_searchController.text = logsProvider.appliedFilters.searchText ?? "";
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final logsProvider = Provider.of<LogsProvider>(context);
|
|
||||||
|
|
||||||
final position = widget.searchButtonRenderBox?.localToGlobal(Offset.zero);
|
|
||||||
final topPadding = MediaQuery.of(globalNavigatorKey.currentContext!).viewPadding.top;
|
|
||||||
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => Navigator.pop(context),
|
|
||||||
child: Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
final double width = constraints.maxWidth - 32 > 500 ? 500 : constraints.maxWidth - 32;
|
|
||||||
return Stack(
|
|
||||||
alignment: Alignment.topCenter,
|
|
||||||
children: [
|
|
||||||
Positioned(
|
|
||||||
top: position != null ? position.dy - topPadding : topPadding,
|
|
||||||
child: SizedBox(
|
|
||||||
width: width,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () => {},
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
borderRadius: BorderRadius.circular(16)
|
|
||||||
),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: _searchController,
|
|
||||||
onChanged: (v) {
|
|
||||||
if (v == "") {
|
|
||||||
logsProvider.setSearchText(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logsProvider.setSearchText(v);
|
|
||||||
},
|
|
||||||
onFieldSubmitted: (v) {
|
|
||||||
widget.onSearch(v);
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
autofocus: true,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: AppLocalizations.of(context)!.search,
|
|
||||||
prefixIcon: const Icon(Icons.search_rounded),
|
|
||||||
border: InputBorder.none,
|
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.grey.withOpacity(0.2),
|
|
||||||
suffixIcon: _searchController.text != ""
|
|
||||||
? IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
_searchController.text = "";
|
|
||||||
logsProvider.setSearchText(null);
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.close_rounded,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
tooltip: AppLocalizations.of(context)!.clearSearch,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
110
lib/widgets/floating_search_bar.dart
Normal file
110
lib/widgets/floating_search_bar.dart
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'package:adguard_home_manager/config/globals.dart';
|
||||||
|
|
||||||
|
class FloatingSearchBar extends StatefulWidget {
|
||||||
|
final void Function(String) onSearchCompleted;
|
||||||
|
final RenderBox? searchButtonRenderBox;
|
||||||
|
final String? existingSearchValue;
|
||||||
|
final void Function(String)? onSearchFieldUpdated;
|
||||||
|
final void Function()? onSearchFieldCleared;
|
||||||
|
|
||||||
|
const FloatingSearchBar({
|
||||||
|
super.key,
|
||||||
|
required this.onSearchCompleted,
|
||||||
|
required this.searchButtonRenderBox,
|
||||||
|
this.existingSearchValue,
|
||||||
|
this.onSearchFieldUpdated,
|
||||||
|
this.onSearchFieldCleared,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FloatingSearchBar> createState() => _SearchState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SearchState extends State<FloatingSearchBar> {
|
||||||
|
final _searchController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
if (widget.existingSearchValue != null) {
|
||||||
|
_searchController.text = widget.existingSearchValue!;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final position = widget.searchButtonRenderBox?.localToGlobal(Offset.zero);
|
||||||
|
final topPadding = MediaQuery.of(globalNavigatorKey.currentContext!).viewPadding.top;
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => Navigator.pop(context),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final double width = constraints.maxWidth - 32 > 500 ? 500 : constraints.maxWidth - 32;
|
||||||
|
return Stack(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
top: position != null ? position.dy - topPadding : topPadding,
|
||||||
|
child: SizedBox(
|
||||||
|
width: width,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => {},
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
borderRadius: BorderRadius.circular(16)
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _searchController,
|
||||||
|
onChanged: widget.onSearchFieldUpdated,
|
||||||
|
onFieldSubmitted: (v) {
|
||||||
|
widget.onSearchCompleted(v);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
autofocus: true,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: AppLocalizations.of(context)!.search,
|
||||||
|
prefixIcon: const Icon(Icons.search_rounded),
|
||||||
|
border: InputBorder.none,
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.grey.withOpacity(0.2),
|
||||||
|
suffixIcon: _searchController.text != ""
|
||||||
|
? IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() => _searchController.text = "");
|
||||||
|
if (widget.onSearchFieldCleared != null) {
|
||||||
|
widget.onSearchFieldCleared!();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.close_rounded,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
tooltip: AppLocalizations.of(context)!.clearSearch,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue