Line data Source code
1 : import 'dart:async'; 2 : import 'dart:io'; 3 : import 'package:flutter_map/flutter_map.dart'; 4 : import 'package:equatable/equatable.dart'; 5 : import 'package:flutter_bloc/flutter_bloc.dart'; 6 : import 'package:memories_app/routes/home/model/story_model.dart'; 7 : import 'package:memories_app/routes/search/model/search_story_repository.dart'; 8 : import 'package:memories_app/routes/search/model/search_story_request_model.dart'; 9 : import 'package:memories_app/routes/search/model/search_story_response_model.dart'; 10 : 11 : part 'search_story_event.dart'; 12 : part 'search_story_state.dart'; 13 : 14 : class _Constants { 15 : static const String errorMessage = 16 : 'Error searching stories.\n Please check your request'; 17 : static const String errorEmptyStories = 18 : 'Cannot find any stories with your search'; 19 : static const String offlineMessage = 20 : 'You are currently offline.\n Please check your internet connection!'; 21 : } 22 : 23 : class SearchStoryBloc extends Bloc<SearchStoryEvent, SearchStoryState> { 24 : final SearchStoryRepository _repository; 25 : 26 1 : SearchStoryBloc({required SearchStoryRepository repository}) 27 : : _repository = repository, 28 1 : super(const SearchStoryState()) { 29 2 : on<SearchStoryEventSearchPressed>(_searchStory); 30 2 : on<SearchStoryErrorPopupClosedEvent>(_onErrorPopupClosed); 31 : } 32 : 33 1 : Future<void> _searchStory(SearchStoryEventSearchPressed event, 34 : Emitter<SearchStoryState> emit) async { 35 1 : final SearchStoryRequestModel model = createSearchModel(event); 36 : SearchStoryResponseModel? response; 37 : 38 : try { 39 2 : response = await _repository.searchStory(model); 40 0 : } on SocketException { 41 0 : emit(const SearchStoryOffline(offlineMessage: _Constants.offlineMessage)); 42 : } catch (error) { 43 0 : emit(SearchStoryFailure(error: error.toString())); 44 : } 45 : 46 : if (response != null) { 47 1 : if (response.stories != null) { 48 2 : if (response.stories!.isNotEmpty) { 49 3 : emit(SearchStorySuccess(response.stories)); 50 : } else { 51 1 : emit(const SearchStoryFailure(error: _Constants.errorEmptyStories)); 52 : } 53 : } else { 54 0 : emit(const SearchStoryFailure(error: _Constants.errorMessage)); 55 : } 56 : } 57 : } 58 : 59 1 : SearchStoryRequestModel createSearchModel( 60 : SearchStoryEventSearchPressed event) { 61 3 : final String dateType = mapDateTypeToValue(event.timeType!.toLowerCase()); 62 : 63 1 : return SearchStoryRequestModel( 64 1 : title: event.title, 65 1 : author: event.author, 66 1 : tag: event.tag, 67 1 : tagLabel: event.tagLabel, 68 : timeType: dateType, 69 1 : timeValue: setTimeModel(event), 70 1 : location: setLocationModel(event), 71 1 : radiusDiff: event.radius, 72 1 : dateDiff: event.dateDiff); 73 : } 74 : 75 1 : TimeValue? setTimeModel(SearchStoryEventSearchPressed event) { 76 2 : if (event.timeType == "Year") { 77 1 : return NormalYear( 78 2 : year: event.year ?? "", seasonName: event.seasonName ?? ""); 79 : } 80 2 : if (event.timeType == "Interval Year") { 81 1 : return IntervalYear( 82 1 : startYear: event.startYear ?? "", 83 1 : endYear: event.endYear ?? "", 84 1 : seasonName: event.seasonName ?? ""); 85 : } 86 : 87 2 : if (event.timeType == "Date") { 88 2 : return NormalDate(date: event.date ?? ""); 89 : } 90 : 91 2 : if (event.timeType == "Interval Date") { 92 1 : return IntervalDate( 93 2 : startDate: event.startDate ?? "", endDate: event.endDate ?? ""); 94 : } 95 : 96 2 : if (event.timeType == "Decade") { 97 3 : return Decade(decade: extractDecade(event.decade) ?? 1900); 98 : } 99 : return null; 100 : } 101 : 102 1 : Location? setLocationModel(SearchStoryEventSearchPressed event) { 103 1 : if (event.marker != null) { 104 2 : return Location(coordinates: <double>[ 105 3 : event.marker!.point.longitude, 106 3 : event.marker!.point.latitude 107 : ], type: "Point"); 108 : } else { 109 : return null; 110 : } 111 : } 112 : 113 1 : int? extractDecade(String? decade) { 114 1 : if (decade == null || decade.isEmpty) { 115 : return null; 116 : } 117 : 118 : // Remove trailing 's' and parse the remaining part as an integer 119 3 : String numericPart = decade.substring(0, decade.length - 1); 120 1 : return int.tryParse(numericPart); 121 : } 122 : 123 1 : void _onErrorPopupClosed( 124 : SearchStoryErrorPopupClosedEvent event, Emitter<SearchStoryState> emit) { 125 1 : emit(const SearchStoryState()); 126 : } 127 : 128 1 : String mapDateTypeToValue(String selectedDateType) { 129 1 : switch (selectedDateType.toLowerCase()) { 130 1 : case "year": 131 : return "year"; 132 1 : case "interval year": 133 : return "year_interval"; 134 1 : case "normal date": 135 : return "normal_date"; 136 1 : case "interval date": 137 : return "interval_date"; 138 1 : case "decade": 139 : return "decade"; 140 : default: 141 : return "year"; 142 : } 143 : } 144 : }