elCaribe app - customization and branding
This commit is contained in:
488
news-app/lib/ui/screens/Search.dart
Normal file
488
news-app/lib/ui/screens/Search.dart
Normal file
@@ -0,0 +1,488 @@
|
||||
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});
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user