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,507 @@
import 'dart:convert';
import 'dart:io';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:hive/hive.dart';
import 'package:intl/intl.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:news/app/routes.dart';
import 'package:news/cubits/Auth/authCubit.dart';
import 'package:news/cubits/Bookmark/bookmarkCubit.dart';
import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart';
import 'package:news/cubits/appLocalizationCubit.dart';
import 'package:news/cubits/appSystemSettingCubit.dart';
import 'package:news/cubits/languageJsonCubit.dart';
import 'package:news/ui/screens/NewsDetail/Widgets/InterstitialAds/googleInterstitialAds.dart';
import 'package:news/ui/screens/NewsDetail/Widgets/InterstitialAds/unityInterstitialAds.dart';
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart';
import 'package:news/ui/styles/colors.dart';
import 'package:news/ui/widgets/SnackBarWidget.dart';
import 'package:news/ui/widgets/customTextLabel.dart';
import 'package:news/utils/constant.dart';
import 'package:news/utils/labelKeys.dart';
import 'package:news/ui/styles/appTheme.dart';
import 'package:news/utils/hiveBoxKeys.dart';
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';
class UiUtils {
static GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
static Future<void> setDynamicStringValue(String key, String value) async {
Hive.box(settingsBoxKey).put(key, value);
}
static Future<void> setDynamicListValue(String key, String value) async {
List<String>? valueList = getDynamicListValue(key);
if (!valueList.contains(value)) {
if (valueList.length > 4) valueList.removeAt(0);
valueList.add(value);
Hive.box(settingsBoxKey).put(key, valueList);
}
}
static List<String> getDynamicListValue(String key) {
return Hive.box(settingsBoxKey).get(key) ?? [];
}
static String getSvgImagePath(String imageName) {
return "assets/images/svgImage/$imageName.svg";
}
static String getPlaceholderPngPath() {
return "assets/images/placeholder.png";
}
static ColorScheme getColorScheme(BuildContext context) {
return Theme.of(context).colorScheme;
}
// get app theme
static String getThemeLabelFromAppTheme(AppTheme appTheme) {
if (appTheme == AppTheme.Dark) {
return darkThemeKey;
}
return lightThemeKey;
}
static AppTheme getAppThemeFromLabel(String label) {
return (label == darkThemeKey) ? AppTheme.Dark : AppTheme.Light;
}
static String getTranslatedLabel(BuildContext context, String labelKey) {
return context.read<LanguageJsonCubit>().getTranslatedLabels(labelKey);
}
static Future<void> loginRequired(BuildContext context) {
showSnackBar(UiUtils.getTranslatedLabel(context, 'loginReqMsg'), context);
Future.delayed(const Duration(milliseconds: 1000), () {
return Navigator.of(context).pushNamed(Routes.login, arguments: {"isFromApp": true}); //pass isFromApp to get back to specified screen
});
return Future(() => null);
}
static Widget showCircularProgress(bool isProgress, Color color) {
if (isProgress) {
return Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(color)));
}
return const SizedBox.shrink();
}
showUploadImageBottomsheet({required BuildContext context, required VoidCallback? onCamera, required VoidCallback? onGallery}) {
return showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(side: BorderSide(color: Colors.transparent), borderRadius: BorderRadius.only(topLeft: Radius.circular(10), topRight: Radius.circular(10))),
builder: (BuildContext buildContext) {
return SafeArea(
child: Wrap(
children: <Widget>[
ListTile(
leading: SvgPictureWidget(assetName: 'gallaryIcon', assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), BlendMode.srcIn)),
title: CustomTextLabel(text: 'photoLibLbl', textStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer, fontSize: 16, fontWeight: FontWeight.w400)),
onTap: onGallery),
ListTile(
leading: SvgPictureWidget(assetName: 'cameraIcon', assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), BlendMode.srcIn)),
title: CustomTextLabel(text: 'cameraLbl', textStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer, fontSize: 16, fontWeight: FontWeight.w400)),
onTap: onCamera),
],
),
);
});
}
static String? convertToAgo(BuildContext context, DateTime input, int from) {
//from - 0 : NewsItem,details, bookmarks & sectionStyle5 , 1 : Comments, 2: Notifications, 3: SectionStyle6
Duration diff = DateTime.now().difference(input);
if (Intl.defaultLocale != null || Intl.defaultLocale!.isEmpty) Intl.defaultLocale = 'en';
initializeDateFormatting(); //locale according to location
final langCode = Hive.box(settingsBoxKey).get(currentLanguageCodeKey);
bool isNegative = diff.isNegative;
if (diff.inDays >= 365 && from == 1) {
double years = calculateYearsDifference(input, DateTime.now());
return "${years.toStringAsFixed(0)} ${getTranslatedLabel(context, 'years')} ${getTranslatedLabel(context, 'ago')}";
} else {
if (diff.inDays >= 1 || (isNegative && diff.inDays < 1)) {
if (from == 0) {
var newFormat = DateFormat("MMM dd, yyyy", langCode);
final newsDate1 = newFormat.format(input);
return newsDate1;
} else if (from == 1) {
int months = calculateMonthsDifference(input, DateTime.now());
if ((months < 12 && diff.inDays >= 30) && !isNegative) {
return "${months.toStringAsFixed(0)} ${getTranslatedLabel(context, 'months')} ${getTranslatedLabel(context, 'ago')}";
} else if ((diff.inHours >= 1 && diff.inHours < 24)) {
return "${getTranslatedLabel(context, 'about')} ${diff.inHours} ${getTranslatedLabel(context, 'hours')} ${input.minute} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}";
} else if ((isNegative && diff.inMinutes < 1)) {
return "${getTranslatedLabel(context, 'about')} ${input.minute} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}";
} else {
return "${diff.inDays} ${getTranslatedLabel(context, 'days')} ${getTranslatedLabel(context, 'ago')}";
}
} else if (from == 2) {
var newFormat = DateFormat("dd MMMM yyyy", langCode);
final newsDate1 = newFormat.format(input);
return newsDate1;
} else if (from == 3) {
var newFormat = DateFormat("MMMM dd, yyyy", langCode);
final newNewsDate = newFormat.format(input);
return newNewsDate;
}
} else if (diff.inHours >= 1 || (isNegative && diff.inMinutes < 1)) {
if (input.minute == 00) {
return "${diff.inHours} ${getTranslatedLabel(context, 'hours')} ${getTranslatedLabel(context, 'ago')}";
} else {
if (from == 2) {
return "${getTranslatedLabel(context, 'about')} ${diff.inHours} ${getTranslatedLabel(context, 'hours')} ${input.minute} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}";
} else {
return "${diff.inHours} ${getTranslatedLabel(context, 'hours')} ${input.minute} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}";
}
}
} else if (diff.inMinutes >= 1 || (isNegative && diff.inMinutes < 1)) {
return "${diff.inMinutes} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}";
} else if (diff.inSeconds >= 1) {
return "${diff.inSeconds} ${getTranslatedLabel(context, 'seconds')} ${getTranslatedLabel(context, 'ago')}";
} else {
return getTranslatedLabel(context, 'justNow');
}
}
return null;
}
static int calculateMonthsDifference(DateTime startDate, DateTime endDate) {
int yearsDifference = endDate.year - startDate.year;
int monthsDifference = endDate.month - startDate.month;
return yearsDifference * 12 + monthsDifference;
}
static double calculateYearsDifference(DateTime startDate, DateTime endDate) {
int monthsDifference = calculateMonthsDifference(startDate, endDate);
return monthsDifference / 12;
}
/// Pluggable date picker theme builder for consistent styling across the app
/// Usage: Theme(data: Theme.of(context).copyWith(datePickerTheme: UiUtils.buildDatePickerTheme(context)))
static DatePickerThemeData buildDatePickerTheme(BuildContext context) {
final colorScheme = getColorScheme(context);
return DatePickerThemeData(
// Base styling
dayStyle: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
// Header configuration
headerBackgroundColor: colorScheme.primary,
headerForegroundColor: colorScheme.onPrimary,
// Today/Current date styling - ensures proper visibility
todayForegroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return colorScheme.onPrimary; // White text when selected
}
return colorScheme.primary; // Primary color when not selected - fixes white color issue
}),
todayBackgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return colorScheme.primary; // Primary background when selected
}
return Colors.transparent; // No background when not selected
}),
// Regular day styling
dayForegroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return colorScheme.onPrimary; // White text on selected days
}
if (states.contains(WidgetState.disabled)) {
return colorScheme.onSurface.withOpacity(0.38); // Disabled state
}
return colorScheme.onSurface; // Normal text color
}),
dayBackgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return colorScheme.primary; // Primary background for selected
}
return Colors.transparent; // Transparent for unselected
}),
// Year picker styling
yearForegroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return colorScheme.onPrimary;
}
return colorScheme.onSurface;
}),
yearBackgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return colorScheme.primary;
}
return Colors.transparent;
}),
// Weekday header styling
weekdayStyle: TextStyle(color: colorScheme.onSurface.withOpacity(0.6), fontWeight: FontWeight.w500, fontSize: 12),
);
}
/// Pluggable themed date picker wrapper that applies consistent styling
/// Fixes the currentDate white color issue across the entire app
/// Usage: UiUtils.showThemedDatePicker(context: context, ...)
static Future<DateTime?> showThemedDatePicker({
required BuildContext context,
required DateTime initialDate,
required DateTime firstDate,
required DateTime lastDate,
DateTime? currentDate,
String? helpText,
String? cancelText,
String? confirmText,
Locale? locale,
bool useRootNavigator = true,
}) async {
return await showDialog<DateTime>(
context: context,
useRootNavigator: useRootNavigator,
builder: (BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(
datePickerTheme: buildDatePickerTheme(context),
),
child: DatePickerDialog(
initialDate: initialDate,
firstDate: firstDate,
lastDate: lastDate,
currentDate: currentDate,
helpText: helpText,
cancelText: cancelText,
confirmText: confirmText,
),
);
},
);
}
static setUIOverlayStyle({required AppTheme appTheme}) {
appTheme == AppTheme.Light
? SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor: backgroundColor.withOpacity(0.8), statusBarBrightness: Brightness.light, statusBarIconBrightness: Brightness.dark))
: SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(statusBarColor: darkSecondaryColor.withOpacity(0.8), statusBarBrightness: Brightness.dark, statusBarIconBrightness: Brightness.light));
}
static userLogOut({required BuildContext contxt}) {
for (int i = 0; i < AuthProviders.values.length; i++) {
if (AuthProviders.values[i].name == contxt.read<AuthCubit>().getType()) {
contxt.read<BookmarkCubit>().resetState();
contxt.read<LikeAndDisLikeCubit>().resetState();
contxt.read<AuthCubit>().signOut(AuthProviders.values[i]).then((value) {
Navigator.of(contxt).pushNamedAndRemoveUntil(Routes.login, (route) => false);
});
}
}
}
//widget for User Profile Picture in Comments
static Widget setFixedSizeboxForProfilePicture({required Widget childWidget}) {
return SizedBox(height: 35, width: 35, child: childWidget);
}
static Future<bool> isValidLocale(String locale) async {
try {
await initializeDateFormatting(locale, null);
DateFormat('EEEE', locale).format(DateTime.now()); // test formatting
return true;
} catch (e) {
return false;
}
}
static Future<void> setValidDefaultLocale(String? locale) async {
if (locale == null || locale.isEmpty || !(await isValidLocale(locale))) {
Intl.defaultLocale = 'en';
} else {
Intl.defaultLocale = locale;
}
Hive.box(settingsBoxKey).put(currentLanguageCodeKey, Intl.defaultLocale);
}
static Future<void> checkIfValidLocale({required String langCode}) async {
await setValidDefaultLocale(langCode); //pass langCode here
}
//Add & Edit News Screen
//roundedRectangle dashed border widget
static Widget dottedRRectBorder({required Widget childWidget}) {
return DottedBorder(options: RoundedRectDottedBorderOptions(radius: const Radius.circular(10), dashPattern: const [6, 3]), child: ClipRRect(child: Center(child: childWidget)));
}
static Widget dropdownArrow({required BuildContext context}) {
return Align(alignment: Alignment.centerRight, child: Icon(Icons.keyboard_arrow_down_outlined, color: getColorScheme(context).primaryContainer));
}
static Widget setRowWithContainer({required BuildContext context, required Widget firstChild, required bool isContentTypeUpload}) {
return Container(
width: double.maxFinite,
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 15),
decoration: BoxDecoration(color: getColorScheme(context).surface, borderRadius: BorderRadius.circular(10.0)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
firstChild,
(isContentTypeUpload)
? Padding(
padding: const EdgeInsetsDirectional.only(start: 20.0),
child: Align(alignment: Alignment.centerRight, child: Icon(Icons.file_upload_outlined, color: getColorScheme(context).primaryContainer)))
: dropdownArrow(context: context)
],
),
);
}
static Widget setBottomsheetContainer({required String listItem, required String compareTo, required BuildContext context, required String entryId}) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0), color: (compareTo != "" && compareTo == entryId) ? Theme.of(context).primaryColor : getColorScheme(context).primaryContainer.withOpacity(0.1)),
padding: const EdgeInsets.all(10.0),
alignment: Alignment.center,
child: CustomTextLabel(
text: listItem,
textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: (compareTo == entryId) ? getColorScheme(context).secondary : getColorScheme(context).primaryContainer)));
}
static Widget setTopPaddingParent({required Widget childWidget}) {
return Padding(padding: const EdgeInsets.only(top: 10.0), child: childWidget);
}
//Home Screen - featured sections
static Widget setPlayButton({required BuildContext context, double heightVal = 40}) {
return Container(
alignment: Alignment.center,
height: heightVal,
width: heightVal,
decoration: BoxDecoration(shape: BoxShape.circle, color: Theme.of(context).primaryColor),
child: const Icon(Icons.play_arrow_sharp, size: 25, color: secondaryColor));
}
//Native Ads
static BannerAd createBannerAd({required BuildContext context}) {
return BannerAd(
adUnitId: context.read<AppConfigurationCubit>().bannerId()!,
request: const AdRequest(),
size: AdSize.mediumRectangle,
listener: BannerAdListener(
onAdLoaded: (_) => debugPrint("native ad is Loaded !!!"),
onAdFailedToLoad: (ad, err) {
ad.dispose();
},
onAdOpened: (Ad ad) => debugPrint('Native ad opened.'),
// Called when an ad opens an overlay that covers the screen.
onAdClosed: (Ad ad) => debugPrint('Native ad closed.'),
// Called when an ad removes an overlay that covers the screen.
onAdImpression: (Ad ad) => debugPrint('Native ad impression.')));
}
static Widget bannerAdsShow({required BuildContext context}) {
return AdWidget(key: UniqueKey(), ad: createBannerAd(context: context)..load());
}
//Interstitial Ads
static showInterstitialAds({required BuildContext context}) {
if (context.read<AppConfigurationCubit>().getInAppAdsMode() == "1") {
if (context.read<AppConfigurationCubit>().checkAdsType() == "google") {
showGoogleInterstitialAd(context);
} else {
showUnityInterstitialAds(context.read<AppConfigurationCubit>().interstitialId()!);
}
}
}
static void shareNews(
{required BuildContext context, required String title, required String slug, required bool isBreakingNews, required bool isNews, required bool isVideo, required String videoId}) {
String contentType = "";
if (isVideo) contentType = "video-news";
if (isNews) contentType = "news";
if (isBreakingNews) contentType = "breaking-news";
String shareLink = "$shareNavigationWebUrl/${context.read<AppLocalizationCubit>().state.languageCode}/$contentType/$slug?language_id=${context.read<AppLocalizationCubit>().state.id}&share=true";
String str = "$title\n\n$appName\n\n${context.read<AppConfigurationCubit>().getShareAppText()}\n\n${context.read<AppConfigurationCubit>().getAndroidAppLink()}\n";
bool isIOSAppLive = context.read<AppConfigurationCubit>().getiOSAppLink()?.trim().isNotEmpty ?? false;
if (isIOSAppLive) str += "\n\n${context.read<AppConfigurationCubit>().getiOSAppLink()}";
str = shareLink + "\n\n" + str;
Share.share(str, subject: appName, sharePositionOrigin: Rect.fromLTWH(0, 0, MediaQuery.of(context).size.width, MediaQuery.of(context).size.height / 2));
}
//calculate time in Minutes to Read News Article
static int calculateReadingTime(String text) {
const wordsPerMinute = 200;
final wordCount = text.trim().split(' ').length;
final readTime = (wordCount / wordsPerMinute).ceil();
return readTime;
}
static Widget applyBoxShadow({required BuildContext context, Widget? child}) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).canvasColor,
boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.1), offset: Offset(0, 3), blurRadius: 6),
],
),
child: child);
}
static String formatDate(String date) {
DateTime dateTime = DateTime.parse(date);
final DateFormat formatter = DateFormat("MMMM d,yyyy");
return formatter.format(dateTime);
}
static gotoStores(BuildContext context) async {
String iosLink = context.read<AppConfigurationCubit>().getiOSAppLink() ?? "";
String androidLink = context.read<AppConfigurationCubit>().getAndroidAppLink() ?? "";
if (await canLaunchUrl(Uri.parse((Platform.isIOS) ? iosLink : androidLink))) {
await launchUrl(Uri.parse((Platform.isIOS) ? iosLink : androidLink), mode: LaunchMode.externalApplication);
}
if (Navigator.of(context).canPop()) Navigator.of(context).pop(false);
}
static String decryptKey({required geminiKey}) {
// Decode Base64 to bytes
Uint8List bytes = base64.decode(geminiKey);
// Convert bytes to String (if it's text)
String decodedString = utf8.decode(bytes);
return decodedString;
}
}
Widget nativeAdsShow({required BuildContext context, required int index}) {
if (context.read<AppConfigurationCubit>().getInAppAdsMode() == "1" &&
context.read<AppConfigurationCubit>().checkAdsType() != null &&
(context.read<AppConfigurationCubit>().getIOSAdsType() != "unity" || context.read<AppConfigurationCubit>().getAdsType() != "unity") &&
index != 0 &&
index % nativeAdsIndex == 0) {
return Padding(
padding: const EdgeInsets.only(bottom: 15.0),
child: Container(
padding: const EdgeInsets.all(7.0),
height: 300,
width: double.infinity,
decoration: BoxDecoration(color: Colors.white.withOpacity(0.5), borderRadius: BorderRadius.circular(10.0)),
child: context.read<AppConfigurationCubit>().checkAdsType() == "google" && (context.read<AppConfigurationCubit>().bannerId() != "")
? UiUtils.bannerAdsShow(context: context)
: SizedBox.shrink()));
} else {
return const SizedBox.shrink();
}
}