- search schmearch
This commit is contained in:
		@@ -3,12 +3,16 @@ class Suggestion {
 | 
				
			|||||||
  final int tagged;
 | 
					  final int tagged;
 | 
				
			||||||
  final double score;
 | 
					  final double score;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Suggestion({required this.tag, required this.tagged, required this.score});
 | 
					  Suggestion({
 | 
				
			||||||
 | 
					    required this.tag,
 | 
				
			||||||
 | 
					    required this.tagged,
 | 
				
			||||||
 | 
					    required this.score,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  factory Suggestion.fromJson(Map<String, dynamic> json) {
 | 
					  factory Suggestion.fromJson(Map<String, dynamic> json) {
 | 
				
			||||||
    return Suggestion(
 | 
					    return Suggestion(
 | 
				
			||||||
      tag: json['tag'].toString(),
 | 
					      tag: json['tag'].toString(),
 | 
				
			||||||
      tagged: int.tryParse(json['tagged'].toString()) ?? 0,
 | 
					      tagged: json['tagged'],
 | 
				
			||||||
      score: (json['score'] as num).toDouble(),
 | 
					      score: (json['score'] as num).toDouble(),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,14 +4,12 @@ import 'package:cached_network_image/cached_network_image.dart';
 | 
				
			|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
					import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
				
			||||||
import 'package:go_router/go_router.dart';
 | 
					import 'package:go_router/go_router.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/models/mediaitem_model.dart';
 | 
				
			||||||
import 'package:f0ckapp/providers/media_provider.dart';
 | 
					import 'package:f0ckapp/providers/media_provider.dart';
 | 
				
			||||||
import 'package:f0ckapp/utils/appversion_util.dart';
 | 
					import 'package:f0ckapp/utils/appversion_util.dart';
 | 
				
			||||||
import 'package:f0ckapp/providers/theme_provider.dart';
 | 
					import 'package:f0ckapp/providers/theme_provider.dart';
 | 
				
			||||||
import 'package:f0ckapp/utils/customsearchdelegate_util.dart';
 | 
					import 'package:f0ckapp/utils/customsearchdelegate_util.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const List<String> mediaTypes = ["alles", "image", "video", "audio"];
 | 
					 | 
				
			||||||
const List<String> mediaModes = ["sfw", "nsfw", "untagged", "all"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MediaGrid extends ConsumerStatefulWidget {
 | 
					class MediaGrid extends ConsumerStatefulWidget {
 | 
				
			||||||
  const MediaGrid({super.key});
 | 
					  const MediaGrid({super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -56,8 +54,8 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    final mediaState = ref.watch(mediaProvider);
 | 
					    final MediaState mediaState = ref.watch(mediaProvider);
 | 
				
			||||||
    final mediaNotifier = ref.read(mediaProvider.notifier);
 | 
					    final MediaNotifier mediaNotifier = ref.read(mediaProvider.notifier);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Scaffold(
 | 
					    return Scaffold(
 | 
				
			||||||
      key: _scaffoldKey,
 | 
					      key: _scaffoldKey,
 | 
				
			||||||
@@ -80,14 +78,12 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
				
			|||||||
        ),
 | 
					        ),
 | 
				
			||||||
        actions: [
 | 
					        actions: [
 | 
				
			||||||
          IconButton(
 | 
					          IconButton(
 | 
				
			||||||
            icon: Icon(Icons.search),
 | 
					            icon: const Icon(Icons.search),
 | 
				
			||||||
            onPressed: () {
 | 
					            onPressed: () async {
 | 
				
			||||||
              showSearch(
 | 
					              await showSearch(
 | 
				
			||||||
                context: context,
 | 
					                context: context,
 | 
				
			||||||
                delegate: CustomSearchDelegate(),
 | 
					                delegate: CustomSearchDelegate(),
 | 
				
			||||||
              );
 | 
					              );
 | 
				
			||||||
              //mediaNotifier.setTag('drachenlord');
 | 
					 | 
				
			||||||
              //_scrollController.jumpTo(0);
 | 
					 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          IconButton(
 | 
					          IconButton(
 | 
				
			||||||
@@ -163,7 +159,7 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
				
			|||||||
              ),
 | 
					              ),
 | 
				
			||||||
              child: null,
 | 
					              child: null,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            ExpansionTile(
 | 
					            /*ExpansionTile(
 | 
				
			||||||
              title: const Text('Login'),
 | 
					              title: const Text('Login'),
 | 
				
			||||||
              children: [
 | 
					              children: [
 | 
				
			||||||
                Padding(
 | 
					                Padding(
 | 
				
			||||||
@@ -193,7 +189,7 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
				
			|||||||
                            const SnackBar(
 | 
					                            const SnackBar(
 | 
				
			||||||
                              content: Text("noch nicht implementiert lol"),
 | 
					                              content: Text("noch nicht implementiert lol"),
 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            /*final success = await login(
 | 
					                            final success = await login(
 | 
				
			||||||
                              _usernameController.text,
 | 
					                              _usernameController.text,
 | 
				
			||||||
                              _passwordController.text,
 | 
					                              _passwordController.text,
 | 
				
			||||||
                            );
 | 
					                            );
 | 
				
			||||||
@@ -204,7 +200,7 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
				
			|||||||
                              ScaffoldMessenger.of(context).showSnackBar(
 | 
					                              ScaffoldMessenger.of(context).showSnackBar(
 | 
				
			||||||
                                SnackBar(content: Text("Login fehlgeschlagen!")),
 | 
					                                SnackBar(content: Text("Login fehlgeschlagen!")),
 | 
				
			||||||
                              );
 | 
					                              );
 | 
				
			||||||
                            }*/
 | 
					                            }
 | 
				
			||||||
                          );
 | 
					                          );
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                        child: const Text('Login'),
 | 
					                        child: const Text('Login'),
 | 
				
			||||||
@@ -213,7 +209,7 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
				
			|||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
            ),
 | 
					            ),*/
 | 
				
			||||||
            ExpansionTile(
 | 
					            ExpansionTile(
 | 
				
			||||||
              title: const Text('Theme'),
 | 
					              title: const Text('Theme'),
 | 
				
			||||||
              children: [
 | 
					              children: [
 | 
				
			||||||
@@ -221,10 +217,10 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
				
			|||||||
                  padding: const EdgeInsets.symmetric(horizontal: 16.0),
 | 
					                  padding: const EdgeInsets.symmetric(horizontal: 16.0),
 | 
				
			||||||
                  child: Column(
 | 
					                  child: Column(
 | 
				
			||||||
                    children: themeMap.entries.map((entry) {
 | 
					                    children: themeMap.entries.map((entry) {
 | 
				
			||||||
                      final themeName = entry.key;
 | 
					                      final String themeName = entry.key;
 | 
				
			||||||
                      final themeData = entry.value;
 | 
					                      final ThemeData themeData = entry.value;
 | 
				
			||||||
                      final currentTheme = ref.watch(themeNotifierProvider);
 | 
					                      final ThemeData currentTheme = ref.watch(themeNotifierProvider);
 | 
				
			||||||
                      final isSelected = currentTheme == themeData;
 | 
					                      final bool isSelected = currentTheme == themeData;
 | 
				
			||||||
                      return ListTile(
 | 
					                      return ListTile(
 | 
				
			||||||
                        title: Text(themeName),
 | 
					                        title: Text(themeName),
 | 
				
			||||||
                        selected: isSelected,
 | 
					                        selected: isSelected,
 | 
				
			||||||
@@ -284,11 +280,11 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
				
			|||||||
          ),
 | 
					          ),
 | 
				
			||||||
          itemCount:
 | 
					          itemCount:
 | 
				
			||||||
              mediaState.mediaItems.length + (mediaState.isLoading ? 1 : 0),
 | 
					              mediaState.mediaItems.length + (mediaState.isLoading ? 1 : 0),
 | 
				
			||||||
          itemBuilder: (context, index) {
 | 
					          itemBuilder: (BuildContext context, int index) {
 | 
				
			||||||
            if (index >= mediaState.mediaItems.length) {
 | 
					            if (index >= mediaState.mediaItems.length) {
 | 
				
			||||||
              return const Center(child: CircularProgressIndicator());
 | 
					              return const Center(child: CircularProgressIndicator());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            final item = mediaState.mediaItems[index];
 | 
					            final MediaItem item = mediaState.mediaItems[index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return InkWell(
 | 
					            return InkWell(
 | 
				
			||||||
              onTap: () async {
 | 
					              onTap: () async {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'dart:async';
 | 
				
			||||||
import 'dart:convert';
 | 
					import 'dart:convert';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:http/http.dart' as http;
 | 
					import 'package:http/http.dart' as http;
 | 
				
			||||||
@@ -6,7 +7,9 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
 | 
				
			|||||||
import 'package:f0ckapp/models/mediaitem_model.dart';
 | 
					import 'package:f0ckapp/models/mediaitem_model.dart';
 | 
				
			||||||
import 'package:f0ckapp/models/suggestion_model.dart';
 | 
					import 'package:f0ckapp/models/suggestion_model.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final FlutterSecureStorage storage = FlutterSecureStorage();
 | 
					final FlutterSecureStorage storage = const FlutterSecureStorage(
 | 
				
			||||||
 | 
					  aOptions: AndroidOptions(encryptedSharedPreferences: true),
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Future<List<MediaItem>> fetchMedia({
 | 
					Future<List<MediaItem>> fetchMedia({
 | 
				
			||||||
  int? older,
 | 
					  int? older,
 | 
				
			||||||
@@ -25,7 +28,7 @@ Future<List<MediaItem>> fetchMedia({
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final response = await http.get(url);
 | 
					  final http.Response response = await http.get(url);
 | 
				
			||||||
  if (response.statusCode == 200) {
 | 
					  if (response.statusCode == 200) {
 | 
				
			||||||
    final List<dynamic> jsonList = jsonDecode(response.body);
 | 
					    final List<dynamic> jsonList = jsonDecode(response.body);
 | 
				
			||||||
    return jsonList.map((item) => MediaItem.fromJson(item)).toList();
 | 
					    return jsonList.map((item) => MediaItem.fromJson(item)).toList();
 | 
				
			||||||
@@ -37,7 +40,7 @@ Future<List<MediaItem>> fetchMedia({
 | 
				
			|||||||
Future<MediaItem> fetchMediaDetail(int itemId) async {
 | 
					Future<MediaItem> fetchMediaDetail(int itemId) async {
 | 
				
			||||||
  final Uri url = Uri.parse('https://api.f0ck.me/item/${itemId.toString()}');
 | 
					  final Uri url = Uri.parse('https://api.f0ck.me/item/${itemId.toString()}');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final response = await http.get(url);
 | 
					  final http.Response response = await http.get(url);
 | 
				
			||||||
  if (response.statusCode == 200) {
 | 
					  if (response.statusCode == 200) {
 | 
				
			||||||
    final Map<String, dynamic> jsonResponse = jsonDecode(response.body);
 | 
					    final Map<String, dynamic> jsonResponse = jsonDecode(response.body);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -50,39 +53,45 @@ Future<MediaItem> fetchMediaDetail(int itemId) async {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Future<List<Suggestion>> fetchSuggestions(String query) async {
 | 
					Future<List<Suggestion>> fetchSuggestions(String query) async {
 | 
				
			||||||
  final Uri uri = Uri.parse(
 | 
					  final Uri uri = Uri.parse('https://api.f0ck.me/search/?q=$query');
 | 
				
			||||||
    'https://f0ck.me/api/v2/admin/tags/suggest?q=$query',
 | 
					  try {
 | 
				
			||||||
  ); // wip: new route in pyapi
 | 
					    final http.Response response = await http
 | 
				
			||||||
  final response = await http.get(uri);
 | 
					        .get(uri)
 | 
				
			||||||
 | 
					        .timeout(const Duration(seconds: 5));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (response.statusCode == 200) {
 | 
					    if (response.statusCode == 200) {
 | 
				
			||||||
    final Map<String, dynamic> decoded = jsonDecode(response.body);
 | 
					      final dynamic decoded = jsonDecode(response.body);
 | 
				
			||||||
    if (decoded['success'] == true && decoded.containsKey('suggestions')) {
 | 
					
 | 
				
			||||||
      final List<dynamic> suggestionsList = decoded['suggestions'];
 | 
					      if (decoded is List) {
 | 
				
			||||||
      return suggestionsList
 | 
					        return decoded
 | 
				
			||||||
          .map(
 | 
					            .map((item) => Suggestion.fromJson(item as Map<String, dynamic>))
 | 
				
			||||||
            (dynamic jsonItem) =>
 | 
					            .toList()
 | 
				
			||||||
                Suggestion.fromJson(jsonItem as Map<String, dynamic>),
 | 
					          ..sort((a, b) => b.score.compareTo(a.score));
 | 
				
			||||||
          )
 | 
					      } else {
 | 
				
			||||||
          .toList()
 | 
					        throw Exception('Unerwartetes Format: Erwartet wurde eine Liste.');
 | 
				
			||||||
        ..sort(
 | 
					      }
 | 
				
			||||||
          (Suggestion a, Suggestion b) =>
 | 
					    } else if (response.statusCode == 400) {
 | 
				
			||||||
              (b.score * b.tagged).compareTo(a.score * a.tagged),
 | 
					      final dynamic error = jsonDecode(response.body);
 | 
				
			||||||
        );
 | 
					      final String message = error is Map<String, dynamic>
 | 
				
			||||||
 | 
					          ? error['detail']?.toString() ?? 'Unbekannter Fehler.'
 | 
				
			||||||
 | 
					          : 'Unbekannter Fehler.';
 | 
				
			||||||
 | 
					      throw Exception('Client-Fehler 400: $message');
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      throw Exception('Nichts gefunden.');
 | 
					      throw Exception(
 | 
				
			||||||
 | 
					        'Fehler beim Abrufen der Vorschläge: ${response.statusCode}',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  } else {
 | 
					  } on TimeoutException {
 | 
				
			||||||
    throw Exception(
 | 
					    throw Exception('Anfrage an die API hat zu lange gedauert.');
 | 
				
			||||||
      'Fehler beim Abrufen der Vorschläge: ${response.statusCode}',
 | 
					  } catch (e) {
 | 
				
			||||||
    );
 | 
					    throw Exception('Fehler beim Verarbeiten der Anfrage: $e');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Future<bool> login(String username, String password) async {
 | 
					Future<bool> login(String username, String password) async {
 | 
				
			||||||
  final Uri url = Uri.parse('https://api.f0ck.me/login');
 | 
					  final Uri url = Uri.parse('https://api.f0ck.me/login');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final response = await http.post(
 | 
					  final http.Response response = await http.post(
 | 
				
			||||||
    url,
 | 
					    url,
 | 
				
			||||||
    body: {'username': username, 'password': password},
 | 
					    body: {'username': username, 'password': password},
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,20 +1,25 @@
 | 
				
			|||||||
import 'dart:async';
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
					import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'package:f0ckapp/services/api_service.dart';
 | 
					import 'package:f0ckapp/services/api_service.dart';
 | 
				
			||||||
import 'package:f0ckapp/models/suggestion_model.dart';
 | 
					import 'package:f0ckapp/models/suggestion_model.dart';
 | 
				
			||||||
import 'package:f0ckapp/providers/media_provider.dart';
 | 
					import 'package:f0ckapp/providers/media_provider.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CustomSearchDelegate extends SearchDelegate<String> {
 | 
					class CustomSearchDelegate extends SearchDelegate<String> {
 | 
				
			||||||
 | 
					  Timer? _debounceTimer;
 | 
				
			||||||
 | 
					  List<Suggestion>? _suggestions;
 | 
				
			||||||
 | 
					  bool _isLoading = false;
 | 
				
			||||||
 | 
					  String? _error;
 | 
				
			||||||
 | 
					  String _lastFetchedQuery = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  List<Widget> buildActions(BuildContext context) {
 | 
					  List<Widget> buildActions(BuildContext context) {
 | 
				
			||||||
    return [
 | 
					    return [
 | 
				
			||||||
      IconButton(
 | 
					      IconButton(
 | 
				
			||||||
        icon: Icon(Icons.clear),
 | 
					        icon: const Icon(Icons.clear),
 | 
				
			||||||
        onPressed: () {
 | 
					        onPressed: () {
 | 
				
			||||||
          query = '';
 | 
					          query = '';
 | 
				
			||||||
 | 
					          _clearResults();
 | 
				
			||||||
          showSuggestions(context);
 | 
					          showSuggestions(context);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
@@ -24,8 +29,11 @@ class CustomSearchDelegate extends SearchDelegate<String> {
 | 
				
			|||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget buildLeading(BuildContext context) {
 | 
					  Widget buildLeading(BuildContext context) {
 | 
				
			||||||
    return IconButton(
 | 
					    return IconButton(
 | 
				
			||||||
      icon: Icon(Icons.arrow_back),
 | 
					      icon: const Icon(Icons.arrow_back),
 | 
				
			||||||
      onPressed: () => close(context, 'null'),
 | 
					      onPressed: () {
 | 
				
			||||||
 | 
					        _debounceTimer?.cancel();
 | 
				
			||||||
 | 
					        close(context, 'null');
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -36,35 +44,58 @@ class CustomSearchDelegate extends SearchDelegate<String> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget buildSuggestions(BuildContext context) {
 | 
					  Widget buildSuggestions(BuildContext context) {
 | 
				
			||||||
    if (query.isEmpty) {
 | 
					    return StatefulBuilder(
 | 
				
			||||||
      return Container(padding: EdgeInsets.all(16.0), child: Text(''));
 | 
					      builder: (BuildContext context, void Function(void Function()) setState) {
 | 
				
			||||||
    }
 | 
					        if (query.isEmpty) {
 | 
				
			||||||
 | 
					          _debounceTimer?.cancel();
 | 
				
			||||||
    final Future<List<Suggestion>> futureSuggestions = Future.delayed(
 | 
					          return Container(padding: const EdgeInsets.all(16.0), child: const Text(''));
 | 
				
			||||||
      Duration(milliseconds: 300),
 | 
					        }
 | 
				
			||||||
      () => fetchSuggestions(query),
 | 
					
 | 
				
			||||||
    );
 | 
					        if (query != _lastFetchedQuery) {
 | 
				
			||||||
 | 
					          _debounceTimer?.cancel();
 | 
				
			||||||
    return FutureBuilder<List<Suggestion>>(
 | 
					          _isLoading = true;
 | 
				
			||||||
      future: futureSuggestions,
 | 
					          _error = null;
 | 
				
			||||||
      builder: (BuildContext context, AsyncSnapshot<List<Suggestion>> snapshot) {
 | 
					          _suggestions = null;
 | 
				
			||||||
        if (snapshot.connectionState == ConnectionState.waiting) {
 | 
					
 | 
				
			||||||
          return Center(child: CircularProgressIndicator());
 | 
					          _debounceTimer = Timer(Duration(milliseconds: 500), () async {
 | 
				
			||||||
        }
 | 
					            try {
 | 
				
			||||||
        if (snapshot.hasError) {
 | 
					              final List<Suggestion> results = await fetchSuggestions(query);
 | 
				
			||||||
          return Center(child: Text("Fehler: ${snapshot.error}"));
 | 
					              _lastFetchedQuery = query;
 | 
				
			||||||
        }
 | 
					              setState(() {
 | 
				
			||||||
        if (!snapshot.hasData || snapshot.data!.isEmpty) {
 | 
					                _suggestions = results;
 | 
				
			||||||
          return Center(child: Text("Keine Vorschläge gefunden."));
 | 
					                _isLoading = false;
 | 
				
			||||||
 | 
					              });
 | 
				
			||||||
 | 
					            } catch (e) {
 | 
				
			||||||
 | 
					              _lastFetchedQuery = query;
 | 
				
			||||||
 | 
					              setState(() {
 | 
				
			||||||
 | 
					                _error = e.toString();
 | 
				
			||||||
 | 
					                _suggestions = [];
 | 
				
			||||||
 | 
					                _isLoading = false;
 | 
				
			||||||
 | 
					              });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return Center(child: _buildLoadingIndicator());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (_isLoading) {
 | 
				
			||||||
 | 
					          return Center(child: _buildLoadingIndicator());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (_error != null) {
 | 
				
			||||||
 | 
					          return Center(child: Text("Fehler: $_error"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (_suggestions == null || _suggestions!.isEmpty) {
 | 
				
			||||||
 | 
					          return Center(child: const Text("Keine Ergebnisse gefunden."));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final List<Suggestion> suggestions = snapshot.data!;
 | 
					 | 
				
			||||||
        return Consumer(
 | 
					        return Consumer(
 | 
				
			||||||
          builder: (BuildContext context, WidgetRef ref, Widget? child) {
 | 
					          builder: (BuildContext context, WidgetRef ref, Widget? child) {
 | 
				
			||||||
            return ListView.builder(
 | 
					            return ListView.builder(
 | 
				
			||||||
              itemCount: suggestions.length,
 | 
					              itemCount: _suggestions!.length,
 | 
				
			||||||
              itemBuilder: (BuildContext context, int index) {
 | 
					              itemBuilder: (BuildContext context, int index) {
 | 
				
			||||||
                final Suggestion suggestion = suggestions[index];
 | 
					                final Suggestion suggestion = _suggestions![index];
 | 
				
			||||||
                return ListTile(
 | 
					                return ListTile(
 | 
				
			||||||
                  title: Text(suggestion.tag),
 | 
					                  title: Text(suggestion.tag),
 | 
				
			||||||
                  subtitle: Text(
 | 
					                  subtitle: Text(
 | 
				
			||||||
@@ -83,4 +114,32 @@ class CustomSearchDelegate extends SearchDelegate<String> {
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Widget _buildLoadingIndicator() {
 | 
				
			||||||
 | 
					    return Column(
 | 
				
			||||||
 | 
					      mainAxisAlignment: MainAxisAlignment.center,
 | 
				
			||||||
 | 
					      children: [
 | 
				
			||||||
 | 
					        const CircularProgressIndicator(strokeWidth: 3.0),
 | 
				
			||||||
 | 
					        const SizedBox(height: 12),
 | 
				
			||||||
 | 
					        const Text(
 | 
				
			||||||
 | 
					          'Vorschläge werden geladen...',
 | 
				
			||||||
 | 
					          style: TextStyle(fontStyle: FontStyle.italic),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _clearResults() {
 | 
				
			||||||
 | 
					    _debounceTimer?.cancel();
 | 
				
			||||||
 | 
					    _suggestions = null;
 | 
				
			||||||
 | 
					    _isLoading = false;
 | 
				
			||||||
 | 
					    _error = null;
 | 
				
			||||||
 | 
					    _lastFetchedQuery = "";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void close(BuildContext context, String result) {
 | 
				
			||||||
 | 
					    _debounceTimer?.cancel();
 | 
				
			||||||
 | 
					    super.close(context, result);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 | 
				
			|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 | 
					# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 | 
				
			||||||
# In Windows, build-name is used as the major, minor, and patch parts
 | 
					# In Windows, build-name is used as the major, minor, and patch parts
 | 
				
			||||||
# of the product and file versions while build-number is used as the build suffix.
 | 
					# of the product and file versions while build-number is used as the build suffix.
 | 
				
			||||||
version: 1.1.11+41
 | 
					version: 1.1.12+42
 | 
				
			||||||
 | 
					
 | 
				
			||||||
environment:
 | 
					environment:
 | 
				
			||||||
  sdk: ^3.9.0-100.2.beta
 | 
					  sdk: ^3.9.0-100.2.beta
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user