elCaribe app - customization and branding

This commit is contained in:
2025-12-12 19:09:42 -04:00
parent 9e5d0d8ebf
commit ba7deac9f3
402 changed files with 31833 additions and 0 deletions

View File

@@ -0,0 +1,138 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:news/ui/styles/colors.dart';
import 'package:news/ui/widgets/customBackBtn.dart';
import 'package:news/ui/widgets/customTextLabel.dart';
import 'package:news/ui/widgets/shimmerNewsList.dart';
import 'package:news/ui/screens/SubCategory/Widgets/SubCatNewsList.dart';
import 'package:news/ui/screens/SubCategory/Widgets/categoryShimmer.dart';
import 'package:news/utils/uiUtils.dart';
import 'package:news/cubits/subCategoryCubit.dart';
import 'package:news/cubits/appLocalizationCubit.dart';
import 'package:news/cubits/appSystemSettingCubit.dart';
import 'package:news/cubits/subCatNewsCubit.dart';
import 'package:news/data/repositories/SubCatNews/subCatRepository.dart';
class SubCategoryScreen extends StatefulWidget {
final String catId;
final String catName;
const SubCategoryScreen({super.key, required this.catId, required this.catName});
@override
SubCategoryScreenState createState() => SubCategoryScreenState();
static Route route(RouteSettings routeSettings) {
final arguments = routeSettings.arguments as Map<String, dynamic>;
return CupertinoPageRoute(builder: (_) => SubCategoryScreen(catId: arguments['catId'], catName: arguments['catName']));
}
}
class SubCategoryScreenState extends State<SubCategoryScreen> with TickerProviderStateMixin {
final List<Map<String, dynamic>> _tabs = [];
TabController? _tc;
bool isSubcatExist = true;
@override
void initState() {
getSubCatData();
super.initState();
}
@override
void dispose() {
if (_tc != null) {
_tc!.dispose();
}
super.dispose();
}
Future getSubCatData() async {
Future.delayed(Duration.zero, () {
context.read<SubCategoryCubit>().getSubCategory(context: context, langId: context.read<AppLocalizationCubit>().state.id, catId: widget.catId);
});
}
Widget subcatData() {
return BlocConsumer<SubCategoryCubit, SubCategoryState>(
bloc: context.read<SubCategoryCubit>(),
listener: (context, state) {
if (state is SubCategoryFetchSuccess) {
setState(() {
for (int i = 0; i < state.subCategory.length; i++) {
_tabs.add({'text': state.subCategory[i].subCatName, 'subCatId': state.subCategory[i].id});
}
_tc = TabController(vsync: this, length: state.subCategory.length)..addListener(() {});
});
}
if (state is SubCategoryFetchFailure) {
isSubcatExist = false;
setState(() {});
}
},
builder: (context, state) {
if (state is SubCategoryFetchSuccess) {
return DefaultTabController(
length: state.subCategory.length,
child: _tabs.isNotEmpty
? TabBar(
controller: _tc,
labelColor: secondaryColor,
indicatorColor: Colors.transparent,
isScrollable: true,
padding: const EdgeInsets.only(top: 10, bottom: 5, left: 3, right: 3),
physics: const AlwaysScrollableScrollPhysics(),
unselectedLabelColor: UiUtils.getColorScheme(context).primaryContainer,
indicator: BoxDecoration(borderRadius: BorderRadius.circular(5.0), color: UiUtils.getColorScheme(context).secondaryContainer),
tabs: _tabs
.map((tab) => AnimatedContainer(
height: 30, duration: const Duration(milliseconds: 600), padding: const EdgeInsetsDirectional.only(top: 5.0, bottom: 5.0), child: Tab(text: tab['text'])))
.toList())
: subcatShimmer());
}
if (state is SubCategoryFetchFailure) {
return const SizedBox.shrink();
}
//state is SubCategoryFetchInProgress || state is SubCategoryInitial
return subcatShimmer();
});
}
setAppBar() {
return PreferredSize(
preferredSize: Size(double.infinity, (isSubcatExist) ? 80 : 40),
child: AppBar(
leading: const CustomBackButton(horizontalPadding: 15),
titleSpacing: 0.0,
centerTitle: false,
backgroundColor: Colors.transparent,
title: CustomTextLabel(
text: widget.catName,
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5)),
bottom: PreferredSize(preferredSize: Size(MediaQuery.of(context).size.width, 30), child: subcatData()),
));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: setAppBar(),
body: BlocBuilder<SubCategoryCubit, SubCategoryState>(builder: (context, state) {
if (state is SubCategoryFetchFailure || context.read<AppConfigurationCubit>().getSubCatMode() != "1") {
return BlocProvider<SubCatNewsCubit>(create: (_) => SubCatNewsCubit(SubCatNewsRepository()), child: SubCatNewsList(from: 1, catId: widget.catId));
}
if (state is SubCategoryFetchSuccess && _tc != null && _tc!.length > 0) {
return TabBarView(
controller: _tc,
children: List<Widget>.generate(_tc!.length, (int index) {
return BlocProvider<SubCatNewsCubit>(
create: (_) => SubCatNewsCubit(SubCatNewsRepository()),
child: SubCatNewsList(from: index == 0 ? 1 : 2, subCatId: index == 0 ? null : _tabs[index]['subCatId'], catId: widget.catId));
}));
}
return ShimmerNewsList(isNews: true);
}),
);
}
}

View File

@@ -0,0 +1,197 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:news/cubits/subCatNewsCubit.dart';
import 'package:news/cubits/surveyQuestionCubit.dart';
import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart';
import 'package:news/ui/widgets/NewsItem.dart';
import 'package:news/ui/screens/SubCategory/Widgets/showSurveyQuestion.dart';
import 'package:news/ui/screens/SubCategory/Widgets/showSurveyResult.dart';
import 'package:news/ui/widgets/errorContainerWidget.dart';
import 'package:news/ui/widgets/shimmerNewsList.dart';
import 'package:news/utils/ErrorMessageKeys.dart';
import 'package:news/utils/constant.dart';
import 'package:news/cubits/Auth/authCubit.dart';
import 'package:news/cubits/appLocalizationCubit.dart';
import 'package:news/data/models/NewsModel.dart';
import 'package:news/utils/uiUtils.dart';
class SubCatNewsList extends StatefulWidget {
final int from;
final String catId;
final String? subCatId;
const SubCatNewsList({super.key, required this.from, required this.catId, this.subCatId});
@override
SubCatNewsListState createState() => SubCatNewsListState();
static Route route(RouteSettings routeSettings) {
final arguments = routeSettings.arguments as Map<String, dynamic>;
return CupertinoPageRoute(builder: (_) => SubCatNewsList(from: arguments['from'], catId: arguments['catId'], subCatId: arguments['subCatId']));
}
}
class SubCatNewsListState extends State<SubCatNewsList> with AutomaticKeepAliveClientMixin {
List<NewsModel> combineList = [];
int totalSurveyQue = 0;
Set<String> get locationValue => SettingsLocalDataRepository().getLocationCityValues();
late final ScrollController controller = ScrollController()..addListener(hasMoreNotiScrollListener);
@override
void initState() {
getSubCatNewsData();
super.initState();
}
@override
void dispose() {
totalSurveyQue = 0;
controller.dispose();
super.dispose();
}
Future getSubCatNewsData() async {
Future.delayed(Duration.zero, () {
if (context.read<AuthCubit>().getUserId() != "0") {
context.read<SurveyQuestionCubit>().getSurveyQuestion(langId: context.read<AppLocalizationCubit>().state.id).whenComplete(() {
getSubcategoryNews();
});
} else {
getSubcategoryNews();
}
});
}
void hasMoreNotiScrollListener() {
if (controller.position.maxScrollExtent == controller.offset) {
if (context.read<SubCatNewsCubit>().hasMoreSubCatNews()) {
context.read<SubCatNewsCubit>().getMoreSubCatNews(
langId: context.read<AppLocalizationCubit>().state.id,
subCatId: widget.from == 2 ? widget.subCatId : null,
catId: widget.from == 1 ? widget.catId : null,
latitude: locationValue.first,
longitude: locationValue.last);
} else {}
}
}
updateCombineList(NewsModel model, int index) {
setState(() {
combineList[index] = model;
});
}
Widget getNewsList() {
return BlocConsumer<SubCatNewsCubit, SubCatNewsState>(
bloc: context.read<SubCatNewsCubit>(),
listener: (context, state) {
if (state is SubCatNewsFetchSuccess) {
combineList.clear();
int cur = 0;
for (int i = 0; i < (state).subCatNews.length; i++) {
if (i != 0 && i % surveyShow == 0) {
if (context.read<SurveyQuestionCubit>().surveyList().isNotEmpty && context.read<SurveyQuestionCubit>().surveyList().length > cur) {
combineList.add(context.read<SurveyQuestionCubit>().surveyList()[cur]);
cur++;
}
}
combineList.add((state).subCatNews[i]);
}
}
},
builder: (context, stateSubCat) {
if (stateSubCat is SubCatNewsFetchSuccess) {
return combineNewsList(stateSubCat, stateSubCat.subCatNews);
}
if (stateSubCat is SubCatNewsFetchFailure) {
return ErrorContainerWidget(
errorMsg: (stateSubCat.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : stateSubCat.errorMessage, onRetry: getSubCatNewsData);
}
//stateSubCat is SubCatNewsFetchInProgress || stateSubCat is SubCatNewsInitial
return ShimmerNewsList(isNews: true);
});
}
setTotalSurveyQueCount() {
for (var element in combineList) {
if (element.type == "survey") totalSurveyQue += 1;
}
}
Widget combineNewsList(SubCatNewsFetchSuccess state, List<NewsModel> newsList) {
setTotalSurveyQueCount();
return RefreshIndicator(
onRefresh: () async {
combineList.clear();
if (context.read<AuthCubit>().getUserId() != "0") {
context.read<SurveyQuestionCubit>().getSurveyQuestion(langId: context.read<AppLocalizationCubit>().state.id).whenComplete(() {
getSubcategoryNews();
});
} else {
getSubcategoryNews();
}
setTotalSurveyQueCount();
setState(() {});
},
child: ListView.builder(
padding: const EdgeInsetsDirectional.symmetric(vertical: 15),
physics: const AlwaysScrollableScrollPhysics(),
controller: controller,
itemCount: combineList.length,
itemBuilder: (context, index) {
return _buildNewsContainer(
model: combineList[index], hasMore: state.hasMore, hasMoreNewsFetchError: state.hasMoreFetchError, index: index, totalCurrentNews: combineList.length, newsList: newsList);
}),
);
}
void getSubcategoryNews() {
context.read<SubCatNewsCubit>().getSubCatNews(
langId: context.read<AppLocalizationCubit>().state.id,
subCatId: widget.from == 2 ? widget.subCatId : null,
catId: widget.from == 1 ? widget.catId : null,
latitude: locationValue.first,
longitude: locationValue.last);
}
_buildNewsContainer({required NewsModel model, required int index, required int totalCurrentNews, required bool hasMoreNewsFetchError, required bool hasMore, required List<NewsModel> newsList}) {
if (index == totalCurrentNews - 1 && index != 0) {
if (hasMore) {
if (hasMoreNewsFetchError) {
return Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0),
child: IconButton(
onPressed: () {
context.read<SubCatNewsCubit>().getMoreSubCatNews(
langId: context.read<AppLocalizationCubit>().state.id,
subCatId: widget.from == 2 ? widget.subCatId : null,
catId: widget.from == 1 ? widget.catId : null,
latitude: locationValue.first,
longitude: locationValue.last);
},
icon: Icon(Icons.error, color: Theme.of(context).primaryColor)),
),
);
} else {
return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)));
}
}
}
return model.type == "survey"
? model.from == 2
? showSurveyQueResult(model, context, true)
: ShowSurveyQue(model: model, index: index, surveyId: context.read<SurveyQuestionCubit>().getSurveyQuestionIndex(questionTitle: model.question!), updateList: updateCombineList)
: NewsItem(model: model, index: (index <= totalSurveyQue) ? index : (index - totalSurveyQue), newslist: newsList, fromShowMore: false);
}
@override
Widget build(BuildContext context) {
super.build(context);
return getNewsList();
}
@override
bool get wantKeepAlive => true;
}

View File

@@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
subcatShimmer() {
return Shimmer.fromColors(
baseColor: Colors.grey.withOpacity(0.4),
highlightColor: Colors.grey.withOpacity(0.4),
child: SingleChildScrollView(
padding: const EdgeInsets.only(top: 10),
scrollDirection: Axis.horizontal,
child: Row(
children: [0, 1, 2, 3, 4, 5, 6]
.map((i) => Padding(
padding: const EdgeInsetsDirectional.only(start: 15, top: 0),
child: Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5.0), color: const Color.fromARGB(255, 59, 49, 49)),
height: 32.0,
width: 70.0,
)))
.toList()),
));
}

View File

@@ -0,0 +1,134 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:news/cubits/getSurveyAnswerCubit.dart';
import 'package:news/cubits/setSurveyAnswerCubit.dart';
import 'package:news/cubits/surveyQuestionCubit.dart';
import 'package:news/ui/styles/colors.dart';
import 'package:news/ui/widgets/customTextLabel.dart';
import 'package:news/utils/uiUtils.dart';
import 'package:news/cubits/appLocalizationCubit.dart';
import 'package:news/data/models/NewsModel.dart';
import 'package:news/ui/widgets/SnackBarWidget.dart';
class ShowSurveyQue extends StatefulWidget {
final NewsModel model;
final int index;
final String surveyId;
final bool isPaddingRequired;
final Function(NewsModel, int) updateList;
const ShowSurveyQue({super.key, required this.model, required this.index, required this.surveyId, required this.updateList, this.isPaddingRequired = true});
@override
ShowSurveyQueState createState() => ShowSurveyQueState();
}
class ShowSurveyQueState extends State<ShowSurveyQue> {
String? optSel;
@override
Widget build(BuildContext context) {
return showSurveyQue();
}
Widget showSurveyQue() {
return BlocConsumer<SetSurveyAnsCubit, SetSurveyAnsState>(
bloc: context.read<SetSurveyAnsCubit>(),
listener: (context, state) {
if (state is SetSurveyAnsInitial || state is SetSurveyAnsFetchInProgress) {
Center(child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor));
}
if (state is SetSurveyAnsFetchSuccess) {
context.read<SurveyQuestionCubit>().removeQuestion(widget.surveyId);
context.read<GetSurveyAnsCubit>().getSurveyAns(langId: context.read<AppLocalizationCubit>().state.id);
}
},
builder: (context, state) {
return BlocConsumer<GetSurveyAnsCubit, GetSurveyAnsState>(
bloc: context.read<GetSurveyAnsCubit>(),
listener: (context, state) {
if (state is GetSurveyAnsInitial || state is GetSurveyAnsFetchInProgress) {
Center(child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor));
}
if (state is GetSurveyAnsFetchSuccess) {
for (var element in state.getSurveyAns) {
if (element.id == widget.model.id) {
NewsModel newModel = element;
newModel.from = 2;
widget.updateList(newModel, widget.index);
}
}
}
},
builder: (context, state) {
return Padding(
padding: EdgeInsets.only(top: 15.0, left: (widget.isPaddingRequired) ? 15 : 0, right: (widget.isPaddingRequired) ? 15 : 0),
child: Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface),
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
CustomTextLabel(
text: widget.model.question!, textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, height: 1.0)),
Padding(
padding: const EdgeInsetsDirectional.only(top: 15.0, start: 7.0, end: 7.0),
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
physics: const NeverScrollableScrollPhysics(),
itemCount: widget.model.optionDataList!.length,
itemBuilder: (context, j) {
return Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: InkWell(
child: Container(
height: 50,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: optSel == widget.model.optionDataList![j].id ? Theme.of(context).primaryColor.withOpacity(0.1) : UiUtils.getColorScheme(context).secondary),
child: CustomTextLabel(
text: widget.model.optionDataList![j].options!,
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(
color: optSel == widget.model.optionDataList![j].id ? Theme.of(context).primaryColor : UiUtils.getColorScheme(context).primaryContainer,
height: 1.0),
textAlign: TextAlign.center)),
onTap: () {
setState(() {
optSel = widget.model.optionDataList![j].id;
});
},
),
);
})),
Padding(
padding: const EdgeInsets.only(top: 15.0),
child: InkWell(
child: Container(
height: 40.0,
width: MediaQuery.of(context).size.width * 0.35,
alignment: Alignment.center,
decoration: BoxDecoration(color: secondaryColor, borderRadius: BorderRadius.circular(7.0)),
child: CustomTextLabel(
text: 'submitBtn',
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).primaryColor, fontWeight: FontWeight.w600, letterSpacing: 0.6),
),
),
onTap: () async {
if (optSel != null) {
//get survey id from survey question
String currentIndex = context.read<SurveyQuestionCubit>().getSurveyQuestionIndex(questionTitle: widget.model.question!);
context.read<SetSurveyAnsCubit>().setSurveyAns(optId: optSel!, queId: currentIndex);
await Future.delayed(Duration(seconds: 2));
} else {
showSnackBar(UiUtils.getTranslatedLabel(context, 'optSel'), context);
}
}),
)
],
)));
});
});
}
}

View File

@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:news/ui/widgets/customTextLabel.dart';
import 'package:news/utils/uiUtils.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
import 'package:news/data/models/NewsModel.dart';
showSurveyQueResult(NewsModel model, BuildContext context, bool isPaddingRequired) {
return Padding(
padding: EdgeInsets.only(top: 15.0, left: (isPaddingRequired) ? 15 : 0, right: (isPaddingRequired) ? 15 : 0),
child: Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface),
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
CustomTextLabel(text: model.question!, textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, height: 1.0)),
Padding(
padding: const EdgeInsetsDirectional.only(top: 15.0, start: 7.0, end: 7.0),
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
physics: const NeverScrollableScrollPhysics(),
itemCount: model.optionDataList!.length,
itemBuilder: (context, j) {
return Padding(
padding: const EdgeInsetsDirectional.only(bottom: 10.0, start: 15.0, end: 15.0),
child: LinearPercentIndicator(
animation: true,
animationDuration: 1000,
lineHeight: 40.0,
percent: model.optionDataList![j].percentage! / 100,
center: CustomTextLabel(text: "${(model.optionDataList![j].percentage!).toStringAsFixed(2)}%", textStyle: Theme.of(context).textTheme.titleSmall),
barRadius: const Radius.circular(16),
progressColor: Theme.of(context).primaryColor,
isRTL: false,
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0)));
})),
],
)));
}