import 'package:cached_query/cached_query.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; import 'package:madari_client/features/settings/service/selected_profile.dart'; import 'package:madari_client/features/streamio_addons/extension/query_extension.dart'; import 'package:madari_client/features/widgetter/plugin_base.dart'; import 'package:madari_client/features/widgetter/plugins/stremio/widgets/catalog_featured_shimmer.dart'; import 'package:madari_client/features/widgetter/service/home_layout_service.dart'; import 'package:madari_client/features/widgetter/state/widget_state_provider.dart'; import 'package:madari_client/features/widgetter/types/home_layout_model.dart'; import 'package:provider/provider.dart'; import '../home/pages/home_page.dart'; import '../pocketbase/service/pocketbase.service.dart'; class LayoutManager extends StatefulWidget { final bool hasSearch; const LayoutManager({ super.key, this.hasSearch = false, }); @override State createState() => LayoutManagerState(); } class LayoutManagerState extends State { final _logger = Logger('LayoutManager'); final ScrollController _scrollController = ScrollController(); List _layouts = []; List _filteredLayouts = []; bool _isLoading = true; @override void initState() { super.initState(); _loadLayouts(); } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override void didChangeDependencies() { super.didChangeDependencies(); } Future refresh() async { final result = await _loadLayouts( refresh: true, ); HomeLayoutService.instance.refreshWidgets.add(true); return result; } Future _loadLayouts({ bool refresh = false, }) async { try { _logger.info('Loading layouts'); final query = Query( key: "home_layout_${SelectedProfileService.instance.selectedProfileId}${widget.hasSearch}", config: QueryConfig( ignoreCacheDuration: refresh, cacheDuration: const Duration(hours: 8), refetchDuration: const Duration(minutes: 5), ), queryFn: () async { return await AppPocketBaseService.instance.pb .collection('home_layout') .getFullList( sort: 'order', filter: "profiles = '${SelectedProfileService.instance.selectedProfileId}'", ); }, ); if (refresh) { await query.refetch(); } final records = await query.queryFn(); setState(() { _layouts = records .map((record) => HomeLayoutModel.fromJson(record.toJson())) .toList(); _filteredLayouts = _layouts; _isLoading = false; }); } catch (e) { _logger.severe('Error loading layouts', e); setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { final search = context.watch().search; return RefreshIndicator( onRefresh: () { return refresh(); }, child: ListenableBuilder( listenable: PluginRegistry.instance, builder: (context, _) { if (_isLoading) { return const CatalogFeaturedShimmer(); } if (_layouts.isEmpty) { return Center( child: Container( padding: const EdgeInsets.all(24.0), constraints: const BoxConstraints(maxWidth: 400), child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.design_services, size: 64, color: Theme.of(context).colorScheme.primary, ), const SizedBox(height: 24), Text( "Configure Home Layout", style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 12), Text( "You need to define your home before start", textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 32), FilledButton.icon( onPressed: () { context.push("/layout"); }, icon: const Icon(Icons.edit_outlined), label: const Text( "Configure Layout", style: TextStyle( fontSize: 15, ), ), style: FilledButton.styleFrom( padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 16, ), ), ), ], ), ), ); } return Column( children: [ if (widget.hasSearch) const SearchBox( hintText: 'Search...', ), if (!widget.hasSearch || search.trim() != "") Expanded( child: CustomScrollView( controller: _scrollController, slivers: [ SliverList( delegate: SliverChildBuilderDelegate( (context, index) { final layout = _filteredLayouts[index]; if (widget.hasSearch) { if (!(layout.pluginId == "stremio_catalog" && layout.type == "catalog_grid")) { return const SizedBox.shrink(); } } return PluginWidget( key: ValueKey( '${layout.id}_${layout.pluginId}_${layout.type}_${search.trim()}', ), layout: layout, pluginContext: PluginContext( index: index, hasSearch: widget.hasSearch, ), ); }, childCount: _filteredLayouts.length, ), ), ], ), ), ], ); }, ), ); } }