elCaribe app - customization and branding
This commit is contained in:
256
news-app/lib/ui/widgets/NewsItem.dart
Normal file
256
news-app/lib/ui/widgets/NewsItem.dart
Normal file
@@ -0,0 +1,256 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/newsCard.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/Bookmark/UpdateBookmarkCubit.dart';
|
||||
import 'package:news/cubits/Bookmark/bookmarkCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart';
|
||||
import 'package:news/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/data/repositories/Bookmark/bookmarkRepository.dart';
|
||||
import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
|
||||
class NewsItem extends StatefulWidget {
|
||||
final NewsModel model;
|
||||
final int index;
|
||||
final List<NewsModel> newslist;
|
||||
final bool fromShowMore;
|
||||
|
||||
const NewsItem({super.key, required this.model, required this.index, required this.newslist, required this.fromShowMore});
|
||||
|
||||
@override
|
||||
NewsItemState createState() => NewsItemState();
|
||||
}
|
||||
|
||||
class NewsItemState extends State<NewsItem> {
|
||||
late BannerAd bannerAd;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (context.read<AppConfigurationCubit>().getInAppAdsMode() == "1" &&
|
||||
(context.read<AppConfigurationCubit>().getAdsType() != "unity" || context.read<AppConfigurationCubit>().getIOSAdsType() != "unity")) bannerAd = UiUtils.createBannerAd(context: context);
|
||||
}
|
||||
|
||||
Widget setButton({required Widget childWidget}) {
|
||||
return Container(padding: const EdgeInsets.all(3), decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: UiUtils.getColorScheme(context).secondary), child: childWidget);
|
||||
}
|
||||
|
||||
Widget setNewsImage() {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: ShaderMask(
|
||||
shaderCallback: (rect) => LinearGradient(begin: Alignment.center, end: Alignment.bottomCenter, colors: [Colors.transparent, darkSecondaryColor.withOpacity(0.9)]).createShader(rect),
|
||||
blendMode: BlendMode.darken,
|
||||
child: Container(
|
||||
color: primaryColor.withAlpha(5),
|
||||
width: double.maxFinite,
|
||||
height: MediaQuery.of(context).size.height / 3.3,
|
||||
child: CustomNetworkImage(networkImageUrl: widget.model.image!, width: double.infinity, height: MediaQuery.of(context).size.height / 4.2, fit: BoxFit.cover, isVideo: false))));
|
||||
}
|
||||
|
||||
Widget setTagsList({required List<String> tagList, required List<String> tagId}) {
|
||||
return Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
bottom: 80.0,
|
||||
start: 7.0,
|
||||
child: widget.model.tagName! != ""
|
||||
? SizedBox(
|
||||
height: 16.0,
|
||||
child: ListView.builder(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
itemCount: tagList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 5.5),
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
height: 20.0,
|
||||
width: 65,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsetsDirectional.only(start: 3.0, end: 3.0, top: 1.0, bottom: 1.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)),
|
||||
color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.85)),
|
||||
child: CustomTextLabel(
|
||||
text: tagList[index],
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).secondary, fontSize: 8.5),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
onTap: () async {
|
||||
Navigator.of(context).pushNamed(Routes.tagScreen, arguments: {"tagId": tagId[index], "tagName": tagList[index]});
|
||||
},
|
||||
),
|
||||
);
|
||||
}))
|
||||
: const SizedBox.shrink());
|
||||
}
|
||||
|
||||
Widget setTitleAndDate() {
|
||||
return Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
bottom: 20,
|
||||
start: 10,
|
||||
end: 10,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
alignment: Alignment.bottomLeft,
|
||||
padding: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0),
|
||||
child: CustomTextLabel(
|
||||
text: widget.model.title!, textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: secondaryColor), maxLines: 2, softWrap: true, overflow: TextOverflow.ellipsis)),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0),
|
||||
child: CustomTextLabel(
|
||||
text: UiUtils.convertToAgo(context, DateTime.parse(widget.model.publishDate ?? widget.model.date!), 0)!,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall!.copyWith(color: secondaryColor.withOpacity(0.6)))),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget shareButton() {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 13.0),
|
||||
child: InkWell(
|
||||
child: setButton(childWidget: const Icon(Icons.share_rounded, size: 20)),
|
||||
onTap: () async {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
UiUtils.shareNews(context: context, slug: widget.model.slug!, title: widget.model.title!, isVideo: false, videoId: "", isBreakingNews: false, isNews: true);
|
||||
} else {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget bookmarkButton() {
|
||||
return BlocProvider(
|
||||
create: (context) => UpdateBookmarkStatusCubit(BookmarkRepository()),
|
||||
child: BlocBuilder<BookmarkCubit, BookmarkState>(
|
||||
bloc: context.read<BookmarkCubit>(),
|
||||
builder: (context, bookmarkState) {
|
||||
bool isBookmark = context.read<BookmarkCubit>().isNewsBookmark(widget.model.id!);
|
||||
return BlocConsumer<UpdateBookmarkStatusCubit, UpdateBookmarkStatusState>(
|
||||
bloc: context.read<UpdateBookmarkStatusCubit>(),
|
||||
listener: ((context, state) {
|
||||
if (state is UpdateBookmarkStatusSuccess) {
|
||||
(state.wasBookmarkNewsProcess) ? context.read<BookmarkCubit>().addBookmarkNews(state.news) : context.read<BookmarkCubit>().removeBookmarkNews(state.news);
|
||||
}
|
||||
}),
|
||||
builder: (context, state) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
if (state is UpdateBookmarkStatusInProgress) return;
|
||||
context.read<UpdateBookmarkStatusCubit>().setBookmarkNews(news: widget.model, status: (isBookmark) ? "0" : "1");
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
},
|
||||
child: setButton(
|
||||
childWidget: (state is UpdateBookmarkStatusInProgress)
|
||||
? SizedBox(height: 20, width: 20, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))
|
||||
: ((isBookmark) ? const Icon(Icons.bookmark_added_rounded, size: 20) : const Icon(Icons.bookmark_add_outlined, size: 20))));
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget likeButton() {
|
||||
bool isLike = context.read<LikeAndDisLikeCubit>().isNewsLikeAndDisLike(widget.model.id!);
|
||||
|
||||
return BlocProvider(
|
||||
create: (context) => UpdateLikeAndDisLikeStatusCubit(LikeAndDisLikeRepository()),
|
||||
child: BlocConsumer<LikeAndDisLikeCubit, LikeAndDisLikeState>(
|
||||
bloc: context.read<LikeAndDisLikeCubit>(),
|
||||
listener: ((context, state) {
|
||||
if (state is LikeAndDisLikeFetchSuccess) {
|
||||
isLike = context.read<LikeAndDisLikeCubit>().isNewsLikeAndDisLike(widget.model.id!);
|
||||
}
|
||||
}),
|
||||
builder: (context, likeAndDislikeState) {
|
||||
return BlocConsumer<UpdateLikeAndDisLikeStatusCubit, UpdateLikeAndDisLikeStatusState>(
|
||||
bloc: context.read<UpdateLikeAndDisLikeStatusCubit>(),
|
||||
listener: ((context, state) {
|
||||
if (state is UpdateLikeAndDisLikeStatusSuccess) {
|
||||
context.read<LikeAndDisLikeCubit>().getLike(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
widget.model.totalLikes = (!isLike)
|
||||
? (int.parse(widget.model.totalLikes.toString()) + 1).toString()
|
||||
: (widget.model.totalLikes!.isNotEmpty)
|
||||
? (int.parse(widget.model.totalLikes.toString()) - 1).toString()
|
||||
: "0";
|
||||
}
|
||||
}),
|
||||
builder: (context, state) {
|
||||
return InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
onTap: () {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
if (state is UpdateLikeAndDisLikeStatusInProgress) {
|
||||
return;
|
||||
}
|
||||
context.read<UpdateLikeAndDisLikeStatusCubit>().setLikeAndDisLikeNews(news: widget.model, status: (isLike) ? "0" : "1");
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
},
|
||||
child: setButton(
|
||||
childWidget: (state is UpdateLikeAndDisLikeStatusInProgress)
|
||||
? SizedBox(height: 20, width: 20, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))
|
||||
: ((isLike) ? const Icon(Icons.thumb_up_alt, size: 20) : const Icon(Icons.thumb_up_off_alt, size: 20))));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
Widget setShareBookmarkLikeOptions() {
|
||||
return Positioned.directional(
|
||||
end: 5,
|
||||
bottom: 5,
|
||||
textDirection: Directionality.of(context),
|
||||
child: Row(
|
||||
children: [shareButton(), SizedBox(width: MediaQuery.of(context).size.width / 98.0), bookmarkButton(), SizedBox(width: MediaQuery.of(context).size.width / 98.0), likeButton()],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget newsData() {
|
||||
return Builder(builder: (context) {
|
||||
return Padding(
|
||||
padding: EdgeInsetsDirectional.only(top: widget.index == 0 ? 0 : 15.0, start: 15, end: 15),
|
||||
child: Column(children: [
|
||||
if (context.read<AppConfigurationCubit>().getInAppAdsMode() == "1" &&
|
||||
(context.read<AppConfigurationCubit>().getAdsType() != "unity" || context.read<AppConfigurationCubit>().getIOSAdsType() != "unity"))
|
||||
nativeAdsShow(context: context, index: widget.index),
|
||||
NewsCard(
|
||||
newsDetail: widget.model,
|
||||
showTags: true,
|
||||
onTap: () {
|
||||
List<NewsModel> newsList = [];
|
||||
newsList.addAll(widget.newslist);
|
||||
newsList.removeAt(widget.index);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": widget.model, "newsList": newsList, "isFromBreak": false, "fromShowMore": widget.fromShowMore});
|
||||
})
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return newsData();
|
||||
}
|
||||
}
|
||||
15
news-app/lib/ui/widgets/SnackBarWidget.dart
Normal file
15
news-app/lib/ui/widgets/SnackBarWidget.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
showSnackBar(String msg, BuildContext context, {int? durationInMiliSeconds}) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: CustomTextLabel(text: msg, textAlign: TextAlign.center, textStyle: TextStyle(color: Theme.of(context).colorScheme.surface)),
|
||||
showCloseIcon: false,
|
||||
duration: Duration(milliseconds: durationInMiliSeconds ?? 1500), //bydefault 4000 ms
|
||||
backgroundColor: UiUtils.getColorScheme(context).primaryContainer,
|
||||
elevation: 1.0,
|
||||
),
|
||||
);
|
||||
}
|
||||
43
news-app/lib/ui/widgets/adSpaces.dart
Normal file
43
news-app/lib/ui/widgets/adSpaces.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
//sponsored Ads
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/data/models/adSpaceModel.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class AdSpaces extends StatelessWidget {
|
||||
AdSpaceModel adsModel;
|
||||
AdSpaces({super.key, required this.adsModel});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
child: InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
onTap: () async {
|
||||
if (await canLaunchUrl(Uri.parse(adsModel.adUrl!))) {
|
||||
//To open link in other apps or outside of Current App
|
||||
//Add -> , mode: LaunchMode.externalApplication
|
||||
await launchUrl(Uri.parse(adsModel.adUrl!));
|
||||
}
|
||||
},
|
||||
child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [
|
||||
Container(
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
padding: const EdgeInsetsDirectional.only(end: 5),
|
||||
child: CustomTextLabel(
|
||||
text: 'sponsoredLbl',
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6), fontWeight: FontWeight.w800),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: CustomNetworkImage(networkImageUrl: adsModel.adImage!, isVideo: false, width: MediaQuery.of(context).size.width, fit: BoxFit.values.first),
|
||||
),
|
||||
])),
|
||||
);
|
||||
}
|
||||
}
|
||||
116
news-app/lib/ui/widgets/breakingNewsItem.dart
Normal file
116
news-app/lib/ui/widgets/breakingNewsItem.dart
Normal file
@@ -0,0 +1,116 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/utils/constant.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
|
||||
class BreakNewsItem extends StatefulWidget {
|
||||
final BreakingNewsModel model;
|
||||
final int index;
|
||||
final List<BreakingNewsModel> breakNewsList;
|
||||
|
||||
const BreakNewsItem({super.key, required this.model, required this.index, required this.breakNewsList});
|
||||
|
||||
@override
|
||||
BreakNewsItemState createState() => BreakNewsItemState();
|
||||
}
|
||||
|
||||
class BreakNewsItemState extends State<BreakNewsItem> {
|
||||
late BannerAd _bannerAd;
|
||||
@override
|
||||
void initState() {
|
||||
if (context.read<AppConfigurationCubit>().getInAppAdsMode() == "1") createBannerAd();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Widget newsData() {
|
||||
return Builder(builder: (context) {
|
||||
return Padding(
|
||||
padding: EdgeInsetsDirectional.only(top: widget.index == 0 ? 0 : 15.0),
|
||||
child: Column(children: [
|
||||
if (context.read<AppConfigurationCubit>().getInAppAdsMode() == "1") nativeAdsShow(),
|
||||
InkWell(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
child: CustomNetworkImage(networkImageUrl: widget.model.image!, width: double.infinity, height: MediaQuery.of(context).size.height / 4.2, fit: BoxFit.cover, isVideo: false)),
|
||||
Container(
|
||||
alignment: Alignment.bottomLeft,
|
||||
padding: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0),
|
||||
child: CustomTextLabel(
|
||||
text: widget.model.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.9)),
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis))
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
List<BreakingNewsModel> newsList = [];
|
||||
newsList.addAll(widget.breakNewsList);
|
||||
newsList.removeAt(widget.index);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": widget.model, "breakNewsList": newsList, "isFromBreak": true, "fromShowMore": false});
|
||||
},
|
||||
),
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
BannerAd createBannerAd() {
|
||||
if (context.read<AppConfigurationCubit>().bannerId() != "") {
|
||||
_bannerAd = BannerAd(
|
||||
adUnitId: context.read<AppConfigurationCubit>().bannerId()!,
|
||||
request: const AdRequest(),
|
||||
size: AdSize.mediumRectangle,
|
||||
listener: BannerAdListener(
|
||||
onAdLoaded: (_) {},
|
||||
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.')),
|
||||
);
|
||||
}
|
||||
return _bannerAd;
|
||||
}
|
||||
|
||||
Widget bannerAdsShow() {
|
||||
return AdWidget(key: UniqueKey(), ad: createBannerAd()..load());
|
||||
}
|
||||
|
||||
Widget nativeAdsShow() {
|
||||
if (context.read<AppConfigurationCubit>().getInAppAdsMode() == "1" &&
|
||||
context.read<AppConfigurationCubit>().checkAdsType() != null &&
|
||||
context.read<AppConfigurationCubit>().getAdsType() != "unity" &&
|
||||
widget.index != 0 &&
|
||||
widget.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() != "") ? bannerAdsShow() : SizedBox.shrink()));
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return newsData();
|
||||
}
|
||||
}
|
||||
58
news-app/lib/ui/widgets/breakingVideoItem.dart
Normal file
58
news-app/lib/ui/widgets/breakingVideoItem.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
|
||||
class BreakVideoItem extends StatefulWidget {
|
||||
final BreakingNewsModel model;
|
||||
|
||||
const BreakVideoItem({super.key, required this.model});
|
||||
|
||||
@override
|
||||
BreakVideoItemState createState() => BreakVideoItemState();
|
||||
}
|
||||
|
||||
class BreakVideoItemState extends State<BreakVideoItem> {
|
||||
Widget videoData(BreakingNewsModel video) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 15.0),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 3, "breakModel": video});
|
||||
},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
(video.contentType == 'video_youtube')
|
||||
? CustomNetworkImage(
|
||||
networkImageUrl: 'https://img.youtube.com/vi/${YoutubePlayer.convertUrlToId(video.contentValue!)!}/0.jpg',
|
||||
fit: BoxFit.cover,
|
||||
width: double.maxFinite,
|
||||
height: 220,
|
||||
isVideo: true)
|
||||
: CustomNetworkImage(networkImageUrl: video.image!, fit: BoxFit.cover, width: double.maxFinite, height: 220, isVideo: true),
|
||||
const CircleAvatar(radius: 30, backgroundColor: Colors.black45, child: Icon(Icons.play_arrow, size: 40, color: Colors.white))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 5, right: 5),
|
||||
child:
|
||||
Align(alignment: Alignment.centerLeft, child: CustomTextLabel(text: video.title!, textStyle: Theme.of(context).textTheme.titleSmall, maxLines: 2, overflow: TextOverflow.ellipsis))),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return videoData(widget.model);
|
||||
}
|
||||
}
|
||||
45
news-app/lib/ui/widgets/customAppBar.dart
Normal file
45
news-app/lib/ui/widgets/customAppBar.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'dart:core';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/widgets/customBackBtn.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
final double height;
|
||||
final double? horizontalPad;
|
||||
final bool isBackBtn, isConvertText;
|
||||
final String label;
|
||||
final List<Widget>? actionWidget;
|
||||
|
||||
const CustomAppBar({Key? key, required this.height, required this.isBackBtn, required this.label, required this.isConvertText, this.horizontalPad, this.actionWidget}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PreferredSize(
|
||||
preferredSize: Size(double.infinity, height),
|
||||
child: UiUtils.applyBoxShadow(
|
||||
context: context,
|
||||
child: AppBar(
|
||||
leading: (isBackBtn) ? CustomBackButton(horizontalPadding: horizontalPad ?? 15) : null,
|
||||
actions: actionWidget,
|
||||
titleSpacing: 0.0,
|
||||
automaticallyImplyLeading: false,
|
||||
centerTitle: false,
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
title: Padding(
|
||||
padding: EdgeInsetsDirectional.only(start: isBackBtn ? 0 : 20),
|
||||
child: !isConvertText
|
||||
? Text(label, style: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5))
|
||||
: CustomTextLabel(
|
||||
text: label,
|
||||
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
||||
}
|
||||
20
news-app/lib/ui/widgets/customBackBtn.dart
Normal file
20
news-app/lib/ui/widgets/customBackBtn.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class CustomBackButton extends StatelessWidget {
|
||||
final Function? onTap;
|
||||
final double? horizontalPadding;
|
||||
|
||||
const CustomBackButton({super.key, this.onTap, this.horizontalPadding});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: horizontalPadding ?? 0),
|
||||
child: InkWell(
|
||||
onTap: () => onTap ?? Navigator.of(context).pop(),
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
child: Icon(Icons.arrow_back, color: UiUtils.getColorScheme(context).primaryContainer)));
|
||||
}
|
||||
}
|
||||
24
news-app/lib/ui/widgets/customTextBtn.dart
Normal file
24
news-app/lib/ui/widgets/customTextBtn.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
|
||||
class CustomTextButton extends StatelessWidget {
|
||||
final Function onTap;
|
||||
final Color? color;
|
||||
final String? text;
|
||||
final ButtonStyle? buttonStyle;
|
||||
final Widget? textWidget;
|
||||
|
||||
const CustomTextButton({super.key, required this.onTap, this.color, this.text, this.buttonStyle, this.textWidget});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextButton(
|
||||
style: buttonStyle ?? ButtonStyle(overlayColor: WidgetStateProperty.all(Colors.transparent), foregroundColor: WidgetStateProperty.all(darkSecondaryColor)),
|
||||
onPressed: () {
|
||||
onTap();
|
||||
},
|
||||
child: textWidget ?? CustomTextLabel(text: text!, textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(color: color, fontWeight: FontWeight.normal)),
|
||||
);
|
||||
}
|
||||
}
|
||||
23
news-app/lib/ui/widgets/customTextLabel.dart
Normal file
23
news-app/lib/ui/widgets/customTextLabel.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/languageJsonCubit.dart';
|
||||
|
||||
class CustomTextLabel extends StatelessWidget {
|
||||
final String text;
|
||||
final TextStyle? textStyle;
|
||||
final TextAlign? textAlign;
|
||||
final int? maxLines;
|
||||
final TextOverflow? overflow;
|
||||
final bool? softWrap;
|
||||
|
||||
const CustomTextLabel({super.key, required this.text, this.textStyle, this.textAlign, this.maxLines, this.overflow, this.softWrap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<LanguageJsonCubit, LanguageJsonState>(
|
||||
builder: (context, state) {
|
||||
return Text(context.read<LanguageJsonCubit>().getTranslatedLabels(text), maxLines: maxLines, overflow: overflow, softWrap: softWrap, style: textStyle, textAlign: textAlign);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
51
news-app/lib/ui/widgets/errorContainerWidget.dart
Normal file
51
news-app/lib/ui/widgets/errorContainerWidget.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
import 'package:news/ui/widgets/customTextBtn.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class ErrorContainerWidget extends StatelessWidget {
|
||||
final String errorMsg;
|
||||
final Function onRetry;
|
||||
const ErrorContainerWidget({super.key, required this.errorMsg, required this.onRetry});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
(errorMsg.contains(UiUtils.getTranslatedLabel(context, 'internetmsg')))
|
||||
? Container(
|
||||
width: MediaQuery.of(context).size.width * (0.7),
|
||||
margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top + MediaQuery.of(context).size.height * (0.05)),
|
||||
height: MediaQuery.of(context).size.height * (0.4),
|
||||
child: Lottie.asset("assets/animations/noInternet.json"))
|
||||
: SizedBox(height: MediaQuery.of(context).size.height * (0.2)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: CustomTextLabel(
|
||||
text: errorMsg,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 4,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textStyle: TextStyle(color: Theme.of(context).colorScheme.primaryContainer, fontSize: 16, fontWeight: FontWeight.w300))),
|
||||
SizedBox(height: MediaQuery.of(context).size.height * (0.035)),
|
||||
CustomTextButton(
|
||||
onTap: () {
|
||||
onRetry.call();
|
||||
},
|
||||
buttonStyle: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(UiUtils.getColorScheme(context).primaryContainer),
|
||||
shape: WidgetStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)))),
|
||||
textWidget: CustomTextLabel(
|
||||
text: 'RetryLbl',
|
||||
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).surface, fontWeight: FontWeight.w600, fontSize: 21, letterSpacing: 0.6),
|
||||
)),
|
||||
SizedBox(height: MediaQuery.of(context).size.height * (0.15)),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
76
news-app/lib/ui/widgets/loadingScreen.dart
Normal file
76
news-app/lib/ui/widgets/loadingScreen.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
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/breakingNewsCubit.dart';
|
||||
import 'package:news/cubits/languageCubit.dart';
|
||||
import 'package:news/cubits/slugNewsCubit.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/utils/strings.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
//TODO: if change in API calls or variables, change the same in Dashboard screen > initDynamicLinks()
|
||||
class LoadingScreen extends StatefulWidget {
|
||||
String routeSettingsName;
|
||||
String newsSlug;
|
||||
|
||||
LoadingScreen({required this.routeSettingsName, required this.newsSlug, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_LoadingScreenState createState() => _LoadingScreenState();
|
||||
}
|
||||
|
||||
class _LoadingScreenState extends State<LoadingScreen> {
|
||||
ValueNotifier<bool> isLoading = ValueNotifier(true);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
context.read<LanguageCubit>().getLanguage().then(
|
||||
(value) {
|
||||
fetchData();
|
||||
},
|
||||
);
|
||||
isLoading.addListener(() {
|
||||
if (!isLoading.value) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> fetchData() async {
|
||||
await Future.delayed(Duration(seconds: 2)); // Simulate API delay
|
||||
if (widget.routeSettingsName.contains('/news/')) {
|
||||
String langCodeShared = widget.routeSettingsName.split("/")[1];
|
||||
String? langIdPass = UiUtils.rootNavigatorKey.currentContext!.read<AppLocalizationCubit>().state.id;
|
||||
if (context.read<LanguageCubit>().langList().isNotEmpty) langIdPass = context.read<LanguageCubit>().langList().firstWhere((e) => e.code == langCodeShared).id;
|
||||
UiUtils.rootNavigatorKey.currentContext?.read<SlugNewsCubit>().getSlugNews(langId: langIdPass ?? "0", newsSlug: widget.newsSlug).then((value) {
|
||||
if (value[DATA] != null) {
|
||||
NewsModel? model = (value[DATA] as List).map((e) => NewsModel.fromJson(e)).toList().first;
|
||||
Navigator.popAndPushNamed(context, Routes.newsDetails,
|
||||
arguments: {"model": model, "slug": widget.newsSlug, "isFromBreak": widget.routeSettingsName.contains('/breaking-news/') ? true : false, "fromShowMore": false});
|
||||
} else {
|
||||
setState(() {
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (widget.routeSettingsName.contains('/breaking-news/')) {
|
||||
UiUtils.rootNavigatorKey.currentContext?.read<BreakingNewsCubit>().getBreakingNews(langId: UiUtils.rootNavigatorKey.currentContext!.read<AppLocalizationCubit>().state.id).then((value) {
|
||||
BreakingNewsModel? brModel = value[0];
|
||||
Navigator.of(context).pushReplacementNamed(Routes.newsDetails, arguments: {"breakModel": brModel, "slug": widget.newsSlug, "isFromBreak": true, "fromShowMore": false});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: isLoading,
|
||||
builder: (context, value, child) {
|
||||
return Scaffold(body: Center(child: CircularProgressIndicator()));
|
||||
});
|
||||
}
|
||||
}
|
||||
31
news-app/lib/ui/widgets/networkImage.dart
Normal file
31
news-app/lib/ui/widgets/networkImage.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class CustomNetworkImage extends StatelessWidget {
|
||||
final String networkImageUrl;
|
||||
final double? width, height;
|
||||
final BoxFit? fit;
|
||||
final bool? isVideo;
|
||||
final Widget? errorBuilder;
|
||||
|
||||
const CustomNetworkImage({super.key, required this.networkImageUrl, this.width, this.height, this.fit, this.isVideo, this.errorBuilder});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FadeInImage.assetNetwork(
|
||||
fadeInDuration: const Duration(milliseconds: 150),
|
||||
fadeOutCurve: Curves.bounceOut,
|
||||
image: networkImageUrl,
|
||||
width: width ?? 100,
|
||||
height: height ?? 100,
|
||||
fit: fit ?? BoxFit.cover,
|
||||
imageErrorBuilder: (context, error, stackTrace) {
|
||||
return Image.asset(UiUtils.getPlaceholderPngPath(), width: width ?? 100, height: height ?? 100);
|
||||
},
|
||||
placeholderErrorBuilder: (context, error, stackTrace) {
|
||||
return Image.asset(UiUtils.getPlaceholderPngPath(), width: width ?? 100, height: height ?? 100);
|
||||
},
|
||||
placeholderFit: fit ?? BoxFit.cover,
|
||||
placeholder: UiUtils.getPlaceholderPngPath());
|
||||
}
|
||||
}
|
||||
179
news-app/lib/ui/widgets/newsCard.dart
Normal file
179
news-app/lib/ui/widgets/newsCard.dart
Normal file
@@ -0,0 +1,179 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/Bookmark/UpdateBookmarkCubit.dart';
|
||||
import 'package:news/cubits/Bookmark/bookmarkCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/data/models/NewsModel.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/utils/uiUtils.dart';
|
||||
|
||||
class NewsCard extends StatefulWidget {
|
||||
NewsModel newsDetail;
|
||||
bool showViews;
|
||||
bool showTags;
|
||||
Function()? onTap;
|
||||
NewsCard({super.key, required this.newsDetail, this.showViews = false, this.showTags = false, required this.onTap});
|
||||
|
||||
@override
|
||||
State<NewsCard> createState() => NewsCardState();
|
||||
}
|
||||
|
||||
class NewsCardState extends State<NewsCard> {
|
||||
List<String> tagList = [];
|
||||
List<String> tagId = [];
|
||||
late NewsModel newsData;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
newsData = widget.newsDetail;
|
||||
|
||||
if (newsData.tagName! != "") {
|
||||
final tagName = newsData.tagName!;
|
||||
tagList = tagName.split(',');
|
||||
}
|
||||
|
||||
if (newsData.tagId != null && newsData.tagId! != "") {
|
||||
tagId = newsData.tagId!.split(",");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(color: UiUtils.getColorScheme(context).surface, borderRadius: BorderRadius.circular(8)),
|
||||
child: GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 8,
|
||||
children: [setImage(), setContent()],
|
||||
),
|
||||
(widget.showViews) ? setBookmarkIconButton() : SizedBox.shrink()
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget setContent() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 8,
|
||||
children: [
|
||||
//title
|
||||
CustomTextLabel(text: widget.newsDetail.title!, textStyle: TextStyle(fontWeight: FontWeight.bold)),
|
||||
Row(
|
||||
spacing: 10,
|
||||
children: [(widget.newsDetail.date != null && widget.newsDetail.date!.isNotEmpty) ? setDate() : SizedBox.shrink(), (widget.showViews) ? setViews() : SizedBox.shrink()],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget setBookmarkIconButton() {
|
||||
return BlocListener<UpdateBookmarkStatusCubit, UpdateBookmarkStatusState>(
|
||||
listener: (context, state) {
|
||||
if (state is UpdateBookmarkStatusSuccess) {
|
||||
context.read<BookmarkCubit>().getBookmark(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
}
|
||||
},
|
||||
child: Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
end: 10,
|
||||
top: 10,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.read<UpdateBookmarkStatusCubit>().setBookmarkNews(news: widget.newsDetail, status: "0");
|
||||
},
|
||||
child: Container(
|
||||
height: 30,
|
||||
width: 30,
|
||||
decoration: BoxDecoration(borderRadius: const BorderRadius.all(Radius.circular(15)), color: UiUtils.getColorScheme(context).surface),
|
||||
child: Icon(Icons.bookmark_outlined, color: UiUtils.getColorScheme(context).onPrimary, size: 20)))),
|
||||
);
|
||||
}
|
||||
|
||||
Widget setViews() {
|
||||
return Row(spacing: 8, children: [
|
||||
Icon(Icons.remove_red_eye_rounded, size: 20),
|
||||
CustomTextLabel(text: "${widget.newsDetail.totalViews} ${UiUtils.getTranslatedLabel(context, 'viewsLbl')}", textStyle: TextStyle(fontSize: 12))
|
||||
]);
|
||||
}
|
||||
|
||||
Widget setDate() {
|
||||
return Row(
|
||||
spacing: 8,
|
||||
children: [Icon(Icons.calendar_month_rounded, size: 20), CustomTextLabel(text: UiUtils.formatDate(widget.newsDetail.date ?? ''), textStyle: TextStyle(fontSize: 12))],
|
||||
);
|
||||
}
|
||||
|
||||
Widget setTags() {
|
||||
return Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
bottom: 15.0,
|
||||
start: 7.0,
|
||||
child: widget.newsDetail.tagName! != ""
|
||||
? SizedBox(
|
||||
height: 30.0,
|
||||
child: ListView.builder(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
itemCount: tagList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 5.5),
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
height: 25.0,
|
||||
width: 65,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsetsDirectional.only(start: 3.0, end: 3.0, top: 1.0, bottom: 1.0),
|
||||
decoration: BoxDecoration(borderRadius: const BorderRadius.all(Radius.circular(15)), color: UiUtils.getColorScheme(context).secondary.withOpacity(0.85)),
|
||||
child: CustomTextLabel(
|
||||
text: tagList[index],
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontSize: 12),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
onTap: () async {
|
||||
Navigator.of(context).pushNamed(Routes.tagScreen, arguments: {"tagId": tagId[index], "tagName": tagList[index]});
|
||||
},
|
||||
),
|
||||
);
|
||||
}))
|
||||
: const SizedBox.shrink());
|
||||
}
|
||||
|
||||
Widget setImage() {
|
||||
return Stack(
|
||||
children: [
|
||||
Container(
|
||||
height: 192,
|
||||
color: borderColor,
|
||||
child: ShaderMask(
|
||||
shaderCallback: (rect) =>
|
||||
LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [darkSecondaryColor.withOpacity(0.6), darkSecondaryColor.withOpacity(0.6)]).createShader(rect),
|
||||
blendMode: BlendMode.darken,
|
||||
child: Container(
|
||||
color: primaryColor.withAlpha(5),
|
||||
width: double.maxFinite,
|
||||
height: MediaQuery.of(context).size.height / 3.3,
|
||||
child: CustomNetworkImage(width: double.maxFinite, networkImageUrl: widget.newsDetail.image ?? ''),
|
||||
))),
|
||||
//tags
|
||||
if (widget.showTags) setTags()
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
72
news-app/lib/ui/widgets/shimmerNewsList.dart
Normal file
72
news-app/lib/ui/widgets/shimmerNewsList.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
class ShimmerNewsList extends StatelessWidget {
|
||||
bool isNews = true;
|
||||
|
||||
ShimmerNewsList({Key? key, required this.isNews}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
//videos,Subcategory,Bookmarks & TagsPage
|
||||
return Shimmer.fromColors(
|
||||
baseColor: Colors.grey.withOpacity(0.6),
|
||||
highlightColor: Colors.grey,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsetsDirectional.only(top: 0.0),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemBuilder: (_, i) => Padding(
|
||||
padding: EdgeInsetsDirectional.only(top: i == 0 ? 0 : 15.0),
|
||||
child: Padding(
|
||||
padding: EdgeInsetsDirectional.only(top: MediaQuery.of(context).size.height / 35.0, start: 15.0, end: 15.0),
|
||||
child: Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height / 4.2,
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Colors.grey.withOpacity(0.6)),
|
||||
)),
|
||||
(!isNews)
|
||||
? const SizedBox.shrink()
|
||||
: Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
bottom: 10.0,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width / 1.15,
|
||||
height: 10.0,
|
||||
margin: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Colors.grey.withOpacity(0.6)),
|
||||
),
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width / 2.0,
|
||||
height: 10.0,
|
||||
margin: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Colors.grey.withOpacity(0.6)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
itemCount: 6,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Widget iconButtons({required BuildContext context}) {
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width / 20.0,
|
||||
height: 20.0,
|
||||
padding: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15.0), color: Colors.grey.withOpacity(0.6)),
|
||||
);
|
||||
}
|
||||
215
news-app/lib/ui/widgets/videoItem.dart
Normal file
215
news-app/lib/ui/widgets/videoItem.dart
Normal file
@@ -0,0 +1,215 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart';
|
||||
import 'package:news/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/strings.dart';
|
||||
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/Bookmark/UpdateBookmarkCubit.dart';
|
||||
import 'package:news/cubits/Bookmark/bookmarkCubit.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/data/repositories/Bookmark/bookmarkRepository.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:html/parser.dart';
|
||||
|
||||
class VideoItem extends StatefulWidget {
|
||||
final NewsModel model;
|
||||
|
||||
VideoItem({super.key, required this.model});
|
||||
|
||||
@override
|
||||
VideoItemState createState() => VideoItemState();
|
||||
}
|
||||
|
||||
class VideoItemState extends State<VideoItem> {
|
||||
String formattedDescription = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void dispose() async {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void checkAndSetDescription({required String descr}) {
|
||||
formattedDescription = "";
|
||||
|
||||
// Parse HTML and extract plain text
|
||||
formattedDescription = parse(descr).body?.text ?? '';
|
||||
}
|
||||
|
||||
Widget videoData(NewsModel video) {
|
||||
checkAndSetDescription(descr: video.shortDesc ?? video.desc ?? '');
|
||||
return Padding(
|
||||
padding: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
ClipRRect(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
ShaderMask(
|
||||
shaderCallback: (Rect bounds) {
|
||||
return LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.transparent, darkSecondaryColor]).createShader(bounds);
|
||||
},
|
||||
blendMode: BlendMode.darken,
|
||||
child: CustomNetworkImage(
|
||||
networkImageUrl: (video.contentType == 'video_youtube' && video.contentValue!.isNotEmpty)
|
||||
? 'https://img.youtube.com/vi/${YoutubePlayer.convertUrlToId(video.contentValue!)!}/0.jpg'
|
||||
: video.image!,
|
||||
fit: BoxFit.cover,
|
||||
width: double.maxFinite,
|
||||
height: (Platform.isIOS) ? MediaQuery.of(context).size.height / 1.29 : MediaQuery.of(context).size.height / 1.25,
|
||||
isVideo: true)),
|
||||
CircleAvatar(radius: 30, backgroundColor: Colors.black45, child: Icon(Icons.play_arrow, size: 40, color: Colors.white)),
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
bottom: 25.0,
|
||||
start: 0,
|
||||
height: MediaQuery.of(context).size.height / 8.4,
|
||||
width: MediaQuery.of(context).size.width / 1.3,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: CustomTextLabel(
|
||||
text: video.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: secondaryColor, fontWeight: FontWeight.bold),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis)),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: CustomTextLabel(
|
||||
text: formattedDescription.trim(), textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: secondaryColor), maxLines: 3, overflow: TextOverflow.ellipsis),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
bottom: 10.0,
|
||||
end: 10.0,
|
||||
height: MediaQuery.of(context).size.height / 6,
|
||||
width: MediaQuery.of(context).size.width / 8,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(bottom: 5.0),
|
||||
child: Column(
|
||||
children: [
|
||||
if (video.sourceType == NEWS) //user can like or bookmark videos of source type news, and not Breaking News
|
||||
Column(children: [
|
||||
likeButton(),
|
||||
const SizedBox(height: 15),
|
||||
BlocProvider(
|
||||
create: (context) => UpdateBookmarkStatusCubit(BookmarkRepository()),
|
||||
child: BlocBuilder<BookmarkCubit, BookmarkState>(
|
||||
bloc: context.read<BookmarkCubit>(),
|
||||
builder: (context, bookmarkState) {
|
||||
bool isBookmark = context.read<BookmarkCubit>().isNewsBookmark(video.id!);
|
||||
return BlocConsumer<UpdateBookmarkStatusCubit, UpdateBookmarkStatusState>(
|
||||
bloc: context.read<UpdateBookmarkStatusCubit>(),
|
||||
listener: ((context, state) {
|
||||
if (state is UpdateBookmarkStatusSuccess) {
|
||||
(state.wasBookmarkNewsProcess) ? context.read<BookmarkCubit>().addBookmarkNews(state.news) : context.read<BookmarkCubit>().removeBookmarkNews(state.news);
|
||||
setState(() {});
|
||||
}
|
||||
}),
|
||||
builder: (context, state) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
if (state is UpdateBookmarkStatusInProgress) return;
|
||||
context.read<UpdateBookmarkStatusCubit>().setBookmarkNews(news: video, status: (isBookmark) ? "0" : "1");
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
},
|
||||
child: state is UpdateBookmarkStatusInProgress
|
||||
? SizedBox(height: 15, width: 15, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))
|
||||
: Icon(isBookmark ? Icons.bookmark_added_rounded : Icons.bookmark_add_outlined, color: secondaryColor));
|
||||
});
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
]),
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
(await InternetConnectivity.isNetworkAvailable())
|
||||
? UiUtils.shareNews(context: context, slug: video.slug ?? "", title: video.title!, isVideo: true, videoId: video.id!, isBreakingNews: false, isNews: false)
|
||||
: showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context);
|
||||
},
|
||||
splashColor: Colors.transparent,
|
||||
child: const Icon(Icons.share_rounded, color: secondaryColor))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return (widget.model.contentValue!.isNotEmpty) ? videoData(widget.model) : const SizedBox.shrink();
|
||||
}
|
||||
|
||||
Widget likeButton() {
|
||||
bool isLike = context.read<LikeAndDisLikeCubit>().isNewsLikeAndDisLike(widget.model.newsId!);
|
||||
|
||||
return BlocProvider(
|
||||
create: (context) => UpdateLikeAndDisLikeStatusCubit(LikeAndDisLikeRepository()),
|
||||
child: BlocConsumer<LikeAndDisLikeCubit, LikeAndDisLikeState>(
|
||||
bloc: context.read<LikeAndDisLikeCubit>(),
|
||||
listener: ((context, state) {
|
||||
if (state is LikeAndDisLikeFetchSuccess) {
|
||||
isLike = context.read<LikeAndDisLikeCubit>().isNewsLikeAndDisLike(widget.model.newsId!);
|
||||
}
|
||||
}),
|
||||
builder: (context, likeAndDislikeState) {
|
||||
return BlocConsumer<UpdateLikeAndDisLikeStatusCubit, UpdateLikeAndDisLikeStatusState>(
|
||||
bloc: context.read<UpdateLikeAndDisLikeStatusCubit>(),
|
||||
listener: ((context, state) {
|
||||
if (state is UpdateLikeAndDisLikeStatusSuccess) {
|
||||
context.read<LikeAndDisLikeCubit>().getLike(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
}
|
||||
}),
|
||||
builder: (context, state) {
|
||||
return InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
onTap: () {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
if (state is UpdateLikeAndDisLikeStatusInProgress) {
|
||||
return;
|
||||
}
|
||||
context.read<UpdateLikeAndDisLikeStatusCubit>().setLikeAndDisLikeNews(
|
||||
news: widget.model,
|
||||
status: (isLike) ? "0" : "1",
|
||||
);
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
child: (state is UpdateLikeAndDisLikeStatusInProgress)
|
||||
? SizedBox(height: 15, width: 15, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))
|
||||
: ((isLike) ? const Icon(Icons.thumb_up_alt, size: 25, color: secondaryColor) : const Icon(Icons.thumb_up_off_alt, size: 25, color: secondaryColor))));
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
86
news-app/lib/ui/widgets/videoPlayContainer.dart
Normal file
86
news-app/lib/ui/widgets/videoPlayContainer.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
import 'package:flick_video_player/flick_video_player.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
|
||||
|
||||
class VideoPlayContainer extends StatefulWidget {
|
||||
final String contentType, contentValue;
|
||||
final bool autoPlay;
|
||||
|
||||
Duration lastPosition;
|
||||
|
||||
VideoPlayContainer({super.key, required this.contentType, required this.contentValue, this.autoPlay = true, this.lastPosition = Duration.zero});
|
||||
@override
|
||||
VideoPlayContainerState createState() => VideoPlayContainerState();
|
||||
}
|
||||
|
||||
class VideoPlayContainerState extends State<VideoPlayContainer> {
|
||||
bool playVideo = false;
|
||||
|
||||
FlickManager? flickManager;
|
||||
YoutubePlayerController? _yc;
|
||||
|
||||
late VideoPlayerController _controller;
|
||||
late final WebViewController webViewController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initialisePlayer();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() async {
|
||||
try {
|
||||
if (widget.contentType == "video_upload") {
|
||||
// pause only if controller still valid
|
||||
if (_controller.value.isInitialized && _controller.value.isPlaying) {
|
||||
_controller.pause();
|
||||
}
|
||||
|
||||
// exit fullscreen safely
|
||||
flickManager?.flickControlManager?.exitFullscreen();
|
||||
|
||||
// dispose only the flick manager
|
||||
flickManager?.dispose();
|
||||
flickManager = null;
|
||||
} else if (widget.contentValue == "video_youtube") {
|
||||
_yc?.dispose();
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
super.dispose(); // always last
|
||||
}
|
||||
|
||||
initialisePlayer() {
|
||||
if (widget.contentValue != "") {
|
||||
if (widget.contentType == "video_upload") {
|
||||
_controller = VideoPlayerController.networkUrl(Uri.parse(widget.contentValue));
|
||||
flickManager = FlickManager(
|
||||
videoPlayerController: _controller,
|
||||
autoPlay: widget.autoPlay,
|
||||
onVideoEnd: () {
|
||||
widget.lastPosition = Duration.zero;
|
||||
});
|
||||
} else if (widget.contentType == "video_youtube" || widget.contentType == "url_youtube") {
|
||||
_yc = YoutubePlayerController(initialVideoId: YoutubePlayer.convertUrlToId(widget.contentValue) ?? "", flags: YoutubePlayerFlags(autoPlay: widget.autoPlay));
|
||||
} else if (widget.contentType == "video_other") {
|
||||
webViewController = WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..loadRequest(Uri.parse(widget.contentValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return (widget.contentType == "video_upload")
|
||||
? FlickVideoPlayer(flickManager: flickManager!)
|
||||
: (widget.contentType == "video_youtube" || widget.contentType == "url_youtube")
|
||||
? YoutubePlayer(controller: _yc!, showVideoProgressIndicator: true, progressIndicatorColor: Theme.of(context).primaryColor)
|
||||
: widget.contentType == "video_other"
|
||||
? Center(child: WebViewWidget(controller: webViewController))
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
18
news-app/lib/ui/widgets/videoShimmer.dart
Normal file
18
news-app/lib/ui/widgets/videoShimmer.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
videoShimmer(BuildContext context) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: Colors.grey.withOpacity(0.6),
|
||||
highlightColor: Colors.grey,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsetsDirectional.only(top: 15.0),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemBuilder: (_, i) => Padding(
|
||||
padding: EdgeInsetsDirectional.only(top: i == 0 ? 0 : 15.0),
|
||||
child: Stack(children: [Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Colors.grey.withOpacity(0.6)), height: 215.0)])),
|
||||
itemCount: 6),
|
||||
));
|
||||
}
|
||||
Reference in New Issue
Block a user