elCaribe app - customization and branding
This commit is contained in:
1428
news-app/lib/ui/screens/AddEditNews/AddNews.dart
Normal file
1428
news-app/lib/ui/screens/AddEditNews/AddNews.dart
Normal file
File diff suppressed because it is too large
Load Diff
164
news-app/lib/ui/screens/AddEditNews/ManageUserNews.dart
Normal file
164
news-app/lib/ui/screens/AddEditNews/ManageUserNews.dart
Normal file
@@ -0,0 +1,164 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/GetUserDraftedNewsCubit.dart';
|
||||
import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart';
|
||||
import 'package:news/ui/screens/AddEditNews/Widgets/userAllNews.dart';
|
||||
import 'package:news/ui/screens/AddEditNews/Widgets/userDrafterNews.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/getUserNewsCubit.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class ManageUserNews extends StatefulWidget {
|
||||
const ManageUserNews({super.key});
|
||||
|
||||
@override
|
||||
ManageUserNewsState createState() => ManageUserNewsState();
|
||||
}
|
||||
|
||||
class ManageUserNewsState extends State<ManageUserNews> with TickerProviderStateMixin {
|
||||
final bool _isButtonExtended = true;
|
||||
late final ScrollController controller = ScrollController()..addListener(hasMoreNewsScrollListener);
|
||||
late final ScrollController draftController = ScrollController()..addListener(hasMoreDraftedNewsScrollListener);
|
||||
|
||||
Set<String> get locationValue => SettingsLocalDataRepository().getLocationCityValues();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
getNews();
|
||||
getUserDraftedNews();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void getUserDraftedNews() {
|
||||
context.read<GetUserDraftedNewsCubit>().getGetUserDraftedNews(userId: int.parse(context.read<AuthCubit>().getUserId()));
|
||||
}
|
||||
|
||||
void getNews() {
|
||||
context.read<GetUserNewsCubit>().getGetUserNews(latitude: locationValue.first, longitude: locationValue.last);
|
||||
}
|
||||
|
||||
void getMoreNews() {
|
||||
context.read<GetUserNewsCubit>().getMoreGetUserNews(latitude: locationValue.first, longitude: locationValue.last);
|
||||
}
|
||||
|
||||
void hasMoreNewsScrollListener() {
|
||||
if (controller.position.maxScrollExtent == controller.offset) {
|
||||
if (context.read<GetUserNewsCubit>().hasMoreGetUserNews()) {
|
||||
getMoreNews();
|
||||
} else {}
|
||||
}
|
||||
}
|
||||
|
||||
void hasMoreDraftedNewsScrollListener() {
|
||||
if (draftController.position.maxScrollExtent == draftController.offset) {
|
||||
if (context.read<GetUserDraftedNewsCubit>().hasMoreGetUserDraftedNews()) {
|
||||
getUserDraftedNews();
|
||||
} else {}
|
||||
}
|
||||
}
|
||||
|
||||
getAppBar() {
|
||||
return PreferredSize(
|
||||
preferredSize: const Size(double.infinity, 45),
|
||||
child: UiUtils.applyBoxShadow(
|
||||
context: context,
|
||||
child: AppBar(
|
||||
centerTitle: false,
|
||||
backgroundColor: Colors.transparent,
|
||||
title: Transform(
|
||||
transform: Matrix4.translationValues(-20.0, 0.0, 0.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'manageNewsLbl',
|
||||
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5))),
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
child: Icon(Icons.arrow_back, color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
newsAddBtn() {
|
||||
return Column(mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||
FloatingActionButton(
|
||||
isExtended: _isButtonExtended,
|
||||
backgroundColor: UiUtils.getColorScheme(context).surface,
|
||||
child: Icon(Icons.add, size: 32, color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(Routes.addNews, arguments: {"isEdit": false, "from": "myNews"});
|
||||
}),
|
||||
const SizedBox(height: 10)
|
||||
]);
|
||||
}
|
||||
|
||||
contentShimmer(BuildContext context) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: Colors.grey.withOpacity(0.6),
|
||||
highlightColor: Colors.grey,
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: const EdgeInsetsDirectional.only(start: 20, end: 20),
|
||||
itemBuilder: (_, i) =>
|
||||
Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Colors.grey.withOpacity(0.6)), margin: const EdgeInsets.only(top: 20), height: 190.0),
|
||||
itemCount: 6));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
appBar: getAppBar(),
|
||||
floatingActionButton: newsAddBtn(),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: UiUtils.getColorScheme(context).surface),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||
margin: const EdgeInsetsDirectional.only(start: 10, end: 10),
|
||||
child: TabBar(
|
||||
indicator: BoxDecoration(
|
||||
color: UiUtils.getColorScheme(context).primaryContainer,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
labelColor: UiUtils.getColorScheme(context).surface,
|
||||
unselectedLabelColor: UiUtils.getColorScheme(context).primaryContainer,
|
||||
tabs: [
|
||||
Tab(text: UiUtils.getTranslatedLabel(context, 'manageNewsAllLbl')),
|
||||
Tab(text: UiUtils.getTranslatedLabel(context, 'manageNewsDraftLbl')),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
children: [
|
||||
UserAllNewsTab(controller: controller, contentShimmer: contentShimmer(context), fetchNews: getNews, fetchMoreNews: getMoreNews),
|
||||
UserDrafterNewsTab(controller: draftController, contentShimmer: contentShimmer(context), fetchDraftedNews: getUserDraftedNews, fetchMoreDraftedNews: getUserDraftedNews),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
302
news-app/lib/ui/screens/AddEditNews/NewsDescription.dart
Normal file
302
news-app/lib/ui/screens/AddEditNews/NewsDescription.dart
Normal file
@@ -0,0 +1,302 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:html_editor_plus/html_editor.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/ui/screens/AddEditNews/Widgets/geminiService.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
|
||||
class NewsDescription extends StatefulWidget {
|
||||
String? description;
|
||||
Function changeDesc;
|
||||
Function? validateDesc;
|
||||
int? from;
|
||||
String? summDescription;
|
||||
Function? isDraftNews;
|
||||
|
||||
NewsDescription(this.description, this.summDescription, this.changeDesc, this.validateDesc, this.from, this.isDraftNews, {super.key});
|
||||
|
||||
@override
|
||||
NewsDescriptionState createState() => NewsDescriptionState();
|
||||
}
|
||||
|
||||
class NewsDescriptionState extends State<NewsDescription> {
|
||||
String result = '';
|
||||
bool isLoading = true;
|
||||
bool isSubmitted = false;
|
||||
|
||||
final HtmlEditorController controller = HtmlEditorController();
|
||||
|
||||
final TextEditingController summaryController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
setValue();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
setValue() async {
|
||||
Future.delayed(
|
||||
const Duration(seconds: 4),
|
||||
() {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
getAppBar() {
|
||||
return PreferredSize(
|
||||
preferredSize: const Size(double.infinity, 45),
|
||||
child: UiUtils.applyBoxShadow(
|
||||
context: context,
|
||||
child: AppBar(
|
||||
centerTitle: false,
|
||||
backgroundColor: Colors.transparent,
|
||||
title: Transform(
|
||||
transform: Matrix4.translationValues(-20.0, 0.0, 0.0),
|
||||
child: CustomTextLabel(
|
||||
text: widget.from == 2 ? 'editNewsLbl' : 'createNewsLbl',
|
||||
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5),
|
||||
),
|
||||
),
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
controller.getText().then((value) {
|
||||
widget.changeDesc(value, summaryController.text, false);
|
||||
});
|
||||
},
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
child: Icon(Icons.arrow_back, color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Container(
|
||||
padding: const EdgeInsetsDirectional.only(end: 20),
|
||||
alignment: Alignment.center,
|
||||
child: CustomTextLabel(text: 'step2of2Lbl', textStyle: Theme.of(context).textTheme.bodySmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6))),
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Widget nextBtn() {
|
||||
return (isSubmitted)
|
||||
? UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)
|
||||
: Padding(
|
||||
padding: const EdgeInsetsDirectional.all(10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => validateTextAndSubmit(isDraft: 1),
|
||||
splashColor: Colors.transparent,
|
||||
child: Container(
|
||||
height: 45.0,
|
||||
width: MediaQuery.of(context).size.width * 0.4,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(7.0),
|
||||
border: Border.all(color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
),
|
||||
child: CustomTextLabel(
|
||||
text: 'saveAsDraftLbl',
|
||||
textStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, fontSize: 18, letterSpacing: 0.6))),
|
||||
)),
|
||||
SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
child: Container(
|
||||
height: 45.0,
|
||||
width: MediaQuery.of(context).size.width * 0.4,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(color: UiUtils.getColorScheme(context).primaryContainer, borderRadius: BorderRadius.circular(7.0)),
|
||||
child: CustomTextLabel(
|
||||
text: 'publishBtnLbl',
|
||||
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).surface, fontWeight: FontWeight.w600, fontSize: 18, letterSpacing: 0.6),
|
||||
),
|
||||
),
|
||||
onTap: () => validateTextAndSubmit(isDraft: 0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void validateTextAndSubmit({required int isDraft}) {
|
||||
widget.isDraftNews!(isDraft);
|
||||
controller.getText().then((value) {
|
||||
isSubmitted = true;
|
||||
widget.validateDesc!(value, summaryController.text);
|
||||
});
|
||||
}
|
||||
|
||||
Widget shimmer() {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Shimmer.fromColors(
|
||||
baseColor: Colors.grey[300]!,
|
||||
highlightColor: Colors.grey[100]!,
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height * 0.741,
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: Theme.of(context).cardColor),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.description != null && widget.description != "") controller.setText(widget.description!); //incase of Edit
|
||||
return Scaffold(
|
||||
appBar: getAppBar(),
|
||||
bottomNavigationBar: nextBtn(),
|
||||
body: PopScope(
|
||||
canPop: false,
|
||||
onPopInvoked: (bool isTrue) {
|
||||
controller.getText().then((value) {
|
||||
widget.changeDesc(value, summaryController.text, false);
|
||||
});
|
||||
},
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (!kIsWeb) {
|
||||
FocusScope.of(context).unfocus(); //dismiss keyboard
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.all(20),
|
||||
child: isLoading
|
||||
? shimmer()
|
||||
: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.45,
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(textTheme: TextTheme(titleSmall: Theme.of(context).textTheme.titleMedium!.copyWith(color: Colors.orange))),
|
||||
child: HtmlEditor(
|
||||
controller: controller,
|
||||
htmlEditorOptions: HtmlEditorOptions(
|
||||
hint: UiUtils.getTranslatedLabel(context, 'descLbl'),
|
||||
adjustHeightForKeyboard: true,
|
||||
autoAdjustHeight: true,
|
||||
shouldEnsureVisible: true,
|
||||
spellCheck: true,
|
||||
disabled: false),
|
||||
htmlToolbarOptions: HtmlToolbarOptions(
|
||||
toolbarPosition: ToolbarPosition.aboveEditor,
|
||||
toolbarType: ToolbarType.nativeExpandable,
|
||||
gridViewHorizontalSpacing: 0,
|
||||
gridViewVerticalSpacing: 0,
|
||||
toolbarItemHeight: 30,
|
||||
buttonColor: UiUtils.getColorScheme(context).primaryContainer,
|
||||
buttonFocusColor: Theme.of(context).primaryColor,
|
||||
buttonBorderColor: Colors.red,
|
||||
buttonFillColor: secondaryColor,
|
||||
dropdownIconColor: Theme.of(context).primaryColor,
|
||||
dropdownIconSize: 26,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
onButtonPressed: (ButtonType type, bool? status, Function? updateStatus) {
|
||||
return true;
|
||||
},
|
||||
onDropdownChanged: (DropdownType type, dynamic changed, Function(dynamic)? updateSelectedItem) {
|
||||
return true;
|
||||
},
|
||||
mediaLinkInsertInterceptor: (String url, InsertFileType type) {
|
||||
return true;
|
||||
},
|
||||
mediaUploadInterceptor: (PlatformFile file, InsertFileType type) async {
|
||||
return true;
|
||||
},
|
||||
),
|
||||
otherOptions: OtherOptions(
|
||||
height: MediaQuery.of(context).size.height * 0.725,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: UiUtils.getColorScheme(context).surface,
|
||||
),
|
||||
),
|
||||
callbacks: Callbacks(
|
||||
onChangeCodeview: (String? changed) {
|
||||
result = changed!;
|
||||
},
|
||||
onImageUploadError: (
|
||||
FileUpload? file,
|
||||
String? base64Str,
|
||||
UploadError error,
|
||||
) {},
|
||||
onNavigationRequestMobile: (String url) {
|
||||
return NavigationActionPolicy.ALLOW;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
summarySection()
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget summarySection() {
|
||||
if (widget.summDescription != null && widget.summDescription!.isNotEmpty) summaryController.text = widget.summDescription!;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
CustomTextLabel(text: UiUtils.getTranslatedLabel(context, 'summarizedDescription'), textStyle: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 10),
|
||||
TextField(
|
||||
controller: summaryController,
|
||||
maxLines: 3,
|
||||
readOnly: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: UiUtils.getTranslatedLabel(context, 'clickSummarizeDescription'),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)),
|
||||
filled: true,
|
||||
contentPadding: EdgeInsets.all(5),
|
||||
fillColor: UiUtils.getColorScheme(context).surface)),
|
||||
const SizedBox(height: 10),
|
||||
Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
String fullText = await controller.getText();
|
||||
if (fullText.trim().isEmpty) return showSnackBar('enterDescriptionFirst', context);
|
||||
String summary = await GeminiService.summarizeDescription(fullText, context.read<AppConfigurationCubit>().getGeminiAPiKey());
|
||||
setState(() {
|
||||
summaryController.text = summary;
|
||||
});
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: UiUtils.getColorScheme(context).primaryContainer,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(7)),
|
||||
),
|
||||
child: Text(
|
||||
UiUtils.getTranslatedLabel(context, 'summarizedDescription'),
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).surface, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
251
news-app/lib/ui/screens/AddEditNews/Widgets/UserNewsWidgets.dart
Normal file
251
news-app/lib/ui/screens/AddEditNews/Widgets/UserNewsWidgets.dart
Normal file
@@ -0,0 +1,251 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/deleteUserNewsCubit.dart';
|
||||
import 'package:news/cubits/getUserNewsCubit.dart';
|
||||
import 'package:news/cubits/themeCubit.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/ui/styles/appTheme.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
import 'package:news/ui/widgets/customTextBtn.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart';
|
||||
import 'package:news/utils/hiveBoxKeys.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class UsernewsWidgets {
|
||||
static final labelKeys = {"standard_post": 'stdPostLbl', "video_youtube": 'videoYoutubeLbl', "video_other": 'videoOtherUrlLbl', "video_upload": 'videoUploadLbl'};
|
||||
|
||||
static buildNewsContainer(
|
||||
{required BuildContext context,
|
||||
required NewsModel model,
|
||||
required int index,
|
||||
required int totalCurrentNews,
|
||||
required bool hasMoreNewsFetchError,
|
||||
required bool hasMore,
|
||||
required Function fetchMoreNews}) {
|
||||
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: () => fetchMoreNews, 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 InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": model, "isFromBreak": false, "fromShowMore": false});
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface),
|
||||
padding: const EdgeInsetsDirectional.all(15),
|
||||
margin: const EdgeInsets.only(top: 20),
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.24,
|
||||
height: MediaQuery.of(context).size.height * 0.26,
|
||||
child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
newsImage(imageURL: model.image!, context: context),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10.0),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
categoryName(context: context, categoryName: (model.categoryName != null && model.categoryName!.trim().isNotEmpty) ? model.categoryName! : ""),
|
||||
setDate(context: context, dateValue: model.date!)
|
||||
]),
|
||||
),
|
||||
Spacer(),
|
||||
deleteAndEditButton(context: context, isEdit: true, onTap: () => Navigator.of(context).pushNamed(Routes.addNews, arguments: {"model": model, "isEdit": true, "from": "myNews"})),
|
||||
deleteAndEditButton(context: context, isEdit: false, onTap: () => deleteNewsDialogue(context, model.id!, index))
|
||||
],
|
||||
),
|
||||
Divider(thickness: 2),
|
||||
Expanded(
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
CustomTextLabel(
|
||||
text: model.title!,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w700)),
|
||||
contentTypeView(context: context, model: model),
|
||||
])),
|
||||
Divider(thickness: 2),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
(model.isExpired == 1)
|
||||
? Container(
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPictureWidget(
|
||||
assetName: 'expiredNews',
|
||||
assetColor:
|
||||
(context.read<ThemeCubit>().state.appTheme == AppTheme.Dark ? ColorFilter.mode(darkIconColor, BlendMode.srcIn) : ColorFilter.mode(iconColor, BlendMode.srcIn))),
|
||||
SizedBox(width: 2.5),
|
||||
Text(
|
||||
UiUtils.getTranslatedLabel(context, 'expiredKey'),
|
||||
style: TextStyle(color: (context.read<ThemeCubit>().state.appTheme == AppTheme.Dark ? darkIconColor : iconColor), fontWeight: FontWeight.w500),
|
||||
),
|
||||
],
|
||||
))
|
||||
: SizedBox.shrink(),
|
||||
(model.status == "0")
|
||||
? Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 3, horizontal: 5),
|
||||
child: Tooltip(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 20),
|
||||
decoration: BoxDecoration(color: UiUtils.getColorScheme(context).primaryContainer, borderRadius: BorderRadius.circular(10)),
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).secondary, fontSize: 10),
|
||||
message: UiUtils.getTranslatedLabel(context, 'newsCreatedSuccessfully'),
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPictureWidget(
|
||||
assetName: 'deactivatedNews',
|
||||
assetColor: (context.read<ThemeCubit>().state.appTheme == AppTheme.Dark
|
||||
? const ColorFilter.mode(darkIconColor, BlendMode.srcIn)
|
||||
: const ColorFilter.mode(iconColor, BlendMode.srcIn))),
|
||||
const SizedBox(width: 2.5),
|
||||
Text(
|
||||
UiUtils.getTranslatedLabel(context, 'deactivatedKey'),
|
||||
style: TextStyle(color: (context.read<ThemeCubit>().state.appTheme == AppTheme.Dark ? darkIconColor : iconColor), fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
))
|
||||
: SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
]),
|
||||
)));
|
||||
}
|
||||
|
||||
static Widget newsImage({required BuildContext context, required String imageURL}) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(45),
|
||||
child: CustomNetworkImage(networkImageUrl: imageURL, fit: BoxFit.cover, height: MediaQuery.of(context).size.width * 0.18, isVideo: false, width: MediaQuery.of(context).size.width * 0.18));
|
||||
}
|
||||
|
||||
static Widget categoryName({required BuildContext context, required String categoryName}) {
|
||||
return (categoryName.trim().isNotEmpty)
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: CustomTextLabel(
|
||||
text: categoryName,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
textStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.9), fontSize: 16, fontWeight: FontWeight.w600)),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
|
||||
static Widget deleteAndEditButton({required BuildContext context, required bool isEdit, required void Function()? onTap}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsetsDirectional.only(top: 3, bottom: 3, start: 5),
|
||||
alignment: Alignment.center,
|
||||
child: SvgPictureWidget(
|
||||
assetName: (isEdit) ? 'editMyNews' : 'deleteMyNews',
|
||||
height: 30,
|
||||
width: 30,
|
||||
fit: BoxFit.contain,
|
||||
assetColor: (isEdit) ? ColorFilter.mode(UiUtils.getColorScheme(context).onPrimary, BlendMode.srcIn) : null),
|
||||
));
|
||||
}
|
||||
|
||||
static Widget setDate({required BuildContext context, required String dateValue}) {
|
||||
DateTime time = DateTime.parse(dateValue);
|
||||
var newFormat = DateFormat("dd-MMM-yyyy", Hive.box(settingsBoxKey).get(currentLanguageCodeKey));
|
||||
final newNewsDate = newFormat.format(time);
|
||||
|
||||
return CustomTextLabel(
|
||||
text: newNewsDate,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8)));
|
||||
}
|
||||
|
||||
static deleteNewsDialogue(BuildContext mainContext, String id, int index) async {
|
||||
await showDialog(
|
||||
context: mainContext,
|
||||
builder: (BuildContext context) {
|
||||
return StatefulBuilder(builder: (BuildContext context, StateSetter setStater) {
|
||||
return AlertDialog(
|
||||
backgroundColor: UiUtils.getColorScheme(context).surface,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))),
|
||||
content: CustomTextLabel(text: 'doYouReallyNewsLbl', textStyle: Theme.of(context).textTheme.titleMedium),
|
||||
title: const CustomTextLabel(text: 'delNewsLbl'),
|
||||
titleTextStyle: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w600),
|
||||
actions: <Widget>[
|
||||
CustomTextButton(
|
||||
textWidget: CustomTextLabel(
|
||||
text: 'noLbl', textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.bold)),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop(false);
|
||||
}),
|
||||
BlocConsumer<DeleteUserNewsCubit, DeleteUserNewsState>(
|
||||
bloc: context.read<DeleteUserNewsCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is DeleteUserNewsSuccess) {
|
||||
context.read<GetUserNewsCubit>().deleteNews(index);
|
||||
showSnackBar(state.message, context);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return CustomTextButton(
|
||||
textWidget: CustomTextLabel(
|
||||
text: 'yesLbl', textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.bold)),
|
||||
onTap: () async {
|
||||
context.read<DeleteUserNewsCubit>().setDeleteUserNews(newsId: id);
|
||||
});
|
||||
})
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static Widget contentTypeView({required BuildContext context, required NewsModel model}) {
|
||||
String contType = "";
|
||||
|
||||
final key = labelKeys[model.contentType];
|
||||
if (key != null) {
|
||||
contType = UiUtils.getTranslatedLabel(context, key);
|
||||
}
|
||||
return (model.contentType != "")
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(top: 7),
|
||||
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
CustomTextLabel(
|
||||
text: 'contentTypeLbl',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
textStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withAlpha((0.3 * 255).round()))),
|
||||
CustomTextLabel(text: " : ", textStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withAlpha((0.3 * 255).round()))),
|
||||
CustomTextLabel(
|
||||
text: contType,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withAlpha((0.3 * 255).round())))
|
||||
]),
|
||||
)
|
||||
: SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/categoryCubit.dart';
|
||||
import 'package:news/cubits/locationCityCubit.dart';
|
||||
import 'package:news/cubits/updateBottomsheetContentCubit.dart';
|
||||
import 'package:news/cubits/tagCubit.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
|
||||
class CustomBottomsheet extends StatefulWidget {
|
||||
final BuildContext context;
|
||||
final String titleTxt;
|
||||
final int listLength;
|
||||
final String language_id;
|
||||
final NullableIndexedWidgetBuilder listViewChild;
|
||||
|
||||
const CustomBottomsheet({super.key, required this.context, required this.titleTxt, required this.listLength, required this.listViewChild, required this.language_id});
|
||||
|
||||
@override
|
||||
CustomBottomsheetState createState() => CustomBottomsheetState();
|
||||
}
|
||||
|
||||
class CustomBottomsheetState extends State<CustomBottomsheet> {
|
||||
late final ScrollController locationScrollController = ScrollController();
|
||||
late final ScrollController languageScrollController = ScrollController();
|
||||
late final ScrollController categoryScrollController = ScrollController();
|
||||
late final ScrollController subcategoryScrollController = ScrollController();
|
||||
late final ScrollController tagScrollController = ScrollController();
|
||||
|
||||
ScrollController scController = ScrollController();
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
initScrollController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
disposeScrollController();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void initScrollController() {
|
||||
switch (widget.titleTxt) {
|
||||
case 'chooseLanLbl':
|
||||
scController = languageScrollController;
|
||||
break;
|
||||
case 'selCatLbl':
|
||||
scController = categoryScrollController;
|
||||
break;
|
||||
case 'selSubCatLbl':
|
||||
scController = subcategoryScrollController;
|
||||
break;
|
||||
case 'selTagLbl':
|
||||
scController = tagScrollController;
|
||||
break;
|
||||
case 'selLocationLbl':
|
||||
scController = locationScrollController;
|
||||
break;
|
||||
}
|
||||
scController.addListener(() => hasMoreLocationScrollListener());
|
||||
}
|
||||
|
||||
disposeScrollController() {
|
||||
switch (widget.titleTxt) {
|
||||
case 'chooseLanLbl':
|
||||
languageScrollController.dispose();
|
||||
break;
|
||||
case 'selCatLbl':
|
||||
categoryScrollController.dispose();
|
||||
break;
|
||||
case 'selSubCatLbl':
|
||||
subcategoryScrollController.dispose();
|
||||
break;
|
||||
case 'selTagLbl':
|
||||
tagScrollController.dispose();
|
||||
break;
|
||||
case 'selLocationLbl':
|
||||
locationScrollController.dispose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void hasMoreLocationScrollListener() {
|
||||
if (scController.offset >= scController.position.maxScrollExtent && !scController.position.outOfRange) {
|
||||
switch (widget.titleTxt) {
|
||||
case 'selCatLbl':
|
||||
if (context.read<CategoryCubit>().hasMoreCategory()) {
|
||||
context.read<CategoryCubit>().getMoreCategory(langId: widget.language_id);
|
||||
}
|
||||
break;
|
||||
case 'selTagLbl':
|
||||
if (context.read<TagCubit>().hasMoreTags()) {
|
||||
context.read<TagCubit>().getMoreTags(langId: widget.language_id);
|
||||
}
|
||||
break;
|
||||
case 'selLocationLbl':
|
||||
if (context.read<LocationCityCubit>().hasMoreLocation()) {
|
||||
context.read<LocationCityCubit>().getMoreLocationCity();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Builder(
|
||||
builder: (BuildContext context) => BlocBuilder<BottomSheetCubit, BottomSheetState>(
|
||||
builder: (context, state) {
|
||||
int listLength = widget.listLength;
|
||||
switch (widget.titleTxt) {
|
||||
case 'selLocationLbl':
|
||||
listLength = state.locationData.length;
|
||||
break;
|
||||
case 'selTagLbl':
|
||||
listLength = state.tagsData.length;
|
||||
break;
|
||||
case 'chooseLanLbl':
|
||||
listLength = state.languageData.length;
|
||||
break;
|
||||
case 'selCatLbl':
|
||||
listLength = state.categoryData.length;
|
||||
}
|
||||
return DraggableScrollableSheet(
|
||||
snap: true,
|
||||
snapSizes: const [0.5, 0.9],
|
||||
expand: false,
|
||||
builder: (_, controller) {
|
||||
controller = scController;
|
||||
return Container(
|
||||
padding: const EdgeInsetsDirectional.only(bottom: 15.0, top: 15.0, start: 20.0, end: 20.0),
|
||||
decoration: BoxDecoration(borderRadius: const BorderRadius.only(topLeft: Radius.circular(30), topRight: Radius.circular(30)), color: UiUtils.getColorScheme(context).surface),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CustomTextLabel(
|
||||
text: widget.titleTxt,
|
||||
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold, color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
const SizedBox(height: 10),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsetsDirectional.only(top: 10.0, bottom: 25.0),
|
||||
itemCount: listLength,
|
||||
itemBuilder: widget.listViewChild)),
|
||||
],
|
||||
));
|
||||
});
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
149
news-app/lib/ui/screens/AddEditNews/Widgets/geminiService.dart
Normal file
149
news-app/lib/ui/screens/AddEditNews/Widgets/geminiService.dart
Normal file
@@ -0,0 +1,149 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:news/utils/api.dart';
|
||||
|
||||
/// AI Service Module using Google Gemini API
|
||||
/// This module contains all AI-related API calls for the application
|
||||
|
||||
class GeminiService {
|
||||
static const String _geminiModel = "gemini-2.0-flash"; // Or gemini-1.5-pro
|
||||
|
||||
/// Helper function to call Gemini API
|
||||
static Future<Map<String, dynamic>> _callGeminiAPI(String prompt, String apiKey) async {
|
||||
try {
|
||||
final requestBody = {
|
||||
"contents": [
|
||||
{
|
||||
"parts": [
|
||||
{"text": prompt}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
final url = Uri.parse("${Api.geminiMetaInfoApi}$_geminiModel:generateContent?key=$apiKey");
|
||||
|
||||
final response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: jsonEncode(requestBody),
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
final errorData = jsonDecode(response.body);
|
||||
throw Exception(errorData["error"]?["message"] ?? "Failed to generate content");
|
||||
}
|
||||
|
||||
return jsonDecode(response.body);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate content
|
||||
static Future<String> generateContent({
|
||||
String? title,
|
||||
String? category,
|
||||
String? language,
|
||||
String? languageCode,
|
||||
required String apiKey,
|
||||
}) async {
|
||||
try {
|
||||
String fullPrompt = "You are a skilled news article writer. Create engaging and informative content.";
|
||||
|
||||
if (title != null) {
|
||||
fullPrompt += "\n\nWrite an article with the title: \"$title\"";
|
||||
}
|
||||
|
||||
if (category != null) {
|
||||
fullPrompt += "\nCategory: $category";
|
||||
}
|
||||
|
||||
if (language != null && languageCode != null) {
|
||||
fullPrompt += "\n\nIMPORTANT: Generate all content in $language language ($languageCode). The response MUST be in $language.";
|
||||
}
|
||||
|
||||
fullPrompt += "\n\nRequest: \n\nArticle:";
|
||||
|
||||
final response = await _callGeminiAPI(fullPrompt, apiKey);
|
||||
|
||||
return response["candidates"][0]["content"]["parts"][0]["text"];
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate meta info
|
||||
static Future<Map<String, dynamic>> generateMetaInfo({required String title, String? language, String? languageCode, required String apiKey}) async {
|
||||
try {
|
||||
String languageInstruction = "";
|
||||
if (language != null && languageCode != null) {
|
||||
languageInstruction = "\n\nIMPORTANT: Generate all content in $language language ($languageCode). The response MUST be in same language as title.";
|
||||
}
|
||||
|
||||
final prompt = """
|
||||
You are an SEO expert. Generate meta title, description, keywords, and a slug for this news article titled: "$title".$languageInstruction
|
||||
|
||||
Return ONLY a JSON object with these fields:
|
||||
- meta_title
|
||||
- meta_description
|
||||
- meta_keywords
|
||||
- slug
|
||||
|
||||
Response must be valid JSON.
|
||||
""";
|
||||
|
||||
final response = await _callGeminiAPI(prompt, apiKey);
|
||||
final responseText = response["candidates"][0]["content"]["parts"][0]["text"].trim();
|
||||
|
||||
try {
|
||||
return jsonDecode(responseText);
|
||||
} catch (_) {
|
||||
final match = RegExp(r"\{[\s\S]*\}").firstMatch(responseText);
|
||||
if (match != null) {
|
||||
return jsonDecode(match.group(0)!);
|
||||
}
|
||||
return {
|
||||
"meta_title": title,
|
||||
"meta_description": "Read about $title in our latest news article.",
|
||||
"meta_keywords": title.toLowerCase().split(" ").join(","),
|
||||
"slug": title.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]+'), "-").replaceAll(RegExp(r'^-|-$'), ""),
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Summarize description
|
||||
static Future<String> summarizeDescription(String description, String apiKey, {String language = "English", String languageCode = "en"}) async {
|
||||
try {
|
||||
if (description.trim().isEmpty) return "";
|
||||
|
||||
final cleanContent = description.replaceAll(RegExp(r"<[^>]*>"), "").trim();
|
||||
if (cleanContent.isEmpty) return "";
|
||||
|
||||
final prompt = """
|
||||
You are a skilled content summarizer. Summarize the following news content:
|
||||
|
||||
Content: "$cleanContent"
|
||||
|
||||
Instructions:
|
||||
- 200-250 words
|
||||
- Maintain key facts
|
||||
- Professional news style
|
||||
- No explanations, only summary
|
||||
- IMPORTANT: Generate in $language ($languageCode).
|
||||
|
||||
Summary:""";
|
||||
|
||||
final response = await _callGeminiAPI(prompt, apiKey);
|
||||
final summary = response["candidates"][0]["content"]["parts"][0]["text"].trim();
|
||||
|
||||
String finalSummary = summary.replaceAll(RegExp(r"^['\']+|['\']+$"), '').trim();
|
||||
return finalSummary;
|
||||
} catch (e) {
|
||||
return description.replaceAll(RegExp(r"<[^>]*>"), "").substring(0, 150) + "...";
|
||||
}
|
||||
}
|
||||
}
|
||||
49
news-app/lib/ui/screens/AddEditNews/Widgets/userAllNews.dart
Normal file
49
news-app/lib/ui/screens/AddEditNews/Widgets/userAllNews.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/getUserNewsCubit.dart';
|
||||
import 'package:news/ui/screens/AddEditNews/Widgets/UserNewsWidgets.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class UserAllNewsTab extends StatelessWidget {
|
||||
final ScrollController controller;
|
||||
final Widget contentShimmer;
|
||||
final Function fetchNews;
|
||||
final Function fetchMoreNews;
|
||||
|
||||
UserAllNewsTab({super.key, required this.controller, required this.contentShimmer, required this.fetchNews, required this.fetchMoreNews});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<GetUserNewsCubit, GetUserNewsState>(builder: (context, state) {
|
||||
if (state is GetUserNewsFetchSuccess) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async => fetchNews,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 10, end: 10, bottom: 10),
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: state.getUserNews.length,
|
||||
itemBuilder: (context, index) {
|
||||
return UsernewsWidgets.buildNewsContainer(
|
||||
context: context,
|
||||
model: state.getUserNews[index],
|
||||
hasMore: state.hasMore,
|
||||
hasMoreNewsFetchError: state.hasMoreFetchError,
|
||||
index: index,
|
||||
totalCurrentNews: state.getUserNews.length,
|
||||
fetchMoreNews: fetchMoreNews);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (state is GetUserNewsFetchFailure) {
|
||||
return ErrorContainerWidget(errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: fetchNews);
|
||||
}
|
||||
return contentShimmer;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/GetUserDraftedNewsCubit.dart';
|
||||
import 'package:news/ui/screens/AddEditNews/Widgets/UserNewsWidgets.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class UserDrafterNewsTab extends StatelessWidget {
|
||||
final ScrollController controller;
|
||||
final Widget contentShimmer;
|
||||
final Function fetchDraftedNews;
|
||||
final Function fetchMoreDraftedNews;
|
||||
|
||||
UserDrafterNewsTab({super.key, required this.controller, required this.contentShimmer, required this.fetchDraftedNews, required this.fetchMoreDraftedNews});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<GetUserDraftedNewsCubit, GetUserDraftedNewsState>(builder: (context, state) {
|
||||
if (state is GetUserDraftedNewsFetchSuccess) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 10, end: 10, bottom: 10),
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async => fetchDraftedNews,
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: state.GetUserDraftedNews.length,
|
||||
itemBuilder: (context, index) {
|
||||
return UsernewsWidgets.buildNewsContainer(
|
||||
context: context,
|
||||
model: state.GetUserDraftedNews[index],
|
||||
hasMore: state.hasMore,
|
||||
hasMoreNewsFetchError: state.hasMoreFetchError,
|
||||
index: index,
|
||||
totalCurrentNews: state.GetUserDraftedNews.length,
|
||||
fetchMoreNews: fetchMoreDraftedNews);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (state is GetUserDraftedNewsFetchFailure) {
|
||||
return ErrorContainerWidget(
|
||||
errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: fetchDraftedNews);
|
||||
}
|
||||
return contentShimmer;
|
||||
});
|
||||
}
|
||||
}
|
||||
115
news-app/lib/ui/screens/BookmarkScreen.dart
Normal file
115
news-app/lib/ui/screens/BookmarkScreen.dart
Normal file
@@ -0,0 +1,115 @@
|
||||
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/Bookmark/bookmarkCubit.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/ui/widgets/customAppBar.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/ui/widgets/newsCard.dart';
|
||||
import 'package:news/ui/widgets/shimmerNewsList.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class BookmarkScreen extends StatefulWidget {
|
||||
const BookmarkScreen({super.key});
|
||||
|
||||
@override
|
||||
BookmarkScreenState createState() => BookmarkScreenState();
|
||||
}
|
||||
|
||||
class BookmarkScreenState extends State<BookmarkScreen> {
|
||||
late final ScrollController _controller = ScrollController()..addListener(hasMoreBookmarkScrollListener);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getBookMark();
|
||||
}
|
||||
|
||||
void getBookMark() async {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
context.read<BookmarkCubit>().getBookmark(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
}
|
||||
}
|
||||
|
||||
void hasMoreBookmarkScrollListener() {
|
||||
if (_controller.position.maxScrollExtent == _controller.offset) {
|
||||
if (context.read<BookmarkCubit>().hasMoreBookmark()) {
|
||||
context.read<BookmarkCubit>().getMoreBookmark(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: CustomAppBar(height: 45, isBackBtn: true, label: 'bookmarkLbl', horizontalPad: 15, isConvertText: true),
|
||||
body: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(bottom: 10.0),
|
||||
child: BlocBuilder<BookmarkCubit, BookmarkState>(
|
||||
builder: (context, state) {
|
||||
if (state is BookmarkFetchSuccess && state.bookmark.isNotEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 15.0, end: 15.0, top: 10.0, bottom: 10.0),
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
getBookMark();
|
||||
},
|
||||
child: ListView.builder(
|
||||
controller: _controller,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemCount: state.bookmark.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildBookmarkContainer(model: state.bookmark[index], hasMore: state.hasMore, hasMoreBookFetchError: state.hasMoreFetchError, index: index, totalCurrentBook: 6);
|
||||
}),
|
||||
),
|
||||
);
|
||||
} else if (state is BookmarkFetchFailure || ((state is! BookmarkFetchInProgress))) {
|
||||
if (state is BookmarkFetchFailure) {
|
||||
return ErrorContainerWidget(
|
||||
errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getBookMark);
|
||||
} else {
|
||||
return const Center(child: CustomTextLabel(text: 'bookmarkNotAvail', textAlign: TextAlign.center));
|
||||
}
|
||||
}
|
||||
//default/Processing state
|
||||
return Padding(padding: const EdgeInsets.only(bottom: 10.0, left: 10.0, right: 10.0), child: ShimmerNewsList(isNews: false));
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
_buildBookmarkContainer({required NewsModel model, required int index, required int totalCurrentBook, required bool hasMoreBookFetchError, required bool hasMore}) {
|
||||
if (index == totalCurrentBook - 1 && index != 0 && hasMore) {
|
||||
if (hasMoreBookFetchError) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
context.read<BookmarkCubit>().getMoreBookmark(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
},
|
||||
icon: Icon(Icons.error, color: Theme.of(context).primaryColor))));
|
||||
}
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 15.0),
|
||||
child: NewsCard(
|
||||
newsDetail: model,
|
||||
showViews: true,
|
||||
onTap: () async {
|
||||
//Interstitial Ad here
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": model, "isFromBreak": false, "fromShowMore": false});
|
||||
}));
|
||||
}
|
||||
}
|
||||
127
news-app/lib/ui/screens/CategoryScreen.dart
Normal file
127
news-app/lib/ui/screens/CategoryScreen.dart
Normal file
@@ -0,0 +1,127 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/categoryCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/data/models/CategoryModel.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/ui/widgets/customAppBar.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
|
||||
class CategoryScreen extends StatefulWidget {
|
||||
const CategoryScreen({super.key});
|
||||
|
||||
@override
|
||||
CategoryScreenState createState() => CategoryScreenState();
|
||||
}
|
||||
|
||||
class CategoryScreenState extends State<CategoryScreen> {
|
||||
late final ScrollController _categoryScrollController = ScrollController()..addListener(hasMoreCategoryScrollListener);
|
||||
|
||||
void getCategory() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<CategoryCubit>().getCategory(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
getCategory();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_categoryScrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void hasMoreCategoryScrollListener() {
|
||||
if (_categoryScrollController.offset >= _categoryScrollController.position.maxScrollExtent && !_categoryScrollController.position.outOfRange) {
|
||||
if (context.read<CategoryCubit>().hasMoreCategory()) {
|
||||
context.read<CategoryCubit>().getMoreCategory(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
} else {}
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildCategory() {
|
||||
return BlocBuilder<CategoryCubit, CategoryState>(
|
||||
builder: (context, state) {
|
||||
if (state is CategoryFetchSuccess) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
getCategory();
|
||||
},
|
||||
child: GridView.count(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
scrollDirection: Axis.vertical,
|
||||
padding: EdgeInsets.only(top: 15, bottom: MediaQuery.of(context).size.height / 10.0, left: 15, right: 15),
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 10,
|
||||
mainAxisSpacing: 10,
|
||||
childAspectRatio: 0.92,
|
||||
shrinkWrap: true,
|
||||
controller: _categoryScrollController,
|
||||
children: List.generate(state.category.length, (index) {
|
||||
return _buildCategoryContainer(
|
||||
category: state.category[index], hasMore: state.hasMore, hasMoreCategoryFetchError: state.hasMoreFetchError, index: index, totalCurrentCategory: state.category.length);
|
||||
}),
|
||||
));
|
||||
}
|
||||
if (state is CategoryFetchFailure) {
|
||||
return ErrorContainerWidget(
|
||||
errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getCategory);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_buildCategoryContainer({required CategoryModel category, required int index, required int totalCurrentCategory, required bool hasMoreCategoryFetchError, required bool hasMore}) {
|
||||
if (index == totalCurrentCategory - 1 && index != 0) {
|
||||
if (hasMore) {
|
||||
if (hasMoreCategoryFetchError) {
|
||||
return const SizedBox.shrink();
|
||||
} else {
|
||||
return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.subCat, arguments: {"catId": category.id, "catName": category.categoryName});
|
||||
},
|
||||
child: Card(
|
||||
color: UiUtils.getColorScheme(context).surface,
|
||||
child: Column(
|
||||
spacing: 10,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
(category.image != null)
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(7.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadiusGeometry.circular(3), child: CustomNetworkImage(networkImageUrl: category.image!, height: 100, width: 140, isVideo: false, fit: BoxFit.cover)),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: CustomTextLabel(
|
||||
text: category.categoryName!,
|
||||
textStyle: TextStyle(fontWeight: FontWeight.w600, fontSize: 16, color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(appBar: CustomAppBar(height: 45, isBackBtn: false, label: 'categoryLbl', isConvertText: true), body: _buildCategory());
|
||||
}
|
||||
}
|
||||
407
news-app/lib/ui/screens/HomePage/HomePage.dart
Normal file
407
news-app/lib/ui/screens/HomePage/HomePage.dart
Normal file
@@ -0,0 +1,407 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:location/location.dart' as loc;
|
||||
import 'package:marqueer/marqueer.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/Auth/registerTokenCubit.dart';
|
||||
import 'package:news/cubits/Bookmark/bookmarkCubit.dart';
|
||||
import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/cubits/breakingNewsCubit.dart';
|
||||
import 'package:news/cubits/featureSectionCubit.dart';
|
||||
import 'package:news/cubits/generalNewsCubit.dart';
|
||||
import 'package:news/cubits/getUserDataByIdCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/languageJsonCubit.dart';
|
||||
import 'package:news/cubits/liveStreamCubit.dart';
|
||||
import 'package:news/cubits/otherPagesCubit.dart';
|
||||
import 'package:news/cubits/sectionByIdCubit.dart';
|
||||
import 'package:news/cubits/settingCubit.dart';
|
||||
import 'package:news/cubits/weatherCubit.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/data/models/authorModel.dart';
|
||||
import 'package:news/data/repositories/SectionById/sectionByIdRepository.dart';
|
||||
import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart';
|
||||
import 'package:news/ui/screens/HomePage/Widgets/GeneralNewsRandomStyle.dart';
|
||||
import 'package:news/ui/screens/HomePage/Widgets/LiveWithSearchView.dart';
|
||||
import 'package:news/ui/screens/HomePage/Widgets/SectionShimmer.dart';
|
||||
import 'package:news/ui/screens/HomePage/Widgets/WeatherData.dart';
|
||||
import 'package:news/ui/screens/HomePage/Widgets/SectionStyle1.dart';
|
||||
import 'package:news/ui/screens/HomePage/Widgets/SectionStyle2.dart';
|
||||
import 'package:news/ui/screens/HomePage/Widgets/SectionStyle3.dart';
|
||||
import 'package:news/ui/screens/HomePage/Widgets/SectionStyle4.dart';
|
||||
import 'package:news/ui/screens/HomePage/Widgets/SectionStyle5.dart';
|
||||
import 'package:news/ui/screens/HomePage/Widgets/SectionStyle6.dart';
|
||||
import 'package:news/ui/screens/Profile/Widgets/customAlertDialog.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
import 'package:news/ui/widgets/adSpaces.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:news/utils/hiveBoxKeys.dart';
|
||||
import 'package:news/utils/strings.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/data/models/AuthModel.dart';
|
||||
import 'package:news/data/models/FeatureSectionModel.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
HomeScreenState createState() => HomeScreenState();
|
||||
}
|
||||
|
||||
class HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
|
||||
late final ScrollController featuredSectionsScrollController = ScrollController()..addListener(hasMoreFeaturedSectionsScrollListener);
|
||||
|
||||
final loc.Location _location = loc.Location();
|
||||
bool? _serviceEnabled;
|
||||
loc.PermissionStatus? _permissionGranted;
|
||||
double? lat;
|
||||
double? lon;
|
||||
bool updateList = false;
|
||||
Set<String> get locationValue => SettingsLocalDataRepository().getLocationCityValues();
|
||||
late final appConfig, authConfig;
|
||||
String languageId = "14"; //set it as default language code
|
||||
|
||||
void getSections() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<SectionCubit>().getSection(langId: languageId, latitude: locationValue.first, longitude: locationValue.last);
|
||||
}).whenComplete(() => getGeneralNews());
|
||||
}
|
||||
|
||||
void getLiveStreamData() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<LiveStreamCubit>().getLiveStream(langId: languageId);
|
||||
});
|
||||
}
|
||||
|
||||
void getBookmark() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<BookmarkCubit>().getBookmark(langId: languageId);
|
||||
});
|
||||
}
|
||||
|
||||
void getLikeNews() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<LikeAndDisLikeCubit>().getLike(langId: languageId);
|
||||
});
|
||||
}
|
||||
|
||||
void getUserData() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<GetUserByIdCubit>().getUserById();
|
||||
});
|
||||
}
|
||||
|
||||
checkForAppUpdate() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
if (context.read<AppConfigurationCubit>().state is AppConfigurationFetchSuccess) {
|
||||
if (context.read<AppConfigurationCubit>().isUpdateRequired()) {
|
||||
openUpdateDialog();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openUpdateDialog() {
|
||||
bool isForceUpdate = (context.read<AppConfigurationCubit>().getForceUpdateMode() != "" && context.read<AppConfigurationCubit>().getForceUpdateMode() == "1") ? true : false;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return StatefulBuilder(builder: (BuildContext context, StateSetter setStater) {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: CustomAlertDialog(
|
||||
isForceAppUpdate: (isForceUpdate) ? true : false,
|
||||
context: context,
|
||||
yesButtonText: 'yesLbl',
|
||||
yesButtonTextPostfix: '',
|
||||
noButtonText: (isForceUpdate) ? 'exitLbl' : 'noLbl',
|
||||
imageName: '',
|
||||
titleWidget: CustomTextLabel(
|
||||
text: (isForceUpdate) ? 'forceUpdateTitleLbl' : 'newVersionAvailableTitleLbl',
|
||||
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w800, color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
messageText: context.read<LanguageJsonCubit>().getTranslatedLabels((isForceUpdate) ? 'forceUpdateDescLbl' : 'newVersionAvailableDescLbl'),
|
||||
onYESButtonPressed: () => UiUtils.gotoStores(context)),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
showPermissionPopup() async {
|
||||
loc.LocationData locationData;
|
||||
|
||||
_serviceEnabled = await _location.serviceEnabled();
|
||||
if (!_serviceEnabled!) {
|
||||
_serviceEnabled = await _location.requestService();
|
||||
if (!_serviceEnabled!) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_permissionGranted = await _location.hasPermission();
|
||||
if (_permissionGranted == loc.PermissionStatus.denied) {
|
||||
SettingsLocalDataRepository().setLocationCityKeys(null, null);
|
||||
_permissionGranted = await _location.requestPermission();
|
||||
if (_permissionGranted != loc.PermissionStatus.granted) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
locationData = await _location.getLocation();
|
||||
|
||||
setState(() {
|
||||
lat = locationData.latitude;
|
||||
lon = locationData.longitude;
|
||||
});
|
||||
getLocationPermission();
|
||||
|
||||
return (locationData);
|
||||
}
|
||||
|
||||
getLocationPermission() async {
|
||||
if (appConfig.getLocationWiseNewsMode() == "1") {
|
||||
SettingsLocalDataRepository().setLocationCityKeys(lat, lon);
|
||||
//update latitude,longitude - along with token
|
||||
if (context.read<SettingsCubit>().getSettings().token != '') {
|
||||
context.read<RegisterTokenCubit>().registerToken(fcmId: context.read<SettingsCubit>().getSettings().token, context: context);
|
||||
context.read<SettingsCubit>().changeFcmToken(context.read<SettingsCubit>().getSettings().token);
|
||||
}
|
||||
} else {
|
||||
SettingsLocalDataRepository().setLocationCityKeys(null, null);
|
||||
}
|
||||
|
||||
if (appConfig.getWeatherMode() == "1") {
|
||||
getWeatherData();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getWeatherData() async {
|
||||
if (lat != null && lon != null) {
|
||||
context.read<WeatherCubit>().getWeatherDetails(langId: (Hive.box(settingsBoxKey).get(currentLanguageCodeKey)), lat: lat.toString(), lon: lon.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void getBreakingNews() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<BreakingNewsCubit>().getBreakingNews(langId: languageId);
|
||||
});
|
||||
}
|
||||
|
||||
void getGeneralNews() {
|
||||
context.read<GeneralNewsCubit>().getGeneralNews(langId: languageId, latitude: locationValue.first, longitude: locationValue.last);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
appConfig = context.read<AppConfigurationCubit>();
|
||||
authConfig = context.read<AuthCubit>();
|
||||
languageId = context.read<AppLocalizationCubit>().state.id;
|
||||
getSections();
|
||||
if (appConfig.getWeatherMode() == "1" || appConfig.getLocationWiseNewsMode() == "1") showPermissionPopup();
|
||||
if (authConfig.getUserId() != "0") {
|
||||
getUserData();
|
||||
}
|
||||
getLiveStreamData();
|
||||
if (appConfig.getBreakingNewsMode() == "1") getBreakingNews();
|
||||
if (appConfig.getMaintenanceMode() == "1") Navigator.of(context).pushReplacementNamed(Routes.maintenance);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
featuredSectionsScrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void hasMoreFeaturedSectionsScrollListener() {
|
||||
if (featuredSectionsScrollController.position.atEdge) {
|
||||
if (context.read<SectionCubit>().hasMoreSections()) {
|
||||
context.read<SectionCubit>().getMoreSections(langId: languageId, latitude: locationValue.first, longitude: locationValue.last);
|
||||
} else {}
|
||||
}
|
||||
}
|
||||
|
||||
Widget breakingNewsMarquee() {
|
||||
return BlocBuilder<BreakingNewsCubit, BreakingNewsState>(builder: ((context, state) {
|
||||
return (state is BreakingNewsFetchSuccess && state.breakingNews.isNotEmpty)
|
||||
? Container(
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
color: primaryColor,
|
||||
height: 32,
|
||||
child: Marqueer.builder(
|
||||
pps: 25.0,
|
||||
restartAfterInteractionDuration: const Duration(seconds: 1),
|
||||
separatorBuilder: (_, index) =>
|
||||
Center(child: Text(' ● ', style: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).secondary, fontWeight: FontWeight.normal))),
|
||||
itemBuilder: (context, index) {
|
||||
var multiplier = index ~/ state.breakingNews.length;
|
||||
var i = index;
|
||||
if (multiplier > 0) {
|
||||
i = index - (multiplier * state.breakingNews.length);
|
||||
}
|
||||
final item = state.breakingNews[i];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: CustomTextLabel(
|
||||
text: item.title!, textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).secondary, fontWeight: FontWeight.normal)));
|
||||
},
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
}));
|
||||
}
|
||||
|
||||
Widget getSectionList() {
|
||||
return BlocBuilder<GeneralNewsCubit, GeneralNewsState>(builder: (context, newsState) {
|
||||
return BlocBuilder<SectionCubit, SectionState>(builder: (context, sectionState) {
|
||||
if (sectionState is SectionFetchSuccess) {
|
||||
//if it has only one section and it doesn't have news in it then show No data found message
|
||||
if (sectionState.section.length == 1) {
|
||||
if (sectionState.section.first.newsType == "breaking_news") {
|
||||
if (sectionState.section.first.breakNewsTotal == 0) {
|
||||
return ErrorContainerWidget(errorMsg: ErrorMessageKeys.noDataMessage, onRetry: _refresh);
|
||||
}
|
||||
} else if (sectionState.section.first.newsTotal != null && sectionState.section.first.newsTotal! == 0) {
|
||||
return ErrorContainerWidget(errorMsg: ErrorMessageKeys.noDataMessage, onRetry: _refresh);
|
||||
}
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: ((context, index) {
|
||||
FeatureSectionModel model = sectionState.section[index];
|
||||
//check for more featured sections
|
||||
if (index == sectionState.section.length - 1 && index != 0) {
|
||||
if (sectionState.hasMore) {
|
||||
if (sectionState.hasMoreFetchError) {
|
||||
return const SizedBox.shrink();
|
||||
} else {
|
||||
return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sectionData(model: model);
|
||||
}),
|
||||
itemCount: sectionState.section.length);
|
||||
}
|
||||
if (sectionState is SectionFetchFailure) {
|
||||
if (context.read<GeneralNewsCubit>().state is GeneralNewsFetchSuccess) {
|
||||
return sectionData(newsModelList: (context.read<GeneralNewsCubit>().state as GeneralNewsFetchSuccess).generalNews);
|
||||
} else {
|
||||
return ErrorContainerWidget(
|
||||
errorMsg: (sectionState.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : sectionState.errorMessage, onRetry: _refresh);
|
||||
}
|
||||
}
|
||||
return sectionShimmer(context); //state is SectionFetchInProgress || state is SectionInitial
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Widget sectionData({FeatureSectionModel? model, List<NewsModel>? newsModelList}) {
|
||||
return (model != null)
|
||||
? Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
if (model.adSpaceDetails != null) AdSpaces(adsModel: model.adSpaceDetails!), //sponsored ads
|
||||
if (model.styleApp == 'style_1') Style1Section(model: model),
|
||||
if (model.styleApp == 'style_2') Style2Section(model: model),
|
||||
if (model.styleApp == 'style_3') Style3Section(model: model),
|
||||
if (model.styleApp == 'style_4') Style4Section(model: model),
|
||||
if (model.styleApp == 'style_5') Style5Section(model: model),
|
||||
if (model.styleApp == 'style_6') BlocProvider(create: (context) => SectionByIdCubit(SectionByIdRepository()), child: Style6Section(model: model))
|
||||
])
|
||||
: GeneralNewsRandomStyle(modelList: newsModelList!);
|
||||
}
|
||||
|
||||
//refresh function to refresh page
|
||||
Future<void> _refresh() async {
|
||||
getSections();
|
||||
getLocationPermission();
|
||||
if (authConfig.getUserId() != "0") {
|
||||
getUserData();
|
||||
getBookmark();
|
||||
getLikeNews();
|
||||
}
|
||||
getLiveStreamData();
|
||||
getPages();
|
||||
|
||||
if (appConfig.getBreakingNewsMode() == "1") getBreakingNews();
|
||||
if (appConfig.getMaintenanceMode() == "1") Navigator.of(context).pushReplacementNamed(Routes.maintenance);
|
||||
if (appConfig.getWeatherMode() == "1") getWeatherData();
|
||||
}
|
||||
|
||||
getPages() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<OtherPageCubit>().getOtherPage(langId: languageId);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: RefreshIndicator(
|
||||
key: _refreshIndicatorKey,
|
||||
onRefresh: () => _refresh(),
|
||||
child: BlocListener<GetUserByIdCubit, GetUserByIdState>(
|
||||
bloc: context.read<GetUserByIdCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is GetUserByIdFetchSuccess) {
|
||||
var data = state.result;
|
||||
if (data[STATUS] == 0) {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'deactiveMsg'), context);
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
UiUtils.userLogOut(contxt: context);
|
||||
});
|
||||
} else {
|
||||
authConfig.updateDetails(
|
||||
authModel: AuthModel(
|
||||
id: data[ID].toString(),
|
||||
name: data[NAME],
|
||||
status: data[STATUS].toString(),
|
||||
mobile: data[MOBILE],
|
||||
email: data[EMAIL],
|
||||
type: data[TYPE],
|
||||
profile: data[PROFILE],
|
||||
role: data[ROLE].toString(),
|
||||
jwtToken: data[TOKEN],
|
||||
isAuthor: data[IS_AUTHOR],
|
||||
authorDetails: (data[IS_AUTHOR] == 1 && data[AUTHOR] != null) ? Author.fromJson(data[AUTHOR]) : null));
|
||||
}
|
||||
}
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
controller: featuredSectionsScrollController,
|
||||
physics: ClampingScrollPhysics(), //To restrict scrolling on Refresh
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 15.0, end: 15.0, bottom: 10.0),
|
||||
child: Column(
|
||||
children: [
|
||||
const LiveWithSearchView(),
|
||||
BlocBuilder<WeatherCubit, WeatherState>(builder: (context, state) {
|
||||
if (state is WeatherFetchSuccess) {
|
||||
return WeatherDataView(weatherData: state.weatherData);
|
||||
}
|
||||
return SizedBox.shrink();
|
||||
}),
|
||||
breakingNewsMarquee(),
|
||||
getSectionList()
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
))),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/data/models/FeatureSectionModel.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
Widget commonSectionTitle(FeatureSectionModel model, BuildContext context) {
|
||||
return ListTile(
|
||||
minVerticalPadding: 5,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: CustomTextLabel(
|
||||
text: model.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.bold),
|
||||
softWrap: true,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis)),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
if ((model.newsType == 'news' || model.newsType == "user_choice") || model.videosType == 'news' && model.newsType != 'breaking_news') {
|
||||
Navigator.of(context).pushNamed(Routes.sectionNews, arguments: {"sectionId": model.id!, "title": model.title!});
|
||||
} else {
|
||||
Navigator.of(context).pushNamed(Routes.sectionBreakNews, arguments: {"sectionId": model.id!, "title": model.title!});
|
||||
}
|
||||
},
|
||||
child: CustomTextLabel(
|
||||
text: 'viewMore',
|
||||
textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(decoration: TextDecoration.underline, fontWeight: FontWeight.bold, color: UiUtils.getColorScheme(context).outline)),
|
||||
)
|
||||
],
|
||||
),
|
||||
subtitle: (model.shortDescription != null)
|
||||
? CustomTextLabel(
|
||||
text: model.shortDescription!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)),
|
||||
softWrap: true,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis)
|
||||
: SizedBox.shrink(),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import 'dart:ui';
|
||||
|
||||
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/generalNewsCubit.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/data/repositories/Settings/settingsLocalDataRepository.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 GeneralNewsRandomStyle extends StatefulWidget {
|
||||
final List<NewsModel> modelList;
|
||||
|
||||
const GeneralNewsRandomStyle({super.key, required this.modelList});
|
||||
|
||||
@override
|
||||
GeneralNewsRandomStyleState createState() => GeneralNewsRandomStyleState();
|
||||
}
|
||||
|
||||
class GeneralNewsRandomStyleState extends State<GeneralNewsRandomStyle> {
|
||||
late final ScrollController scrollController = ScrollController()..addListener(hasMoreGeneralNewsListener);
|
||||
late int counter; //counter will handle unique index in both list & grid
|
||||
late List<NewsModel> newsList;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
newsList = widget.modelList;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.removeListener(() {});
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void hasMoreGeneralNewsListener() {
|
||||
if (scrollController.position.maxScrollExtent == scrollController.offset) {
|
||||
if (context.read<GeneralNewsCubit>().hasMoreGeneralNews()) {
|
||||
context.read<GeneralNewsCubit>().getMoreGeneralNews(
|
||||
langId: context.read<AppLocalizationCubit>().state.id,
|
||||
latitude: SettingsLocalDataRepository().getLocationCityValues().first,
|
||||
longitude: SettingsLocalDataRepository().getLocationCityValues().last);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
counter = 0;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListView.separated(
|
||||
controller: scrollController,
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: newsList.length,
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return (index.isOdd && counter + 1 < newsList.length) ? getGrid() : const SizedBox.shrink();
|
||||
},
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return (counter < newsList.length) ? listRow(counter++) : const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget getGrid() {
|
||||
return SizedBox(
|
||||
height: 200,
|
||||
child: GridView.count(
|
||||
crossAxisCount: 1,
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: List.generate((counter % 3 == 0) ? 3 : 2, (index) {
|
||||
return Padding(padding: const EdgeInsets.only(right: 15), child: listRow((counter < newsList.length) ? counter++ : counter));
|
||||
})));
|
||||
}
|
||||
|
||||
Widget listRow(int index) {
|
||||
NewsModel newsModel = newsList[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 15),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
List<NewsModel> newList = [];
|
||||
newList.addAll(newsList);
|
||||
newList.removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": newsList[index], "newsList": newList, "isFromBreak": false, "fromShowMore": false});
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
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(
|
||||
width: double.maxFinite,
|
||||
height: MediaQuery.of(context).size.height / 3.3,
|
||||
color: primaryColor.withValues(alpha: 0.15),
|
||||
child: CustomNetworkImage(
|
||||
networkImageUrl: newsModel.image!,
|
||||
fit: BoxFit.cover,
|
||||
width: double.maxFinite,
|
||||
height: MediaQuery.of(context).size.height / 3.3,
|
||||
isVideo: newsModel.type == 'videos' ? true : false),
|
||||
))),
|
||||
if (newsModel.type == 'videos')
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
top: MediaQuery.of(context).size.height * 0.12,
|
||||
start: MediaQuery.of(context).size.width / 3,
|
||||
end: MediaQuery.of(context).size.width / 3,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
List<NewsModel> allNewsList = List.from(newsList)..removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": newsModel, "otherVideos": allNewsList});
|
||||
},
|
||||
child: UiUtils.setPlayButton(context: context))),
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
bottom: 10,
|
||||
start: 10,
|
||||
end: 10,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (newsModel.categoryName != null)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: CustomTextLabel(
|
||||
text: newsModel.categoryName!,
|
||||
textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(color: secondaryColor.withOpacity(0.6)),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: CustomTextLabel(
|
||||
text: newsModel.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: secondaryColor, fontWeight: FontWeight.normal),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
],
|
||||
))
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
145
news-app/lib/ui/screens/HomePage/Widgets/LiveWithSearchView.dart
Normal file
145
news-app/lib/ui/screens/HomePage/Widgets/LiveWithSearchView.dart
Normal file
@@ -0,0 +1,145 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/themeCubit.dart';
|
||||
import 'package:news/ui/styles/appTheme.dart';
|
||||
import 'package:news/cubits/liveStreamCubit.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
|
||||
class LiveWithSearchView extends StatefulWidget {
|
||||
const LiveWithSearchView({super.key});
|
||||
|
||||
@override
|
||||
LiveWithSearchState createState() => LiveWithSearchState();
|
||||
}
|
||||
|
||||
class LiveWithSearchState extends State<LiveWithSearchView> {
|
||||
Widget liveWithSearchView() {
|
||||
return BlocBuilder<LiveStreamCubit, LiveStreamState>(
|
||||
bloc: context.read<LiveStreamCubit>(),
|
||||
builder: (context, state) {
|
||||
if (state is LiveStreamFetchSuccess) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// Logo Caribe
|
||||
Container(
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: UiUtils.getColorScheme(context).surface),
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: Center(
|
||||
child: SvgPictureWidget(assetName: "logo_caribe", height: 40.0, width: 40.0),
|
||||
)),
|
||||
const SizedBox(width: 10),
|
||||
// Search bar
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(30.0), color: UiUtils.getColorScheme(context).surface),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 10.0),
|
||||
child: Icon(Icons.search_rounded, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 10.0),
|
||||
child: CustomTextLabel(text: 'searchHomeNews', maxLines: 3, textStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)))),
|
||||
],
|
||||
),
|
||||
)),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.search);
|
||||
},
|
||||
)),
|
||||
if (state.liveStream.isNotEmpty && context.read<AppConfigurationCubit>().getLiveStreamMode() == "1")
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 10.0),
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: UiUtils.getColorScheme(context).surface),
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: Center(
|
||||
child: SvgPictureWidget(assetName: (context.read<ThemeCubit>().state.appTheme == AppTheme.Dark ? "live_news_dark" : "live_news"), height: 30.0, width: 54.0),
|
||||
)),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.live, arguments: {"liveNews": state.liveStream});
|
||||
},
|
||||
))
|
||||
],
|
||||
));
|
||||
}
|
||||
if (state is LiveStreamFetchFailure) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
// Logo Caribe
|
||||
Container(
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: UiUtils.getColorScheme(context).surface),
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: Center(
|
||||
child: SvgPictureWidget(assetName: "logo_caribe", height: 40.0, width: 40.0),
|
||||
)),
|
||||
const SizedBox(width: 10),
|
||||
// Search bar
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(30.0), color: UiUtils.getColorScheme(context).surface),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(padding: const EdgeInsetsDirectional.only(start: 10.0), child: Icon(Icons.search_rounded, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 10.0),
|
||||
child: CustomTextLabel(text: 'searchHomeNews', maxLines: 3, textStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.search);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return shimmerData(); //state is LiveStreamFetchInProgress || state is LiveStreamInitial
|
||||
});
|
||||
}
|
||||
|
||||
Widget shimmerData() {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: Colors.grey.withOpacity(0.6),
|
||||
highlightColor: Colors.grey,
|
||||
child: Container(
|
||||
height: 60,
|
||||
margin: const EdgeInsets.only(top: 15),
|
||||
width: double.maxFinite,
|
||||
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(30), color: Colors.grey.withOpacity(0.6))));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return liveWithSearchView();
|
||||
}
|
||||
}
|
||||
29
news-app/lib/ui/screens/HomePage/Widgets/SectionShimmer.dart
Normal file
29
news-app/lib/ui/screens/HomePage/Widgets/SectionShimmer.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
Widget sectionShimmer(BuildContext context) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: Colors.grey.withOpacity(0.6),
|
||||
highlightColor: Colors.grey,
|
||||
child: ListView(shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsetsDirectional.only(top: 10), children: [
|
||||
Container(
|
||||
height: 55,
|
||||
width: double.maxFinite,
|
||||
decoration: BoxDecoration(color: Colors.grey.withOpacity(0.6), borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: 5,
|
||||
itemBuilder: (context, index) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
height: MediaQuery.of(context).size.height / 3.3,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
margin: const EdgeInsets.only(top: 15),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: Colors.grey.withOpacity(0.6)),
|
||||
);
|
||||
}),
|
||||
]));
|
||||
}
|
||||
321
news-app/lib/ui/screens/HomePage/Widgets/SectionStyle1.dart
Normal file
321
news-app/lib/ui/screens/HomePage/Widgets/SectionStyle1.dart
Normal file
@@ -0,0 +1,321 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:news/ui/screens/HomePage/Widgets/CommonSectionTitle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/data/models/FeatureSectionModel.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/constant.dart';
|
||||
import 'package:news/utils/strings.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
|
||||
class Style1Section extends StatefulWidget {
|
||||
final FeatureSectionModel model;
|
||||
|
||||
const Style1Section({super.key, required this.model});
|
||||
|
||||
@override
|
||||
Style1SectionState createState() => Style1SectionState();
|
||||
}
|
||||
|
||||
class Style1SectionState extends State<Style1Section> {
|
||||
int? style1Sel;
|
||||
PageController? _pageStyle1Controller = PageController();
|
||||
int limit = limitOfStyle1;
|
||||
int newsLength = 0;
|
||||
int brNewsLength = 0;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageStyle1Controller!.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
newsLength = (widget.model.newsType == 'news' || widget.model.newsType == "user_choice") ? widget.model.news!.length : widget.model.videos!.length;
|
||||
brNewsLength = widget.model.newsType == 'breaking_news' ? widget.model.breakNews!.length : widget.model.breakVideos!.length;
|
||||
|
||||
return style1Data(widget.model);
|
||||
}
|
||||
|
||||
Widget style1Data(FeatureSectionModel model) {
|
||||
if (model.breakVideos!.isNotEmpty || model.breakNews!.isNotEmpty || model.videos!.isNotEmpty || model.news!.isNotEmpty) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
commonSectionTitle(model, context),
|
||||
if ((model.newsType == 'news' || model.videosType == "news" || model.newsType == "user_choice") &&
|
||||
((model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.isNotEmpty : model.videos!.isNotEmpty))
|
||||
style1NewsData(model),
|
||||
if ((model.newsType == 'breaking_news' || model.videosType == "breaking_news") && (model.newsType == 'breaking_news' ? model.breakNews!.isNotEmpty : model.breakVideos!.isNotEmpty))
|
||||
style1BreakNewsData(model)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
Widget style1NewsData(FeatureSectionModel model) {
|
||||
if ((model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.length > 1 : model.videos!.length > 1) {
|
||||
style1Sel ??= 1;
|
||||
_pageStyle1Controller = PageController(initialPage: 1, viewportFraction: 0.87);
|
||||
} else {
|
||||
style1Sel = 0;
|
||||
_pageStyle1Controller = PageController(initialPage: 0, viewportFraction: 1);
|
||||
}
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.36,
|
||||
width: double.maxFinite,
|
||||
child: PageView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: min(newsLength, limit),
|
||||
scrollDirection: Axis.horizontal,
|
||||
pageSnapping: true,
|
||||
controller: _pageStyle1Controller,
|
||||
onPageChanged: (index) {
|
||||
setState(() => style1Sel = index);
|
||||
},
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
NewsModel data = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news![index] : model.videos![index];
|
||||
return InkWell(
|
||||
child: Padding(
|
||||
padding: EdgeInsetsDirectional.only(start: 7, end: 7, top: style1Sel == index ? 0 : MediaQuery.of(context).size.height * 0.027),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: CustomNetworkImage(
|
||||
networkImageUrl: data.image!,
|
||||
height: style1Sel == index ? MediaQuery.of(context).size.height / 4 : MediaQuery.of(context).size.height / 5,
|
||||
width: double.maxFinite,
|
||||
fit: BoxFit.cover,
|
||||
isVideo: model.newsType == 'videos' ? true : false),
|
||||
),
|
||||
if (model.newsType == 'videos')
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
top: MediaQuery.of(context).size.height * 0.075,
|
||||
start: MediaQuery.of(context).size.width / 3,
|
||||
end: MediaQuery.of(context).size.width / 3,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
List<NewsModel> newsList = List.from(model.videos ?? [])..removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": data, "otherVideos": newsList});
|
||||
},
|
||||
child: UiUtils.setPlayButton(context: context)),
|
||||
),
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
start: 8,
|
||||
end: 8,
|
||||
top: MediaQuery.of(context).size.height / 7,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: MediaQuery.of(context).size.height / 5,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
margin: const EdgeInsetsDirectional.all(10),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: UiUtils.getColorScheme(context).surface),
|
||||
padding: const EdgeInsets.all(13),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (data.categoryName != null)
|
||||
Container(
|
||||
height: 20.0,
|
||||
padding: const EdgeInsetsDirectional.only(start: 8.0, end: 8.0, top: 2.5),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color: Theme.of(context).primaryColor),
|
||||
child: CustomTextLabel(
|
||||
text: data.categoryName!,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: secondaryColor),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 15.0),
|
||||
child: CustomTextLabel(
|
||||
text: data.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.normal),
|
||||
softWrap: true,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis)),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (model.newsType == 'news' || model.newsType == "user_choice") {
|
||||
//interstitial ads
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
List<NewsModel> newsList = [];
|
||||
newsList.addAll(model.news!);
|
||||
newsList.removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": data, "newsList": newsList, "isFromBreak": false, "fromShowMore": false});
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
style1Indicator(model, min(newsLength, limit))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget style1BreakNewsData(FeatureSectionModel model) {
|
||||
if (model.newsType == 'breaking_news' ? model.breakNews!.length > 1 : model.breakVideos!.length > 1) {
|
||||
style1Sel ??= 1;
|
||||
_pageStyle1Controller = PageController(initialPage: 1, viewportFraction: 0.87);
|
||||
} else {
|
||||
style1Sel = 0;
|
||||
_pageStyle1Controller = PageController(initialPage: 0, viewportFraction: 1);
|
||||
}
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.36,
|
||||
width: double.maxFinite,
|
||||
child: PageView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: min(brNewsLength, limit),
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: _pageStyle1Controller,
|
||||
reverse: false,
|
||||
onPageChanged: (index) {
|
||||
setState(() => style1Sel = index);
|
||||
},
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
BreakingNewsModel data = model.newsType == 'breaking_news' ? model.breakNews![index] : model.breakVideos![index];
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsetsDirectional.only(start: 7, end: 7, top: style1Sel == index ? 0 : MediaQuery.of(context).size.height * 0.027),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (model.newsType == 'breaking_news') {
|
||||
//interstitial ads
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
List<BreakingNewsModel> breakList = [];
|
||||
breakList.addAll(model.breakNews!);
|
||||
breakList.removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": data, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false});
|
||||
}
|
||||
},
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: CustomNetworkImage(
|
||||
networkImageUrl: data.image!,
|
||||
height: style1Sel == index ? MediaQuery.of(context).size.height / 4 : MediaQuery.of(context).size.height / 5,
|
||||
width: double.maxFinite,
|
||||
fit: BoxFit.cover,
|
||||
isVideo: model.newsType == 'videos' ? true : false),
|
||||
),
|
||||
if (model.newsType == 'videos')
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
top: MediaQuery.of(context).size.height * 0.075,
|
||||
start: MediaQuery.of(context).size.width / 3,
|
||||
end: MediaQuery.of(context).size.width / 3,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
List<BreakingNewsModel> allBrNewsList = List.from(model.breakVideos ?? [])..removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 3, "breakModel": data, "otherBreakingVideos": allBrNewsList});
|
||||
},
|
||||
child: UiUtils.setPlayButton(context: context)),
|
||||
),
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
start: 8,
|
||||
end: 8,
|
||||
top: MediaQuery.of(context).size.height / 7,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: MediaQuery.of(context).size.height / 5,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
margin: const EdgeInsetsDirectional.all(10),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: UiUtils.getColorScheme(context).surface),
|
||||
padding: const EdgeInsets.all(13),
|
||||
child: CustomTextLabel(
|
||||
text: data.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.normal),
|
||||
softWrap: true,
|
||||
maxLines: 4,
|
||||
overflow: TextOverflow.ellipsis))),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
style1Indicator(model, min(brNewsLength, limit))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget style1Indicator(FeatureSectionModel model, int len) {
|
||||
return len <= 1
|
||||
? const SizedBox.shrink()
|
||||
: Align(
|
||||
alignment: Alignment.center,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: map<Widget>(
|
||||
(model.newsType == 'news' || model.newsType == "user_choice")
|
||||
? model.news!
|
||||
: (model.newsType == BREAKING_NEWS)
|
||||
? model.breakNews!
|
||||
: (model.newsType == 'videos' && (model.videosTotal! > 0 && model.videos!.isNotEmpty))
|
||||
? model.videos!
|
||||
: model.breakVideos!,
|
||||
(index, url) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 5.0, end: 5.0),
|
||||
child: Container(
|
||||
height: 14.0,
|
||||
width: 14.0,
|
||||
decoration: BoxDecoration(color: Colors.transparent, shape: BoxShape.circle, border: Border.all(color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
child: style1Sel == index
|
||||
? Container(
|
||||
margin: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(color: Theme.of(context).primaryColor, shape: BoxShape.circle),
|
||||
)
|
||||
: const SizedBox.shrink()),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
List<T> map<T>(List list, Function handler) {
|
||||
List<T> result = [];
|
||||
int mapLength = (widget.model.newsType == 'news' || widget.model.newsType == "user_choice") ? min(newsLength, limit) : min(brNewsLength, limit);
|
||||
for (var i = 0; i < mapLength; i++) {
|
||||
result.add(handler(i, list[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
170
news-app/lib/ui/screens/HomePage/Widgets/SectionStyle2.dart
Normal file
170
news-app/lib/ui/screens/HomePage/Widgets/SectionStyle2.dart
Normal file
@@ -0,0 +1,170 @@
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/ui/screens/HomePage/Widgets/CommonSectionTitle.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/data/models/FeatureSectionModel.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/constant.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class Style2Section extends StatelessWidget {
|
||||
final FeatureSectionModel model;
|
||||
bool isNews = true;
|
||||
|
||||
Style2Section({super.key, required this.model});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return style2Data(model, context);
|
||||
}
|
||||
|
||||
Widget style2Data(FeatureSectionModel model, BuildContext context) {
|
||||
if (model.breakVideos!.isNotEmpty || model.breakNews!.isNotEmpty || model.videos!.isNotEmpty || model.news!.isNotEmpty) {
|
||||
if (model.newsType == 'news' || model.videosType == "news" || model.newsType == "user_choice") {
|
||||
if ((model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.isNotEmpty : model.videos!.isNotEmpty) {
|
||||
isNews = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (model.newsType == 'breaking_news' || model.videosType == "breaking_news") {
|
||||
if (model.newsType == 'breaking_news' ? model.breakNews!.isNotEmpty : model.breakVideos!.isNotEmpty) {
|
||||
isNews = false;
|
||||
}
|
||||
}
|
||||
int limit = limitOfAllOtherStyle;
|
||||
int newsLength = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.length : model.videos!.length;
|
||||
int brNewsLength = model.newsType == 'breaking_news' ? model.breakNews!.length : model.breakVideos!.length;
|
||||
|
||||
var totalCount = (isNews) ? min(newsLength, limit) : min(brNewsLength, limit);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
commonSectionTitle(model, context),
|
||||
ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: totalCount,
|
||||
itemBuilder: (context, index) => (isNews)
|
||||
? setStyle2(context: context, index: index, model: model, newsModel: (model.newsType == 'news' || model.newsType == "user_choice") ? model.news![index] : model.videos![index])
|
||||
: setStyle2(context: context, index: index, model: model, breakingNewsModel: (model.newsType == 'breaking_news') ? model.breakNews![index] : model.breakVideos![index])),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
Widget setStyle2({required BuildContext context, required int index, required FeatureSectionModel model, NewsModel? newsModel, BreakingNewsModel? breakingNewsModel}) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: index == 0 ? 0 : 15),
|
||||
child: Column(
|
||||
children: [
|
||||
if (context.read<AppConfigurationCubit>().getInAppAdsMode() == "1" &&
|
||||
(context.read<AppConfigurationCubit>().getAdsType() != "unity" || context.read<AppConfigurationCubit>().getIOSAdsType() != "unity"))
|
||||
nativeAdsShow(context: context, index: index),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
//interstitial ads
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
if (model.newsType == 'news' || model.newsType == "user_choice") {
|
||||
List<NewsModel> newsList = [];
|
||||
newsList.addAll(model.news!);
|
||||
newsList.removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": newsModel, "newsList": newsList, "isFromBreak": false, "fromShowMore": false});
|
||||
} else if (model.newsType == 'breaking_news') {
|
||||
List<BreakingNewsModel> breakList = [];
|
||||
breakList.addAll(model.breakNews!);
|
||||
breakList.removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": breakingNewsModel, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false});
|
||||
}
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
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: (newsModel != null) ? newsModel.image! : breakingNewsModel!.image!,
|
||||
fit: BoxFit.cover,
|
||||
width: double.maxFinite,
|
||||
height: MediaQuery.of(context).size.height / 3.3,
|
||||
isVideo: model.newsType == 'videos' ? true : false),
|
||||
),
|
||||
)),
|
||||
if (model.newsType == 'videos')
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
top: MediaQuery.of(context).size.height * 0.12,
|
||||
start: MediaQuery.of(context).size.width / 3,
|
||||
end: MediaQuery.of(context).size.width / 3,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
List<NewsModel> newsList = [];
|
||||
List<BreakingNewsModel> brNewsList = [];
|
||||
if (model.breakVideos != null && model.breakVideos!.isNotEmpty) brNewsList = List.from(model.breakVideos ?? [])..removeAt(index);
|
||||
if (model.videos != null && model.videos!.isNotEmpty) newsList = List.from(model.videos ?? [])..removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {
|
||||
"from": 1,
|
||||
"model": (newsModel != null) ? newsModel : breakingNewsModel!,
|
||||
if (newsList.isNotEmpty) "otherVideos": newsList,
|
||||
if (brNewsList.isNotEmpty) "otherBreakingVideos": brNewsList
|
||||
});
|
||||
},
|
||||
child: UiUtils.setPlayButton(context: context))),
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
bottom: 10,
|
||||
start: 10,
|
||||
end: 10,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (newsModel != null && newsModel.categoryName != null)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: CustomTextLabel(
|
||||
text: newsModel.categoryName!,
|
||||
textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(color: secondaryColor.withOpacity(0.6)),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: CustomTextLabel(
|
||||
text: (newsModel != null) ? newsModel.title! : breakingNewsModel!.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: secondaryColor, fontWeight: FontWeight.normal),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
],
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
241
news-app/lib/ui/screens/HomePage/Widgets/SectionStyle3.dart
Normal file
241
news-app/lib/ui/screens/HomePage/Widgets/SectionStyle3.dart
Normal file
@@ -0,0 +1,241 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/data/models/FeatureSectionModel.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/constant.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/ui/screens/HomePage/Widgets/CommonSectionTitle.dart';
|
||||
|
||||
class Style3Section extends StatelessWidget {
|
||||
final FeatureSectionModel model;
|
||||
|
||||
const Style3Section({super.key, required this.model});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return style3Data(model, context);
|
||||
}
|
||||
|
||||
Widget style3Data(FeatureSectionModel model, BuildContext context) {
|
||||
int limit = limitOfAllOtherStyle;
|
||||
int newsLength = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.length : model.videos!.length;
|
||||
int brNewsLength = model.newsType == 'breaking_news' ? model.breakNews!.length : model.breakVideos!.length;
|
||||
|
||||
if (model.breakVideos!.isNotEmpty || model.breakNews!.isNotEmpty || model.videos!.isNotEmpty || model.news!.isNotEmpty) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
commonSectionTitle(model, context),
|
||||
if ((model.newsType == 'breaking_news' || model.videosType == "breaking_news") && (model.newsType == 'breaking_news' ? model.breakNews!.isNotEmpty : model.breakVideos!.isNotEmpty))
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.34,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemCount: min(brNewsLength, limit),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
BreakingNewsModel data = model.newsType == 'breaking_news' ? model.breakNews![index] : model.breakVideos![index];
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if ((index != 0 && index % nativeAdsIndex == 0) &&
|
||||
context.read<AppConfigurationCubit>().getInAppAdsMode() == "1" &&
|
||||
(context.read<AppConfigurationCubit>().getAdsType() != "unity" || context.read<AppConfigurationCubit>().getIOSAdsType() != "unity"))
|
||||
SizedBox(width: MediaQuery.of(context).size.width * 0.87, child: nativeAdsShow(context: context, index: index)),
|
||||
InkWell(
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.87,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
start: 0,
|
||||
end: 0,
|
||||
top: MediaQuery.of(context).size.height / 15,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: MediaQuery.of(context).size.height / 4,
|
||||
margin: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 10, end: 10, top: 10, bottom: 10),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: UiUtils.getColorScheme(context).surface),
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).size.height / 9),
|
||||
child: CustomTextLabel(
|
||||
text: data.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.normal),
|
||||
softWrap: true,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis)),
|
||||
)),
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
start: 30,
|
||||
end: 30,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: CustomNetworkImage(
|
||||
networkImageUrl: data.image!,
|
||||
height: MediaQuery.of(context).size.height / 4.7,
|
||||
width: double.maxFinite,
|
||||
fit: BoxFit.cover,
|
||||
isVideo: model.newsType == 'videos' ? true : false),
|
||||
),
|
||||
),
|
||||
if (model.newsType == 'videos')
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
top: MediaQuery.of(context).size.height * 0.085,
|
||||
start: MediaQuery.of(context).size.width / 3,
|
||||
end: MediaQuery.of(context).size.width / 3,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
List<BreakingNewsModel> brNewsList = List.from(model.breakVideos ?? [])..removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 3, "breakModel": data, "otherBreakingVideos": brNewsList});
|
||||
},
|
||||
child: UiUtils.setPlayButton(context: context)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (model.newsType == 'breaking_news') {
|
||||
//interstitial ads
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
List<BreakingNewsModel> breakList = [];
|
||||
breakList.addAll(model.breakNews!);
|
||||
breakList.removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": data, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
})),
|
||||
if ((model.newsType == 'news' || model.videosType == "news" || model.newsType == "user_choice") &&
|
||||
((model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.isNotEmpty : model.videos!.isNotEmpty))
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.34,
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemCount: min(newsLength, limit),
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
NewsModel data = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news![index] : model.videos![index];
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if ((index != 0 && index % nativeAdsIndex == 0) &&
|
||||
context.read<AppConfigurationCubit>().getInAppAdsMode() == "1" &&
|
||||
(context.read<AppConfigurationCubit>().getAdsType() != "unity" || context.read<AppConfigurationCubit>().getIOSAdsType() != "unity"))
|
||||
SizedBox(width: MediaQuery.of(context).size.width * 0.87, child: nativeAdsShow(context: context, index: index)),
|
||||
InkWell(
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.87,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
start: 0,
|
||||
end: 0,
|
||||
top: MediaQuery.of(context).size.height / 15,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: MediaQuery.of(context).size.height / 3.8,
|
||||
margin: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 10, end: 10, top: 10, bottom: 10),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: UiUtils.getColorScheme(context).surface),
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).size.height / 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (data.categoryName != null)
|
||||
Container(
|
||||
height: 20.0,
|
||||
padding: const EdgeInsetsDirectional.only(start: 8.0, end: 8.0, top: 2.5),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color: Theme.of(context).primaryColor),
|
||||
child: CustomTextLabel(
|
||||
text: data.categoryName!,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: secondaryColor),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0),
|
||||
child: CustomTextLabel(
|
||||
text: data.title!,
|
||||
textStyle:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.normal),
|
||||
softWrap: true,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis)),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
start: 30,
|
||||
end: 30,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: CustomNetworkImage(
|
||||
networkImageUrl: data.image!,
|
||||
height: MediaQuery.of(context).size.height / 4.7,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
fit: BoxFit.cover,
|
||||
isVideo: model.newsType == 'videos' ? true : false),
|
||||
),
|
||||
),
|
||||
if (model.newsType == 'videos')
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
top: MediaQuery.of(context).size.height * 0.085,
|
||||
start: MediaQuery.of(context).size.width / 3,
|
||||
end: MediaQuery.of(context).size.width / 3,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
List<NewsModel> allNewsList = List.from(model.videos ?? [])..removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": data, "otherVideos": allNewsList});
|
||||
},
|
||||
child: UiUtils.setPlayButton(context: context)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (model.newsType == 'news' || model.newsType == "user_choice") {
|
||||
//interstitial ads
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
List<NewsModel> newsList = [];
|
||||
newsList.addAll(model.news!);
|
||||
newsList.removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": data, "newsList": newsList, "isFromBreak": false, "fromShowMore": false});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
})),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
}
|
||||
189
news-app/lib/ui/screens/HomePage/Widgets/SectionStyle4.dart
Normal file
189
news-app/lib/ui/screens/HomePage/Widgets/SectionStyle4.dart
Normal file
@@ -0,0 +1,189 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/screens/HomePage/Widgets/CommonSectionTitle.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/data/models/FeatureSectionModel.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/constant.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
|
||||
class Style4Section extends StatelessWidget {
|
||||
final FeatureSectionModel model;
|
||||
|
||||
const Style4Section({super.key, required this.model});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return style4Data(model, context);
|
||||
}
|
||||
|
||||
Widget style4Data(FeatureSectionModel model, BuildContext context) {
|
||||
int limit = limitOfAllOtherStyle;
|
||||
int newsLength = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.length : model.videos!.length;
|
||||
int brNewsLength = model.newsType == 'breaking_news' ? model.breakNews!.length : model.breakVideos!.length;
|
||||
|
||||
if (model.breakVideos!.isNotEmpty || model.breakNews!.isNotEmpty || model.videos!.isNotEmpty || model.news!.isNotEmpty) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
commonSectionTitle(model, context),
|
||||
if ((model.newsType == 'news' || model.videosType == "news" || model.newsType == "user_choice") &&
|
||||
((model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.isNotEmpty : model.videos!.isNotEmpty))
|
||||
Column(
|
||||
children: [
|
||||
setGridLayout(
|
||||
context: context,
|
||||
totalCount: min(newsLength, limit),
|
||||
childWidget: (context, index) {
|
||||
NewsModel data = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news![index] : model.videos![index];
|
||||
return InkWell(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(7),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: UiUtils.getColorScheme(context).surface),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(children: [
|
||||
setNewsImage(context: context, imageURL: data.image!),
|
||||
if (data.categoryName != null && data.categoryName != "")
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Container(
|
||||
margin: const EdgeInsetsDirectional.only(start: 7.0, top: 7.0),
|
||||
height: 18.0,
|
||||
padding: const EdgeInsetsDirectional.only(start: 6.0, end: 6.0, top: 2.5),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color: Theme.of(context).primaryColor),
|
||||
child: CustomTextLabel(
|
||||
text: data.categoryName!,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: secondaryColor),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
),
|
||||
if (model.newsType == 'videos')
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
top: MediaQuery.of(context).size.height * 0.065,
|
||||
start: MediaQuery.of(context).size.width / 6,
|
||||
end: MediaQuery.of(context).size.width / 6,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
List<NewsModel> allNewsList = List.from(model.videos ?? [])..removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": data, "otherVideos": allNewsList});
|
||||
},
|
||||
child: UiUtils.setPlayButton(context: context)),
|
||||
),
|
||||
]),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: CustomTextLabel(
|
||||
text: data.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
softWrap: true,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis)),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (model.newsType == 'news' || model.newsType == "user_choice") {
|
||||
//show interstitial ads
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
List<NewsModel> newsList = [];
|
||||
newsList.addAll(model.news!);
|
||||
newsList.removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": data, "newsList": newsList, "isFromBreak": false, "fromShowMore": false});
|
||||
}
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
if ((model.newsType == 'breaking_news' || model.videosType == "breaking_news") && (model.newsType == 'breaking_news' ? model.breakNews!.isNotEmpty : model.breakVideos!.isNotEmpty))
|
||||
setGridLayout(
|
||||
context: context,
|
||||
totalCount: min(brNewsLength, limit),
|
||||
childWidget: (context, index) {
|
||||
BreakingNewsModel data = model.newsType == 'breaking_news' ? model.breakNews![index] : model.breakVideos![index];
|
||||
return InkWell(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(7),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: UiUtils.getColorScheme(context).surface),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(children: [
|
||||
setNewsImage(context: context, imageURL: data.image!),
|
||||
if (model.newsType == 'videos')
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
top: MediaQuery.of(context).size.height * 0.065,
|
||||
start: MediaQuery.of(context).size.width / 6,
|
||||
end: MediaQuery.of(context).size.width / 6,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
List<BreakingNewsModel> brNewsList = List.from(model.breakVideos ?? [])..removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 3, "breakModel": data, "otherBreakingVideos": brNewsList});
|
||||
},
|
||||
child: UiUtils.setPlayButton(context: context)),
|
||||
),
|
||||
]),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: CustomTextLabel(
|
||||
text: data.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
softWrap: true,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis)),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (model.newsType == 'breaking_news') {
|
||||
//interstitial ads
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
List<BreakingNewsModel> breakList = [];
|
||||
breakList.addAll(model.breakNews!);
|
||||
breakList.removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": data, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false});
|
||||
}
|
||||
},
|
||||
);
|
||||
})
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
Widget setGridLayout({required BuildContext context, required int totalCount, required Widget? Function(BuildContext, int) childWidget}) {
|
||||
return GridView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(mainAxisExtent: MediaQuery.of(context).size.height * 0.27, crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 13),
|
||||
shrinkWrap: true,
|
||||
itemCount: totalCount,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: childWidget);
|
||||
}
|
||||
|
||||
Widget setNewsImage({required BuildContext context, required String imageURL}) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: CustomNetworkImage(
|
||||
networkImageUrl: imageURL,
|
||||
height: MediaQuery.of(context).size.height * 0.175,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
fit: BoxFit.cover,
|
||||
isVideo: model.newsType == 'videos' ? true : false));
|
||||
}
|
||||
}
|
||||
400
news-app/lib/ui/screens/HomePage/Widgets/SectionStyle5.dart
Normal file
400
news-app/lib/ui/screens/HomePage/Widgets/SectionStyle5.dart
Normal file
@@ -0,0 +1,400 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:news/ui/screens/HomePage/Widgets/CommonSectionTitle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/data/models/FeatureSectionModel.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/constant.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
|
||||
class Style5Section extends StatelessWidget {
|
||||
final FeatureSectionModel model;
|
||||
|
||||
const Style5Section({super.key, required this.model});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return style5Data(model, context);
|
||||
}
|
||||
|
||||
Widget style5SingleNewsData(FeatureSectionModel model, BuildContext context) {
|
||||
NewsModel data = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news![0] : model.videos![0];
|
||||
return InkWell(
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height * 0.28,
|
||||
width: double.maxFinite,
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15)),
|
||||
child: Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: CustomNetworkImage(
|
||||
networkImageUrl: data.image!, height: MediaQuery.of(context).size.height * 0.28, width: double.maxFinite, fit: BoxFit.cover, isVideo: model.newsType == 'videos' ? true : false),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(MediaQuery.of(context).size.height * 0.01),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (data.categoryName != null)
|
||||
Container(
|
||||
height: 20.0,
|
||||
padding: const EdgeInsetsDirectional.only(start: 8.0, end: 8.0, top: 2.5),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color: Theme.of(context).primaryColor),
|
||||
child: CustomTextLabel(
|
||||
text: data.categoryName!,
|
||||
textAlign: TextAlign.left,
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: secondaryColor),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.01),
|
||||
child: CustomTextLabel(
|
||||
text: data.title!,
|
||||
textAlign: TextAlign.left,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: secondaryColor, fontWeight: FontWeight.w700),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.01),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.calendar_month, color: secondaryColor, size: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 10),
|
||||
child: CustomTextLabel(
|
||||
text: UiUtils.convertToAgo(context, DateTime.parse(data.publishDate ?? data.date!), 0)!,
|
||||
textAlign: TextAlign.left,
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: secondaryColor, fontWeight: FontWeight.w600),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (model.newsType == 'videos')
|
||||
InkWell(
|
||||
child: Padding(padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.02), child: UiUtils.setPlayButton(context: context)),
|
||||
onTap: () {
|
||||
List<NewsModel> allNewsList = List.from(model.videos ?? [])..removeWhere((item) => item.id == data.id);
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": data, "otherVideos": allNewsList});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (model.newsType == 'news' || model.newsType == "user_choice") {
|
||||
//interstitial ads
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
List<NewsModel> newsList = [];
|
||||
newsList.addAll(model.news!);
|
||||
newsList.removeAt(0);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": data, "newsList": newsList, "isFromBreak": false, "fromShowMore": false});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget style5SingleBreakNewsData(FeatureSectionModel model, BuildContext context) {
|
||||
BreakingNewsModel data = model.newsType == 'breaking_news' ? model.breakNews![0] : model.breakVideos![0];
|
||||
return InkWell(
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height * 0.28,
|
||||
width: double.maxFinite,
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15)),
|
||||
child: Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: ShaderMask(
|
||||
shaderCallback: (rect) => LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.transparent, darkSecondaryColor.withOpacity(0.9)]).createShader(rect),
|
||||
blendMode: BlendMode.darken,
|
||||
child: CustomNetworkImage(
|
||||
networkImageUrl: data.image!, height: MediaQuery.of(context).size.height * 0.28, width: double.maxFinite, fit: BoxFit.cover, isVideo: model.newsType == 'videos' ? true : false),
|
||||
),
|
||||
),
|
||||
(model.newsType == 'videos')
|
||||
? Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(MediaQuery.of(context).size.height * 0.01),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.02),
|
||||
child: CustomTextLabel(
|
||||
text: data.title!,
|
||||
textAlign: TextAlign.left,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: secondaryColor, fontWeight: FontWeight.w700),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
InkWell(
|
||||
child: Padding(padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.02), child: UiUtils.setPlayButton(context: context)),
|
||||
onTap: () {
|
||||
List<BreakingNewsModel> brNewsList = List.from(model.breakVideos ?? [])..removeWhere((item) => item.id == data.id);
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 3, "breakModel": data, "otherBreakingVideos": brNewsList});
|
||||
},
|
||||
),
|
||||
],
|
||||
)),
|
||||
)
|
||||
: Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(MediaQuery.of(context).size.height * 0.01),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15)),
|
||||
child: CustomTextLabel(
|
||||
text: data.title!,
|
||||
textAlign: TextAlign.left,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: secondaryColor, fontWeight: FontWeight.w700),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (model.newsType == 'breaking_news') {
|
||||
//show interstitial ads
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
List<BreakingNewsModel> breakList = [];
|
||||
breakList.addAll(model.breakNews!);
|
||||
breakList.removeAt(0);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": data, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget style5Data(FeatureSectionModel model, BuildContext context) {
|
||||
int limit = limitOfAllOtherStyle;
|
||||
int newsLength = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.length : model.videos!.length;
|
||||
int brNewsLength = model.newsType == 'breaking_news' ? model.breakNews!.length : model.breakVideos!.length;
|
||||
|
||||
if (model.breakVideos!.isNotEmpty || model.breakNews!.isNotEmpty || model.videos!.isNotEmpty || model.news!.isNotEmpty) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
commonSectionTitle(model, context),
|
||||
if ((model.newsType == 'news' || model.videosType == "news" || model.newsType == "user_choice") && (model.newsType == 'news' || model.newsType == "user_choice")
|
||||
? model.news!.isNotEmpty
|
||||
: model.videos!.isNotEmpty)
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
style5SingleNewsData(model, context),
|
||||
if (newsLength > 1)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 15.0),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: List.generate(min(newsLength, limit), (index) {
|
||||
if (newsLength > index + 1) {
|
||||
NewsModel data = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news![index + 1] : model.videos![index + 1];
|
||||
return InkWell(
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 2.35,
|
||||
child: Padding(
|
||||
padding: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 10.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: CustomNetworkImage(
|
||||
networkImageUrl: data.image!,
|
||||
height: MediaQuery.of(context).size.height * 0.15,
|
||||
width: MediaQuery.of(context).size.width / 2.35,
|
||||
fit: BoxFit.cover,
|
||||
isVideo: model.newsType == 'videos' ? true : false),
|
||||
),
|
||||
if (data.categoryName != null)
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Container(
|
||||
margin: const EdgeInsetsDirectional.only(start: 7.0, top: 7.0),
|
||||
height: 18.0,
|
||||
padding: const EdgeInsetsDirectional.only(start: 6.0, end: 6.0, top: 2.5),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color: Theme.of(context).primaryColor),
|
||||
child: CustomTextLabel(
|
||||
text: data.categoryName!,
|
||||
textAlign: TextAlign.left,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: secondaryColor),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
),
|
||||
if (model.newsType == 'videos')
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
top: MediaQuery.of(context).size.height * 0.058,
|
||||
start: MediaQuery.of(context).size.width / 8.5,
|
||||
end: MediaQuery.of(context).size.width / 8.5,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
List<NewsModel> allNewsList = List.from(model.videos ?? [])..removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": data, "otherVideos": allNewsList});
|
||||
},
|
||||
child: UiUtils.setPlayButton(context: context, heightVal: 28)),
|
||||
),
|
||||
]),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: CustomTextLabel(
|
||||
text: data.title!,
|
||||
textStyle: Theme.of(context).textTheme.labelMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
softWrap: true,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.calendar_month, color: UiUtils.getColorScheme(context).primaryContainer, size: 15),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 5),
|
||||
child: CustomTextLabel(
|
||||
text: UiUtils.convertToAgo(context, DateTime.parse(data.publishDate ?? data.date!), 0)!,
|
||||
textAlign: TextAlign.left,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (model.newsType == 'news' || model.newsType == "user_choice") {
|
||||
//interstitial ads
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
List<NewsModel> newsList = [];
|
||||
newsList.addAll(model.news!);
|
||||
newsList.removeAt(index + 1);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": data, "newsList": newsList, "isFromBreak": false, "fromShowMore": false});
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
})),
|
||||
)),
|
||||
],
|
||||
),
|
||||
if ((model.newsType == 'breaking_news' && model.breakNews?.isNotEmpty == true) || (model.videosType == 'breaking_news' && model.breakVideos?.isNotEmpty == true))
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
style5SingleBreakNewsData(model, context),
|
||||
if (brNewsLength > 1)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 15.0),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: List.generate(min(brNewsLength, limit), (index) {
|
||||
if (brNewsLength > index + 1) {
|
||||
BreakingNewsModel data = model.newsType == 'breaking_news' ? model.breakNews![index + 1] : model.breakVideos![index + 1];
|
||||
return InkWell(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width / 2.35,
|
||||
padding: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 10.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: CustomNetworkImage(
|
||||
networkImageUrl: data.image!,
|
||||
height: MediaQuery.of(context).size.height * 0.15,
|
||||
width: MediaQuery.of(context).size.width / 2.35,
|
||||
fit: BoxFit.cover,
|
||||
isVideo: model.newsType == 'videos' ? true : false),
|
||||
),
|
||||
if (model.newsType == 'videos')
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
top: MediaQuery.of(context).size.height * 0.058,
|
||||
start: MediaQuery.of(context).size.width / 8.5,
|
||||
end: MediaQuery.of(context).size.width / 8.5,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
List<BreakingNewsModel> brNewsList = List.from(model.breakVideos ?? [])..removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 3, "breakModel": data, "otherBreakingVideos": brNewsList});
|
||||
},
|
||||
child: UiUtils.setPlayButton(context: context, heightVal: 28)),
|
||||
),
|
||||
]),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: CustomTextLabel(
|
||||
text: data.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
softWrap: true,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis)),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (model.newsType == 'breaking_news') {
|
||||
//interstitial ads
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
List<BreakingNewsModel> breakList = [];
|
||||
breakList.addAll(model.breakNews!);
|
||||
breakList.removeAt(index + 1);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": data, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false});
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
})),
|
||||
)),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
}
|
||||
216
news-app/lib/ui/screens/HomePage/Widgets/SectionStyle6.dart
Normal file
216
news-app/lib/ui/screens/HomePage/Widgets/SectionStyle6.dart
Normal file
@@ -0,0 +1,216 @@
|
||||
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/sectionByIdCubit.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/data/models/FeatureSectionModel.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
|
||||
class Style6Section extends StatefulWidget {
|
||||
final FeatureSectionModel model;
|
||||
|
||||
const Style6Section({super.key, required this.model});
|
||||
|
||||
@override
|
||||
Style6SectionState createState() => Style6SectionState();
|
||||
}
|
||||
|
||||
class Style6SectionState extends State<Style6Section> {
|
||||
int? style6Sel;
|
||||
bool isNews = true;
|
||||
late final ScrollController style6ScrollController = ScrollController()..addListener(hasMoreSectionScrollListener);
|
||||
late BreakingNewsModel breakingNewsData;
|
||||
late NewsModel newsData;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getSectionDataById();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
style6ScrollController.removeListener(() {});
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void hasMoreSectionScrollListener() {
|
||||
if (style6ScrollController.position.maxScrollExtent == style6ScrollController.offset) {
|
||||
if (context.read<SectionByIdCubit>().hasMoreSections() && !(context.read<SectionByIdCubit>().state is SectionByIdFetchInProgress)) {
|
||||
context.read<SectionByIdCubit>().getMoreSectionById(langId: context.read<AppLocalizationCubit>().state.id, sectionId: widget.model.id!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return style6Data();
|
||||
}
|
||||
|
||||
void getSectionDataById() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<SectionByIdCubit>().getSectionById(
|
||||
langId: context.read<AppLocalizationCubit>().state.id,
|
||||
sectionId: widget.model.id!,
|
||||
latitude: SettingsLocalDataRepository().getLocationCityValues().first,
|
||||
longitude: SettingsLocalDataRepository().getLocationCityValues().last);
|
||||
});
|
||||
}
|
||||
|
||||
Widget style6Data() {
|
||||
return BlocBuilder<SectionByIdCubit, SectionByIdState>(builder: (context, state) {
|
||||
if (state is SectionByIdFetchSuccess) {
|
||||
isNews = (state.type == 'news' || state.type == 'user_choice' || state.type == 'videos') && state.newsModel.isNotEmpty
|
||||
? true
|
||||
: (state.type == 'breaking_news' || state.type == 'videos') && state.breakNewsModel.isNotEmpty
|
||||
? false
|
||||
: isNews;
|
||||
int totalCount = (isNews) ? state.newsModel.length : state.breakNewsModel.length;
|
||||
return (state.breakNewsModel.isNotEmpty || state.newsModel.isNotEmpty)
|
||||
? Padding(padding: const EdgeInsets.symmetric(vertical: 5), child: style6NewsDetails(newsData: state.newsModel, brNewsData: state.breakNewsModel, total: totalCount, type: state.type))
|
||||
: SizedBox.shrink();
|
||||
}
|
||||
return SizedBox.shrink();
|
||||
});
|
||||
}
|
||||
|
||||
Widget style6NewsDetails({List<NewsModel>? newsData, List<BreakingNewsModel>? brNewsData, String? type, int total = 0}) {
|
||||
return SizedBox(
|
||||
height: MediaQuery.of(context).size.height / 2.8,
|
||||
child: SingleChildScrollView(
|
||||
controller: style6ScrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: List.generate(total, (index) {
|
||||
return SizedBox(
|
||||
width: 180,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: (isNews)
|
||||
? setImageCard(index: index, total: total, type: type!, newsModel: newsData![index], allNewsList: newsData)
|
||||
: setImageCard(index: index, total: total, type: type!, breakingNewsModel: brNewsData![index], breakingNewsList: brNewsData)));
|
||||
}))));
|
||||
}
|
||||
|
||||
Widget setImageCard(
|
||||
{required int index,
|
||||
required int total,
|
||||
required String type,
|
||||
NewsModel? newsModel,
|
||||
BreakingNewsModel? breakingNewsModel,
|
||||
List<NewsModel>? allNewsList,
|
||||
List<BreakingNewsModel>? breakingNewsList}) {
|
||||
return InkWell(
|
||||
child: Stack(children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: ShaderMask(
|
||||
shaderCallback: (bounds) {
|
||||
return LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.transparent, darkSecondaryColor.withOpacity(0.9)]).createShader(bounds);
|
||||
},
|
||||
blendMode: BlendMode.darken,
|
||||
child: Container(
|
||||
color: primaryColor.withValues(alpha: 0.15),
|
||||
width: MediaQuery.of(context).size.width / 1.9,
|
||||
height: MediaQuery.of(context).size.height / 2.5,
|
||||
child: CustomNetworkImage(
|
||||
networkImageUrl: (newsModel != null) ? newsModel.image! : breakingNewsModel!.image!,
|
||||
height: MediaQuery.of(context).size.height / 2.5,
|
||||
width: MediaQuery.of(context).size.width / 1.9,
|
||||
fit: BoxFit.cover,
|
||||
isVideo: type == "videos"),
|
||||
),
|
||||
)),
|
||||
(newsModel != null && newsModel.categoryName != null)
|
||||
? Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Container(
|
||||
margin: const EdgeInsetsDirectional.only(start: 7.0, top: 7.0),
|
||||
height: 20.0,
|
||||
padding: const EdgeInsetsDirectional.only(start: 6.0, end: 6.0, top: 2.5),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(3), color: Theme.of(context).primaryColor),
|
||||
child: CustomTextLabel(
|
||||
text: newsModel.categoryName!,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: secondaryColor),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)))
|
||||
: const SizedBox.shrink(),
|
||||
(type == "videos")
|
||||
? Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
top: MediaQuery.of(context).size.height * 0.13,
|
||||
start: MediaQuery.of(context).size.width / 6,
|
||||
end: MediaQuery.of(context).size.width / 6,
|
||||
child: UiUtils.setPlayButton(context: context))
|
||||
: const SizedBox.shrink(),
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
bottom: 5,
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
(newsModel != null)
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: CustomTextLabel(
|
||||
text: UiUtils.convertToAgo(context, DateTime.parse(newsModel.publishDate ?? newsModel.date!), 3)!,
|
||||
textStyle: Theme.of(context).textTheme.labelSmall!.copyWith(color: Colors.white)))
|
||||
: const SizedBox.shrink(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
|
||||
width: MediaQuery.of(context).size.width * 0.50,
|
||||
child: CustomTextLabel(
|
||||
text: (newsModel != null) ? newsModel.title! : breakingNewsModel!.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: secondaryColor),
|
||||
softWrap: true,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis))
|
||||
]),
|
||||
),
|
||||
]),
|
||||
onTap: () {
|
||||
if (type == "videos") {
|
||||
List<NewsModel> newsList = [];
|
||||
List<BreakingNewsModel> brNewsList = [];
|
||||
if (newsModel != null && allNewsList != null && allNewsList.isNotEmpty) {
|
||||
newsList = List.from(allNewsList);
|
||||
newsList.removeAt(index);
|
||||
} else if (breakingNewsModel != null && breakingNewsList != null && breakingNewsList.isNotEmpty) {
|
||||
brNewsList = List.from(breakingNewsList);
|
||||
brNewsList.removeAt(index);
|
||||
}
|
||||
// List<NewsModel> newsList = List.from(allNewsList ?? [])..removeAt(index);
|
||||
// List<BreakingNewsModel> breakNewsList = List.from(breakingNewsList ?? [])..removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {
|
||||
"from": (newsModel != null) ? 1 : 3,
|
||||
if (newsModel != null) "model": newsModel,
|
||||
if (breakingNewsModel != null) "breakModel": breakingNewsModel,
|
||||
"otherBreakingVideos": brNewsList,
|
||||
"otherVideos": newsList,
|
||||
});
|
||||
} else if (type == 'news' || type == "user_choice") {
|
||||
//interstitial ads
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
if (allNewsList != null && allNewsList.isNotEmpty) {
|
||||
List<NewsModel> newsList = List.from(allNewsList);
|
||||
newsList.removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": newsModel, "newsList": newsList, "isFromBreak": false, "fromShowMore": false});
|
||||
}
|
||||
} else {
|
||||
if (breakingNewsList != null && breakingNewsList.isNotEmpty) {
|
||||
//interstitial ads
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
List<BreakingNewsModel> breakList = List.from(breakingNewsList);
|
||||
breakList.removeAt(index);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": breakingNewsModel, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false});
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
140
news-app/lib/ui/screens/HomePage/Widgets/WeatherData.dart
Normal file
140
news-app/lib/ui/screens/HomePage/Widgets/WeatherData.dart
Normal file
@@ -0,0 +1,140 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:news/utils/hiveBoxKeys.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/data/models/WeatherData.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
|
||||
class WeatherDataView extends StatefulWidget {
|
||||
final WeatherDetails weatherData;
|
||||
|
||||
const WeatherDataView({super.key, required this.weatherData});
|
||||
|
||||
@override
|
||||
WeatherDataState createState() => WeatherDataState();
|
||||
}
|
||||
|
||||
class WeatherDataState extends State<WeatherDataView> {
|
||||
late Future<void> _future;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_future = UiUtils.checkIfValidLocale(langCode: Hive.box(settingsBoxKey).get(currentLanguageCodeKey));
|
||||
}
|
||||
|
||||
Widget weatherDataView() {
|
||||
return FutureBuilder<void>(
|
||||
future: _future,
|
||||
builder: (context, snapshot) {
|
||||
DateTime now = DateTime.now();
|
||||
String day = DateFormat('EEEE').format(now);
|
||||
WeatherDetails weatherData = widget.weatherData;
|
||||
return Container(
|
||||
margin: const EdgeInsetsDirectional.only(top: 15.0),
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface),
|
||||
height: MediaQuery.of(context).size.height * 0.14,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
CustomTextLabel(
|
||||
text: 'weatherLbl',
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8)),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Image.network("https:${weatherData.icon!}", width: 40.0, height: 40.0),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 7.0),
|
||||
child: CustomTextLabel(
|
||||
text: "${weatherData.tempC!.toString()}\u2103",
|
||||
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8)),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
maxLines: 1))
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
CustomTextLabel(
|
||||
text: "${weatherData.name!},${weatherData.region!}",
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
maxLines: 1),
|
||||
CustomTextLabel(
|
||||
text: weatherData.country!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
maxLines: 1),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 3.0),
|
||||
child: CustomTextLabel(
|
||||
text: day,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8)),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
maxLines: 1)),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 3.0),
|
||||
child: CustomTextLabel(
|
||||
text: weatherData.text!,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8)),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 3.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Icon(Icons.arrow_upward_outlined, size: 13.0, color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
CustomTextLabel(
|
||||
text: "H:${weatherData.maxTempC!.toString()}\u2103",
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8)),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 8.0),
|
||||
child: Icon(Icons.arrow_downward_outlined, size: 13.0, color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
CustomTextLabel(
|
||||
text: "L:${weatherData.minTempC!.toString()}\u2103",
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8)),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)
|
||||
],
|
||||
)),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return weatherDataView();
|
||||
}
|
||||
}
|
||||
125
news-app/lib/ui/screens/ImagePreviewScreen.dart
Normal file
125
news-app/lib/ui/screens/ImagePreviewScreen.dart
Normal file
@@ -0,0 +1,125 @@
|
||||
// ignore: must_be_immutable
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
|
||||
class ImagePreview extends StatefulWidget {
|
||||
final int index;
|
||||
final List<String> imgList;
|
||||
|
||||
const ImagePreview({super.key, required this.index, required this.imgList});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => StatePreview();
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(builder: (_) => ImagePreview(index: arguments['index'], imgList: arguments['imgList']));
|
||||
}
|
||||
}
|
||||
|
||||
class StatePreview extends State<ImagePreview> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
int? curPos;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
curPos = widget.index;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: Stack(
|
||||
children: <Widget>[
|
||||
PageView.builder(
|
||||
itemCount: widget.imgList.length,
|
||||
controller: PageController(initialPage: curPos!),
|
||||
onPageChanged: (index) async {
|
||||
setState(() {
|
||||
curPos = index;
|
||||
});
|
||||
},
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return PhotoView(
|
||||
backgroundDecoration: BoxDecoration(color: Theme.of(context).colorScheme.secondary),
|
||||
initialScale: PhotoViewComputedScale.contained * 0.9,
|
||||
minScale: PhotoViewComputedScale.contained * 0.9,
|
||||
imageProvider: NetworkImage(widget.imgList[index]));
|
||||
}),
|
||||
Positioned.directional(
|
||||
top: 35,
|
||||
start: 20.0,
|
||||
textDirection: Directionality.of(context),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(22.0),
|
||||
child: Container(
|
||||
height: 35,
|
||||
width: 35,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(color: UiUtils.getColorScheme(context).primaryContainer, shape: BoxShape.circle),
|
||||
child: Icon(
|
||||
Icons.keyboard_backspace_rounded,
|
||||
color: UiUtils.getColorScheme(context).surface,
|
||||
))),
|
||||
)),
|
||||
Positioned(bottom: 10.0, left: 25.0, right: 25.0, child: SelectedPhoto(numberOfDots: widget.imgList.length, photoIndex: curPos!))
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class SelectedPhoto extends StatelessWidget {
|
||||
final int? numberOfDots;
|
||||
final int? photoIndex;
|
||||
|
||||
const SelectedPhoto({super.key, this.numberOfDots, this.photoIndex});
|
||||
|
||||
Widget _inactivePhoto(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 3.0, end: 3.0),
|
||||
child: Container(height: 8.0, width: 8.0, decoration: BoxDecoration(color: Theme.of(context).primaryColor.withOpacity(0.4), borderRadius: BorderRadius.circular(4.0))),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _activePhoto(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 5.0, right: 5.0),
|
||||
child: Container(
|
||||
height: 10.0,
|
||||
width: 10.0,
|
||||
decoration:
|
||||
BoxDecoration(color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(10.0), boxShadow: const [BoxShadow(color: Colors.grey, spreadRadius: 0.0, blurRadius: 2.0)]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildDots(BuildContext context) {
|
||||
List<Widget> dots = [];
|
||||
for (int i = 0; i < numberOfDots!; i++) {
|
||||
dots.add(i == photoIndex ? _activePhoto(context) : _inactivePhoto(context));
|
||||
}
|
||||
return dots;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: _buildDots(context)),
|
||||
);
|
||||
}
|
||||
}
|
||||
79
news-app/lib/ui/screens/LiveStreaming.dart
Normal file
79
news-app/lib/ui/screens/LiveStreaming.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/ui/widgets/customAppBar.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/data/models/LiveStreamingModel.dart';
|
||||
|
||||
class LiveStreaming extends StatefulWidget {
|
||||
List<LiveStreamingModel> liveNews;
|
||||
|
||||
LiveStreaming({super.key, required this.liveNews});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => StateLive();
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(builder: (_) => LiveStreaming(liveNews: arguments['liveNews']));
|
||||
}
|
||||
}
|
||||
|
||||
class StateLive extends State<LiveStreaming> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(appBar: CustomAppBar(height: 45, isBackBtn: true, label: 'liveVideosLbl', horizontalPad: 15, isConvertText: true), body: mainListBuilder());
|
||||
}
|
||||
|
||||
mainListBuilder() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: ListView.separated(
|
||||
itemBuilder: ((context, index) {
|
||||
List<LiveStreamingModel> liveVideos = List.from(widget.liveNews)..removeAt(index);
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: index == 0 ? 0 : 20),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 2, "liveModel": widget.liveNews[index], "otherLiveVideos": liveVideos});
|
||||
},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
CustomNetworkImage(networkImageUrl: widget.liveNews[index].image!, height: 200, fit: BoxFit.cover, isVideo: true, width: double.infinity),
|
||||
const CircleAvatar(radius: 30, backgroundColor: Colors.black45, child: Icon(Icons.play_arrow, size: 40, color: Colors.white)),
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
bottom: 10,
|
||||
start: 20,
|
||||
end: 20,
|
||||
child: CustomTextLabel(
|
||||
text: widget.liveNews[index].title!, textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: secondaryColor), maxLines: 2, overflow: TextOverflow.ellipsis),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
separatorBuilder: (context, index) {
|
||||
return const SizedBox(height: 3.0);
|
||||
},
|
||||
itemCount: widget.liveNews.length),
|
||||
);
|
||||
}
|
||||
}
|
||||
316
news-app/lib/ui/screens/ManagePreference.dart
Normal file
316
news-app/lib/ui/screens/ManagePreference.dart
Normal file
@@ -0,0 +1,316 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/UserPreferences/setUserPreferenceCatCubit.dart';
|
||||
import 'package:news/cubits/UserPreferences/userByCategoryCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/categoryCubit.dart';
|
||||
import 'package:news/data/models/CategoryModel.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/ui/widgets/networkImage.dart';
|
||||
import 'package:news/ui/widgets/customBackBtn.dart';
|
||||
import 'package:news/ui/widgets/customTextBtn.dart';
|
||||
|
||||
class ManagePref extends StatefulWidget {
|
||||
final int? from;
|
||||
|
||||
const ManagePref({super.key, this.from});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return StateManagePref();
|
||||
}
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(builder: (_) => ManagePref(from: arguments['from']));
|
||||
}
|
||||
}
|
||||
|
||||
class StateManagePref extends State<ManagePref> with TickerProviderStateMixin {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
String catId = "";
|
||||
List<String> selectedChoices = [];
|
||||
String selCatId = "";
|
||||
|
||||
static int _count = 0;
|
||||
List<bool> _checks = [];
|
||||
late final ScrollController _categoryScrollController = ScrollController()..addListener(hasMoreCategoryScrollListener);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
getUserData();
|
||||
}
|
||||
|
||||
void getUserData() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<UserByCatCubit>().getUserById();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_categoryScrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void hasMoreCategoryScrollListener() {
|
||||
if (_categoryScrollController.offset >= _categoryScrollController.position.maxScrollExtent && !_categoryScrollController.position.outOfRange) {
|
||||
if (context.read<CategoryCubit>().hasMoreCategory()) {
|
||||
context.read<CategoryCubit>().getMoreCategory(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(key: _scaffoldKey, appBar: setAppBar(), body: contentView());
|
||||
}
|
||||
|
||||
setAppBar() {
|
||||
return PreferredSize(
|
||||
preferredSize: const Size(double.infinity, 70),
|
||||
child: UiUtils.applyBoxShadow(
|
||||
context: context,
|
||||
child: AppBar(
|
||||
leading: widget.from == 1 ? const CustomBackButton(horizontalPadding: 15) : const SizedBox.shrink(),
|
||||
titleSpacing: 0.0,
|
||||
centerTitle: false,
|
||||
backgroundColor: Colors.transparent,
|
||||
title: CustomTextLabel(
|
||||
text: 'managePreferences',
|
||||
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5)),
|
||||
actions: [skipBtn()],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
skipBtn() {
|
||||
if (widget.from != 1) {
|
||||
return Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: CustomTextButton(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushReplacementNamed(Routes.home, arguments: false);
|
||||
},
|
||||
text: 'skip',
|
||||
color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)));
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
contentView() {
|
||||
return BlocConsumer<UserByCatCubit, UserByCatState>(
|
||||
bloc: context.read<UserByCatCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is UserByCatFetchSuccess) {
|
||||
if (state.userByCat != null) {
|
||||
for (int i = 0; i < state.userByCat.length; i++) {
|
||||
catId = state.userByCat["category_id"];
|
||||
}
|
||||
setState(() {
|
||||
selectedChoices = catId == "" ? catId.split('') : catId.split(',');
|
||||
});
|
||||
}
|
||||
context.read<CategoryCubit>().getCategory(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return BlocConsumer<CategoryCubit, CategoryState>(
|
||||
bloc: context.read<CategoryCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is CategoryFetchSuccess) {
|
||||
if ((state).category.isNotEmpty) {
|
||||
setState(() {
|
||||
_count = (state).category.length;
|
||||
_checks = List.generate(_count, (i) => (selectedChoices.contains((state).category[i].id)) ? true : false);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is CategoryFetchInProgress || state is CategoryInitial) {
|
||||
return UiUtils.showCircularProgress(true, Theme.of(context).primaryColor);
|
||||
}
|
||||
if (state is CategoryFetchFailure) {
|
||||
return ErrorContainerWidget(
|
||||
errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getUserData);
|
||||
}
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsetsDirectional.only(start: 15.0, end: 15.0, bottom: 20.0),
|
||||
controller: _categoryScrollController,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 25.0),
|
||||
child: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, crossAxisSpacing: 20, mainAxisSpacing: 20),
|
||||
shrinkWrap: true,
|
||||
itemCount: (state as CategoryFetchSuccess).category.length,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return _buildCategoryContainer(
|
||||
category: state.category[index],
|
||||
hasMore: state.hasMore,
|
||||
hasMoreCategoryFetchError: state.hasMoreFetchError,
|
||||
index: index,
|
||||
totalCurrentCategory: state.category.length);
|
||||
})),
|
||||
nxtBtn()
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
selectCatTxt() {
|
||||
return Transform(
|
||||
transform: Matrix4.translationValues(-50.0, 0.0, 0.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'sel_pref_cat', textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w100, letterSpacing: 0.5)),
|
||||
);
|
||||
}
|
||||
|
||||
_buildCategoryContainer({required CategoryModel category, required int index, required int totalCurrentCategory, required bool hasMoreCategoryFetchError, required bool hasMore}) {
|
||||
if (index == totalCurrentCategory - 1 && index != 0) {
|
||||
if (hasMore) {
|
||||
if (hasMoreCategoryFetchError) {
|
||||
return const SizedBox.shrink();
|
||||
} else {
|
||||
return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Container(
|
||||
padding: EdgeInsets.zero,
|
||||
decoration: const BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(15))),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
_checks[index] = !_checks[index];
|
||||
if (selectedChoices.contains(category.id)) {
|
||||
selectedChoices.remove(category.id);
|
||||
setState(() {});
|
||||
} else {
|
||||
selectedChoices.add(category.id!);
|
||||
setState(() {});
|
||||
}
|
||||
if (selectedChoices.isEmpty) {
|
||||
setState(() {
|
||||
selectedChoices.add("0");
|
||||
});
|
||||
} else {
|
||||
if (selectedChoices.contains("0")) {
|
||||
selectedChoices = List.from(selectedChoices)..remove("0");
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15.0)),
|
||||
child: CustomNetworkImage(networkImageUrl: category.image!, fit: BoxFit.cover, isVideo: false, height: MediaQuery.of(context).size.height / 2.9, width: double.maxFinite)),
|
||||
Positioned.directional(
|
||||
//checkbox
|
||||
textDirection: Directionality.of(context),
|
||||
end: 10,
|
||||
top: 10,
|
||||
child: Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
decoration:
|
||||
BoxDecoration(borderRadius: const BorderRadius.all(Radius.circular(5)), color: selectedChoices.contains(category.id) ? Theme.of(context).primaryColor : secondaryColor),
|
||||
child: selectedChoices.contains(category.id) ? const Icon(Icons.check_rounded, color: secondaryColor, size: 20) : null)),
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
bottom: 0,
|
||||
start: 0,
|
||||
end: 0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(15.0), bottomRight: Radius.circular(15.0)),
|
||||
gradient: LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [darkSecondaryColor.withOpacity(0.03), darkSecondaryColor.withOpacity(0.85)])),
|
||||
padding: EdgeInsets.zero,
|
||||
margin: EdgeInsets.zero,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.all(10),
|
||||
child: CustomTextLabel(
|
||||
text: category.categoryName!,
|
||||
textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.normal, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
nxtBtn() {
|
||||
return BlocConsumer<SetUserPrefCatCubit, SetUserPrefCatState>(
|
||||
bloc: context.read<SetUserPrefCatCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is SetUserPrefCatFetchSuccess) {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'preferenceSave'), context);
|
||||
if (widget.from == 2) {
|
||||
Navigator.of(context).pushReplacementNamed(Routes.home, arguments: false);
|
||||
} else {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 30.0),
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
height: 45.0,
|
||||
width: MediaQuery.of(context).size.width * 0.95,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(15.0)),
|
||||
child: (state is SetUserPrefCatFetchInProgress)
|
||||
? UiUtils.showCircularProgress(true, secondaryColor)
|
||||
: CustomTextLabel(
|
||||
//@Start
|
||||
text: (widget.from == 2) ? 'nxt' : 'saveLbl',
|
||||
//from Settings
|
||||
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: secondaryColor, fontWeight: FontWeight.w600, fontSize: 18),
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
if (selectedChoices.isEmpty) {
|
||||
//no preference selected
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(Routes.home, (Route<dynamic> route) => false);
|
||||
return;
|
||||
} else if (selectedChoices.length == 1) {
|
||||
setState(() {
|
||||
selCatId = selectedChoices.join();
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
selCatId = selectedChoices.join(',');
|
||||
});
|
||||
}
|
||||
context.read<SetUserPrefCatCubit>().setUserPrefCat(catId: selCatId);
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
135
news-app/lib/ui/screens/NewsDetail/NewsDetailScreen.dart
Normal file
135
news-app/lib/ui/screens/NewsDetail/NewsDetailScreen.dart
Normal file
@@ -0,0 +1,135 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/utils/constant.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:unity_ads_plugin/unity_ads_plugin.dart';
|
||||
import 'package:news/data/models/NewsModel.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/NewsDetail/Widgets/RerwardAds/googleRewardAds.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/RerwardAds/unityRewardAds.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/NewsSubDetailsScreen.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
|
||||
class NewsDetailScreen extends StatefulWidget {
|
||||
final NewsModel? model;
|
||||
final List<NewsModel>? newsList;
|
||||
final BreakingNewsModel? breakModel;
|
||||
final List<BreakingNewsModel>? breakNewsList;
|
||||
final bool isFromBreak;
|
||||
final bool fromShowMore;
|
||||
final String? slug;
|
||||
|
||||
const NewsDetailScreen({super.key, this.model, this.breakModel, this.breakNewsList, this.newsList, this.slug, required this.isFromBreak, required this.fromShowMore});
|
||||
|
||||
@override
|
||||
NewsDetailsState createState() => NewsDetailsState();
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(
|
||||
builder: (_) => NewsDetailScreen(
|
||||
model: arguments['model'],
|
||||
breakModel: arguments['breakModel'],
|
||||
breakNewsList: arguments['breakNewsList'],
|
||||
newsList: arguments['newsList'],
|
||||
isFromBreak: arguments['isFromBreak'],
|
||||
fromShowMore: arguments['fromShowMore'],
|
||||
slug: arguments['slug']));
|
||||
}
|
||||
}
|
||||
|
||||
class NewsDetailsState extends State<NewsDetailScreen> {
|
||||
final PageController pageController = PageController();
|
||||
|
||||
bool isScrollLocked = false;
|
||||
|
||||
void _setScrollLock(bool lock) {
|
||||
setState(() {
|
||||
isScrollLocked = lock;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if (context.read<AppConfigurationCubit>().getInAppAdsMode() == "1") {
|
||||
if (context.read<AppConfigurationCubit>().checkAdsType() == "google") {
|
||||
createGoogleInterstitialAd(context);
|
||||
createGoogleRewardedAd(context);
|
||||
} else {
|
||||
if (context.read<AppConfigurationCubit>().unityGameId() != null) {
|
||||
UnityAds.init(
|
||||
gameId: context.read<AppConfigurationCubit>().unityGameId()!,
|
||||
testMode: true, //set it to False @Deployement
|
||||
onComplete: () {
|
||||
loadUnityInterAd(context.read<AppConfigurationCubit>().interstitialId()!);
|
||||
loadUnityRewardAd(context.read<AppConfigurationCubit>().rewardId()!);
|
||||
},
|
||||
onFailed: (error, message) => debugPrint('Initialization Failed: $error $message'),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Widget showBreakingNews() {
|
||||
return PageView.builder(
|
||||
controller: pageController,
|
||||
onPageChanged: (index) async {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
if (index % rewardAdsIndex == 0) showRewardAds();
|
||||
if (index % interstitialAdsIndex == 0) UiUtils.showInterstitialAds(context: context);
|
||||
}
|
||||
},
|
||||
itemCount: (widget.breakNewsList == null || widget.breakNewsList!.isEmpty) ? 1 : widget.breakNewsList!.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
return NewsSubDetails(
|
||||
onLockScroll: (p0) {}, //no change for breaking news , No comments widget
|
||||
breakModel: (index == 0) ? widget.breakModel : widget.breakNewsList![index - 1],
|
||||
fromShowMore: widget.fromShowMore,
|
||||
isFromBreak: widget.isFromBreak,
|
||||
model: widget.model);
|
||||
});
|
||||
}
|
||||
|
||||
void showRewardAds() {
|
||||
if (context.read<AppConfigurationCubit>().getInAppAdsMode() == "1") {
|
||||
if (context.read<AppConfigurationCubit>().checkAdsType() == "google") {
|
||||
showGoogleRewardedAd(context);
|
||||
} else {
|
||||
showUnityRewardAds(context.read<AppConfigurationCubit>().rewardId()!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget showNews() {
|
||||
return PageView.builder(
|
||||
controller: pageController,
|
||||
physics: isScrollLocked ? NeverScrollableScrollPhysics() : AlwaysScrollableScrollPhysics(),
|
||||
onPageChanged: (index) async {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
if (index % rewardAdsIndex == 0) showRewardAds();
|
||||
if (index % interstitialAdsIndex == 0) UiUtils.showInterstitialAds(context: context);
|
||||
}
|
||||
},
|
||||
itemCount: (widget.newsList == null || widget.newsList!.isEmpty) ? 1 : widget.newsList!.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
return NewsSubDetails(
|
||||
onLockScroll: _setScrollLock,
|
||||
model: (index == 0) ? widget.model : widget.newsList![index - 1],
|
||||
fromShowMore: widget.fromShowMore,
|
||||
isFromBreak: widget.isFromBreak,
|
||||
breakModel: widget.breakModel);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(backgroundColor: UiUtils.getColorScheme(context).secondary, body: widget.isFromBreak ? showBreakingNews() : showNews());
|
||||
}
|
||||
}
|
||||
318
news-app/lib/ui/screens/NewsDetail/Widgets/CommentView.dart
Normal file
318
news-app/lib/ui/screens/NewsDetail/Widgets/CommentView.dart
Normal file
@@ -0,0 +1,318 @@
|
||||
import 'package:news/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/delAndReportCom.dart';
|
||||
import 'package:news/cubits/NewsComment/likeAndDislikeCommCubit.dart';
|
||||
import 'package:news/cubits/NewsComment/setCommentCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/data/models/CommentModel.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/commentNewsCubit.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CommentView extends StatefulWidget {
|
||||
final String newsId;
|
||||
final Function updateComFun;
|
||||
final Function updateIsReplyFun;
|
||||
|
||||
const CommentView({super.key, required this.newsId, required this.updateComFun, required this.updateIsReplyFun});
|
||||
|
||||
@override
|
||||
CommentViewState createState() => CommentViewState();
|
||||
}
|
||||
|
||||
class CommentViewState extends State<CommentView> {
|
||||
final TextEditingController _commentC = TextEditingController();
|
||||
TextEditingController reportC = TextEditingController();
|
||||
bool comBtnEnabled = false, isReply = false, isSending = false;
|
||||
int? replyComIndex;
|
||||
|
||||
Widget commentsLengthView(int length) {
|
||||
return Row(children: [
|
||||
if (length > 0)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Row(children: [
|
||||
CustomTextLabel(
|
||||
text: 'allLbl',
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6), fontSize: 12.0, fontWeight: FontWeight.w600)),
|
||||
CustomTextLabel(
|
||||
text: " $length ",
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6), fontSize: 12.0, fontWeight: FontWeight.w600)),
|
||||
CustomTextLabel(
|
||||
text: 'comsLbl',
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6), fontSize: 12.0, fontWeight: FontWeight.w600)),
|
||||
])),
|
||||
const Spacer(),
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: InkWell(
|
||||
child: const Icon(Icons.close_rounded),
|
||||
onTap: () {
|
||||
widget.updateComFun(false);
|
||||
},
|
||||
))
|
||||
]);
|
||||
}
|
||||
|
||||
Widget profileWithSendCom() {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 5.0),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: BlocBuilder<AuthCubit, AuthState>(builder: (context, state) {
|
||||
if (state is Authenticated && context.read<AuthCubit>().getProfile() != "") {
|
||||
return CircleAvatar(backgroundImage: NetworkImage(context.read<AuthCubit>().getProfile()));
|
||||
} else {
|
||||
return UiUtils.setFixedSizeboxForProfilePicture(childWidget: const Icon(Icons.account_circle, size: 35));
|
||||
}
|
||||
})),
|
||||
BlocListener<SetCommentCubit, SetCommentState>(
|
||||
bloc: context.read<SetCommentCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is SetCommentFetchSuccess) {
|
||||
context.read<CommentNewsCubit>().commentUpdateList(state.setComment, state.total);
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.unfocus();
|
||||
}
|
||||
_commentC.clear();
|
||||
isSending = false;
|
||||
setState(() {});
|
||||
}
|
||||
if (state is SetCommentFetchInProgress) {
|
||||
setState(() => isSending = true);
|
||||
}
|
||||
},
|
||||
child: Expanded(
|
||||
flex: 7,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 18.0),
|
||||
child: TextField(
|
||||
controller: _commentC,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)),
|
||||
onChanged: (String val) {
|
||||
(_commentC.text.trim().isNotEmpty) ? setState(() => comBtnEnabled = true) : setState(() => comBtnEnabled = false);
|
||||
},
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: null,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.only(top: 10.0, bottom: 2.0),
|
||||
isDense: true,
|
||||
suffixIconConstraints: BoxConstraints(maxHeight: (!isSending) ? 35 : 0, maxWidth: (!isSending) ? 30 : 0),
|
||||
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5), width: 1.5)),
|
||||
hintText: UiUtils.getTranslatedLabel(context, 'shareThoghtLbl'),
|
||||
hintStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)),
|
||||
suffixIcon: (_commentC.text.trim().isNotEmpty)
|
||||
? (!isSending)
|
||||
? IconButton(
|
||||
icon: Icon(Icons.send, color: comBtnEnabled ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8) : Colors.transparent, size: 20.0),
|
||||
onPressed: () async {
|
||||
(context.read<AuthCubit>().getUserId() != "0")
|
||||
? context.read<SetCommentCubit>().setComment(parentId: "0", newsId: widget.newsId, message: _commentC.text)
|
||||
: UiUtils.loginRequired(context);
|
||||
})
|
||||
: SizedBox(height: 12, width: 12, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))
|
||||
: SizedBox.shrink())))))
|
||||
]));
|
||||
}
|
||||
|
||||
_buildCommContainer({required CommentModel model, required int index, required int totalCurrentComm, required bool hasMoreCommFetchError, required bool hasMore}) {
|
||||
model = model;
|
||||
if (index == totalCurrentComm - 1 && index <= 0) {
|
||||
//check if hasMore
|
||||
if (hasMore) {
|
||||
if (hasMoreCommFetchError) {
|
||||
return const SizedBox.shrink();
|
||||
} else {
|
||||
return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Builder(builder: (context) {
|
||||
return Row(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
|
||||
(model.profile != null && model.profile != "")
|
||||
? UiUtils.setFixedSizeboxForProfilePicture(
|
||||
childWidget: CircleAvatar(backgroundImage: (model.profile != null) ? NetworkImage(model.profile!) : NetworkImage(const Icon(Icons.account_circle, size: 35) as String), radius: 32))
|
||||
: UiUtils.setFixedSizeboxForProfilePicture(childWidget: const Icon(Icons.account_circle, size: 35)),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 15.0),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Row(children: [
|
||||
CustomTextLabel(
|
||||
text: model.name!, textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontSize: 13)),
|
||||
Padding(padding: const EdgeInsetsDirectional.only(start: 10.0), child: Icon(Icons.circle, size: 4.0, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 10.0),
|
||||
child: CustomTextLabel(
|
||||
text: UiUtils.convertToAgo(context, DateTime.parse(model.date!), 1)!,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontSize: 10),
|
||||
))
|
||||
]),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: CustomTextLabel(
|
||||
text: model.message!, textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.normal))),
|
||||
BlocBuilder<LikeAndDislikeCommCubit, LikeAndDislikeCommState>(builder: (context, state) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 15.0),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: const Icon(Icons.thumb_up_off_alt_rounded),
|
||||
onTap: () {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
context
|
||||
.read<LikeAndDislikeCommCubit>()
|
||||
.setLikeAndDislikeComm(langId: context.read<AppLocalizationCubit>().state.id, commId: model.id!, status: (model.like == "1") ? "0" : "1", fromLike: true);
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
}),
|
||||
model.totalLikes! != "0"
|
||||
? Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 4.0),
|
||||
child: CustomTextLabel(text: model.totalLikes!, textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer)))
|
||||
: const SizedBox(width: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 35),
|
||||
child: InkWell(
|
||||
child: const Icon(Icons.thumb_down_alt_rounded),
|
||||
onTap: () {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
context
|
||||
.read<LikeAndDislikeCommCubit>()
|
||||
.setLikeAndDislikeComm(langId: context.read<AppLocalizationCubit>().state.id, commId: model.id!, status: (model.dislike == "1") ? "0" : "2", fromLike: false);
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
},
|
||||
)),
|
||||
model.totalDislikes! != "0"
|
||||
? Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 4.0),
|
||||
child:
|
||||
CustomTextLabel(text: model.totalDislikes!, textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer)))
|
||||
: const SizedBox(width: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 35),
|
||||
child: InkWell(
|
||||
child: const Icon(Icons.quickreply_rounded),
|
||||
onTap: () {
|
||||
widget.updateIsReplyFun(true, index);
|
||||
setState(() {
|
||||
isReply = true;
|
||||
replyComIndex = index;
|
||||
});
|
||||
},
|
||||
)),
|
||||
model.replyComList!.isNotEmpty
|
||||
? Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 5.0),
|
||||
child: CustomTextLabel(
|
||||
text: model.replyComList!.length.toString(),
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer)))
|
||||
: const SizedBox.shrink(),
|
||||
const Spacer(),
|
||||
if (context.read<AuthCubit>().getUserId() != "0")
|
||||
InkWell(
|
||||
child: Icon(Icons.more_vert_outlined, color: UiUtils.getColorScheme(context).primaryContainer, size: 17),
|
||||
onTap: () => delAndReportCom(index: index, newsId: widget.newsId, context: context, model: model, reportC: reportC, setState: setState))
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0),
|
||||
child: InkWell(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Row(children: [
|
||||
CustomTextLabel(
|
||||
text: model.replyComList!.isNotEmpty ? "${model.replyComList!.length} " : "",
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).primaryColor, fontSize: 12, fontWeight: FontWeight.w600)),
|
||||
CustomTextLabel(
|
||||
text: model.replyComList!.isNotEmpty ? ((model.replyComList!.length == 1) ? 'replyLbl' : 'repliesLbl') : "",
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).primaryColor, fontSize: 12, fontWeight: FontWeight.w600))
|
||||
])),
|
||||
onTap: () {
|
||||
widget.updateIsReplyFun(true, index);
|
||||
setState(() {
|
||||
isReply = true;
|
||||
replyComIndex = index;
|
||||
});
|
||||
}))
|
||||
]))),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
Widget allComListView(CommentNewsFetchSuccess state) {
|
||||
return BlocListener<LikeAndDislikeCommCubit, LikeAndDislikeCommState>(
|
||||
listener: (context, likeDislikeState) {
|
||||
if (likeDislikeState is LikeAndDislikeCommSuccess) {
|
||||
final defaultIndex = state.commentNews.indexWhere((element) => element.id == likeDislikeState.comment.id);
|
||||
if (defaultIndex != -1) {
|
||||
state.commentNews[defaultIndex] = likeDislikeState.comment;
|
||||
context.read<CommentNewsCubit>().emitSuccessState(state.commentNews);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
child: ListView.separated(
|
||||
separatorBuilder: (BuildContext context, int index) => Divider(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5)),
|
||||
shrinkWrap: true,
|
||||
primary: false,
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: state.commentNews.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildCommContainer(
|
||||
model: state.commentNews[index],
|
||||
hasMore: state.hasMore,
|
||||
hasMoreCommFetchError: (state).hasMoreFetchError,
|
||||
index: index,
|
||||
totalCurrentComm: (state.commentNews[index].replyComList!.length + state.commentNews.length),
|
||||
);
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
Widget commentView() {
|
||||
return BlocBuilder<CommentNewsCubit, CommentNewsState>(builder: (context, state) {
|
||||
if (state is CommentNewsFetchInProgress || state is CommentNewsInitial) {
|
||||
return Center(child: UiUtils.showCircularProgress(true, UiUtils.getColorScheme(context).primaryContainer));
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 10.0, bottom: 10.0),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
if (state is CommentNewsFetchSuccess) commentsLengthView((state).commentNews.length),
|
||||
if (state is! CommentNewsFetchSuccess)
|
||||
Row(children: [const Spacer(), Align(alignment: Alignment.topRight, child: InkWell(child: const Icon(Icons.close_rounded), onTap: () => widget.updateComFun(false)))]),
|
||||
if ((state is CommentNewsFetchFailure && !state.errorMessage.contains(ErrorMessageKeys.noInternet) || state is CommentNewsFetchSuccess)) profileWithSendCom(),
|
||||
if (state is CommentNewsFetchFailure)
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height / 2,
|
||||
child: Center(
|
||||
child: CustomTextLabel(
|
||||
text: (state.errorMessage.contains(ErrorMessageKeys.noInternet))
|
||||
? UiUtils.getTranslatedLabel(context, 'internetmsg')
|
||||
: (state.errorMessage == "No Data Found")
|
||||
? UiUtils.getTranslatedLabel(context, 'noComments')
|
||||
: state.errorMessage,
|
||||
textAlign: TextAlign.center))),
|
||||
if (state is CommentNewsFetchSuccess) allComListView(state)
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(create: (context) => LikeAndDislikeCommCubit(LikeAndDislikeCommRepository()), child: commentView());
|
||||
}
|
||||
}
|
||||
98
news-app/lib/ui/screens/NewsDetail/Widgets/ImageView.dart
Normal file
98
news-app/lib/ui/screens/NewsDetail/Widgets/ImageView.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import '../../../../app/routes.dart';
|
||||
import '../../../../data/models/BreakingNewsModel.dart';
|
||||
import '../../../../data/models/NewsModel.dart';
|
||||
|
||||
class ImageView extends StatefulWidget {
|
||||
final NewsModel? model;
|
||||
final BreakingNewsModel? breakModel;
|
||||
final bool isFromBreak;
|
||||
|
||||
const ImageView({super.key, this.model, this.breakModel, required this.isFromBreak});
|
||||
|
||||
@override
|
||||
ImageViewState createState() => ImageViewState();
|
||||
}
|
||||
|
||||
class ImageViewState extends State<ImageView> {
|
||||
List<String> allImage = [];
|
||||
int _curSlider = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
allImage.clear();
|
||||
if (!widget.isFromBreak) {
|
||||
allImage.add(widget.model!.image!);
|
||||
if (widget.model!.imageDataList!.isNotEmpty) {
|
||||
for (int i = 0; i < widget.model!.imageDataList!.length; i++) {
|
||||
allImage.add(widget.model!.imageDataList![i].otherImage!);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
allImage.add(widget.breakModel!.image!);
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Widget imageView() {
|
||||
return SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.40,
|
||||
width: double.maxFinite,
|
||||
child: !widget.isFromBreak
|
||||
? PageView.builder(
|
||||
itemCount: allImage.length,
|
||||
onPageChanged: (index) {
|
||||
setState(() {
|
||||
_curSlider = index;
|
||||
});
|
||||
},
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return InkWell(
|
||||
child: CustomNetworkImage(networkImageUrl: allImage[index], width: double.infinity, height: MediaQuery.of(context).size.height * 0.42, isVideo: false, fit: BoxFit.cover),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.imagePreview, arguments: {"index": index, "imgList": allImage});
|
||||
},
|
||||
);
|
||||
})
|
||||
: CustomNetworkImage(networkImageUrl: widget.breakModel!.image!, width: double.infinity, height: MediaQuery.of(context).size.height * 0.42, isVideo: false, fit: BoxFit.cover));
|
||||
}
|
||||
|
||||
List<T> map<T>(List list, Function handler) {
|
||||
List<T> result = [];
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
result.add(handler(i, list[i]));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Widget imageSliderDot() {
|
||||
if (!widget.isFromBreak && allImage.length > 1) {
|
||||
return Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(top: MediaQuery.of(context).size.height / 2.6 - 32),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: map<Widget>(allImage, (index, url) {
|
||||
return Container(
|
||||
width: _curSlider == index ? 10.0 : 8.0,
|
||||
height: _curSlider == index ? 10.0 : 8.0,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 1.0),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: (_curSlider == index) ? const [BoxShadow(color: Colors.grey, spreadRadius: 0.0, blurRadius: 2.0)] : [],
|
||||
color: _curSlider == index ? secondaryColor : secondaryColor.withOpacity((0.5))));
|
||||
}))));
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(children: [imageView(), imageSliderDot()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
|
||||
const AdRequest request = AdRequest(
|
||||
//static
|
||||
keywords: <String>['foo', 'bar'],
|
||||
contentUrl: 'http://foo.com/bar.html',
|
||||
nonPersonalizedAds: true,
|
||||
);
|
||||
int maxFailedLoadAttempts = 3;
|
||||
InterstitialAd? _interstitialAd;
|
||||
int _numInterstitialLoadAttempts = 0;
|
||||
|
||||
void createGoogleInterstitialAd(BuildContext context) {
|
||||
if (context.read<AppConfigurationCubit>().interstitialId() != "") {
|
||||
InterstitialAd.load(
|
||||
adUnitId: context.read<AppConfigurationCubit>().interstitialId()!,
|
||||
request: request,
|
||||
adLoadCallback: InterstitialAdLoadCallback(
|
||||
onAdLoaded: (InterstitialAd ad) {
|
||||
_interstitialAd = ad;
|
||||
_numInterstitialLoadAttempts = 0;
|
||||
_interstitialAd!.setImmersiveMode(true);
|
||||
},
|
||||
onAdFailedToLoad: (LoadAdError error) {
|
||||
_numInterstitialLoadAttempts += 1;
|
||||
_interstitialAd = null;
|
||||
if (_numInterstitialLoadAttempts <= maxFailedLoadAttempts) {
|
||||
createGoogleInterstitialAd(context);
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void showGoogleInterstitialAd(BuildContext context) {
|
||||
if (_interstitialAd == null) {
|
||||
return;
|
||||
}
|
||||
_interstitialAd!.fullScreenContentCallback = FullScreenContentCallback(
|
||||
onAdShowedFullScreenContent: (InterstitialAd ad) => debugPrint('ad onAdShowedFullScreenContent.'),
|
||||
onAdDismissedFullScreenContent: (InterstitialAd ad) {
|
||||
ad.dispose();
|
||||
createGoogleInterstitialAd(context);
|
||||
},
|
||||
onAdFailedToShowFullScreenContent: (InterstitialAd ad, AdError error) {
|
||||
ad.dispose();
|
||||
createGoogleInterstitialAd(context);
|
||||
},
|
||||
);
|
||||
_interstitialAd!.show();
|
||||
_interstitialAd = null;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:unity_ads_plugin/unity_ads_plugin.dart';
|
||||
|
||||
void loadUnityInterAd(String placementId) {
|
||||
UnityAds.load(placementId: placementId, onComplete: (placementId) {}, onFailed: (placementId, error, message) {});
|
||||
}
|
||||
|
||||
void showUnityInterstitialAds(String placementId) {
|
||||
UnityAds.showVideoAd(
|
||||
placementId: placementId,
|
||||
onComplete: (placementId) {
|
||||
loadUnityInterAd(placementId);
|
||||
},
|
||||
onFailed: (placementId, error, message) {
|
||||
loadUnityInterAd(placementId);
|
||||
},
|
||||
onStart: (placementId) => debugPrint('Video Ad $placementId started'),
|
||||
onClick: (placementId) => debugPrint('Video Ad $placementId click'),
|
||||
onSkipped: (placementId) {
|
||||
loadUnityInterAd(placementId);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_tts/flutter_tts.dart';
|
||||
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/adSpacesNewsDetailsCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/commentNewsCubit.dart';
|
||||
import 'package:news/cubits/relatedNewsCubit.dart';
|
||||
import 'package:news/cubits/setNewsViewsCubit.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/ui/widgets/adSpaces.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:unity_ads_plugin/unity_ads_plugin.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/ImageView.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/horizontalBtnList.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/relatedNewsList.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/setBannderAds.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/tagView.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/titleView.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/videoBtn.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/CommentView.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/ReplyCommentView.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/backBtn.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/dateView.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/descView.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/likeBtn.dart';
|
||||
|
||||
class NewsSubDetails extends StatefulWidget {
|
||||
final NewsModel? model;
|
||||
final BreakingNewsModel? breakModel;
|
||||
final bool fromShowMore;
|
||||
final bool isFromBreak;
|
||||
final Function(bool) onLockScroll;
|
||||
|
||||
const NewsSubDetails({super.key, this.model, this.breakModel, required this.fromShowMore, required this.isFromBreak, required this.onLockScroll});
|
||||
|
||||
@override
|
||||
NewsSubDetailsState createState() => NewsSubDetailsState();
|
||||
}
|
||||
|
||||
class NewsSubDetailsState extends State<NewsSubDetails> {
|
||||
bool comEnabled = false;
|
||||
bool isReply = false;
|
||||
int? replyComIndex;
|
||||
int fontValue = 15;
|
||||
bool isPlaying = false;
|
||||
double volume = 0.5;
|
||||
double pitch = 1.0;
|
||||
double rate = 0.5;
|
||||
BannerAd? _bannerAd;
|
||||
NewsModel? newsModel;
|
||||
FlutterTts? _flutterTts;
|
||||
bool _isScrollingUp = false;
|
||||
late final ScrollController controller = ScrollController()..addListener(hasMoreCommScrollListener);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
newsModel = widget.model;
|
||||
getComments();
|
||||
getRelatedNews();
|
||||
initializeTts();
|
||||
setNewsViews(isBreakingNews: (widget.isFromBreak) ? true : false);
|
||||
if (context.read<AppConfigurationCubit>().getInAppAdsMode() == "1") bannerAdsInitialized();
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<AdSpacesNewsDetailsCubit>().getAdspaceForNewsDetails(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
});
|
||||
}
|
||||
|
||||
setNewsViews({required bool isBreakingNews}) {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<SetNewsViewsCubit>().setNewsViews(newsId: isBreakingNews ? widget.breakModel!.id! : (newsModel!.newsId ?? newsModel!.id)!, isBreakingNews: isBreakingNews);
|
||||
});
|
||||
}
|
||||
|
||||
getComments() {
|
||||
if (!widget.isFromBreak && context.read<AppConfigurationCubit>().getCommentsMode() == "1") {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<CommentNewsCubit>().getCommentNews(newsId: (newsModel!.newsId != null && newsModel!.newsId!.trim().isNotEmpty) ? newsModel!.newsId! : newsModel!.id!);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getRelatedNews() {
|
||||
if (!widget.isFromBreak) {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<RelatedNewsCubit>().getRelatedNews(
|
||||
langId: context.read<AppLocalizationCubit>().state.id,
|
||||
catId: (newsModel!.categoryId == "0" || newsModel!.categoryId == '') ? newsModel!.categoryId : null,
|
||||
subCatId: (newsModel!.subCatId != "0" || newsModel!.subCatId != '') ? newsModel!.subCatId : null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_flutterTts!.stop();
|
||||
controller.removeListener(hasMoreCommScrollListener);
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
updateFontVal(int fontVal) {
|
||||
setState(() => fontValue = fontVal);
|
||||
}
|
||||
|
||||
initializeTts() {
|
||||
_flutterTts = FlutterTts();
|
||||
_flutterTts!.awaitSpeakCompletion(true);
|
||||
_flutterTts!.setStartHandler(() async {
|
||||
if (mounted) {
|
||||
setState(() => isPlaying = true);
|
||||
}
|
||||
});
|
||||
|
||||
_flutterTts!.setCompletionHandler(() {
|
||||
if (mounted) {
|
||||
setState(() => isPlaying = false);
|
||||
}
|
||||
});
|
||||
|
||||
_flutterTts!.setErrorHandler((err) {
|
||||
if (mounted) {
|
||||
setState(() => isPlaying = false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bannerAdsInitialized() {
|
||||
if (context.read<AppConfigurationCubit>().checkAdsType() == "unity") {
|
||||
UnityAds.init(
|
||||
gameId: context.read<AppConfigurationCubit>().unityGameId()!,
|
||||
testMode: true, //set it to false @Deployment
|
||||
onComplete: () {},
|
||||
onFailed: (error, message) {});
|
||||
}
|
||||
|
||||
if (context.read<AppConfigurationCubit>().checkAdsType() == "google") {
|
||||
_createBottomBannerAd();
|
||||
}
|
||||
}
|
||||
|
||||
void _createBottomBannerAd() {
|
||||
if (context.read<AppConfigurationCubit>().bannerId() != "") {
|
||||
_bannerAd = BannerAd(
|
||||
adUnitId: context.read<AppConfigurationCubit>().bannerId()!,
|
||||
request: const AdRequest(),
|
||||
size: AdSize.banner,
|
||||
listener: BannerAdListener(
|
||||
onAdLoaded: (_) {},
|
||||
onAdFailedToLoad: (ad, err) {
|
||||
ad.dispose();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
_bannerAd!.load();
|
||||
}
|
||||
}
|
||||
|
||||
speak(String description) async {
|
||||
if (description.isNotEmpty) {
|
||||
await _flutterTts!.setVolume(volume);
|
||||
await _flutterTts!.setSpeechRate(rate);
|
||||
await _flutterTts!.setPitch(pitch);
|
||||
await _flutterTts!.setLanguage(() {
|
||||
return context.read<AppLocalizationCubit>().state.languageCode;
|
||||
}());
|
||||
int length = description.length;
|
||||
|
||||
if (length < 4000) {
|
||||
setState(() => isPlaying = true);
|
||||
await _flutterTts!.speak(description);
|
||||
_flutterTts!.setCompletionHandler(() {
|
||||
setState(() {
|
||||
_flutterTts!.stop();
|
||||
isPlaying = false;
|
||||
});
|
||||
});
|
||||
} else if (length < 8000) {
|
||||
if (Platform.isAndroid) await _flutterTts!.setQueueMode(1);
|
||||
String temp1 = description.substring(0, length ~/ 2);
|
||||
await _flutterTts!.speak(temp1);
|
||||
_flutterTts!.setCompletionHandler(() {
|
||||
setState(() {
|
||||
isPlaying = true;
|
||||
});
|
||||
});
|
||||
String temp2 = description.substring(temp1.length, description.length);
|
||||
await _flutterTts!.speak(temp2);
|
||||
_flutterTts!.setCompletionHandler(() {
|
||||
setState(() {
|
||||
isPlaying = false;
|
||||
});
|
||||
});
|
||||
} else if (length < 12000) {
|
||||
if (Platform.isAndroid) await _flutterTts!.setQueueMode(2);
|
||||
String temp1 = description.substring(0, 3999);
|
||||
await _flutterTts!.speak(temp1);
|
||||
_flutterTts!.setCompletionHandler(() {
|
||||
setState(() {
|
||||
isPlaying = true;
|
||||
});
|
||||
});
|
||||
String temp2 = description.substring(temp1.length, 7999);
|
||||
await _flutterTts!.speak(temp2);
|
||||
_flutterTts!.setCompletionHandler(() {
|
||||
setState(() {});
|
||||
});
|
||||
String temp3 = description.substring(temp2.length, description.length);
|
||||
await _flutterTts!.speak(temp3);
|
||||
_flutterTts!.setCompletionHandler(() {
|
||||
setState(() {
|
||||
isPlaying = false;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
String temp = description.substring(0, description.length);
|
||||
await _flutterTts!.speak(temp);
|
||||
_flutterTts!.setCompletionHandler(() {
|
||||
setState(() {
|
||||
isPlaying = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stop() async {
|
||||
var result = await _flutterTts!.stop();
|
||||
if (result == 1) setState(() => isPlaying = false);
|
||||
}
|
||||
|
||||
onBackPress(bool isTrue) {
|
||||
(widget.fromShowMore == true) ? Navigator.of(context).popUntil((route) => route.isFirst) : Navigator.pop(context);
|
||||
}
|
||||
|
||||
Widget showViews() {
|
||||
return Row(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
Icon(Icons.remove_red_eye_rounded, size: 17, color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
const SizedBox(width: 5),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: CustomTextLabel(
|
||||
text: ((!widget.isFromBreak) ? newsModel!.totalViews : widget.breakModel!.totalViews) ?? '0',
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600),
|
||||
textAlign: TextAlign.center))
|
||||
]);
|
||||
}
|
||||
|
||||
Widget showAuthor() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 5, left: 5, right: 5),
|
||||
child: Container(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.authorDetails, arguments: {"authorId": newsModel!.userAthorDetails!.id ?? "0"});
|
||||
},
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircleAvatar(backgroundImage: NetworkImage(newsModel!.userAthorDetails!.profile!)),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: CustomTextLabel(
|
||||
text: newsModel!.userAthorDetails!.name ?? '',
|
||||
textAlign: TextAlign.left,
|
||||
textStyle: TextStyle(
|
||||
color: UiUtils.getColorScheme(context).primaryContainer,
|
||||
fontWeight: FontWeight.w600, // SemiBold
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
otherMainDetails() {
|
||||
int readingTime = UiUtils.calculateReadingTime(widget.isFromBreak ? widget.breakModel!.desc! : newsModel!.desc!);
|
||||
String minutesPostfix = (readingTime == 1) ? UiUtils.getTranslatedLabel(context, 'minute') : UiUtils.getTranslatedLabel(context, 'minutes');
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).size.height / 2.7),
|
||||
child: Container(
|
||||
padding: const EdgeInsetsDirectional.only(top: 20.0, start: 20.0, end: 20.0),
|
||||
width: double.maxFinite,
|
||||
decoration: BoxDecoration(borderRadius: const BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), color: UiUtils.getColorScheme(context).secondary),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
|
||||
allRowBtn(
|
||||
isFromBreak: widget.isFromBreak,
|
||||
context: context,
|
||||
breakModel: widget.isFromBreak ? widget.breakModel : null,
|
||||
model: !widget.isFromBreak ? newsModel! : null,
|
||||
fontVal: fontValue,
|
||||
updateFont: updateFontVal,
|
||||
isPlaying: isPlaying,
|
||||
speak: speak,
|
||||
stop: stop,
|
||||
updateComEnabled: updateCommentshow),
|
||||
if (newsModel!.authorDetails != null) showAuthor(),
|
||||
BlocBuilder<AdSpacesNewsDetailsCubit, AdSpacesNewsDetailsState>(
|
||||
builder: (context, state) {
|
||||
return (state is AdSpacesNewsDetailsFetchSuccess && state.adSpaceTopData != null) ? AdSpaces(adsModel: state.adSpaceTopData!) : const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!widget.isFromBreak) tagView(model: newsModel!, context: context, isFromDetailsScreen: true),
|
||||
if (!isReply && !comEnabled)
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 8.0),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
if (!widget.isFromBreak) dateView(context, newsModel!.publishDate ?? newsModel!.date!),
|
||||
if (!widget.isFromBreak) const SizedBox(width: 20),
|
||||
showViews(),
|
||||
const SizedBox(width: 20),
|
||||
Icon(Icons.circle, size: 10, color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
const SizedBox(width: 10),
|
||||
CustomTextLabel(
|
||||
text: "$readingTime $minutesPostfix ${UiUtils.getTranslatedLabel(context, 'read')}",
|
||||
textStyle:
|
||||
Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 12.0, fontWeight: FontWeight.w400))
|
||||
])),
|
||||
if (!isReply && !comEnabled) titleView(title: widget.isFromBreak ? widget.breakModel!.title! : newsModel!.title!, context: context),
|
||||
if (!isReply && !comEnabled) descView(desc: widget.isFromBreak ? widget.breakModel!.desc! : newsModel!.desc!, context: context, fontValue: fontValue.toDouble()),
|
||||
],
|
||||
),
|
||||
if (!widget.isFromBreak && !isReply && comEnabled) CommentView(newsId: newsModel!.id!, updateComFun: updateCommentshow, updateIsReplyFun: updateComReply),
|
||||
if (!widget.isFromBreak && isReply && comEnabled) ReplyCommentView(replyComIndex: replyComIndex!, replyComFun: updateComReply, newsId: newsModel!.id!),
|
||||
BlocBuilder<AdSpacesNewsDetailsCubit, AdSpacesNewsDetailsState>(
|
||||
builder: (context, state) {
|
||||
return (state is AdSpacesNewsDetailsFetchSuccess && state.adSpaceBottomData != null)
|
||||
? Padding(padding: const EdgeInsetsDirectional.only(bottom: 5), child: AdSpaces(adsModel: state.adSpaceBottomData!))
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
if (!widget.isFromBreak && !isReply && !comEnabled && newsModel != null) RelatedNewsList(model: newsModel!),
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
||||
updateCommentshow(bool comEnabledUpdate) {
|
||||
setState(() {
|
||||
comEnabled = comEnabledUpdate;
|
||||
widget.onLockScroll(comEnabledUpdate);
|
||||
});
|
||||
}
|
||||
|
||||
updateComReply(bool comReplyUpdate, int comIndex) {
|
||||
setState(() {
|
||||
isReply = comReplyUpdate;
|
||||
replyComIndex = comIndex;
|
||||
widget.onLockScroll(false);
|
||||
});
|
||||
}
|
||||
|
||||
void hasMoreCommScrollListener() {
|
||||
if (!widget.isFromBreak && comEnabled && !isReply) {
|
||||
if (controller.position.maxScrollExtent == controller.offset) {
|
||||
if (context.read<CommentNewsCubit>().hasMoreCommentNews()) {
|
||||
context.read<CommentNewsCubit>().getMoreCommentNews(newsId: (newsModel!.newsId != null && newsModel!.newsId!.trim().isNotEmpty) ? newsModel!.newsId! : newsModel!.id!);
|
||||
} else {}
|
||||
}
|
||||
}
|
||||
//for comments area
|
||||
if (controller.position.userScrollDirection == ScrollDirection.forward) {
|
||||
// User is scrolling up
|
||||
if (!_isScrollingUp) {
|
||||
setState(() {
|
||||
_isScrollingUp = true;
|
||||
});
|
||||
}
|
||||
} else if (controller.position.userScrollDirection == ScrollDirection.reverse) {
|
||||
// User is scrolling down
|
||||
if (_isScrollingUp) {
|
||||
setState(() {
|
||||
_isScrollingUp = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
onPopInvoked: (bool isTrue) => onBackPress,
|
||||
child: AnimatedPadding(
|
||||
duration: Duration(milliseconds: 300),
|
||||
padding: EdgeInsets.only(top: _isScrollingUp ? MediaQuery.of(context).viewPadding.top : 0),
|
||||
child: Column(children: [
|
||||
Expanded(
|
||||
flex: (Platform.isAndroid) ? 12 : 9,
|
||||
child: SingleChildScrollView(
|
||||
controller: !widget.isFromBreak && comEnabled && !isReply ? controller : null,
|
||||
child: Stack(children: <Widget>[
|
||||
ImageView(isFromBreak: widget.isFromBreak, model: newsModel, breakModel: widget.breakModel),
|
||||
backBtn(context, widget.fromShowMore),
|
||||
videoBtn(context: context, isFromBreak: widget.isFromBreak, model: !widget.isFromBreak ? newsModel! : null, breakModel: widget.isFromBreak ? widget.breakModel! : null),
|
||||
otherMainDetails(),
|
||||
if (!widget.isFromBreak) likeBtn(context, newsModel!),
|
||||
])),
|
||||
),
|
||||
if ((context.read<AppConfigurationCubit>().getInAppAdsMode() == "1") || _bannerAd != null) Flexible(child: setBannerAd(context, _bannerAd))
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
324
news-app/lib/ui/screens/NewsDetail/Widgets/ReplyCommentView.dart
Normal file
324
news-app/lib/ui/screens/NewsDetail/Widgets/ReplyCommentView.dart
Normal file
@@ -0,0 +1,324 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/data/models/CommentModel.dart';
|
||||
import 'package:news/ui/screens/NewsDetail/Widgets/delAndReportReplyComm.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/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/NewsComment/likeAndDislikeCommCubit.dart';
|
||||
import 'package:news/cubits/NewsComment/setCommentCubit.dart';
|
||||
import 'package:news/cubits/commentNewsCubit.dart';
|
||||
import 'package:news/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart';
|
||||
|
||||
class ReplyCommentView extends StatefulWidget {
|
||||
final int replyComIndex;
|
||||
final Function replyComFun;
|
||||
final String newsId;
|
||||
|
||||
const ReplyCommentView({super.key, required this.replyComIndex, required this.replyComFun, required this.newsId});
|
||||
|
||||
@override
|
||||
ReplyCommentViewState createState() => ReplyCommentViewState();
|
||||
}
|
||||
|
||||
class ReplyCommentViewState extends State<ReplyCommentView> {
|
||||
bool isReply = false, replyComEnabled = false, isSending = false;
|
||||
final TextEditingController _replyComC = TextEditingController();
|
||||
TextEditingController reportC = TextEditingController();
|
||||
|
||||
Widget allReplyComView(CommentModel model) {
|
||||
return Row(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Row(children: [
|
||||
CustomTextLabel(
|
||||
text: 'allLbl',
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6), fontSize: 12.0, fontWeight: FontWeight.w600)),
|
||||
CustomTextLabel(
|
||||
text: " ${model.replyComList!.length} ",
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6), fontSize: 12.0, fontWeight: FontWeight.w600)),
|
||||
CustomTextLabel(
|
||||
text: (model.replyComList!.length == 1) ? 'replyLbl' : 'repliesLbl',
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6), fontSize: 12.0, fontWeight: FontWeight.w600)),
|
||||
])),
|
||||
const Spacer(),
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: InkWell(
|
||||
child: const Icon(Icons.close),
|
||||
onTap: () {
|
||||
setState(() => isReply = false);
|
||||
widget.replyComFun(false, widget.replyComIndex);
|
||||
}))
|
||||
]);
|
||||
}
|
||||
|
||||
replyComProfileWithCom(CommentModel modelCom) {
|
||||
return BlocBuilder<LikeAndDislikeCommCubit, LikeAndDislikeCommState>(
|
||||
builder: (context, state) {
|
||||
DateTime replyTime = DateTime.parse(modelCom.date!);
|
||||
return Container(
|
||||
decoration: BoxDecoration(border: Border.all(width: 2, color: borderColor)),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
|
||||
modelCom.profile != null && modelCom.profile != ""
|
||||
? UiUtils.setFixedSizeboxForProfilePicture(childWidget: CircleAvatar(backgroundImage: NetworkImage(modelCom.profile!), radius: 32))
|
||||
: UiUtils.setFixedSizeboxForProfilePicture(childWidget: const Icon(Icons.account_circle, size: 35)),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 15.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CustomTextLabel(
|
||||
text: modelCom.name!,
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontSize: 13)),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 10.0),
|
||||
child: Icon(Icons.circle, size: 4.0, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 10.0),
|
||||
child: CustomTextLabel(
|
||||
text: UiUtils.convertToAgo(context, replyTime, 1)!,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontSize: 10)))
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: CustomTextLabel(
|
||||
text: modelCom.message!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.normal)),
|
||||
),
|
||||
],
|
||||
))),
|
||||
]));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
replyComSendReplyView(CommentModel model) {
|
||||
return context.read<AuthCubit>().getUserId() != "0"
|
||||
? Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 10.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: context.read<AuthCubit>().getProfile() != ""
|
||||
? UiUtils.setFixedSizeboxForProfilePicture(childWidget: CircleAvatar(backgroundImage: NetworkImage(context.read<AuthCubit>().getProfile()), radius: 32))
|
||||
: UiUtils.setFixedSizeboxForProfilePicture(childWidget: const Icon(Icons.account_circle, size: 35))),
|
||||
BlocListener<SetCommentCubit, SetCommentState>(
|
||||
bloc: context.read<SetCommentCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is SetCommentFetchSuccess) {
|
||||
context.read<CommentNewsCubit>().commentUpdateList(state.setComment, state.total);
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.unfocus();
|
||||
}
|
||||
_replyComC.clear();
|
||||
isSending = false;
|
||||
setState(() {});
|
||||
}
|
||||
if (state is SetCommentFetchInProgress) {
|
||||
setState(() => isSending = true);
|
||||
}
|
||||
},
|
||||
child: Expanded(
|
||||
flex: 7,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 18.0),
|
||||
child: TextField(
|
||||
controller: _replyComC,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)),
|
||||
onChanged: (String val) {
|
||||
if (_replyComC.text.trim().isNotEmpty) {
|
||||
setState(() => replyComEnabled = true);
|
||||
} else {
|
||||
setState(() => replyComEnabled = false);
|
||||
}
|
||||
},
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: null,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.only(top: 10.0, bottom: 2.0),
|
||||
isDense: true,
|
||||
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5), width: 1.5)),
|
||||
hintText: UiUtils.getTranslatedLabel(context, 'publicReply'),
|
||||
hintStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)),
|
||||
suffixIconConstraints: const BoxConstraints(maxHeight: 35, maxWidth: 30),
|
||||
suffixIcon: (_replyComC.text.trim().isNotEmpty)
|
||||
? (!isSending)
|
||||
? IconButton(
|
||||
icon: Icon(Icons.send, color: replyComEnabled ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8) : Colors.transparent, size: 20.0),
|
||||
onPressed: () async {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
context.read<SetCommentCubit>().setComment(parentId: model.id!, newsId: widget.newsId, message: _replyComC.text);
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
},
|
||||
)
|
||||
: SizedBox(height: 12, width: 12, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))
|
||||
: SizedBox.shrink()),
|
||||
))))
|
||||
],
|
||||
))
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
|
||||
replyAllComListView(CommentModel model) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
child: SingleChildScrollView(
|
||||
child: ListView.separated(
|
||||
separatorBuilder: (BuildContext context, int index) => Divider(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5)),
|
||||
shrinkWrap: true,
|
||||
reverse: true,
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: model.replyComList!.length,
|
||||
itemBuilder: (context, index) {
|
||||
DateTime time1 = DateTime.parse(model.replyComList![index].date!);
|
||||
return Builder(builder: (context) {
|
||||
return Row(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
|
||||
model.replyComList![index].profile != null && model.replyComList![index].profile != ""
|
||||
? UiUtils.setFixedSizeboxForProfilePicture(childWidget: CircleAvatar(backgroundImage: NetworkImage(model.replyComList![index].profile!), radius: 32))
|
||||
: UiUtils.setFixedSizeboxForProfilePicture(childWidget: const Icon(Icons.account_circle, size: 35)),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 15.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CustomTextLabel(
|
||||
text: model.replyComList![index].name!,
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontSize: 13)),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 10.0),
|
||||
child: Icon(Icons.circle, size: 4.0, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 10.0),
|
||||
child: CustomTextLabel(
|
||||
text: UiUtils.convertToAgo(context, time1, 1)!,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontSize: 10),
|
||||
)),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: CustomTextLabel(
|
||||
text: model.replyComList![index].message!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.normal),
|
||||
),
|
||||
),
|
||||
BlocBuilder<LikeAndDislikeCommCubit, LikeAndDislikeCommState>(builder: (context, state) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 15.0),
|
||||
child: Row(
|
||||
children: [
|
||||
InkWell(
|
||||
child: const Icon(Icons.thumb_up_off_alt_rounded),
|
||||
onTap: () {
|
||||
(context.read<AuthCubit>().getUserId() != "0")
|
||||
? context.read<LikeAndDislikeCommCubit>().setLikeAndDislikeComm(
|
||||
langId: context.read<AppLocalizationCubit>().state.id,
|
||||
commId: model.replyComList![index].id!,
|
||||
status: (model.replyComList![index].like == "1") ? "0" : "1",
|
||||
fromLike: true)
|
||||
: UiUtils.loginRequired(context);
|
||||
},
|
||||
),
|
||||
model.replyComList![index].totalLikes! != "0"
|
||||
? Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 4.0),
|
||||
child: CustomTextLabel(
|
||||
text: model.replyComList![index].totalLikes!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer)))
|
||||
: const SizedBox(width: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 35),
|
||||
child: InkWell(
|
||||
child: const Icon(Icons.thumb_down_alt_rounded),
|
||||
onTap: () {
|
||||
(context.read<AuthCubit>().getUserId() != "0")
|
||||
? context.read<LikeAndDislikeCommCubit>().setLikeAndDislikeComm(
|
||||
langId: context.read<AppLocalizationCubit>().state.id,
|
||||
commId: model.replyComList![index].id!,
|
||||
status: (model.replyComList![index].dislike == "1") ? "0" : "2",
|
||||
fromLike: false)
|
||||
: UiUtils.loginRequired(context);
|
||||
},
|
||||
)),
|
||||
model.replyComList![index].totalDislikes! != "0"
|
||||
? Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 4.0),
|
||||
child: CustomTextLabel(
|
||||
text: model.replyComList![index].totalDislikes!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer)))
|
||||
: const SizedBox.shrink(),
|
||||
const Spacer(),
|
||||
if (context.read<AuthCubit>().getUserId() != "0")
|
||||
InkWell(
|
||||
child: Icon(Icons.more_vert_outlined, color: UiUtils.getColorScheme(context).primaryContainer, size: 17),
|
||||
onTap: () => delAndReportReplyComm(model: model, context: context, reportC: reportC, newsId: widget.newsId, setState: setState, replyIndex: index))
|
||||
],
|
||||
),
|
||||
);
|
||||
})
|
||||
],
|
||||
))),
|
||||
]);
|
||||
});
|
||||
})));
|
||||
}
|
||||
|
||||
Widget replyCommentView() {
|
||||
return BlocBuilder<CommentNewsCubit, CommentNewsState>(builder: (context, state) {
|
||||
if (state is CommentNewsFetchSuccess && state.commentNews.isNotEmpty) {
|
||||
return BlocListener<LikeAndDislikeCommCubit, LikeAndDislikeCommState>(
|
||||
listener: (context, likeDislikeState) {
|
||||
if (likeDislikeState is LikeAndDislikeCommSuccess) {
|
||||
final defaultIndex = state.commentNews.indexWhere((element) => element.id == likeDislikeState.comment.id);
|
||||
if (defaultIndex != -1) {
|
||||
state.commentNews[defaultIndex].replyComList = likeDislikeState.comment.replyComList;
|
||||
context.read<CommentNewsCubit>().emitSuccessState(state.commentNews);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 10.0, bottom: 10.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
allReplyComView(state.commentNews[widget.replyComIndex]),
|
||||
replyComProfileWithCom(state.commentNews[widget.replyComIndex]),
|
||||
replyComSendReplyView(state.commentNews[widget.replyComIndex]),
|
||||
replyAllComListView(state.commentNews[widget.replyComIndex])
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
if (state is CommentNewsFetchFailure) {
|
||||
return Center(child: CustomTextLabel(text: state.errorMessage, textAlign: TextAlign.center));
|
||||
}
|
||||
//state is CommentNewsFetchInProgress || state is CommentNewsInitial
|
||||
return const Padding(padding: EdgeInsets.only(bottom: 10.0, left: 10.0, right: 10.0), child: SizedBox.shrink());
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(create: (context) => LikeAndDislikeCommCubit(LikeAndDislikeCommRepository()), child: replyCommentView());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
|
||||
const AdRequest request = AdRequest(
|
||||
//static
|
||||
keywords: <String>['foo', 'bar'],
|
||||
contentUrl: 'http://foo.com/bar.html',
|
||||
nonPersonalizedAds: true,
|
||||
);
|
||||
RewardedAd? rewardedAd;
|
||||
int _numRewardedLoadAttempts = 0;
|
||||
int maxFailedLoadAttempts = 3;
|
||||
|
||||
void createGoogleRewardedAd(BuildContext context) {
|
||||
if (context.read<AppConfigurationCubit>().rewardId() != "") {
|
||||
RewardedAd.load(
|
||||
adUnitId: context.read<AppConfigurationCubit>().rewardId()!,
|
||||
request: request,
|
||||
rewardedAdLoadCallback: RewardedAdLoadCallback(
|
||||
onAdLoaded: (RewardedAd ad) {
|
||||
rewardedAd = ad;
|
||||
_numRewardedLoadAttempts = 0;
|
||||
},
|
||||
onAdFailedToLoad: (LoadAdError error) {
|
||||
rewardedAd = null;
|
||||
_numRewardedLoadAttempts += 1;
|
||||
if (_numRewardedLoadAttempts <= maxFailedLoadAttempts) {
|
||||
createGoogleRewardedAd(context);
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void showGoogleRewardedAd(BuildContext context) {
|
||||
if (context.read<AppConfigurationCubit>().rewardId() != "") {
|
||||
if (rewardedAd == null) {
|
||||
return;
|
||||
}
|
||||
rewardedAd!.fullScreenContentCallback = FullScreenContentCallback(
|
||||
onAdShowedFullScreenContent: (RewardedAd ad) => debugPrint('ad onAdShowedFullScreenContent.'),
|
||||
onAdDismissedFullScreenContent: (RewardedAd ad) {
|
||||
ad.dispose();
|
||||
createGoogleRewardedAd(context);
|
||||
},
|
||||
onAdFailedToShowFullScreenContent: (RewardedAd ad, AdError error) {
|
||||
ad.dispose();
|
||||
createGoogleRewardedAd(context);
|
||||
},
|
||||
);
|
||||
|
||||
rewardedAd!.setImmersiveMode(true);
|
||||
rewardedAd!.show(onUserEarnedReward: (AdWithoutView ad, RewardItem reward) {});
|
||||
rewardedAd = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:unity_ads_plugin/unity_ads_plugin.dart';
|
||||
|
||||
void loadUnityRewardAd(String placementId) {
|
||||
UnityAds.load(placementId: placementId, onComplete: (placementId) {}, onFailed: (placementId, error, message) {});
|
||||
}
|
||||
|
||||
void showUnityRewardAds(String placementId) {
|
||||
UnityAds.showVideoAd(
|
||||
placementId: placementId,
|
||||
onComplete: (placementId) {
|
||||
loadUnityRewardAd(placementId);
|
||||
},
|
||||
onFailed: (placementId, error, message) {
|
||||
loadUnityRewardAd(placementId);
|
||||
},
|
||||
onStart: (placementId) => debugPrint('Video Ad $placementId started'),
|
||||
onClick: (placementId) => debugPrint('Video Ad $placementId click'),
|
||||
onSkipped: (placementId) {
|
||||
loadUnityRewardAd(placementId);
|
||||
},
|
||||
);
|
||||
}
|
||||
121
news-app/lib/ui/screens/NewsDetail/Widgets/ShowMoreNewsList.dart
Normal file
121
news-app/lib/ui/screens/NewsDetail/Widgets/ShowMoreNewsList.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/relatedNewsCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart';
|
||||
import 'package:news/ui/widgets/NewsItem.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/ui/widgets/shimmerNewsList.dart';
|
||||
|
||||
import 'package:news/ui/widgets/customAppBar.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class ShowMoreNewsList extends StatefulWidget {
|
||||
final NewsModel model;
|
||||
|
||||
const ShowMoreNewsList({super.key, required this.model});
|
||||
|
||||
@override
|
||||
ShowMoreNewsListState createState() => ShowMoreNewsListState();
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(builder: (_) => ShowMoreNewsList(model: arguments['model']));
|
||||
}
|
||||
}
|
||||
|
||||
class ShowMoreNewsListState extends State<ShowMoreNewsList> {
|
||||
late final ScrollController controller = ScrollController()..addListener(hasMoreRelatedNewsScrollListener);
|
||||
Set<String> get locationValue => SettingsLocalDataRepository().getLocationCityValues();
|
||||
|
||||
void hasMoreRelatedNewsScrollListener() {
|
||||
if (controller.position.maxScrollExtent == controller.offset) {
|
||||
if (context.read<RelatedNewsCubit>().hasMoreRelatedNews()) {
|
||||
context.read<RelatedNewsCubit>().getMoreRelatedNews(
|
||||
langId: context.read<AppLocalizationCubit>().state.id,
|
||||
catId: widget.model.subCatId == "0" || widget.model.subCatId == '' ? widget.model.categoryId : null,
|
||||
subCatId: widget.model.subCatId != "0" || widget.model.subCatId != '' ? widget.model.subCatId : null,
|
||||
latitude: locationValue.first,
|
||||
longitude: locationValue.last);
|
||||
} else {}
|
||||
}
|
||||
}
|
||||
|
||||
_buildRelatedNewsContainer(
|
||||
{required NewsModel model, required int index, required int totalCurrentRelatedNews, required bool hasMoreRelatedNewsFetchError, required bool hasMore, required List<NewsModel> newsList}) {
|
||||
if (index == totalCurrentRelatedNews - 1 && index != 0) {
|
||||
if (hasMore) {
|
||||
if (hasMoreRelatedNewsFetchError) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
context.read<RelatedNewsCubit>().getMoreRelatedNews(
|
||||
langId: context.read<AppLocalizationCubit>().state.id,
|
||||
catId: widget.model.subCatId == "0" || widget.model.subCatId == '' ? widget.model.categoryId : null,
|
||||
subCatId: widget.model.subCatId != "0" || widget.model.subCatId != '' ? widget.model.subCatId : 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 NewsItem(model: model, index: index, newslist: newsList, fromShowMore: true);
|
||||
}
|
||||
|
||||
void refreshNewsList() {
|
||||
context.read<RelatedNewsCubit>().getRelatedNews(
|
||||
langId: context.read<AppLocalizationCubit>().state.id,
|
||||
catId: widget.model.subCatId == "0" || widget.model.subCatId == '' ? widget.model.categoryId : null,
|
||||
subCatId: widget.model.subCatId != "0" || widget.model.subCatId != '' ? widget.model.subCatId : null);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: CustomAppBar(height: 45, isBackBtn: true, label: 'relatedNews', horizontalPad: 15, isConvertText: true),
|
||||
body: BlocBuilder<RelatedNewsCubit, RelatedNewsState>(
|
||||
builder: (context, state) {
|
||||
if (state is RelatedNewsFetchSuccess) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
refreshNewsList();
|
||||
},
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: state.relatedNews.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildRelatedNewsContainer(
|
||||
model: state.relatedNews[index],
|
||||
hasMore: state.hasMore,
|
||||
hasMoreRelatedNewsFetchError: state.hasMoreFetchError,
|
||||
index: index,
|
||||
totalCurrentRelatedNews: state.relatedNews.length,
|
||||
newsList: state.relatedNews);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (state is RelatedNewsFetchFailure) {
|
||||
return ErrorContainerWidget(
|
||||
errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: refreshNewsList);
|
||||
}
|
||||
//state is RelatedNewsInitial || state is RelatedNewsFetchInProgress
|
||||
return Padding(padding: const EdgeInsets.only(bottom: 10.0, left: 10.0, right: 10.0), child: ShimmerNewsList(isNews: true));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
24
news-app/lib/ui/screens/NewsDetail/Widgets/backBtn.dart
Normal file
24
news-app/lib/ui/screens/NewsDetail/Widgets/backBtn.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
|
||||
Widget backBtn(BuildContext context, bool fromShowMore) {
|
||||
return Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
top: 40,
|
||||
start: 20.0,
|
||||
child: InkWell(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(52.0),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: 39,
|
||||
width: 39,
|
||||
decoration: const BoxDecoration(color: secondaryColor, shape: BoxShape.circle),
|
||||
child: const Icon(Icons.keyboard_backspace_rounded, color: darkSecondaryColor)))),
|
||||
onTap: () {
|
||||
(fromShowMore == true) ? Navigator.of(context).popUntil((route) => route.isFirst) : Navigator.pop(context);
|
||||
}));
|
||||
}
|
||||
15
news-app/lib/ui/screens/NewsDetail/Widgets/dateView.dart
Normal file
15
news-app/lib/ui/screens/NewsDetail/Widgets/dateView.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';
|
||||
|
||||
Widget dateView(BuildContext context, String date) {
|
||||
return Row(
|
||||
children: [
|
||||
const Icon(Icons.access_time_filled_rounded, size: 15),
|
||||
const SizedBox(width: 3),
|
||||
CustomTextLabel(
|
||||
text: UiUtils.convertToAgo(context, DateTime.parse(date), 0)!,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 12.0, fontWeight: FontWeight.w600))
|
||||
],
|
||||
);
|
||||
}
|
||||
131
news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportCom.dart
Normal file
131
news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportCom.dart
Normal file
@@ -0,0 +1,131 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/NewsComment/deleteCommentCubit.dart';
|
||||
import 'package:news/cubits/NewsComment/flagCommentCubit.dart';
|
||||
import 'package:news/cubits/commentNewsCubit.dart';
|
||||
import 'package:news/data/models/CommentModel.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart';
|
||||
import 'package:news/ui/widgets/customTextBtn.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
|
||||
delAndReportCom(
|
||||
{required int index,
|
||||
required CommentModel model,
|
||||
required BuildContext context,
|
||||
required TextEditingController reportC,
|
||||
required String newsId,
|
||||
required StateSetter setState,
|
||||
Function? isReplyUpdate}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
elevation: 2.0,
|
||||
backgroundColor: UiUtils.getColorScheme(context).surface,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0))),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
if (context.read<AuthCubit>().getUserId() == model.userId!)
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
context.read<DeleteCommCubit>().setDeleteComm(commId: model.id!);
|
||||
}
|
||||
},
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
CustomTextLabel(
|
||||
text: 'deleteTxt',
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold)),
|
||||
const Spacer(),
|
||||
BlocConsumer<DeleteCommCubit, DeleteCommState>(
|
||||
bloc: context.read<DeleteCommCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is DeleteCommSuccess) {
|
||||
context.read<CommentNewsCubit>().deleteComment(index);
|
||||
showSnackBar(state.message, context);
|
||||
if (isReplyUpdate != null) {
|
||||
isReplyUpdate(false, index);
|
||||
}
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return SvgPictureWidget(assetName: "delete_icon", assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer, BlendMode.srcIn), height: 20, width: 20);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (context.read<AuthCubit>().getUserId() != model.userId!)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 15),
|
||||
child: Row(children: <Widget>[
|
||||
CustomTextLabel(
|
||||
text: 'reportTxt',
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold)),
|
||||
const Spacer(),
|
||||
SvgPictureWidget(assetName: "flag_icon", assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer, BlendMode.srcIn), height: 20, width: 20)
|
||||
])),
|
||||
if (context.read<AuthCubit>().getUserId() != model.userId!)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: TextField(
|
||||
controller: reportC,
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: null,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)),
|
||||
decoration: InputDecoration(
|
||||
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), width: 0.5)),
|
||||
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), width: 0.5)),
|
||||
))),
|
||||
if (context.read<AuthCubit>().getUserId() != model.userId!)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
CustomTextButton(
|
||||
onTap: () => Navigator.pop(context),
|
||||
textWidget: CustomTextLabel(
|
||||
text: 'cancelBtn',
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold))),
|
||||
BlocConsumer<SetFlagCubit, SetFlagState>(
|
||||
bloc: context.read<SetFlagCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is SetFlagFetchSuccess) {
|
||||
setState(() => reportC.text = "");
|
||||
showSnackBar(state.message, context);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return CustomTextButton(
|
||||
onTap: () {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
if (reportC.text.trim().isNotEmpty) {
|
||||
context.read<SetFlagCubit>().setFlag(commId: model.id!, newsId: newsId, message: reportC.text);
|
||||
} else {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'firstFillData'), context);
|
||||
}
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
},
|
||||
textWidget: CustomTextLabel(
|
||||
text: 'submitBtn',
|
||||
textStyle:
|
||||
Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold)));
|
||||
})
|
||||
],
|
||||
),
|
||||
],
|
||||
)));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/data/models/CommentModel.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart';
|
||||
import 'package:news/ui/widgets/customTextBtn.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/NewsComment/deleteCommentCubit.dart';
|
||||
import 'package:news/cubits/NewsComment/flagCommentCubit.dart';
|
||||
import 'package:news/cubits/commentNewsCubit.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
|
||||
delAndReportReplyComm(
|
||||
{required CommentModel model, required BuildContext context, required TextEditingController reportC, required String newsId, required StateSetter setState, required int replyIndex}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
elevation: 2.0,
|
||||
backgroundColor: UiUtils.getColorScheme(context).surface,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0))),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
if (context.read<AuthCubit>().getUserId() == model.replyComList![replyIndex].userId!)
|
||||
InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
onTap: () async {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
context.read<DeleteCommCubit>().setDeleteComm(commId: model.replyComList![replyIndex].id!);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
CustomTextLabel(
|
||||
text: 'deleteTxt',
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold)),
|
||||
const Spacer(),
|
||||
BlocConsumer<DeleteCommCubit, DeleteCommState>(
|
||||
bloc: context.read<DeleteCommCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is DeleteCommSuccess) {
|
||||
context.read<CommentNewsCubit>().deleteCommentReply(model.id!, replyIndex);
|
||||
showSnackBar(state.message, context);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return SvgPictureWidget(assetName: "delete_icon", assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer, BlendMode.srcIn), height: 20, width: 20);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (context.read<AuthCubit>().getUserId() != model.replyComList![replyIndex].userId!)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 15),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
CustomTextLabel(
|
||||
text: 'reportTxt',
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold)),
|
||||
const Spacer(),
|
||||
SvgPictureWidget(assetName: "flag_icon", assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer, BlendMode.srcIn), height: 20, width: 20)
|
||||
],
|
||||
)),
|
||||
if (context.read<AuthCubit>().getUserId() != model.replyComList![replyIndex].userId!)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: TextField(
|
||||
controller: reportC,
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: null,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)),
|
||||
decoration: InputDecoration(
|
||||
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), width: 0.5)),
|
||||
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), width: 0.5)),
|
||||
),
|
||||
)),
|
||||
if (context.read<AuthCubit>().getUserId() != model.replyComList![replyIndex].userId!)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
CustomTextButton(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
textWidget: CustomTextLabel(
|
||||
text: 'cancelBtn',
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold))),
|
||||
BlocConsumer<SetFlagCubit, SetFlagState>(
|
||||
bloc: context.read<SetFlagCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is SetFlagFetchSuccess) {
|
||||
setState(() {
|
||||
reportC.text = "";
|
||||
});
|
||||
|
||||
showSnackBar(state.message, context);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return CustomTextButton(
|
||||
onTap: () {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
if (reportC.text.trim().isNotEmpty) {
|
||||
context.read<SetFlagCubit>().setFlag(commId: model.replyComList![replyIndex].id!, newsId: newsId, message: reportC.text);
|
||||
} else {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'firstFillData'), context);
|
||||
}
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
},
|
||||
textWidget: CustomTextLabel(
|
||||
text: 'submitBtn',
|
||||
textStyle:
|
||||
Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold)));
|
||||
})
|
||||
],
|
||||
),
|
||||
],
|
||||
)));
|
||||
});
|
||||
}
|
||||
41
news-app/lib/ui/screens/NewsDetail/Widgets/descView.dart
Normal file
41
news-app/lib/ui/screens/NewsDetail/Widgets/descView.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
||||
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:news/ui/screens/NewsDetailsVideo.dart';
|
||||
|
||||
Widget descView({required String desc, required double fontValue, required BuildContext context}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 5.0),
|
||||
child: HtmlWidget(
|
||||
desc,
|
||||
onTapUrl: (String? url) async {
|
||||
if (await canLaunchUrl(Uri.parse(url!))) {
|
||||
await launchUrl(Uri.parse(url));
|
||||
return true;
|
||||
} else {
|
||||
throw 'Could not launch $url';
|
||||
}
|
||||
},
|
||||
onErrorBuilder: (context, element, error) => CustomTextLabel(text: '$element error: $error'),
|
||||
onLoadingBuilder: (context, element, loadingProgress) => UiUtils.showCircularProgress(true, Theme.of(context).primaryColor),
|
||||
renderMode: RenderMode.column,
|
||||
// set the default styling for text
|
||||
textStyle: TextStyle(fontSize: fontValue.toDouble()),
|
||||
customWidgetBuilder: (element) {
|
||||
if ((element.toString() == "<html iframe>") || (element.toString() == "<html video>")) {
|
||||
return FittedBox(
|
||||
fit: BoxFit.fill,
|
||||
child: Container(
|
||||
height: 220,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
color: Colors.transparent,
|
||||
child: (element.toString() == "<html iframe>") ? NewsDetailsVideo(src: element.attributes["src"], type: "1") : NewsDetailsVideo(type: "2", src: element.outerHtml)),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
));
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:html/parser.dart' show parse;
|
||||
import 'package:flutter_bloc/flutter_bloc.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/appSystemSettingCubit.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
Widget allRowBtn(
|
||||
{required bool isFromBreak,
|
||||
required BuildContext context,
|
||||
BreakingNewsModel? breakModel,
|
||||
NewsModel? model,
|
||||
required int fontVal,
|
||||
required Function updateFont,
|
||||
required bool isPlaying,
|
||||
required Function speak,
|
||||
required Function stop,
|
||||
required Function updateComEnabled}) {
|
||||
return !isFromBreak
|
||||
? Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 85),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(children: [
|
||||
if (context.read<AppConfigurationCubit>().getCommentsMode() == "1")
|
||||
InkWell(
|
||||
child: Column(children: [
|
||||
const Icon(Icons.insert_comment_rounded),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 4.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'comLbl',
|
||||
maxLines: 3,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 9.0)))
|
||||
]),
|
||||
onTap: () {
|
||||
if (model!.isCommentEnabled != null && model.isCommentEnabled == 0) {
|
||||
//comments disabled by Admin
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, "disabledCommentsMsg"), context);
|
||||
} else {
|
||||
updateComEnabled(true);
|
||||
}
|
||||
}),
|
||||
InkWell(
|
||||
child: setShareBtn(context),
|
||||
onTap: () async {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
UiUtils.shareNews(context: context, slug: model!.slug!, title: model.title!, isNews: true, isVideo: false, videoId: "", isBreakingNews: false);
|
||||
} else {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context);
|
||||
}
|
||||
},
|
||||
),
|
||||
BlocBuilder<BookmarkCubit, BookmarkState>(
|
||||
bloc: context.read<BookmarkCubit>(),
|
||||
builder: (context, bookmarkState) {
|
||||
bool isBookmark = context.read<BookmarkCubit>().isNewsBookmark(model!.newsId!);
|
||||
return BlocConsumer<UpdateBookmarkStatusCubit, UpdateBookmarkStatusState>(
|
||||
bloc: context.read<UpdateBookmarkStatusCubit>(),
|
||||
listener: ((context, state) {
|
||||
if (state is UpdateBookmarkStatusSuccess) {
|
||||
if (state.wasBookmarkNewsProcess) {
|
||||
context.read<BookmarkCubit>().addBookmarkNews(state.news);
|
||||
} else {
|
||||
context.read<BookmarkCubit>().removeBookmarkNews(state.news);
|
||||
}
|
||||
}
|
||||
isBookmark = context.read<BookmarkCubit>().isNewsBookmark(model.newsId!);
|
||||
}),
|
||||
builder: (context, state) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
if (state is UpdateBookmarkStatusInProgress) {
|
||||
return;
|
||||
}
|
||||
context.read<UpdateBookmarkStatusCubit>().setBookmarkNews(news: model, status: (isBookmark) ? "0" : "1");
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
},
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.13,
|
||||
child: Column(children: [
|
||||
state is UpdateBookmarkStatusInProgress
|
||||
? SizedBox(height: 24, width: 24, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))
|
||||
: Icon(isBookmark ? Icons.bookmark_added_rounded : Icons.bookmark_add_outlined),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 4.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'saveLbl',
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 9.0)))
|
||||
]),
|
||||
));
|
||||
});
|
||||
}),
|
||||
InkWell(
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.13,
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const Icon(Icons.text_fields_rounded),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 4.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'txtSizeLbl',
|
||||
maxLines: 3,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 9.0)))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
changeFontSizeSheet(context, fontVal, updateFont);
|
||||
},
|
||||
),
|
||||
InkWell(
|
||||
child: setSpeakBtn(context, isPlaying),
|
||||
onTap: () {
|
||||
if (isPlaying) {
|
||||
stop();
|
||||
} else {
|
||||
final document = parse("${model!.title}\n${model.desc}"); //Speak Title along with Description
|
||||
String parsedString = parse(document.body!.text).documentElement!.text;
|
||||
speak(parsedString);
|
||||
}
|
||||
})
|
||||
]),
|
||||
))
|
||||
: Row(children: [
|
||||
InkWell(
|
||||
child: setTextSize(context),
|
||||
onTap: () {
|
||||
changeFontSizeSheet(context, fontVal, updateFont);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 8.0),
|
||||
child: InkWell(
|
||||
child: setSpeakBtn(context, isPlaying),
|
||||
onTap: () {
|
||||
if (isPlaying) {
|
||||
stop();
|
||||
} else {
|
||||
final document = parse("${breakModel!.title}\n${breakModel.desc}"); //Speak Title along with Description
|
||||
String parsedString = parse(document.body!.text).documentElement!.text;
|
||||
speak(parsedString);
|
||||
}
|
||||
})),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 8.0),
|
||||
child: InkWell(
|
||||
child: setShareBtn(context),
|
||||
onTap: () async {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
UiUtils.shareNews(context: context, slug: breakModel!.slug!, title: breakModel.title!, isVideo: false, videoId: "", isBreakingNews: true, isNews: false);
|
||||
} else {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context);
|
||||
}
|
||||
}))
|
||||
]);
|
||||
}
|
||||
|
||||
changeFontSizeSheet(BuildContext context, int fontValue, Function updateFun) {
|
||||
showModalBottomSheet<dynamic>(
|
||||
context: context,
|
||||
elevation: 5.0,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(50), topRight: Radius.circular(50))),
|
||||
builder: (BuildContext context) {
|
||||
return StatefulBuilder(builder: (BuildContext context, setStater) {
|
||||
return Container(
|
||||
padding: const EdgeInsetsDirectional.only(bottom: 20.0, top: 5.0, start: 20.0, end: 20.0),
|
||||
decoration: const BoxDecoration(borderRadius: BorderRadius.only(topLeft: Radius.circular(50), topRight: Radius.circular(50))),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 30),
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
const Icon(Icons.text_fields_rounded),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: CustomTextLabel(
|
||||
text: 'txtSizeLbl',
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
)),
|
||||
CustomTextLabel(text: "( $fontValue )", textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
])),
|
||||
SliderTheme(
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
activeTrackColor: Colors.red[700],
|
||||
inactiveTrackColor: Colors.red[100],
|
||||
trackShape: const RoundedRectSliderTrackShape(),
|
||||
trackHeight: 4.0,
|
||||
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8.0),
|
||||
thumbColor: Colors.redAccent,
|
||||
overlayColor: Colors.red.withAlpha(32),
|
||||
overlayShape: const RoundSliderOverlayShape(overlayRadius: 28.0),
|
||||
tickMarkShape: const RoundSliderTickMarkShape(),
|
||||
activeTickMarkColor: Colors.red[700],
|
||||
inactiveTickMarkColor: Colors.red[100],
|
||||
valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
|
||||
valueIndicatorColor: Colors.redAccent,
|
||||
valueIndicatorTextStyle: const TextStyle(color: Colors.white)),
|
||||
child: Slider(
|
||||
label: '$fontValue',
|
||||
value: fontValue.toDouble(),
|
||||
activeColor: Theme.of(context).primaryColor,
|
||||
min: 15,
|
||||
max: 40,
|
||||
divisions: 10,
|
||||
onChanged: (value) {
|
||||
setStater(() {
|
||||
fontValue = value.round();
|
||||
updateFun(value.round());
|
||||
});
|
||||
}))
|
||||
],
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setShareBtn(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.13,
|
||||
child: Column(
|
||||
children: [
|
||||
const Icon(Icons.share_rounded),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 4.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'shareLbl',
|
||||
maxLines: 3,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 9.0),
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
setSpeakBtn(BuildContext context, bool isPlaying) {
|
||||
return SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.13,
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(Icons.speaker_phone_rounded, color: isPlaying ? Theme.of(context).primaryColor : UiUtils.getColorScheme(context).primaryContainer),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 4.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'speakLoudLbl',
|
||||
maxLines: 3,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: isPlaying ? Theme.of(context).primaryColor : UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 9.0)))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
setTextSize(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const Icon(Icons.text_fields_rounded),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 4.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'txtSizeLbl', maxLines: 2, textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 9.0)))
|
||||
],
|
||||
);
|
||||
}
|
||||
89
news-app/lib/ui/screens/NewsDetail/Widgets/likeBtn.dart
Normal file
89
news-app/lib/ui/screens/NewsDetail/Widgets/likeBtn.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.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/models/NewsModel.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
Widget likeBtn(BuildContext context, NewsModel model) {
|
||||
bool isLike = context.read<LikeAndDisLikeCubit>().isNewsLikeAndDisLike(model.newsId!);
|
||||
return BlocConsumer<LikeAndDisLikeCubit, LikeAndDisLikeState>(
|
||||
bloc: context.read<LikeAndDisLikeCubit>(),
|
||||
listener: ((context, state) {
|
||||
if (state is LikeAndDisLikeFetchSuccess) {
|
||||
isLike = context.read<LikeAndDisLikeCubit>().isNewsLikeAndDisLike(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);
|
||||
model.totalLikes = (!isLike)
|
||||
? (int.parse(model.totalLikes.toString()) + 1).toString()
|
||||
: (model.totalLikes!.isNotEmpty)
|
||||
? (int.parse(model.totalLikes.toString()) - 1).toString()
|
||||
: "0";
|
||||
}
|
||||
}),
|
||||
builder: (context, state) {
|
||||
isLike = context.read<LikeAndDisLikeCubit>().isNewsLikeAndDisLike(model.newsId!);
|
||||
return Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
top: MediaQuery.of(context).size.height / 2.90,
|
||||
end: MediaQuery.of(context).size.width / 10.8,
|
||||
child: Column(
|
||||
children: [
|
||||
InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
onTap: () {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
if (state is UpdateLikeAndDisLikeStatusInProgress) {
|
||||
return;
|
||||
}
|
||||
context.read<UpdateLikeAndDisLikeStatusCubit>().setLikeAndDisLikeNews(news: model, status: (isLike) ? "0" : "1");
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
},
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(52.0),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: 39,
|
||||
width: 39,
|
||||
decoration: BoxDecoration(boxShadow: [
|
||||
BoxShadow(blurRadius: 6, offset: const Offset(5.0, 5.0), color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.4), spreadRadius: 0),
|
||||
], color: secondaryColor, shape: BoxShape.circle),
|
||||
child: (state is UpdateLikeAndDisLikeStatusInProgress)
|
||||
? SizedBox(height: 20, width: 20, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))
|
||||
: isLike
|
||||
? const Icon(Icons.thumb_up_alt, color: darkSecondaryColor)
|
||||
: const Icon(Icons.thumb_up_off_alt, color: darkSecondaryColor),
|
||||
))),
|
||||
),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 7.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 5.0),
|
||||
child: CustomTextLabel(
|
||||
text: (int.tryParse(model.totalLikes!)! > 0) ? "${model.totalLikes!} ${UiUtils.getTranslatedLabel(context, 'likeLbl')}" : "",
|
||||
maxLines: 2,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
166
news-app/lib/ui/screens/NewsDetail/Widgets/relatedNewsList.dart
Normal file
166
news-app/lib/ui/screens/NewsDetail/Widgets/relatedNewsList.dart
Normal file
@@ -0,0 +1,166 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/relatedNewsCubit.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
|
||||
class RelatedNewsList extends StatefulWidget {
|
||||
final NewsModel model;
|
||||
|
||||
const RelatedNewsList({super.key, required this.model});
|
||||
|
||||
@override
|
||||
RelatedNewsListState createState() => RelatedNewsListState();
|
||||
}
|
||||
|
||||
class RelatedNewsListState extends State<RelatedNewsList> {
|
||||
int sliderIndex = 0;
|
||||
List<NewsModel> relatedList = [];
|
||||
|
||||
Widget getRelatedList() {
|
||||
return BlocConsumer<RelatedNewsCubit, RelatedNewsState>(
|
||||
bloc: context.read<RelatedNewsCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is RelatedNewsFetchSuccess) {
|
||||
setState(() {
|
||||
relatedList.clear();
|
||||
relatedList.addAll(state.relatedNews);
|
||||
relatedList.removeWhere((element) => (element.id == widget.model.id!) || (element.id == widget.model.newsId!));
|
||||
});
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is RelatedNewsFetchSuccess && relatedList.isNotEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 15.0, bottom: 15.0),
|
||||
child: Column(children: [
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(bottom: 15.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'relatedNews',
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600)))),
|
||||
showRelatedNews(relatedList)
|
||||
]));
|
||||
}
|
||||
return const SizedBox.shrink(); //state is RelatedNewsFetchInProgress || state is RelatedNewsInitial || state is RelatedNewsFetchFailure
|
||||
});
|
||||
}
|
||||
|
||||
Widget relatedNewsData(NewsModel model, List<NewsModel> newsList) {
|
||||
return Stack(children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(15.0), topRight: Radius.circular(15.0)),
|
||||
child: ShaderMask(
|
||||
shaderCallback: (rect) => const LinearGradient(begin: Alignment.center, end: Alignment.bottomCenter, colors: [Colors.transparent, secondaryColor]).createShader(rect),
|
||||
blendMode: BlendMode.darken,
|
||||
child: GestureDetector(
|
||||
child: CustomNetworkImage(networkImageUrl: model.image!, width: double.maxFinite, height: MediaQuery.of(context).size.height / 2.9, isVideo: false, fit: BoxFit.cover),
|
||||
onTap: () {
|
||||
List<NewsModel> addNewsList = [];
|
||||
addNewsList.addAll(newsList);
|
||||
addNewsList.remove(model);
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": model, "newsList": addNewsList, "isFromBreak": false, "fromShowMore": false});
|
||||
}))),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
padding: EdgeInsetsDirectional.only(bottom: MediaQuery.of(context).size.height / 18.9, start: MediaQuery.of(context).size.width / 20.0, end: 5.0),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: CustomTextLabel(
|
||||
text: model.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: secondaryColor, fontWeight: FontWeight.normal, fontSize: 12.5, height: 1.0),
|
||||
maxLines: 1,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis)))
|
||||
]);
|
||||
}
|
||||
|
||||
List<T> map<T>(List list, Function handler) {
|
||||
List<T> result = [];
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
result.add(handler(i, list[i]));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
CustomTextLabel setCoverageText(BuildContext context) {
|
||||
return CustomTextLabel(
|
||||
text: 'viewFullCoverage',
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.9), fontWeight: FontWeight.w600),
|
||||
);
|
||||
}
|
||||
|
||||
Icon setCoverageIcon(BuildContext context) {
|
||||
return Icon(Icons.image, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.9));
|
||||
}
|
||||
|
||||
Widget showRelatedNews(List<NewsModel> relatedNews) {
|
||||
return Column(children: [
|
||||
Stack(children: [
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height / 2.9,
|
||||
width: double.infinity,
|
||||
child: PageView.builder(
|
||||
itemCount: relatedNews.length,
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
onPageChanged: (index) {
|
||||
setState(() {
|
||||
sliderIndex = index;
|
||||
});
|
||||
},
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return relatedNewsData(relatedNews[index], relatedNews);
|
||||
})),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: EdgeInsetsDirectional.only(top: MediaQuery.of(context).size.height / 3.3),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: map<Widget>(relatedNews, (index, url) {
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
width: sliderIndex == index ? MediaQuery.of(context).size.width / 15.0 : MediaQuery.of(context).size.width / 15.0,
|
||||
height: 5.0,
|
||||
margin: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 2.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
color: sliderIndex == index ? Theme.of(context).primaryColor : darkBackgroundColor.withOpacity(0.5),
|
||||
));
|
||||
}))))
|
||||
]),
|
||||
Container(
|
||||
height: 38.0,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(15.0), bottomRight: Radius.circular(15.0)),
|
||||
color: UiUtils.getColorScheme(context).surface,
|
||||
boxShadow: [BoxShadow(color: borderColor.withOpacity(0.4), offset: const Offset(0.0, 2.0), blurRadius: 6.0, spreadRadius: 0)]),
|
||||
child: ElevatedButton.icon(
|
||||
icon: setCoverageIcon(context),
|
||||
label: setCoverageText(context),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(Routes.showMoreRelatedNews, arguments: {"model": widget.model});
|
||||
},
|
||||
style: ElevatedButton.styleFrom(foregroundColor: Theme.of(context).primaryColor, backgroundColor: Colors.transparent, shadowColor: Colors.transparent),
|
||||
))
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return getRelatedList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||
import 'package:unity_ads_plugin/unity_ads_plugin.dart' as unity;
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
|
||||
setBannerAd(BuildContext context, BannerAd? bannerAd) {
|
||||
if (context.read<AppConfigurationCubit>().bannerId() != "") {
|
||||
switch (context.read<AppConfigurationCubit>().checkAdsType()) {
|
||||
case "google":
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 5.0, end: 5.0),
|
||||
child: SizedBox(width: double.maxFinite, height: bannerAd!.size.height.toDouble(), child: AdWidget(ad: bannerAd)),
|
||||
);
|
||||
|
||||
case "unity":
|
||||
return unity.UnityBannerAd(
|
||||
placementId: context.read<AppConfigurationCubit>().bannerId()!,
|
||||
onLoad: (placementId) => debugPrint('Banner loaded: $placementId'),
|
||||
onClick: (placementId) => debugPrint('Banner clicked: $placementId'),
|
||||
onFailed: (placementId, error, message) => debugPrint('Banner Ad $placementId failed: $error $message'),
|
||||
);
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
}
|
||||
63
news-app/lib/ui/screens/NewsDetail/Widgets/tagView.dart
Normal file
63
news-app/lib/ui/screens/NewsDetail/Widgets/tagView.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
Widget tagView({required NewsModel model, required BuildContext context, bool? isFromDetailsScreen = false}) {
|
||||
List<String> tagList = [];
|
||||
|
||||
if (model.tagName! != "") {
|
||||
final tagName = model.tagName!;
|
||||
tagList = tagName.split(',');
|
||||
}
|
||||
|
||||
List<String> tagId = [];
|
||||
|
||||
if (model.tagId != null && model.tagId! != "") {
|
||||
tagId = model.tagId!.split(",");
|
||||
}
|
||||
|
||||
return model.tagName! != ""
|
||||
? Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 15.0),
|
||||
child: SizedBox(
|
||||
height: 20.0,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: List.generate(tagList.length, (index) {
|
||||
return Padding(
|
||||
padding: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 7),
|
||||
child: InkWell(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(3.0),
|
||||
child: (isFromDetailsScreen != null && isFromDetailsScreen)
|
||||
? tagsContainer(context: context, tagList: tagList, index: index)
|
||||
: BackdropFilter(filter: ImageFilter.blur(sigmaX: 30, sigmaY: 30), child: tagsContainer(context: context, tagList: tagList, index: index))),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.tagScreen, arguments: {"tagId": tagId[index], "tagName": tagList[index]});
|
||||
},
|
||||
));
|
||||
}),
|
||||
),
|
||||
)))
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
|
||||
Widget tagsContainer({required BuildContext context, required List<String> tagList, required int index}) {
|
||||
return Container(
|
||||
height: 20.0,
|
||||
width: 65,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsetsDirectional.only(start: 3.0, end: 3.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(10.0), bottomRight: Radius.circular(10.0)), color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)),
|
||||
child: CustomTextLabel(
|
||||
text: tagList[index],
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).secondary, fontSize: 11),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true));
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
Widget titleView({required String title, required BuildContext context}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 6.0),
|
||||
child: CustomTextLabel(text: title, textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600)));
|
||||
}
|
||||
24
news-app/lib/ui/screens/NewsDetail/Widgets/videoBtn.dart
Normal file
24
news-app/lib/ui/screens/NewsDetail/Widgets/videoBtn.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
|
||||
Widget videoBtn({required BuildContext context, required bool isFromBreak, NewsModel? model, BreakingNewsModel? breakModel}) {
|
||||
if ((breakModel != null && breakModel.contentValue != "") || model != null && model.contentValue != "") {
|
||||
return Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
top: 35,
|
||||
end: 20.0,
|
||||
child: InkWell(
|
||||
child:
|
||||
Container(height: 39, width: 39, decoration: const BoxDecoration(color: secondaryColor, shape: BoxShape.circle), child: const Icon(Icons.play_arrow_rounded, color: darkSecondaryColor)),
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.newsVideo, arguments: (!isFromBreak) ? {"from": 1, "model": model} : {"from": 3, "breakModel": breakModel, "otherVideos": [], "otherBreakingVideos": []});
|
||||
},
|
||||
));
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
75
news-app/lib/ui/screens/NewsDetailsVideo.dart
Normal file
75
news-app/lib/ui/screens/NewsDetailsVideo.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
|
||||
class NewsDetailsVideo extends StatefulWidget {
|
||||
String? src;
|
||||
String type;
|
||||
|
||||
NewsDetailsVideo({super.key, this.src, required this.type});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => StateNewsDetailsVideo();
|
||||
}
|
||||
|
||||
class StateNewsDetailsVideo extends State<NewsDetailsVideo> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
bool _isNetworkAvail = true;
|
||||
var iframe;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
checkNetwork();
|
||||
if ((widget.type == "1") || (widget.type == "3")) {
|
||||
iframe = '''
|
||||
<html>
|
||||
<iframe src="${widget.src!}" width="100%" height="100%" allowfullscreen="allowfullscreen" frame-options="sameorigin"></iframe>
|
||||
</html>
|
||||
''';
|
||||
} else {
|
||||
iframe = '''
|
||||
<html>
|
||||
<video controls="controls" width="100%" height="100%">
|
||||
<source src="${widget.src!}"></video>
|
||||
</html>
|
||||
''';
|
||||
}
|
||||
}
|
||||
|
||||
checkNetwork() async {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
setState(() {
|
||||
_isNetworkAvail = true;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_isNetworkAvail = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// set screen back to portrait mode
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// set screen to landscape mode bydefault
|
||||
return SafeArea(child: Scaffold(key: _scaffoldKey, body: _isNetworkAvail ? viewVideo() : const SizedBox.shrink()));
|
||||
}
|
||||
|
||||
//news video link set
|
||||
viewVideo() {
|
||||
WebUri frm;
|
||||
frm = WebUri.uri(Uri.dataFromString(iframe, mimeType: 'text/html'));
|
||||
return Center(
|
||||
child: InAppWebView(initialUrlRequest: URLRequest(url: frm)),
|
||||
);
|
||||
}
|
||||
}
|
||||
185
news-app/lib/ui/screens/NewsVideo.dart
Normal file
185
news-app/lib/ui/screens/NewsVideo.dart
Normal file
@@ -0,0 +1,185 @@
|
||||
import 'package:flick_video_player/flick_video_player.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:youtube_parser/youtube_parser.dart';
|
||||
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:news/ui/screens/NewsDetailsVideo.dart';
|
||||
import 'package:news/data/models/LiveStreamingModel.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
|
||||
class NewsVideo extends StatefulWidget {
|
||||
int from;
|
||||
LiveStreamingModel? liveModel;
|
||||
NewsModel? model;
|
||||
BreakingNewsModel? breakModel;
|
||||
|
||||
NewsVideo({super.key, this.model, required this.from, this.liveModel, this.breakModel});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => StateVideo();
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(builder: (_) => NewsVideo(from: arguments['from'], liveModel: arguments['liveModel'], model: arguments['model'], breakModel: arguments['breakModel']));
|
||||
}
|
||||
}
|
||||
|
||||
class StateVideo extends State<NewsVideo> {
|
||||
FlickManager? flickManager;
|
||||
YoutubePlayerController? _yc;
|
||||
bool _isNetworkAvail = true;
|
||||
|
||||
VideoPlayerController? _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
checkNetwork();
|
||||
initialisePlayer();
|
||||
}
|
||||
|
||||
initialisePlayer() {
|
||||
switch (widget.from) {
|
||||
case 1:
|
||||
if (widget.model!.contentValue != "" || widget.model!.contentValue != null) {
|
||||
if (widget.model!.contentType == "video_upload") {
|
||||
_controller = VideoPlayerController.networkUrl(Uri.parse(widget.model!.contentValue!));
|
||||
flickManager = FlickManager(videoPlayerController: _controller!, autoPlay: true);
|
||||
} else if (widget.model!.contentType == "video_youtube") {
|
||||
_yc = YoutubePlayerController(initialVideoId: YoutubePlayer.convertUrlToId(widget.model!.contentValue!) ?? "", flags: const YoutubePlayerFlags(autoPlay: true));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (widget.liveModel!.type == "url_youtube") {
|
||||
_yc = YoutubePlayerController(initialVideoId: getIdFromUrl(widget.liveModel!.url!) ?? "", flags: const YoutubePlayerFlags(autoPlay: true, isLive: true));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (widget.breakModel!.contentValue != "" || widget.breakModel!.contentValue != null) {
|
||||
if (widget.breakModel!.contentType == "video_upload") {
|
||||
_controller = VideoPlayerController.networkUrl(Uri.parse(widget.breakModel!.contentValue!));
|
||||
flickManager = FlickManager(videoPlayerController: _controller!, autoPlay: true);
|
||||
} else if (widget.breakModel!.contentType == "video_youtube") {
|
||||
_yc = YoutubePlayerController(initialVideoId: YoutubePlayer.convertUrlToId(widget.breakModel!.contentValue!) ?? "", flags: const YoutubePlayerFlags(autoPlay: true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkNetwork() async {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
setState(() => _isNetworkAvail = true);
|
||||
} else {
|
||||
setState(() => _isNetworkAvail = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values);
|
||||
if (_controller != null && _controller!.value.isPlaying) _controller!.pause();
|
||||
switch (widget.from) {
|
||||
case 1:
|
||||
if (widget.model!.contentType == "video_upload") {
|
||||
Future.delayed(const Duration(milliseconds: 10)).then((value) {
|
||||
flickManager!.flickControlManager!.exitFullscreen();
|
||||
flickManager!.dispose();
|
||||
_controller!.dispose();
|
||||
_controller = null;
|
||||
flickManager = null;
|
||||
});
|
||||
} else if (widget.model!.contentType == "video_youtube") {
|
||||
_yc!.dispose();
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (widget.liveModel!.type == "url_youtube") {
|
||||
_yc!.dispose();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (widget.breakModel!.contentType == "video_upload") {
|
||||
_controller = null;
|
||||
flickManager!.dispose();
|
||||
} else if (widget.breakModel!.contentType == "video_youtube") {
|
||||
_yc!.dispose();
|
||||
}
|
||||
}
|
||||
Future.delayed(const Duration(milliseconds: 20)).then((value) {
|
||||
super.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
extendBodyBehindAppBar: true, //to show Landscape video fullscreen
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
leading: InkWell(
|
||||
onTap: () {
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 20),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(22.0),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(color: UiUtils.getColorScheme(context).primaryContainer, shape: BoxShape.circle),
|
||||
child: Icon(Icons.keyboard_backspace_rounded, color: UiUtils.getColorScheme(context).surface)))),
|
||||
)),
|
||||
body: PopScope(
|
||||
canPop: true,
|
||||
onPopInvoked: (val) async {
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
if (widget.from == 1 && (widget.model!.contentType == "video_upload")) {
|
||||
_controller!.pause();
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 10)).then((value) {
|
||||
flickManager!.flickControlManager!.exitFullscreen();
|
||||
flickManager!.dispose();
|
||||
_controller!.dispose();
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 15.0, end: 15.0, bottom: 5.0),
|
||||
child: _isNetworkAvail
|
||||
? Container(alignment: Alignment.center, decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0)), child: viewVideo())
|
||||
: const Center(child: CustomTextLabel(text: 'internetmsg'))),
|
||||
));
|
||||
}
|
||||
|
||||
viewVideo() {
|
||||
return widget.from == 1
|
||||
? widget.model!.contentType == "video_upload"
|
||||
? FlickVideoPlayer(flickManager: flickManager!, flickVideoWithControlsFullscreen: const FlickVideoWithControls(videoFit: BoxFit.fitWidth))
|
||||
: widget.model!.contentType == "video_youtube"
|
||||
? YoutubePlayer(controller: _yc!, showVideoProgressIndicator: true, progressIndicatorColor: Theme.of(context).primaryColor)
|
||||
: widget.model!.contentType == "video_other"
|
||||
? Center(child: NewsDetailsVideo(src: widget.model!.contentValue, type: "3"))
|
||||
: const SizedBox.shrink()
|
||||
: widget.from == 2
|
||||
? widget.liveModel!.type == "url_youtube"
|
||||
? YoutubePlayer(controller: _yc!, showVideoProgressIndicator: true, progressIndicatorColor: Theme.of(context).primaryColor)
|
||||
: Center(child: NewsDetailsVideo(src: widget.liveModel!.url, type: "3"))
|
||||
: widget.breakModel!.contentType == "video_upload"
|
||||
? FlickVideoPlayer(flickManager: flickManager!)
|
||||
: widget.breakModel!.contentType == "video_youtube"
|
||||
? YoutubePlayer(controller: _yc!, showVideoProgressIndicator: true, progressIndicatorColor: Theme.of(context).primaryColor)
|
||||
: widget.breakModel!.contentType == "video_other"
|
||||
? Center(child: NewsDetailsVideo(src: widget.breakModel!.contentValue, type: "3"))
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
55
news-app/lib/ui/screens/PrivacyPolicyScreen.dart
Normal file
55
news-app/lib/ui/screens/PrivacyPolicyScreen.dart
Normal file
@@ -0,0 +1,55 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
||||
import 'package:news/ui/widgets/customAppBar.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class PrivacyPolicy extends StatefulWidget {
|
||||
final String? title;
|
||||
final String? from;
|
||||
final String? desc;
|
||||
|
||||
const PrivacyPolicy({super.key, this.title, this.from, this.desc});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return StatePrivacy();
|
||||
}
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(
|
||||
builder: (_) => PrivacyPolicy(
|
||||
from: arguments['from'],
|
||||
title: arguments['title'],
|
||||
desc: arguments['desc'],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class StatePrivacy extends State<PrivacyPolicy> with TickerProviderStateMixin {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: CustomAppBar(height: 45, isBackBtn: true, label: widget.title!, horizontalPad: 15, isConvertText: false),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsetsDirectional.only(start: 20.0, end: 20.0, top: 10.0),
|
||||
child: HtmlWidget(
|
||||
widget.desc!,
|
||||
onTapUrl: (String? url) async {
|
||||
if (await canLaunchUrl(Uri.parse(url!))) {
|
||||
await launchUrl(Uri.parse(url));
|
||||
return true;
|
||||
} else {
|
||||
throw 'Could not launch $url';
|
||||
}
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
555
news-app/lib/ui/screens/Profile/ProfileScreen.dart
Normal file
555
news-app/lib/ui/screens/Profile/ProfileScreen.dart
Normal file
@@ -0,0 +1,555 @@
|
||||
import 'dart:io';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:in_app_review/in_app_review.dart';
|
||||
import 'package:news/app/app.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/Auth/deleteUserCubit.dart';
|
||||
import 'package:news/cubits/Auth/registerTokenCubit.dart';
|
||||
import 'package:news/cubits/Author/authorCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/cubits/languageJsonCubit.dart';
|
||||
import 'package:news/cubits/otherPagesCubit.dart';
|
||||
import 'package:news/cubits/settingCubit.dart';
|
||||
import 'package:news/data/repositories/Auth/authLocalDataSource.dart';
|
||||
import 'package:news/ui/screens/Profile/Widgets/customAlertDialog.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/customAppBar.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/constant.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:news/cubits/themeCubit.dart';
|
||||
import 'package:news/ui/styles/appTheme.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
|
||||
class ProfileScreen extends StatefulWidget {
|
||||
const ProfileScreen({super.key});
|
||||
|
||||
@override
|
||||
ProfileScreenState createState() => ProfileScreenState();
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
return CupertinoPageRoute(builder: (_) => const ProfileScreen());
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileScreenState extends State<ProfileScreen> {
|
||||
File? image;
|
||||
String? name, mobile, email, profile;
|
||||
TextEditingController? nameC, monoC, emailC = TextEditingController();
|
||||
AuthLocalDataSource authLocalDataSource = AuthLocalDataSource();
|
||||
bool isEditMono = false, isEditEmail = false, isAuthor = false;
|
||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
final InAppReview _inAppReview = InAppReview.instance;
|
||||
late AuthorStatus authorStatus;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
getOtherPagesData();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
getOtherPagesData() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<OtherPageCubit>().getOtherPage(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
});
|
||||
}
|
||||
|
||||
Widget pagesBuild() {
|
||||
return BlocBuilder<OtherPageCubit, OtherPageState>(builder: (context, state) {
|
||||
if (state is OtherPageFetchSuccess) {
|
||||
return ScrollConfiguration(
|
||||
behavior: GlobalScrollBehavior(),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: state.otherPage.length,
|
||||
itemBuilder: ((context, index) =>
|
||||
setDrawerItem(state.otherPage[index].title!, Icons.info_rounded, false, true, false, 8, image: state.otherPage[index].image!, desc: state.otherPage[index].pageContent))),
|
||||
);
|
||||
} else {
|
||||
//state is OtherPageFetchInProgress || state is OtherPageInitial || state is OtherPageFetchFailure
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
switchTheme(bool value) async {
|
||||
if (value) {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);
|
||||
context.read<ThemeCubit>().changeTheme(AppTheme.Dark);
|
||||
UiUtils.setUIOverlayStyle(appTheme: AppTheme.Dark);
|
||||
//for non-appbar screens
|
||||
} else {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context);
|
||||
}
|
||||
} else {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);
|
||||
context.read<ThemeCubit>().changeTheme(AppTheme.Light);
|
||||
UiUtils.setUIOverlayStyle(appTheme: AppTheme.Light);
|
||||
//for non-appbar screens
|
||||
} else {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool getTheme() {
|
||||
return (context.read<ThemeCubit>().state.appTheme == AppTheme.Dark) ? true : false;
|
||||
}
|
||||
|
||||
bool getNotification() {
|
||||
if (context.read<SettingsCubit>().state.settingsModel!.notification == true) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switchNotification(bool value) {
|
||||
if (!value) {
|
||||
FirebaseMessaging.instance.deleteToken().then((value) async {
|
||||
context.read<RegisterTokenCubit>().registerToken(fcmId: '', context: context);
|
||||
});
|
||||
} else {
|
||||
FirebaseMessaging.instance.getToken().then((value) async {
|
||||
context.read<RegisterTokenCubit>().registerToken(fcmId: value!, context: context);
|
||||
context.read<SettingsCubit>().changeFcmToken(value);
|
||||
});
|
||||
}
|
||||
|
||||
context.read<SettingsCubit>().changeNotification(value);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
//set drawer item list
|
||||
Widget setDrawerItem(String title, IconData? icon, bool isTrailing, bool isNavigate, bool isSwitch, int id, {String? image, String? desc, bool isDeleteAcc = false}) {
|
||||
return ListTile(
|
||||
dense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: Container(
|
||||
height: 30,
|
||||
width: 30,
|
||||
padding: EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(7), color: borderColor.withOpacity(0.2)),
|
||||
child: (image != null && image != "")
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
child: Image.network(
|
||||
image,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: (image.contains("png")) ? UiUtils.getColorScheme(context).primaryContainer : null,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Icon(icon);
|
||||
},
|
||||
),
|
||||
)
|
||||
: Icon(icon, size: 20)),
|
||||
iconColor: (isDeleteAcc) ? (context.read<ThemeCubit>().state.appTheme == AppTheme.Dark ? darkIconColor : iconColor) : UiUtils.getColorScheme(context).primaryContainer,
|
||||
trailing: (isTrailing)
|
||||
? SizedBox(
|
||||
height: 45,
|
||||
width: 55,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.fill,
|
||||
child: Switch.adaptive(
|
||||
onChanged: (id == 0) ? switchTheme : switchNotification,
|
||||
value: (id == 0) ? getTheme() : getNotification(),
|
||||
activeColor: Theme.of(context).primaryColor,
|
||||
activeTrackColor: Theme.of(context).primaryColor,
|
||||
inactiveThumbColor: Colors.grey,
|
||||
inactiveTrackColor: Colors.grey)))
|
||||
: const SizedBox.shrink(),
|
||||
title: CustomTextLabel(
|
||||
text: title,
|
||||
textStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(color: (isDeleteAcc) ? (context.read<ThemeCubit>().state.appTheme == AppTheme.Dark ? darkIconColor : iconColor) : UiUtils.getColorScheme(context).primaryContainer)),
|
||||
onTap: () {
|
||||
if (isNavigate) {
|
||||
switch (id) {
|
||||
case 2:
|
||||
Navigator.of(context).pushNamed(Routes.languageList, arguments: {"from": "profile"});
|
||||
break;
|
||||
case 3:
|
||||
Navigator.of(context).pushNamed(Routes.bookmark);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
Navigator.of(context).pushNamed(Routes.addNews, arguments: {"isEdit": false, "from": "profile"});
|
||||
break;
|
||||
case 6:
|
||||
Navigator.of(context).pushNamed(Routes.manageUserNews);
|
||||
break;
|
||||
case 7:
|
||||
Navigator.of(context).pushNamed(Routes.managePref, arguments: {"from": 1});
|
||||
break;
|
||||
case 8:
|
||||
Navigator.of(context).pushNamed(Routes.privacy, arguments: {"from": "setting", "title": title, "desc": desc});
|
||||
break;
|
||||
case 9:
|
||||
_openStoreListing();
|
||||
break;
|
||||
case 10:
|
||||
String str = "$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()}";
|
||||
Share.share(str, sharePositionOrigin: Rect.fromLTWH(0, 0, MediaQuery.of(context).size.width, MediaQuery.of(context).size.height / 2));
|
||||
break;
|
||||
case 12:
|
||||
deleteAccount();
|
||||
break;
|
||||
case 13:
|
||||
Navigator.of(context).pushNamed(Routes.editUserProfile, arguments: {'from': 'profile'});
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
logOutDialog() async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return StatefulBuilder(builder: (BuildContext context, StateSetter setStater) {
|
||||
return CustomAlertDialog(
|
||||
isForceAppUpdate: false,
|
||||
context: context,
|
||||
yesButtonText: 'yesLbl',
|
||||
yesButtonTextPostfix: 'logoutLbl',
|
||||
noButtonText: 'noLbl',
|
||||
imageName: 'logout',
|
||||
titleWidget: CustomTextLabel(
|
||||
text: 'logoutLbl', textStyle: Theme.of(this.context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w500, color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
messageText: 'logoutTxt',
|
||||
onYESButtonPressed: () async {
|
||||
UiUtils.userLogOut(contxt: context);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//set Delete dialogue
|
||||
deleteAccount() async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return StatefulBuilder(builder: (BuildContext context, StateSetter setStater) {
|
||||
return CustomAlertDialog(
|
||||
context: context,
|
||||
isForceAppUpdate: false,
|
||||
yesButtonText: (_auth.currentUser != null) ? 'yesLbl' : 'logoutLbl',
|
||||
yesButtonTextPostfix: (_auth.currentUser != null) ? 'deleteTxt' : '',
|
||||
noButtonText: (_auth.currentUser != null) ? 'noLbl' : 'cancelBtn',
|
||||
imageName: 'deleteAccount',
|
||||
titleWidget: (_auth.currentUser != null)
|
||||
? CustomTextLabel(
|
||||
text: 'deleteAcc', textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w800, color: UiUtils.getColorScheme(context).primaryContainer))
|
||||
: CustomTextLabel(
|
||||
text: 'deleteAlertTitle', textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w800, color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
messageText: (_auth.currentUser != null) ? 'deleteConfirm' : 'deleteRelogin',
|
||||
onYESButtonPressed: () async {
|
||||
(_auth.currentUser != null) ? proceedToDeleteProfile() : askToLoginAgain();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
askToLoginAgain() {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'loginReqMsg'), context);
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(Routes.login, (route) => false);
|
||||
}
|
||||
|
||||
proceedToDeleteProfile() async {
|
||||
//delete user from firebase
|
||||
try {
|
||||
await _auth.currentUser!.delete().then((value) {
|
||||
//delete user prefs from App-local
|
||||
context.read<DeleteUserCubit>().deleteUser().then((value) {
|
||||
showSnackBar(value["message"], context);
|
||||
for (int i = 0; i < AuthProviders.values.length; i++) {
|
||||
if (AuthProviders.values[i].name == context.read<AuthCubit>().getType()) {
|
||||
context.read<AuthCubit>().signOut(AuthProviders.values[i]).then((value) {
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(Routes.login, (route) => false);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} on FirebaseAuthException catch (error) {
|
||||
if (error.code == "requires-recent-login") {
|
||||
for (int i = 0; i < AuthProviders.values.length; i++) {
|
||||
if (AuthProviders.values[i].name == context.read<AuthCubit>().getType()) {
|
||||
context.read<AuthCubit>().signOut(AuthProviders.values[i]).then((value) {
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(Routes.login, (route) => false);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw showSnackBar('${error.message}', context);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
Future<void> _openStoreListing() => _inAppReview.openStoreListing(appStoreId: context.read<AppConfigurationCubit>().getAppstoreId(), microsoftStoreId: 'microsoftStoreId');
|
||||
|
||||
Widget setHeader() {
|
||||
return BlocBuilder<AuthCubit, AuthState>(builder: (context, authState) {
|
||||
if (authState is Authenticated && context.read<AuthCubit>().getUserId() != "0") {
|
||||
isAuthor = (authState.authModel.isAuthor == 1);
|
||||
authorStatus = ((authState.authModel.authorDetails != null) ? authState.authModel.authorDetails?.status : AuthorStatus.rejected)!;
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 15.0, end: 15.0, top: 15.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsetsDirectional.only(start: 20.0, end: 20.0, top: 10),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15.0), color: Theme.of(context).colorScheme.surface),
|
||||
child: Row(crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
|
||||
CircleAvatar(
|
||||
radius: 34,
|
||||
backgroundColor: Colors.transparent,
|
||||
child: ClipOval(
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
child: (authState.authModel.profile != null && authState.authModel.profile.toString().trim().isNotEmpty)
|
||||
? Image.network(
|
||||
authState.authModel.profile!,
|
||||
fit: BoxFit.fill,
|
||||
width: 80,
|
||||
height: 80,
|
||||
filterQuality: FilterQuality.high,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return const Icon(Icons.person);
|
||||
},
|
||||
)
|
||||
: Icon(Icons.person, color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.6,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 5),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
if (authState.authModel.name != null && authState.authModel.name != "")
|
||||
CustomTextLabel(
|
||||
text: authState.authModel.name!,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w800, color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
const SizedBox(height: 3),
|
||||
if (authState.authModel.mobile != null && authState.authModel.mobile!.trim().isNotEmpty)
|
||||
CustomTextLabel(
|
||||
text: authState.authModel.mobile!, textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))),
|
||||
const SizedBox(height: 3),
|
||||
if (authState.authModel.email != null && authState.authModel.email != "")
|
||||
CustomTextLabel(
|
||||
text: authState.authModel.email!,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))),
|
||||
BlocConsumer<AuthorCubit, AuthorState>(
|
||||
listener: (context, state) {
|
||||
if (state is AuthorRequestSent) showSnackBar(state.responseMessage, context);
|
||||
},
|
||||
builder: (context, state) {
|
||||
return buildAuthorButton(context, state);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[
|
||||
//For Guest User
|
||||
Container(
|
||||
margin: const EdgeInsets.all(15),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, border: Border.all(color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
alignment: Alignment.center,
|
||||
child: Icon(Icons.person, size: 25.0, color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
Expanded(child: setGuestText())
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget buildAuthorButton(BuildContext context, AuthorState state) {
|
||||
//check if user is author already
|
||||
if (isAuthor || authorStatus == AuthorStatus.approved || state is AuthorApproved) {
|
||||
return IntrinsicWidth(
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(color: backgroundColor, borderRadius: BorderRadius.circular(5), border: Border.all(color: authorApprovedColor)),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.verified_outlined, size: 18, color: authorApprovedColor),
|
||||
SizedBox(width: 6),
|
||||
const CustomTextLabel(text: 'authorLbl', textStyle: TextStyle(color: authorApprovedColor, fontWeight: FontWeight.w600)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (authorStatus == AuthorStatus.pending || state is AuthorRequestSent) {
|
||||
return IntrinsicWidth(
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(color: backgroundColor, borderRadius: BorderRadius.circular(8), border: Border.all(color: authorReviewColor)),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
Icon(Icons.info_outline, size: 18, color: authorReviewColor),
|
||||
SizedBox(width: 6),
|
||||
CustomTextLabel(text: 'authorReviewPendingLbl', textStyle: TextStyle(color: authorReviewColor, fontWeight: FontWeight.w500)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (authorStatus == AuthorStatus.rejected || state is AuthorInitial) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
//redirect to update profile screen
|
||||
Navigator.of(context).pushNamed(Routes.editUserProfile, arguments: {'from': 'becomeAuthor'});
|
||||
},
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(color: backgroundColor, borderRadius: BorderRadius.circular(5), border: Border.all(color: authorRequestColor)),
|
||||
child: const CustomTextLabel(text: 'becomeAuthorLbl', textStyle: TextStyle(color: authorRequestColor, fontWeight: FontWeight.w500))),
|
||||
);
|
||||
}
|
||||
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
Widget setGuestText() {
|
||||
return BlocBuilder<LanguageJsonCubit, LanguageJsonState>(
|
||||
builder: (context, langState) {
|
||||
return RichText(
|
||||
text: TextSpan(
|
||||
text: UiUtils.getTranslatedLabel(context, 'plzLbl'),
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, overflow: TextOverflow.ellipsis),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: " ${UiUtils.getTranslatedLabel(context, 'loginBtn')} ",
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).primaryColor, fontWeight: FontWeight.w600, overflow: TextOverflow.ellipsis),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
setState(() {
|
||||
Navigator.of(context).pushReplacementNamed(Routes.login);
|
||||
});
|
||||
});
|
||||
}),
|
||||
TextSpan(
|
||||
text: "${UiUtils.getTranslatedLabel(context, 'firstAccLbl')} ", style: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
TextSpan(
|
||||
text: UiUtils.getTranslatedLabel(context, 'allFunLbl'),
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, overflow: TextOverflow.ellipsis))
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget setBody() {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 15.0, end: 15.0, top: 15.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsetsDirectional.only(start: 20.0, end: 20.0),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15.0), color: Theme.of(context).colorScheme.surface),
|
||||
child: ScrollConfiguration(
|
||||
behavior: GlobalScrollBehavior(),
|
||||
child: BlocBuilder<AuthCubit, AuthState>(
|
||||
builder: (context, state) {
|
||||
return ListView(
|
||||
padding: const EdgeInsetsDirectional.only(top: 10.0),
|
||||
shrinkWrap: true,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
children: <Widget>[
|
||||
if (context.read<AuthCubit>().getUserId() != "0") setDrawerItem('editProfile', Icons.edit_outlined, false, true, false, 13),
|
||||
setDrawerItem('darkModeLbl', Icons.swap_horizontal_circle, true, false, true, 0),
|
||||
setDrawerItem('notificationLbl', Icons.notifications_rounded, true, false, true, 1),
|
||||
setDrawerItem('changeLang', Icons.g_translate_rounded, false, true, false, 2),
|
||||
Divider(thickness: 2),
|
||||
if (context.read<AuthCubit>().getUserId() != "0") setDrawerItem('bookmarkLbl', Icons.bookmarks_rounded, false, true, false, 3),
|
||||
// if (context.read<AuthCubit>().getUserId() != "0") setDrawerItem('notificationLbl', Icons.notifications, false, true, false, 4),
|
||||
if (context.read<AuthCubit>().getUserId() != "0" && isAuthor)
|
||||
// context.read<AuthCubit>().getRole() != "0")
|
||||
setDrawerItem('createNewsLbl', Icons.add_box_rounded, false, true, false, 5),
|
||||
if (context.read<AuthCubit>().getUserId() != "0" && isAuthor)
|
||||
// context.read<AuthCubit>().getRole() != "0")
|
||||
setDrawerItem('manageNewsLbl', Icons.edit_document, false, true, false, 6),
|
||||
if (context.read<AuthCubit>().getUserId() != "0") setDrawerItem('managePreferences', Icons.thumbs_up_down_rounded, false, true, false, 7),
|
||||
if (context.read<AuthCubit>().getUserId() != "0") Divider(thickness: 2),
|
||||
pagesBuild(),
|
||||
setDrawerItem('rateUs', Icons.stars_sharp, false, true, false, 9),
|
||||
setDrawerItem('shareApp', Icons.share_rounded, false, true, false, 10),
|
||||
if (context.read<AuthCubit>().getUserId() != "0") setDrawerItem('deleteAcc', Icons.delete_forever_rounded, false, true, false, 12, isDeleteAcc: true),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: CustomAppBar(
|
||||
height: 45,
|
||||
isBackBtn: false,
|
||||
label: 'myProfile',
|
||||
isConvertText: true,
|
||||
actionWidget: [if (context.read<AuthCubit>().getUserId() != "0") logoutButton()],
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsetsDirectional.only(top: 15.0, bottom: 10.0),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: <Widget>[setHeader(), setBody()])),
|
||||
],
|
||||
)));
|
||||
}
|
||||
|
||||
Widget logoutButton() {
|
||||
return GestureDetector(
|
||||
onTap: () => logOutDialog(),
|
||||
child: Container(
|
||||
margin: EdgeInsetsDirectional.only(end: 15, top: 12, bottom: 12),
|
||||
height: 30,
|
||||
width: 30,
|
||||
padding: EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(7), color: Colors.transparent, border: Border.all(width: 2, color: borderColor.withOpacity(0.3))),
|
||||
child: Icon(Icons.power_settings_new_rounded, color: UiUtils.getColorScheme(context).primaryContainer, size: 20)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class CustomAlertDialog extends StatelessWidget {
|
||||
final BuildContext context;
|
||||
final String yesButtonText;
|
||||
final String yesButtonTextPostfix;
|
||||
final String noButtonText;
|
||||
final String imageName;
|
||||
final Widget titleWidget;
|
||||
final String messageText;
|
||||
final Function() onYESButtonPressed;
|
||||
final bool isForceAppUpdate;
|
||||
const CustomAlertDialog(
|
||||
{super.key,
|
||||
required this.context,
|
||||
required this.yesButtonText,
|
||||
required this.yesButtonTextPostfix,
|
||||
required this.noButtonText,
|
||||
required this.imageName,
|
||||
required this.titleWidget,
|
||||
required this.messageText,
|
||||
required this.onYESButtonPressed,
|
||||
required this.isForceAppUpdate});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
backgroundColor: UiUtils.getColorScheme(context).surface,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0))),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SvgPictureWidget(assetName: imageName),
|
||||
const SizedBox(height: 15),
|
||||
titleWidget,
|
||||
const SizedBox(height: 5),
|
||||
CustomTextLabel(text: messageText, textAlign: TextAlign.center, textStyle: Theme.of(this.context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
],
|
||||
),
|
||||
actionsAlignment: MainAxisAlignment.spaceAround,
|
||||
actionsOverflowButtonSpacing: 15,
|
||||
actions: <Widget>[
|
||||
MaterialButton(
|
||||
minWidth: MediaQuery.of(context).size.width / 3.5,
|
||||
elevation: 0.0,
|
||||
highlightColor: Colors.transparent,
|
||||
color: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4), side: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
onPressed: () => (isForceAppUpdate) ? exit(0) : Navigator.of(context).pop(false),
|
||||
child: CustomTextLabel(
|
||||
text: noButtonText, textStyle: Theme.of(this.context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w500)),
|
||||
),
|
||||
MaterialButton(
|
||||
elevation: 0.0,
|
||||
color: UiUtils.getColorScheme(context).primaryContainer,
|
||||
splashColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
onPressed: onYESButtonPressed,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: UiUtils.getTranslatedLabel(context, yesButtonText),
|
||||
style: Theme.of(this.context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).surface, fontWeight: FontWeight.w500),
|
||||
children: [
|
||||
const TextSpan(text: " , "),
|
||||
TextSpan(
|
||||
text: UiUtils.getTranslatedLabel(context, yesButtonTextPostfix),
|
||||
style: Theme.of(this.context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).surface, fontWeight: FontWeight.w500))
|
||||
]),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
401
news-app/lib/ui/screens/Profile/userProfile.dart
Normal file
401
news-app/lib/ui/screens/Profile/userProfile.dart
Normal file
@@ -0,0 +1,401 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/Auth/updateUserCubit.dart';
|
||||
import 'package:news/cubits/Author/authorCubit.dart';
|
||||
import 'package:news/cubits/languageCubit.dart';
|
||||
import 'package:news/data/repositories/Auth/authLocalDataSource.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
|
||||
import 'package:news/ui/widgets/customAppBar.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/utils/validators.dart';
|
||||
|
||||
class UserProfileScreen extends StatefulWidget {
|
||||
final String from;
|
||||
const UserProfileScreen({super.key, required this.from});
|
||||
|
||||
@override
|
||||
State createState() => UserProfileScreenState();
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
Map arguments = routeSettings.arguments as Map;
|
||||
return CupertinoPageRoute(builder: (_) => UserProfileScreen(from: arguments['from'] as String));
|
||||
}
|
||||
}
|
||||
|
||||
class UserProfileScreenState extends State<UserProfileScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
AuthLocalDataSource authLocalDataSource = AuthLocalDataSource();
|
||||
dynamic size;
|
||||
FocusNode nameFocus = FocusNode();
|
||||
FocusNode mobNoFocus = FocusNode();
|
||||
FocusNode emailFocus = FocusNode();
|
||||
FocusNode crntFocus = FocusNode();
|
||||
FocusNode authorBioFocus = FocusNode();
|
||||
FocusNode telegramLinkFocus = FocusNode();
|
||||
FocusNode facebookLinkFocus = FocusNode();
|
||||
FocusNode linkedInLinkFocus = FocusNode();
|
||||
FocusNode whatsappLinkFocus = FocusNode();
|
||||
|
||||
TextEditingController? nameC = TextEditingController(), monoC = TextEditingController(), emailC = TextEditingController();
|
||||
TextEditingController? authorBioC = TextEditingController();
|
||||
TextEditingController? telegramLinkC = TextEditingController();
|
||||
TextEditingController? facebookLinkC = TextEditingController();
|
||||
TextEditingController? whatsappLinkC = TextEditingController();
|
||||
TextEditingController? linkedInLinkC = TextEditingController();
|
||||
String? profile, mobile;
|
||||
bool isEditMono = false, isEditEmail = false, isSaving = false, isThisUserAnAuthor = false;
|
||||
String? updateValue;
|
||||
File? image;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
isThisUserAnAuthor = context.read<AuthCubit>().isAuthor();
|
||||
setControllers();
|
||||
if (widget.from == "login") getLanguageList();
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
setControllers() {
|
||||
nameC = TextEditingController(text: authLocalDataSource.getName());
|
||||
emailC = TextEditingController(text: authLocalDataSource.getEmail());
|
||||
monoC = TextEditingController(text: authLocalDataSource.getMobile());
|
||||
profile = context.read<AuthCubit>().getProfile();
|
||||
if (isThisUserAnAuthor) {
|
||||
authorBioC = TextEditingController(text: (authLocalDataSource.getAuthorBio().isEmpty) ? context.read<AuthCubit>().getAuthorBio() : authLocalDataSource.getAuthorBio());
|
||||
whatsappLinkC =
|
||||
TextEditingController(text: (authLocalDataSource.getAuthorWhatsappLink()!.isEmpty) ? context.read<AuthCubit>().getAuthorWhatsappLink() : authLocalDataSource.getAuthorWhatsappLink());
|
||||
telegramLinkC =
|
||||
TextEditingController(text: (authLocalDataSource.getAuthorTelegramLink()!.isEmpty) ? context.read<AuthCubit>().getAuthorTelegramLink() : authLocalDataSource.getAuthorTelegramLink());
|
||||
linkedInLinkC =
|
||||
TextEditingController(text: (authLocalDataSource.getAuthorLinkedInLink()!.isEmpty) ? context.read<AuthCubit>().getAuthorLinkedInLink() : authLocalDataSource.getAuthorLinkedInLink());
|
||||
facebookLinkC =
|
||||
TextEditingController(text: (authLocalDataSource.getAuthorFacebookLink()!.isEmpty) ? context.read<AuthCubit>().getAuthorFacebookLink() : authLocalDataSource.getAuthorFacebookLink());
|
||||
}
|
||||
}
|
||||
|
||||
getLanguageList() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<LanguageCubit>().getLanguage();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
size = MediaQuery.of(context).size;
|
||||
return Scaffold(appBar: CustomAppBar(height: 45, isBackBtn: (widget.from == "login") ? false : true, label: 'editProfile', isConvertText: true), body: buildProfileFields());
|
||||
}
|
||||
|
||||
onBackPress(bool isTrue) {
|
||||
(widget.from == "login") ? Navigator.of(context).popUntil((route) => route.isFirst) : Navigator.pop(context);
|
||||
}
|
||||
|
||||
setPendingStatusControllerValues() {
|
||||
authorBioC?.clear();
|
||||
whatsappLinkC?.clear();
|
||||
telegramLinkC?.clear();
|
||||
linkedInLinkC?.clear();
|
||||
facebookLinkC?.clear();
|
||||
}
|
||||
|
||||
buildProfileFields() {
|
||||
return PopScope(
|
||||
canPop: (widget.from != "login") ? true : false,
|
||||
onPopInvoked: (bool isTrue) => onBackPress,
|
||||
child: BlocConsumer<UpdateUserCubit, UpdateUserState>(
|
||||
listener: (context, state) {
|
||||
//show snackbar incase of success & failure both
|
||||
if (state is UpdateUserFetchSuccess && state.updatedUser != null) {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'profileUpdateMsg'), context);
|
||||
isSaving = false;
|
||||
setState(() {});
|
||||
if (widget.from == "becomeAuthor") {
|
||||
//call author request API here
|
||||
context.read<AuthorCubit>().requestToBecomeAuthor();
|
||||
setPendingStatusControllerValues();
|
||||
}
|
||||
if (widget.from == "login") {
|
||||
if (context.read<LanguageCubit>().langList().length > 1) {
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(Routes.languageList, (route) => false, arguments: {"from": "firstLogin"});
|
||||
} else {
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(Routes.managePref, (route) => false, arguments: {"from": 2});
|
||||
}
|
||||
} else {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
if (state is UpdateUserFetchFailure) {
|
||||
isSaving = false;
|
||||
showSnackBar(state.errorMessage, context);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return SingleChildScrollView(
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(children: [
|
||||
profileWidget(),
|
||||
setTextField(
|
||||
validatorMethod: (value) => Validators.nameValidation(value!, context),
|
||||
focusNode: nameFocus,
|
||||
nextFocus: (context.read<AuthCubit>().getType() != loginMbl) ? mobNoFocus : emailFocus,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.name,
|
||||
controller: nameC,
|
||||
hintlbl: UiUtils.getTranslatedLabel(context, 'nameLbl')),
|
||||
SizedBox(height: size.height * 0.03),
|
||||
setMobileNumber(),
|
||||
SizedBox(height: size.height * 0.03),
|
||||
setTextField(
|
||||
validatorMethod: (value) => value!.trim().isEmpty ? null : Validators.emailValidation(value, context),
|
||||
focusNode: emailFocus,
|
||||
nextFocus: isThisUserAnAuthor ? authorBioFocus : null,
|
||||
textInputAction: isThisUserAnAuthor ? TextInputAction.next : TextInputAction.done,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
controller: emailC,
|
||||
isenable: (context.read<AuthCubit>().getType() == loginMbl) ? true : false,
|
||||
hintlbl: UiUtils.getTranslatedLabel(context, 'emailLbl')),
|
||||
SizedBox(height: size.height * 0.05),
|
||||
setAuthorBio(),
|
||||
SizedBox(height: size.height * 0.05),
|
||||
Container(
|
||||
alignment: AlignmentGeometry.centerLeft,
|
||||
padding: EdgeInsetsDirectional.only(start: 20),
|
||||
child: CustomTextLabel(
|
||||
text: 'socialMediaLinksLbl',
|
||||
textStyle: Theme.of(this.context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w700))),
|
||||
SizedBox(height: size.height * 0.03),
|
||||
setSocialMediaLink(linkController: telegramLinkC, currFocus: telegramLinkFocus, nextFocus: whatsappLinkFocus, typeForHintText: 'Telegram'),
|
||||
SizedBox(height: size.height * 0.03),
|
||||
setSocialMediaLink(linkController: whatsappLinkC, currFocus: whatsappLinkFocus, nextFocus: facebookLinkFocus, typeForHintText: 'Whatsapp'),
|
||||
SizedBox(height: size.height * 0.03),
|
||||
setSocialMediaLink(linkController: facebookLinkC, currFocus: facebookLinkFocus, nextFocus: linkedInLinkFocus, typeForHintText: 'Facebook'),
|
||||
SizedBox(height: size.height * 0.03),
|
||||
setSocialMediaLink(linkController: linkedInLinkC, currFocus: linkedInLinkFocus, nextFocus: null, typeForHintText: 'LinkedIn'),
|
||||
SizedBox(height: size.height * 0.03),
|
||||
submitBtn(context)
|
||||
])));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget setAuthorBio() {
|
||||
return Container(
|
||||
width: double.maxFinite,
|
||||
child: setTextField(
|
||||
hintlbl: UiUtils.getTranslatedLabel(context, 'addYourBioHintLbl'),
|
||||
controller: authorBioC,
|
||||
focusNode: authorBioFocus,
|
||||
nextFocus: telegramLinkFocus,
|
||||
textInputAction: TextInputAction.newline,
|
||||
maxLines: 2));
|
||||
}
|
||||
|
||||
Widget setSocialMediaLink({required TextEditingController? linkController, required FocusNode currFocus, required FocusNode? nextFocus, required String typeForHintText}) {
|
||||
return setTextField(
|
||||
hintlbl: getAddLinkHint(context, typeForHintText),
|
||||
controller: linkController,
|
||||
focusNode: currFocus,
|
||||
nextFocus: nextFocus,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.url);
|
||||
}
|
||||
|
||||
String getAddLinkHint(BuildContext context, String type) {
|
||||
final template = UiUtils.getTranslatedLabel(context, 'addLinkHereHintLbl');
|
||||
return template.replaceAll("{type}", type);
|
||||
}
|
||||
|
||||
Widget setMobileNumber() {
|
||||
return SizedBox(
|
||||
height: 60,
|
||||
child: setTextField(
|
||||
validatorMethod: (value) => value!.trim().isEmpty ? null : Validators.mobValidation(value, context),
|
||||
keyboardType: TextInputType.phone,
|
||||
hintlbl: UiUtils.getTranslatedLabel(context, 'mobileLbl'),
|
||||
textInputAction: TextInputAction.next,
|
||||
controller: monoC,
|
||||
focusNode: mobNoFocus,
|
||||
nextFocus: isThisUserAnAuthor ? authorBioFocus : emailFocus,
|
||||
isenable: (context.read<AuthCubit>().getType() != loginMbl) ? true : false));
|
||||
}
|
||||
|
||||
//set image camera
|
||||
getFromCamera() async {
|
||||
try {
|
||||
XFile? pickedFile = await ImagePicker().pickImage(source: ImageSource.camera);
|
||||
if (pickedFile != null) {
|
||||
Navigator.of(context).pop(); //pop dialog
|
||||
setState(() {
|
||||
image = File(pickedFile.path);
|
||||
profile = image!.path;
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// set image gallery
|
||||
_getFromGallery() async {
|
||||
XFile? pickedFile = await ImagePicker().pickImage(source: ImageSource.gallery, maxWidth: 1800, maxHeight: 1800);
|
||||
if (pickedFile != null) {
|
||||
setState(() {
|
||||
image = File(pickedFile.path);
|
||||
profile = image!.path;
|
||||
Navigator.of(context).pop(); //pop dialog
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
profileWidget() {
|
||||
return Container(padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 15), child: Center(child: profileImgWidget()));
|
||||
}
|
||||
|
||||
Widget profilePictureWidget() {
|
||||
final fallbackIcon = Container(
|
||||
padding: EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, border: BoxBorder.all(color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
child: Icon(Icons.person, color: UiUtils.getColorScheme(context).primaryContainer));
|
||||
|
||||
if (image != null) {
|
||||
return Image.file(image!, fit: BoxFit.fill, width: 75, height: 75);
|
||||
}
|
||||
|
||||
if (profile == null || profile!.trim().isEmpty) {
|
||||
return fallbackIcon;
|
||||
}
|
||||
|
||||
return Image.network(profile!,
|
||||
fit: BoxFit.fill, width: 85, errorBuilder: (_, __, ___) => fallbackIcon, loadingBuilder: (_, child, loadingProgress) => loadingProgress == null ? child : fallbackIcon);
|
||||
}
|
||||
|
||||
profileImgWidget() {
|
||||
return GestureDetector(
|
||||
onTap: () => UiUtils().showUploadImageBottomsheet(context: context, onCamera: getFromCamera, onGallery: _getFromGallery),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 46,
|
||||
backgroundColor: Colors.transparent,
|
||||
child: CircleAvatar(radius: 44, backgroundColor: Colors.transparent, child: ClipOval(clipBehavior: Clip.antiAliasWithSaveLayer, child: profilePictureWidget()))),
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(5),
|
||||
decoration:
|
||||
BoxDecoration(color: UiUtils.getColorScheme(context).primaryContainer, shape: BoxShape.circle, border: Border.all(width: 2, color: UiUtils.getColorScheme(context).secondary)),
|
||||
child: Icon(Icons.camera_alt_outlined, color: Theme.of(context).colorScheme.secondary, size: 20)))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
submitBtn(BuildContext context) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Theme.of(context).primaryColor, shadowColor: Colors.transparent, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0))),
|
||||
child: Container(
|
||||
height: 45.0,
|
||||
width: MediaQuery.of(context).size.width * 0.8,
|
||||
alignment: Alignment.center,
|
||||
child: (isSaving)
|
||||
? UiUtils.showCircularProgress(true, UiUtils.getColorScheme(context).primaryContainer)
|
||||
: CustomTextLabel(text: 'saveLbl', textStyle: Theme.of(this.context).textTheme.titleMedium?.copyWith(color: secondaryColor, fontWeight: FontWeight.w500, letterSpacing: 0.6)),
|
||||
),
|
||||
onPressed: () async {
|
||||
validateData();
|
||||
}));
|
||||
}
|
||||
|
||||
validateData() async {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
profileupdateprocess();
|
||||
} else {}
|
||||
}
|
||||
|
||||
profileupdateprocess() async {
|
||||
isSaving = true;
|
||||
//in case of Clearing Existing mobile number -> set mobile to blank, so it can be passed to replace existing value of mobile number as NULL mobile number won't be passed to APi
|
||||
mobile = monoC!.text.trim();
|
||||
|
||||
if (mobile == null && context.read<AuthCubit>().getType() != loginMbl && context.read<AuthCubit>().getMobile().isNotEmpty) {
|
||||
mobile = " ";
|
||||
}
|
||||
try {
|
||||
context.read<UpdateUserCubit>().setUpdateUser(
|
||||
context: context,
|
||||
name: nameC!.text,
|
||||
email: emailC!.text,
|
||||
mobile: mobile,
|
||||
filePath: (image != null) ? image!.path : "",
|
||||
authorBio: authorBioC?.text,
|
||||
whatsappLink: whatsappLinkC?.text,
|
||||
facebookLink: facebookLinkC?.text,
|
||||
telegramLink: telegramLinkC?.text,
|
||||
linkedInLink: linkedInLinkC?.text);
|
||||
} catch (e) {
|
||||
showSnackBar(e.toString(), context);
|
||||
}
|
||||
}
|
||||
|
||||
setTextField(
|
||||
{String? Function(String?)? validatorMethod,
|
||||
FocusNode? focusNode,
|
||||
FocusNode? nextFocus,
|
||||
TextInputAction? textInputAction,
|
||||
TextInputType? keyboardType,
|
||||
TextEditingController? controller,
|
||||
String? hintlbl,
|
||||
bool isenable = true,
|
||||
int? maxLines}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: TextFormField(
|
||||
enabled: isenable,
|
||||
decoration: InputDecoration(
|
||||
hintText: hintlbl,
|
||||
hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).outline.withOpacity(0.2)),
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).colorScheme.surface,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 17, vertical: 17),
|
||||
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).outline.withOpacity(0.7)), borderRadius: BorderRadius.circular(5.0)),
|
||||
enabledBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(5.0)),
|
||||
disabledBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(5.0)),
|
||||
),
|
||||
validator: validatorMethod,
|
||||
inputFormatters: [(focusNode == mobNoFocus) ? FilteringTextInputFormatter.digitsOnly : FilteringTextInputFormatter.singleLineFormatter],
|
||||
focusNode: focusNode,
|
||||
textInputAction: textInputAction,
|
||||
onEditingComplete: () {
|
||||
crntFocus = FocusNode();
|
||||
(nextFocus != null) ? fieldFocusChange(context, focusNode!, nextFocus) : focusNode?.unfocus();
|
||||
},
|
||||
onChanged: (String value) {
|
||||
setState(() => crntFocus = focusNode!);
|
||||
},
|
||||
onTapOutside: (val) => FocusScope.of(context).unfocus(),
|
||||
maxLines: (maxLines ?? 1),
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(color: (isenable) ? UiUtils.getColorScheme(context).primaryContainer : borderColor),
|
||||
keyboardType: keyboardType,
|
||||
controller: controller));
|
||||
}
|
||||
|
||||
fieldFocusChange(BuildContext context, FocusNode currentFocus, FocusNode nextFocus) {
|
||||
currentFocus.unfocus();
|
||||
FocusScope.of(context).requestFocus(nextFocus);
|
||||
}
|
||||
}
|
||||
189
news-app/lib/ui/screens/PushNotificationService.dart
Normal file
189
news-app/lib/ui/screens/PushNotificationService.dart
Normal file
@@ -0,0 +1,189 @@
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:news/cubits/getUserDataByIdCubit.dart';
|
||||
import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart';
|
||||
import 'package:news/ui/screens/dashBoard/dashBoardScreen.dart';
|
||||
import 'package:news/utils/constant.dart';
|
||||
import 'package:news/utils/strings.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'dart:io';
|
||||
import 'package:news/cubits/NewsByIdCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/app/app.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
FirebaseMessaging messaging = FirebaseMessaging.instance;
|
||||
SettingsLocalDataRepository settingsRepo = SettingsLocalDataRepository();
|
||||
|
||||
backgroundMessage(NotificationResponse notificationResponse) {
|
||||
//for notification only
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {}
|
||||
|
||||
void redirectToNewsDetailsScreen(RemoteMessage message, BuildContext context) async {
|
||||
var data = message.data;
|
||||
if (data[TYPE] == "default" || data[TYPE] == "category") {
|
||||
var payload = data[NEWS_ID];
|
||||
var lanId = data[LANGUAGE_ID] ?? "14";
|
||||
|
||||
if (lanId == context.read<AppLocalizationCubit>().state.id) {
|
||||
//show only if Current language is Same as Notification Language
|
||||
if (payload == null) {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => const MyApp()));
|
||||
} else {
|
||||
context.read<NewsByIdCubit>().getNewsById(newsId: payload, langId: context.read<AppLocalizationCubit>().state.id).then((value) {
|
||||
UiUtils.rootNavigatorKey.currentState!.pushNamed(Routes.newsDetails, arguments: {"model": value[0], "isFromBreak": false, "fromShowMore": false});
|
||||
}).catchError((e) {});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PushNotificationService {
|
||||
late BuildContext context;
|
||||
|
||||
PushNotificationService({required this.context});
|
||||
|
||||
Future initialise() async {
|
||||
messaging.getToken();
|
||||
const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('notification_icon');
|
||||
const DarwinInitializationSettings initializationSettingsIOS = DarwinInitializationSettings(requestAlertPermission: true, requestBadgePermission: false, requestSoundPermission: true);
|
||||
Future<dynamic> notificationHandler(RemoteMessage message) async {
|
||||
if (settingsRepo.getNotification()) {
|
||||
var data = message.data;
|
||||
var notif = message.notification;
|
||||
if (data.isNotEmpty) {
|
||||
var title = (data[TITLE] != null) ? data[TITLE].toString() : appName;
|
||||
var body = (data[MESSAGE] != null) ? data[MESSAGE].toString() : "";
|
||||
var image = data[IMAGE];
|
||||
var payload = data[NEWS_ID];
|
||||
String lanId = (data[LANGUAGE_ID] != null) ? data[LANGUAGE_ID].toString() : "1"; //1 = ENGLISH bydefault
|
||||
(payload == null) ? payload = "" : payload = payload;
|
||||
if ((data[TYPE] == "default" || data[TYPE] == "category" || data[TYPE] == "comment" || data[TYPE] == "comment_like" || data[TYPE] == "newlyadded")) {
|
||||
if (lanId == context.read<AppLocalizationCubit>().state.id) {
|
||||
//show only if Current language is Same as Notification Language
|
||||
(image != null && image != "") ? generateImageNotification(title, body, image, payload) : generateSimpleNotification(title, body, payload);
|
||||
}
|
||||
} else if ((data[TYPE] == "author_approved") || (data[TYPE] == "author_rejected")) {
|
||||
generateSimpleNotification(title, body, payload);
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<GetUserByIdCubit>().getUserById(); // update Author status
|
||||
});
|
||||
}
|
||||
} else if (notif != null) {
|
||||
//Direct Firebase Notification
|
||||
RemoteNotification notification = notif;
|
||||
String title = notif.title.toString();
|
||||
String msg = notif.body.toString();
|
||||
String iosImg = (notification.apple != null && notification.apple!.imageUrl != null) ? notification.apple!.imageUrl! : "";
|
||||
String androidImg = (notification.android != null && notification.android!.imageUrl != null) ? notification.android!.imageUrl! : "";
|
||||
if (title != '' && msg != '') {
|
||||
if (Platform.isIOS) {
|
||||
(iosImg != "") ? generateImageNotification(title, msg, notification.apple!.imageUrl!, '') : generateSimpleNotification(title, msg, '');
|
||||
}
|
||||
if (Platform.isAndroid) {
|
||||
(androidImg != "") ? generateImageNotification(title, msg, notification.android!.imageUrl!, '') : generateSimpleNotification(title, msg, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//for android 13 - notification permission
|
||||
flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()?.requestNotificationsPermission();
|
||||
|
||||
InitializationSettings initializationSettings = const InitializationSettings(android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
|
||||
|
||||
await flutterLocalNotificationsPlugin.initialize(
|
||||
initializationSettings,
|
||||
onDidReceiveNotificationResponse: (NotificationResponse notificationResponse) {
|
||||
switch (notificationResponse.notificationResponseType) {
|
||||
case NotificationResponseType.selectedNotification:
|
||||
selectNotificationPayload(notificationResponse.payload!);
|
||||
break;
|
||||
case NotificationResponseType.selectedNotificationAction:
|
||||
break;
|
||||
}
|
||||
},
|
||||
onDidReceiveBackgroundNotificationResponse: backgroundMessage,
|
||||
);
|
||||
messaging.getInitialMessage().then((RemoteMessage? message) async {
|
||||
if (message != null && message.data.isNotEmpty) {
|
||||
isNotificationReceivedInbg = true;
|
||||
notificationNewsId = message.data[NEWS_ID];
|
||||
saleNotification = message.data['sale'] ?? '';
|
||||
}
|
||||
});
|
||||
|
||||
_startForegroundService();
|
||||
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
||||
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
|
||||
await notificationHandler(message);
|
||||
});
|
||||
|
||||
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
|
||||
redirectToNewsDetailsScreen(message, context);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _startForegroundService() async {
|
||||
const AndroidNotificationDetails androidNotificationDetails =
|
||||
AndroidNotificationDetails('YOUR_PACKAGE_NAME_HERE', 'news', channelDescription: 'your channel description', importance: Importance.max, priority: Priority.high, ticker: 'ticker');
|
||||
await flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.startForegroundService(1, 'plain title', 'plain body', notificationDetails: androidNotificationDetails, payload: '');
|
||||
}
|
||||
|
||||
selectNotificationPayload(String? payload) async {
|
||||
if (payload != null && payload.isNotEmpty && payload != "0") {
|
||||
context.read<NewsByIdCubit>().getNewsById(newsId: payload, langId: context.read<AppLocalizationCubit>().state.id).then((value) {
|
||||
UiUtils.rootNavigatorKey.currentState!.pushNamed(Routes.newsDetails, arguments: {"model": value[0], "isFromBreak": false, "fromShowMore": false});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _downloadAndSaveImage(String url, String fileName) async {
|
||||
if (url.isNotEmpty && url != "null") {
|
||||
final Directory directory = await getApplicationDocumentsDirectory();
|
||||
final String filePath = '${directory.path}/$fileName';
|
||||
final Response response = await get(Uri.parse(url));
|
||||
final File file = File(filePath);
|
||||
await file.writeAsBytes(response.bodyBytes);
|
||||
return filePath;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> generateImageNotification(String title, String msg, String image, String type) async {
|
||||
var largeIconPath = await _downloadAndSaveImage(image, Platform.isAndroid ? 'largeIcon' : 'largeIcon.png');
|
||||
var bigPicturePath = await _downloadAndSaveImage(image, Platform.isAndroid ? 'bigPicture' : 'bigPicture.png');
|
||||
var bigPictureStyleInformation =
|
||||
BigPictureStyleInformation(FilePathAndroidBitmap(bigPicturePath), hideExpandedLargeIcon: true, contentTitle: title, htmlFormatContentTitle: true, summaryText: msg, htmlFormatSummaryText: true);
|
||||
var androidPlatformChannelSpecifics = AndroidNotificationDetails('big text channel id', 'big text channel name',
|
||||
channelDescription: 'big text channel description', largeIcon: FilePathAndroidBitmap(largeIconPath), styleInformation: bigPictureStyleInformation);
|
||||
final DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails(
|
||||
categoryIdentifier: "", presentAlert: true, presentSound: true, attachments: <DarwinNotificationAttachment>[DarwinNotificationAttachment(bigPicturePath, hideThumbnail: false)]);
|
||||
var platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics, iOS: darwinNotificationDetails);
|
||||
if (msg != "") await flutterLocalNotificationsPlugin.show(1, title, msg, platformChannelSpecifics, payload: type);
|
||||
}
|
||||
|
||||
Future<void> generateSimpleNotification(String title, String msg, String type) async {
|
||||
var androidPlatformChannelSpecifics = const AndroidNotificationDetails(
|
||||
'YOUR_PACKAGE_NAME_HERE', //your package name
|
||||
'news',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
ticker: 'ticker');
|
||||
DarwinNotificationDetails darwinNotificationDetails = const DarwinNotificationDetails(categoryIdentifier: "", presentAlert: true, presentSound: true);
|
||||
var platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics, iOS: darwinNotificationDetails);
|
||||
if (msg.isNotEmpty) await flutterLocalNotificationsPlugin.show(1, title, msg, platformChannelSpecifics, payload: type);
|
||||
}
|
||||
124
news-app/lib/ui/screens/RSSFeedDetailsScreen.dart
Normal file
124
news-app/lib/ui/screens/RSSFeedDetailsScreen.dart
Normal file
@@ -0,0 +1,124 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:xml2json/xml2json.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class RSSFeedDetailsScreen extends StatefulWidget {
|
||||
String feedUrl;
|
||||
|
||||
RSSFeedDetailsScreen({Key? key, required this.feedUrl}) : super(key: key);
|
||||
|
||||
@override
|
||||
_RSSFeedDetailsScreenState createState() => _RSSFeedDetailsScreenState();
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(builder: (_) => RSSFeedDetailsScreen(feedUrl: arguments['feedUrl']));
|
||||
}
|
||||
}
|
||||
|
||||
class _RSSFeedDetailsScreenState extends State<RSSFeedDetailsScreen> {
|
||||
List<dynamic> _feedItems = [];
|
||||
bool isFeedLoaded = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
fetchFeed();
|
||||
}
|
||||
|
||||
Widget buildFeedItem(Map<String, dynamic> item) {
|
||||
return GestureDetector(
|
||||
onTap: () => _launchURL(item['link'] ?? ''),
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
margin: EdgeInsets.all(8.0),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
((item.containsKey('image') && item['image'] != null) || (item.containsKey('url') && item['image']['url'] != null))
|
||||
? Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 10.0),
|
||||
child: CustomNetworkImage(
|
||||
networkImageUrl: (item['image'] != null) ? item['image'] : item['image']['url'],
|
||||
height: MediaQuery.of(context).size.height * 0.13,
|
||||
width: MediaQuery.of(context).size.width * 0.23,
|
||||
fit: BoxFit.cover),
|
||||
)
|
||||
: SizedBox.shrink(),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CustomTextLabel(text: item['title'] ?? 'No Title', maxLines: 2, textStyle: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||||
SizedBox(height: 8),
|
||||
CustomTextLabel(text: item['description'] ?? 'No Description', maxLines: 3, textStyle: TextStyle(color: borderColor)),
|
||||
SizedBox(height: 8),
|
||||
CustomTextLabel(text: item['pubDate'] ?? 'Unknown Date', textStyle: TextStyle(fontSize: 12, color: Colors.grey[500]))
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _launchURL(String url) async {
|
||||
if (await canLaunch(url)) {
|
||||
await launch(url);
|
||||
} else {
|
||||
throw 'Could not launch $url';
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchFeed() async {
|
||||
final url = widget.feedUrl;
|
||||
try {
|
||||
final response = await http.get(Uri.parse(url));
|
||||
if (response.statusCode == 200) {
|
||||
final xml2json = Xml2Json();
|
||||
xml2json.parse(response.body);
|
||||
|
||||
final jsonString = xml2json.toParker();
|
||||
final jsonData = jsonDecode(jsonString);
|
||||
|
||||
_feedItems = jsonData['rss']['channel']['item'] ?? [];
|
||||
} else {
|
||||
throw Exception('Failed to load feed');
|
||||
}
|
||||
} catch (e) {}
|
||||
isFeedLoaded = true;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: RefreshIndicator(
|
||||
onRefresh: fetchFeed,
|
||||
child: !isFeedLoaded
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: _feedItems.isEmpty
|
||||
? ErrorContainerWidget(errorMsg: ErrorMessageKeys.noDataMessage, onRetry: fetchFeed)
|
||||
: ListView.builder(
|
||||
itemCount: _feedItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
return buildFeedItem(_feedItems[index]);
|
||||
},
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
335
news-app/lib/ui/screens/RSSFeedScreen.dart
Normal file
335
news-app/lib/ui/screens/RSSFeedScreen.dart
Normal file
@@ -0,0 +1,335 @@
|
||||
import 'dart:math';
|
||||
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/categoryCubit.dart';
|
||||
import 'package:news/cubits/rssFeedCubit.dart';
|
||||
import 'package:news/data/models/CategoryModel.dart';
|
||||
import 'package:news/data/models/RSSFeedModel.dart';
|
||||
import 'package:news/ui/screens/AddEditNews/Widgets/customBottomsheet.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class RSSFeedScreen extends StatefulWidget {
|
||||
@override
|
||||
RSSFeedScreenState createState() => RSSFeedScreenState();
|
||||
}
|
||||
|
||||
class RSSFeedScreenState extends State<RSSFeedScreen> {
|
||||
final Set<String> selectedTopics = {};
|
||||
final Random random = Random();
|
||||
late final ScrollController _controller = ScrollController()..addListener(hasMoreRssFeedScrollListener);
|
||||
String? catSel = "", subCatSel = "", subCatSelId, catSelId;
|
||||
int? catIndex, subCatIndex;
|
||||
bool isFilter = true, showSubcat = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getRSSFeed();
|
||||
}
|
||||
|
||||
setAppBar() {
|
||||
return PreferredSize(
|
||||
preferredSize: const Size(double.infinity, 52),
|
||||
child: UiUtils.applyBoxShadow(
|
||||
context: context,
|
||||
child: Padding(
|
||||
padding: EdgeInsetsDirectional.only(top: MediaQuery.of(context).padding.top + 10.0, start: 25, end: 25, bottom: 15),
|
||||
child: Row(children: [
|
||||
CustomTextLabel(
|
||||
text: 'rssFeed',
|
||||
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5),
|
||||
),
|
||||
Spacer(),
|
||||
GestureDetector(child: Icon(Icons.filter_list_rounded), onTap: () => setCategorySubcategoryFilter(context))
|
||||
]),
|
||||
)));
|
||||
}
|
||||
|
||||
setCategorySubcategoryFilter(BuildContext context) {
|
||||
showModalBottomSheet<dynamic>(
|
||||
context: context,
|
||||
elevation: 5.0,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(50), topRight: Radius.circular(50))),
|
||||
builder: (BuildContext context) {
|
||||
return StatefulBuilder(builder: (context, setBottomSheetState) {
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height * 0.35,
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 5, left: 10, right: 10),
|
||||
child: Column(children: [
|
||||
titleWithDropdownButton(),
|
||||
const SizedBox(height: 5),
|
||||
(isFilter) ? setCategoryFilter(setBottomSheetState) : SizedBox.shrink(),
|
||||
(isFilter) ? setSubCategoryFilter(setBottomSheetState) : SizedBox.shrink(),
|
||||
]));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void getRSSFeed() async {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<RSSFeedCubit>().getRSSFeed(langId: context.read<AppLocalizationCubit>().state.id, categoryId: catSelId, subCategoryId: subCatSelId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void hasMoreRssFeedScrollListener() {
|
||||
if (_controller.position.maxScrollExtent == _controller.offset) {
|
||||
if (context.read<RSSFeedCubit>().hasMoreRSSFeed()) {
|
||||
context.read<RSSFeedCubit>().getMoreRSSFeed(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget titleWithDropdownButton() {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
isFilter = !isFilter;
|
||||
setState(() {});
|
||||
},
|
||||
child: Container(
|
||||
width: double.maxFinite,
|
||||
alignment: Alignment.topLeft,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 15),
|
||||
child: Row(
|
||||
children: [
|
||||
CustomTextLabel(text: 'FilterBy', textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold, color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
Spacer(),
|
||||
if (catSelId != null || subCatSelId != null) (isFilter) ? clearFilterButton() : SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: setAppBar(),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: BlocBuilder<RSSFeedCubit, RSSFeedState>(
|
||||
builder: (context, state) {
|
||||
if (state is RSSFeedFetchSuccess) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
getRSSFeed();
|
||||
},
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.only(top: 15),
|
||||
itemCount: state.RSSFeed.length,
|
||||
controller: _controller,
|
||||
itemBuilder: (context, index) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
margin: EdgeInsets.symmetric(vertical: 5),
|
||||
child: buildRSSFeedItem(state.RSSFeed[index], index, state.RSSFeed.length, state.hasMoreFetchError, state.hasMore));
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (state is RSSFeedFetchFailure) {
|
||||
return ErrorContainerWidget(
|
||||
errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage,
|
||||
onRetry: getRSSFeed); //Add category & subcategory id if selected
|
||||
}
|
||||
if (state is RSSFeedFetchInProgress || state is RSSFeedInitial) {
|
||||
return Center(child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor));
|
||||
}
|
||||
return SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildRSSFeedItem(RSSFeedModel feed, int index, int totalCurrentFeeds, bool hasMoreFeedsFetchError, bool hasMore) {
|
||||
if (index == totalCurrentFeeds - 1 && index != 0) {
|
||||
if (hasMore) {
|
||||
if (hasMoreFeedsFetchError) {
|
||||
return const SizedBox.shrink();
|
||||
} else {
|
||||
return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
Navigator.of(context).pushNamed(Routes.rssFeedDetails, arguments: {"feedUrl": feed.feedUrl});
|
||||
},
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height * 0.08,
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(10)), border: Border.all(color: borderColor.withAlpha((0.3 * 255).round()))),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Center(child: SvgPictureWidget(assetName: 'rss_feed', height: 20, width: 10, fit: BoxFit.fill)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(feed.feedName!,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.bold, fontSize: 16),
|
||||
textAlign: TextAlign.start),
|
||||
),
|
||||
)
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget setCategoryFilter(void Function(void Function()) setStater) {
|
||||
return BlocConsumer<CategoryCubit, CategoryState>(listener: (context, state) {
|
||||
if (catSel != "") catIndex = context.read<CategoryCubit>().getCategoryIndex(categoryName: catSel!);
|
||||
}, builder: (context, state) {
|
||||
if (state is CategoryFetchSuccess) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 5),
|
||||
child: InkWell(
|
||||
onTap: () => showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (BuildContext context) {
|
||||
return CustomBottomsheet(
|
||||
context: context,
|
||||
titleTxt: 'selCatLbl',
|
||||
language_id: context.read<AppLocalizationCubit>().state.id,
|
||||
listLength: context.read<CategoryCubit>().getCatList().length,
|
||||
listViewChild: (context, index) {
|
||||
return catListItem(index, context.read<CategoryCubit>().getCatList(), setStater);
|
||||
});
|
||||
}),
|
||||
child: UiUtils.setRowWithContainer(
|
||||
context: context,
|
||||
firstChild: CustomTextLabel(
|
||||
text: (catSel == "" || catSel == null) ? 'catLbl' : catSel!,
|
||||
textStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(color: catSel == "" ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer)),
|
||||
isContentTypeUpload: false),
|
||||
),
|
||||
);
|
||||
}
|
||||
return SizedBox.shrink();
|
||||
});
|
||||
}
|
||||
|
||||
Widget catListItem(int index, List<CategoryModel> catList, void Function(void Function()) setStater) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 10.0),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
subCatSel = "";
|
||||
subCatSelId = null;
|
||||
catSel = catList[index].categoryName!;
|
||||
catIndex = index;
|
||||
catSelId = catList[index].id!;
|
||||
showSubcat = true;
|
||||
});
|
||||
setStater(() {});
|
||||
getRSSFeed();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: UiUtils.setBottomsheetContainer(entryId: catSelId ?? "0", listItem: catList[index].categoryName!, compareTo: catList[index].id!, context: context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget subCatListItem(int index, List<CategoryModel> catList, void Function(void Function()) setStater) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 10.0),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
subCatSel = catList[catIndex!].subData![index].subCatName!;
|
||||
subCatSelId = catList[catIndex!].subData![index].id!;
|
||||
});
|
||||
setStater(() {});
|
||||
getRSSFeed();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: UiUtils.setBottomsheetContainer(
|
||||
entryId: subCatSelId ?? "0", listItem: catList[catIndex!].subData![index].subCatName!, compareTo: catList[catIndex!].subData![index].id!, context: context)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget setSubCategoryFilter(void Function(void Function()) setStater) {
|
||||
if ((catSel != "" && catSel != null) &&
|
||||
(catIndex != null) &&
|
||||
(!catIndex!.isNegative && context.read<CategoryCubit>().getCatList().isNotEmpty) &&
|
||||
(context.read<CategoryCubit>().getCatList()[catIndex!].subData!.isNotEmpty)) {
|
||||
return BlocBuilder<CategoryCubit, CategoryState>(
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 5),
|
||||
child: InkWell(
|
||||
onTap: () => showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (BuildContext context) {
|
||||
return CustomBottomsheet(
|
||||
context: context,
|
||||
titleTxt: 'selSubCatLbl',
|
||||
language_id: context.read<AppLocalizationCubit>().state.id,
|
||||
listLength: context.read<CategoryCubit>().getCatList()[catIndex!].subData!.length,
|
||||
listViewChild: (context, index) => subCatListItem(index, context.read<CategoryCubit>().getCatList(), setStater));
|
||||
}),
|
||||
child: UiUtils.setRowWithContainer(
|
||||
context: context,
|
||||
firstChild: CustomTextLabel(
|
||||
text: (subCatSel == "" || subCatSel == null) ? 'subcatLbl' : subCatSel!,
|
||||
textStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(color: subCatSel == "" ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer)),
|
||||
isContentTypeUpload: false),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
Widget clearFilterButton() {
|
||||
return Center(
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
catSelId = null;
|
||||
catSel = null;
|
||||
subCatSelId = null;
|
||||
subCatSel = null;
|
||||
showSubcat = false;
|
||||
});
|
||||
getRSSFeed(); //call API without Ids
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: CustomTextLabel(
|
||||
text: 'clearFilter',
|
||||
textStyle: TextStyle(decoration: TextDecoration.underline, decorationThickness: 2),
|
||||
)));
|
||||
}
|
||||
}
|
||||
488
news-app/lib/ui/screens/Search.dart
Normal file
488
news-app/lib/ui/screens/Search.dart
Normal file
@@ -0,0 +1,488 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/cubits/categoryCubit.dart';
|
||||
import 'package:news/cubits/tagCubit.dart';
|
||||
import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/data/models/CategoryModel.dart';
|
||||
import 'package:news/data/models/TagModel.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart';
|
||||
import 'package:news/ui/screens/filter/FilterBottomSheet.dart';
|
||||
import 'package:news/ui/screens/filter/widgets/duration_filter_widget.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
import 'package:news/ui/widgets/customBackBtn.dart';
|
||||
import 'package:news/utils/constant.dart';
|
||||
import 'package:news/utils/hiveBoxKeys.dart';
|
||||
import 'package:news/utils/api.dart';
|
||||
import 'package:news/utils/strings.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'dart:async';
|
||||
|
||||
class Search extends StatefulWidget {
|
||||
const Search({super.key});
|
||||
|
||||
@override
|
||||
SearchState createState() => SearchState();
|
||||
}
|
||||
|
||||
bool buildResult = false; //used in 2 classes here _SearchState & _SuggestionList
|
||||
|
||||
class SearchState extends State<Search> with TickerProviderStateMixin {
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
int pos = 0;
|
||||
List<NewsModel> searchList = [];
|
||||
final List<TextEditingController> _controllerList = [];
|
||||
bool isNetworkAvail = true;
|
||||
|
||||
String query = "";
|
||||
int notificationoffset = 0;
|
||||
ScrollController? notificationcontroller;
|
||||
bool notificationisloadmore = true, notificationisgettingdata = false, notificationisnodata = false;
|
||||
|
||||
// Filter related variables
|
||||
bool isFilterApplied = false;
|
||||
List<CategoryModel> filterSelectedCategories = [];
|
||||
List<TagModel> filterSelectedTags = [];
|
||||
DateTime? filterSelectedDate;
|
||||
DurationFilter? filterDurationFilter;
|
||||
|
||||
Timer? _debounce;
|
||||
List<NewsModel> history = [];
|
||||
|
||||
List<String> hisList = [];
|
||||
|
||||
// Apply filters to search results
|
||||
void applyFilters() {
|
||||
notificationoffset = 0;
|
||||
|
||||
getSearchNews(isFilter: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
searchList.clear();
|
||||
|
||||
// Load categories and tags if not already loaded
|
||||
context.read<CategoryCubit>().loadIfFailed(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
context.read<TagCubit>().loadIfFailed(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
|
||||
notificationoffset = 0;
|
||||
|
||||
notificationcontroller = ScrollController(keepScrollOffset: true);
|
||||
notificationcontroller!.addListener(_searchScrollListener);
|
||||
|
||||
_controller.addListener(() {
|
||||
if (_controller.text.isEmpty) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
query = "";
|
||||
});
|
||||
}
|
||||
} else {
|
||||
query = _controller.text.trim();
|
||||
notificationoffset = 0;
|
||||
notificationisnodata = false;
|
||||
buildResult = false;
|
||||
if (query.isNotEmpty) {
|
||||
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
||||
_debounce = Timer(const Duration(milliseconds: 500), () {
|
||||
notificationisloadmore = true;
|
||||
notificationoffset = 0;
|
||||
getSearchNews();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_searchScrollListener() {
|
||||
if (notificationcontroller!.offset >= notificationcontroller!.position.maxScrollExtent && !notificationcontroller!.position.outOfRange) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
getSearchNews();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<String>> getHistory() async {
|
||||
hisList = UiUtils.getDynamicListValue(historyListKey);
|
||||
return hisList;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
notificationcontroller!.dispose();
|
||||
_controller.dispose();
|
||||
for (int i = 0; i < _controllerList.length; i++) {
|
||||
_controllerList[i].dispose();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
PreferredSizeWidget appbar() {
|
||||
return AppBar(
|
||||
leading: const CustomBackButton(horizontalPadding: 15),
|
||||
backgroundColor: Theme.of(context).canvasColor,
|
||||
title: TextField(
|
||||
controller: _controller,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.fromLTRB(0, 15.0, 0, 15.0),
|
||||
hintText: UiUtils.getTranslatedLabel(context, 'search'),
|
||||
hintStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)),
|
||||
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
fillColor: secondaryColor)),
|
||||
titleSpacing: 0,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
_controller.text = '';
|
||||
},
|
||||
icon: Icon(Icons.close, color: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
IconButton(
|
||||
padding: EdgeInsetsDirectional.only(end: 7),
|
||||
onPressed: () async {
|
||||
FocusManager.instance.rootScope.unfocus();
|
||||
final NewsFilterData? result = await showFilterBottomSheet(
|
||||
context: context,
|
||||
isCategoryModeON: (context.read<AppConfigurationCubit>().getCategoryMode() == "1"),
|
||||
initialFilters: NewsFilterData(
|
||||
selectedCategories: filterSelectedCategories,
|
||||
selectedTags: filterSelectedTags,
|
||||
selectedDate: filterSelectedDate,
|
||||
durationFilter: filterDurationFilter,
|
||||
),
|
||||
);
|
||||
|
||||
if (result != null) {
|
||||
setState(() {
|
||||
buildResult = true;
|
||||
isFilterApplied = true;
|
||||
filterSelectedCategories = result.selectedCategories;
|
||||
filterSelectedTags = result.selectedTags;
|
||||
filterSelectedDate = result.selectedDate;
|
||||
filterDurationFilter = result.durationFilter;
|
||||
applyFilters();
|
||||
});
|
||||
} else if (result == null) {
|
||||
setState(() {
|
||||
buildResult = false;
|
||||
isFilterApplied = false;
|
||||
filterSelectedCategories = [];
|
||||
filterSelectedTags = [];
|
||||
filterSelectedDate = null;
|
||||
filterDurationFilter = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.filter_list,
|
||||
color: UiUtils.getColorScheme(context).primaryContainer,
|
||||
))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: appbar(),
|
||||
body: _showContent(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget listItem(int index) {
|
||||
if (_controllerList.length < index + 1) {
|
||||
_controllerList.add(TextEditingController());
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(bottom: 7.0),
|
||||
child: ListTile(
|
||||
title: CustomTextLabel(
|
||||
text: searchList[index].title!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.bold),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
leading: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(7.0), child: CustomNetworkImage(networkImageUrl: searchList[index].image ?? "", height: 80, width: 80, fit: BoxFit.cover, isVideo: false)),
|
||||
onTap: () async {
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
List<NewsModel> addNewsList = [];
|
||||
addNewsList.addAll(searchList);
|
||||
addNewsList.removeAt(index);
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": searchList[index], "newsList": addNewsList, "isFromBreak": false, "fromShowMore": false});
|
||||
}));
|
||||
}
|
||||
|
||||
Future getSearchNews({bool? isFilter}) async {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
try {
|
||||
String latitude = SettingsLocalDataRepository().getLocationCityValues().first;
|
||||
String longitude = SettingsLocalDataRepository().getLocationCityValues().last;
|
||||
|
||||
if (notificationisloadmore || (isFilter ?? false)) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
notificationisloadmore = false;
|
||||
notificationisgettingdata = true;
|
||||
if (notificationoffset == 0) {
|
||||
searchList = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var parameter = {
|
||||
if (query.isNotEmpty) SEARCH: query.trim(),
|
||||
LIMIT: "20",
|
||||
OFFSET: notificationoffset.toString(),
|
||||
LANGUAGE_ID: context.read<AppLocalizationCubit>().state.id,
|
||||
};
|
||||
if (latitude != "null" && longitude != "null") {
|
||||
parameter[LATITUDE] = latitude;
|
||||
parameter[LONGITUDE] = longitude;
|
||||
}
|
||||
|
||||
// Apply filters if present
|
||||
if (filterSelectedCategories.isNotEmpty) {
|
||||
parameter[CATEGORY_ID] = filterSelectedCategories.map((cat) => cat.id).join(',');
|
||||
}
|
||||
if (filterSelectedTags.isNotEmpty) {
|
||||
parameter[TAG_ID] = filterSelectedTags.map((tag) => tag.id).join(',');
|
||||
}
|
||||
if (filterSelectedDate != null) {
|
||||
parameter[DATE] = "${filterSelectedDate!.year}-${filterSelectedDate!.month.toString().padLeft(2, '0')}-${filterSelectedDate!.day.toString().padLeft(2, '0')}";
|
||||
}
|
||||
if (filterDurationFilter != null) {
|
||||
///To map will be called so automatic it will add the parm of selected type
|
||||
parameter.addAll(filterDurationFilter!.toMap());
|
||||
}
|
||||
|
||||
final result = await Api.sendApiRequest(body: parameter, url: Api.getNewsApi);
|
||||
|
||||
bool error = result["error"];
|
||||
int totalResult = result[TOTAL];
|
||||
notificationisgettingdata = false;
|
||||
if (notificationoffset == 0) {
|
||||
if (error == false && totalResult > 0) {
|
||||
notificationisnodata = false;
|
||||
} else {
|
||||
notificationisnodata = true;
|
||||
}
|
||||
}
|
||||
if (error == false && totalResult > 0) {
|
||||
if (mounted) {
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() => setState(() {
|
||||
List mainlist = result[DATA];
|
||||
if (mainlist.isNotEmpty) {
|
||||
List<NewsModel> items = [];
|
||||
List<NewsModel> allItems = [];
|
||||
items.addAll(mainlist.map((data) => NewsModel.fromJson(data)).toList());
|
||||
allItems.addAll(items);
|
||||
|
||||
if (notificationoffset == 0 && (isFilter ?? !buildResult)) {
|
||||
NewsModel element = NewsModel(title: '${UiUtils.getTranslatedLabel(context, 'searchForLbl')} "$query"', image: "", history: false);
|
||||
searchList.insert(0, element);
|
||||
for (int i = 0; i < history.length; i++) {
|
||||
if (history[i].title == query) {
|
||||
searchList.insert(0, history[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (NewsModel item in items) {
|
||||
searchList.where((i) => i.id == item.id).map((obj) {
|
||||
allItems.remove(item);
|
||||
return obj;
|
||||
}).toList();
|
||||
}
|
||||
searchList.addAll(allItems);
|
||||
notificationisloadmore = false;
|
||||
notificationoffset = notificationoffset + limitOfAPIData;
|
||||
} else {
|
||||
notificationisloadmore = false;
|
||||
}
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
notificationisloadmore = false;
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
} on TimeoutException catch (_) {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'somethingMSg'), context);
|
||||
setState(() {
|
||||
notificationisloadmore = false;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
notificationisnodata = true;
|
||||
notificationisloadmore = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
isNetworkAvail = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clearAll() {
|
||||
setState(() {
|
||||
query = _controller.text;
|
||||
notificationoffset = 0;
|
||||
notificationisloadmore = true;
|
||||
searchList.clear();
|
||||
});
|
||||
}
|
||||
|
||||
_showContent() {
|
||||
if (!isFilterApplied && _controller.text == "") {
|
||||
return FutureBuilder<List<String>>(
|
||||
future: getHistory(),
|
||||
builder: (BuildContext context, AsyncSnapshot<List<String>> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
|
||||
final List<String> entities = snapshot.data!;
|
||||
final List<NewsModel> itemList = [];
|
||||
for (int i = 0; i < entities.length; i++) {
|
||||
NewsModel item = NewsModel.history(entities[i]);
|
||||
itemList.add(item);
|
||||
}
|
||||
history.clear();
|
||||
history.addAll(itemList);
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsetsDirectional.only(top: 15.0),
|
||||
child: Column(
|
||||
children: [
|
||||
_SuggestionList(
|
||||
textController: _controller,
|
||||
suggestions: itemList,
|
||||
notificationController: notificationcontroller,
|
||||
getProduct: getSearchNews,
|
||||
clearAll: clearAll,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const Column();
|
||||
}
|
||||
});
|
||||
} else if (buildResult) {
|
||||
return notificationisnodata
|
||||
? const Center(child: CustomTextLabel(text: 'noNews'))
|
||||
: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 15.0),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsetsDirectional.only(bottom: 5, start: 10, end: 10, top: 12),
|
||||
controller: notificationcontroller,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemCount: searchList.length,
|
||||
itemBuilder: (context, index) {
|
||||
NewsModel? item;
|
||||
try {
|
||||
item = searchList.isEmpty ? null : searchList[index];
|
||||
if (notificationisloadmore && index == (searchList.length - 1) && notificationcontroller!.position.pixels <= 0) {
|
||||
getSearchNews();
|
||||
}
|
||||
} on Exception catch (_) {}
|
||||
return item == null ? const SizedBox.shrink() : listItem(index);
|
||||
}),
|
||||
),
|
||||
notificationisgettingdata ? const Padding(padding: EdgeInsetsDirectional.only(top: 5, bottom: 5), child: CircularProgressIndicator()) : const SizedBox.shrink()
|
||||
],
|
||||
));
|
||||
}
|
||||
return notificationisnodata
|
||||
? const Center(child: CustomTextLabel(text: 'noNews'))
|
||||
: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 15.0),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Expanded(child: _SuggestionList(textController: _controller, suggestions: searchList, notificationController: notificationcontroller, getProduct: getSearchNews, clearAll: clearAll)),
|
||||
notificationisgettingdata ? const Padding(padding: EdgeInsetsDirectional.only(top: 5, bottom: 5), child: CircularProgressIndicator()) : const SizedBox.shrink()
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class _SuggestionList extends StatelessWidget {
|
||||
const _SuggestionList({this.suggestions, this.textController, this.notificationController, this.getProduct, this.clearAll});
|
||||
|
||||
final List<NewsModel>? suggestions;
|
||||
final TextEditingController? textController;
|
||||
|
||||
final notificationController;
|
||||
final Function? getProduct, clearAll;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.separated(
|
||||
itemCount: suggestions!.length,
|
||||
shrinkWrap: true,
|
||||
controller: notificationController,
|
||||
separatorBuilder: (BuildContext context, int index) => const Divider(),
|
||||
itemBuilder: (BuildContext context, int i) {
|
||||
final NewsModel suggestion = suggestions![i];
|
||||
|
||||
return ListTile(
|
||||
title: CustomTextLabel(
|
||||
text: suggestion.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.bold),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis),
|
||||
leading: textController!.text.toString().trim().isEmpty || suggestion.history!
|
||||
? const Icon(Icons.history)
|
||||
: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(7.0),
|
||||
child: suggestion.image == ''
|
||||
? Image.asset(UiUtils.getPlaceholderPngPath(), height: 80, width: 80)
|
||||
: CustomNetworkImage(networkImageUrl: suggestion.image!, height: 80, width: 80, fit: BoxFit.cover, isVideo: false)),
|
||||
trailing: SvgPictureWidget(
|
||||
assetName: "searchbar_arrow",
|
||||
height: 11,
|
||||
width: 11,
|
||||
fit: BoxFit.contain,
|
||||
assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), BlendMode.srcIn)),
|
||||
onTap: () async {
|
||||
if (suggestion.title!.startsWith('${UiUtils.getTranslatedLabel(context, 'searchForLbl')} ')) {
|
||||
UiUtils.setDynamicListValue(historyListKey, textController!.text.toString().trim());
|
||||
buildResult = true;
|
||||
clearAll!();
|
||||
getProduct!();
|
||||
} else if (suggestion.history!) {
|
||||
clearAll!();
|
||||
buildResult = true;
|
||||
textController!.text = suggestion.title!;
|
||||
textController!.selection = TextSelection.fromPosition(TextPosition(offset: textController!.text.length));
|
||||
} else {
|
||||
UiUtils.setDynamicListValue(historyListKey, textController!.text.trim());
|
||||
buildResult = false;
|
||||
UiUtils.showInterstitialAds(context: context);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": suggestion, "isFromBreak": false, "fromShowMore": false});
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart';
|
||||
import 'package:news/ui/widgets/breakingVideoItem.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/uiUtils.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/sectionByIdCubit.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/ui/widgets/breakingNewsItem.dart';
|
||||
import 'package:news/ui/widgets/customAppBar.dart';
|
||||
|
||||
class SectionMoreBreakingNewsList extends StatefulWidget {
|
||||
final String sectionId;
|
||||
final String title;
|
||||
|
||||
const SectionMoreBreakingNewsList({super.key, required this.sectionId, required this.title});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _SectionBreakingNewsState();
|
||||
}
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(builder: (_) => SectionMoreBreakingNewsList(sectionId: arguments['sectionId'], title: arguments['title']));
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionBreakingNewsState extends State<SectionMoreBreakingNewsList> {
|
||||
late final ScrollController controller = ScrollController()..addListener(hasMoreSectionScrollListener);
|
||||
Set<String> get locationValue => SettingsLocalDataRepository().getLocationCityValues();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
getSectionByData();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void getSectionByData() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<SectionByIdCubit>().getSectionById(langId: context.read<AppLocalizationCubit>().state.id, sectionId: widget.sectionId, latitude: locationValue.first, longitude: locationValue.last);
|
||||
});
|
||||
}
|
||||
|
||||
void hasMoreSectionScrollListener() {
|
||||
if (controller.position.maxScrollExtent == controller.offset) {
|
||||
if (context.read<SectionByIdCubit>().hasMoreSections() && !(context.read<SectionByIdCubit>().state is SectionByIdFetchInProgress)) {
|
||||
context.read<SectionByIdCubit>().getMoreSectionById(langId: context.read<AppLocalizationCubit>().state.id, sectionId: widget.sectionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_buildSectionBreakingNewsContainer({required BreakingNewsModel model, required String type, required int index, required List<BreakingNewsModel> newsList}) {
|
||||
return type == 'breaking_news' ? BreakNewsItem(model: model, index: index, breakNewsList: newsList) : BreakVideoItem(model: model);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: CustomAppBar(height: 45, isBackBtn: true, label: widget.title, horizontalPad: 15, isConvertText: false),
|
||||
body: BlocBuilder<SectionByIdCubit, SectionByIdState>(
|
||||
builder: (context, state) {
|
||||
if (state is SectionByIdFetchSuccess) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.all(10.0),
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context
|
||||
.read<SectionByIdCubit>()
|
||||
.getSectionById(sectionId: widget.sectionId, langId: context.read<AppLocalizationCubit>().state.id, latitude: locationValue.first, longitude: locationValue.last);
|
||||
},
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: state.breakNewsModel.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildSectionBreakingNewsContainer(
|
||||
model: (state).breakNewsModel[index], type: (state).type, index: index, newsList: (state).type == 'breaking_news' ? state.breakNewsModel : []);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (state is SectionByIdFetchFailure) {
|
||||
return ErrorContainerWidget(
|
||||
errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getSectionByData);
|
||||
}
|
||||
//state is SectionByIdFetchInProgress || state is SectionByIdInitial
|
||||
return ShimmerNewsList(isNews: false);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
104
news-app/lib/ui/screens/SectionMoreNews/SectionMoreNewsList.dart
Normal file
104
news-app/lib/ui/screens/SectionMoreNews/SectionMoreNewsList.dart
Normal file
@@ -0,0 +1,104 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/sectionByIdCubit.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart';
|
||||
import 'package:news/ui/widgets/NewsItem.dart';
|
||||
import 'package:news/ui/widgets/customAppBar.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/ui/widgets/shimmerNewsList.dart';
|
||||
import 'package:news/ui/widgets/videoItem.dart';
|
||||
|
||||
class SectionMoreNewsList extends StatefulWidget {
|
||||
final String sectionId;
|
||||
final String title;
|
||||
|
||||
const SectionMoreNewsList({super.key, required this.sectionId, required this.title});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _SectionNewsState();
|
||||
}
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(builder: (_) => SectionMoreNewsList(sectionId: arguments['sectionId'], title: arguments['title']));
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionNewsState extends State<SectionMoreNewsList> {
|
||||
late final ScrollController controller = ScrollController()..addListener(hasMoreSectionScrollListener);
|
||||
Set<String> get locationValue => SettingsLocalDataRepository().getLocationCityValues();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
getSectionByData();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void getSectionByData() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<SectionByIdCubit>().getSectionById(langId: context.read<AppLocalizationCubit>().state.id, sectionId: widget.sectionId, latitude: locationValue.first, longitude: locationValue.last);
|
||||
});
|
||||
}
|
||||
|
||||
void hasMoreSectionScrollListener() {
|
||||
if (controller.position.maxScrollExtent == controller.offset) {
|
||||
if (context.read<SectionByIdCubit>().hasMoreSections() && !(context.read<SectionByIdCubit>().state is SectionByIdFetchInProgress)) {
|
||||
context.read<SectionByIdCubit>().getMoreSectionById(langId: context.read<AppLocalizationCubit>().state.id, sectionId: widget.sectionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
_buildSectionNewsContainer({required NewsModel model, required String type, required int index, required List<NewsModel> newsList}) {
|
||||
return (type == 'news' || type == 'user_choice') ? NewsItem(model: model, index: index, newslist: newsList, fromShowMore: false) : VideoItem(model: model);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: CustomAppBar(height: 45, isBackBtn: true, label: widget.title, horizontalPad: 15, isConvertText: false),
|
||||
body: BlocBuilder<SectionByIdCubit, SectionByIdState>(
|
||||
builder: (context, state) {
|
||||
if (state is SectionByIdFetchSuccess) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(vertical: 10),
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context
|
||||
.read<SectionByIdCubit>()
|
||||
.getSectionById(sectionId: widget.sectionId, langId: context.read<AppLocalizationCubit>().state.id, latitude: locationValue.first, longitude: locationValue.last);
|
||||
},
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: state.newsModel.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildSectionNewsContainer(
|
||||
model: (state).newsModel[index], type: (state).type, index: index, newsList: ((state).type == 'news' || state.type == 'user_choice') ? state.newsModel : []);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (state is SectionByIdFetchFailure) {
|
||||
return ErrorContainerWidget(
|
||||
errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getSectionByData);
|
||||
}
|
||||
//state is SectionByIdFetchInProgress || state is SectionByIdInitial
|
||||
return ShimmerNewsList(isNews: true);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
138
news-app/lib/ui/screens/SubCategory/SubCategoryScreen.dart
Normal file
138
news-app/lib/ui/screens/SubCategory/SubCategoryScreen.dart
Normal 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);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
197
news-app/lib/ui/screens/SubCategory/Widgets/SubCatNewsList.dart
Normal file
197
news-app/lib/ui/screens/SubCategory/Widgets/SubCatNewsList.dart
Normal 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;
|
||||
}
|
||||
@@ -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()),
|
||||
));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}),
|
||||
)
|
||||
],
|
||||
)));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
})),
|
||||
],
|
||||
)));
|
||||
}
|
||||
281
news-app/lib/ui/screens/TagNewsScreen.dart
Normal file
281
news-app/lib/ui/screens/TagNewsScreen.dart
Normal file
@@ -0,0 +1,281 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/ui/widgets/customAppBar.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/shimmerNewsList.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/tagNewsCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/Bookmark/bookmarkCubit.dart';
|
||||
import 'package:news/cubits/Bookmark/UpdateBookmarkCubit.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/Settings/settingsLocalDataRepository.dart';
|
||||
import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart';
|
||||
|
||||
class NewsTag extends StatefulWidget {
|
||||
final String tagId;
|
||||
final String tagName;
|
||||
|
||||
const NewsTag({super.key, required this.tagId, required this.tagName});
|
||||
|
||||
@override
|
||||
NewsTagState createState() => NewsTagState();
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(builder: (_) => NewsTag(tagId: arguments['tagId'], tagName: arguments['tagName']));
|
||||
}
|
||||
}
|
||||
|
||||
class NewsTagState extends State<NewsTag> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getTagWiseNews();
|
||||
}
|
||||
|
||||
getTagWiseNews() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<TagNewsCubit>().getTagNews(
|
||||
tagId: widget.tagId,
|
||||
langId: context.read<AppLocalizationCubit>().state.id,
|
||||
latitude: SettingsLocalDataRepository().getLocationCityValues().first,
|
||||
longitude: SettingsLocalDataRepository().getLocationCityValues().last);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(appBar: CustomAppBar(isConvertText: false, height: 45, isBackBtn: true, label: widget.tagName, horizontalPad: 15), body: viewContent());
|
||||
}
|
||||
|
||||
newsItem(NewsModel model, int index) {
|
||||
List<String> tagList = [];
|
||||
List<String> tagId = [];
|
||||
|
||||
DateTime time1 = DateTime.parse(model.publishDate ?? model.date!);
|
||||
|
||||
if (model.tagName! != "") {
|
||||
final tagName = model.tagName!;
|
||||
tagList = tagName.split(',');
|
||||
}
|
||||
|
||||
if (model.tagId! != "") tagId = model.tagId!.split(",");
|
||||
|
||||
return Builder(builder: (context) {
|
||||
bool isLike = context.read<LikeAndDisLikeCubit>().isNewsLikeAndDisLike(model.id!);
|
||||
return Padding(
|
||||
padding: EdgeInsetsDirectional.only(top: index == 0 ? 0 : MediaQuery.of(context).size.height / 25.0),
|
||||
child: Column(children: <Widget>[
|
||||
InkWell(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).size.height / 4.2,
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomLeft,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
child: CustomNetworkImage(networkImageUrl: model.image!, width: double.maxFinite, height: MediaQuery.of(context).size.height / 4.2, isVideo: false, fit: BoxFit.cover)),
|
||||
if (model.tagName! != "")
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 5.0, left: 5.0, right: 5.0),
|
||||
child: SizedBox(
|
||||
height: 16,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
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: 9.5),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
onTap: () async {
|
||||
Navigator.of(context).pushNamed(Routes.tagScreen, arguments: {"tagId": tagId[index], "tagName": tagList[index]});
|
||||
},
|
||||
));
|
||||
})),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0),
|
||||
child: CustomTextLabel(
|
||||
text: model.title!,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: Theme.of(context).colorScheme.primaryContainer.withOpacity(0.9)),
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0),
|
||||
child: CustomTextLabel(
|
||||
text: UiUtils.convertToAgo(context, time1, 0)!,
|
||||
textStyle: Theme.of(context).textTheme.bodySmall!.copyWith(color: Theme.of(context).colorScheme.primaryContainer.withOpacity(0.6)))),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 13.0),
|
||||
child: InkWell(
|
||||
child: const Icon(Icons.share_rounded),
|
||||
onTap: () async {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
UiUtils.shareNews(context: context, slug: model.slug!, title: model.title!, isVideo: false, videoId: "", isBreakingNews: false, isNews: true);
|
||||
} else {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(width: MediaQuery.of(context).size.width / 99.0),
|
||||
BlocProvider(
|
||||
create: (context) => UpdateBookmarkStatusCubit(BookmarkRepository()),
|
||||
child: BlocBuilder<BookmarkCubit, BookmarkState>(
|
||||
bloc: context.read<BookmarkCubit>(),
|
||||
builder: (context, bookmarkState) {
|
||||
bool isBookmark = context.read<BookmarkCubit>().isNewsBookmark(model.id!);
|
||||
return BlocConsumer<UpdateBookmarkStatusCubit, UpdateBookmarkStatusState>(
|
||||
bloc: context.read<UpdateBookmarkStatusCubit>(),
|
||||
listener: ((context, state) {
|
||||
if (state is UpdateBookmarkStatusSuccess) {
|
||||
if (state.wasBookmarkNewsProcess) {
|
||||
context.read<BookmarkCubit>().addBookmarkNews(state.news);
|
||||
} else {
|
||||
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: model, status: (isBookmark) ? "0" : "1");
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: EdgeInsetsDirectional.zero,
|
||||
child: InkWell(
|
||||
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))));
|
||||
});
|
||||
}),
|
||||
),
|
||||
SizedBox(width: MediaQuery.of(context).size.width / 99.0),
|
||||
BlocProvider(
|
||||
create: (context) => UpdateLikeAndDisLikeStatusCubit(LikeAndDisLikeRepository()),
|
||||
child: BlocConsumer<LikeAndDisLikeCubit, LikeAndDisLikeState>(
|
||||
bloc: context.read<LikeAndDisLikeCubit>(),
|
||||
listener: ((context, state) {
|
||||
isLike = context.read<LikeAndDisLikeCubit>().isNewsLikeAndDisLike(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);
|
||||
state.news.totalLikes = (!isLike)
|
||||
? (int.parse(state.news.totalLikes.toString()) + 1).toString()
|
||||
: (state.news.totalLikes!.isNotEmpty)
|
||||
? (int.parse(state.news.totalLikes.toString()) - 1).toString()
|
||||
: "0";
|
||||
}
|
||||
}),
|
||||
builder: (context, state) {
|
||||
isLike = context.read<LikeAndDisLikeCubit>().isNewsLikeAndDisLike(model.id!);
|
||||
return InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
onTap: () {
|
||||
if (context.read<AuthCubit>().getUserId() != "0") {
|
||||
if (state is UpdateLikeAndDisLikeStatusInProgress) {
|
||||
return;
|
||||
}
|
||||
context.read<UpdateLikeAndDisLikeStatusCubit>().setLikeAndDisLikeNews(news: model, status: (isLike) ? "0" : "1");
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
},
|
||||
child: (state is UpdateLikeAndDisLikeStatusInProgress)
|
||||
? SizedBox(height: 20, width: 20, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))
|
||||
: isLike
|
||||
? const Icon(Icons.thumb_up_alt)
|
||||
: const Icon(Icons.thumb_up_off_alt));
|
||||
});
|
||||
})),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
if (index % 3 == 0) UiUtils.showInterstitialAds(context: context);
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": model, "isFromBreak": false, "fromShowMore": false});
|
||||
},
|
||||
),
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
viewContent() {
|
||||
return BlocBuilder<TagNewsCubit, TagNewsState>(builder: (context, state) {
|
||||
if (state is TagNewsFetchSuccess) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 10.0, bottom: 10.0, start: 13.0, end: 13.0),
|
||||
child: ListView.builder(
|
||||
itemCount: state.tagNews.length,
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemBuilder: ((context, index) {
|
||||
return newsItem((state).tagNews[index], index);
|
||||
})));
|
||||
}
|
||||
if (state is TagNewsFetchFailure) {
|
||||
return ErrorContainerWidget(
|
||||
errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getTagWiseNews);
|
||||
}
|
||||
return ShimmerNewsList(isNews: true);
|
||||
});
|
||||
}
|
||||
}
|
||||
229
news-app/lib/ui/screens/Videos/VideoScreen.dart
Normal file
229
news-app/lib/ui/screens/Videos/VideoScreen.dart
Normal file
@@ -0,0 +1,229 @@
|
||||
import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/cubits/videosCubit.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/customAppBar.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/ui/widgets/videoItem.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:news/utils/constant.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class VideoScreen extends StatefulWidget {
|
||||
const VideoScreen({super.key});
|
||||
|
||||
@override
|
||||
VideoScreenState createState() => VideoScreenState();
|
||||
|
||||
static Route<dynamic> route(RouteSettings routeSettings) {
|
||||
return CupertinoPageRoute(builder: (_) => const VideoScreen());
|
||||
}
|
||||
}
|
||||
|
||||
class VideoScreenState extends State<VideoScreen> {
|
||||
late final PageController _videoScrollController = PageController()..addListener(hasMoreVideoScrollListener);
|
||||
|
||||
int currentIndex = 0;
|
||||
int totalItems = 0;
|
||||
String? initializedVideoId;
|
||||
late String latitude, longitude;
|
||||
VideoViewType? videoViewType;
|
||||
void getVideos() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<VideoCubit>().getVideo(langId: context.read<AppLocalizationCubit>().state.id, latitude: latitude, longitude: longitude);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
videoViewType = context.read<AppConfigurationCubit>().getVideoTypePreference();
|
||||
|
||||
setLatitudeLongitude();
|
||||
getVideos();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_videoScrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void setLatitudeLongitude() {
|
||||
latitude = SettingsLocalDataRepository().getLocationCityValues().first;
|
||||
longitude = SettingsLocalDataRepository().getLocationCityValues().last;
|
||||
}
|
||||
|
||||
void hasMoreVideoScrollListener() {
|
||||
if (_videoScrollController.offset >= _videoScrollController.position.maxScrollExtent && !_videoScrollController.position.outOfRange) {
|
||||
if (context.read<VideoCubit>().hasMoreVideo()) {
|
||||
context.read<VideoCubit>().getMoreVideo(langId: context.read<AppLocalizationCubit>().state.id, latitude: latitude, longitude: longitude);
|
||||
} else {}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(appBar: CustomAppBar(height: 44, isBackBtn: false, label: 'videosLbl', isConvertText: true), body: _buildVideos(videoViewType ?? VideoViewType.normal));
|
||||
}
|
||||
|
||||
Widget _buildVideos(VideoViewType type) {
|
||||
return BlocBuilder<VideoCubit, VideoState>(builder: (context, state) {
|
||||
if (state is VideoFetchSuccess) {
|
||||
totalItems = state.video.length;
|
||||
if (type == VideoViewType.page) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
getVideos();
|
||||
},
|
||||
child: PageView.builder(
|
||||
controller: _videoScrollController,
|
||||
scrollDirection: Axis.vertical,
|
||||
physics: PageScrollPhysics(),
|
||||
itemCount: totalItems,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildVideoContainer(
|
||||
video: state.video[index], hasMore: state.hasMore, hasMoreVideoFetchError: state.hasMoreFetchError, index: index, totalCurrentVideo: state.video.length);
|
||||
}));
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 15.0),
|
||||
child: ListView.separated(
|
||||
controller: _videoScrollController,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildHorizontalViewContainer(videosList: state.video, video: state.video[index], index: index, totalCurrentVideo: state.video.length);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 16);
|
||||
},
|
||||
itemCount: state.video.length),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (state is VideoFetchFailure) {
|
||||
return ErrorContainerWidget(errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getVideos);
|
||||
}
|
||||
return SizedBox.shrink();
|
||||
});
|
||||
}
|
||||
|
||||
_buildVideoContainer({required NewsModel video, required int index, required int totalCurrentVideo, required bool hasMoreVideoFetchError, required bool hasMore}) {
|
||||
if (index == totalCurrentVideo - 1 && index != 0) {
|
||||
if (hasMore) {
|
||||
if (hasMoreVideoFetchError) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
context.read<VideoCubit>().getMoreVideo(
|
||||
langId: context.read<AppLocalizationCubit>().state.id,
|
||||
latitude: SettingsLocalDataRepository().getLocationCityValues().first,
|
||||
longitude: SettingsLocalDataRepository().getLocationCityValues().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 VideoItem(model: video);
|
||||
}
|
||||
|
||||
Widget _buildHorizontalViewContainer({required List<NewsModel> videosList, required NewsModel video, required int index, required int totalCurrentVideo}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: VideoNewsCard(
|
||||
video: video,
|
||||
videosList: videosList,
|
||||
onInitializeVideo: () {
|
||||
initializedVideoId = video.id;
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoNewsCard extends StatefulWidget {
|
||||
final NewsModel video;
|
||||
final List<NewsModel> videosList;
|
||||
const VideoNewsCard({super.key, required this.videosList, required this.video, required this.onInitializeVideo});
|
||||
final void Function() onInitializeVideo;
|
||||
|
||||
@override
|
||||
VideoNewsCardState createState() => VideoNewsCardState();
|
||||
}
|
||||
|
||||
class VideoNewsCardState extends State<VideoNewsCard> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(color: UiUtils.getColorScheme(context).surface, borderRadius: BorderRadius.circular(8)),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
List<NewsModel> videosList = List.from(widget.videosList)..removeWhere((x) => x.id == widget.video.id);
|
||||
Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": widget.video, "otherVideos": videosList});
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Container(height: 192, color: borderColor, child: _buildThumbnail()),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 8,
|
||||
children: [
|
||||
CustomTextLabel(text: widget.video.title!, textStyle: TextStyle(fontWeight: FontWeight.bold)),
|
||||
if (widget.video.date != null && widget.video.date!.isNotEmpty)
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [Icon(Icons.calendar_month_rounded), CustomTextLabel(text: UiUtils.formatDate(widget.video.date ?? ''))],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildThumbnail() {
|
||||
return Container(
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
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.video.image ?? ''),
|
||||
)),
|
||||
Center(
|
||||
child: Icon(Icons.play_circle_outline_rounded, size: 50, color: backgroundColor),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
93
news-app/lib/ui/screens/Videos/Widgets/otherVideosCard.dart
Normal file
93
news-app/lib/ui/screens/Videos/Widgets/otherVideosCard.dart
Normal file
@@ -0,0 +1,93 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/data/models/LiveStreamingModel.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 OtherVideosCard extends StatelessWidget {
|
||||
final NewsModel? model;
|
||||
final BreakingNewsModel? brModel;
|
||||
final LiveStreamingModel? liveModel;
|
||||
OtherVideosCard({super.key, this.model, this.brModel, this.liveModel});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isLive = liveModel != null;
|
||||
return InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
onTap: () {
|
||||
bool isNews = (model != null) ? true : false;
|
||||
bool isBreakingNews = (brModel != null) ? true : false;
|
||||
int fromVal = (isNews) ? 1 : (isBreakingNews ? 2 : 3); // VAL 1 = news, 2 = liveNews , 3 = breakingNews
|
||||
// and pass current model accordingly.Do not pass other videos - set it from newsVideo screen only
|
||||
Map<String, dynamic> videoArguments = {"from": fromVal};
|
||||
if (fromVal == 1) {
|
||||
videoArguments.addAll({"model": model});
|
||||
} else if (fromVal == 2) {
|
||||
videoArguments.addAll({"brModel": brModel});
|
||||
} else {
|
||||
videoArguments.addAll({"liveModel": liveModel});
|
||||
}
|
||||
Navigator.pushReplacementNamed(context, Routes.newsVideo, arguments: videoArguments);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 6),
|
||||
decoration: BoxDecoration(color: UiUtils.getColorScheme(context).surface, borderRadius: BorderRadius.circular(12), border: Border.all(color: dividerColor.withAlpha(70))),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: CustomNetworkImage(
|
||||
networkImageUrl: (isLive)
|
||||
? liveModel!.image!
|
||||
: (model != null)
|
||||
? model!.image!
|
||||
: brModel!.image!,
|
||||
height: 60,
|
||||
width: 90,
|
||||
fit: BoxFit.cover)),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
(!isLive && (model != null && model!.categoryName != null && model!.categoryName!.isNotEmpty))
|
||||
? Container(
|
||||
height: 25.0,
|
||||
width: 65,
|
||||
alignment: Alignment.center,
|
||||
margin: const EdgeInsetsDirectional.only(start: 2.0, end: 2.0, top: 1.0, bottom: 1.0),
|
||||
decoration: BoxDecoration(
|
||||
color: UiUtils.getColorScheme(context).onPrimary.withAlpha(40),
|
||||
border: Border.all(color: UiUtils.getColorScheme(context).secondary.withOpacity(0.85)),
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
child: CustomTextLabel(
|
||||
text: model!.categoryName ?? "",
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).onPrimary, fontWeight: FontWeight.bold, fontSize: 12),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true))
|
||||
: SizedBox.shrink(),
|
||||
SizedBox(height: 4),
|
||||
CustomTextLabel(
|
||||
text: (isLive)
|
||||
? liveModel!.title!
|
||||
: (model != null)
|
||||
? model!.title ?? ""
|
||||
: brModel!.title ?? "",
|
||||
textStyle: TextStyle(color: UiUtils.getColorScheme(context).onPrimary),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
271
news-app/lib/ui/screens/Videos/Widgets/videoCard.dart
Normal file
271
news-app/lib/ui/screens/Videos/Widgets/videoCard.dart
Normal file
@@ -0,0 +1,271 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:news/ui/styles/colors.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/LikeAndDislikeNews/LikeAndDislikeCubit.dart';
|
||||
import 'package:news/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/data/models/LiveStreamingModel.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/widgets/SnackBarWidget.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/videoPlayContainer.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:news/utils/strings.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class VideoCard extends StatefulWidget {
|
||||
final NewsModel? model;
|
||||
final BreakingNewsModel? brModel;
|
||||
final LiveStreamingModel? liveModel;
|
||||
VideoCard({super.key, this.model, this.liveModel, this.brModel});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => VideoCardState();
|
||||
}
|
||||
|
||||
class VideoCardState extends State<VideoCard> {
|
||||
late NewsModel? model;
|
||||
late BreakingNewsModel? brModel;
|
||||
late LiveStreamingModel? liveModel;
|
||||
bool isLiveVideo = false, isBreakingVideo = false;
|
||||
List<String>? tagList = [];
|
||||
List<String>? tagId = [];
|
||||
String formattedDate = "", contentType = "", contentValue = "", titleTxt = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
model = widget.model;
|
||||
brModel = widget.brModel;
|
||||
liveModel = widget.liveModel;
|
||||
isLiveVideo = (liveModel != null);
|
||||
isBreakingVideo = (brModel != null);
|
||||
setFormattedDate();
|
||||
setTitle();
|
||||
setContentValueAndContentType();
|
||||
setTags();
|
||||
}
|
||||
|
||||
void setTitle() {
|
||||
titleTxt = (isLiveVideo)
|
||||
? liveModel?.title ?? ""
|
||||
: (isBreakingVideo)
|
||||
? brModel?.title ?? ""
|
||||
: model?.title ?? "";
|
||||
}
|
||||
|
||||
void setTags() {
|
||||
if (model != null && model?.tagName != null && (model!.sourceType != null && model!.sourceType != BREAKING_NEWS)) {
|
||||
if (model?.tagId != null && model!.tagId!.isNotEmpty) {
|
||||
tagId = model?.tagId?.split(",");
|
||||
}
|
||||
|
||||
if (model!.tagName!.isNotEmpty) {
|
||||
final tagName = model?.tagName!;
|
||||
tagList = tagName?.split(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setFormattedDate() {
|
||||
String dateVal = (isLiveVideo) ? liveModel!.updatedDate ?? "" : (model?.publishDate ?? model?.date ?? "");
|
||||
if (dateVal.isNotEmpty) {
|
||||
DateTime parsedDate = DateFormat("yyyy-MM-dd").parse(dateVal);
|
||||
formattedDate = DateFormat("MMM dd, yyyy").format(parsedDate);
|
||||
}
|
||||
}
|
||||
|
||||
void setContentValueAndContentType() {
|
||||
contentType = (isLiveVideo) ? liveModel?.type ?? "" : ((model != null) ? model?.contentType ?? "" : brModel!.contentType ?? "");
|
||||
contentValue = (isLiveVideo)
|
||||
? liveModel?.url ?? ""
|
||||
: (model != null)
|
||||
? model?.contentValue ?? ""
|
||||
: brModel!.contentValue ?? "";
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget likeButton() {
|
||||
bool isLike = context.read<LikeAndDisLikeCubit>().isNewsLikeAndDisLike(widget.model?.newsId ?? "0");
|
||||
|
||||
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(model?.newsId ?? "0");
|
||||
} else {
|
||||
isLike = false; //in case of failue - no other likes found
|
||||
}
|
||||
}),
|
||||
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: model ?? NewsModel(), status: (isLike) ? "0" : "1");
|
||||
} else {
|
||||
UiUtils.loginRequired(context);
|
||||
}
|
||||
},
|
||||
child: designButtons(
|
||||
childWidget: (state is UpdateLikeAndDisLikeStatusInProgress)
|
||||
? SizedBox(height: 15, width: 15, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))
|
||||
: ((isLike)
|
||||
? Icon(Icons.thumb_up_alt, size: 25, color: UiUtils.getColorScheme(context).onPrimary)
|
||||
: Icon(Icons.thumb_up_off_alt, size: 25, color: UiUtils.getColorScheme(context).onPrimary))));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
return Container(
|
||||
decoration: BoxDecoration(color: UiUtils.getColorScheme(context).surface, borderRadius: BorderRadius.circular(12)),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: AspectRatio(aspectRatio: 16 / 9, child: VideoPlayContainer(contentType: contentType, contentValue: contentValue)),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
(model != null && model!.sourceType != BREAKING_NEWS)
|
||||
? Wrap(
|
||||
spacing: 8,
|
||||
children: tagList!
|
||||
.map((tag) => tag.trim())
|
||||
.where((tag) => tag.isNotEmpty)
|
||||
.map(
|
||||
(tag) => InkWell(
|
||||
onTap: () async {
|
||||
Navigator.of(context).pushNamed(Routes.tagScreen, arguments: {"tagId": tagId, "tagName": tagList});
|
||||
},
|
||||
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: tag,
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontSize: 12),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
)
|
||||
: SizedBox.shrink(),
|
||||
const SizedBox(height: 10),
|
||||
CustomTextLabel(text: titleTxt, textStyle: TextStyle(color: UiUtils.getColorScheme(context).onPrimary)),
|
||||
const SizedBox(height: 10),
|
||||
Divider(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
(formattedDate.isNotEmpty)
|
||||
? Row(
|
||||
children: [
|
||||
Icon(Icons.calendar_month_rounded, size: 20),
|
||||
SizedBox(width: 4),
|
||||
CustomTextLabel(text: formattedDate, textStyle: TextStyle(fontSize: 12, color: UiUtils.getColorScheme(context).onPrimary)),
|
||||
],
|
||||
)
|
||||
: SizedBox.shrink(),
|
||||
Row(
|
||||
children: [
|
||||
if ((model?.sourceType != null && model?.newsId != null || model?.id != null) || (model?.sourceType == NEWS || model?.sourceType == VIDEOS))
|
||||
Row(children: [
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
(await InternetConnectivity.isNetworkAvailable())
|
||||
? UiUtils.shareNews(
|
||||
context: context, slug: model?.slug ?? "", title: model?.title ?? "", isVideo: true, videoId: model?.id ?? "0", isBreakingNews: false, isNews: false)
|
||||
: showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context);
|
||||
},
|
||||
splashColor: Colors.transparent,
|
||||
child: designButtons(childWidget: Icon(Icons.share_rounded, color: UiUtils.getColorScheme(context).onPrimary))),
|
||||
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(model?.id ?? "0");
|
||||
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: model!, 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))
|
||||
: designButtons(
|
||||
childWidget: Icon(isBookmark ? Icons.bookmark_added_rounded : Icons.bookmark_add_outlined, color: UiUtils.getColorScheme(context).onPrimary)));
|
||||
});
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
likeButton()
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget designButtons({required Widget childWidget}) {
|
||||
return Container(
|
||||
height: 30,
|
||||
width: 30,
|
||||
margin: EdgeInsets.symmetric(horizontal: 5),
|
||||
padding: EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(7), color: borderColor.withOpacity(0.2)),
|
||||
child: childWidget);
|
||||
}
|
||||
}
|
||||
104
news-app/lib/ui/screens/Videos/videoDetailsScreen.dart
Normal file
104
news-app/lib/ui/screens/Videos/videoDetailsScreen.dart
Normal file
@@ -0,0 +1,104 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/data/models/LiveStreamingModel.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/ui/screens/Videos/Widgets/otherVideosCard.dart';
|
||||
import 'package:news/ui/screens/Videos/Widgets/videoCard.dart';
|
||||
import 'package:news/ui/widgets/customAppBar.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class VideoDetailsScreen extends StatefulWidget {
|
||||
int from;
|
||||
LiveStreamingModel? liveModel;
|
||||
NewsModel? model;
|
||||
BreakingNewsModel? breakModel;
|
||||
List<NewsModel>? otherVideos;
|
||||
List<BreakingNewsModel>? otherBreakingVideos;
|
||||
List<LiveStreamingModel>? otherLiveVideos;
|
||||
|
||||
VideoDetailsScreen({super.key, this.model, required this.from, this.liveModel, this.breakModel, required this.otherVideos, this.otherLiveVideos, this.otherBreakingVideos});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => VideoDetailsState();
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(
|
||||
builder: (_) => VideoDetailsScreen(
|
||||
from: arguments['from'],
|
||||
liveModel: arguments['liveModel'],
|
||||
model: arguments['model'],
|
||||
breakModel: arguments['breakModel'],
|
||||
otherVideos: arguments['otherVideos'],
|
||||
otherLiveVideos: arguments['otherLiveVideos'],
|
||||
otherBreakingVideos: arguments['otherBreakingVideos']));
|
||||
}
|
||||
}
|
||||
|
||||
class VideoDetailsState extends State<VideoDetailsScreen> {
|
||||
bool isLiveVideo = false, isBreakingNewsVideo = false, isNewsVideo = true;
|
||||
//FROM VAL 1 = news, 2 = liveNews , 3 = breakingNews
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
isLiveVideo = (widget.from == 2);
|
||||
isBreakingNewsVideo = (widget.from == 3);
|
||||
isNewsVideo = !(isLiveVideo || isBreakingNewsVideo);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// set screen back to portrait mode
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: UiUtils.getColorScheme(context).surface,
|
||||
appBar: CustomAppBar(height: 45, isBackBtn: true, label: 'videosLbl', isConvertText: true),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
VideoCard(
|
||||
model: (widget.model != null) ? widget.model : null, brModel: (widget.breakModel != null) ? widget.breakModel : null, liveModel: (widget.liveModel != null) ? widget.liveModel : null),
|
||||
const SizedBox(height: 16),
|
||||
((isLiveVideo && widget.otherLiveVideos != null && widget.otherLiveVideos!.isNotEmpty) ||
|
||||
((widget.otherVideos != null && widget.otherVideos!.isNotEmpty) || (widget.otherBreakingVideos != null && widget.otherBreakingVideos!.isNotEmpty)))
|
||||
? Text(
|
||||
UiUtils.getTranslatedLabel(context, 'recentVidLbl'),
|
||||
style: TextStyle(color: UiUtils.getColorScheme(context).onPrimary, fontWeight: FontWeight.bold, fontSize: 18),
|
||||
)
|
||||
: SizedBox.shrink(),
|
||||
const SizedBox(height: 8),
|
||||
((isLiveVideo && widget.otherLiveVideos != null && widget.otherLiveVideos!.isNotEmpty) ||
|
||||
((widget.otherVideos != null && widget.otherVideos!.isNotEmpty) || (widget.otherBreakingVideos != null && widget.otherBreakingVideos!.isNotEmpty)))
|
||||
? ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: (isLiveVideo)
|
||||
? widget.otherLiveVideos!.length
|
||||
: (widget.otherVideos != null && widget.otherVideos!.isNotEmpty)
|
||||
? widget.otherVideos!.length
|
||||
: widget.otherBreakingVideos!.length,
|
||||
itemBuilder: (context, index) => OtherVideosCard(
|
||||
brModel: (widget.otherBreakingVideos != null && widget.otherBreakingVideos!.isNotEmpty) ? widget.otherBreakingVideos![index] : null,
|
||||
model: (widget.otherVideos != null && widget.otherVideos!.isNotEmpty) ? widget.otherVideos![index] : null,
|
||||
liveModel: (widget.otherLiveVideos != null && widget.otherLiveVideos!.isNotEmpty) ? widget.otherLiveVideos![index] : null),
|
||||
)
|
||||
: SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
137
news-app/lib/ui/screens/auth/ForgotPassword.dart
Normal file
137
news-app/lib/ui/screens/auth/ForgotPassword.dart
Normal file
@@ -0,0 +1,137 @@
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart';
|
||||
import 'package:news/ui/widgets/customBackBtn.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/setEmail.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/setLoginAndSignUpBtn.dart';
|
||||
|
||||
class ForgotPassword extends StatefulWidget {
|
||||
const ForgotPassword({super.key});
|
||||
|
||||
@override
|
||||
FrgtPswdState createState() => FrgtPswdState();
|
||||
}
|
||||
|
||||
class FrgtPswdState extends State<ForgotPassword> {
|
||||
TextEditingController emailC = TextEditingController();
|
||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
final GlobalKey<FormState> _formkey = GlobalKey<FormState>();
|
||||
int durationInMiliSeconds = 2500;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
emailC.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(body: screenContent());
|
||||
}
|
||||
|
||||
Widget backBtn() {
|
||||
return const Padding(padding: EdgeInsets.only(top: 20.0, left: 10.0), child: CustomBackButton());
|
||||
}
|
||||
|
||||
Widget forgotIcon() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Center(
|
||||
child: SvgPictureWidget(
|
||||
assetName: "forgot",
|
||||
width: 120,
|
||||
height: 120,
|
||||
fit: BoxFit.fill,
|
||||
assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget forgotPassLbl() {
|
||||
return Center(
|
||||
child: CustomTextLabel(
|
||||
text: 'forgotPassLbl',
|
||||
maxLines: 3,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600, fontSize: 22, color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget forgotPassHead() {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 20.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'frgtPassHead',
|
||||
maxLines: 3,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500, fontSize: 16, color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
));
|
||||
}
|
||||
|
||||
Widget forgotPassSubHead() {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 30.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'forgotPassSub',
|
||||
maxLines: 3,
|
||||
textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.normal, fontSize: 14.0, color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
));
|
||||
}
|
||||
|
||||
Widget emailTextCtrl() {
|
||||
return SetEmail(emailC: emailC, email: emailC.text, topPad: 25);
|
||||
}
|
||||
|
||||
Widget submitBtn() {
|
||||
return SetLoginAndSignUpBtn(
|
||||
onTap: () async {
|
||||
FocusScope.of(context).unfocus(); //dismiss keyboard
|
||||
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
Future.delayed(const Duration(seconds: 1)).then((_) async {
|
||||
if (emailC.text.isEmpty) {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'emailValid'), context, durationInMiliSeconds: durationInMiliSeconds);
|
||||
} else {
|
||||
try {
|
||||
await _auth.sendPasswordResetEmail(email: emailC.text.trim());
|
||||
final form = _formkey.currentState;
|
||||
form!.save();
|
||||
if (form.validate()) {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'passReset'), context, durationInMiliSeconds: durationInMiliSeconds);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
} on FirebaseAuthException catch (e) {
|
||||
if (e.code == "user-not-found") {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'userNotFound'), context, durationInMiliSeconds: durationInMiliSeconds);
|
||||
} else {
|
||||
showSnackBar(e.message!, context, durationInMiliSeconds: durationInMiliSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context, durationInMiliSeconds: durationInMiliSeconds);
|
||||
}
|
||||
},
|
||||
text: 'submitBtn',
|
||||
topPad: 30);
|
||||
}
|
||||
|
||||
Widget screenContent() {
|
||||
return Container(
|
||||
padding: const EdgeInsetsDirectional.all(20.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Form(
|
||||
key: _formkey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [backBtn(), const SizedBox(height: 50), forgotIcon(), forgotPassLbl(), forgotPassHead(), forgotPassSubHead(), emailTextCtrl(), submitBtn()],
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
263
news-app/lib/ui/screens/auth/RequestOtpScreen.dart
Normal file
263
news-app/lib/ui/screens/auth/RequestOtpScreen.dart
Normal file
@@ -0,0 +1,263 @@
|
||||
import 'package:country_code_picker/country_code_picker.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:news/utils/validators.dart';
|
||||
|
||||
class RequestOtp extends StatefulWidget {
|
||||
const RequestOtp({super.key});
|
||||
|
||||
@override
|
||||
RequestOtpState createState() => RequestOtpState();
|
||||
}
|
||||
|
||||
class RequestOtpState extends State<RequestOtp> {
|
||||
TextEditingController phoneC = TextEditingController();
|
||||
String? phone, conCode;
|
||||
final GlobalKey<FormState> _formkey = GlobalKey<FormState>();
|
||||
bool isLoading = false;
|
||||
CountryCode? code;
|
||||
String? verificationId;
|
||||
String errorMessage = '';
|
||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
int forceResendingToken = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: <Widget>[
|
||||
SafeArea(
|
||||
child: showContent(),
|
||||
),
|
||||
UiUtils.showCircularProgress(isLoading, Theme.of(context).primaryColor)
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
//show form content
|
||||
showContent() {
|
||||
return Container(
|
||||
padding: const EdgeInsetsDirectional.all(20.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Form(
|
||||
key: _formkey,
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
|
||||
Align(
|
||||
//backButton
|
||||
alignment: Alignment.topLeft,
|
||||
child: InkWell(onTap: () => Navigator.of(context).pop(), splashColor: Colors.transparent, child: const Icon(Icons.keyboard_backspace_rounded))),
|
||||
const SizedBox(height: 50),
|
||||
otpVerifySet(),
|
||||
enterMblSet(),
|
||||
receiveDigitSet(),
|
||||
setCodeWithMono(),
|
||||
reqOtpBtn()
|
||||
]))),
|
||||
);
|
||||
}
|
||||
|
||||
otpVerifySet() {
|
||||
return CustomTextLabel(
|
||||
text: 'loginLbl',
|
||||
textStyle: Theme.of(context).textTheme.headlineSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w800, letterSpacing: 0.5),
|
||||
textAlign: TextAlign.center);
|
||||
}
|
||||
|
||||
enterMblSet() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 35.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'enterMblLbl',
|
||||
textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w500),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
receiveDigitSet() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'receiveDigitLbl',
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 14),
|
||||
textAlign: TextAlign.left),
|
||||
);
|
||||
}
|
||||
|
||||
setCodeWithMono() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 30.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Theme.of(context).colorScheme.surface),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[setCountryCode(), setMono()],
|
||||
)));
|
||||
}
|
||||
|
||||
setCountryCode() {
|
||||
double width = MediaQuery.of(context).size.width;
|
||||
double height = MediaQuery.of(context).size.height;
|
||||
return SizedBox(
|
||||
height: 45,
|
||||
child: CountryCodePicker(
|
||||
boxDecoration: BoxDecoration(color: Theme.of(context).colorScheme.surface),
|
||||
searchDecoration: InputDecoration(hintStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer), fillColor: UiUtils.getColorScheme(context).primaryContainer),
|
||||
initialSelection: context.read<AppConfigurationCubit>().getCountryCode(),
|
||||
dialogSize: Size(width - 50, height - 50),
|
||||
builder: (CountryCode? code) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 10.0, bottom: 10.0, start: 20.0, end: 4.0),
|
||||
child:
|
||||
ClipRRect(borderRadius: BorderRadius.circular(3.0), child: Image.asset(code!.flagUri.toString(), package: 'country_code_picker', height: 30, width: 30, fit: BoxFit.cover))),
|
||||
Container(
|
||||
//CountryCode
|
||||
width: 55.0,
|
||||
height: 55.0,
|
||||
alignment: Alignment.center,
|
||||
child: CustomTextLabel(
|
||||
text: code.dialCode.toString(),
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true)),
|
||||
],
|
||||
);
|
||||
},
|
||||
onChanged: (CountryCode countryCode) {
|
||||
conCode = countryCode.dialCode;
|
||||
},
|
||||
onInit: (CountryCode? code) {
|
||||
conCode = code?.dialCode;
|
||||
}));
|
||||
}
|
||||
|
||||
setMono() {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 5.0, bottom: 15.0),
|
||||
child: Container(
|
||||
height: 40,
|
||||
width: MediaQuery.of(context).size.width * 0.57,
|
||||
alignment: Alignment.center,
|
||||
child: TextFormField(
|
||||
keyboardType: const TextInputType.numberWithOptions(signed: true, decimal: true),
|
||||
controller: phoneC,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)),
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
validator: (val) => Validators.mobValidation(val!, context),
|
||||
onSaved: (String? value) => phone = value,
|
||||
decoration: InputDecoration(
|
||||
hintText: UiUtils.getTranslatedLabel(context, 'enterMblLbl'),
|
||||
hintStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5)),
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> verifyPhone(BuildContext context) async {
|
||||
try {
|
||||
await _auth.verifyPhoneNumber(
|
||||
phoneNumber: (conCode!.contains("+")) ? "$conCode${phoneC.text.trim()}" : "+$conCode${phoneC.text.trim()}",
|
||||
verificationCompleted: (AuthCredential phoneAuthCredential) {
|
||||
showSnackBar(phoneAuthCredential.toString(), context);
|
||||
},
|
||||
verificationFailed: (FirebaseAuthException exception) {
|
||||
setState(() => isLoading = false);
|
||||
if (exception.code == "invalid-phone-number") {
|
||||
//invalidPhoneNumber
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'invalidPhoneNumber'), context);
|
||||
} else {
|
||||
showSnackBar('${exception.message}', context);
|
||||
}
|
||||
},
|
||||
forceResendingToken: forceResendingToken,
|
||||
codeAutoRetrievalTimeout: (String verId) => verificationId = verId,
|
||||
codeSent: processCodeSent(),
|
||||
//smsOTPSent
|
||||
timeout: const Duration(seconds: 60));
|
||||
} on FirebaseAuthException catch (authError) {
|
||||
setState(() => isLoading = false);
|
||||
showSnackBar(authError.message!, context);
|
||||
} on FirebaseException catch (e) {
|
||||
setState(() => isLoading = false);
|
||||
showSnackBar(e.toString(), context);
|
||||
} catch (e) {
|
||||
setState(() => isLoading = false);
|
||||
showSnackBar(e.toString(), context);
|
||||
}
|
||||
}
|
||||
|
||||
processCodeSent() {
|
||||
try {
|
||||
smsOTPSent(String? verId, [int? forceCodeResend]) async {
|
||||
if (forceCodeResend != null) forceResendingToken = forceCodeResend;
|
||||
verificationId = verId;
|
||||
setState(() => isLoading = false);
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'codeSent'), context);
|
||||
await Navigator.of(context).pushNamed(Routes.verifyOtp, arguments: {"verifyId": verificationId, "countryCode": conCode, "mono": phoneC.text.trim()});
|
||||
}
|
||||
|
||||
return smsOTPSent;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
reqOtpBtn() {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.only(top: 60.0),
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
height: 45.0,
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(7.0)),
|
||||
child:
|
||||
CustomTextLabel(text: 'reqOtpLbl', textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: secondaryColor, fontWeight: FontWeight.w600, letterSpacing: 0.5, fontSize: 16))),
|
||||
onTap: () async {
|
||||
FocusScope.of(context).unfocus(); //dismiss keyboard
|
||||
if (validateAndSave()) {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
setState(() => isLoading = true);
|
||||
verifyPhone(context);
|
||||
} else {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
//check validation of form data
|
||||
bool validateAndSave() {
|
||||
final form = _formkey.currentState;
|
||||
form!.save();
|
||||
return (form.validate()) ? true : false;
|
||||
}
|
||||
}
|
||||
268
news-app/lib/ui/screens/auth/VerifyOtpScreen.dart
Normal file
268
news-app/lib/ui/screens/auth/VerifyOtpScreen.dart
Normal file
@@ -0,0 +1,268 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/Auth/socialSignUpCubit.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:sms_autofill/sms_autofill.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
|
||||
class VerifyOtp extends StatefulWidget {
|
||||
String? verifyId, countryCode, mono;
|
||||
|
||||
VerifyOtp({super.key, this.verifyId, this.countryCode, this.mono});
|
||||
|
||||
@override
|
||||
VerifyOtpState createState() => VerifyOtpState();
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(
|
||||
builder: (_) => VerifyOtp(
|
||||
verifyId: arguments['verifyId'],
|
||||
countryCode: arguments['countryCode'],
|
||||
mono: arguments['mono'],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class VerifyOtpState extends State<VerifyOtp> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
String? otp;
|
||||
final GlobalKey<FormState> _formkey = GlobalKey<FormState>();
|
||||
int secondsRemaining = 60;
|
||||
bool enableResend = false;
|
||||
Timer? timer;
|
||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
String? verifId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
timer = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
if (secondsRemaining != 0) {
|
||||
setState(() {
|
||||
secondsRemaining--;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
enableResend = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _resendCode() {
|
||||
otp = "";
|
||||
_onVerifyCode();
|
||||
setState(() {
|
||||
secondsRemaining = 60;
|
||||
enableResend = false;
|
||||
});
|
||||
}
|
||||
|
||||
void _onVerifyCode() async {
|
||||
verificationCompleted(AuthCredential phoneAuthCredential) {
|
||||
_auth.signInWithCredential(phoneAuthCredential).then((UserCredential value) {
|
||||
if (value.user != null) {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'otpMsg'), context);
|
||||
} else {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'otpError'), context);
|
||||
}
|
||||
}).catchError((error) {
|
||||
showSnackBar(error.toString(), context);
|
||||
});
|
||||
}
|
||||
|
||||
verificationFailed(FirebaseAuthException authException) {
|
||||
if (authException.code == 'invalidVerificationCode') {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'invalidVerificationCode'), context);
|
||||
} else {
|
||||
showSnackBar(authException.message.toString(), context);
|
||||
}
|
||||
}
|
||||
|
||||
codeSent(String? verificationId, [int? forceResendingToken]) async {
|
||||
verifId = verificationId;
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
verifId = verificationId;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
codeAutoRetrievalTimeout(String verificationId) {
|
||||
verifId = verificationId;
|
||||
setState(() {
|
||||
verifId = verificationId;
|
||||
});
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'otpTimeoutLbl'), context);
|
||||
}
|
||||
|
||||
await _auth.verifyPhoneNumber(
|
||||
phoneNumber: "+${widget.countryCode}${widget.mono}",
|
||||
timeout: const Duration(seconds: 60),
|
||||
verificationCompleted: verificationCompleted,
|
||||
verificationFailed: verificationFailed,
|
||||
codeSent: codeSent,
|
||||
codeAutoRetrievalTimeout: codeAutoRetrievalTimeout);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: BlocConsumer<SocialSignUpCubit, SocialSignUpState>(
|
||||
bloc: context.read<SocialSignUpCubit>(),
|
||||
listener: (context, state) async {
|
||||
if (state is SocialSignUpFailure) {
|
||||
showSnackBar(state.errorMessage, context);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Stack(
|
||||
children: <Widget>[SafeArea(child: showContent()), if (state is SocialSignUpProgress) Center(child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))],
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
//show form content
|
||||
showContent() {
|
||||
return Container(
|
||||
padding: const EdgeInsetsDirectional.all(20.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Form(
|
||||
key: _formkey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[const SizedBox(height: 50), otpVerifySet(), otpSentSet(), mblSet(), otpFillSet(), buildTimer(), submitBtn(), if (secondsRemaining == 0) showResendOTPButton()]))),
|
||||
);
|
||||
}
|
||||
|
||||
otpVerifySet() {
|
||||
return CustomTextLabel(
|
||||
text: 'otpVerifyLbl',
|
||||
textStyle: Theme.of(context).textTheme.headlineSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w800, letterSpacing: 0.5),
|
||||
textAlign: TextAlign.left);
|
||||
}
|
||||
|
||||
otpSentSet() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 25.0),
|
||||
child: CustomTextLabel(text: 'otpSentLbl', textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w500)),
|
||||
);
|
||||
}
|
||||
|
||||
mblSet() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0),
|
||||
child: CustomTextLabel(
|
||||
text: "${widget.countryCode} ${widget.mono}", textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8))),
|
||||
);
|
||||
}
|
||||
|
||||
otpFillSet() {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.only(top: 30.0),
|
||||
child: PinFieldAutoFill(
|
||||
decoration: BoxLooseDecoration(
|
||||
strokeColorBuilder: PinListenColorBuilder(Theme.of(context).colorScheme.surface, Theme.of(context).colorScheme.surface),
|
||||
bgColorBuilder: PinListenColorBuilder(Theme.of(context).colorScheme.surface, Theme.of(context).colorScheme.surface),
|
||||
gapSpace: 7.0),
|
||||
currentCode: otp,
|
||||
codeLength: 6,
|
||||
keyboardType: const TextInputType.numberWithOptions(signed: true, decimal: true),
|
||||
onCodeChanged: (String? code) {
|
||||
otp = code;
|
||||
},
|
||||
onCodeSubmitted: (String code) {
|
||||
otp = code;
|
||||
}));
|
||||
}
|
||||
|
||||
showResendOTPButton() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
alignment: Alignment.center,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
text: "${UiUtils.getTranslatedLabel(context, 'didntGetCode')} ",
|
||||
style: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontWeight: FontWeight.bold, letterSpacing: 0.5),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: UiUtils.getTranslatedLabel(context, 'resendLbl'),
|
||||
style: TextStyle(color: UiUtils.getColorScheme(context).secondaryContainer, fontWeight: FontWeight.bold, letterSpacing: 0.5, decoration: TextDecoration.underline),
|
||||
recognizer: TapGestureRecognizer()..onTap = enableResend ? _resendCode : null,
|
||||
),
|
||||
],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
));
|
||||
}
|
||||
|
||||
buildTimer() {
|
||||
return Container(
|
||||
alignment: AlignmentDirectional.bottomStart,
|
||||
padding: const EdgeInsets.only(top: 30.0),
|
||||
child: secondsRemaining != 0
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
CustomTextLabel(text: 'resendCodeLbl', textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8))),
|
||||
CustomTextLabel(text: ' 00:${secondsRemaining.toString().padLeft(2, '0')}', textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: Theme.of(context).primaryColor)),
|
||||
],
|
||||
)
|
||||
: SizedBox.shrink(),
|
||||
);
|
||||
}
|
||||
|
||||
submitBtn() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 45.0),
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
height: 45.0,
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(7.0)),
|
||||
child:
|
||||
CustomTextLabel(text: 'submitBtn', textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: secondaryColor, fontWeight: FontWeight.w600, fontSize: 16, letterSpacing: 0.6)),
|
||||
),
|
||||
onTap: () async {
|
||||
if (otp!.trim().isEmpty) return showSnackBar(UiUtils.getTranslatedLabel(context, 'enterOtpTxt'), context);
|
||||
FocusScope.of(context).unfocus(); //dismiss keyboard
|
||||
|
||||
if (validateAndSave()) {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
context.read<SocialSignUpCubit>().socialSignUpUser(authProvider: AuthProviders.mobile, verifiedId: widget.verifyId, otp: otp, context: context);
|
||||
} else {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
//check validation of form data
|
||||
bool validateAndSave() {
|
||||
final form = _formkey.currentState;
|
||||
form!.save();
|
||||
if (form.validate()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
35
news-app/lib/ui/screens/auth/Widgets/bottomComBtn.dart
Normal file
35
news-app/lib/ui/screens/auth/Widgets/bottomComBtn.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class BottomCommButton extends StatelessWidget {
|
||||
final Function onTap;
|
||||
final String img;
|
||||
final Color? btnColor;
|
||||
final String btnCaption;
|
||||
|
||||
const BottomCommButton({super.key, required this.onTap, required this.img, this.btnColor, required this.btnCaption});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String textLbl = "${UiUtils.getTranslatedLabel(context, 'continueWith')} ${UiUtils.getTranslatedLabel(context, btnCaption)}";
|
||||
return InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
child: Container(
|
||||
height: 45.0,
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(color: UiUtils.getColorScheme(context).surface, borderRadius: BorderRadius.circular(7.0)),
|
||||
padding: const EdgeInsets.all(9.0),
|
||||
margin: EdgeInsets.symmetric(vertical: 10),
|
||||
child: Wrap(
|
||||
spacing: 15,
|
||||
children: [
|
||||
SvgPictureWidget(assetName: img, width: 20, height: 20, fit: BoxFit.contain, assetColor: btnColor != null ? ColorFilter.mode(btnColor!, BlendMode.srcIn) : null),
|
||||
CustomTextLabel(text: textLbl, textStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w600))
|
||||
],
|
||||
)),
|
||||
onTap: () => onTap());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
fieldFocusChange(BuildContext context, FocusNode currentFocus, FocusNode nextFocus) {
|
||||
currentFocus.unfocus();
|
||||
FocusScope.of(context).requestFocus(nextFocus);
|
||||
}
|
||||
77
news-app/lib/ui/screens/auth/Widgets/setConfimPass.dart
Normal file
77
news-app/lib/ui/screens/auth/Widgets/setConfimPass.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class SetConfirmPass extends StatefulWidget {
|
||||
final FocusNode currFocus;
|
||||
final TextEditingController confPassC;
|
||||
late String confPass;
|
||||
late String pass;
|
||||
|
||||
SetConfirmPass({super.key, required this.currFocus, required this.confPassC, required this.confPass, required this.pass});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _SetConfPassState();
|
||||
}
|
||||
}
|
||||
|
||||
class _SetConfPassState extends State<SetConfirmPass> {
|
||||
bool isObscure = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: TextFormField(
|
||||
focusNode: widget.currFocus,
|
||||
textInputAction: TextInputAction.done,
|
||||
controller: widget.confPassC,
|
||||
obscureText: isObscure,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: UiUtils.getColorScheme(context).primaryContainer,
|
||||
),
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return UiUtils.getTranslatedLabel(context, 'confPassRequired');
|
||||
}
|
||||
if (value.trim() != widget.pass.trim()) {
|
||||
return UiUtils.getTranslatedLabel(context, 'confPassNotMatch');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
onChanged: (String value) {
|
||||
widget.confPass = value;
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: UiUtils.getTranslatedLabel(context, 'confpassLbl'),
|
||||
hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5),
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 12.0),
|
||||
child: IconButton(
|
||||
icon: isObscure
|
||||
? Icon(Icons.visibility_rounded, size: 20, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6))
|
||||
: Icon(Icons.visibility_off_rounded, size: 20, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)),
|
||||
splashColor: Colors.transparent,
|
||||
onPressed: () {
|
||||
setState(() => isObscure = !isObscure);
|
||||
},
|
||||
)),
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).colorScheme.surface,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: UiUtils.getColorScheme(context).outline.withOpacity(0.7)),
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
25
news-app/lib/ui/screens/auth/Widgets/setDivider.dart
Normal file
25
news-app/lib/ui/screens/auth/Widgets/setDivider.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class SetDividerOR extends StatelessWidget {
|
||||
const SetDividerOR({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color color = UiUtils.getColorScheme(context).outline.withOpacity(0.9);
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 30.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(child: Divider(indent: 10, endIndent: 10, color: color)),
|
||||
CustomTextLabel(
|
||||
text: 'orLbl',
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.merge(TextStyle(color: color, fontSize: 12.0)),
|
||||
),
|
||||
Expanded(child: Divider(indent: 10, endIndent: 10, color: color)),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
47
news-app/lib/ui/screens/auth/Widgets/setEmail.dart
Normal file
47
news-app/lib/ui/screens/auth/Widgets/setEmail.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/utils/validators.dart';
|
||||
|
||||
import 'fieldFocusChange.dart';
|
||||
|
||||
class SetEmail extends StatelessWidget {
|
||||
final FocusNode? currFocus;
|
||||
final FocusNode? nextFocus;
|
||||
final TextEditingController emailC;
|
||||
late String email;
|
||||
final double topPad;
|
||||
|
||||
SetEmail({super.key, this.currFocus, this.nextFocus, required this.emailC, required this.email, required this.topPad});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: topPad),
|
||||
child: TextFormField(
|
||||
focusNode: currFocus,
|
||||
textInputAction: TextInputAction.next,
|
||||
controller: emailC,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer),
|
||||
validator: (val) => Validators.emailValidation(val!, context),
|
||||
onFieldSubmitted: (v) {
|
||||
if (currFocus != null || nextFocus != null) fieldFocusChange(context, currFocus!, nextFocus!);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: UiUtils.getTranslatedLabel(context, 'emailLbl'),
|
||||
hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5)),
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).colorScheme.surface,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: UiUtils.getColorScheme(context).outline.withOpacity(0.7)),
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
16
news-app/lib/ui/screens/auth/Widgets/setForgotPass.dart
Normal file
16
news-app/lib/ui/screens/auth/Widgets/setForgotPass.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/widgets/customTextBtn.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
|
||||
setForgotPass(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 5.0),
|
||||
child: Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: CustomTextButton(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.forgotPass),
|
||||
buttonStyle: ButtonStyle(overlayColor: WidgetStateProperty.all(Colors.transparent), foregroundColor: WidgetStateProperty.all(UiUtils.getColorScheme(context).outline.withOpacity(0.7))),
|
||||
textWidget: const CustomTextLabel(text: 'forgotPassLbl'))));
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
|
||||
class SetLoginAndSignUpBtn extends StatelessWidget {
|
||||
final Function onTap;
|
||||
final String text;
|
||||
final double topPad;
|
||||
|
||||
const SetLoginAndSignUpBtn({super.key, required this.onTap, required this.text, required this.topPad});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: topPad),
|
||||
child: InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
child: Container(
|
||||
height: 45.0,
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(7.0)),
|
||||
child: CustomTextLabel(
|
||||
text: text,
|
||||
textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: secondaryColor, fontWeight: FontWeight.w600, fontSize: 16, letterSpacing: 0.6),
|
||||
),
|
||||
),
|
||||
onTap: () => onTap()),
|
||||
);
|
||||
}
|
||||
}
|
||||
49
news-app/lib/ui/screens/auth/Widgets/setName.dart
Normal file
49
news-app/lib/ui/screens/auth/Widgets/setName.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/utils/validators.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/fieldFocusChange.dart';
|
||||
|
||||
class SetName extends StatelessWidget {
|
||||
final FocusNode currFocus;
|
||||
final FocusNode nextFocus;
|
||||
final TextEditingController nameC;
|
||||
late String name;
|
||||
|
||||
SetName({super.key, required this.currFocus, required this.nextFocus, required this.nameC, required this.name});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 40),
|
||||
child: TextFormField(
|
||||
focusNode: currFocus,
|
||||
textInputAction: TextInputAction.next,
|
||||
controller: nameC,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: UiUtils.getColorScheme(context).primaryContainer,
|
||||
),
|
||||
validator: (val) => Validators.nameValidation(val!, context),
|
||||
onFieldSubmitted: (v) {
|
||||
fieldFocusChange(context, currFocus, nextFocus);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: UiUtils.getTranslatedLabel(context, 'nameLbl'),
|
||||
hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).colorScheme.surface,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: UiUtils.getColorScheme(context).outline.withOpacity(0.7)),
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
81
news-app/lib/ui/screens/auth/Widgets/setPassword.dart
Normal file
81
news-app/lib/ui/screens/auth/Widgets/setPassword.dart
Normal file
@@ -0,0 +1,81 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/utils/validators.dart';
|
||||
|
||||
import 'fieldFocusChange.dart';
|
||||
|
||||
class SetPassword extends StatefulWidget {
|
||||
final FocusNode currFocus;
|
||||
final FocusNode? nextFocus;
|
||||
final TextEditingController passC;
|
||||
late String pass;
|
||||
final double topPad;
|
||||
final bool isLogin;
|
||||
|
||||
SetPassword({super.key, required this.currFocus, this.nextFocus, required this.passC, required this.pass, required this.topPad, required this.isLogin});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _SetPassState();
|
||||
}
|
||||
}
|
||||
|
||||
class _SetPassState extends State<SetPassword> {
|
||||
bool isObscure = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StatefulBuilder(builder: (context, StateSetter setStater) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: widget.topPad),
|
||||
child: TextFormField(
|
||||
focusNode: widget.currFocus,
|
||||
textInputAction: widget.isLogin ? TextInputAction.done : TextInputAction.next,
|
||||
controller: widget.passC,
|
||||
obscureText: isObscure,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: UiUtils.getColorScheme(context).primaryContainer,
|
||||
),
|
||||
validator: (val) => Validators.passValidation(val!, context),
|
||||
onFieldSubmitted: (v) {
|
||||
if (!widget.isLogin) {
|
||||
fieldFocusChange(context, widget.currFocus, widget.nextFocus!);
|
||||
}
|
||||
},
|
||||
onChanged: (String value) {
|
||||
widget.pass = value;
|
||||
setStater(() {});
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: UiUtils.getTranslatedLabel(context, 'passLbl'),
|
||||
hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5),
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 12.0),
|
||||
child: IconButton(
|
||||
icon: isObscure
|
||||
? Icon(Icons.visibility_rounded, size: 20, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6))
|
||||
: Icon(Icons.visibility_off_rounded, size: 20, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)),
|
||||
splashColor: Colors.transparent,
|
||||
onPressed: () {
|
||||
setState(() => isObscure = !isObscure);
|
||||
},
|
||||
)),
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).colorScheme.surface,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: UiUtils.getColorScheme(context).outline.withOpacity(0.7)),
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
40
news-app/lib/ui/screens/auth/Widgets/setTermPolicy.dart
Normal file
40
news-app/lib/ui/screens/auth/Widgets/setTermPolicy.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/privacyTermsCubit.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
setTermPolicyTxt(BuildContext context, PrivacyTermsFetchSuccess state) {
|
||||
return Container(
|
||||
alignment: AlignmentDirectional.bottomCenter,
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
child: RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(children: [
|
||||
TextSpan(
|
||||
text: "${UiUtils.getTranslatedLabel(context, 'agreeTermPolicyLbl')}\n",
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
TextSpan(
|
||||
text: UiUtils.getTranslatedLabel(context, 'termLbl'),
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Theme.of(context).primaryColor, decoration: TextDecoration.underline, overflow: TextOverflow.ellipsis),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = (() {
|
||||
Navigator.of(context).pushNamed(Routes.privacy, arguments: {"from": "login", "title": state.termsPolicy.title, "desc": state.termsPolicy.pageContent});
|
||||
}),
|
||||
),
|
||||
TextSpan(
|
||||
text: "\t${UiUtils.getTranslatedLabel(context, 'andLbl')}\t",
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
TextSpan(
|
||||
text: UiUtils.getTranslatedLabel(context, 'priPolicy'),
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Theme.of(context).primaryColor, decoration: TextDecoration.underline, overflow: TextOverflow.ellipsis),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = (() {
|
||||
Navigator.of(context).pushNamed(Routes.privacy, arguments: {"from": "login", "title": state.privacyPolicy.title, "desc": state.privacyPolicy.pageContent});
|
||||
}),
|
||||
),
|
||||
]),
|
||||
));
|
||||
}
|
||||
18
news-app/lib/ui/screens/auth/Widgets/svgPictureWidget.dart
Normal file
18
news-app/lib/ui/screens/auth/Widgets/svgPictureWidget.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class SvgPictureWidget extends StatelessWidget {
|
||||
String assetName;
|
||||
ColorFilter? assetColor;
|
||||
double? height, width;
|
||||
BoxFit? fit;
|
||||
|
||||
SvgPictureWidget({Key? key, required this.assetName, this.assetColor, this.height, this.width, this.fit}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SvgPicture.asset(
|
||||
placeholderBuilder: (_) => Center(child: CircularProgressIndicator()), UiUtils.getSvgImagePath(assetName), colorFilter: assetColor, height: height, width: width, fit: fit ?? BoxFit.fill);
|
||||
}
|
||||
}
|
||||
455
news-app/lib/ui/screens/auth/loginScreen.dart
Normal file
455
news-app/lib/ui/screens/auth/loginScreen.dart
Normal file
@@ -0,0 +1,455 @@
|
||||
import 'dart:io';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/Auth/registerTokenCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/cubits/privacyTermsCubit.dart';
|
||||
import 'package:news/cubits/settingCubit.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/bottomComBtn.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/fieldFocusChange.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/setConfimPass.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/setDivider.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/setEmail.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/setForgotPass.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/setLoginAndSignUpBtn.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/setName.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/setPassword.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/setTermPolicy.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
|
||||
import 'package:news/ui/widgets/customTextBtn.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/constant.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/Auth/socialSignUpCubit.dart';
|
||||
import 'package:news/utils/validators.dart';
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
final bool? isFromApp;
|
||||
const LoginScreen({super.key, this.isFromApp});
|
||||
|
||||
@override
|
||||
LoginScreenState createState() => LoginScreenState();
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
if (routeSettings.arguments == null) {
|
||||
return CupertinoPageRoute(builder: (_) => const LoginScreen());
|
||||
} else {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(builder: (_) => LoginScreen(isFromApp: arguments['isFromApp'] ?? false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LoginScreenState extends State<LoginScreen> with TickerProviderStateMixin {
|
||||
final GlobalKey<FormState> _formkey = GlobalKey<FormState>();
|
||||
TabController? _tabController;
|
||||
FocusNode emailFocus = FocusNode();
|
||||
FocusNode passFocus = FocusNode();
|
||||
FocusNode nameFocus = FocusNode();
|
||||
FocusNode emailSFocus = FocusNode();
|
||||
FocusNode passSFocus = FocusNode();
|
||||
FocusNode confPassFocus = FocusNode();
|
||||
TextEditingController? emailC, passC, sEmailC, sPassC, sNameC, sConfPassC;
|
||||
String? name, email, pass, mobile, profile, confPass;
|
||||
bool isPolicyAvailable = false;
|
||||
bool isObscure = true; //setPassword widget
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<PrivacyTermsCubit>().getPrivacyTerms(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
});
|
||||
|
||||
_tabController = TabController(length: 2, vsync: this, initialIndex: 0);
|
||||
assignAllTextController();
|
||||
_tabController!.addListener(() {
|
||||
FocusScope.of(context).unfocus(); //dismiss keyboard
|
||||
clearLoginTextFields();
|
||||
clearSignUpTextFields();
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
assignAllTextController() {
|
||||
emailC = TextEditingController();
|
||||
passC = TextEditingController();
|
||||
sEmailC = TextEditingController();
|
||||
sPassC = TextEditingController();
|
||||
sNameC = TextEditingController();
|
||||
sConfPassC = TextEditingController();
|
||||
}
|
||||
|
||||
clearSignUpTextFields() {
|
||||
setState(() {
|
||||
sNameC!.clear();
|
||||
sEmailC!.clear();
|
||||
sPassC!.clear();
|
||||
sConfPassC!.clear();
|
||||
});
|
||||
}
|
||||
|
||||
clearLoginTextFields() {
|
||||
setState(() {
|
||||
emailC!.clear();
|
||||
passC!.clear();
|
||||
});
|
||||
}
|
||||
|
||||
disposeAllTextController() {
|
||||
emailC!.dispose();
|
||||
passC!.dispose();
|
||||
sEmailC!.dispose();
|
||||
sPassC!.dispose();
|
||||
sNameC!.dispose();
|
||||
sConfPassC!.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController?.dispose();
|
||||
disposeAllTextController();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
showContent() {
|
||||
return BlocConsumer<SocialSignUpCubit, SocialSignUpState>(
|
||||
bloc: context.read<SocialSignUpCubit>(),
|
||||
listener: (context, state) async {
|
||||
if (state is SocialSignUpFailure) {
|
||||
showSnackBar(state.errorMessage, context);
|
||||
}
|
||||
if (state is SocialSignUpSuccess) {
|
||||
context.read<AuthCubit>().checkAuthStatus();
|
||||
if (state.authModel.status == "0") {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'deactiveMsg'), context);
|
||||
} else {
|
||||
FirebaseMessaging.instance.getToken().then((token) async {
|
||||
if (token != null) {
|
||||
context.read<RegisterTokenCubit>().registerToken(fcmId: token, context: context);
|
||||
if (token != context.read<SettingsCubit>().getSettings().token) {
|
||||
context.read<SettingsCubit>().changeFcmToken(token);
|
||||
}
|
||||
if (state.authModel.isFirstLogin != null && state.authModel.isFirstLogin!.isNotEmpty && state.authModel.isFirstLogin == "0" && state.authModel.type != loginApple) {
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(Routes.editUserProfile, (route) => false, arguments: {"from": "login"});
|
||||
} else if (widget.isFromApp == true) {
|
||||
Navigator.pop(context);
|
||||
} else {
|
||||
Navigator.pushNamedAndRemoveUntil(context, Routes.home, (route) => false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Form(
|
||||
key: _formkey,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsetsDirectional.only(top: 30.0, bottom: 5.0, start: 20.0, end: 20.0),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[skipBtn(), showTabs(), showTabBarView()]),
|
||||
),
|
||||
if (state is SocialSignUpProgress) UiUtils.showCircularProgress(true, Theme.of(context).primaryColor),
|
||||
],
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
skipBtn() {
|
||||
return Align(
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
child: CustomTextButton(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushReplacementNamed(Routes.home, arguments: false);
|
||||
},
|
||||
text: UiUtils.getTranslatedLabel(context, 'skip'),
|
||||
color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7),
|
||||
));
|
||||
}
|
||||
|
||||
showTabs() {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: DefaultTabController(
|
||||
length: 2,
|
||||
child: Container(
|
||||
padding: const EdgeInsetsDirectional.only(start: 10.0),
|
||||
child: TabBar(
|
||||
overlayColor: WidgetStateProperty.all(Colors.transparent),
|
||||
controller: _tabController,
|
||||
labelStyle: Theme.of(context).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600, letterSpacing: 0.5),
|
||||
labelPadding: EdgeInsets.zero,
|
||||
labelColor: backgroundColor,
|
||||
unselectedLabelColor: UiUtils.getColorScheme(context).primaryContainer,
|
||||
indicator: BoxDecoration(borderRadius: BorderRadius.circular(50), color: UiUtils.getColorScheme(context).secondaryContainer),
|
||||
tabs: [Tab(text: UiUtils.getTranslatedLabel(context, 'signInTab')), Tab(text: UiUtils.getTranslatedLabel(context, 'signupBtn'))])),
|
||||
));
|
||||
}
|
||||
|
||||
bool validateAndSave() {
|
||||
final form = _formkey.currentState;
|
||||
form!.save();
|
||||
|
||||
if (!isPolicyAvailable) {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'addTCFirst'), context);
|
||||
return false;
|
||||
}
|
||||
|
||||
return form.validate();
|
||||
}
|
||||
|
||||
Widget setPassword({required FocusNode currFocus, FocusNode? nextFocus, required TextEditingController passC, required String pass, required double topPad, required bool isLogin}) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: topPad),
|
||||
child: TextFormField(
|
||||
focusNode: currFocus,
|
||||
textInputAction: isLogin ? TextInputAction.done : TextInputAction.next,
|
||||
controller: passC,
|
||||
obscureText: isObscure,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: UiUtils.getColorScheme(context).primaryContainer,
|
||||
),
|
||||
validator: (val) => Validators.passValidation(val!, context),
|
||||
onFieldSubmitted: (v) {
|
||||
if (!isLogin) {
|
||||
fieldFocusChange(context, currFocus, nextFocus!);
|
||||
}
|
||||
},
|
||||
onChanged: (String value) {
|
||||
pass = value;
|
||||
setState(() {});
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: UiUtils.getTranslatedLabel(context, 'passLbl'),
|
||||
hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5),
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 12.0),
|
||||
child: IconButton(
|
||||
icon: isObscure
|
||||
? Icon(Icons.visibility_rounded, size: 20, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6))
|
||||
: Icon(Icons.visibility_off_rounded, size: 20, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)),
|
||||
splashColor: Colors.transparent,
|
||||
onPressed: () {
|
||||
setState(() => isObscure = !isObscure);
|
||||
},
|
||||
)),
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).colorScheme.surface,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: UiUtils.getColorScheme(context).outline.withOpacity(0.7)),
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
showTabBarView() {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: MediaQuery.of(context).size.height * 1.0,
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
dragStartBehavior: DragStartBehavior.start,
|
||||
children: [
|
||||
//Login
|
||||
SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5.0),
|
||||
child: Column(children: [
|
||||
loginTxt(),
|
||||
SetEmail(currFocus: emailFocus, nextFocus: passFocus, emailC: emailC!, email: email ?? '', topPad: 20),
|
||||
SetPassword(currFocus: passFocus, passC: passC!, pass: pass ?? '', topPad: 20, isLogin: true),
|
||||
setForgotPass(context),
|
||||
SetLoginAndSignUpBtn(
|
||||
onTap: () async {
|
||||
FocusScope.of(context).unfocus(); //dismiss keyboard
|
||||
if (validateAndSave()) {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
context.read<SocialSignUpCubit>().socialSignUpUser(email: emailC!.text.trim(), password: passC!.text, authProvider: AuthProviders.email, context: context);
|
||||
} else {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context);
|
||||
}
|
||||
}
|
||||
},
|
||||
text: 'loginTxt',
|
||||
topPad: 20),
|
||||
SetDividerOR(),
|
||||
bottomBtn(),
|
||||
BlocConsumer<PrivacyTermsCubit, PrivacyTermsState>(listener: (context, state) {
|
||||
if (state is PrivacyTermsFetchSuccess) {
|
||||
isPolicyAvailable = true;
|
||||
}
|
||||
if (state is PrivacyTermsFetchFailure) {
|
||||
isPolicyAvailable = false;
|
||||
}
|
||||
}, builder: (context, state) {
|
||||
return (state is PrivacyTermsFetchSuccess) ? setTermPolicyTxt(context, state) : const SizedBox.shrink();
|
||||
})
|
||||
]),
|
||||
)),
|
||||
//SignUp
|
||||
SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5.0),
|
||||
child: Column(
|
||||
children: [
|
||||
signUpTxt(),
|
||||
SetName(currFocus: nameFocus, nextFocus: emailSFocus, nameC: sNameC!, name: sNameC!.text),
|
||||
SetEmail(currFocus: emailSFocus, nextFocus: passSFocus, emailC: sEmailC!, email: sEmailC!.text, topPad: 20),
|
||||
setPassword(currFocus: passSFocus, nextFocus: confPassFocus, passC: sPassC!, pass: sPassC!.text, topPad: 20, isLogin: false),
|
||||
SetConfirmPass(currFocus: confPassFocus, confPassC: sConfPassC!, confPass: sConfPassC!.text, pass: sPassC!.text),
|
||||
SetLoginAndSignUpBtn(
|
||||
onTap: () async {
|
||||
FocusScope.of(context).unfocus(); //dismiss keyboard
|
||||
final form = _formkey.currentState;
|
||||
if (form!.validate()) {
|
||||
form.save();
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
registerWithEmailPassword(sEmailC!.text.trim(), sPassC!.text.trim());
|
||||
} else {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context);
|
||||
}
|
||||
}
|
||||
},
|
||||
text: 'signupBtn',
|
||||
topPad: 25)
|
||||
],
|
||||
),
|
||||
))
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
registerWithEmailPassword(String email, String password) async {
|
||||
try {
|
||||
final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword(email: email, password: password);
|
||||
User? user = credential.user;
|
||||
user!.updateDisplayName(sNameC!.text.trim()).then((value) => debugPrint("updated name is - ${user.displayName}"));
|
||||
user.reload();
|
||||
|
||||
user.sendEmailVerification().then((value) => showSnackBar('${UiUtils.getTranslatedLabel(context, 'verifSentMail')} $email', context));
|
||||
clearSignUpTextFields();
|
||||
_tabController!.animateTo(0);
|
||||
FocusScope.of(context).requestFocus(emailFocus);
|
||||
} on FirebaseAuthException catch (e) {
|
||||
if (e.code == 'weakPassword') {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'weakPassword'), context);
|
||||
}
|
||||
if (e.code == 'email-already-in-use') {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'emailAlreadyInUse'), context);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
loginTxt() {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 20.0, start: 10.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'loginDescr',
|
||||
textStyle: Theme.of(context).textTheme.headlineSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w800, letterSpacing: 0.5),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
signUpTxt() {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(top: 35.0, start: 10.0),
|
||||
child: CustomTextLabel(
|
||||
text: 'signupDescr',
|
||||
textStyle: Theme.of(context).textTheme.headlineSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w800, letterSpacing: 0.5),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
bottomBtn() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: (fblogInEnabled) ? MainAxisAlignment.spaceBetween : MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
BottomCommButton(
|
||||
btnCaption: 'google',
|
||||
onTap: () {
|
||||
if (!isPolicyAvailable) {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'addTCFirst'), context);
|
||||
} else {
|
||||
context.read<SocialSignUpCubit>().socialSignUpUser(authProvider: AuthProviders.gmail, context: context);
|
||||
}
|
||||
},
|
||||
img: 'google_button'),
|
||||
if (fblogInEnabled)
|
||||
BottomCommButton(
|
||||
onTap: () {
|
||||
if (!isPolicyAvailable) {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'addTCFirst'), context);
|
||||
} else {
|
||||
context.read<SocialSignUpCubit>().socialSignUpUser(authProvider: AuthProviders.fb, context: context);
|
||||
}
|
||||
},
|
||||
img: 'facebook_button',
|
||||
btnCaption: 'fb'),
|
||||
if (Platform.isIOS)
|
||||
BottomCommButton(
|
||||
onTap: () {
|
||||
if (!isPolicyAvailable) {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'addTCFirst'), context);
|
||||
} else {
|
||||
context.read<SocialSignUpCubit>().socialSignUpUser(authProvider: AuthProviders.apple, context: context);
|
||||
}
|
||||
},
|
||||
img: 'apple_logo',
|
||||
btnCaption: 'apple',
|
||||
btnColor: UiUtils.getColorScheme(context).primaryContainer),
|
||||
if (context.read<AppConfigurationCubit>().getMobileLoginMode() != "" && context.read<AppConfigurationCubit>().getMobileLoginMode() != "0")
|
||||
BottomCommButton(
|
||||
onTap: () {
|
||||
if (!isPolicyAvailable) {
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'addTCFirst'), context);
|
||||
} else {
|
||||
Navigator.of(context).pushNamed(Routes.requestOtp);
|
||||
}
|
||||
},
|
||||
img: 'phone_button',
|
||||
btnCaption: 'mobileLbl',
|
||||
btnColor: UiUtils.getColorScheme(context).primaryContainer)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: showContent(),
|
||||
);
|
||||
}
|
||||
}
|
||||
275
news-app/lib/ui/screens/authorDetailsScreen.dart
Normal file
275
news-app/lib/ui/screens/authorDetailsScreen.dart
Normal file
@@ -0,0 +1,275 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/Author/authorNewsCubit.dart';
|
||||
import 'package:news/cubits/NewsByIdCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.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/errorContainerWidget.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/utils/constant.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class AuthorDetailsScreen extends StatefulWidget {
|
||||
final String authorId;
|
||||
const AuthorDetailsScreen({super.key, required this.authorId});
|
||||
|
||||
@override
|
||||
State<AuthorDetailsScreen> createState() => _AuthorDetailsScreenState();
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(builder: (_) => AuthorDetailsScreen(authorId: arguments['authorId']));
|
||||
}
|
||||
}
|
||||
|
||||
class _AuthorDetailsScreenState extends State<AuthorDetailsScreen> {
|
||||
AuthorLayoutType layout = AuthorLayoutType.list;
|
||||
double borderRadius = 10;
|
||||
late NewsModel newsData;
|
||||
String totalViews = "0", totalLikes = "0";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
getNewsByAuthor();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void getNewsByAuthor() {
|
||||
context.read<AuthorNewsCubit>().getAuthorNews(authorId: widget.authorId);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const CustomTextLabel(text: 'authorLbl'),
|
||||
leading: CustomBackButton(),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(3.0),
|
||||
child: Container(
|
||||
margin: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(border: Border.all(color: UiUtils.getColorScheme(context).outline), borderRadius: BorderRadius.circular(5)),
|
||||
child: GestureDetector(
|
||||
child: Icon(layout == AuthorLayoutType.list ? Icons.grid_view : Icons.view_list, size: 25),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
layout = layout == AuthorLayoutType.list ? AuthorLayoutType.grid : AuthorLayoutType.list;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: BlocBuilder<AuthorNewsCubit, AuthorNewsState>(
|
||||
builder: (context, state) {
|
||||
if (state is AuthorNewsFetchSuccess) {
|
||||
return Column(
|
||||
children: [
|
||||
authorHeader(state: state),
|
||||
const SizedBox(height: 10),
|
||||
Expanded(child: layout == AuthorLayoutType.list ? authorNewsListView(state: state) : authorNewsGridView(state: state)),
|
||||
],
|
||||
);
|
||||
} else if (state is AuthorNewsFetchFailed) {
|
||||
return ErrorContainerWidget(errorMsg: state.errorMessage, onRetry: getNewsByAuthor);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget authorHeader({required AuthorNewsFetchSuccess state}) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: UiUtils.getColorScheme(context).surface,
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// author image
|
||||
ClipRRect(borderRadius: BorderRadius.circular(6), child: CustomNetworkImage(networkImageUrl: state.authorData.profile ?? "")),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CustomTextLabel(text: state.authorData.name ?? "", textStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
|
||||
SizedBox(height: 6),
|
||||
CustomTextLabel(text: state.authorData.authorData!.bio ?? "", textStyle: TextStyle(fontSize: 13)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
children: [
|
||||
CustomTextLabel(text: 'followLbl'),
|
||||
SizedBox(width: 6),
|
||||
(state.authorData.authorData != null)
|
||||
? Row(spacing: 6.5, children: [
|
||||
showSocialMediaLinks(socialMediaLink: state.authorData.authorData!.telegramLink ?? "", socialMediaIconName: "telegram"),
|
||||
showSocialMediaLinks(socialMediaLink: state.authorData.authorData!.facebookLink ?? "", socialMediaIconName: "facebook"),
|
||||
showSocialMediaLinks(socialMediaLink: state.authorData.authorData!.whatsappLink ?? "", socialMediaIconName: "whatsapp"),
|
||||
showSocialMediaLinks(socialMediaLink: state.authorData.authorData!.linkedinLink ?? "", socialMediaIconName: "linkedin"),
|
||||
])
|
||||
: SizedBox.shrink()
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget showSocialMediaLinks({required String socialMediaLink, required String socialMediaIconName}) {
|
||||
return Container(
|
||||
height: 30,
|
||||
width: 30,
|
||||
padding: EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(7), color: UiUtils.getColorScheme(context).outline.withOpacity(0.2)),
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
if (await canLaunchUrl(Uri.parse(socialMediaLink))) {
|
||||
await launchUrl(Uri.parse(socialMediaLink), mode: LaunchMode.externalApplication);
|
||||
}
|
||||
},
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
child: SvgPictureWidget(
|
||||
assetName: socialMediaIconName,
|
||||
height: 11,
|
||||
width: 11,
|
||||
fit: BoxFit.contain,
|
||||
assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), BlendMode.srcIn)),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget authorNewsListView({required AuthorNewsFetchSuccess state}) {
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: state.AuthorNewsList.length, itemBuilder: (BuildContext, index) => newsListTile(newsItem: state.AuthorNewsList[index]));
|
||||
}
|
||||
|
||||
Widget newsListTile({required NewsModel newsItem}) {
|
||||
return GestureDetector(
|
||||
onTap: () => redirectToNewsDetailsScreen(newsItem: newsItem),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
decoration: BoxDecoration(color: UiUtils.getColorScheme(context).surface, borderRadius: BorderRadius.circular(12)),
|
||||
child: Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(borderRadius), bottomLeft: Radius.circular(borderRadius)),
|
||||
child: CustomNetworkImage(networkImageUrl: newsItem.image!, width: 110, height: 110, fit: BoxFit.cover)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CustomTextLabel(text: newsItem.title ?? "", maxLines: 2, overflow: TextOverflow.ellipsis, textStyle: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
|
||||
SizedBox(height: 6),
|
||||
CustomTextLabel(text: newsItem.desc ?? "", maxLines: 3, overflow: TextOverflow.ellipsis, textStyle: TextStyle(fontSize: 13)),
|
||||
SizedBox(height: 6),
|
||||
Divider(),
|
||||
SizedBox(height: 3),
|
||||
Row(
|
||||
children: [
|
||||
CustomTextLabel(
|
||||
text: UiUtils.convertToAgo(context, DateTime.parse(newsItem.publishDate ?? newsItem.date!), 0)!,
|
||||
textStyle: TextStyle(fontSize: 12, color: UiUtils.getColorScheme(context).outline)),
|
||||
Spacer(),
|
||||
showViewsAndLikes(newsId: newsItem.id ?? "0", totalViews: newsItem.totalViews ?? "0", totalLikes: newsItem.totalLikes ?? "0")
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget showViewsAndLikes({required String newsId, required String totalViews, required String totalLikes}) {
|
||||
return FutureBuilder(
|
||||
future: context.read<NewsByIdCubit>().getNewsById(newsId: newsId, langId: context.read<AppLocalizationCubit>().state.id),
|
||||
builder: (context, snapshot) {
|
||||
final updated = snapshot.data![0];
|
||||
totalViews = updated.totalViews ?? "0";
|
||||
totalLikes = updated.totalLikes ?? "0";
|
||||
return Row(
|
||||
spacing: 15,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 5,
|
||||
children: [Icon(Icons.remove_red_eye_rounded, size: 10), CustomTextLabel(text: totalViews, textStyle: TextStyle(fontSize: 12))],
|
||||
),
|
||||
Row(
|
||||
spacing: 5,
|
||||
children: [Icon(Icons.thumb_up_alt_outlined, size: 10), CustomTextLabel(text: totalLikes, textStyle: TextStyle(fontSize: 12))],
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget authorNewsGridView({required AuthorNewsFetchSuccess state}) {
|
||||
return GridView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
itemCount: state.AuthorNewsList.length,
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, mainAxisSpacing: 16, crossAxisSpacing: 16, childAspectRatio: 0.65),
|
||||
itemBuilder: (BuildContext, index) {
|
||||
return newsGridTile(newsItem: state.AuthorNewsList[index]);
|
||||
});
|
||||
}
|
||||
|
||||
Widget newsGridTile({required NewsModel newsItem}) {
|
||||
return GestureDetector(
|
||||
onTap: () => redirectToNewsDetailsScreen(newsItem: newsItem),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(borderRadius), color: UiUtils.getColorScheme(context).surface, boxShadow: const [BoxShadow(blurRadius: 5, spreadRadius: 1, color: dividerColor)]),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipRRect(borderRadius: BorderRadius.circular(8), child: CustomNetworkImage(networkImageUrl: newsItem.image!, height: 120, width: double.infinity, fit: BoxFit.cover)),
|
||||
const SizedBox(height: 8),
|
||||
CustomTextLabel(
|
||||
text: UiUtils.convertToAgo(context, DateTime.parse(newsItem.publishDate ?? newsItem.date!), 0)!, textStyle: TextStyle(color: UiUtils.getColorScheme(context).outline, fontSize: 11)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextLabel(text: newsItem.title ?? "", maxLines: 2, overflow: TextOverflow.ellipsis, textStyle: TextStyle(fontWeight: FontWeight.w600, fontSize: 14)),
|
||||
const SizedBox(height: 4),
|
||||
CustomTextLabel(text: newsItem.desc ?? "", maxLines: 2, overflow: TextOverflow.ellipsis, textStyle: TextStyle(fontSize: 12)),
|
||||
Divider(),
|
||||
showViewsAndLikes(newsId: newsItem.id ?? "0", totalViews: newsItem.totalViews ?? "0", totalLikes: newsItem.totalLikes ?? "0")
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void redirectToNewsDetailsScreen({required NewsModel newsItem}) {
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": newsItem, "isFromBreak": false, "fromShowMore": false});
|
||||
}
|
||||
}
|
||||
213
news-app/lib/ui/screens/dashBoard/dashBoardScreen.dart
Normal file
213
news-app/lib/ui/screens/dashBoard/dashBoardScreen.dart
Normal file
@@ -0,0 +1,213 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/ConnectivityCubit.dart';
|
||||
import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/Bookmark/bookmarkCubit.dart';
|
||||
import 'package:news/cubits/NewsByIdCubit.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/cubits/breakingNewsCubit.dart';
|
||||
import 'package:news/cubits/languageCubit.dart';
|
||||
import 'package:news/cubits/slugNewsCubit.dart';
|
||||
import 'package:news/cubits/themeCubit.dart';
|
||||
import 'package:news/data/models/BreakingNewsModel.dart';
|
||||
import 'package:news/data/models/NewsModel.dart';
|
||||
import 'package:news/ui/screens/CategoryScreen.dart';
|
||||
import 'package:news/ui/screens/HomePage/HomePage.dart';
|
||||
import 'package:news/ui/screens/Profile/ProfileScreen.dart';
|
||||
import 'package:news/ui/screens/RSSFeedScreen.dart';
|
||||
import 'package:news/ui/screens/Videos/VideoScreen.dart';
|
||||
import 'package:news/ui/widgets/SnackBarWidget.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/utils/strings.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
|
||||
GlobalKey<HomeScreenState>? homeScreenKey;
|
||||
bool? isNotificationReceivedInbg, isShared;
|
||||
String? notificationNewsId;
|
||||
String? saleNotification;
|
||||
String? routeSettingsName, newsSlug;
|
||||
|
||||
class DashBoard extends StatefulWidget {
|
||||
const DashBoard({super.key});
|
||||
|
||||
@override
|
||||
DashBoardState createState() => DashBoardState();
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
return CupertinoPageRoute(builder: (_) => const DashBoard());
|
||||
}
|
||||
}
|
||||
|
||||
class DashBoardState extends State<DashBoard> {
|
||||
List<Widget> fragments = [];
|
||||
DateTime? currentBackPressTime;
|
||||
int _selectedIndex = 0;
|
||||
List<IconData> iconList = [];
|
||||
List<String> itemName = [];
|
||||
bool shouldPopScope = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
homeScreenKey = GlobalKey<HomeScreenState>();
|
||||
iconList = [
|
||||
Icons.home_rounded,
|
||||
Icons.video_collection_rounded,
|
||||
//Add only if Category Mode is enabled From Admin panel.
|
||||
if (context.read<AppConfigurationCubit>().getCategoryMode() == "1") Icons.grid_view_rounded,
|
||||
if (context.read<AppConfigurationCubit>().getRSSFeedMode() == "1") Icons.rss_feed_rounded,
|
||||
Icons.settings_rounded
|
||||
];
|
||||
itemName = [
|
||||
'homeLbl',
|
||||
'videosLbl',
|
||||
if (context.read<AppConfigurationCubit>().getCategoryMode() == "1") 'categoryLbl',
|
||||
if (context.read<AppConfigurationCubit>().getRSSFeedMode() == "1") 'rssFeed',
|
||||
'profile'
|
||||
];
|
||||
fragments = [
|
||||
HomeScreen(key: homeScreenKey),
|
||||
const VideoScreen(),
|
||||
//Add only if Category Mode is enabled From Admin panel.
|
||||
if (context.read<AppConfigurationCubit>().getCategoryMode() == "1") const CategoryScreen(),
|
||||
if (context.read<AppConfigurationCubit>().getRSSFeedMode() == "1") RSSFeedScreen(),
|
||||
const ProfileScreen(),
|
||||
];
|
||||
if ((isShared != null && isShared == true) && routeSettingsName != null && newsSlug != null) initDynamicLinks();
|
||||
checkForPengingNotifications();
|
||||
checkMaintenanceMode();
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void checkMaintenanceMode() {
|
||||
if (context.read<AppConfigurationCubit>().getMaintenanceMode() == "1") {
|
||||
//app is in maintenance mode - no function should be performed
|
||||
Navigator.of(context).pushReplacementNamed(Routes.maintenance);
|
||||
}
|
||||
}
|
||||
|
||||
void checkForPengingNotifications() async {
|
||||
if (isNotificationReceivedInbg != null && notificationNewsId != null && notificationNewsId != "0" && isNotificationReceivedInbg!) {
|
||||
context.read<NewsByIdCubit>().getNewsById(newsId: notificationNewsId!, langId: context.read<AppLocalizationCubit>().state.id).then((value) {
|
||||
if (value.isNotEmpty) {
|
||||
Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": value[0], "isFromBreak": false, "fromShowMore": false});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void initDynamicLinks() async {
|
||||
await Future.delayed(Duration(seconds: 2)); // Simulate API delay
|
||||
if (routeSettingsName!.contains('/news/')) {
|
||||
String langCodeShared = 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: newsSlug).then((value) {
|
||||
NewsModel? model = (value[DATA] as List).map((e) => NewsModel.fromJson(e)).toList().first;
|
||||
Navigator.pushNamed(context, Routes.newsDetails,
|
||||
arguments: {"model": model, "slug": newsSlug, "isFromBreak": routeSettingsName!.contains('/breaking-news/') ? true : false, "fromShowMore": false});
|
||||
});
|
||||
} else if (routeSettingsName!.contains('/breaking-news/')) {
|
||||
//for 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).pushNamed(Routes.newsDetails, arguments: {"breakModel": brModel, "slug": newsSlug, "isFromBreak": true, "fromShowMore": false});
|
||||
});
|
||||
}
|
||||
isShared = false; //reset
|
||||
}
|
||||
|
||||
onWillPop(bool isTrue) {
|
||||
DateTime now = DateTime.now();
|
||||
if (_selectedIndex != 0) {
|
||||
setState(() {
|
||||
_selectedIndex = 0;
|
||||
shouldPopScope = false;
|
||||
});
|
||||
} else if (currentBackPressTime == null || now.difference(currentBackPressTime!) > const Duration(seconds: 2)) {
|
||||
currentBackPressTime = now;
|
||||
showSnackBar(UiUtils.getTranslatedLabel(context, 'exitWR'), context);
|
||||
setState(() => shouldPopScope = false);
|
||||
}
|
||||
|
||||
setState(() => shouldPopScope = true);
|
||||
}
|
||||
|
||||
Widget buildNavBarItem(IconData icon, String itemName, int index) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
setState(() => _selectedIndex = index);
|
||||
},
|
||||
child: Container(
|
||||
height: 60,
|
||||
width: MediaQuery.of(context).size.width / iconList.length,
|
||||
decoration: index == _selectedIndex ? BoxDecoration(border: Border(top: BorderSide(width: 3, color: Theme.of(context).primaryColor))) : null,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 3),
|
||||
Icon(icon, color: index == _selectedIndex ? Theme.of(context).primaryColor : UiUtils.getColorScheme(context).outline),
|
||||
SizedBox(height: 2.5),
|
||||
CustomTextLabel(
|
||||
text: itemName, softWrap: true, textStyle: TextStyle(color: (index == _selectedIndex) ? Theme.of(context).primaryColor : UiUtils.getColorScheme(context).outline, fontSize: 12))
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bottomBar() {
|
||||
List<Widget> navBarItemList = [];
|
||||
for (var i = 0; i < iconList.length; i++) {
|
||||
navBarItemList.add(buildNavBarItem(iconList[i], itemName[i], i));
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: (Platform.isIOS) ? EdgeInsets.only(bottom: 15) : EdgeInsets.zero,
|
||||
decoration: BoxDecoration(
|
||||
color: UiUtils.getColorScheme(context).secondary,
|
||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)),
|
||||
boxShadow: [BoxShadow(blurRadius: 6, offset: const Offset(5.0, 5.0), color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.4), spreadRadius: 0)],
|
||||
),
|
||||
child: ClipRRect(borderRadius: const BorderRadius.only(topLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)), child: Row(children: navBarItemList)));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
UiUtils.setUIOverlayStyle(appTheme: context.read<ThemeCubit>().state.appTheme); //set UiOverlayStyle according to selected theme
|
||||
return PopScope(
|
||||
canPop: shouldPopScope,
|
||||
onPopInvoked: onWillPop,
|
||||
child: BlocConsumer<AuthCubit, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is Authenticated) {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<BookmarkCubit>().getBookmark(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
context.read<LikeAndDisLikeCubit>().getLike(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
});
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
bottomNavigationBar: bottomBar(),
|
||||
body: BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||
builder: (context, connectivityStatus) {
|
||||
if (connectivityStatus is ConnectivityDisconnected) {
|
||||
return ErrorContainerWidget(errorMsg: UiUtils.getTranslatedLabel(context, 'internetmsg'), onRetry: () {});
|
||||
} else {
|
||||
return IndexedStack(index: _selectedIndex, children: fragments);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
413
news-app/lib/ui/screens/filter/FilterBottomSheet.dart
Normal file
413
news-app/lib/ui/screens/filter/FilterBottomSheet.dart
Normal file
@@ -0,0 +1,413 @@
|
||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/categoryCubit.dart';
|
||||
import 'package:news/cubits/tagCubit.dart';
|
||||
import 'package:news/data/models/CategoryModel.dart';
|
||||
import 'package:news/data/models/TagModel.dart';
|
||||
import 'package:news/ui/screens/filter/widgets/custom_date_selector.dart';
|
||||
import 'package:news/ui/screens/filter/widgets/duration_filter_widget.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class NewsFilterData {
|
||||
final List<CategoryModel> selectedCategories;
|
||||
final List<TagModel> selectedTags;
|
||||
final DateTime? selectedDate;
|
||||
final DurationFilter? durationFilter;
|
||||
|
||||
NewsFilterData({required this.selectedCategories, required this.selectedTags, this.selectedDate, this.durationFilter});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'NewsFilterData(selectedCategories: $selectedCategories, selectedTags: $selectedTags, selectedDate: $selectedDate, durationFilter: $durationFilter)';
|
||||
}
|
||||
}
|
||||
|
||||
class FilterBottomSheet extends StatefulWidget {
|
||||
final NewsFilterData initialFilters;
|
||||
final bool isCategoryModeON;
|
||||
|
||||
const FilterBottomSheet({
|
||||
Key? key,
|
||||
required this.isCategoryModeON,
|
||||
required this.initialFilters,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<FilterBottomSheet> createState() => _FilterBottomSheetState();
|
||||
}
|
||||
|
||||
class _FilterBottomSheetState extends State<FilterBottomSheet> {
|
||||
late final ValueNotifier<int> selectedFilterTabIndex = ValueNotifier(0);
|
||||
late List<CategoryModel> selectedCategories;
|
||||
late List<TagModel> selectedTags;
|
||||
DateTime? selectedDate;
|
||||
|
||||
List<String> filterTabs = [];
|
||||
|
||||
late DurationFilter? durationFilter = widget.initialFilters.durationFilter;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
filterTabs = [if (widget.isCategoryModeON) "catLbl", "tagLbl", "date"];
|
||||
|
||||
selectedCategories = List.from(widget.initialFilters.selectedCategories);
|
||||
selectedTags = List.from(widget.initialFilters.selectedTags);
|
||||
selectedDate = widget.initialFilters.selectedDate;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
selectedFilterTabIndex.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget _buildFilterTabItem({required String title, required int index}) {
|
||||
return ValueListenableBuilder<int>(
|
||||
valueListenable: selectedFilterTabIndex,
|
||||
builder: (context, value, child) {
|
||||
final isSelected = value == index;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
selectedFilterTabIndex.value = index;
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 15),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? UiUtils.getColorScheme(context).secondaryContainer : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: isSelected ? UiUtils.getColorScheme(context).secondary : UiUtils.getColorScheme(context).primaryContainer,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: isSelected ? UiUtils.getColorScheme(context).surface : UiUtils.getColorScheme(context).onSurface,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCategoryList() {
|
||||
return BlocBuilder<CategoryCubit, CategoryState>(
|
||||
builder: (context, state) {
|
||||
if (state is CategoryFetchInProgress) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (state is CategoryFetchFailure) {
|
||||
return Center(child: Text(state.errorMessage));
|
||||
}
|
||||
if (state is CategoryFetchSuccess) {
|
||||
return NotificationListener(
|
||||
onNotification: (notification) {
|
||||
if (notification is ScrollUpdateNotification) {
|
||||
if (notification.metrics.pixels == notification.metrics.maxScrollExtent) {
|
||||
if (context.read<CategoryCubit>().hasMoreCategory()) {
|
||||
context.read<CategoryCubit>().getMoreCategory(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: state.category.length,
|
||||
itemBuilder: (context, index) {
|
||||
final category = state.category[index];
|
||||
final isSelected = selectedCategories.contains(category);
|
||||
|
||||
return CheckboxListTile(
|
||||
title: Text(
|
||||
category.categoryName ?? "",
|
||||
style: TextStyle(
|
||||
color: UiUtils.getColorScheme(context).onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
value: isSelected,
|
||||
activeColor: UiUtils.getColorScheme(context).primary,
|
||||
checkColor: UiUtils.getColorScheme(context).onPrimary,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
if (value == true) {
|
||||
if (!selectedCategories.contains(category)) {
|
||||
selectedCategories.add(category);
|
||||
}
|
||||
} else {
|
||||
selectedCategories.remove(category);
|
||||
}
|
||||
});
|
||||
},
|
||||
controlAffinity: ListTileControlAffinity.trailing,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTagList() {
|
||||
return BlocBuilder<TagCubit, TagState>(
|
||||
builder: (context, state) {
|
||||
if (state is TagFetchInProgress) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (state is TagFetchFailure) {
|
||||
return Center(child: Text(state.errorMessage));
|
||||
}
|
||||
if (state is TagFetchSuccess) {
|
||||
return NotificationListener(
|
||||
onNotification: (notification) {
|
||||
if (notification is ScrollUpdateNotification) {
|
||||
if (notification.metrics.pixels == notification.metrics.maxScrollExtent) {
|
||||
if (context.read<TagCubit>().hasMoreTags()) {
|
||||
context.read<TagCubit>().getMoreTags(langId: context.read<AppLocalizationCubit>().state.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: state.tag.length,
|
||||
itemBuilder: (context, index) {
|
||||
final tag = state.tag[index];
|
||||
final isSelected = selectedTags.contains(tag);
|
||||
|
||||
return CheckboxListTile(
|
||||
title: Text(
|
||||
tag.tagName ?? "",
|
||||
style: TextStyle(
|
||||
color: UiUtils.getColorScheme(context).onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
value: isSelected,
|
||||
activeColor: UiUtils.getColorScheme(context).primary,
|
||||
checkColor: UiUtils.getColorScheme(context).onPrimary,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
if (value == true) {
|
||||
if (!selectedTags.contains(tag)) {
|
||||
selectedTags.add(tag);
|
||||
}
|
||||
} else {
|
||||
selectedTags.remove(tag);
|
||||
}
|
||||
});
|
||||
},
|
||||
controlAffinity: ListTileControlAffinity.trailing,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDatePicker() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 250,
|
||||
child: CustomCalendarView(
|
||||
selectedDate: selectedDate,
|
||||
onDateSelected: (day) {
|
||||
selectedDate = day;
|
||||
setState(() {});
|
||||
},
|
||||
)),
|
||||
DurationFilterWidget(
|
||||
selectedFilter: durationFilter,
|
||||
filters: [
|
||||
LastDays(daysCount: 1),
|
||||
LastDays(daysCount: 7),
|
||||
LastDays(daysCount: 30),
|
||||
LastDays(daysCount: 60),
|
||||
LastDays(daysCount: 90),
|
||||
...List.generate(
|
||||
7,
|
||||
(int index) {
|
||||
return Year(year: DateTime.now().year - index);
|
||||
},
|
||||
),
|
||||
],
|
||||
onSelected: (DurationFilter? filter) {
|
||||
durationFilter = filter;
|
||||
setState(() {});
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilterContent() {
|
||||
return ValueListenableBuilder<int>(
|
||||
valueListenable: selectedFilterTabIndex,
|
||||
builder: (context, selectedIndex, _) {
|
||||
final views = widget.isCategoryModeON ? [_buildCategoryList(), _buildTagList(), _buildDatePicker()] : [_buildTagList(), _buildDatePicker()];
|
||||
return views[selectedIndex];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height * 0.7,
|
||||
decoration: BoxDecoration(
|
||||
color: UiUtils.getColorScheme(context).surface,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 5,
|
||||
margin: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
UiUtils.getTranslatedLabel(context, "filter"),
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: UiUtils.getColorScheme(context).onSurface,
|
||||
),
|
||||
),
|
||||
Divider(),
|
||||
const SizedBox(height: 15),
|
||||
Expanded(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: filterTabs
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: _buildFilterTabItem(
|
||||
title: UiUtils.getTranslatedLabel(context, entry.value),
|
||||
index: entry.key,
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 1,
|
||||
color: Colors.grey[300],
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
),
|
||||
Expanded(
|
||||
flex: 7,
|
||||
child: _buildFilterContent(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
selectedCategories = <CategoryModel>[];
|
||||
selectedTags = <TagModel>[];
|
||||
selectedDate = null;
|
||||
durationFilter = null;
|
||||
});
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(color: primaryColor),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
),
|
||||
child: Text(
|
||||
UiUtils.getTranslatedLabel(context, 'clear'),
|
||||
style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
final result = NewsFilterData(selectedCategories: selectedCategories, selectedTags: selectedTags, selectedDate: selectedDate, durationFilter: durationFilter);
|
||||
Navigator.pop(context, result);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
),
|
||||
child: Text(
|
||||
UiUtils.getTranslatedLabel(context, 'apply'),
|
||||
style: TextStyle(
|
||||
color: UiUtils.getColorScheme(context).onPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to show the filter bottom sheet
|
||||
Future<NewsFilterData?> showFilterBottomSheet({
|
||||
required BuildContext context,
|
||||
required bool isCategoryModeON,
|
||||
required NewsFilterData initialFilters,
|
||||
}) async {
|
||||
return await showModalBottomSheet<NewsFilterData>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => FilterBottomSheet(
|
||||
isCategoryModeON: isCategoryModeON,
|
||||
initialFilters: initialFilters,
|
||||
));
|
||||
}
|
||||
169
news-app/lib/ui/screens/filter/widgets/custom_date_selector.dart
Normal file
169
news-app/lib/ui/screens/filter/widgets/custom_date_selector.dart
Normal file
@@ -0,0 +1,169 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
class CustomDateSelector extends StatefulWidget {
|
||||
final DateTime? selectedDate;
|
||||
final void Function(DateTime? date) onDateChanged;
|
||||
const CustomDateSelector({
|
||||
Key? key,
|
||||
required this.onDateChanged,
|
||||
this.selectedDate,
|
||||
}) : super(key: key);
|
||||
@override
|
||||
_CustomDateSelectorState createState() => _CustomDateSelectorState();
|
||||
}
|
||||
|
||||
class _CustomDateSelectorState extends State<CustomDateSelector> {
|
||||
late DateTime? selectedDate = widget.selectedDate;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant CustomDateSelector oldWidget) {
|
||||
if (oldWidget.selectedDate != widget.selectedDate) {
|
||||
selectedDate = widget.selectedDate;
|
||||
setState(() {});
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
colorScheme:
|
||||
ColorScheme.of(context).copyWith(primary: primaryColor),
|
||||
datePickerTheme: UiUtils.buildDatePickerTheme(context)),
|
||||
child: CalendarDatePicker(
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(1999),
|
||||
currentDate: selectedDate,
|
||||
lastDate: DateTime.now(),
|
||||
onDateChanged: (DateTime date) {
|
||||
setState(() {
|
||||
selectedDate = date;
|
||||
});
|
||||
widget.onDateChanged(date);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomCalendarView extends StatefulWidget {
|
||||
final Function(DateTime date) onDateSelected;
|
||||
final DateTime? selectedDate;
|
||||
const CustomCalendarView(
|
||||
{super.key, required this.onDateSelected, this.selectedDate});
|
||||
@override
|
||||
_CustomCalendarViewState createState() => _CustomCalendarViewState();
|
||||
}
|
||||
|
||||
class _CustomCalendarViewState extends State<CustomCalendarView> {
|
||||
DateTime selectedDate = DateTime.now();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final firstDayOfMonth = DateTime(selectedDate.year, selectedDate.month, 1);
|
||||
final startWeekday = firstDayOfMonth.weekday; // Mon = 1, Sun = 7
|
||||
final daysInMonth =
|
||||
DateUtils.getDaysInMonth(selectedDate.year, selectedDate.month);
|
||||
|
||||
// Previous month filler days
|
||||
final prevMonth = DateTime(selectedDate.year, selectedDate.month - 1);
|
||||
final daysInPrevMonth =
|
||||
DateUtils.getDaysInMonth(prevMonth.year, prevMonth.month);
|
||||
final prefixDays = List.generate(
|
||||
startWeekday - 1,
|
||||
(i) => DateTime(prevMonth.year, prevMonth.month,
|
||||
daysInPrevMonth - (startWeekday - 2 - i)));
|
||||
|
||||
// Current month days
|
||||
final currentMonthDays = List.generate(daysInMonth,
|
||||
(i) => DateTime(selectedDate.year, selectedDate.month, i + 1));
|
||||
|
||||
// Fillers from next month
|
||||
final totalSlots = prefixDays.length + currentMonthDays.length;
|
||||
final suffixDays = List.generate(42 - totalSlots,
|
||||
(i) => DateTime(selectedDate.year, selectedDate.month + 1, i + 1));
|
||||
|
||||
final allDays = [...prefixDays, ...currentMonthDays, ...suffixDays];
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Text(
|
||||
DateFormat.yMMMM().format(selectedDate),
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||
.map((d) => Expanded(
|
||||
child: Center(
|
||||
child: Text(d,
|
||||
style: TextStyle(fontWeight: FontWeight.w500)),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: GridView.builder(
|
||||
padding: const EdgeInsets.all(8),
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 7,
|
||||
crossAxisSpacing: 6,
|
||||
mainAxisSpacing: 6,
|
||||
),
|
||||
itemCount: allDays.length,
|
||||
itemBuilder: (_, i) {
|
||||
final day = allDays[i];
|
||||
final isCurrentMonth = day.month == selectedDate.month;
|
||||
final isToday = DateUtils.isSameDay(day, DateTime.now());
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
final bool isFutureDate = day.isAfter(DateTime.now());
|
||||
|
||||
if (isCurrentMonth && !isFutureDate) {
|
||||
widget.onDateSelected(day);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
(widget.selectedDate?.day == day.day && isCurrentMonth)
|
||||
? UiUtils.getColorScheme(context).primary
|
||||
: (isToday
|
||||
? UiUtils.getColorScheme(context)
|
||||
.primary
|
||||
.withValues(alpha: 0.1)
|
||||
: null),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${day.day}',
|
||||
style: TextStyle(
|
||||
color: isCurrentMonth ? null : Colors.grey,
|
||||
fontWeight:
|
||||
isToday ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:news/utils/strings.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
|
||||
abstract class DurationFilter {
|
||||
Map<String, String> toMap();
|
||||
String toText(BuildContext context);
|
||||
}
|
||||
|
||||
class LastDays extends DurationFilter {
|
||||
final int daysCount;
|
||||
LastDays({required this.daysCount});
|
||||
@override
|
||||
toMap() {
|
||||
return {
|
||||
LAST_N_DAYS: daysCount.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toText(BuildContext context) {
|
||||
if (daysCount == 1) {
|
||||
return UiUtils.getTranslatedLabel(context, 'today');
|
||||
}
|
||||
|
||||
return UiUtils.getTranslatedLabel(context, 'last') + ' $daysCount ' + UiUtils.getTranslatedLabel(context, 'days');
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(covariant DurationFilter other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is LastDays) {
|
||||
return other.daysCount == daysCount;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => daysCount.hashCode;
|
||||
}
|
||||
|
||||
class Year extends DurationFilter {
|
||||
final int year;
|
||||
Year({required this.year});
|
||||
@override
|
||||
Map<String, String> toMap() {
|
||||
return {
|
||||
YEAR: year.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toText(BuildContext context) {
|
||||
return year.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(covariant DurationFilter other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is Year) {
|
||||
return other.year == year;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => year.hashCode;
|
||||
}
|
||||
|
||||
class DurationFilterWidget extends StatefulWidget {
|
||||
final DurationFilter? selectedFilter;
|
||||
final List<DurationFilter> filters;
|
||||
final void Function(DurationFilter? filter)? onSelected;
|
||||
const DurationFilterWidget({super.key, required this.filters, this.onSelected, this.selectedFilter});
|
||||
|
||||
@override
|
||||
State<DurationFilterWidget> createState() => _DurationFilterWidgetState();
|
||||
}
|
||||
|
||||
class _DurationFilterWidgetState extends State<DurationFilterWidget> {
|
||||
late DurationFilter? selectedFilter = widget.selectedFilter;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(DurationFilterWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.selectedFilter != oldWidget.selectedFilter) {
|
||||
selectedFilter = widget.selectedFilter;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: widget.filters.map(
|
||||
(DurationFilter e) {
|
||||
return _buildDurationFilter(context, e);
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDurationFilter(BuildContext context, DurationFilter filter) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (selectedFilter == filter) {
|
||||
selectedFilter = null;
|
||||
} else {
|
||||
selectedFilter = filter;
|
||||
}
|
||||
widget.onSelected?.call(selectedFilter);
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
child: Container(
|
||||
width: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: selectedFilter == filter ? UiUtils.getColorScheme(context).secondaryContainer.withValues(alpha: 0.2) : UiUtils.getColorScheme(context).surface,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(filter.toText(context)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
196
news-app/lib/ui/screens/introSlider.dart
Normal file
196
news-app/lib/ui/screens/introSlider.dart
Normal file
@@ -0,0 +1,196 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/customTextBtn.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/settingCubit.dart';
|
||||
|
||||
class Slide {
|
||||
final String? imageUrl;
|
||||
final String? title;
|
||||
final String? description;
|
||||
|
||||
Slide({@required this.imageUrl, @required this.title, @required this.description});
|
||||
}
|
||||
|
||||
class IntroSliderScreen extends StatefulWidget {
|
||||
const IntroSliderScreen({super.key});
|
||||
|
||||
@override
|
||||
GettingStartedScreenState createState() => GettingStartedScreenState();
|
||||
}
|
||||
|
||||
class GettingStartedScreenState extends State<IntroSliderScreen> with TickerProviderStateMixin {
|
||||
PageController pageController = PageController();
|
||||
|
||||
int currentIndex = 0;
|
||||
|
||||
late List<Slide> slideList = [
|
||||
Slide(imageUrl: 'onboarding1', title: UiUtils.getTranslatedLabel(context, 'welTitle1'), description: UiUtils.getTranslatedLabel(context, 'welDes1')),
|
||||
Slide(imageUrl: 'onboarding2', title: UiUtils.getTranslatedLabel(context, 'welTitle2'), description: UiUtils.getTranslatedLabel(context, 'welDes2')),
|
||||
Slide(imageUrl: 'onboarding3', title: UiUtils.getTranslatedLabel(context, 'welTitle3'), description: UiUtils.getTranslatedLabel(context, 'welDes3')),
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
bottomNavigationBar: nextButton(),
|
||||
appBar: AppBar(automaticallyImplyLeading: false, centerTitle: false, actions: [setSkipButton()], title: SvgPictureWidget(assetName: "intro_icon", fit: BoxFit.cover)),
|
||||
body: _buildIntroSlider());
|
||||
}
|
||||
|
||||
gotoNext() {
|
||||
context.read<SettingsCubit>().changeShowIntroSlider(false);
|
||||
Navigator.of(context).pushReplacementNamed(Routes.login);
|
||||
}
|
||||
|
||||
void onPageChanged(int index) {
|
||||
setState(() {
|
||||
currentIndex = index; //update current index for Next button
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildIntroSlider() {
|
||||
return PageView.builder(
|
||||
onPageChanged: onPageChanged,
|
||||
controller: pageController,
|
||||
itemCount: slideList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
final imagePath = slideList[index].imageUrl;
|
||||
|
||||
return SizedBox(
|
||||
width: size.width * 0.99,
|
||||
height: size.height * 0.75,
|
||||
child: Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.identity()
|
||||
..setEntry(3, 2, 0.001)
|
||||
..rotateX(0.1),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: size.width * 0.3,
|
||||
right: size.width * 0.3,
|
||||
top: size.height * 0.71,
|
||||
child: Container(
|
||||
height: 7,
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: UiUtils.getColorScheme(context).primary.withOpacity(0.3),
|
||||
blurRadius: 20,
|
||||
spreadRadius: 5,
|
||||
offset: Offset.zero,
|
||||
),
|
||||
],
|
||||
shape: BoxShape.rectangle,
|
||||
),
|
||||
),
|
||||
),
|
||||
Card(
|
||||
color: secondaryColor,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(40.0)),
|
||||
elevation: 0,
|
||||
margin: const EdgeInsets.fromLTRB(24, 30, 24, 20),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: imagePath != null ? SvgPictureWidget(assetName: imagePath, fit: BoxFit.contain) : SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
titleText(index),
|
||||
subtitleText(index),
|
||||
progressIndicator(index),
|
||||
const SizedBox(height: 25),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget setSkipButton() {
|
||||
return (currentIndex != slideList.length - 1)
|
||||
? CustomTextButton(
|
||||
onTap: () {
|
||||
gotoNext();
|
||||
},
|
||||
color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7),
|
||||
text: UiUtils.getTranslatedLabel(context, 'skip'))
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
|
||||
Widget titleText(int index) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(left: 20, right: 10),
|
||||
margin: const EdgeInsets.only(bottom: 20.0, left: 10, right: 10),
|
||||
alignment: Alignment.center,
|
||||
child: CustomTextLabel(text: slideList[index].title!, textStyle: Theme.of(context).textTheme.headlineSmall?.copyWith(color: darkSecondaryColor, fontWeight: FontWeight.bold, letterSpacing: 0.5)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget subtitleText(int index) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
margin: const EdgeInsets.only(bottom: 55.0, left: 10, right: 10),
|
||||
child: CustomTextLabel(
|
||||
text: slideList[index].description!,
|
||||
textAlign: TextAlign.left,
|
||||
textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: darkSecondaryColor.withOpacity(0.5), fontWeight: FontWeight.normal, letterSpacing: 0.5),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis));
|
||||
}
|
||||
|
||||
Widget progressIndicator(int index) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List<Widget>.generate(
|
||||
slideList.length,
|
||||
(index) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: InkWell(
|
||||
onTap: () => pageController.animateToPage(index, duration: const Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn),
|
||||
child: (currentIndex == index)
|
||||
? ClipRRect(borderRadius: BorderRadius.circular(10.0), child: Container(height: 10, width: 40.0, color: darkSecondaryColor))
|
||||
: CircleAvatar(radius: 5, backgroundColor: darkSecondaryColor.withOpacity(0.6))))));
|
||||
}
|
||||
|
||||
Widget nextButton() {
|
||||
return MaterialButton(
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
padding: EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.width * 0.28, vertical: 20),
|
||||
onPressed: () {
|
||||
if (currentIndex == slideList.length - 1 && currentIndex != 0) {
|
||||
gotoNext();
|
||||
} else {
|
||||
currentIndex += 1;
|
||||
pageController.animateToPage(currentIndex, duration: const Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
height: 50,
|
||||
width: 162,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(14)),
|
||||
child: CustomTextLabel(
|
||||
text: (currentIndex == (slideList.length - 1)) ? 'loginBtn' : 'nxt',
|
||||
textStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(color: secondaryColor, fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center)));
|
||||
}
|
||||
}
|
||||
255
news-app/lib/ui/screens/languageList.dart
Normal file
255
news-app/lib/ui/screens/languageList.dart
Normal file
@@ -0,0 +1,255 @@
|
||||
import 'dart:convert';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/Auth/registerTokenCubit.dart';
|
||||
import 'package:news/cubits/generalNewsCubit.dart';
|
||||
import 'package:news/cubits/rssFeedCubit.dart';
|
||||
import 'package:news/cubits/settingCubit.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/cubits/breakingNewsCubit.dart';
|
||||
import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart';
|
||||
import 'package:news/ui/screens/dashBoard/dashBoardScreen.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/customAppBar.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/ui/widgets/networkImage.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:news/utils/internetConnectivity.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/cubits/Auth/authCubit.dart';
|
||||
import 'package:news/cubits/languageCubit.dart';
|
||||
import 'package:news/cubits/Bookmark/bookmarkCubit.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/categoryCubit.dart';
|
||||
import 'package:news/cubits/featureSectionCubit.dart';
|
||||
import 'package:news/cubits/languageJsonCubit.dart';
|
||||
import 'package:news/cubits/liveStreamCubit.dart';
|
||||
import 'package:news/cubits/otherPagesCubit.dart';
|
||||
import 'package:news/cubits/videosCubit.dart';
|
||||
import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart';
|
||||
|
||||
class LanguageList extends StatefulWidget {
|
||||
final String? from;
|
||||
const LanguageList({super.key, this.from});
|
||||
|
||||
@override
|
||||
LanguageListState createState() => LanguageListState();
|
||||
|
||||
static Route route(RouteSettings routeSettings) {
|
||||
final arguments = routeSettings.arguments as Map<String, dynamic>;
|
||||
return CupertinoPageRoute(builder: (_) => LanguageList(from: arguments['from']));
|
||||
}
|
||||
}
|
||||
|
||||
class LanguageListState extends State<LanguageList> {
|
||||
String? selLanCode, selLanId;
|
||||
late String latitude, longitude;
|
||||
int? selLanRTL;
|
||||
bool isNetworkAvail = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
isNetworkAvailable();
|
||||
getLanguageData();
|
||||
setLatitudeLongitude();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future getLanguageData() async {
|
||||
Future.delayed(Duration.zero, () {
|
||||
context.read<LanguageCubit>().getLanguage();
|
||||
});
|
||||
}
|
||||
|
||||
void setLatitudeLongitude() {
|
||||
latitude = SettingsLocalDataRepository().getLocationCityValues().first;
|
||||
longitude = SettingsLocalDataRepository().getLocationCityValues().last;
|
||||
}
|
||||
|
||||
Widget getLangList() {
|
||||
return BlocBuilder<AppLocalizationCubit, AppLocalizationState>(builder: (context, stateLocale) {
|
||||
return BlocBuilder<LanguageCubit, LanguageState>(builder: (context, state) {
|
||||
if (state is LanguageFetchSuccess) {
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.only(bottom: 20, top: 10),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemBuilder: ((context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20.0, 5.0, 20.0, 5.0),
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height * 0.08,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15.0),
|
||||
color: (selLanCode ?? stateLocale.languageCode) == state.language[index].code! ? UiUtils.getColorScheme(context).primaryContainer : null),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
Intl.defaultLocale = state.language[index].code;
|
||||
selLanCode = state.language[index].code!;
|
||||
selLanId = state.language[index].id!;
|
||||
selLanRTL = state.language[index].isRTL!;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 15, right: 15),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
CustomNetworkImage(networkImageUrl: state.language[index].image!, isVideo: false, height: 40, fit: BoxFit.fill, width: 40),
|
||||
SizedBox(width: MediaQuery.of(context).size.width * 0.05),
|
||||
CustomTextLabel(
|
||||
text: state.language[index].languageDisplayName ?? state.language[index].language!,
|
||||
textStyle: Theme.of(this.context).textTheme.titleLarge?.copyWith(
|
||||
color: ((selLanCode ?? (stateLocale).languageCode) == state.language[index].code!)
|
||||
? UiUtils.getColorScheme(context).secondary
|
||||
: UiUtils.getColorScheme(context).primaryContainer)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
}),
|
||||
separatorBuilder: (context, index) {
|
||||
return const SizedBox(height: 1.0);
|
||||
},
|
||||
itemCount: state.language.length);
|
||||
}
|
||||
if (state is LanguageFetchFailure) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 30.0, right: 30.0),
|
||||
child: ErrorContainerWidget(
|
||||
errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getLanguageData),
|
||||
);
|
||||
}
|
||||
return const Padding(padding: EdgeInsets.only(bottom: 10.0, left: 30.0, right: 30.0), child: SizedBox.shrink());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
saveBtn() {
|
||||
return BlocConsumer<LanguageJsonCubit, LanguageJsonState>(
|
||||
bloc: context.read<LanguageJsonCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is LanguageJsonFetchSuccess) {
|
||||
final langId = selLanId ?? context.read<AppLocalizationCubit>().state.id;
|
||||
|
||||
UiUtils.setDynamicStringValue(
|
||||
context.read<AppLocalizationCubit>().state.languageCode,
|
||||
jsonEncode(state.languageJson),
|
||||
).then((_) {
|
||||
// Update languageId
|
||||
homeScreenKey?.currentState?.languageId = langId;
|
||||
|
||||
// Fetch general data
|
||||
context.read<OtherPageCubit>().getOtherPage(langId: langId);
|
||||
context.read<SectionCubit>().getSection(langId: langId, latitude: latitude, longitude: longitude);
|
||||
context.read<LiveStreamCubit>().getLiveStream(langId: langId);
|
||||
context.read<GeneralNewsCubit>().getGeneralNews(langId: langId, latitude: latitude, longitude: longitude);
|
||||
context.read<VideoCubit>().getVideo(langId: langId, latitude: latitude, longitude: longitude);
|
||||
context.read<CategoryCubit>().getCategory(langId: langId);
|
||||
|
||||
// Conditional based on config
|
||||
final config = context.read<AppConfigurationCubit>();
|
||||
if (config.getWeatherMode() == "1") {
|
||||
homeScreenKey?.currentState?.getWeatherData();
|
||||
}
|
||||
if (config.getRSSFeedMode() == "1") {
|
||||
context.read<RSSFeedCubit>().getRSSFeed(langId: langId);
|
||||
}
|
||||
if (config.getBreakingNewsMode() == "1") {
|
||||
context.read<BreakingNewsCubit>().getBreakingNews(langId: langId);
|
||||
}
|
||||
|
||||
// If user is logged in
|
||||
final auth = context.read<AuthCubit>();
|
||||
if (auth.getUserId() != "0") {
|
||||
context.read<LikeAndDisLikeCubit>().getLike(langId: langId);
|
||||
context.read<BookmarkCubit>().getBookmark(langId: langId);
|
||||
updateUserLanguageWithFCMid();
|
||||
}
|
||||
});
|
||||
|
||||
if (widget.from != null && widget.from == "firstLogin" && context.read<CategoryCubit>().getCatList().isNotEmpty) {
|
||||
//check if it is firstLogin - then goto Home or else pop
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(Routes.managePref, (route) => false, arguments: {"from": 2});
|
||||
} else if (widget.from == "firstLogin") {
|
||||
Navigator.of(context).pushReplacementNamed(Routes.home, arguments: false);
|
||||
} else {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is LanguageJsonFetchSuccess) {
|
||||
return InkWell(
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
child: Container(
|
||||
height: 45.0,
|
||||
margin: const EdgeInsetsDirectional.all(20),
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(15.0)),
|
||||
child: CustomTextLabel(text: 'saveLbl', textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: backgroundColor, fontWeight: FontWeight.bold))),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (selLanCode != null && context.read<AppLocalizationCubit>().state.languageCode != selLanCode) {
|
||||
context.read<AppLocalizationCubit>().changeLanguage(selLanCode!, selLanId!, selLanRTL!);
|
||||
context.read<LanguageJsonCubit>().getLanguageJson(lanCode: selLanCode!);
|
||||
} else {
|
||||
if (widget.from != null && widget.from == "firstLogin" && context.read<CategoryCubit>().getCatList().isNotEmpty) {
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(Routes.managePref, (route) => false, arguments: {"from": 2});
|
||||
} else if (widget.from == "firstLogin") {
|
||||
Navigator.of(context).pushReplacementNamed(Routes.home, arguments: false);
|
||||
} else {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
});
|
||||
}
|
||||
|
||||
void updateUserLanguageWithFCMid() async {
|
||||
String currentFCMId = context.read<SettingsCubit>().getSettings().token.trim();
|
||||
|
||||
if (currentFCMId.isEmpty) {
|
||||
final newToken = await FirebaseMessaging.instance.getToken();
|
||||
if (newToken != null) {
|
||||
currentFCMId = newToken;
|
||||
context.read<RegisterTokenCubit>().registerToken(fcmId: currentFCMId, context: context);
|
||||
}
|
||||
} else {
|
||||
context.read<RegisterTokenCubit>().registerToken(fcmId: currentFCMId, context: context);
|
||||
}
|
||||
|
||||
context.read<SettingsCubit>().changeFcmToken(currentFCMId);
|
||||
}
|
||||
|
||||
isNetworkAvailable() async {
|
||||
if (await InternetConnectivity.isNetworkAvailable()) {
|
||||
setState(() {
|
||||
isNetworkAvail = true;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
isNetworkAvail = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: CustomAppBar(height: 45, isBackBtn: true, label: 'chooseLanLbl', horizontalPad: 15, isConvertText: true),
|
||||
bottomNavigationBar: (isNetworkAvail) ? saveBtn() : const SizedBox.shrink(),
|
||||
body: getLangList());
|
||||
}
|
||||
}
|
||||
52
news-app/lib/ui/screens/maintenanceScreen.dart
Normal file
52
news-app/lib/ui/screens/maintenanceScreen.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
|
||||
class MaintenanceScreen extends StatefulWidget {
|
||||
const MaintenanceScreen({super.key});
|
||||
|
||||
@override
|
||||
MaintenanceScreenState createState() => MaintenanceScreenState();
|
||||
}
|
||||
|
||||
class MaintenanceScreenState extends State<MaintenanceScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 15.0, end: 15.0, top: 10.0, bottom: 10.0),
|
||||
child: BlocBuilder<AppConfigurationCubit, AppConfigurationState>(
|
||||
builder: (context, state) {
|
||||
if (state is AppConfigurationFetchSuccess && state.appConfiguration.maintenanceMode == "1") {
|
||||
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
SvgPictureWidget(assetName: "maintenance"),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: CustomTextLabel(
|
||||
textStyle: TextStyle(color: Theme.of(context).colorScheme.primaryContainer, fontSize: 18, fontWeight: FontWeight.w600),
|
||||
textAlign: TextAlign.center,
|
||||
text: 'maintenanceMessageLbl'))
|
||||
]);
|
||||
} else if (state is AppConfigurationFetchSuccess && state.appConfiguration.maintenanceMode == "0") {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
//default/Processing state
|
||||
return Padding(padding: const EdgeInsets.only(bottom: 10.0, left: 10.0, right: 10.0), child: CircularProgressIndicator());
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
134
news-app/lib/ui/screens/splashScreen.dart
Normal file
134
news-app/lib/ui/screens/splashScreen.dart
Normal file
@@ -0,0 +1,134 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:news/cubits/appLocalizationCubit.dart';
|
||||
import 'package:news/cubits/languageCubit.dart';
|
||||
import 'package:news/cubits/languageJsonCubit.dart';
|
||||
import 'package:news/cubits/settingCubit.dart';
|
||||
import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart';
|
||||
import 'package:news/ui/styles/appTheme.dart';
|
||||
import 'package:news/ui/styles/colors.dart';
|
||||
import 'package:news/ui/widgets/customTextLabel.dart';
|
||||
import 'package:news/ui/widgets/errorContainerWidget.dart';
|
||||
import 'package:news/utils/ErrorMessageKeys.dart';
|
||||
import 'package:news/utils/uiUtils.dart';
|
||||
import 'package:news/app/routes.dart';
|
||||
import 'package:news/cubits/appSystemSettingCubit.dart';
|
||||
import 'package:news/utils/hiveBoxKeys.dart';
|
||||
|
||||
class Splash extends StatefulWidget {
|
||||
const Splash({super.key});
|
||||
|
||||
@override
|
||||
SplashState createState() => SplashState();
|
||||
}
|
||||
|
||||
class SplashState extends State<Splash> with TickerProviderStateMixin {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
fetchAppConfigurations();
|
||||
}
|
||||
|
||||
fetchAppConfigurations() {
|
||||
context.read<AppConfigurationCubit>().fetchAppConfiguration();
|
||||
}
|
||||
|
||||
fetchLanguages({required AppConfigurationFetchSuccess state}) async {
|
||||
String currentLanguage = Hive.box(settingsBoxKey).get(currentLanguageCodeKey) ?? "";
|
||||
if (currentLanguage == "" && state.appConfiguration.defaultLangDataModel != null) {
|
||||
context
|
||||
.read<AppLocalizationCubit>()
|
||||
.changeLanguage(state.appConfiguration.defaultLangDataModel!.code!, state.appConfiguration.defaultLangDataModel!.id!, state.appConfiguration.defaultLangDataModel!.isRTL!);
|
||||
context.read<LanguageJsonCubit>().fetchCurrentLanguageAndLabels(state.appConfiguration.defaultLangDataModel!.code!);
|
||||
} else {
|
||||
context.read<LanguageJsonCubit>().fetchCurrentLanguageAndLabels(currentLanguage);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (Platform.isAndroid) UiUtils.setUIOverlayStyle(appTheme: AppTheme.Dark); //set UiOverlayStyle to dark - due to fixed splashScreen backgroundColor // according to selected theme
|
||||
return Scaffold(backgroundColor: Theme.of(context).secondaryHeaderColor, body: buildScale());
|
||||
}
|
||||
|
||||
Future<void> navigationPage() async {
|
||||
Future.delayed(const Duration(seconds: 4), () async {
|
||||
final currentSettings = context.read<SettingsCubit>().state.settingsModel;
|
||||
if (context.read<AppConfigurationCubit>().getMaintenanceMode() == "1") {
|
||||
//app is in maintenance mode - no function should be performed
|
||||
Navigator.of(context).pushReplacementNamed(Routes.maintenance);
|
||||
} else if (currentSettings!.showIntroSlider) {
|
||||
Navigator.of(context).pushReplacementNamed(Routes.introSlider);
|
||||
} else {
|
||||
Navigator.of(context).pushReplacementNamed(Routes.home, arguments: false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget buildScale() {
|
||||
return BlocConsumer<AppConfigurationCubit, AppConfigurationState>(
|
||||
bloc: context.read<AppConfigurationCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is AppConfigurationFetchSuccess) {
|
||||
fetchLanguages(state: state);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return BlocConsumer<LanguageJsonCubit, LanguageJsonState>(
|
||||
bloc: context.read<LanguageJsonCubit>(),
|
||||
listener: (context, state) {
|
||||
if (state is LanguageJsonFetchSuccess) {
|
||||
navigationPage();
|
||||
context.read<LanguageCubit>().getLanguage(); //Load languages for dynamic link
|
||||
}
|
||||
},
|
||||
builder: (context, langState) {
|
||||
if (state is AppConfigurationFetchFailure) {
|
||||
return ErrorContainerWidget(
|
||||
errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage,
|
||||
onRetry: () {
|
||||
fetchAppConfigurations();
|
||||
},
|
||||
);
|
||||
} else if (langState is LanguageJsonFetchFailure) {
|
||||
return ErrorContainerWidget(
|
||||
errorMsg: (langState.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : langState.errorMessage,
|
||||
onRetry: () {
|
||||
fetchLanguages(state: state as AppConfigurationFetchSuccess);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
width: double.maxFinite,
|
||||
decoration: BoxDecoration(color: primaryColor),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [const SizedBox(height: 220), splashLogoIcon(), newsTextIcon(), subTitle(), const Spacer(), bottomText()]),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Widget splashLogoIcon() {
|
||||
return Center(child: SvgPictureWidget(assetName: "splash_icon", height: 110.0, fit: BoxFit.fill));
|
||||
}
|
||||
|
||||
Widget newsTextIcon() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20.0),
|
||||
child: Center(child: SvgPictureWidget(assetName: "caribe_blanco", height: 58.0, width: 300, fit: BoxFit.fill)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget subTitle() =>
|
||||
CustomTextLabel(text: 'fastTrendNewsLbl', textAlign: TextAlign.center, textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(color: backgroundColor, fontWeight: FontWeight.bold));
|
||||
|
||||
Widget bottomText() => Container(margin: const EdgeInsetsDirectional.only(bottom: 20), child: SvgPictureWidget(assetName: "wrteam_logo", height: 40.0, fit: BoxFit.fill));
|
||||
}
|
||||
59
news-app/lib/ui/styles/appTheme.dart
Normal file
59
news-app/lib/ui/styles/appTheme.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'colors.dart';
|
||||
|
||||
enum AppTheme { Light, Dark }
|
||||
|
||||
final appThemeData = {
|
||||
AppTheme.Light: ThemeData(
|
||||
useMaterial3: false,
|
||||
fontFamily: 'Roboto',
|
||||
brightness: Brightness.light,
|
||||
primaryColor: primaryColor,
|
||||
canvasColor: backgroundColor,
|
||||
textTheme: const TextTheme().apply(bodyColor: darkSecondaryColor, displayColor: darkSecondaryColor),
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0.0,
|
||||
backgroundColor: backgroundColor,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light, statusBarIconBrightness: Brightness.dark, statusBarColor: backgroundColor.withOpacity(0.8))),
|
||||
iconTheme: const IconThemeData(color: darkSecondaryColor),
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: primaryColor,
|
||||
brightness: Brightness.light,
|
||||
surface: secondaryColor,
|
||||
secondary: secondaryColor,
|
||||
onPrimary: darkBackgroundColor,
|
||||
secondaryContainer: darkSecondaryColor,
|
||||
outline: borderColor,
|
||||
primaryContainer: darkSecondaryColor),
|
||||
dialogBackgroundColor: backgroundColor //for datePicker
|
||||
),
|
||||
AppTheme.Dark: ThemeData(
|
||||
useMaterial3: false,
|
||||
fontFamily: 'Roboto',
|
||||
brightness: Brightness.dark,
|
||||
primaryColor: primaryColor,
|
||||
canvasColor: darkSecondaryColor,
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0.0,
|
||||
backgroundColor: darkBackgroundColor,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarBrightness: Brightness.dark,
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
statusBarColor: darkSecondaryColor.withOpacity(0.8),
|
||||
)),
|
||||
textTheme: const TextTheme().apply(bodyColor: secondaryColor, displayColor: secondaryColor),
|
||||
iconTheme: const IconThemeData(color: secondaryColor),
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: primaryColor,
|
||||
onPrimary: secondaryColor,
|
||||
surface: darkBackgroundColor,
|
||||
brightness: Brightness.dark,
|
||||
secondary: darkSecondaryColor,
|
||||
secondaryContainer: primaryColor,
|
||||
outline: backgroundColor,
|
||||
primaryContainer: secondaryColor //for datePicker
|
||||
),
|
||||
dialogBackgroundColor: darkBackgroundColor),
|
||||
};
|
||||
18
news-app/lib/ui/styles/colors.dart
Normal file
18
news-app/lib/ui/styles/colors.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Color primaryColor = const Color(0xff008DA8);
|
||||
const Color secondaryColor = Color(0xffffffff);
|
||||
const Color backgroundColor = Color(0xffF1F6F9);
|
||||
const Color borderColor = Color(0xff6B6B6B);
|
||||
const Color warningColor = Colors.amberAccent;
|
||||
const Color iconColor = Color(0xffBE151E);
|
||||
|
||||
const Color darkSecondaryColor = Color(0xff1B2D51);
|
||||
const Color darkBackgroundColor = Color(0xff1F345E);
|
||||
const Color darkIconColor = Color(0xffFF787F);
|
||||
|
||||
const Color dividerColor = Color(0x1F000000);
|
||||
|
||||
const Color authorRequestColor = darkSecondaryColor;
|
||||
const Color authorReviewColor = Color(0xff017A80);
|
||||
const Color authorApprovedColor = Color(0xff060F72);
|
||||
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);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user