Files
elcaribe/news-app/lib/ui/screens/Search.dart

489 lines
19 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:news/app/routes.dart';
import 'package:news/cubits/appLocalizationCubit.dart';
import 'package:news/cubits/appSystemSettingCubit.dart';
import 'package:news/cubits/categoryCubit.dart';
import 'package:news/cubits/tagCubit.dart';
import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart';
import 'package:news/data/models/NewsModel.dart';
import 'package:news/data/models/CategoryModel.dart';
import 'package:news/data/models/TagModel.dart';
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart';
import 'package:news/ui/screens/filter/FilterBottomSheet.dart';
import 'package:news/ui/screens/filter/widgets/duration_filter_widget.dart';
import 'package:news/ui/styles/colors.dart';
import 'package:news/ui/widgets/customTextLabel.dart';
import 'package:news/ui/widgets/networkImage.dart';
import 'package:news/ui/widgets/SnackBarWidget.dart';
import 'package:news/ui/widgets/customBackBtn.dart';
import 'package:news/utils/constant.dart';
import 'package:news/utils/hiveBoxKeys.dart';
import 'package:news/utils/api.dart';
import 'package:news/utils/strings.dart';
import 'package:news/utils/uiUtils.dart';
import 'package:news/utils/internetConnectivity.dart';
import 'dart:async';
class Search extends StatefulWidget {
const Search({super.key});
@override
SearchState createState() => SearchState();
}
bool buildResult = false; //used in 2 classes here _SearchState & _SuggestionList
class SearchState extends State<Search> with TickerProviderStateMixin {
final TextEditingController _controller = TextEditingController();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
int pos = 0;
List<NewsModel> searchList = [];
final List<TextEditingController> _controllerList = [];
bool isNetworkAvail = true;
String query = "";
int notificationoffset = 0;
ScrollController? notificationcontroller;
bool notificationisloadmore = true, notificationisgettingdata = false, notificationisnodata = false;
// Filter related variables
bool isFilterApplied = false;
List<CategoryModel> filterSelectedCategories = [];
List<TagModel> filterSelectedTags = [];
DateTime? filterSelectedDate;
DurationFilter? filterDurationFilter;
Timer? _debounce;
List<NewsModel> history = [];
List<String> hisList = [];
// Apply filters to search results
void applyFilters() {
notificationoffset = 0;
getSearchNews(isFilter: true);
}
@override
void initState() {
super.initState();
searchList.clear();
// Load categories and tags if not already loaded
context.read<CategoryCubit>().loadIfFailed(langId: context.read<AppLocalizationCubit>().state.id);
context.read<TagCubit>().loadIfFailed(langId: context.read<AppLocalizationCubit>().state.id);
notificationoffset = 0;
notificationcontroller = ScrollController(keepScrollOffset: true);
notificationcontroller!.addListener(_searchScrollListener);
_controller.addListener(() {
if (_controller.text.isEmpty) {
if (mounted) {
setState(() {
query = "";
});
}
} else {
query = _controller.text.trim();
notificationoffset = 0;
notificationisnodata = false;
buildResult = false;
if (query.isNotEmpty) {
if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 500), () {
notificationisloadmore = true;
notificationoffset = 0;
getSearchNews();
});
}
}
});
}
_searchScrollListener() {
if (notificationcontroller!.offset >= notificationcontroller!.position.maxScrollExtent && !notificationcontroller!.position.outOfRange) {
if (mounted) {
setState(() {
getSearchNews();
});
}
}
}
Future<List<String>> getHistory() async {
hisList = UiUtils.getDynamicListValue(historyListKey);
return hisList;
}
@override
void dispose() {
notificationcontroller!.dispose();
_controller.dispose();
for (int i = 0; i < _controllerList.length; i++) {
_controllerList[i].dispose();
}
super.dispose();
}
PreferredSizeWidget appbar() {
return AppBar(
leading: const CustomBackButton(horizontalPadding: 15),
backgroundColor: Theme.of(context).canvasColor,
title: TextField(
controller: _controller,
autofocus: true,
decoration: InputDecoration(
contentPadding: const EdgeInsets.fromLTRB(0, 15.0, 0, 15.0),
hintText: UiUtils.getTranslatedLabel(context, 'search'),
hintStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)),
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer)),
focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer)),
fillColor: secondaryColor)),
titleSpacing: 0,
actions: [
IconButton(
onPressed: () {
_controller.text = '';
},
icon: Icon(Icons.close, color: UiUtils.getColorScheme(context).primaryContainer)),
IconButton(
padding: EdgeInsetsDirectional.only(end: 7),
onPressed: () async {
FocusManager.instance.rootScope.unfocus();
final NewsFilterData? result = await showFilterBottomSheet(
context: context,
isCategoryModeON: (context.read<AppConfigurationCubit>().getCategoryMode() == "1"),
initialFilters: NewsFilterData(
selectedCategories: filterSelectedCategories,
selectedTags: filterSelectedTags,
selectedDate: filterSelectedDate,
durationFilter: filterDurationFilter,
),
);
if (result != null) {
setState(() {
buildResult = true;
isFilterApplied = true;
filterSelectedCategories = result.selectedCategories;
filterSelectedTags = result.selectedTags;
filterSelectedDate = result.selectedDate;
filterDurationFilter = result.durationFilter;
applyFilters();
});
} else if (result == null) {
setState(() {
buildResult = false;
isFilterApplied = false;
filterSelectedCategories = [];
filterSelectedTags = [];
filterSelectedDate = null;
filterDurationFilter = null;
});
}
},
icon: Icon(
Icons.filter_list,
color: UiUtils.getColorScheme(context).primaryContainer,
))
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: appbar(),
body: _showContent(),
);
}
Widget listItem(int index) {
if (_controllerList.length < index + 1) {
_controllerList.add(TextEditingController());
}
return Padding(
padding: const EdgeInsetsDirectional.only(bottom: 7.0),
child: ListTile(
title: CustomTextLabel(
text: searchList[index].title!,
textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.bold),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
leading: ClipRRect(
borderRadius: BorderRadius.circular(7.0), child: CustomNetworkImage(networkImageUrl: searchList[index].image ?? "", height: 80, width: 80, fit: BoxFit.cover, isVideo: false)),
onTap: () async {
FocusScope.of(context).requestFocus(FocusNode());
List<NewsModel> addNewsList = [];
addNewsList.addAll(searchList);
addNewsList.removeAt(index);
UiUtils.showInterstitialAds(context: context);
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": searchList[index], "newsList": addNewsList, "isFromBreak": false, "fromShowMore": false});
}));
}
Future getSearchNews({bool? isFilter}) async {
if (await InternetConnectivity.isNetworkAvailable()) {
try {
String latitude = SettingsLocalDataRepository().getLocationCityValues().first;
String longitude = SettingsLocalDataRepository().getLocationCityValues().last;
if (notificationisloadmore || (isFilter ?? false)) {
if (mounted) {
setState(() {
notificationisloadmore = false;
notificationisgettingdata = true;
if (notificationoffset == 0) {
searchList = [];
}
});
}
var parameter = {
if (query.isNotEmpty) SEARCH: query.trim(),
LIMIT: "20",
OFFSET: notificationoffset.toString(),
LANGUAGE_ID: context.read<AppLocalizationCubit>().state.id,
};
if (latitude != "null" && longitude != "null") {
parameter[LATITUDE] = latitude;
parameter[LONGITUDE] = longitude;
}
// Apply filters if present
if (filterSelectedCategories.isNotEmpty) {
parameter[CATEGORY_ID] = filterSelectedCategories.map((cat) => cat.id).join(',');
}
if (filterSelectedTags.isNotEmpty) {
parameter[TAG_ID] = filterSelectedTags.map((tag) => tag.id).join(',');
}
if (filterSelectedDate != null) {
parameter[DATE] = "${filterSelectedDate!.year}-${filterSelectedDate!.month.toString().padLeft(2, '0')}-${filterSelectedDate!.day.toString().padLeft(2, '0')}";
}
if (filterDurationFilter != null) {
///To map will be called so automatic it will add the parm of selected type
parameter.addAll(filterDurationFilter!.toMap());
}
final result = await Api.sendApiRequest(body: parameter, url: Api.getNewsApi);
bool error = result["error"];
int totalResult = result[TOTAL];
notificationisgettingdata = false;
if (notificationoffset == 0) {
if (error == false && totalResult > 0) {
notificationisnodata = false;
} else {
notificationisnodata = true;
}
}
if (error == false && totalResult > 0) {
if (mounted) {
Future.delayed(
Duration.zero,
() => setState(() {
List mainlist = result[DATA];
if (mainlist.isNotEmpty) {
List<NewsModel> items = [];
List<NewsModel> allItems = [];
items.addAll(mainlist.map((data) => NewsModel.fromJson(data)).toList());
allItems.addAll(items);
if (notificationoffset == 0 && (isFilter ?? !buildResult)) {
NewsModel element = NewsModel(title: '${UiUtils.getTranslatedLabel(context, 'searchForLbl')} "$query"', image: "", history: false);
searchList.insert(0, element);
for (int i = 0; i < history.length; i++) {
if (history[i].title == query) {
searchList.insert(0, history[i]);
}
}
}
for (NewsModel item in items) {
searchList.where((i) => i.id == item.id).map((obj) {
allItems.remove(item);
return obj;
}).toList();
}
searchList.addAll(allItems);
notificationisloadmore = false;
notificationoffset = notificationoffset + limitOfAPIData;
} else {
notificationisloadmore = false;
}
}));
}
} else {
notificationisloadmore = false;
setState(() {});
}
}
} on TimeoutException catch (_) {
showSnackBar(UiUtils.getTranslatedLabel(context, 'somethingMSg'), context);
setState(() {
notificationisloadmore = false;
});
} catch (e) {
setState(() {
notificationisnodata = true;
notificationisloadmore = false;
});
}
} else {
setState(() {
isNetworkAvail = false;
});
}
}
clearAll() {
setState(() {
query = _controller.text;
notificationoffset = 0;
notificationisloadmore = true;
searchList.clear();
});
}
_showContent() {
if (!isFilterApplied && _controller.text == "") {
return FutureBuilder<List<String>>(
future: getHistory(),
builder: (BuildContext context, AsyncSnapshot<List<String>> snapshot) {
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
final List<String> entities = snapshot.data!;
final List<NewsModel> itemList = [];
for (int i = 0; i < entities.length; i++) {
NewsModel item = NewsModel.history(entities[i]);
itemList.add(item);
}
history.clear();
history.addAll(itemList);
return SingleChildScrollView(
padding: const EdgeInsetsDirectional.only(top: 15.0),
child: Column(
children: [
_SuggestionList(
textController: _controller,
suggestions: itemList,
notificationController: notificationcontroller,
getProduct: getSearchNews,
clearAll: clearAll,
),
],
),
);
} else {
return const Column();
}
});
} else if (buildResult) {
return notificationisnodata
? const Center(child: CustomTextLabel(text: 'noNews'))
: Padding(
padding: const EdgeInsetsDirectional.only(top: 15.0),
child: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
padding: const EdgeInsetsDirectional.only(bottom: 5, start: 10, end: 10, top: 12),
controller: notificationcontroller,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: searchList.length,
itemBuilder: (context, index) {
NewsModel? item;
try {
item = searchList.isEmpty ? null : searchList[index];
if (notificationisloadmore && index == (searchList.length - 1) && notificationcontroller!.position.pixels <= 0) {
getSearchNews();
}
} on Exception catch (_) {}
return item == null ? const SizedBox.shrink() : listItem(index);
}),
),
notificationisgettingdata ? const Padding(padding: EdgeInsetsDirectional.only(top: 5, bottom: 5), child: CircularProgressIndicator()) : const SizedBox.shrink()
],
));
}
return notificationisnodata
? const Center(child: CustomTextLabel(text: 'noNews'))
: Padding(
padding: const EdgeInsetsDirectional.only(top: 15.0),
child: Column(
children: <Widget>[
Expanded(child: _SuggestionList(textController: _controller, suggestions: searchList, notificationController: notificationcontroller, getProduct: getSearchNews, clearAll: clearAll)),
notificationisgettingdata ? const Padding(padding: EdgeInsetsDirectional.only(top: 5, bottom: 5), child: CircularProgressIndicator()) : const SizedBox.shrink()
],
));
}
}
class _SuggestionList extends StatelessWidget {
const _SuggestionList({this.suggestions, this.textController, this.notificationController, this.getProduct, this.clearAll});
final List<NewsModel>? suggestions;
final TextEditingController? textController;
final notificationController;
final Function? getProduct, clearAll;
@override
Widget build(BuildContext context) {
return ListView.separated(
itemCount: suggestions!.length,
shrinkWrap: true,
controller: notificationController,
separatorBuilder: (BuildContext context, int index) => const Divider(),
itemBuilder: (BuildContext context, int i) {
final NewsModel suggestion = suggestions![i];
return ListTile(
title: CustomTextLabel(
text: suggestion.title!,
textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.bold),
maxLines: 2,
overflow: TextOverflow.ellipsis),
leading: textController!.text.toString().trim().isEmpty || suggestion.history!
? const Icon(Icons.history)
: ClipRRect(
borderRadius: BorderRadius.circular(7.0),
child: suggestion.image == ''
? Image.asset(UiUtils.getPlaceholderPngPath(), height: 80, width: 80)
: CustomNetworkImage(networkImageUrl: suggestion.image!, height: 80, width: 80, fit: BoxFit.cover, isVideo: false)),
trailing: SvgPictureWidget(
assetName: "searchbar_arrow",
height: 11,
width: 11,
fit: BoxFit.contain,
assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), BlendMode.srcIn)),
onTap: () async {
if (suggestion.title!.startsWith('${UiUtils.getTranslatedLabel(context, 'searchForLbl')} ')) {
UiUtils.setDynamicListValue(historyListKey, textController!.text.toString().trim());
buildResult = true;
clearAll!();
getProduct!();
} else if (suggestion.history!) {
clearAll!();
buildResult = true;
textController!.text = suggestion.title!;
textController!.selection = TextSelection.fromPosition(TextPosition(offset: textController!.text.length));
} else {
UiUtils.setDynamicListValue(historyListKey, textController!.text.trim());
buildResult = false;
UiUtils.showInterstitialAds(context: context);
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": suggestion, "isFromBreak": false, "fromShowMore": false});
}
});
},
);
}
}