From ba7deac9f340902ecc5102c39d3c16dcc8c55064 Mon Sep 17 00:00:00 2001 From: ellecio2 Date: Fri, 12 Dec 2025 19:09:42 -0400 Subject: [PATCH] elCaribe app - customization and branding --- .../com/gqlabs/gsmgateway/CallService.java | 4 + news-app/.gitignore | 102 + news-app/analysis_options.yaml | 32 + news-app/android/app/build.gradle | 87 + news-app/android/app/google-services.json | 29 + .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 98 + .../main/kotlin/com/news_app/MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../app/src/main/res/drawable/ic_launcher.png | Bin 0 -> 1880 bytes .../main/res/drawable/launch_background.xml | 12 + .../main/res/drawable/notification_icon.png | Bin 0 -> 1880 bytes .../android/app/src/main/res/logo_caribe.png | Bin 0 -> 1880 bytes .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 1880 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 1880 bytes .../res/mipmap-hdpi/ic_launcher_squircle.png | Bin 0 -> 1880 bytes .../src/main/res/mipmap-ldpi/ic_launcher.png | Bin 0 -> 1880 bytes .../res/mipmap-ldpi/ic_launcher_round.png | Bin 0 -> 1880 bytes .../res/mipmap-ldpi/ic_launcher_squircle.png | Bin 0 -> 1880 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1880 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 1880 bytes .../res/mipmap-mdpi/ic_launcher_squircle.png | Bin 0 -> 1880 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 1880 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 1880 bytes .../res/mipmap-xhdpi/ic_launcher_squircle.png | Bin 0 -> 1880 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1880 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 1880 bytes .../mipmap-xxhdpi/ic_launcher_squircle.png | Bin 0 -> 1880 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1880 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 1880 bytes .../mipmap-xxxhdpi/ic_launcher_squircle.png | Bin 0 -> 1880 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/colors.xml | 4 + .../app/src/main/res/values/strings.xml | 7 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + news-app/android/build.gradle | 33 + news-app/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + news-app/android/settings.gradle | 26 + news-app/assets/animations/noInternet.json | 1 + news-app/assets/font/Roboto-Bold.ttf | Bin 0 -> 167336 bytes news-app/assets/font/Roboto-Light.ttf | Bin 0 -> 167000 bytes news-app/assets/font/Roboto-Medium.ttf | Bin 0 -> 168644 bytes news-app/assets/font/Roboto-Regular.ttf | Bin 0 -> 168260 bytes news-app/assets/images/live_news.png | Bin 0 -> 1289 bytes news-app/assets/images/live_news_dark.png | Bin 0 -> 1289 bytes news-app/assets/images/placeholder.png | Bin 0 -> 18069 bytes .../assets/images/svgImage/apple_logo.svg | 6 + .../assets/images/svgImage/cameraIcon.svg | 4 + .../assets/images/svgImage/caribe_blanco.svg | 8 + .../images/svgImage/deactivatedNews.svg | 10 + .../assets/images/svgImage/deleteAccount.svg | 26 + .../assets/images/svgImage/deleteMyNews.svg | 6 + .../assets/images/svgImage/delete_icon.svg | 7 + .../assets/images/svgImage/editMyNews.svg | 5 + .../assets/images/svgImage/expiredNews.svg | 6 + news-app/assets/images/svgImage/facebook.svg | 4 + .../images/svgImage/facebook_button.svg | 3 + news-app/assets/images/svgImage/filter.svg | 3 + news-app/assets/images/svgImage/flag_icon.svg | 6 + news-app/assets/images/svgImage/forgot.svg | 29 + .../assets/images/svgImage/gallaryIcon.svg | 5 + .../assets/images/svgImage/google_button.svg | 8 + .../assets/images/svgImage/intro_icon.svg | 8 + news-app/assets/images/svgImage/linkedin.svg | 7 + news-app/assets/images/svgImage/live_news.svg | 11 + .../assets/images/svgImage/live_news_dark.svg | 11 + .../assets/images/svgImage/logo_caribe.svg | 8 + news-app/assets/images/svgImage/logout.svg | 26 + .../assets/images/svgImage/maintenance.svg | 80 + news-app/assets/images/svgImage/news.svg | 9 + .../assets/images/svgImage/onboarding1.svg | 262 +++ .../assets/images/svgImage/onboarding2.svg | 948 +++++++++ .../assets/images/svgImage/onboarding3.svg | 895 +++++++++ .../assets/images/svgImage/phone_button.svg | 3 + .../images/svgImage/placeholder_video.svg | 15 + news-app/assets/images/svgImage/rss_feed.svg | 13 + .../images/svgImage/searchbar_arrow.svg | 6 + .../assets/images/svgImage/splash_icon.svg | 10 + news-app/assets/images/svgImage/telegram.svg | 2 + news-app/assets/images/svgImage/whatsapp.svg | 5 + .../assets/images/svgImage/wrteam_logo.svg | 10 + news-app/assets/languages/jsonData.json | 244 +++ news-app/devtools_options.yaml | 3 + news-app/ios/Flutter/AppFrameworkInfo.plist | 26 + news-app/ios/Flutter/Debug.xcconfig | 2 + news-app/ios/Flutter/Release.xcconfig | 3 + news-app/ios/Podfile | 46 + news-app/ios/Runner.xcodeproj/project.pbxproj | 624 ++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 90 + .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + news-app/ios/Runner/AppDelegate.swift | 71 + .../AppIcon.appiconset/Contents.json | 122 ++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 79297 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 2815 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 3526 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 4243 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 3153 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 4137 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 5273 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 3526 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 4989 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 6700 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 6700 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 9837 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 4832 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 8395 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 9905 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + news-app/ios/Runner/Info.plist | 138 ++ news-app/ios/Runner/PrivacyInfo.xcprivacy | 91 + news-app/ios/Runner/Runner-Bridging-Header.h | 2 + news-app/ios/Runner/Runner.entitlements | 18 + news-app/lib/app/app.dart | 258 +++ news-app/lib/app/routes.dart | 203 ++ news-app/lib/cubits/AddNewsCubit.dart | 86 + news-app/lib/cubits/Auth/authCubit.dart | 98 + news-app/lib/cubits/Auth/deleteUserCubit.dart | 37 + .../lib/cubits/Auth/registerTokenCubit.dart | 37 + .../lib/cubits/Auth/socialSignUpCubit.dart | 40 + news-app/lib/cubits/Auth/updateUserCubit.dart | 61 + news-app/lib/cubits/Author/authorCubit.dart | 43 + .../lib/cubits/Author/authorNewsCubit.dart | 49 + .../cubits/Bookmark/UpdateBookmarkCubit.dart | 38 + .../lib/cubits/Bookmark/bookmarkCubit.dart | 111 ++ news-app/lib/cubits/ConnectivityCubit.dart | 79 + .../LikeAndDislikeCubit.dart | 55 + .../updateLikeAndDislikeCubit.dart | 38 + news-app/lib/cubits/NewsByIdCubit.dart | 39 + .../NewsComment/deleteCommentCubit.dart | 37 + .../cubits/NewsComment/flagCommentCubit.dart | 36 + .../NewsComment/likeAndDislikeCommCubit.dart | 41 + .../cubits/NewsComment/setCommentCubit.dart | 39 + .../setUserPreferenceCatCubit.dart | 37 + .../UserPreferences/userByCategoryCubit.dart | 36 + .../lib/cubits/adSpacesNewsDetailsCubit.dart | 41 + news-app/lib/cubits/appLocalizationCubit.dart | 26 + .../lib/cubits/appSystemSettingCubit.dart | 254 +++ news-app/lib/cubits/breakingNewsCubit.dart | 40 + news-app/lib/cubits/categoryCubit.dart | 112 ++ news-app/lib/cubits/commentNewsCubit.dart | 111 ++ news-app/lib/cubits/deleteImageId.dart | 37 + news-app/lib/cubits/deleteUserNewsCubit.dart | 44 + news-app/lib/cubits/featureSectionCubit.dart | 71 + news-app/lib/cubits/generalNewsCubit.dart | 77 + news-app/lib/cubits/getSurveyAnswerCubit.dart | 37 + news-app/lib/cubits/getUserDataByIdCubit.dart | 35 + .../lib/cubits/getUserDraftedNewsCubit.dart | 76 + news-app/lib/cubits/getUserNewsCubit.dart | 87 + news-app/lib/cubits/languageCubit.dart | 47 + news-app/lib/cubits/languageJsonCubit.dart | 57 + news-app/lib/cubits/liveStreamCubit.dart | 39 + news-app/lib/cubits/locationCityCubit.dart | 73 + news-app/lib/cubits/otherPagesCubit.dart | 37 + news-app/lib/cubits/privacyTermsCubit.dart | 38 + news-app/lib/cubits/relatedNewsCubit.dart | 77 + news-app/lib/cubits/rssFeedCubit.dart | 71 + news-app/lib/cubits/sectionByIdCubit.dart | 132 ++ news-app/lib/cubits/setNewsViewsCubit.dart | 37 + news-app/lib/cubits/setSurveyAnswerCubit.dart | 37 + news-app/lib/cubits/settingCubit.dart | 41 + news-app/lib/cubits/slugCheckCubit.dart | 38 + news-app/lib/cubits/slugNewsCubit.dart | 49 + news-app/lib/cubits/subCatNewsCubit.dart | 81 + news-app/lib/cubits/subCategoryCubit.dart | 40 + news-app/lib/cubits/surveyQuestionCubit.dart | 53 + news-app/lib/cubits/tagCubit.dart | 92 + news-app/lib/cubits/tagNewsCubit.dart | 37 + news-app/lib/cubits/themeCubit.dart | 21 + .../cubits/updateBottomsheetContentCubit.dart | 63 + news-app/lib/cubits/videosCubit.dart | 63 + news-app/lib/cubits/weatherCubit.dart | 41 + .../data/models/AppSystemSettingModel.dart | 124 ++ news-app/lib/data/models/AuthModel.dart | 34 + .../lib/data/models/BreakingNewsModel.dart | 19 + news-app/lib/data/models/CategoryModel.dart | 26 + news-app/lib/data/models/CommentModel.dart | 60 + .../lib/data/models/FeatureSectionModel.dart | 97 + .../lib/data/models/LiveStreamingModel.dart | 11 + news-app/lib/data/models/NewsModel.dart | 196 ++ news-app/lib/data/models/OptionModel.dart | 20 + news-app/lib/data/models/OtherPageModel.dart | 15 + news-app/lib/data/models/RSSFeedModel.dart | 23 + news-app/lib/data/models/SettingsModel.dart | 42 + news-app/lib/data/models/TagModel.dart | 11 + news-app/lib/data/models/WeatherData.dart | 24 + news-app/lib/data/models/adSpaceModel.dart | 13 + .../lib/data/models/appLanguageModel.dart | 16 + news-app/lib/data/models/authorModel.dart | 49 + .../lib/data/models/locationCityModel.dart | 14 + .../AddNews/addNewsRemoteDataSource.dart | 78 + .../AddNews/addNewsRepository.dart | 69 + .../AppSystemSetting/systemRepository.dart | 13 + .../Auth/authLocalDataSource.dart | 119 ++ .../Auth/authRemoteDataSource.dart | 299 +++ .../repositories/Auth/authRepository.dart | 171 ++ .../Bookmark/bookmarkRemoteDataSource.dart | 24 + .../Bookmark/bookmarkRepository.dart | 27 + .../breakNewsRemoteDataSource.dart | 14 + .../BreakingNews/breakNewsRepository.dart | 22 + .../Category/categoryRemoteDataSource.dart | 14 + .../Category/categoryRepository.dart | 24 + .../CommentNews/commNewsRemoteDataSource.dart | 14 + .../CommentNews/commNewsRepository.dart | 30 + .../deleteImageIdRemoteDataSource.dart | 19 + .../DeleteImageId/deleteImageRepository.dart | 20 + .../deleteUserNewsRemoteDataSource.dart | 19 + .../deleteUserNewsRepository.dart | 20 + .../deleteUserNotiRemoteDataSource.dart | 14 + .../deleteUserNotiRepository.dart | 18 + .../sectionRemoteDataSource.dart | 19 + .../FeatureSection/sectionRepository.dart | 24 + .../getSurveyAnsRemoteDataSource.dart | 14 + .../getSurveyAnsRepository.dart | 20 + .../GetUserById/getUserByIdDataSource.dart | 12 + .../GetUserById/getUserByIdRepository.dart | 20 + .../getUserNewsRemoteDataSource.dart | 16 + .../GetUserNews/getUserNewsRepository.dart | 24 + .../languageJsonRemoteDataRepo.dart | 15 + .../LanguageJson/languageJsonRepository.dart | 40 + .../LikeAndDisLikeNewsDataSource.dart | 26 + .../LikeAndDisLikeNewsRepository.dart | 26 + .../LiveStream/liveRemoteDataSource.dart | 15 + .../LiveStream/liveStreamRepository.dart | 22 + .../NewsById/NewsByIdRemoteDataSource.dart | 14 + .../NewsById/NewsByIdRepository.dart | 22 + .../DeleteComment/deleteCommDataSource.dart | 14 + .../DeleteComment/deleteCommRepository.dart | 18 + .../FlagComment/flagCommRemoteDataSource.dart | 14 + .../FlagComment/flagCommRepository.dart | 19 + .../likeAndDislikeCommDataSource.dart | 14 + .../likeAndDislikeCommRepository.dart | 35 + .../SetComment/setComRemoteDataSource.dart | 14 + .../SetComment/setComRepository.dart | 19 + .../OtherPages/otherPageRemoteDataSorce.dart | 14 + .../OtherPages/otherPagesRepository.dart | 34 + .../RelatedNews/relatedNewsDataSource.dart | 18 + .../RelatedNews/relatedNewsRepository.dart | 22 + .../sectionByIdRemoteDataSource.dart | 22 + .../SectionById/sectionByIdRepository.dart | 25 + .../setNewsViewsDataRemoteSource.dart | 14 + .../SetNewsViews/setNewsViewsRepository.dart | 19 + .../setSurveyAnsDataRemoteSource.dart | 14 + .../setSurveyAnsRepository.dart | 21 + .../setUserPrefCatRemoteDataSource.dart | 15 + .../setUserPrefCatRepository.dart | 21 + .../Settings/settingRepository.dart | 29 + .../Settings/settingsLocalDataRepository.dart | 75 + .../subCatNewsRemoteDataSource.dart | 19 + .../SubCatNews/subCatRepository.dart | 26 + .../SubCategory/subCatRemoteDataSource.dart | 15 + .../SubCategory/subCatRepository.dart | 29 + .../surveyQueRemoteDataSource.dart | 15 + .../SurveyQuestion/surveyQueRepository.dart | 22 + .../repositories/Tag/tagRemoteDataSource.dart | 14 + .../data/repositories/Tag/tagRepository.dart | 22 + .../TagNews/tagNewsDataSource.dart | 17 + .../TagNews/tagNewsRepository.dart | 22 + .../userByCatRemoteDataSource.dart | 12 + .../UserByCategory/userByCatRepository.dart | 21 + .../Videos/videoRemoteDataSource.dart | 18 + .../repositories/Videos/videosRepository.dart | 23 + .../language/languageRemoteDataSource.dart | 12 + .../language/languageRepository.dart | 22 + news-app/lib/main.dart | 5 + .../lib/ui/screens/AddEditNews/AddNews.dart | 1428 ++++++++++++++ .../screens/AddEditNews/ManageUserNews.dart | 164 ++ .../screens/AddEditNews/NewsDescription.dart | 302 +++ .../AddEditNews/Widgets/UserNewsWidgets.dart | 251 +++ .../Widgets/customBottomsheet.dart | 157 ++ .../AddEditNews/Widgets/geminiService.dart | 149 ++ .../AddEditNews/Widgets/userAllNews.dart | 49 + .../AddEditNews/Widgets/userDrafterNews.dart | 50 + news-app/lib/ui/screens/BookmarkScreen.dart | 115 ++ news-app/lib/ui/screens/CategoryScreen.dart | 127 ++ .../lib/ui/screens/HomePage/HomePage.dart | 407 ++++ .../HomePage/Widgets/CommonSectionTitle.dart | 46 + .../Widgets/GeneralNewsRandomStyle.dart | 165 ++ .../HomePage/Widgets/LiveWithSearchView.dart | 145 ++ .../HomePage/Widgets/SectionShimmer.dart | 29 + .../HomePage/Widgets/SectionStyle1.dart | 321 ++++ .../HomePage/Widgets/SectionStyle2.dart | 170 ++ .../HomePage/Widgets/SectionStyle3.dart | 241 +++ .../HomePage/Widgets/SectionStyle4.dart | 189 ++ .../HomePage/Widgets/SectionStyle5.dart | 400 ++++ .../HomePage/Widgets/SectionStyle6.dart | 216 +++ .../screens/HomePage/Widgets/WeatherData.dart | 140 ++ .../lib/ui/screens/ImagePreviewScreen.dart | 125 ++ news-app/lib/ui/screens/LiveStreaming.dart | 79 + news-app/lib/ui/screens/ManagePreference.dart | 316 +++ .../screens/NewsDetail/NewsDetailScreen.dart | 135 ++ .../NewsDetail/Widgets/CommentView.dart | 318 +++ .../screens/NewsDetail/Widgets/ImageView.dart | 98 + .../googleInterstitialAds.dart | 56 + .../InterstitialAds/unityInterstitialAds.dart | 23 + .../Widgets/NewsSubDetailsScreen.dart | 417 ++++ .../NewsDetail/Widgets/ReplyCommentView.dart | 324 ++++ .../Widgets/RerwardAds/googleRewardAds.dart | 58 + .../Widgets/RerwardAds/unityRewardAds.dart | 23 + .../NewsDetail/Widgets/ShowMoreNewsList.dart | 121 ++ .../screens/NewsDetail/Widgets/backBtn.dart | 24 + .../screens/NewsDetail/Widgets/dateView.dart | 15 + .../NewsDetail/Widgets/delAndReportCom.dart | 131 ++ .../Widgets/delAndReportReplyComm.dart | 130 ++ .../screens/NewsDetail/Widgets/descView.dart | 41 + .../NewsDetail/Widgets/horizontalBtnList.dart | 288 +++ .../screens/NewsDetail/Widgets/likeBtn.dart | 89 + .../NewsDetail/Widgets/relatedNewsList.dart | 166 ++ .../NewsDetail/Widgets/setBannderAds.dart | 27 + .../screens/NewsDetail/Widgets/tagView.dart | 63 + .../screens/NewsDetail/Widgets/titleView.dart | 9 + .../screens/NewsDetail/Widgets/videoBtn.dart | 24 + news-app/lib/ui/screens/NewsDetailsVideo.dart | 75 + news-app/lib/ui/screens/NewsVideo.dart | 185 ++ .../lib/ui/screens/PrivacyPolicyScreen.dart | 55 + .../lib/ui/screens/Profile/ProfileScreen.dart | 555 ++++++ .../Profile/Widgets/customAlertDialog.dart | 81 + .../lib/ui/screens/Profile/userProfile.dart | 401 ++++ .../ui/screens/PushNotificationService.dart | 189 ++ .../lib/ui/screens/RSSFeedDetailsScreen.dart | 124 ++ news-app/lib/ui/screens/RSSFeedScreen.dart | 335 ++++ news-app/lib/ui/screens/Search.dart | 488 +++++ .../SectionMoreBreakNewsList.dart | 104 + .../SectionMoreNews/SectionMoreNewsList.dart | 104 + .../SubCategory/SubCategoryScreen.dart | 138 ++ .../SubCategory/Widgets/SubCatNewsList.dart | 197 ++ .../SubCategory/Widgets/categoryShimmer.dart | 22 + .../Widgets/showSurveyQuestion.dart | 134 ++ .../SubCategory/Widgets/showSurveyResult.dart | 39 + news-app/lib/ui/screens/TagNewsScreen.dart | 281 +++ .../lib/ui/screens/Videos/VideoScreen.dart | 229 +++ .../Videos/Widgets/otherVideosCard.dart | 93 + .../ui/screens/Videos/Widgets/videoCard.dart | 271 +++ .../ui/screens/Videos/videoDetailsScreen.dart | 104 + .../lib/ui/screens/auth/ForgotPassword.dart | 137 ++ .../lib/ui/screens/auth/RequestOtpScreen.dart | 263 +++ .../lib/ui/screens/auth/VerifyOtpScreen.dart | 268 +++ .../ui/screens/auth/Widgets/bottomComBtn.dart | 35 + .../auth/Widgets/fieldFocusChange.dart | 6 + .../screens/auth/Widgets/setConfimPass.dart | 77 + .../ui/screens/auth/Widgets/setDivider.dart | 25 + .../lib/ui/screens/auth/Widgets/setEmail.dart | 47 + .../screens/auth/Widgets/setForgotPass.dart | 16 + .../auth/Widgets/setLoginAndSignUpBtn.dart | 31 + .../lib/ui/screens/auth/Widgets/setName.dart | 49 + .../ui/screens/auth/Widgets/setPassword.dart | 81 + .../screens/auth/Widgets/setTermPolicy.dart | 40 + .../auth/Widgets/svgPictureWidget.dart | 18 + news-app/lib/ui/screens/auth/loginScreen.dart | 455 +++++ .../lib/ui/screens/authorDetailsScreen.dart | 275 +++ .../ui/screens/dashBoard/dashBoardScreen.dart | 213 +++ .../ui/screens/filter/FilterBottomSheet.dart | 413 ++++ .../filter/widgets/custom_date_selector.dart | 169 ++ .../widgets/duration_filter_widget.dart | 128 ++ news-app/lib/ui/screens/introSlider.dart | 196 ++ news-app/lib/ui/screens/languageList.dart | 255 +++ .../lib/ui/screens/maintenanceScreen.dart | 52 + news-app/lib/ui/screens/splashScreen.dart | 134 ++ news-app/lib/ui/styles/appTheme.dart | 59 + news-app/lib/ui/styles/colors.dart | 18 + news-app/lib/ui/widgets/NewsItem.dart | 256 +++ news-app/lib/ui/widgets/SnackBarWidget.dart | 15 + news-app/lib/ui/widgets/adSpaces.dart | 43 + news-app/lib/ui/widgets/breakingNewsItem.dart | 116 ++ .../lib/ui/widgets/breakingVideoItem.dart | 58 + news-app/lib/ui/widgets/customAppBar.dart | 45 + news-app/lib/ui/widgets/customBackBtn.dart | 20 + news-app/lib/ui/widgets/customTextBtn.dart | 24 + news-app/lib/ui/widgets/customTextLabel.dart | 23 + .../lib/ui/widgets/errorContainerWidget.dart | 51 + news-app/lib/ui/widgets/loadingScreen.dart | 76 + news-app/lib/ui/widgets/networkImage.dart | 31 + news-app/lib/ui/widgets/newsCard.dart | 179 ++ news-app/lib/ui/widgets/shimmerNewsList.dart | 72 + news-app/lib/ui/widgets/videoItem.dart | 215 +++ .../lib/ui/widgets/videoPlayContainer.dart | 86 + news-app/lib/ui/widgets/videoShimmer.dart | 18 + news-app/lib/utils/ErrorMessageKeys.dart | 7 + news-app/lib/utils/api.dart | 165 ++ news-app/lib/utils/appLanguages.dart | 244 +++ news-app/lib/utils/constant.dart | 38 + news-app/lib/utils/hiveBoxKeys.dart | 39 + news-app/lib/utils/internetConnectivity.dart | 13 + news-app/lib/utils/labelKeys.dart | 2 + news-app/lib/utils/strings.dart | 165 ++ news-app/lib/utils/uiUtils.dart | 507 +++++ news-app/lib/utils/validators.dart | 119 ++ news-app/pubspec.lock | 1698 +++++++++++++++++ news-app/pubspec.yaml | 93 + news-app/test/widget_test.dart | 30 + 402 files changed, 31833 insertions(+) create mode 100644 GSMGateway/app/src/main/java/com/gqlabs/gsmgateway/CallService.java create mode 100644 news-app/.gitignore create mode 100644 news-app/analysis_options.yaml create mode 100644 news-app/android/app/build.gradle create mode 100644 news-app/android/app/google-services.json create mode 100644 news-app/android/app/src/debug/AndroidManifest.xml create mode 100644 news-app/android/app/src/main/AndroidManifest.xml create mode 100644 news-app/android/app/src/main/kotlin/com/news_app/MainActivity.kt create mode 100644 news-app/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 news-app/android/app/src/main/res/drawable/ic_launcher.png create mode 100644 news-app/android/app/src/main/res/drawable/launch_background.xml create mode 100644 news-app/android/app/src/main/res/drawable/notification_icon.png create mode 100644 news-app/android/app/src/main/res/logo_caribe.png create mode 100644 news-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 news-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 news-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_squircle.png create mode 100644 news-app/android/app/src/main/res/mipmap-ldpi/ic_launcher.png create mode 100644 news-app/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png create mode 100644 news-app/android/app/src/main/res/mipmap-ldpi/ic_launcher_squircle.png create mode 100644 news-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 news-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 news-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_squircle.png create mode 100644 news-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 news-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 news-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_squircle.png create mode 100644 news-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 news-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 news-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_squircle.png create mode 100644 news-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 news-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 news-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_squircle.png create mode 100644 news-app/android/app/src/main/res/values-night/styles.xml create mode 100644 news-app/android/app/src/main/res/values/colors.xml create mode 100644 news-app/android/app/src/main/res/values/strings.xml create mode 100644 news-app/android/app/src/main/res/values/styles.xml create mode 100644 news-app/android/app/src/profile/AndroidManifest.xml create mode 100644 news-app/android/build.gradle create mode 100644 news-app/android/gradle.properties create mode 100644 news-app/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 news-app/android/settings.gradle create mode 100644 news-app/assets/animations/noInternet.json create mode 100644 news-app/assets/font/Roboto-Bold.ttf create mode 100644 news-app/assets/font/Roboto-Light.ttf create mode 100644 news-app/assets/font/Roboto-Medium.ttf create mode 100644 news-app/assets/font/Roboto-Regular.ttf create mode 100644 news-app/assets/images/live_news.png create mode 100644 news-app/assets/images/live_news_dark.png create mode 100644 news-app/assets/images/placeholder.png create mode 100644 news-app/assets/images/svgImage/apple_logo.svg create mode 100644 news-app/assets/images/svgImage/cameraIcon.svg create mode 100644 news-app/assets/images/svgImage/caribe_blanco.svg create mode 100644 news-app/assets/images/svgImage/deactivatedNews.svg create mode 100644 news-app/assets/images/svgImage/deleteAccount.svg create mode 100644 news-app/assets/images/svgImage/deleteMyNews.svg create mode 100644 news-app/assets/images/svgImage/delete_icon.svg create mode 100644 news-app/assets/images/svgImage/editMyNews.svg create mode 100644 news-app/assets/images/svgImage/expiredNews.svg create mode 100644 news-app/assets/images/svgImage/facebook.svg create mode 100644 news-app/assets/images/svgImage/facebook_button.svg create mode 100644 news-app/assets/images/svgImage/filter.svg create mode 100644 news-app/assets/images/svgImage/flag_icon.svg create mode 100644 news-app/assets/images/svgImage/forgot.svg create mode 100644 news-app/assets/images/svgImage/gallaryIcon.svg create mode 100644 news-app/assets/images/svgImage/google_button.svg create mode 100644 news-app/assets/images/svgImage/intro_icon.svg create mode 100644 news-app/assets/images/svgImage/linkedin.svg create mode 100644 news-app/assets/images/svgImage/live_news.svg create mode 100644 news-app/assets/images/svgImage/live_news_dark.svg create mode 100644 news-app/assets/images/svgImage/logo_caribe.svg create mode 100644 news-app/assets/images/svgImage/logout.svg create mode 100644 news-app/assets/images/svgImage/maintenance.svg create mode 100644 news-app/assets/images/svgImage/news.svg create mode 100644 news-app/assets/images/svgImage/onboarding1.svg create mode 100644 news-app/assets/images/svgImage/onboarding2.svg create mode 100644 news-app/assets/images/svgImage/onboarding3.svg create mode 100644 news-app/assets/images/svgImage/phone_button.svg create mode 100644 news-app/assets/images/svgImage/placeholder_video.svg create mode 100644 news-app/assets/images/svgImage/rss_feed.svg create mode 100644 news-app/assets/images/svgImage/searchbar_arrow.svg create mode 100644 news-app/assets/images/svgImage/splash_icon.svg create mode 100644 news-app/assets/images/svgImage/telegram.svg create mode 100644 news-app/assets/images/svgImage/whatsapp.svg create mode 100644 news-app/assets/images/svgImage/wrteam_logo.svg create mode 100644 news-app/assets/languages/jsonData.json create mode 100644 news-app/devtools_options.yaml create mode 100644 news-app/ios/Flutter/AppFrameworkInfo.plist create mode 100644 news-app/ios/Flutter/Debug.xcconfig create mode 100644 news-app/ios/Flutter/Release.xcconfig create mode 100644 news-app/ios/Podfile create mode 100644 news-app/ios/Runner.xcodeproj/project.pbxproj create mode 100644 news-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 news-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 news-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 news-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 news-app/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 news-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 news-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 news-app/ios/Runner/AppDelegate.swift create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 news-app/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 news-app/ios/Runner/Base.lproj/Main.storyboard create mode 100644 news-app/ios/Runner/Info.plist create mode 100644 news-app/ios/Runner/PrivacyInfo.xcprivacy create mode 100644 news-app/ios/Runner/Runner-Bridging-Header.h create mode 100644 news-app/ios/Runner/Runner.entitlements create mode 100644 news-app/lib/app/app.dart create mode 100644 news-app/lib/app/routes.dart create mode 100644 news-app/lib/cubits/AddNewsCubit.dart create mode 100644 news-app/lib/cubits/Auth/authCubit.dart create mode 100644 news-app/lib/cubits/Auth/deleteUserCubit.dart create mode 100644 news-app/lib/cubits/Auth/registerTokenCubit.dart create mode 100644 news-app/lib/cubits/Auth/socialSignUpCubit.dart create mode 100644 news-app/lib/cubits/Auth/updateUserCubit.dart create mode 100644 news-app/lib/cubits/Author/authorCubit.dart create mode 100644 news-app/lib/cubits/Author/authorNewsCubit.dart create mode 100644 news-app/lib/cubits/Bookmark/UpdateBookmarkCubit.dart create mode 100644 news-app/lib/cubits/Bookmark/bookmarkCubit.dart create mode 100644 news-app/lib/cubits/ConnectivityCubit.dart create mode 100644 news-app/lib/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart create mode 100644 news-app/lib/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart create mode 100644 news-app/lib/cubits/NewsByIdCubit.dart create mode 100644 news-app/lib/cubits/NewsComment/deleteCommentCubit.dart create mode 100644 news-app/lib/cubits/NewsComment/flagCommentCubit.dart create mode 100644 news-app/lib/cubits/NewsComment/likeAndDislikeCommCubit.dart create mode 100644 news-app/lib/cubits/NewsComment/setCommentCubit.dart create mode 100644 news-app/lib/cubits/UserPreferences/setUserPreferenceCatCubit.dart create mode 100644 news-app/lib/cubits/UserPreferences/userByCategoryCubit.dart create mode 100644 news-app/lib/cubits/adSpacesNewsDetailsCubit.dart create mode 100644 news-app/lib/cubits/appLocalizationCubit.dart create mode 100644 news-app/lib/cubits/appSystemSettingCubit.dart create mode 100644 news-app/lib/cubits/breakingNewsCubit.dart create mode 100644 news-app/lib/cubits/categoryCubit.dart create mode 100644 news-app/lib/cubits/commentNewsCubit.dart create mode 100644 news-app/lib/cubits/deleteImageId.dart create mode 100644 news-app/lib/cubits/deleteUserNewsCubit.dart create mode 100644 news-app/lib/cubits/featureSectionCubit.dart create mode 100644 news-app/lib/cubits/generalNewsCubit.dart create mode 100644 news-app/lib/cubits/getSurveyAnswerCubit.dart create mode 100644 news-app/lib/cubits/getUserDataByIdCubit.dart create mode 100644 news-app/lib/cubits/getUserDraftedNewsCubit.dart create mode 100644 news-app/lib/cubits/getUserNewsCubit.dart create mode 100644 news-app/lib/cubits/languageCubit.dart create mode 100644 news-app/lib/cubits/languageJsonCubit.dart create mode 100644 news-app/lib/cubits/liveStreamCubit.dart create mode 100644 news-app/lib/cubits/locationCityCubit.dart create mode 100644 news-app/lib/cubits/otherPagesCubit.dart create mode 100644 news-app/lib/cubits/privacyTermsCubit.dart create mode 100644 news-app/lib/cubits/relatedNewsCubit.dart create mode 100644 news-app/lib/cubits/rssFeedCubit.dart create mode 100644 news-app/lib/cubits/sectionByIdCubit.dart create mode 100644 news-app/lib/cubits/setNewsViewsCubit.dart create mode 100644 news-app/lib/cubits/setSurveyAnswerCubit.dart create mode 100644 news-app/lib/cubits/settingCubit.dart create mode 100644 news-app/lib/cubits/slugCheckCubit.dart create mode 100644 news-app/lib/cubits/slugNewsCubit.dart create mode 100644 news-app/lib/cubits/subCatNewsCubit.dart create mode 100644 news-app/lib/cubits/subCategoryCubit.dart create mode 100644 news-app/lib/cubits/surveyQuestionCubit.dart create mode 100644 news-app/lib/cubits/tagCubit.dart create mode 100644 news-app/lib/cubits/tagNewsCubit.dart create mode 100644 news-app/lib/cubits/themeCubit.dart create mode 100644 news-app/lib/cubits/updateBottomsheetContentCubit.dart create mode 100644 news-app/lib/cubits/videosCubit.dart create mode 100644 news-app/lib/cubits/weatherCubit.dart create mode 100644 news-app/lib/data/models/AppSystemSettingModel.dart create mode 100644 news-app/lib/data/models/AuthModel.dart create mode 100644 news-app/lib/data/models/BreakingNewsModel.dart create mode 100644 news-app/lib/data/models/CategoryModel.dart create mode 100644 news-app/lib/data/models/CommentModel.dart create mode 100644 news-app/lib/data/models/FeatureSectionModel.dart create mode 100644 news-app/lib/data/models/LiveStreamingModel.dart create mode 100644 news-app/lib/data/models/NewsModel.dart create mode 100644 news-app/lib/data/models/OptionModel.dart create mode 100644 news-app/lib/data/models/OtherPageModel.dart create mode 100644 news-app/lib/data/models/RSSFeedModel.dart create mode 100644 news-app/lib/data/models/SettingsModel.dart create mode 100644 news-app/lib/data/models/TagModel.dart create mode 100644 news-app/lib/data/models/WeatherData.dart create mode 100644 news-app/lib/data/models/adSpaceModel.dart create mode 100644 news-app/lib/data/models/appLanguageModel.dart create mode 100644 news-app/lib/data/models/authorModel.dart create mode 100644 news-app/lib/data/models/locationCityModel.dart create mode 100644 news-app/lib/data/repositories/AddNews/addNewsRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/AddNews/addNewsRepository.dart create mode 100644 news-app/lib/data/repositories/AppSystemSetting/systemRepository.dart create mode 100644 news-app/lib/data/repositories/Auth/authLocalDataSource.dart create mode 100644 news-app/lib/data/repositories/Auth/authRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/Auth/authRepository.dart create mode 100644 news-app/lib/data/repositories/Bookmark/bookmarkRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/Bookmark/bookmarkRepository.dart create mode 100644 news-app/lib/data/repositories/BreakingNews/breakNewsRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/BreakingNews/breakNewsRepository.dart create mode 100644 news-app/lib/data/repositories/Category/categoryRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/Category/categoryRepository.dart create mode 100644 news-app/lib/data/repositories/CommentNews/commNewsRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/CommentNews/commNewsRepository.dart create mode 100644 news-app/lib/data/repositories/DeleteImageId/deleteImageIdRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/DeleteImageId/deleteImageRepository.dart create mode 100644 news-app/lib/data/repositories/DeleteUserNews/deleteUserNewsRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/DeleteUserNews/deleteUserNewsRepository.dart create mode 100644 news-app/lib/data/repositories/DeleteUserNotification/deleteUserNotiRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/DeleteUserNotification/deleteUserNotiRepository.dart create mode 100644 news-app/lib/data/repositories/FeatureSection/sectionRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/FeatureSection/sectionRepository.dart create mode 100644 news-app/lib/data/repositories/GetSurveyAnswer/getSurveyAnsRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/GetSurveyAnswer/getSurveyAnsRepository.dart create mode 100644 news-app/lib/data/repositories/GetUserById/getUserByIdDataSource.dart create mode 100644 news-app/lib/data/repositories/GetUserById/getUserByIdRepository.dart create mode 100644 news-app/lib/data/repositories/GetUserNews/getUserNewsRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/GetUserNews/getUserNewsRepository.dart create mode 100644 news-app/lib/data/repositories/LanguageJson/languageJsonRemoteDataRepo.dart create mode 100644 news-app/lib/data/repositories/LanguageJson/languageJsonRepository.dart create mode 100644 news-app/lib/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsDataSource.dart create mode 100644 news-app/lib/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart create mode 100644 news-app/lib/data/repositories/LiveStream/liveRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/LiveStream/liveStreamRepository.dart create mode 100644 news-app/lib/data/repositories/NewsById/NewsByIdRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/NewsById/NewsByIdRepository.dart create mode 100644 news-app/lib/data/repositories/NewsComment/DeleteComment/deleteCommDataSource.dart create mode 100644 news-app/lib/data/repositories/NewsComment/DeleteComment/deleteCommRepository.dart create mode 100644 news-app/lib/data/repositories/NewsComment/FlagComment/flagCommRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/NewsComment/FlagComment/flagCommRepository.dart create mode 100644 news-app/lib/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommDataSource.dart create mode 100644 news-app/lib/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart create mode 100644 news-app/lib/data/repositories/NewsComment/SetComment/setComRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/NewsComment/SetComment/setComRepository.dart create mode 100644 news-app/lib/data/repositories/OtherPages/otherPageRemoteDataSorce.dart create mode 100644 news-app/lib/data/repositories/OtherPages/otherPagesRepository.dart create mode 100644 news-app/lib/data/repositories/RelatedNews/relatedNewsDataSource.dart create mode 100644 news-app/lib/data/repositories/RelatedNews/relatedNewsRepository.dart create mode 100644 news-app/lib/data/repositories/SectionById/sectionByIdRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/SectionById/sectionByIdRepository.dart create mode 100644 news-app/lib/data/repositories/SetNewsViews/setNewsViewsDataRemoteSource.dart create mode 100644 news-app/lib/data/repositories/SetNewsViews/setNewsViewsRepository.dart create mode 100644 news-app/lib/data/repositories/SetSurveyAnswer/setSurveyAnsDataRemoteSource.dart create mode 100644 news-app/lib/data/repositories/SetSurveyAnswer/setSurveyAnsRepository.dart create mode 100644 news-app/lib/data/repositories/SetUserPreferenceCat/setUserPrefCatRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/SetUserPreferenceCat/setUserPrefCatRepository.dart create mode 100644 news-app/lib/data/repositories/Settings/settingRepository.dart create mode 100644 news-app/lib/data/repositories/Settings/settingsLocalDataRepository.dart create mode 100644 news-app/lib/data/repositories/SubCatNews/subCatNewsRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/SubCatNews/subCatRepository.dart create mode 100644 news-app/lib/data/repositories/SubCategory/subCatRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/SubCategory/subCatRepository.dart create mode 100644 news-app/lib/data/repositories/SurveyQuestion/surveyQueRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/SurveyQuestion/surveyQueRepository.dart create mode 100644 news-app/lib/data/repositories/Tag/tagRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/Tag/tagRepository.dart create mode 100644 news-app/lib/data/repositories/TagNews/tagNewsDataSource.dart create mode 100644 news-app/lib/data/repositories/TagNews/tagNewsRepository.dart create mode 100644 news-app/lib/data/repositories/UserByCategory/userByCatRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/UserByCategory/userByCatRepository.dart create mode 100644 news-app/lib/data/repositories/Videos/videoRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/Videos/videosRepository.dart create mode 100644 news-app/lib/data/repositories/language/languageRemoteDataSource.dart create mode 100644 news-app/lib/data/repositories/language/languageRepository.dart create mode 100644 news-app/lib/main.dart create mode 100644 news-app/lib/ui/screens/AddEditNews/AddNews.dart create mode 100644 news-app/lib/ui/screens/AddEditNews/ManageUserNews.dart create mode 100644 news-app/lib/ui/screens/AddEditNews/NewsDescription.dart create mode 100644 news-app/lib/ui/screens/AddEditNews/Widgets/UserNewsWidgets.dart create mode 100644 news-app/lib/ui/screens/AddEditNews/Widgets/customBottomsheet.dart create mode 100644 news-app/lib/ui/screens/AddEditNews/Widgets/geminiService.dart create mode 100644 news-app/lib/ui/screens/AddEditNews/Widgets/userAllNews.dart create mode 100644 news-app/lib/ui/screens/AddEditNews/Widgets/userDrafterNews.dart create mode 100644 news-app/lib/ui/screens/BookmarkScreen.dart create mode 100644 news-app/lib/ui/screens/CategoryScreen.dart create mode 100644 news-app/lib/ui/screens/HomePage/HomePage.dart create mode 100644 news-app/lib/ui/screens/HomePage/Widgets/CommonSectionTitle.dart create mode 100644 news-app/lib/ui/screens/HomePage/Widgets/GeneralNewsRandomStyle.dart create mode 100644 news-app/lib/ui/screens/HomePage/Widgets/LiveWithSearchView.dart create mode 100644 news-app/lib/ui/screens/HomePage/Widgets/SectionShimmer.dart create mode 100644 news-app/lib/ui/screens/HomePage/Widgets/SectionStyle1.dart create mode 100644 news-app/lib/ui/screens/HomePage/Widgets/SectionStyle2.dart create mode 100644 news-app/lib/ui/screens/HomePage/Widgets/SectionStyle3.dart create mode 100644 news-app/lib/ui/screens/HomePage/Widgets/SectionStyle4.dart create mode 100644 news-app/lib/ui/screens/HomePage/Widgets/SectionStyle5.dart create mode 100644 news-app/lib/ui/screens/HomePage/Widgets/SectionStyle6.dart create mode 100644 news-app/lib/ui/screens/HomePage/Widgets/WeatherData.dart create mode 100644 news-app/lib/ui/screens/ImagePreviewScreen.dart create mode 100644 news-app/lib/ui/screens/LiveStreaming.dart create mode 100644 news-app/lib/ui/screens/ManagePreference.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/NewsDetailScreen.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/CommentView.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/ImageView.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/InterstitialAds/googleInterstitialAds.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/InterstitialAds/unityInterstitialAds.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/NewsSubDetailsScreen.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/ReplyCommentView.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/RerwardAds/googleRewardAds.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/RerwardAds/unityRewardAds.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/ShowMoreNewsList.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/backBtn.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/dateView.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportCom.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportReplyComm.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/descView.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/horizontalBtnList.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/likeBtn.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/relatedNewsList.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/setBannderAds.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/tagView.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/titleView.dart create mode 100644 news-app/lib/ui/screens/NewsDetail/Widgets/videoBtn.dart create mode 100644 news-app/lib/ui/screens/NewsDetailsVideo.dart create mode 100644 news-app/lib/ui/screens/NewsVideo.dart create mode 100644 news-app/lib/ui/screens/PrivacyPolicyScreen.dart create mode 100644 news-app/lib/ui/screens/Profile/ProfileScreen.dart create mode 100644 news-app/lib/ui/screens/Profile/Widgets/customAlertDialog.dart create mode 100644 news-app/lib/ui/screens/Profile/userProfile.dart create mode 100644 news-app/lib/ui/screens/PushNotificationService.dart create mode 100644 news-app/lib/ui/screens/RSSFeedDetailsScreen.dart create mode 100644 news-app/lib/ui/screens/RSSFeedScreen.dart create mode 100644 news-app/lib/ui/screens/Search.dart create mode 100644 news-app/lib/ui/screens/SectionMoreNews/SectionMoreBreakNewsList.dart create mode 100644 news-app/lib/ui/screens/SectionMoreNews/SectionMoreNewsList.dart create mode 100644 news-app/lib/ui/screens/SubCategory/SubCategoryScreen.dart create mode 100644 news-app/lib/ui/screens/SubCategory/Widgets/SubCatNewsList.dart create mode 100644 news-app/lib/ui/screens/SubCategory/Widgets/categoryShimmer.dart create mode 100644 news-app/lib/ui/screens/SubCategory/Widgets/showSurveyQuestion.dart create mode 100644 news-app/lib/ui/screens/SubCategory/Widgets/showSurveyResult.dart create mode 100644 news-app/lib/ui/screens/TagNewsScreen.dart create mode 100644 news-app/lib/ui/screens/Videos/VideoScreen.dart create mode 100644 news-app/lib/ui/screens/Videos/Widgets/otherVideosCard.dart create mode 100644 news-app/lib/ui/screens/Videos/Widgets/videoCard.dart create mode 100644 news-app/lib/ui/screens/Videos/videoDetailsScreen.dart create mode 100644 news-app/lib/ui/screens/auth/ForgotPassword.dart create mode 100644 news-app/lib/ui/screens/auth/RequestOtpScreen.dart create mode 100644 news-app/lib/ui/screens/auth/VerifyOtpScreen.dart create mode 100644 news-app/lib/ui/screens/auth/Widgets/bottomComBtn.dart create mode 100644 news-app/lib/ui/screens/auth/Widgets/fieldFocusChange.dart create mode 100644 news-app/lib/ui/screens/auth/Widgets/setConfimPass.dart create mode 100644 news-app/lib/ui/screens/auth/Widgets/setDivider.dart create mode 100644 news-app/lib/ui/screens/auth/Widgets/setEmail.dart create mode 100644 news-app/lib/ui/screens/auth/Widgets/setForgotPass.dart create mode 100644 news-app/lib/ui/screens/auth/Widgets/setLoginAndSignUpBtn.dart create mode 100644 news-app/lib/ui/screens/auth/Widgets/setName.dart create mode 100644 news-app/lib/ui/screens/auth/Widgets/setPassword.dart create mode 100644 news-app/lib/ui/screens/auth/Widgets/setTermPolicy.dart create mode 100644 news-app/lib/ui/screens/auth/Widgets/svgPictureWidget.dart create mode 100644 news-app/lib/ui/screens/auth/loginScreen.dart create mode 100644 news-app/lib/ui/screens/authorDetailsScreen.dart create mode 100644 news-app/lib/ui/screens/dashBoard/dashBoardScreen.dart create mode 100644 news-app/lib/ui/screens/filter/FilterBottomSheet.dart create mode 100644 news-app/lib/ui/screens/filter/widgets/custom_date_selector.dart create mode 100644 news-app/lib/ui/screens/filter/widgets/duration_filter_widget.dart create mode 100644 news-app/lib/ui/screens/introSlider.dart create mode 100644 news-app/lib/ui/screens/languageList.dart create mode 100644 news-app/lib/ui/screens/maintenanceScreen.dart create mode 100644 news-app/lib/ui/screens/splashScreen.dart create mode 100644 news-app/lib/ui/styles/appTheme.dart create mode 100644 news-app/lib/ui/styles/colors.dart create mode 100644 news-app/lib/ui/widgets/NewsItem.dart create mode 100644 news-app/lib/ui/widgets/SnackBarWidget.dart create mode 100644 news-app/lib/ui/widgets/adSpaces.dart create mode 100644 news-app/lib/ui/widgets/breakingNewsItem.dart create mode 100644 news-app/lib/ui/widgets/breakingVideoItem.dart create mode 100644 news-app/lib/ui/widgets/customAppBar.dart create mode 100644 news-app/lib/ui/widgets/customBackBtn.dart create mode 100644 news-app/lib/ui/widgets/customTextBtn.dart create mode 100644 news-app/lib/ui/widgets/customTextLabel.dart create mode 100644 news-app/lib/ui/widgets/errorContainerWidget.dart create mode 100644 news-app/lib/ui/widgets/loadingScreen.dart create mode 100644 news-app/lib/ui/widgets/networkImage.dart create mode 100644 news-app/lib/ui/widgets/newsCard.dart create mode 100644 news-app/lib/ui/widgets/shimmerNewsList.dart create mode 100644 news-app/lib/ui/widgets/videoItem.dart create mode 100644 news-app/lib/ui/widgets/videoPlayContainer.dart create mode 100644 news-app/lib/ui/widgets/videoShimmer.dart create mode 100644 news-app/lib/utils/ErrorMessageKeys.dart create mode 100644 news-app/lib/utils/api.dart create mode 100644 news-app/lib/utils/appLanguages.dart create mode 100644 news-app/lib/utils/constant.dart create mode 100644 news-app/lib/utils/hiveBoxKeys.dart create mode 100644 news-app/lib/utils/internetConnectivity.dart create mode 100644 news-app/lib/utils/labelKeys.dart create mode 100644 news-app/lib/utils/strings.dart create mode 100644 news-app/lib/utils/uiUtils.dart create mode 100644 news-app/lib/utils/validators.dart create mode 100644 news-app/pubspec.lock create mode 100644 news-app/pubspec.yaml create mode 100644 news-app/test/widget_test.dart diff --git a/GSMGateway/app/src/main/java/com/gqlabs/gsmgateway/CallService.java b/GSMGateway/app/src/main/java/com/gqlabs/gsmgateway/CallService.java new file mode 100644 index 00000000..07359405 --- /dev/null +++ b/GSMGateway/app/src/main/java/com/gqlabs/gsmgateway/CallService.java @@ -0,0 +1,4 @@ +package com.gqlabs.gsmgateway; + +public class CallService { +} diff --git a/news-app/.gitignore b/news-app/.gitignore new file mode 100644 index 00000000..0f5815ed --- /dev/null +++ b/news-app/.gitignore @@ -0,0 +1,102 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# VS Code related +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/ephemeral +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +*.keystore +!**/android/app/debug.keystore + +# Generated files +*.g.dart +*.freezed.dart + +# Local env files +.env +.env.* + +# Coverage +coverage/ diff --git a/news-app/analysis_options.yaml b/news-app/analysis_options.yaml new file mode 100644 index 00000000..109446c8 --- /dev/null +++ b/news-app/analysis_options.yaml @@ -0,0 +1,32 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +analyzer: + errors: + deprecated_member_use: ignore + must_be_immutable: ignore + override_on_non_overriding_member: ignore + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/news-app/android/app/build.gradle b/news-app/android/app/build.gradle new file mode 100644 index 00000000..e5df0e2c --- /dev/null +++ b/news-app/android/app/build.gradle @@ -0,0 +1,87 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "com.google.gms.google-services" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + + +def keystoreProperties = new Properties() + def keystorePropertiesFile = rootProject.file('key.properties') + if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) + } + +android { + testOptions.unitTests.includeAndroidResources = true + namespace 'com.news_app' + compileSdk flutter.compileSdkVersion + ndkVersion "27.0.12077973" + + compileOptions { + coreLibraryDesugaringEnabled true + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.news_app" + minSdk 27 + targetSdk flutter.targetSdkVersion + multiDexEnabled true + versionCode flutter.versionCode + versionName flutter.versionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + //Don't Change Signing Configs below. + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + minifyEnabled false // Change from true to false + shrinkResources false // Change from true to false + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug //Change it to signingConfigs.release for release apk + } + } +} + + + +flutter { + source '../..' +} + +dependencies { + implementation 'com.android.support:multidex:2.0.1' + implementation platform('com.google.firebase:firebase-bom:32.3.1') + implementation 'com.google.firebase:firebase-auth' + implementation 'com.google.android.gms:play-services-ads:23.6.0' + implementation 'com.google.android.gms:play-services-measurement-api:21.6.1' + implementation 'com.google.android.gms:play-services-auth:21.3.0' + implementation 'com.google.firebase:firebase-analytics' + implementation 'com.google.guava:guava:30.1.1-android' + implementation 'com.facebook.android:facebook-android-sdk:[8,9)' + implementation 'com.google.android.ump:user-messaging-platform:2.1.0' + implementation 'com.google.android.recaptcha:recaptcha:18.4.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' + } + diff --git a/news-app/android/app/google-services.json b/news-app/android/app/google-services.json new file mode 100644 index 00000000..313f2d09 --- /dev/null +++ b/news-app/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "106496138088", + "project_id": "news-demo-34c11", + "storage_bucket": "news-demo-34c11.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:106496138088:android:ed64e0205fbece3c5514dd", + "android_client_info": { + "package_name": "com.news_app" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyD9qu92sCBHK2c85JPJSFRfviXDvDF9fD4" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/news-app/android/app/src/debug/AndroidManifest.xml b/news-app/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/news-app/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/news-app/android/app/src/main/AndroidManifest.xml b/news-app/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8edf8856 --- /dev/null +++ b/news-app/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/news-app/android/app/src/main/kotlin/com/news_app/MainActivity.kt b/news-app/android/app/src/main/kotlin/com/news_app/MainActivity.kt new file mode 100644 index 00000000..f214b8ba --- /dev/null +++ b/news-app/android/app/src/main/kotlin/com/news_app/MainActivity.kt @@ -0,0 +1,5 @@ +package com.news_app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() {} diff --git a/news-app/android/app/src/main/res/drawable-v21/launch_background.xml b/news-app/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..a864ec0f --- /dev/null +++ b/news-app/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/news-app/android/app/src/main/res/drawable/ic_launcher.png b/news-app/android/app/src/main/res/drawable/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..7f0f9843bd11ddd4cbe86ba2eac49a793967b016 GIT binary patch literal 1880 zcmV-e2dDUnP){mo=X^x{!z>tQ%rx1|!v1kD$+Be$BrXDl7FtR{K%w;ZUE0zD3KaxFnL{!J zbw-JZFVq=F6oviTdr>I8y{8YA^d#SZnsa~W_vv~5e&=AAv&u1{8;A*72TB8#fLcMl zph3_GsX_9$R&A9ZvkplFUU4mzA31kpuV>VWUfAw4^9N~ z0A+xN-XYYehDcvL9EA!5i7@+Cx;Ld^(x*U2dP+12K&T|Lujhy^pG-Yvspm3mh1!Wt zjyXlAAq;szA(nK9S3gFMcqBAW)*Dl(y2QwlXruY6PG-LBo-Ye`He4S@yA=?dufbO1 z9+{QT{d4ecaJ+ayvHFYsnx^^sD>r>DHF3804&zh2E-99;_&Ss$)%s?OZo4@SXTYp( z;>0eGy!lSatQ?-kk?Iy5*K9nbKWFnQmDSiWWkW@6L?l;!*f$&h*8#8>Cf_NbjAuXO zV^SbnVn{62eb#yRp;J!e$tP-}Vd$#dGVPw*{g}nGC2{DaH2}kAI51bzuvK~{!Wlqg z%c_$#41kk3&~qu~P+q8WD~K&seR#4q1l8H>ww{2GD^9IREYM|)owUEwo%fF5DSz-|;SbF11hb_*2?EtF zs&5yURj=^Ks(fMx*fSFg31y6Yak`IuSoq@>c^(Py`uQ4445yJ8+-^`a=D@J z>MTp&A8EYk9+D?LZD+6HCMJvs$$RNO07rjAfijlZBK7y4S)xHITT|JR#${#t+16~t z2vk40XI0vB^651(VMM;cssXwGK> zV+0MGm@p!lVzu%Nu9Ah?ThWD8zgV+1uUIQUv2&=QeMvyB*xnORnh~souHvFn{dQEa z(0qBlHCu54?YAg)c59ngK=0a{ekd`5wOXs>lZ`(=)^^Pw#hIo%Pdo#1C6sf^s2F|~ z2gO!=k>&u*)8}@AwPF~}!ZDpA>p*eDSW+80W9S<7V~HPamo$f4v$Id#<_TMZ&Q2&| z0l~&6GbboKU!g<=+bB7+9HrrzTxDTq%Qea%aSp#~w|i!#BVp4ZMpr4CuP#6Z+kn?^ zMQPZ0cn;&pB#TS+%Xg_-!mF+iFn(_A3R!+azc8vmnGQqH@jNA6g@h6v&jzq9wlr=8 zfcWf|e~!2thUh;qpPxDmGzQH2WEW_V!8S_`OP%JJV;ZDgJZ5keQdF>vb6rdrX73tm z1S1y@tIPfLJXs4W*zLy;-GbPXfk^;wd`l%PPbRd*30VDJM)DK| zbNGs1V?dESxk+&XRizKh?tQET=}5I!Sic$-Y+g~-UgN5V>I5tt*Vl^;Vdi#X1y!jK zy4ZV1kcM;!hJ7aw6>N*l5HvMc(t+XtIR0Nup*q#vM)|dVcbEb-c0#>61k;8saLT^> zNo9R3DqLbo^%5Auf6@%Wp_x48cO3(R6Qb#)6Ez=rWsB@h?z{?HkOIwFJmpbTFk?wB zubmx~E4g54Si;`*5Kb4VM+`j{6G6~c`e7ZYNJ9pQneL7$D{@bFr;J-oDK%!3pF3v0%uFvDsS=+;mA~`wsOP+ z%PQ(tnb+B?X$}PNM-)DqC36Tkr1Tr~-0g%S?KWpaeW|-JEu5>w-7lM zy2WjV$(7ep_?I%VwhakWfa`y+NW16vGhI%(-9KD^$uFFzEQk5{aP!G*2#!6KKBKAwQca?!4tm)kH(@-GI&V##M8R zH0i+{*;S7$5oO#3z?Zhkn&*#xyIpZMGK6h$@u0bsAmALnMheQ6jzsWOH(+B|Ehw&5 zB^<9-#R;_P+5D=mfE=++wtQLQp;&?DRD7{k%dnhwK)6PF4d;Rvf3M+O@T!&obJ5Fn z0?q}WLyYe^7kj`m9?->}9F2!_!AD}R;au=p-*`Y5dx$v^(8Zq4nwLrT*!h2tL@o8Y Sa+&G?0000 + + + + + + + diff --git a/news-app/android/app/src/main/res/drawable/notification_icon.png b/news-app/android/app/src/main/res/drawable/notification_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7f0f9843bd11ddd4cbe86ba2eac49a793967b016 GIT binary patch literal 1880 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 Sa+&G?0000 + + + + + + diff --git a/news-app/android/app/src/main/res/values/colors.xml b/news-app/android/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..31c07a61 --- /dev/null +++ b/news-app/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #008DA8 + diff --git a/news-app/android/app/src/main/res/values/strings.xml b/news-app/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..2add2288 --- /dev/null +++ b/news-app/android/app/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + + elCaribe + 3187011931582004 + fb3187011931582004 + c5fcb2473b8557c9ff049dd807db8240 + diff --git a/news-app/android/app/src/main/res/values/styles.xml b/news-app/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..d74aa35c --- /dev/null +++ b/news-app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/news-app/android/app/src/profile/AndroidManifest.xml b/news-app/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/news-app/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/news-app/android/build.gradle b/news-app/android/build.gradle new file mode 100644 index 00000000..17bb8a3a --- /dev/null +++ b/news-app/android/build.gradle @@ -0,0 +1,33 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' + +subprojects { + afterEvaluate { project -> + if (project.plugins.hasPlugin("com.android.application") || + project.plugins.hasPlugin("com.android.library")) { + project.android { + compileSdkVersion 36 + } + } + if (project.hasProperty('android')) { + project.android { + if (namespace == null) { + namespace project.group + } + } + } + } + project.buildDir = "${rootProject.buildDir}/${project.name}" + project.evaluationDependsOn(':app') +} + + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/news-app/android/gradle.properties b/news-app/android/gradle.properties new file mode 100644 index 00000000..ff5389fd --- /dev/null +++ b/news-app/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx2048m +android.useAndroidX=true +android.enableJetifier=true diff --git a/news-app/android/gradle/wrapper/gradle-wrapper.properties b/news-app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..da32f1c2 --- /dev/null +++ b/news-app/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl = https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/news-app/android/settings.gradle b/news-app/android/settings.gradle new file mode 100644 index 00000000..4b1ccad6 --- /dev/null +++ b/news-app/android/settings.gradle @@ -0,0 +1,26 @@ + pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.9.1" apply false + id "org.jetbrains.kotlin.android" version "2.1.10" apply false + id "com.google.gms.google-services" version "4.4.2" apply false +} + +include ":app" diff --git a/news-app/assets/animations/noInternet.json b/news-app/assets/animations/noInternet.json new file mode 100644 index 00000000..bffec4e6 --- /dev/null +++ b/news-app/assets/animations/noInternet.json @@ -0,0 +1 @@ +{"nm":"No internet Illustration","ddd":0,"h":160,"w":160,"meta":{"g":"LottieFiles AE "},"layers":[{"ty":0,"nm":"Mobile Bar","sr":1,"st":14.0000005702317,"op":164.000006679857,"ip":14.0000005702317,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[80,80,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[80,240,0],"t":14,"ti":[0,26.667,0],"to":[0,-26.667,0]},{"s":[80,80,0],"t":21.0000008553475}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":18},{"s":[100],"t":20.0000008146167}],"ix":11}},"ef":[],"w":160,"h":160,"refId":"comp_0","ind":1},{"ty":4,"nm":"Phone Outlines","sr":1,"st":1.00000004073083,"op":75.0000030548126,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":true,"ao":0,"ks":{"a":{"a":0,"k":[80,99.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[80,99.5,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"masksProperties":[{"nm":"Mask 1","inv":false,"mode":"i","x":{"a":0,"k":0,"ix":4},"o":{"a":0,"k":100,"ix":3},"pt":{"a":0,"k":{"c":true,"i":[[44.183,0],[0,-44.183],[-44.183,0],[0,44.183]],"o":[[-44.183,0],[0,44.183],[44.183,0],[0,-44.183]],"v":[[80,0],[0,80],[80,160],[160,80]]},"ix":1}}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-7.18,0],[0,0],[0,-7.18],[0,0],[7.18,0],[0,0],[0,7.18],[0,0]],"o":[[0,0],[7.18,0],[0,0],[0,7.18],[0,0],[-7.18,0],[0,0],[0,-7.18]],"v":[[-33,-72],[33,-72],[46,-59],[46,59],[33,72],[-33,72],[-46,59],[-46,-59]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"c":{"a":0,"k":[0.698,0.102,0.102],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":1,"k":[{"o":{"x":0.743,"y":0.748},"i":{"x":0.256,"y":1},"s":[80,240],"t":14,"ti":[0,19.167],"to":[0,-19.167]},{"s":[80,125],"t":21.0000008553475}],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 2","ix":2,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-7.18,0],[0,0],[0,-7.18],[0,0],[7.18,0],[0,0],[0,7.18],[0,0]],"o":[[0,0],[7.18,0],[0,0],[0,7.18],[0,0],[-7.18,0],[0,0],[0,-7.18]],"v":[[-33,-53.5],[33,-53.5],[46,-40.5],[46,77.5],[33,90.5],[-33,90.5],[-46,77.5],[-46,-40.5]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,1,1],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":1,"k":[{"o":{"x":0.743,"y":0.733},"i":{"x":0.213,"y":1},"s":[80,240],"t":14,"ti":[0,22.25],"to":[0,-22.25]},{"s":[80,106.5],"t":21.0000008553475}],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":2},{"ty":4,"nm":"BG Circle Outlines","sr":1,"st":0,"op":75.0000030548126,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[80,80,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":1,"y":0.863},"i":{"x":0.565,"y":1},"s":[0,0,100],"t":0},{"s":[100,100,100],"t":13.0000005295009}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[80,80,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-44.183,0],[0,-44.183],[44.183,0],[0,44.183]],"o":[[44.183,0],[0,44.183],[-44.183,0],[0,-44.183]],"v":[[0,-80],[80,0],[0,80],[-80,0]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.9529,0.8784],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[80,80],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3}],"v":"4.8.0","fr":29.9700012207031,"op":75.0000030548126,"ip":0,"assets":[{"nm":"","id":"comp_0","layers":[{"ty":4,"nm":"Cross Outlines","sr":1,"st":12.00000048877,"op":162.000006598395,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[9.485,9.486,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.982,"y":0},"i":{"x":0.667,"y":1},"s":[0,0,100],"t":36},{"o":{"x":0.67,"y":0},"i":{"x":0.833,"y":0.833},"s":[120,120,100],"t":46},{"s":[100,100,100],"t":50.0000020365418}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[56.45,86.45,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0},"i":{"x":0.667,"y":1},"s":[0],"t":12},{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[0],"t":36},{"s":[100],"t":43.0000017514259}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0.248,-0.247],[0,0],[0.248,0.248],[0,0],[-0.248,0.247],[-0.248,-0.248],[0,0]],"o":[[0,0],[-0.248,0.248],[0,0],[-0.248,-0.248],[0.248,-0.248],[0,0],[0.248,0.248]],"v":[[3.143,3.142],[3.143,3.142],[2.245,3.142],[-3.143,-2.245],[-3.143,-3.142],[-2.245,-3.142],[3.143,2.245]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,1,1],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[9.485,9.486],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 2","ix":2,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-0.248,-0.248],[0,0],[0.248,-0.248],[0,0],[0.248,0.248],[-0.248,0.248],[0,0]],"o":[[0,0],[0.248,0.247],[0,0],[-0.248,0.248],[-0.248,-0.247],[0,0],[0.248,-0.248]],"v":[[3.143,-3.142],[3.143,-3.142],[3.143,-2.245],[-2.245,3.142],[-3.143,3.142],[-3.143,2.245],[2.245,-3.142]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,1,1],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[9.485,9.486],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 3","ix":3,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-3.286,-3.286],[0,0],[3.286,-3.286],[3.286,3.287],[-3.286,3.286]],"o":[[0,0],[3.286,3.286],[-3.286,3.287],[-3.286,-3.286],[3.286,-3.286]],"v":[[5.95,-5.95],[5.95,-5.95],[5.95,5.949],[-5.949,5.949],[-5.949,-5.95]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.902,0.2902,0.098],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[9.485,9.486],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1},{"ty":4,"nm":"Bar 1 Outlines","sr":1,"st":12.00000048877,"op":162.000006598395,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[8,28,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[104,103,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-25],[2.5,-25],[5,-22.5],[5,22.5],[2.5,25],[-2.5,25],[-5,22.5],[-5,-22.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.7098,0.0706,0.0706],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,28],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":2},{"ty":4,"nm":"Bar 1 Outlines 2","sr":1,"st":12.00000048877,"op":162.000006598395,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":true,"ao":0,"ks":{"a":{"a":0,"k":[8,28,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[104,103,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"masksProperties":[{"nm":"Mask 1","inv":false,"mode":"a","x":{"a":0,"k":0,"ix":4},"o":{"a":0,"k":100,"ix":3},"pt":{"a":1,"k":[{"o":{"x":0.517,"y":0.517},"i":{"x":0,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3,3],[3,53],[13,53],[13,3]]}],"t":12},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[2.75,56.25],[2.75,106.25],[12.75,106.25],[12.75,56.25]]}],"t":23.0000009368092}],"ix":1}}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-25],[2.5,-25],[5,-22.5],[5,22.5],[2.5,25],[-2.5,25],[-5,22.5],[-5,-22.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[1,0.5412,0.3961],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.5412,0.3961],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,28],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3},{"ty":4,"nm":"Bar 2 Outlines","sr":1,"st":12.00000048877,"op":162.000006598395,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[8,23,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[88,108,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-20],[2.5,-20],[5,-17.5],[5,17.5],[2.5,20],[-2.5,20],[-5,17.5],[-5,-17.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.6275,0.098,0.098],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,23],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":4},{"ty":4,"nm":"Bar 2 Outlines 2","sr":1,"st":22.0000008960784,"op":172.000007005704,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":true,"ao":0,"ks":{"a":{"a":0,"k":[8,23,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[88,108,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"masksProperties":[{"nm":"Mask 1","inv":false,"mode":"a","x":{"a":0,"k":0,"ix":4},"o":{"a":0,"k":100,"ix":3},"pt":{"a":1,"k":[{"o":{"x":0.623,"y":0.612},"i":{"x":0.047,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3,3],[3,43],[13,43],[13,3]]}],"t":16},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[2.75,45],[2.75,85],[12.75,85],[12.75,45]]}],"t":28.0000011404634}],"ix":1}}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-20],[2.5,-20],[5,-17.5],[5,17.5],[2.5,20],[-2.5,20],[-5,17.5],[-5,-17.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.4745,0.0863,0.0863],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.4745,0.0863,0.0863],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,23],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":5},{"ty":4,"nm":"Bar 3 Outlines","sr":1,"st":12.00000048877,"op":162.000006598395,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[8,18,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[72,113,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-15],[2.5,-15],[5,-12.5],[5,12.5],[2.5,15],[-2.5,15],[-5,12.5],[-5,-12.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.5333,0.1294,0.102],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,18],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":6},{"ty":4,"nm":"Bar 3 Outlines 2","sr":1,"st":32.0000013033867,"op":182.000007413012,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":true,"ao":0,"ks":{"a":{"a":0,"k":[8,18,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[72,113,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"masksProperties":[{"nm":"Mask 1","inv":false,"mode":"a","x":{"a":0,"k":0,"ix":4},"o":{"a":0,"k":100,"ix":3},"pt":{"a":1,"k":[{"o":{"x":0.432,"y":0.402},"i":{"x":0.163,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3,3],[3,33],[13,33],[13,3]]}],"t":22},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[2.75,35],[2.75,65],[12.75,65],[12.75,35]]}],"t":32.0000013033867}],"ix":1}}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-15],[2.5,-15],[5,-12.5],[5,12.5],[2.5,15],[-2.5,15],[-5,12.5],[-5,-12.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.3647,0.0824,0.0784],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.3647,0.0824,0.0784],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,18],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":7},{"ty":4,"nm":"Bar 4 Outlines","sr":1,"st":12.00000048877,"op":162.000006598395,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[8,13,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[56,118,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-10],[2.5,-10],[5,-7.5],[5,7.5],[2.5,10],[-2.5,10],[-5,7.5],[-5,-7.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.4627,0.1137,0.0824],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,13],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":8},{"ty":4,"nm":"Bar 4 Outlines 2","sr":1,"st":63.0000025660426,"op":213.000008675668,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":true,"ao":0,"ks":{"a":{"a":0,"k":[8,13,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[56,118,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"masksProperties":[{"nm":"Mask 1","inv":false,"mode":"i","x":{"a":0,"k":0,"ix":4},"o":{"a":0,"k":100,"ix":3},"pt":{"a":1,"k":[{"o":{"x":0.359,"y":0.354},"i":{"x":0.137,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3,3],[3,23],[13,23],[13,3]]}],"t":27},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3,25.25],[3,45.25],[13,45.25],[13,25.25]]}],"t":39.0000015885026}],"ix":1}}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-10],[2.5,-10],[5,-7.5],[5,7.5],[2.5,10],[-2.5,10],[-5,7.5],[-5,-7.5]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.5412,0.3961],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,13],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":9}]}]} \ No newline at end of file diff --git a/news-app/assets/font/Roboto-Bold.ttf b/news-app/assets/font/Roboto-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..43da14d84ecb949ca5f5e8ecca3a514aa7fe1c7d GIT binary patch literal 167336 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 T&&O!b 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

1!1exG)EK6u~BkmExnKuQI2?mS0J2 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 Og7zwfTI$9`zx;34o!sdF literal 0 HcmV?d00001 diff --git a/news-app/assets/font/Roboto-Medium.ttf b/news-app/assets/font/Roboto-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ac0f908b9c9c73da558b45d65cc5c6094874d3e8 GIT binary patch literal 168644 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 X76wnEi(n^v8!x?bp_;lO!DIggn3ZN8 literal 0 HcmV?d00001 diff --git a/news-app/assets/font/Roboto-Regular.ttf b/news-app/assets/font/Roboto-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ddf4bfacb396e97546364ccfeeb9c31dfaea4c25 GIT binary patch literal 168260 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

S&jh1n&JeeMOkU4{=4@cYr6k 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≤FHXCuU9Qce=@vmZ{%9^5??OaVQJ8!4u8-w;yt zGSf}w{_*50pIMtH))x&$Q}axgpy4z3*ZNBi`SbQCA6bGSU<$bp;prp9IP*#G5;tDF z!#;m)YCm)K?3H1p9|vVdnN*ofj-$y*meTV~S;HwjPnNth*M{+|_sp6PC(Trj<o-- z)0y^aD}vzUH`&)RDUAL@>zjYZ7)trPKdc188#9mmW|KTF;z^`d=eQ4^87+tnf_oLX z<|Fnq*c5&#`g8WP*%a|5n}RN~oc&6Al_EB=Dg5gAHlDtNO@V6KmJIQvlwAHGw@pm{3FdkUYpWt5%3{x`IDO*x<(6{-@Y*oe8O++&}qWU|jz zve`dUa)g5K2xZ3dM444D)r-B4T9AEFwJ7_NYAN<*_=OQgtxh{2=T%Cs)>3P+udCK) zKS&+Mei~Y=q9$weg`$0hE~RKcqAMxrNr%`UVa-y}Zfej_OVI4>?`kRR)3glsSy&$x z{WI3P6{)GP|3arV(H_W6|3<&aK1xq#pQ%4!|A@DM6pS~8p}FJ}f&3AoCdWl+>7T@A zwk>R1*?ty3OPVuptHyN;e6tB&*d}K;oD06T`4ar82L^ZUDQXYJl?&T)Ka%M?X-b3(XHe;A)T=VtjsV@my z)bBo{u28Z{M754+9r1#AVQ8QlxU==e;ERFNUl!E;mZ43s+y|Abzq-vP&Nej&e7(TyYhKT3nAB!knQ?CV-d|IoHlsKZz>ejd0v*qZnEWM zyaz3~cR?P4LdUyH32NW^V$j0Y zw*v=LU-&9`TANnTX*HBtDK}YWPb-l_3zJSzMz(QlN)S9PuU*Y6l9F7Nn`~`K1(|1? zU~ID#Z&926wjl4UKqF_m+F%8(}EUCt_*4c-$w)_(JU|vpkgE*lb=qV=OX5Jof+kC-2Mq zli!8*{23oUEit(~iO;+K--pJ^|D4uC4U&eovo00jvRPvW`{?ni5PR|qbKld;9`93{2zA>&oJ$v5K zv%HqS)t{I8?{WOHZlZA)t~UADup4~!;lKSe!f5O7^HLi}pyoC9*Nu(FUgNCcG}gn% z#f+`SN#i!{j$g3+<4N>3NeiPEa0$uh=W?&De?E0RSD&{x?ipAVUy5~^x4l(`QOAEDYB=rPnEQV-g%NMm(u@D9-cICf!)l>lyh)F;(xf8 z7BpoPvj-yzT>5wE`JL4N;mVUf8ROLL|fvTu?iXDH`|WbFU>@Bbi2^K_s5&v+`kjNS5gSD*JMf8m(A-8EKA zt;YC4_Cxcpm^m&YTmSw?{~ni@pYbJknPWHU6fA_^%(hF27nno8$oDNvi!yxSxtu7E zW?WTN6V>^8&MVlIYhp2~jTK4S;p$c$FXt+G-10ZYfntV-ouOJjX%wmL_h ztA44@Q|GG-)UVWq`PbH-uB)lPs{7R6)Pw3_HB3FGo={J!r_~7coO)ips7CRnju=*s zW7RnIrkbGMW-Tq5b+o&zprx{Umd>hKHfv=MSSQP2b(!~yc%{Re6|q9Bi21QvRzfSO zmD0*+<+KW#zg9_mNvon&(_Uuntd=(0vr@(i{CsmYdXZh4!|X?Ol-eejQDr5!(y2a!Yt5NCd+j9YjYgE}hx@ zMOU@}(VefKRKjlA3$E$SRteibvQ*b1?Rd!NzA1 z0RM~S5w4Y_%&7BgJ)eL1-^YOoT{N3;!jMY$N~8-m3Rox z77>r&TR-s_-Yq6_;8}lRz_(Qt0pC_rba?k=#iG^G8nN=(R%@sDXN^9e^@k#~lL+wK)04|@eRN`HWwMuo?ch@V8v`yM3r7^b1AC)HB7Hx~tRD-vb zW^yf8X#sEVS6ac*hm>~MC{HLIwUgQ@r5o?2oKt%6c1pC;3+}$6^oGN)Dt+McIHj+4 zOG{D)vO;T9hRan~Wdxk>Rz|Xd`cN6AL-kPQI`5S1 zRATkr`fth&{h)qOxvd}3k0^KaWBN%YNk6ThR_^K%`dQ^3Z=PIGoLE|;lvLh9xu&G) z@p`=C(jB@(aqIW=drG>_rex^ZdN!8if}#;m?+V@5cl%2}}XA z!7bGDG zSPRyH@4$Mn!AMazg73j5@B`Qieg@mXFJL^2;{TkTMPCI7wTvyX5; z;ctWo2oG`35pWb72jSopfDYl404*nqGh&u9$zoqK#0QuLOfHE0WhKqnx7 zpR_CJ4&Zqmp4Z<7eLz1j01N^{z%b*UJ_3vcqrtmi92gHKQm4saD)@jtw~8{rkNO(! zTMssoW-}o?$NTdo^y8!p2WO2iJqp~z_D~<0aYZ!2=Ghb-v^jHuD9&SyD@tkJKx<4G z1X4s1#<(JkXGPRiMm#d*3NqyiGUbZq&sUx+5%&_PCyHpK(Ov@$!0VtPXau@|H$Yb~ zpR}vNT5yy+qRHb5xC*Wlch5-DGK_eA0+UL}!ed5~egd523z%m`5j_gr zW3E@8+JsV@RBCdOnuJo5P->D&O+u+jD5bwh>G>oeCT7$MA2=oE{zyL4=tO1+BR_fgfDmkH& z6Dm2Ok`pR9p^y^_IiZjf3OS*W6AC$@kP`|yp^y^_IiZjf3OS*W6KXi2h7)Qyp@tJ` zIH86UYB-^W6KXi2h7)Qyp@tJ`IH86UYB-^W6KXi2h7)Qyp@tJ`IH86UYB-^Q6ACz? zfD;Nhp@0(#IH7penziS8uoKV0C1)Na8 z$vFNdH7LcCIb&>bGPXDwTbzt7PW4al5Ip9|)CxK5MBX})kxt~J6M5uB9yyUmPUI24 zMhBoE^2mt{aw12Zk}r)}jNr2vC1){8&SI3D#V9$;5vR?>o?FV>O);1IZPWHWMSDzl6X8H|vbjF6d(PMM4_nT#Bnj2fAY8kyP+BSVV=w?GQ#nMNj~KqjL=CZj+mQZo~& znTgcQL?UJ)5i^Pj? z=m2Je#b60o3dmFa8XN?NK^Qm&E&^(xVlPr-z#rfNctj0qfq7sBfHE5QXsN(W=?j9w zz!!7`oxvNR8|VRgf?l9EfVz5rFc1s|-+_J9trfMmQF|M;w^4f=wYO1w8@0Dldz;v2 z9D@RWP{0NSY*4@k1#D2j1_f+Tzy<|uP{0NSY*4@k1#D2j1_f+Tzy<|uP{0NSY*4@k z1#D2j1_f+Tzy<|uP{0NSY*4@k1#D2j1_f+Tzy<|uP{0NSY*4@k1#D2j1_f+Tzy<|u zP{0NSY*4@k1#D2j1_f+Tzy<|uP{0NSY*4@k1#D2j1_f+rq*WLJn_-`BZbYMFCZS^{ zi4(?c5o<)DMYNkTtKLN`f5FG)ftNkShbm)G(47Mv7DPr@}@g{kj7Ex&r;W0{yxIHH@T&k<>7f8b(sXNNN~K4I`;tB(;mA zc9GO7k{U%)i%3czNvR_#btEN@q{NYwIFb@aQsPKT97%~IDRCqvj-Ss)wyPI~&Lpl^zYAcuZ0fM&!g7Q#|S3{oqRQ6`R2CQd=7D6fNNpap0{ z+?#}Nfwuwukx?j)Q7Dd4D2`Dmj!`I%Q7Dd4D2`DmPThhW*lNTe+hdUJG0659WP6Mn zYQ!^dh-cmq&%7ZXshEgVOk~uGW7LXc)QV%&ieuD@W7LXc)QV%&ic=%O1#k&OgDc=F z$ly8DQ@sy<2Y>R6hv2ah!~7tg`9VDMgLvi#@yrk6nIFU>ffE_+;P1&JKnh_@4_aPH(d9b|HB7;%i8 zak>hS!+HTw$cSMSjnn-IOBjhr`9!@GD95$(#5V+uK~vBiv;?g|TMz^~abFkE6?6yi z7UN$G<6jJ;ZXBa-9HVX=V_^&ho4CH2y1@q58>(uKpLpD_N#GXBNF@oqTY4ad9TcsCsGhQnQOcpMxa2Zy`i za5o(8g2UZ#cpO~qr04j;(f8nJHyrJTqffxmE;!l_$GYK8H(VJ9SGwU!H(cn3>)ddi z8?JLR#>O(n#xlmnGRDR-#>T>lZaC2mC%WN8H=O8(<6Ll@3yyQaaV|J64vve1G$BxG4^9a=}S1ILQt7xZ$2SxW^6G#KARj+6^O?F+Y|uKNjwB!#Qzq4sX(fOpXma z#}%N0BA^7VP!f~^xGyVe1HiW& zA0j-4+>#?Mx*DzNPiv;rg5k8(dRl5dEw!GOT2D)@r&ZEvm2_GqomNSwRl;eNaHyLO zbvy6I3i9qNWd*>ory4rSA!Y&sMThnnF~GaPEJhnnl5=6Wa@4h6%ZSU405 zhhpK>KAqa9Q~PvkpHA)5seL-NPp9_b)IOZrhg0)(Y8+0D!1?_X)*d;d_9HBTM?y(Z3j8-S>`vU%1#|`70er?Nc#l!=9{l`t_C1JmIRl?a zzLUXJAZO$2iO-#p>&J-;2WR;r#RV)R7x_v}6vsCS6Tv->(}~Xl+1PFVBz(Z^O7;Ty zOh8Y5hc8v6nR?(BT6_~cMc$AW`y+3GIJ=lKB$=$6S`8!sXv93E z33_NVN+RbY-C4En0eS*vFqGECSYe~2Qm?%vqDUVDc(S+&B@)poY~1hUS{iBHgy|p? zWPxmOpEdbE2p@olAji0b9(xHL_LAabBx5tX%y?r{{Ea*4u$P$sNNTvyWiMe%i$Zpt zP?~Yv0<`5?AlHJ3Z-?&N0lZ24Ti|WbA3$B&!leu)-(g6S;Yjkaq#tLTVN8o)Op9Pl zi(pKPU`&fpL2W~k^+GDgzIB}=I8R8?*D$a3y2{8NAIY-MR%r;xt#T+dQesZCcUP33mq!))HOE9y1fv_aui-e^JOQY|VLM518wHWa^HW%z4{N((VC!!4a+<z|>8Z_$#ew4#evWaWdeXEh-%h_JJ9*KCDxd>w8#dfXnUP{cS4MGr&K z!&+s|e*{N4K0_DK!EkIDX=PU&s%m;)Yjz*Dk+ zr^8cr_$d{BvcXRm;T=1*u~8cvwXwk;c6h@MZ`k1tJ9F<0c*71)q)|f~JR#*mI&E*K zjqS9rowk*7BAvFCvLc-}O`|>2Xv;L((Lo#9X+Jw{XQ$RS+RZ_m*{QvacCyn(c069H zputt6b~QkK&;T?t5}-%|)VL)+=X@Dh33ifp7vXNgD6V5&W$oJ!N+s~7L0 zZB}PZoyHv;HU&s5=xv> z0blxnFMYrl3fkaxUwGXYUibZrWRjzn4c_&IqBi)}7pmIuj4uLJ8MV;yxYr}4h`UbQ zZO-ozW&rdjc-9x5_0=_Wsp9BeFMt<8X}-e8$~S!L3*Y*}x4!VLFMR6@-}=J0zVNLt zeCrF}`og!q@U1VcBS$7VD#;N^O15Nu8u?(yg%5pmC7c|EY`hWH2-{gRsNNhKV-&}i zIgTN`ZrVVe3uMmli<5#Wdhnec1x&r z2j05_@7;m-lAxBfqe>fU61GcTDE=7q_ zplK#FO`$Y$^yZxf*6ml(&sf_g*A#M1fhL*IBomrsLX%ABA^TwpbjXAbvae-Q=S=FH zNu5)ub0$wn;eD&_jL$tlPtYHm(NRJtkh9Y?&fOfR1IBxi1+u|?uKhvy06YXa=oY+} zkJjiy*T_Q8NJm?AF><@n6kUwmF0@1!TB3{5+l^M}LMwEk6}r$0U5wstMr;?Fo(m1n zg@)%s!*ikGxzOBPXlgDrH5a3^8!gR+mgYhmbD@p77@ggW%x<(UmtFvr1D}AE0DT6n z$t5a_rz=a@Xi3>=dmp;GGDqKj6IxX4xssvQwC4r!bRtGm}nX7M;Q@+RZFlzDMFVErZeu zm}ddZZ~gPU8{%e`obq2;{{H>@8c(f)p7%A}%!b{}hEw!Biy!adFxyRGwwuCCH--0G zt;|#l0_=*kaws;jP;6qMyjNKm8xJ4LV0Ggy!lyQ#P_c^h1HfbB31!`8E3F@ftt=E9 z?-ly&7G%?Q@jGuhXh1i%E9OcB_MUR^Sq0+!vFB7GYy-M;eK6q=FpM-Kz`GnzAbuA1 zo-e>0Fc*9Y{=c>Zp*g7E0;r{;J*c!KZ%w(?^UX>0`FgH7NE@VwQJJR8wo?%5CM z5$Yk*@@57$z)HqlMNg!zkkjbcS(8XEU#C@{(FaHhE=}S2lTNlb6&KvdJr(yt1*_ zc;1`$7u|U44G&R&0o%c!;30TyglHCHvE~JQ zKtWI#Y$AROI0+(+#q{J5dT|K7ECj1=2v*$?thymsbwl*Gz}sLDSP!-de|Jc9-1J$t{`OlF2QZ+>*&HncVJ@TQXnJ z@RTb}K9>(=#PEIo8}N=IDTx09Z-I!`@YE1A%Pwe9FY?~XW!6O8tfr;1zEo61FlUx) zqLJiwp7LF1j(CsR$$jH0Eqj%gjb`2#!`y(izE3WA<}o%C1?#34_LXv2uWB3dN?rOmwjZSxVAYRj#43Y~ zL&|8QbDikD@+Bgkcn?PF~ch2)&^wA zw<45s4E5_k{R&aimP(hLbm$nS49aoQV_v1l45GdTsIP^R*M+86@Fpq1j7PrnSj4zs zzW3OXRjyvF1@tDA?>mlz!k_Sl$PCs?XY)P8FVTGV@qXt)u6y3^IEnX2VOsgFI1cqs zVmUiy{6-D$(%NTf?O0m-l=2m=d&4+O3!bF~BjJzAP`5V{s2{et)zEt%e6b&jdU)kD z{85}12!{p{)a^T3pgVQ@n7T#Ke(}^TgVvZzYedi*U8&OoC5HEDlHpG;?s7w^bf}cc z-RV#u6AIiVRWx6}>B#f?G4qXqKYb{xTo>HOJ@>fBZO){c&}XEroHarZUnu;Ni?lt! zZT+CbK1vpWC*eIjzwaCSxo;<}62-lzdERxHl5f^WZrfBep304RR`|xNFSEgkfDr1!{K7)=q9&@nWgf^=cfp5^BrxG zEl%)W|0$?=-S~<6M8M&9o>}z_rDUE}Pbh$OrZ+xi{owxkLZ`)h8G9(tDKRc*A1z*; zT)WWX3u*BU)b~I2&n0B}a zC2qmJ3A9%N(u&!i&}r#Y9A$7<26yqbA7c2Dk6h)YO;kg*!5HV+DvSPn9qJ|12dtXu16CdH zuifNyh^>~8Uwf);e&?wU7QHKYh4C|>@Ix$HeG!L6?=}3sZ1@^F*;??8#xzzGerIci z@7E(}lp_q$Mt+HrUm%y>U%{sLS3A@DtGy!qzB-wHUvHRxU)@c=uO6n~*PEu_7hYfZ zec|G#zKzpr_GA#I_uNDMRmzDAfHU+cCYv5#Q%#Sr5AgWd3}eM*9q~1>CN@4^f44+rZ?Ad)0^vr z>CF{xdUKt`=P+5EGJU#En?7AC<)2^y!K;eY#>zpROCGPgk7j(-m*} zblo(4x^Cf*a0wdKEN%y$YGWUd2pbuS%w`S7p=J>m}3ItBUFCRaJg% z7=JM7c%)4waw^zRBX{kwuq|E`Xve^)otzpFd`T@RHvP0y}D zrf1g>)3a-Y>De{X^z0gCdUlOBJ-b$#o?WX<&#r&zL-nD`Px^3uxU$*w^4em0d4-x@ zUOP-LuU)2>*KX6x>sQmuYp?0$wa@hOI%Ilz9X7qZ!b~r(qo$YF3De6f-1PD~ZF+f~ zF}=JZOfN51@bL1A(vRZh6^)nISvyGK) zh0Il^nEkV~**{C0{WHMqp8@pGD)iN=Y~}F4tj4%iolT*yy^Q>=%~k+Cp$>P}Wh+Jh z;#*<#uh;PGlwO`iun0CLO%t~Ac!f2^7q}Ul^bSMQr}wo$N(Zr7@dFJ;+O%VP(d_@; zX8+ge|2>cqZ?b9lYW5_J^a_2!lm$M>g8q#01K50!3Ii#<^bPeg<${7-*um^zC!31b z@Gi=>n@u$(g^wvIbW>7znUccalo7>{5I3oJ5}QAA!p2@oiZZ68zz>Yg%ajxqkrX+i zBGSSTUZ%XLXv&Ld`U}~azsIn^yjVttQ{0n2I5Ov!t^CAt;|+tivZK3 zw6ro0DHC8ynE+GDR5qndVWdogcu7fA5=8}MO$wuoQ*ok;^0p1f($lnpDR(NEa;LB< zcYIB`Q{0q0^80Te^ZhsJX<7ui^Mz3HiJmQr;}<RBz3?s-o10swpN)$y9 zkSHoB5*=2DRX~HXh=_oK#)rN<#034gA`*j3UU-`5%rN8oJf8}P3b=qED(<3wqM}h; z;ub<=f8SGm@7$RLOybLb?{$Cr^y%8Vx~lqAb^ZF(L0kF}(pl!05pp?rhW9J{3iwCC zHN0QxSHeGr_G(lAIA5j3L5H=eU&YsHanNLK>eujb@n_tiRem9`mX_ES8+;5Rvu%fa6o17`uYrd5` zE@Hjr2I!yvl8}pKF}j|2$Q?ZCopL9!?gE=?!ivqmf?py_A~h+`(&}jR@!P$^iajbuKX^TN-yj>%DtwOFjEroQjZrIl zl$UXT1?|;CtY3kmHw@j34r`IT#XnXB@;0+&E$L(4g}yKE^F&*~*t~oo%+<2q^oQgP z-D1-GR6dOkWmV_Th=qnRIsBU0l4A6Xzacd=O>qAcm|PRtF5Ah;w_tN#cE}FW*$GDH zWtZ%N-z$4bb04!T8G0|)*ksLSiJGu(v!bQfKpA^!krrW#1x;mW6K#SOz^0&k9km&A za|PNQl&_<w*l?*+7@duv6h1Rc|A-IgKr1==e0dnZer~K3g}to zza#wNpn+bG&?DeGfeL#4f&KvgNYFvAN9j@Uok0n`9<4{i9|KzGu@BG%{#Z~$uU)k( zd^ha|f1Dl%-(9=I_s|~jJ+)_)(&L#o%4jd`1^+|lk1~3Ko&bL$^GF%(t-aw-(v#p> zyb%5r=9MzqNBh9{)xPkj>Z$OjG0&9Ie%cTIbmp5f^y_7m)c_qpDGk(t@Pl*^{9qjn zKSYPX55*3W)3fv}_;M|WAExwBI$VdtkI)hDBXuPFC>;fl{x_6SLR4s@1n5k(oQw1l zW+4moQna0mbQXWV73gfVo{RJ{{(>v8F$DPQm3n2=jy1Qhidu#BGgYtFtC?Aur}J<} zYn*viw8n|GSQkgCcYvsRy;B)?>s@*m&-H8lH91+LOUMIyI3=!X`)#UMYB9A(TDY6+?VMx=yF|7$Q8N*&Fn|@QBpwToHQAWklQu7 zhNpWHo$W*QDdtcM^l5ar4^{ryg{v44y{dem6g>w4xsH|PdZdk&3D>}l$Y_Fo^G zmyK-f!YZ5Sreix3-E??#({V>PoiOO8^CjST!avdf=e9i8ihj9O&iOmf_GRL;A(C_Z z+VFO5hiOn2GJU{YH^_!*9L_Bnk=a%n z=wA{`wK<2C-XVdS3rj_?CWyJ&W;Eek2xD&flBl0!^D|$i_7Yq7RuUFEUp65~AW5qE z>&Dr)&8I6NPSRp>F|3Si%jYPGowy!|wxxyJDu>Nz z=SJLO<)Q!Ms8R=aAus1Sy6!HxFJ6aH?h-Wit-l+Dll=G!|5cQwm_t$zdw5epb z`7GOuvoUh+mSS$(pIgo^e@;Rr*O&$hd(!@M_poyZ?u2ceu;nBkxLTd-y1i8LOhw2Z zduppOD_fJ+wq{cz(NpmC<#$pu?Wp^@TauQigd5W?Ofo0ETmiDsu5xlgNY_d^cUWng zl(NmJgf?YY7Rxr1(1~nLWTC~dRUWNB$hAKUmuqi{{7qbvwvx0EOT(TkqNgdSJrWsh z{c$d>zxSNG-+|9b9&+t-|L6QWlgnkUT_!bQWuHHiwrTCt${^pzBqf#HFGuUHEmv1c zosl0glX>NK>Gz+hV!Lv z=nV56In(I4Sr<>3>CT@u>)5Vt3bdP>4n5AzhIV&zpgr7NXiqmEdc3=Fw#8b=d`;{c zVkMvxb3VOXA9n_pc1An1EjH6#;pV#Q+yb}QEpZiCR$J~?yJy`d=54B|j!jXz@0im| z{ruC)heP|9_bqk12Mrlm>PHXmJF?UtSw3VC{P6Mt_)<2x+D+=$QC8+=eW36j7KhQ8 z3oS~KTLs~;)?mDav^UN1RvWdpXzz+q8=*L)j!}~}w#oq8g*DYYfFEshf(&ek?vd~o)9PWF67_B=1^KliWB;STZ7 zYQrVhbF%+oia#B%E)TF9jQl91`Y5!U3S$qh>tgE!RDccuK7p&R@|p>2fl_vJeE z9l7@L@Mx#|*k9saq7}dBUU1L5jqW+O!L4`CGNahdcV%|5ll#d1-hJpka9fy ze#ac;JML}P!P`vRf5W}*UURRySFmfb)qUb;`OExlf4RTZ|HPML<>E7DF8|=Za9?7R zkCn=>c(Dz87u(&R-M4Os+v#>;_hOIR%K*sfUW_?nbJb%t6WbRVEMFA*2L42U5ObZ4 z{K5JyRxq0SX1+OgFp9D7*AmNyt$iDRsLhWtJJwB(lkU<(ddl(AOMWOP$cfThPLh-5 z6zL;<l43)E_T!t~1HbO?qC>bqh%NQ9eF}%RIS8ekMO>z-C|48})hYD!!?eM{fg zclfIQoxZE@VR>Y|*o#dVs z>)uFeT;HM2zp^LD*8EP(Z?49%OBscH+v;`ut@*kwWxuVUm=L)K$D88U;}7Gl@#pb3eDEmN zV~f(w(yi0&(#NEGr~9PK(&g#V>B;G7>DlSI>G|pF(>JFVrf*N*lYSumXnJk>Wu*E< zq^?g-j$+m~&0yoWFtl-Vr|&CbPT#m9knS|kU-VTZ>GA1_=_y=~ci6vy&Ipu0iqhl3 zF*;*Q$EQca9pk&e4F?@L9aN>iKf|BtFYr_Rg?_5P$WQYZ<690wFx}7aGvJ4^hT<51 zt{?43_)&hOAB)x1bNo0z#FzO2exM)Z2lKt(+7I)S2z@ehy^H)zy-9D@TXccm>L=?$ zy^U~#2|0jRlcH9B7~#tZT}s+xNiCoH*`z#%xr@5%_uA^c%?RCwv~Sgg`P51KwlJzF zVRUf_VOkNgH8X*Wc=i%^HEn=1$1$NFv#%JRZXFf6b#ASD#<%sQ{xILpxAz@9IAhv_ zwVWKy!P3=ie~Rzp`}$M;X}%vN{{O2A-|>HNRbjKcRR}iRA58cRFyS)(h#km3vV)@` z{OM@8@G$V;5um}N_~Ykn5aF?)!RLSi|2)E$DOSR+k8X%=iWWo*`GA$x!OiD2NJ}==(a}n_tf@r3}HJ4CQ)u5BhK{in`@8DxJmUqAex3Z%4 zHc&hV+J}ua{I(1Em)+{Kjlo$59BH~ z3O+Sb$ALCE9Rp$mr&Dw~v8IAMT@E@lTjvmRj?UHjdLuo=V%!$!?S!~p?*<`Cffn7z zzb6lZ_4q(tKz$x1#m6|FAjE3WoQ;gr-h_uTru-0uCS{b7<96K*4^4UMykUf8_Ef`E zvs9}T{eOz5;#o6dGd_m(uLOyIo)PnCt9^GR_uQKTf54JC-(TV2doV>gun!1yWuREq zqn&g?BG`B65&WHU6#t;~$jXcXSqWh?OlL*|{8Yk%hSEz#ezKoTe>2n11mn7i9*Yqy zd*V03pNr(NuU6|>Yx`MxPkLEm*6ClZcO|wVCCR*dYkELOoFTNuVf3^a`t5W1&tVch zZ_|+Hri2!^$StC6KjYRBx-I>m&2QQrEpKz-D-CsxYS04ZJw-(qz+Vfk03jtV*r>(53tFwefL8j~L#%4}8h@j?=ox{(0a~HF z58UxBXqEm7%4*ZlO8;?)W&ab`_)mh%XTin(v8j>w%tapxzF!C5KL+1N%te0*t?;)% zt7uIURgvDU7K(Oj=`ApS5LdhNZO|%kOS|(5Xrq__<{SWgME4fN34DOGc zJ8KYIm{lRnn(TdUw@|zhb{~6w{?>vn<a@nViJI7q$NdF-r7VZ__w7)-pIos=u&Sjph}lOtHFKkj{A%X?;!cN zwpfGgz*kbiy+LqC7IODSfnROBw;WdLguq)%s!+zR)J1b>mHq}=4O(oeJ!LLJOC|g| zbCFk}75<^%a!GKpHd+C8Zl!b?v`P+#R)Q{D_@j&$M&HoRf%3NjIk)n!^!652=#QXF z{oBwg{dZ`!j)qq1cxVk0)WVE|F4c3PReCnG5?vJY9Sf~MH^qE^46V}1&`PAO`A&gW z=v3%Zod&Ja3!s%c16rdOLM!wl=u&0ekMD1xmF@}JY*{qOS&0sEtHRY#XKiJuTL~?5 zYoMc@m8Rviab$neM##RS=_$ML|6K-43nwxMWmYs;7ai%>0=YSeQTh_blTfkUJ+-R092#Y>SsP4cr>Pknh_&0_B;+j4nDmV6 zf3}h1yF>FIwCUefx4u06pD^>sn(yB%lN@B;S4;i5y=h}%W zQZXkvMSr;PpUt!i0+cmpY2a(r;`r^w`fhagl#Ri_R|P{~PutOsHuHovrjZ zn2k;HnDq=X#zw8j&TKnx3v9D`%=?7nLdu?{b)u`)6fW#MyP!-mhe2KWG3%nm(kpyl z+XM*fKNe;4-C!o{#&#^&L9*B_pvTxij&W12V<11L|GnCR+;crW?GERdvWuCu?lL~9 z{Fj_ir~0qGlzYP4-`%y7|MPD!JIAYFR3P1KuKYNyBi~?4F8ALf=XXp}Z2CC&gVBF^ zrR@*4F>c-XpXJiR)f=rj-FxX1mc-s5chZ{8KWPeN+ljvWPhYl+>Y`9~8D zt~Z#h_;{a?#?u1-=FH0*Evgz7S$A(+v3=G5Wl!$~%|FucX!{?@nZ-fB8+l}Ib*(dc z5%YWbsf*34d@r|e|=V;oN4%)@IqG%{Or&Z-m;E=QYwhtQtfZ7<7I2DBjkbv1TwHH#uSHZ!Bmf|7*iQU1fmPB z5?K&#ATGUdU2v6X47f&2Mx9-_DlmfQmjw9*Gk9;@TrQLK|G6aF=V_A;%u%wuzWhgo z_VI1Moi1gXI_x^9@^}L?*LNN!p<=sxYE^GJG|FDHHkiszQ!_XaYq!5i$XT2*=^5Am zY$L~ahvq+M)4!{3eR=vnVdjrD-@jQVImo=Pmilvh)4tYE+_^uVC9eD@AQ!@0XFO}k z-|NRKIXU}ze%;yeqR4A$WqqwEySeIKL))DqDN4FWe6B>^Vaez!yZz6Dfq_ZN)5S5Q zVoq|30=EI*6f-x0AHJ*1%-l9yd%q;}anc{=MG^ZMIOBMh8ynRfSm*medd8zVc9oX> z2@>Z||Bza3wy{6K>w)To2^IY>brg@K-`Ha4v7d3`BL9XKon6ZRH|$H8P}2}PTj_By z8=K@Y>ltE>I**k<*Z_X)>^ls!x9L|3aRT-bSbL78L@gSzr#)gDd$k3*=X!eD9nLXj7c*OxTp56iQO!(N)_)t;ZCHnB}+hIMW4y+oEm+l-2IQ?U>XijEjW?psQ z%8~75i}AEWTfeSh_hxSt3S*pLSQu)NtT$oaCK(;Msl3aUnKl|NwCT4#lYgWsVLB5> zvq>%wkFmt(6Bmy?kU2NQxy_8N&9XBCYovErHHa`U==$zaysbHD6)-_F Nc)I$ztaD0e0syvhsgeKy literal 0 HcmV?d00001 diff --git a/news-app/assets/images/placeholder.png b/news-app/assets/images/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..2eb62c488f8cc9d87a9a18eb652e9997f8f1fa39 GIT binary patch literal 18069 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 + + diff --git a/news-app/assets/images/svgImage/cameraIcon.svg b/news-app/assets/images/svgImage/cameraIcon.svg new file mode 100644 index 00000000..3cbe607f --- /dev/null +++ b/news-app/assets/images/svgImage/cameraIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/news-app/assets/images/svgImage/caribe_blanco.svg b/news-app/assets/images/svgImage/caribe_blanco.svg new file mode 100644 index 00000000..e3c157a7 --- /dev/null +++ b/news-app/assets/images/svgImage/caribe_blanco.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/deactivatedNews.svg b/news-app/assets/images/svgImage/deactivatedNews.svg new file mode 100644 index 00000000..9face8bb --- /dev/null +++ b/news-app/assets/images/svgImage/deactivatedNews.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/deleteAccount.svg b/news-app/assets/images/svgImage/deleteAccount.svg new file mode 100644 index 00000000..2d964d93 --- /dev/null +++ b/news-app/assets/images/svgImage/deleteAccount.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/deleteMyNews.svg b/news-app/assets/images/svgImage/deleteMyNews.svg new file mode 100644 index 00000000..c8e8bcb0 --- /dev/null +++ b/news-app/assets/images/svgImage/deleteMyNews.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/news-app/assets/images/svgImage/delete_icon.svg b/news-app/assets/images/svgImage/delete_icon.svg new file mode 100644 index 00000000..98c88c11 --- /dev/null +++ b/news-app/assets/images/svgImage/delete_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/news-app/assets/images/svgImage/editMyNews.svg b/news-app/assets/images/svgImage/editMyNews.svg new file mode 100644 index 00000000..fd2eb589 --- /dev/null +++ b/news-app/assets/images/svgImage/editMyNews.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/news-app/assets/images/svgImage/expiredNews.svg b/news-app/assets/images/svgImage/expiredNews.svg new file mode 100644 index 00000000..d8626e0e --- /dev/null +++ b/news-app/assets/images/svgImage/expiredNews.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/news-app/assets/images/svgImage/facebook.svg b/news-app/assets/images/svgImage/facebook.svg new file mode 100644 index 00000000..1618cd03 --- /dev/null +++ b/news-app/assets/images/svgImage/facebook.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/facebook_button.svg b/news-app/assets/images/svgImage/facebook_button.svg new file mode 100644 index 00000000..6adffefd --- /dev/null +++ b/news-app/assets/images/svgImage/facebook_button.svg @@ -0,0 +1,3 @@ + + + diff --git a/news-app/assets/images/svgImage/filter.svg b/news-app/assets/images/svgImage/filter.svg new file mode 100644 index 00000000..50fcf7cf --- /dev/null +++ b/news-app/assets/images/svgImage/filter.svg @@ -0,0 +1,3 @@ + + + diff --git a/news-app/assets/images/svgImage/flag_icon.svg b/news-app/assets/images/svgImage/flag_icon.svg new file mode 100644 index 00000000..5d788ab7 --- /dev/null +++ b/news-app/assets/images/svgImage/flag_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/news-app/assets/images/svgImage/forgot.svg b/news-app/assets/images/svgImage/forgot.svg new file mode 100644 index 00000000..979581e0 --- /dev/null +++ b/news-app/assets/images/svgImage/forgot.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/gallaryIcon.svg b/news-app/assets/images/svgImage/gallaryIcon.svg new file mode 100644 index 00000000..2d491918 --- /dev/null +++ b/news-app/assets/images/svgImage/gallaryIcon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/news-app/assets/images/svgImage/google_button.svg b/news-app/assets/images/svgImage/google_button.svg new file mode 100644 index 00000000..59d1c254 --- /dev/null +++ b/news-app/assets/images/svgImage/google_button.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/news-app/assets/images/svgImage/intro_icon.svg b/news-app/assets/images/svgImage/intro_icon.svg new file mode 100644 index 00000000..6a6b5071 --- /dev/null +++ b/news-app/assets/images/svgImage/intro_icon.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/linkedin.svg b/news-app/assets/images/svgImage/linkedin.svg new file mode 100644 index 00000000..902724e5 --- /dev/null +++ b/news-app/assets/images/svgImage/linkedin.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/live_news.svg b/news-app/assets/images/svgImage/live_news.svg new file mode 100644 index 00000000..973009ce --- /dev/null +++ b/news-app/assets/images/svgImage/live_news.svg @@ -0,0 +1,11 @@ + + + + diff --git a/news-app/assets/images/svgImage/live_news_dark.svg b/news-app/assets/images/svgImage/live_news_dark.svg new file mode 100644 index 00000000..ae1ceedb --- /dev/null +++ b/news-app/assets/images/svgImage/live_news_dark.svg @@ -0,0 +1,11 @@ + + + + diff --git a/news-app/assets/images/svgImage/logo_caribe.svg b/news-app/assets/images/svgImage/logo_caribe.svg new file mode 100644 index 00000000..6c0b69a7 --- /dev/null +++ b/news-app/assets/images/svgImage/logo_caribe.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/logout.svg b/news-app/assets/images/svgImage/logout.svg new file mode 100644 index 00000000..b60d8621 --- /dev/null +++ b/news-app/assets/images/svgImage/logout.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/maintenance.svg b/news-app/assets/images/svgImage/maintenance.svg new file mode 100644 index 00000000..3a4474a3 --- /dev/null +++ b/news-app/assets/images/svgImage/maintenance.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/news.svg b/news-app/assets/images/svgImage/news.svg new file mode 100644 index 00000000..c945a54d --- /dev/null +++ b/news-app/assets/images/svgImage/news.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/news-app/assets/images/svgImage/onboarding1.svg b/news-app/assets/images/svgImage/onboarding1.svg new file mode 100644 index 00000000..d715c902 --- /dev/null +++ b/news-app/assets/images/svgImage/onboarding1.svg @@ -0,0 +1,262 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/onboarding2.svg b/news-app/assets/images/svgImage/onboarding2.svg new file mode 100644 index 00000000..67670ee9 --- /dev/null +++ b/news-app/assets/images/svgImage/onboarding2.svg @@ -0,0 +1,948 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/onboarding3.svg b/news-app/assets/images/svgImage/onboarding3.svg new file mode 100644 index 00000000..3ea81f5d --- /dev/null +++ b/news-app/assets/images/svgImage/onboarding3.svg @@ -0,0 +1,895 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/phone_button.svg b/news-app/assets/images/svgImage/phone_button.svg new file mode 100644 index 00000000..4f568022 --- /dev/null +++ b/news-app/assets/images/svgImage/phone_button.svg @@ -0,0 +1,3 @@ + + + diff --git a/news-app/assets/images/svgImage/placeholder_video.svg b/news-app/assets/images/svgImage/placeholder_video.svg new file mode 100644 index 00000000..df4b14e3 --- /dev/null +++ b/news-app/assets/images/svgImage/placeholder_video.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/rss_feed.svg b/news-app/assets/images/svgImage/rss_feed.svg new file mode 100644 index 00000000..a9261d31 --- /dev/null +++ b/news-app/assets/images/svgImage/rss_feed.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/searchbar_arrow.svg b/news-app/assets/images/svgImage/searchbar_arrow.svg new file mode 100644 index 00000000..13e706a2 --- /dev/null +++ b/news-app/assets/images/svgImage/searchbar_arrow.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/news-app/assets/images/svgImage/splash_icon.svg b/news-app/assets/images/svgImage/splash_icon.svg new file mode 100644 index 00000000..99ea7b16 --- /dev/null +++ b/news-app/assets/images/svgImage/splash_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/telegram.svg b/news-app/assets/images/svgImage/telegram.svg new file mode 100644 index 00000000..f47bb43e --- /dev/null +++ b/news-app/assets/images/svgImage/telegram.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/whatsapp.svg b/news-app/assets/images/svgImage/whatsapp.svg new file mode 100644 index 00000000..e945a88e --- /dev/null +++ b/news-app/assets/images/svgImage/whatsapp.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/wrteam_logo.svg b/news-app/assets/images/svgImage/wrteam_logo.svg new file mode 100644 index 00000000..25a598ba --- /dev/null +++ b/news-app/assets/images/svgImage/wrteam_logo.svg @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/news-app/assets/languages/jsonData.json b/news-app/assets/languages/jsonData.json new file mode 100644 index 00000000..a417275a --- /dev/null +++ b/news-app/assets/languages/jsonData.json @@ -0,0 +1,244 @@ +{ + "somethingMSg": "Something went wrong. Please try again after some time", + "bookmarkLbl": "Bookmarks", + "loginLbl": "Login", + "welTitle1": "Always Up-to-Date", + "welTitle2": "Bookmark & Share", + "welTitle3": "New Categories", + "welDes1": "Receive notifications for the most recent news updates and many more.", + "welDes2": "Save and easily share news with your friends using our intuitive news app feature.", + "welDes3": "Enjoy expertly tailored news, crafted exclusively for your interests.", + "nameLbl": "Name", + "emailLbl": "Email", + "passLbl": "Password", + "confpassLbl": "Confirm Password", + "priPolicy": "Privacy Policy", + "andLbl": " and ", + "termLbl": "Terms of Service", + "forgotPassLbl": "Forgot Password ?", + "internetmsg": "Internet Connection not available", + "loginMsg": "Login Successfully", + "loginNowLbl": "Login Now", + "logoutLbl": "Logout", + "cancelBtn": "Cancel", + "noNews": "News Not Available", + "exitWR": "Double tap back button to exit", + "shareLbl": "Share", + "deactiveMsg": "You are deactivated by admin", + "bookmarkNotAvail": "Bookmarks Not Available", + "notiNotAvail": "Notifications Not Available", + "notificationLbl": "Notifications", + "logoutTxt": "Are you sure you want to Logout?", + "yesLbl": "Yes", + "noLbl": "No", + "frgtPassHead": "Enter the email address associated with your account", + "forgotPassSub": "We will email you a link to reset your password", + "submitBtn": "Submit", + "verifyEmailMsg": "Please first verify your email address!!!", + "passReset": "Password reset link has been sent to your mail", + "profileUpdateMsg": "Profile Data Updated Successfully", + "bookmarkLogin": "Please Login to Access Your Bookmarks !!", + "preferenceSave": "Your preference saved!!", + "managePreferences": "Manage Preferences", + "loginReqMsg": "Login Required...", + "firstFillData": "Please First Fill Data...!", + "deleteTxt": "Delete", + "reportTxt": "Report", + "nameRequired": "Name is Required", + "nameLength": "Name should be atleast 2 character long", + "emailRequired": "email address is Required", + "emailValid": "Please enter a valid email Address!", + "pwdRequired": "Password is Required", + "confPassRequired": "Confirm Password is Required", + "confPassNotMatch": "Confirm Password not match", + "photoLibLbl": "Photo Library", + "cameraLbl": "Camera", + "verifSentMail": "Verification email sent to ", + "cancelLogin": "Login cancelled by the user.", + "loginTxt": "Log In", + "loginBtn": "Login", + "signupBtn": "Sign Up", + "otpVerifyLbl": "OTP Verification", + "enterMblLbl": "Enter Your Mobile Number", + "receiveDigitLbl": "You'll Receive 6 digit code for phone number verification", + "reqOtpLbl": "Request OTP", + "otpSentLbl": "OTP has been sent to ", + "resendCodeLbl": "Resend Code in", + "mobileLbl": "Mobile", + "darkModeLbl": "Dark Mode", + "changeLang": "Change Language", + "rateUs": "Rate Us", + "shareApp": "Share App", + "weatherLbl": "Weather Forecast", + "categoryLbl": "Categories", + "allLbl": "All", + "comLbl": "Comment ", + "saveLbl": "Save", + "txtSizeLbl": "Text Size", + "speakLoudLbl": "Speak Loud", + "likeLbl": "likes", + "comsLbl": "Comments", + "shareThoghtLbl": "Share Your Thoughts.", + "repliesLbl": "Replies", + "publicReply": "Add a public reply...", + "personalLbl": "Personal", + "newsLbl": "News", + "plzLbl": "Please", + "fastTrendNewsLbl": "Porque Mereces Verdaderas Respuestas! ", + "enterOtpTxt": "Please Enter OTP", + "otpError": "Error validating OTP, try again", + "otpMsg": "OTP verified successfully", + "resendLbl": "Resend OTP", + "otpTimeoutLbl": "Otp Retrieval Timeout!!!", + "mblRequired": "Mobile number is Required", + "mblValid": "Please enter a valid mobile number!", + "codeSent": "Code Sent Successfully!!!", + "relatedNews": "You might also like", + "optSel": "Please Select One Option!!!", + "madeBy": "Made by", + "skip": "Skip", + "nxt": "Next", + "signInTab": "Sign In", + "agreeTermPolicyLbl": "By Logging In, you agree to our", + "addTCFirst": "Please Ask Admin to Add Privacy Policy & Terms and Conditions first !!", + "orLbl": "or Log In with", + "signupDescr": "Create\nan Account", + "firstAccLbl": "First to access", + "allFunLbl": "all Functions", + "chooseLanLbl": "Select Language", + "videosLbl": "Videos", + "search": "Search", + "searchHomeNews": "Search News, Categories, etc.", + "viewMore": "View More", + "viewFullCoverage": "View full Coverage", + "updateName": "Update your Name", + "loginDescr": "Let's Sign \nYou In", + "logoutAcc": "Logout Account", + "deleteAcc": "Delete Account", + "deleteAlertTitle": "Re-Login", + "deleteRelogin": "To Delete your Account, You need to Login again.\nAfter that you will be able to Delete your Account.", + "deleteConfirm": "Are you sure?\nDo You Really Want to Delete Your Account?", + "pwdLength": "Password should be more than 6 character long", + "userNotFound": "No user found for given email.", + "wrongPassword": "Wrong password provided for that user.", + "weakPassword": "The password provided is too weak.", + "emailAlreadyInUse": "The account already exists for that email.", + "invalidPhoneNumber": "The provided phone number is not valid.", + "invalidVerificationCode": "The sms verification code used to create the phone auth credential is invalid.", + "ago": "ago", + "years": "years", + "months": "months", + "minutes": "minutes", + "seconds": "seconds", + "hours": "hours", + "days": "days", + "justNow": "just now", + "about": "about", + "liveVideosLbl": "Live Videos", + "stdPostLbl": "Standard Post", + "videoYoutubeLbl": "Video (Youtube)", + "videoOtherUrlLbl": "Video (Other Url)", + "videoUploadLbl": "Video (Upload)", + "createNewsLbl": "Create News", + "step1Of2Lbl": "Step 1 of 2", + "catLbl": "Category", + "plzSelCatLbl": "Please select category", + "subcatLbl": "SubCategory", + "contentTypeLbl": "Content Type", + "uploadVideoLbl": "Upload Video", + "youtubeUrlLbl": "Youtube Url", + "otherUrlLbl": "Other Url", + "selContentTypeLbl": "Select Content Type", + "titleLbl": "Title", + "tagLbl": "Tag", + "showTilledDate": "Show Till Date", + "uploadMainImageLbl": "Upload Main Image", + "uploadOtherImageLbl": "Upload Other Image", + "plzUploadVideoLbl": "Please upload video!!!", + "plzAddMainImageLbl": "Please add main image!!!", + "selTagLbl": "Select Tag", + "selSubCatLbl": "Select Sub Category", + "selCatLbl": "Select Category", + "editNewsLbl": "Edit News", + "doYouReallyNewsLbl": "Do You Really Want to Delete this News?", + "delNewsLbl": "Delete News", + "newsTitleReqLbl": "News title is required!!!", + "plzAddValidTitleLbl": "Please add valid news title!!!", + "urlReqLbl": "Url is required!!!", + "plzValidUrlLbl": "Please add valid url!!!", + "manageNewsLbl": "Manage News", + "step2of2Lbl": "Step 2 of 2", + "descLbl": "Description", + "RetryLbl": "Retry", + "previewLbl": "Preview", + "sponsoredLbl": "Sponsored", + "searchForLbl": "Search Result for", + "readLessLbl": "Read less", + "readMoreLbl": "Read more", + "myProfile": "My Profile", + "editProfile": "Edit Profile", + "noComments": "Be the First One to Comment !!!", + "minute": "minute", + "read": "read", + "selLocationLbl": "Select Location", + "metaKeywordLbl": "Meta Keyword", + "metaTitleLbl": "Meta Title", + "metaDescriptionLbl": "Meta Description", + "slugLbl": "Slug", + "metaTitleWarningLbl": "Meta Title length should not exceed 60 characters.", + "metaDescriptionWarningLbl": "Meta Description length should between 50 to 160 characters.", + "metaKeywordWarningLbl": "Meta Keywords are not more than 10 keyword phrases & should be comma separated.", + "slugWarningLbl": "Slug only accept lowercase letters, numbers, and hyphens. No spaces or special characters allowed.", + "metaTitleRequired": "Meta title is Required", + "metaDescriptionRequired": "Meta Description is Required", + "metaKeywordRequired": "Meta Keyword is Required", + "slugRequired": "Slug is Required", + "slugValid": "Please enter valid Slug!", + "slugUsedAlready": "This slug is already in use. Please add any other slug.", + "maintenanceMessageLbl": "We're Under Maintenance\nPlease try again later.", + "notificationLogin": "Please Login to view Your Notifications !!", + "publishDate": "Publish Date", + "dateConfirmation": "Please change Show till date , as it can't be set before Publish date", + "expired": "Expired", + "deactivated": "Deactivated", + "clearFilter": "Clear filter", + "FilterBy": "Filter by", + "rssFeed": "RSS Feed", + "profile": "Profile", + "homeLbl": "Home", + "replyLbl": "Reply", + "disabledCommentsMsg": "Comments are disabled for this News by admin", + "last": "Last", + "today": "Today", + "clear": "Clear", + "apply": "Apply", + "date": "Date", + "continueWith": "Continue with", + "google": "Google", + "apple": "Apple", + "fb": "Facebook", + "didntGetCode": "Didn't get Code?", + "viewsLbl": "Views", + "recentVidLbl": "Recent News Videos", + "forceUpdateTitleLbl": "Update Required", + "newVersionAvailableTitleLbl": "New Version Available !!!", + "newVersionAvailableDescLbl": "Would you like to update now ?", + "forceUpdateDescLbl": "A new version of this app is available with important improvements and features. To continue using the app, please update to the latest version.", + "exitLbl": "Exit", + "newsCreatedSuccessfully": "Your news has been created successfully! It will be visible to others after admin approval.", + "summarizedDescription": "Summarized Description", + "clickSummarizeDescription": "Click 'Summarize Description' to generate a summary of your content", + "enterDescriptionFirst": "Add description First to generate Summarized Description", + "authorReviewPendingLbl": "Pending Review", + "becomeAuthorLbl": "Become an Author", + "authorLbl": "Author", + "saveAsDraftLbl": "Save As Draft", + "manageNewsAllLbl": "All News", + "manageNewsDraftLbl": "Draft News", + "publishBtnLbl": "Publish", + "addYourBioHintLbl": "Add your bio", + "addLinkHereHintLbl": "Add {type} Link here", + "noteForAuthorLbl": "You can add social media links & Bio only if you are the author.", + "socialMediaLinksLbl": "Social media links", + "followLbl": "Follow :" +} \ No newline at end of file diff --git a/news-app/devtools_options.yaml b/news-app/devtools_options.yaml new file mode 100644 index 00000000..fa0b357c --- /dev/null +++ b/news-app/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/news-app/ios/Flutter/AppFrameworkInfo.plist b/news-app/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..c95f1736 --- /dev/null +++ b/news-app/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 15.0 + + diff --git a/news-app/ios/Flutter/Debug.xcconfig b/news-app/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..ec97fc6f --- /dev/null +++ b/news-app/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/news-app/ios/Flutter/Release.xcconfig b/news-app/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..aa0ffe9d --- /dev/null +++ b/news-app/ios/Flutter/Release.xcconfig @@ -0,0 +1,3 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +//#include "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig" +#include "Generated.xcconfig" diff --git a/news-app/ios/Podfile b/news-app/ios/Podfile new file mode 100644 index 00000000..5c0aae9f --- /dev/null +++ b/news-app/ios/Podfile @@ -0,0 +1,46 @@ +# Uncomment this line to define a global platform for your project + platform :ios, '15.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' + end + end +end diff --git a/news-app/ios/Runner.xcodeproj/project.pbxproj b/news-app/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..f7d9cc32 --- /dev/null +++ b/news-app/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,624 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 6D5AA4DC1D9BE0D29BBC3D47 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8406A87EDAB3F42C9E9C8625 /* Pods_Runner.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + C64F89812BEA00CF00945F40 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = C64F89802BEA00CF00945F40 /* PrivacyInfo.xcprivacy */; }; + C6B156C729ED4959004F7E4D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C6B156C629ED4959004F7E4D /* GoogleService-Info.plist */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3EB85B3888093677A730AD02 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 62A0D45B537B579D78E7A373 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8406A87EDAB3F42C9E9C8625 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 88E5CF1A7479A9215E46F87B /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C64F89802BEA00CF00945F40 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + C6B156C629ED4959004F7E4D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + C739818628E4631E00744836 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6D5AA4DC1D9BE0D29BBC3D47 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 541E6B33177097DE8D31256D /* Pods */ = { + isa = PBXGroup; + children = ( + 62A0D45B537B579D78E7A373 /* Pods-Runner.debug.xcconfig */, + 88E5CF1A7479A9215E46F87B /* Pods-Runner.release.xcconfig */, + 3EB85B3888093677A730AD02 /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 541E6B33177097DE8D31256D /* Pods */, + CC2378898FAFB2F8CCB774D0 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + C64F89802BEA00CF00945F40 /* PrivacyInfo.xcprivacy */, + C6B156C629ED4959004F7E4D /* GoogleService-Info.plist */, + C739818628E4631E00744836 /* Runner.entitlements */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + CC2378898FAFB2F8CCB774D0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8406A87EDAB3F42C9E9C8625 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 11FA84E1B6E74DCAB7043F8B /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 66D5C516A2D49648D98696C5 /* [CP] Embed Pods Frameworks */, + 7452FB0F6FB0AC1495261F6F /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C64F89812BEA00CF00945F40 /* PrivacyInfo.xcprivacy in Resources */, + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + C6B156C729ED4959004F7E4D /* GoogleService-Info.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 11FA84E1B6E74DCAB7043F8B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 66D5C516A2D49648D98696C5 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 7452FB0F6FB0AC1495261F6F /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 32; + DEVELOPMENT_TEAM = 89C47N4UTZ; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "elCaribe"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = YOUR_PACKAGE_NAME_HERE; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 32; + DEVELOPMENT_TEAM = 89C47N4UTZ; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "elCaribe"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = YOUR_PACKAGE_NAME_HERE; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 32; + DEVELOPMENT_TEAM = 89C47N4UTZ; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "elCaribe"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = YOUR_PACKAGE_NAME_HERE; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/news-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/news-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/news-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/news-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/news-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/news-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/news-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/news-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/news-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/news-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/news-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..9c12df59 --- /dev/null +++ b/news-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/ios/Runner.xcworkspace/contents.xcworkspacedata b/news-app/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/news-app/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/news-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/news-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/news-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/news-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/news-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/news-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/news-app/ios/Runner/AppDelegate.swift b/news-app/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..d97a1839 --- /dev/null +++ b/news-app/ios/Runner/AppDelegate.swift @@ -0,0 +1,71 @@ +import UIKit +import Flutter +import Firebase +import AppTrackingTransparency +import GoogleMobileAds + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + FirebaseApp.configure() + GeneratedPluginRegistrant.register(with: self) + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate + } + + // Disables Publisher first-party ID + MobileAds.shared.requestConfiguration.setPublisherFirstPartyIDEnabled(false) + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + override func application( + _ application: UIApplication, + open url: URL, + options: [UIApplication.OpenURLOptionsKey : Any] = [:] + ) -> Bool { + let urlString = url.absoluteString + return true + } + + override func applicationDidBecomeActive(_ application: UIApplication) { + if #available(iOS 15.0, *) { + ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in + // Tracking authorization completed. Start loading ads here. + // loadAd + }) + } + } + } +class AppLinks { + + var window: UIWindow? + + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + return handleDeepLink(url: url) + } + + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + if userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let url = userActivity.webpageURL { + return handleDeepLink(url: url) + } + return false + } + + private func handleDeepLink(url: URL) -> Bool { + guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { + return false + } + + if let urlPattern = components.path.split(separator: "/").last { + return true + } + + return false + } + +} diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..53611299 --- /dev/null +++ b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "filename" : "Icon-App-20x20@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-20x20@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-29x29@1x.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-40x40@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-40x40@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-60x60@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "Icon-App-60x60@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "Icon-App-20x20@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-20x20@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-29x29@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-40x40@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-40x40@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-76x76@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "Icon-App-76x76@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "Icon-App-83.5x83.5@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "Icon-App-1024x1024@1x.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..f8454df21c9ea068f817eff18146509a198e5904 GIT binary patch literal 79297 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 literal 0 HcmV?d00001 diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dd91ab16043cddfae91f502bda29e679e47bcb1a GIT binary patch literal 2815 zcmbVO3vd)w79Fz5LI?tpPa&&IJBq0AroYoY-DVQP%!CA)NIrx_lHK&@^<*|P)8lj} znS>Q;ahIR4VpgJtL|KYZAORI4TE%x^x3mh11w|vf8-%j(xBP#C0o~U#nIXC$DW|HZ zUw5B(?!D*U``%kRt8n^VL&p!bSS)uHKn-maLp;Sd?p_fl1Kk4>_%8x9_%Me#L2> zWA_r?unX4s^P3{Dq^Yn}ZmN}e#hR0iWkm%<5QK(^MT3EmE<~Ny7_WfF=4;%F#UMtl z)0%4<#LB(1FqamA7;hthOb`Ud@ivkpWr|a$U=%@7IN`ua7LX2spah1-`hHeqE~2PH zu{*EN7|oFvN?YuGK|k=&46; zK43-@7F-c18d{`O(*lW1%}Th8xm?)8bNnGiYtSF*2ZwIafKDsQ83718pomhE5=c^@ z>B)p$pouutt0{i9F$tvrNdby7p>|fF`5!=0#VDd7-Vvu1YW?mEX6*Qk@S(YX#2xy5TfXXri;3e7)XiBCSR+1P+Q4{l@i1<-s ziGiQZgX;fggb9{h6p@Y+r}uToG||^L3iz|_oF|VXSci+VdnkrtN!GhEJp@E`OD&Q1G1w2$0(?gb}B%0?%<^Q?N zWBOxIMSL5#p$AFAm) zv^;)0*LAV}ryXZWJExE`DjJm(0i`mi%M?xmP?eDMl-*(H`9$FT9cKZ>my&jYai9ey zu|y2iaf%@M{tH%oo?Ih_d=PDDxb^49Gq45YM~S%Ik~g4uD8@$KF_>6w#!C$UarMQ% z_1p4LE@Q9IK57n=J2-kso&jJ8=@dab{F|#bccC*yw7{KP>R8c!Y@uWC%0k(Tk{x-riVO>gcz(u@%cv)q43ZK*5Tx^{0UGuOR%xHYpP?|8@X+)0lN z`PMbEC^d6Q>-LI`n>L*~aO&W^i^5A~)iZi`W@qOd>%B2QtGV;vuE5~$C){&rHr0M0 zHwA0){H;7RZu#MRCt9Xfoop+9cT1bMpnKx-=XZ>>0igz+s`~WKPNi&>4xf${_*?jL*4&b6X-dx?IYWf`?mc_-}-cL{;s-9?~Y|2 zoT_d+wQ|&`-+A9Y_C`jk6jefzX6E$pusHGVy=R|mJNxGatH0jOUA=U&clFK}<-=>g zy0Q3$FYq_ERHjX(DnhOFYU!1~WS;*c%WnDP%Ju7&Cr*@oy6Cme{o8Go^Bjl1xze+# zylaSM^XvMAob!W5Tb8!G->q%+&TE|RtG<}`v0k|0(ZHmARq0om%bz?y7IR*$$)Axv zb;`E6X;{@9a(TF-W4XKYMCSgB*VEFPJf3gQJac^Mp6NY1A~)*0zj}7_;PL7Ar{s@t zW-Q*f``EDkDHc!eL!Ygfw2m$i1ka6n{N-Wy1+MM6F!H(XvQ>Cju?Zv8(y5 zJrkn-5BBN{7oF~|?$~&6nXUcu>Dqcrtuf`J1+V^QXVJK&=hh6W`t2_tpE2gWqRPV3 zRg-)l-1FXw*-!0Tc|P@5*Y19@?} Ce$_hw literal 0 HcmV?d00001 diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..07e6271d1b9e6c65eb7a2c632dbe2cb892a13eac GIT binary patch literal 3526 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 PpH@^@bm*RtwAKFu{yZes literal 0 HcmV?d00001 diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..eb4993b7b0977a1b9f797477c0e7d82919ba6a50 GIT binary patch literal 4243 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 LcZUo1>wo+wurHMf literal 0 HcmV?d00001 diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..060886bcd89fc00fb999143c872dc58c3556b8f2 GIT binary patch literal 3153 zcmbVO3se(V8jhj}Dz;W_0ddVqX~oB6o|$Au0}F(QNI($}XtiZB4;V=@F%zCXu(nzi zu}Uqrm98F(T1#EgRS;=~Z53NBRS#NZMSM`rqV-YMbw#c2O@L@yt(@kZ$=sRy-S7Xu z|8eiEn3@pdH!yIZLZR@R9BW7d_ff9Pr!V+#{`qDqxcS;*XUYo2pkc17kK&_Je}%$l zoS2;INHxaOtYlF$oMhtFxfUBhD-@H$a%~Ko$vc!LK3%lxRX1vCRZ5Z5t5P&Z#Au7; zGsM_@J3l=?A(_q3WGPM+7OI?-O9KK6?_iX<7PD2RbM-1WFAc`7*RV?IhBz|ys&JP< zWvX$iGE%bhN=l7DEP^0PEu}`aD2r)@2}%sXFc{InC<&oD8o_7+S9bqYz?_{E=p;jQ zw=wXfS7kUHHX4R=a&pu;xLUHO!ze{jFoMAt1_1;l=UE+0E@YKo?qM+SGHVxY4pFiy zU5t!L%5vycz|}4d7Mq9GDtE^TL=4VlY%r=uT$o53u=@&QONT^F$l#V40Ay>B#lwOf`W?S7zguAFvrq@WVbLtu4rM>dDv!6 zS1CO~q$4G>WCw`iX2U7NSsGl3Su=V3LzLlLMDM= zAqGP+0wpmmN0@rn8zeT%mDis299YkiT2|1YS_tK|JVcU&0GSvDhggyn2%h2zlqY)D zC)!2OSd95k^MLw)q_Br294L}O;^gk`h+xvYM`kfpqm7EzBBU--tBJx0j6}&u3X3G7 z@hB}4K@icCU|0DAU{_~>L09u~5(~sVGKM1=7WV94+B5;TkFaZL|5+I5fIG2Yc ziJ5SUVz}RPxsvZn9%Z6%mSHG}CpjE4X+Yj7u&+Ug!0ITDqcn(C_t7b^c(em)qT$h>bKX#lXP(;vy$`MQq*wS6u)?qvk=}ZLZ5ncfEph14` zI0<1?GOD2oUC;i|Tdy$$r5@j{y4JB7j5VDHrx~pJ^J03pTl_}M)AqYw>A?s!`b;tI z!tAOq__w9!o_p-P1BrCM0*9z;=sCW@gXaT)w}Lp?!SQ}ceFS`0cr{EmgeS+YxK=VH ze)bIi!w;P;c#d#=@J`0tf9}fTQH9are_gUA!V>sWa?*}>{k9w^pU2D{R)mHd_k_N% zAtW%AzLsjXy5D?7?Rj_^+mz4As1Yo334L)*!InLYwvo0+V9|1sbJrpkQH0AudVJ>ZoaTz>CgZ9=cqGHi#o;Dmei^XA3Fasv8CV( z?5lq;1^4b+>f>>bCtk)~i3|GdCJ{Kz@0PnalPuF%fGkL<9b=cc`NLH5K-dVrB)$2<2wCv1LWrNbt=Tc4t z)YaK8^wm3R@BL7IkWz^L{ZE~e-?qPcYW#qsr~enGc1%j0Gjy+4;POpk(`EIGjcvOe za}tARFCNOxN*TG~=(&P1`^2{5F}(kK?}S$6w|?Hyna7`OsOspL-B)*h9{<2+cCNjx zZT~v!{PDG)tR`x4JadTOy<_)-z7(OQaY1KZH-B)c`r+{D!Wmea88nUH-879QE&SXt?A-pzEW5a#Vuh?-6NB{tx3Bj5YuO literal 0 HcmV?d00001 diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..24c21081af588a2b0d9dd912eec4730c31e7ac6c GIT binary patch literal 4137 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 literal 0 HcmV?d00001 diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..0d685edafe1947ce49e400f14dc7b6ae4ca939e6 GIT binary patch literal 6700 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 Z&9HpZiPeHh;>Q0uhPo!Cf}^J*{s(suUs3=7 literal 0 HcmV?d00001 diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e5d6d49cd0a4a0e627abfb78a8a0cd23cac0fa03 GIT binary patch literal 6700 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%0DBthG@I2IoZ8x?rIrheRX&4i7=VJ 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 literal 0 HcmV?d00001 diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..29fabb69cb65af1c755db44c7cf20e8b41d2f351 GIT binary patch literal 9905 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 Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/news-app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/news-app/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/news-app/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/ios/Runner/Base.lproj/Main.storyboard b/news-app/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/news-app/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/ios/Runner/Info.plist b/news-app/ios/Runner/Info.plist new file mode 100644 index 00000000..f8824ccb --- /dev/null +++ b/news-app/ios/Runner/Info.plist @@ -0,0 +1,138 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + elCaribe + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + elCaribe + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + fb3187011931582004 + + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.googleusercontent.apps.1085975883181-q56lvc3nb2j3lc2rpkl9sv0ve6sf12hn + + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + news + + + + CFBundleURLName + YOUR_PACKAGE_NAME_HERE + CFBundleURLSchemes + + news + + + + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + FacebookAppID + 3187011931582004 + FacebookClientToken + c5fcb2473b8557c9ff049dd807db8240 + FacebookDisplayName + news + GADApplicationIdentifier + ca-app-pub-3940256099942544~1458002511 + LSApplicationQueriesSchemes + + https + http + + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsArbitraryLoadsInWebContent + + + NSCameraUsageDescription + Allow Camera access to upload photo from your device to set Profile Picture & add images in News + NSLocationAlwaysAndWhenInUseUsageDescription + Your location is required to get current weather of your location + NSLocationAlwaysUsageDescription + We kindly request your location to provide you with relevant local news. + NSLocationWhenInUseUsageDescription + To ensure personalized news, please share your location with us. + NSMicrophoneUsageDescription + Allow microphone access to listen to news content + NSPhotoLibraryUsageDescription + Allow library access to upload photo from your device to set Profile Picture & add images in News + NSUserTrackingUsageDescription + This identifier will be used to deliver personalized ads to you. + SKAdNetworkItems + + + SKAdNetworkIdentifier + cstr6suwn9.skadnetwork + + + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + fetch + remote-notification + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UIViewControllerBasedStatusBarAppearance + + io.flutter.embedded_views_preview + + setAdvertiserTrackingEnabled + + ITSAppUsesNonExemptEncryption + + + + diff --git a/news-app/ios/Runner/PrivacyInfo.xcprivacy b/news-app/ios/Runner/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..1f8b2799 --- /dev/null +++ b/news-app/ios/Runner/PrivacyInfo.xcprivacy @@ -0,0 +1,91 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePreciseLocation + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePhotosorVideos + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePhoneNumber + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising + App Functionality +App Functionality +App Functionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeEmailAddress + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeName + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + C56D.1 + + + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + + + diff --git a/news-app/ios/Runner/Runner-Bridging-Header.h b/news-app/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..4a45a93f --- /dev/null +++ b/news-app/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1,2 @@ +#import "GeneratedPluginRegistrant.h" + diff --git a/news-app/ios/Runner/Runner.entitlements b/news-app/ios/Runner/Runner.entitlements new file mode 100644 index 00000000..08046e6f --- /dev/null +++ b/news-app/ios/Runner/Runner.entitlements @@ -0,0 +1,18 @@ + + + + + aps-environment + development + com.apple.developer.applesignin + + Default + + com.apple.developer.associated-domains + + applinks:enter_your_website_url_here + + com.apple.developer.usernotifications.time-sensitive + + + diff --git a/news-app/lib/app/app.dart b/news-app/lib/app/app.dart new file mode 100644 index 00000000..35bd8ca3 --- /dev/null +++ b/news-app/lib/app/app.dart @@ -0,0 +1,258 @@ +import 'dart:io'; +import 'dart:ui'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:hive_flutter/adapters.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:intl/intl.dart' as intl; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/AddNewsCubit.dart'; +import 'package:news/cubits/Auth/deleteUserCubit.dart'; +import 'package:news/cubits/Auth/registerTokenCubit.dart'; +import 'package:news/cubits/Auth/updateUserCubit.dart'; +import 'package:news/cubits/Author/authorCubit.dart'; +import 'package:news/cubits/Author/authorNewsCubit.dart'; +import 'package:news/cubits/Bookmark/UpdateBookmarkCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/cubits/ConnectivityCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart'; +import 'package:news/cubits/NewsComment/deleteCommentCubit.dart'; +import 'package:news/cubits/NewsComment/flagCommentCubit.dart'; +import 'package:news/cubits/NewsComment/likeAndDislikeCommCubit.dart'; +import 'package:news/cubits/NewsComment/setCommentCubit.dart'; +import 'package:news/cubits/UserPreferences/setUserPreferenceCatCubit.dart'; +import 'package:news/cubits/adSpacesNewsDetailsCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/breakingNewsCubit.dart'; +import 'package:news/cubits/commentNewsCubit.dart'; +import 'package:news/cubits/deleteImageId.dart'; +import 'package:news/cubits/deleteUserNewsCubit.dart'; +import 'package:news/cubits/generalNewsCubit.dart'; +import 'package:news/cubits/getSurveyAnswerCubit.dart'; +import 'package:news/cubits/getUserDataByIdCubit.dart'; +import 'package:news/cubits/getUserNewsCubit.dart'; +import 'package:news/cubits/languageCubit.dart'; +import 'package:news/cubits/locationCityCubit.dart'; +import 'package:news/cubits/privacyTermsCubit.dart'; +import 'package:news/cubits/relatedNewsCubit.dart'; +import 'package:news/cubits/rssFeedCubit.dart'; +import 'package:news/cubits/sectionByIdCubit.dart'; +import 'package:news/cubits/setNewsViewsCubit.dart'; +import 'package:news/cubits/setSurveyAnswerCubit.dart'; +import 'package:news/cubits/settingCubit.dart'; +import 'package:news/cubits/Auth/socialSignUpCubit.dart'; +import 'package:news/cubits/UserPreferences/userByCategoryCubit.dart'; +import 'package:news/cubits/slugCheckCubit.dart'; +import 'package:news/cubits/slugNewsCubit.dart'; +import 'package:news/cubits/tagCubit.dart'; +import 'package:news/cubits/tagNewsCubit.dart'; +import 'package:news/cubits/NewsByIdCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.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/subCategoryCubit.dart'; +import 'package:news/cubits/surveyQuestionCubit.dart'; +import 'package:news/cubits/themeCubit.dart'; +import 'package:news/cubits/updateBottomsheetContentCubit.dart'; +import 'package:news/cubits/videosCubit.dart'; +import 'package:news/cubits/weatherCubit.dart'; +import 'package:news/cubits/GetUserDraftedNewsCubit.dart'; +import 'package:news/data/repositories/AddNews/addNewsRepository.dart'; +import 'package:news/data/repositories/Auth/authRepository.dart'; +import 'package:news/data/repositories/Bookmark/bookmarkRepository.dart'; +import 'package:news/data/repositories/BreakingNews/breakNewsRepository.dart'; +import 'package:news/data/repositories/CommentNews/commNewsRepository.dart'; +import 'package:news/data/repositories/DeleteImageId/deleteImageRepository.dart'; +import 'package:news/data/repositories/DeleteUserNews/deleteUserNewsRepository.dart'; +import 'package:news/data/repositories/GetSurveyAnswer/getSurveyAnsRepository.dart'; +import 'package:news/data/repositories/GetUserById/getUserByIdRepository.dart'; +import 'package:news/data/repositories/GetUserNews/getUserNewsRepository.dart'; +import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart'; +import 'package:news/data/repositories/NewsComment/DeleteComment/deleteCommRepository.dart'; +import 'package:news/data/repositories/NewsComment/FlagComment/flagCommRepository.dart'; +import 'package:news/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart'; +import 'package:news/data/repositories/NewsComment/SetComment/setComRepository.dart'; +import 'package:news/data/repositories/RelatedNews/relatedNewsRepository.dart'; +import 'package:news/data/repositories/SectionById/sectionByIdRepository.dart'; +import 'package:news/data/repositories/SetNewsViews/setNewsViewsRepository.dart'; +import 'package:news/data/repositories/SetSurveyAnswer/setSurveyAnsRepository.dart'; +import 'package:news/data/repositories/SetUserPreferenceCat/setUserPrefCatRepository.dart'; +import 'package:news/data/repositories/SurveyQuestion/surveyQueRepository.dart'; +import 'package:news/data/repositories/Tag/tagRepository.dart'; +import 'package:news/data/repositories/TagNews/tagNewsRepository.dart'; +import 'package:news/data/repositories/UserByCategory/userByCatRepository.dart'; +import 'package:news/data/repositories/language/languageRepository.dart'; +import 'package:news/data/repositories/Settings/settingRepository.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/data/repositories/AppSystemSetting/systemRepository.dart'; +import 'package:news/data/repositories/Category/categoryRepository.dart'; +import 'package:news/data/repositories/FeatureSection/sectionRepository.dart'; +import 'package:news/data/repositories/LanguageJson/languageJsonRepository.dart'; +import 'package:news/data/repositories/LiveStream/liveStreamRepository.dart'; +import 'package:news/data/repositories/NewsById/NewsByIdRepository.dart'; +import 'package:news/data/repositories/OtherPages/otherPagesRepository.dart'; +import 'package:news/data/repositories/SubCategory/subCatRepository.dart'; +import 'package:news/data/repositories/Videos/videosRepository.dart'; +import 'package:news/ui/screens/PushNotificationService.dart'; +import 'package:news/ui/styles/appTheme.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +late PackageInfo packageInfo; + +Future initializeApp() async { + WidgetsFlutterBinding.ensureInitialized(); + HttpOverrides.global = MyHttpOverrides(); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + + MobileAds.instance.initialize(); + + packageInfo = await PackageInfo.fromPlatform(); + + await Firebase.initializeApp(); + + await Hive.initFlutter(); + + await Hive.openBox(authBoxKey); + + await Hive.openBox(settingsBoxKey); + + await Hive.openBox(locationCityBoxKey); + await Hive.openBox(videoPreferenceKey); + + runApp(MultiBlocProvider(providers: [ + BlocProvider(create: (_) => ConnectivityCubit(ConnectivityService())), + BlocProvider(create: (context) => AppConfigurationCubit(SystemRepository())), + BlocProvider(create: (_) => SettingsCubit(SettingsRepository())), + BlocProvider(create: (_) => AppLocalizationCubit(SettingsLocalDataRepository())), + BlocProvider(create: (_) => ThemeCubit(SettingsLocalDataRepository())), + BlocProvider(create: (_) => LanguageJsonCubit(LanguageJsonRepository())), + BlocProvider(create: (context) => LanguageCubit(LanguageRepository())), + BlocProvider(create: (_) => SectionCubit(SectionRepository())), + BlocProvider(create: (_) => PrivacyTermsCubit(OtherPageRepository())), + BlocProvider(create: (_) => VideoCubit(VideoRepository())), + BlocProvider(create: (_) => NewsByIdCubit(NewsByIdRepository())), + BlocProvider(create: (_) => OtherPageCubit(OtherPageRepository())), + BlocProvider(create: (_) => LiveStreamCubit(LiveStreamRepository())), + BlocProvider(create: (_) => CategoryCubit(CategoryRepository())), + BlocProvider(create: (_) => SubCategoryCubit(SubCategoryRepository())), + BlocProvider(create: (_) => SurveyQuestionCubit(SurveyQuestionRepository())), + BlocProvider(create: (_) => SetSurveyAnsCubit(SetSurveyAnsRepository())), + BlocProvider(create: (_) => GetSurveyAnsCubit(GetSurveyAnsRepository())), + BlocProvider(create: (_) => CommentNewsCubit(CommentNewsRepository())), + BlocProvider(create: (_) => RelatedNewsCubit(RelatedNewsRepository())), + BlocProvider(create: (_) => SocialSignUpCubit(AuthRepository())), + BlocProvider(create: (_) => AuthCubit(AuthRepository())), + BlocProvider(create: (_) => RegisterTokenCubit(AuthRepository())), + BlocProvider(create: (_) => UserByCatCubit(UserByCatRepository())), + BlocProvider(create: (_) => SetUserPrefCatCubit(SetUserPrefCatRepository())), + BlocProvider(create: (_) => UpdateUserCubit(AuthRepository())), + BlocProvider(create: (_) => DeleteUserCubit(AuthRepository())), + BlocProvider(create: (_) => BookmarkCubit(BookmarkRepository())), + BlocProvider(create: (_) => UpdateBookmarkStatusCubit(BookmarkRepository())), + BlocProvider(create: (_) => LikeAndDisLikeCubit(LikeAndDisLikeRepository())), + BlocProvider(create: (_) => UpdateLikeAndDisLikeStatusCubit(LikeAndDisLikeRepository())), + BlocProvider(create: (_) => BreakingNewsCubit(BreakingNewsRepository())), + BlocProvider(create: (_) => TagNewsCubit(TagNewsRepository())), + BlocProvider(create: (_) => SetCommentCubit(SetCommentRepository())), + BlocProvider(create: (_) => LikeAndDislikeCommCubit(LikeAndDislikeCommRepository())), + BlocProvider(create: (_) => DeleteCommCubit(DeleteCommRepository())), + BlocProvider(create: (_) => SetFlagCubit(SetFlagRepository())), + BlocProvider(create: (_) => AddNewsCubit(AddNewsRepository())), + BlocProvider(create: (_) => TagCubit(TagRepository())), + BlocProvider(create: (_) => GetUserNewsCubit(GetUserNewsRepository())), + BlocProvider(create: (_) => DeleteUserNewsCubit(DeleteUserNewsRepository())), + BlocProvider(create: (_) => DeleteImageCubit(DeleteImageRepository())), + BlocProvider(create: (_) => GetUserByIdCubit(GetUserByIdRepository())), + BlocProvider(create: (_) => SectionByIdCubit(SectionByIdRepository())), + BlocProvider(create: (_) => SetNewsViewsCubit(SetNewsViewsRepository())), + BlocProvider(create: (_) => AdSpacesNewsDetailsCubit()), + BlocProvider(create: (_) => LocationCityCubit()), + BlocProvider(create: (_) => BottomSheetCubit()), + BlocProvider(create: (_) => SlugCheckCubit()), + BlocProvider(create: (_) => GeneralNewsCubit()), + BlocProvider(create: (_) => RSSFeedCubit()), + BlocProvider(create: (_) => SlugNewsCubit()), + BlocProvider(create: (_) => WeatherCubit()), + BlocProvider(create: (_) => AuthorCubit()), + BlocProvider(create: (_) => AuthorNewsCubit()), + BlocProvider(create: (_) => GetUserDraftedNewsCubit()) + ], child: const MyApp())); +} + +class GlobalScrollBehavior extends ScrollBehavior { + @override + ScrollPhysics getScrollPhysics(BuildContext context) { + return const BouncingScrollPhysics(); + } +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + void initState() { + final pushNotificationService = PushNotificationService(context: context); + pushNotificationService.initialise(); + var brightness = PlatformDispatcher.instance.platformBrightness; + if (SettingsLocalDataRepository().getCurrentTheme().isEmpty) { + (brightness == Brightness.dark) ? context.read().changeTheme(AppTheme.Dark) : context.read().changeTheme(AppTheme.Light); + } + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Builder(builder: (context) { + if (Hive.box(settingsBoxKey).get(currentLanguageCodeKey) != null || Hive.box(settingsBoxKey).get(currentLanguageCodeKey) != "") { + initializeDateFormatting(); + intl.Intl.defaultLocale = Hive.box(settingsBoxKey).get(currentLanguageCodeKey); //set default Locale @Start + } + final currentTheme = context.watch().state.appTheme; + return BlocBuilder( + builder: (context, state) { + return MaterialApp( + navigatorKey: UiUtils.rootNavigatorKey, + theme: appThemeData[currentTheme], + debugShowCheckedModeBanner: false, + initialRoute: Routes.splash, + title: appName, + onGenerateRoute: Routes.onGenerateRouted, + builder: (context, widget) { + return ScrollConfiguration( + behavior: GlobalScrollBehavior(), + child: Directionality( + textDirection: state.isRTL == '' || state.isRTL == 0 ? TextDirection.ltr : TextDirection.rtl, + child: Padding( + padding: EdgeInsets.only(bottom: MediaQuery.viewPaddingOf(context).bottom), + child: widget!, + ))); + }); + }, + ); + }); + } +} + +class MyHttpOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext? context) { + return super.createHttpClient(context)..badCertificateCallback = (X509Certificate cert, String host, int port) => true; + } +} diff --git a/news-app/lib/app/routes.dart b/news-app/lib/app/routes.dart new file mode 100644 index 00000000..f5e8577b --- /dev/null +++ b/news-app/lib/app/routes.dart @@ -0,0 +1,203 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:news/ui/screens/AddEditNews/AddNews.dart'; +import 'package:news/ui/screens/BookmarkScreen.dart'; +import 'package:news/ui/screens/ImagePreviewScreen.dart'; +import 'package:news/ui/screens/LiveStreaming.dart'; +import 'package:news/ui/screens/NewsDetail/NewsDetailScreen.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/ShowMoreNewsList.dart'; +import 'package:news/ui/screens/PrivacyPolicyScreen.dart'; +import 'package:news/ui/screens/Profile/userProfile.dart'; +import 'package:news/ui/screens/RSSFeedDetailsScreen.dart'; +import 'package:news/ui/screens/Search.dart'; +import 'package:news/ui/screens/AddEditNews/ManageUserNews.dart'; +import 'package:news/ui/screens/SubCategory/SubCategoryScreen.dart'; +import 'package:news/ui/screens/TagNewsScreen.dart'; +import 'package:news/ui/screens/Videos/videoDetailsScreen.dart'; +import 'package:news/ui/screens/auth/ForgotPassword.dart'; +import 'package:news/ui/screens/auth/RequestOtpScreen.dart'; +import 'package:news/ui/screens/auth/VerifyOtpScreen.dart'; +import 'package:news/ui/screens/authorDetailsScreen.dart'; +import 'package:news/ui/screens/dashBoard/dashBoardScreen.dart'; +import 'package:news/ui/screens/introSlider.dart'; +import 'package:news/ui/screens/languageList.dart'; +import 'package:news/ui/screens/maintenanceScreen.dart'; +import 'package:news/ui/screens/splashScreen.dart'; +import 'package:news/ui/screens/ManagePreference.dart'; +import 'package:news/ui/screens/SectionMoreNews/SectionMoreBreakNewsList.dart'; +import 'package:news/ui/screens/SectionMoreNews/SectionMoreNewsList.dart'; +import 'package:news/ui/screens/auth/loginScreen.dart'; +import 'package:news/ui/widgets/loadingScreen.dart'; + +class Routes { + static const String splash = "splash"; + static const String home = "/"; + static const String introSlider = "introSlider"; + static const String languageList = "languageList"; + static const String login = "login"; + static const String privacy = "privacy"; + static const String search = "search"; + static const String live = "live"; + static const String subCat = "subCat"; + static const String requestOtp = "requestOtp"; + static const String verifyOtp = "verifyOtp"; + static const String managePref = "managePref"; + static const String newsVideo = "newsVideo"; + static const String bookmark = "bookmark"; + static const String newsDetails = "newsDetails"; + static const String imagePreview = "imagePreview"; + static const String tagScreen = "tagScreen"; + static const String addNews = "AddNews"; + static const String editNews = "editNews"; + static const String manageUserNews = "showNews"; + static const String forgotPass = "forgotPass"; + static const String sectionNews = "sectionNews"; + static const String sectionBreakNews = "sectionBreakNews"; + static const String showMoreRelatedNews = "showMoreRelatedNews"; + static const String editUserProfile = "editUserProfile"; + static const String maintenance = "maintenance"; + static const String rssFeedDetails = "rssFeedDetails"; + static const String authorDetails = "authorDetails"; + + static String currentRoute = splash; + static String previousRoute = ""; + + static Route onGenerateRouted(RouteSettings routeSettings) { + previousRoute = currentRoute; + currentRoute = routeSettings.name ?? ""; + + if (routeSettings.name!.contains('/news/') || routeSettings.name!.contains('/breaking-news/') || routeSettings.name!.contains('/video-news/')) { + final String? currNewsSlug = routeSettings.name!.split('/').last.split('?language_id').first; + + if (previousRoute == splash) { + //app is closed + isShared = true; + + Uri uri = Uri.parse(routeSettings.name!); + String path = uri.path; + routeSettingsName = path; + newsSlug = currNewsSlug; + return CupertinoPageRoute(builder: (_) => const Splash()); + } else { + //app is running + return CupertinoPageRoute(builder: (_) => LoadingScreen(routeSettingsName: routeSettings.name!, newsSlug: currNewsSlug ?? "")); + } + } + switch (routeSettings.name) { + case splash: + { + return CupertinoPageRoute(builder: (_) => const Splash()); + } + case home: + { + return DashBoard.route(routeSettings); + } + case introSlider: + { + return CupertinoPageRoute(builder: (_) => const IntroSliderScreen()); + } + case login: + { + return LoginScreen.route(routeSettings); + } + case languageList: + { + return LanguageList.route(routeSettings); + } + case privacy: + { + return PrivacyPolicy.route(routeSettings); + } + case search: + { + return CupertinoPageRoute(builder: (_) => const Search()); + } + case live: + { + return LiveStreaming.route(routeSettings); + } + case subCat: + { + return SubCategoryScreen.route(routeSettings); + } + case requestOtp: + { + return CupertinoPageRoute(builder: (_) => const RequestOtp()); + } + case verifyOtp: + { + return VerifyOtp.route(routeSettings); + } + case managePref: + { + return ManagePref.route(routeSettings); + } + case newsVideo: + { + return VideoDetailsScreen.route(routeSettings); + } + case bookmark: + { + return CupertinoPageRoute(builder: (_) => const BookmarkScreen()); + } + case newsDetails: + { + return NewsDetailScreen.route(routeSettings); + } + case imagePreview: + { + return ImagePreview.route(routeSettings); + } + case tagScreen: + { + return NewsTag.route(routeSettings); + } + case addNews: + { + return AddNews.route(routeSettings); + } + case manageUserNews: + { + return CupertinoPageRoute(builder: (_) => const ManageUserNews()); + } + case forgotPass: + { + return CupertinoPageRoute(builder: (_) => const ForgotPassword()); + } + + case sectionNews: + { + return SectionMoreNewsList.route(routeSettings); + } + case sectionBreakNews: + { + return SectionMoreBreakingNewsList.route(routeSettings); + } + case showMoreRelatedNews: + { + return ShowMoreNewsList.route(routeSettings); + } + case editUserProfile: + { + return UserProfileScreen.route(routeSettings); + } + case maintenance: + { + return CupertinoPageRoute(builder: (_) => const MaintenanceScreen()); + } + case rssFeedDetails: + { + return RSSFeedDetailsScreen.route(routeSettings); + } + case authorDetails: + { + return AuthorDetailsScreen.route(routeSettings); + } + + default: + { + return CupertinoPageRoute(builder: (context) => const Scaffold()); + } + } + } +} diff --git a/news-app/lib/cubits/AddNewsCubit.dart b/news-app/lib/cubits/AddNewsCubit.dart new file mode 100644 index 00000000..1616dd06 --- /dev/null +++ b/news-app/lib/cubits/AddNewsCubit.dart @@ -0,0 +1,86 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/AddNews/addNewsRepository.dart'; + +abstract class AddNewsState {} + +class AddNewsInitial extends AddNewsState {} + +class AddNewsFetchInProgress extends AddNewsState {} + +class AddNewsFetchSuccess extends AddNewsState { + var addNews; + + AddNewsFetchSuccess({required this.addNews}); +} + +class AddNewsFetchFailure extends AddNewsState { + final String errorMessage; + + AddNewsFetchFailure(this.errorMessage); +} + +class AddNewsCubit extends Cubit { + final AddNewsRepository _addNewsRepository; + + AddNewsCubit(this._addNewsRepository) : super(AddNewsInitial()); + + void addNews( + {required BuildContext context, + required String actionType, + required String catId, + required String title, + required String conTypeId, + required String conType, + required String langId, + File? image, + String? newsId, + String? subCatId, + String? showTill, + String? tagId, + String? url, + String? desc, + String? summDescription, + String? locationId, + File? videoUpload, + List? otherImage, + String? publishDate, + required String metaTitle, + required String metaDescription, + required String metaKeyword, + required String slug, + required int isDraft}) async { + try { + emit(AddNewsFetchInProgress()); + final result = await _addNewsRepository.addNews( + context: context, + actionType: actionType, + newsId: newsId, + title: title, + image: image, + conTypeId: conTypeId, + conType: conType, + langId: langId, + catId: catId, + videoUpload: videoUpload, + url: url, + tagId: tagId, + otherImage: otherImage, + desc: desc, + showTill: showTill, + subCatId: subCatId, + locationId: locationId, + metaTitle: metaTitle, + metaDescription: metaDescription, + metaKeyword: metaKeyword, + slug: slug, + publishDate: publishDate ?? null, + summDescription: summDescription, + isDraft: isDraft); + emit(AddNewsFetchSuccess(addNews: result)); + } catch (e) { + emit(AddNewsFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/Auth/authCubit.dart b/news-app/lib/cubits/Auth/authCubit.dart new file mode 100644 index 00000000..9ce3daa3 --- /dev/null +++ b/news-app/lib/cubits/Auth/authCubit.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/AuthModel.dart'; +import 'package:news/data/repositories/Auth/authRepository.dart'; + +const String loginEmail = "email"; +const String loginGmail = "gmail"; +const String loginFb = "fb"; +const String loginMbl = "mobile"; +const String loginApple = "apple"; + +enum AuthProviders { gmail, fb, apple, mobile, email } + +@immutable +abstract class AuthState {} + +class AuthInitial extends AuthState {} + +class Authenticated extends AuthState { + //to store authDetails + final AuthModel authModel; + + Authenticated({required this.authModel}); +} + +class Unauthenticated extends AuthState {} + +class AuthCubit extends Cubit { + final AuthRepository _authRepository; + + AuthCubit(this._authRepository) : super(AuthInitial()) { + checkAuthStatus(); + } + + AuthRepository get authRepository => _authRepository; + + void checkAuthStatus() { + //authDetails is map. keys are isLogin,userId,authProvider + final authDetails = _authRepository.getLocalAuthDetails(); + + (authDetails['isLogIn']) ? emit(Authenticated(authModel: AuthModel.fromJson(authDetails))) : emit(Unauthenticated()); + } + + String getUserId() { + return (state is Authenticated) ? (state as Authenticated).authModel.id! : "0"; + } + + String getProfile() { + return (state is Authenticated) + ? ((state as Authenticated).authModel.profile!.trim().isNotEmpty) + ? (state as Authenticated).authModel.profile! + : "" + : ""; + } + + String getMobile() { + return (state is Authenticated) ? (state as Authenticated).authModel.mobile! : ""; + } + + String getType() { + return (state is Authenticated) ? (state as Authenticated).authModel.type! : ""; + } + + String getAuthorBio() { + return (state is Authenticated && (state as Authenticated).authModel.authorDetails != null) ? ((state as Authenticated).authModel.authorDetails?.bio ?? "") : ""; + } + + String getAuthorWhatsappLink() { + return (state is Authenticated && (state as Authenticated).authModel.authorDetails != null) ? (state as Authenticated).authModel.authorDetails?.whatsappLink ?? "" : "0"; + } + + String getAuthorTelegramLink() { + return (state is Authenticated && (state as Authenticated).authModel.authorDetails != null) ? (state as Authenticated).authModel.authorDetails?.telegramLink ?? "" : "0"; + } + + String getAuthorFacebookLink() { + return (state is Authenticated && (state as Authenticated).authModel.authorDetails != null) ? (state as Authenticated).authModel.authorDetails?.facebookLink ?? "" : "0"; + } + + String getAuthorLinkedInLink() { + return (state is Authenticated && (state as Authenticated).authModel.authorDetails != null) ? (state as Authenticated).authModel.authorDetails?.linkedinLink ?? "" : "0"; + } + + void updateDetails({required AuthModel authModel}) { + emit(Authenticated(authModel: authModel)); + } + + Future signOut(AuthProviders authProvider) async { + if (state is Authenticated) { + _authRepository.signOut(authProvider); + emit(Unauthenticated()); + } + } + + bool isAuthor() { + return (state is Authenticated) ? ((state as Authenticated).authModel.isAuthor == 1) : false; + } +} diff --git a/news-app/lib/cubits/Auth/deleteUserCubit.dart b/news-app/lib/cubits/Auth/deleteUserCubit.dart new file mode 100644 index 00000000..908ada42 --- /dev/null +++ b/news-app/lib/cubits/Auth/deleteUserCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/Auth/authRepository.dart'; + +abstract class DeleteUserState {} + +class DeleteUserInitial extends DeleteUserState {} + +class DeleteUserFetchInProgress extends DeleteUserState {} + +class DeleteUserFetchSuccess extends DeleteUserState { + dynamic deleteUser; + + DeleteUserFetchSuccess({required this.deleteUser}); +} + +class DeleteUserFetchFailure extends DeleteUserState { + final String errorMessage; + + DeleteUserFetchFailure(this.errorMessage); +} + +class DeleteUserCubit extends Cubit { + final AuthRepository _deleteUserRepository; + + DeleteUserCubit(this._deleteUserRepository) : super(DeleteUserInitial()); + + Future deleteUser({String? name, String? mobile, String? email, String? filePath}) async { + try { + emit(DeleteUserFetchInProgress()); + final result = await _deleteUserRepository.deleteUser(); + emit(DeleteUserFetchSuccess(deleteUser: result)); + return result; + } catch (e) { + emit(DeleteUserFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/Auth/registerTokenCubit.dart b/news-app/lib/cubits/Auth/registerTokenCubit.dart new file mode 100644 index 00000000..423b7b41 --- /dev/null +++ b/news-app/lib/cubits/Auth/registerTokenCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/Auth/authRepository.dart'; + +@immutable +abstract class RegisterTokenState {} + +class RegisterTokenInitial extends RegisterTokenState {} + +class RegisterTokenProgress extends RegisterTokenState { + RegisterTokenProgress(); +} + +class RegisterTokenSuccess extends RegisterTokenState { + RegisterTokenSuccess(); +} + +class RegisterTokenFailure extends RegisterTokenState { + final String errorMessage; + + RegisterTokenFailure(this.errorMessage); +} + +class RegisterTokenCubit extends Cubit { + final AuthRepository _authRepository; + + RegisterTokenCubit(this._authRepository) : super(RegisterTokenInitial()); + + void registerToken({required String fcmId, required BuildContext context}) { + emit(RegisterTokenProgress()); + _authRepository.registerToken(fcmId: fcmId, context: context).then((result) { + emit(RegisterTokenSuccess()); + }).catchError((e) { + emit(RegisterTokenFailure(e.toString())); + }); + } +} diff --git a/news-app/lib/cubits/Auth/socialSignUpCubit.dart b/news-app/lib/cubits/Auth/socialSignUpCubit.dart new file mode 100644 index 00000000..d7da2acf --- /dev/null +++ b/news-app/lib/cubits/Auth/socialSignUpCubit.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/data/models/AuthModel.dart'; +import 'package:news/data/repositories/Auth/authRepository.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; + +@immutable +abstract class SocialSignUpState {} + +class SocialSignUpInitial extends SocialSignUpState {} + +class SocialSignUpProgress extends SocialSignUpState {} + +class SocialSignUpSuccess extends SocialSignUpState { + final AuthModel authModel; + + SocialSignUpSuccess({required this.authModel}); +} + +class SocialSignUpFailure extends SocialSignUpState { + final String errorMessage; + + SocialSignUpFailure(this.errorMessage); +} + +class SocialSignUpCubit extends Cubit { + final AuthRepository _authRepository; + + SocialSignUpCubit(this._authRepository) : super(SocialSignUpInitial()); + + void socialSignUpUser({required AuthProviders authProvider, required BuildContext context, String? email, String? password, String? otp, String? verifiedId}) { + emit(SocialSignUpProgress()); + _authRepository.signInUser(email: email, otp: otp, password: password, verifiedId: verifiedId, authProvider: authProvider, context: context).then((result) { + emit(SocialSignUpSuccess(authModel: AuthModel.fromJson(result[DATA]))); + }).catchError((e) { + emit(SocialSignUpFailure(e.toString())); + }); + } +} diff --git a/news-app/lib/cubits/Auth/updateUserCubit.dart b/news-app/lib/cubits/Auth/updateUserCubit.dart new file mode 100644 index 00000000..cd03aaf2 --- /dev/null +++ b/news-app/lib/cubits/Auth/updateUserCubit.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/data/models/AuthModel.dart'; +import 'package:news/data/repositories/Auth/authRepository.dart'; + +abstract class UpdateUserState {} + +class UpdateUserInitial extends UpdateUserState {} + +class UpdateUserFetchInProgress extends UpdateUserState {} + +class UpdateUserFetchSuccess extends UpdateUserState { + AuthModel? updatedUser; + String? imgUpdatedPath; + + UpdateUserFetchSuccess({this.updatedUser, this.imgUpdatedPath}); +} + +class UpdateUserFetchFailure extends UpdateUserState { + final String errorMessage; + + UpdateUserFetchFailure(this.errorMessage); +} + +class UpdateUserCubit extends Cubit { + final AuthRepository _updateUserRepository; + + UpdateUserCubit(this._updateUserRepository) : super(UpdateUserInitial()); + + void setUpdateUser( + {String? name, + String? mobile, + String? email, + String? filePath, + required BuildContext context, + String? authorBio, + String? whatsappLink, + String? facebookLink, + String? telegramLink, + String? linkedInLink}) async { + try { + emit(UpdateUserFetchInProgress()); + final Map result = await _updateUserRepository.updateUserData( + mobile: mobile, + name: name, + email: email, + filePath: filePath, + authorBio: authorBio, + whatsappLink: whatsappLink, + facebookLink: facebookLink, + telegramLink: telegramLink, + linkedInLink: linkedInLink); + //only incase of name,mobile & mail, not Profile Picture + context.read().updateDetails(authModel: AuthModel.fromJson(result["data"])); + emit(UpdateUserFetchSuccess(updatedUser: AuthModel.fromJson(result["data"]))); + } catch (e) { + emit(UpdateUserFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/Author/authorCubit.dart b/news-app/lib/cubits/Author/authorCubit.dart new file mode 100644 index 00000000..9258a6d5 --- /dev/null +++ b/news-app/lib/cubits/Author/authorCubit.dart @@ -0,0 +1,43 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +abstract class AuthorState {} + +class AuthorInitial extends AuthorState {} + +class AuthorInProgress extends AuthorState {} + +class AuthorApproved extends AuthorState { + AuthorApproved(); +} + +class AuthorRequestSent extends AuthorState { + final String responseMessage; + AuthorRequestSent(this.responseMessage); +} + +class AuthorPending extends AuthorState { + final String errorMessage; + + AuthorPending(this.errorMessage); +} + +class AuthorRejected extends AuthorState { + final String errorMessage; + + AuthorRejected(this.errorMessage); +} + +class AuthorCubit extends Cubit { + AuthorCubit() : super(AuthorInitial()); + + void requestToBecomeAuthor() async { + try { + final result = await Api.sendApiRequest(body: {}, url: Api.becomeAnAuthorApi); + (!result[ERROR]) ? emit(AuthorRequestSent(result[MESSAGE])) : emit(AuthorPending(result[MESSAGE])); + } catch (e) { + emit(AuthorPending(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/Author/authorNewsCubit.dart b/news-app/lib/cubits/Author/authorNewsCubit.dart new file mode 100644 index 00000000..3181a801 --- /dev/null +++ b/news-app/lib/cubits/Author/authorNewsCubit.dart @@ -0,0 +1,49 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +abstract class AuthorNewsState {} + +class AuthorNewsInitial extends AuthorNewsState {} + +class AuthorNewsFetchInProgress extends AuthorNewsState {} + +class AuthorNewsFetchSuccess extends AuthorNewsState { + final List AuthorNewsList; + final UserAuthorModel authorData; + final int totalAuthorNewsCount; + final bool hasMoreFetchError; + final bool hasMore; + + AuthorNewsFetchSuccess({required this.AuthorNewsList, required this.authorData, required this.totalAuthorNewsCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class AuthorNewsFetchFailed extends AuthorNewsState { + final String errorMessage; + + AuthorNewsFetchFailed(this.errorMessage); +} + +class AuthorNewsCubit extends Cubit { + AuthorNewsCubit() : super(AuthorNewsInitial()); + + void getAuthorNews({required String authorId}) async { + try { + emit(AuthorNewsInitial()); + final apiUrl = "${Api.getAuthorNewsApi}/${authorId}"; + final result = await Api.sendApiRequest(body: null, url: apiUrl, isGet: true); + + (!result[ERROR]) + ? emit(AuthorNewsFetchSuccess( + AuthorNewsList: (result[DATA][NEWS][DATA] as List).map((e) => NewsModel.fromJson(e)).toList(), + authorData: UserAuthorModel.fromJson(result[DATA][USER]), + totalAuthorNewsCount: result[DATA][NEWS][TOTAL], + hasMore: false, + hasMoreFetchError: false)) + : emit(AuthorNewsFetchFailed(result[MESSAGE])); + } catch (e) { + emit(AuthorNewsFetchFailed(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/Bookmark/UpdateBookmarkCubit.dart b/news-app/lib/cubits/Bookmark/UpdateBookmarkCubit.dart new file mode 100644 index 00000000..e5958803 --- /dev/null +++ b/news-app/lib/cubits/Bookmark/UpdateBookmarkCubit.dart @@ -0,0 +1,38 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Bookmark/bookmarkRepository.dart'; +import 'package:news/utils/api.dart'; + +abstract class UpdateBookmarkStatusState {} + +class UpdateBookmarkStatusInitial extends UpdateBookmarkStatusState {} + +class UpdateBookmarkStatusInProgress extends UpdateBookmarkStatusState {} + +class UpdateBookmarkStatusSuccess extends UpdateBookmarkStatusState { + final NewsModel news; + final bool wasBookmarkNewsProcess; //to check that process of Bookmark done or not + UpdateBookmarkStatusSuccess(this.news, this.wasBookmarkNewsProcess); +} + +class UpdateBookmarkStatusFailure extends UpdateBookmarkStatusState { + final String errorMessage; + + UpdateBookmarkStatusFailure(this.errorMessage); +} + +class UpdateBookmarkStatusCubit extends Cubit { + final BookmarkRepository bookmarkRepository; + + UpdateBookmarkStatusCubit(this.bookmarkRepository) : super(UpdateBookmarkStatusInitial()); + + void setBookmarkNews({required NewsModel news, required String status}) { + emit(UpdateBookmarkStatusInProgress()); + bookmarkRepository.setBookmark(newsId: (news.newsId != null) ? news.newsId! : news.id!, status: status).then((value) { + emit(UpdateBookmarkStatusSuccess(news, status == "1" ? true : false)); + }).catchError((e) { + ApiMessageAndCodeException apiMessageAndCodeException = e; + emit(UpdateBookmarkStatusFailure(apiMessageAndCodeException.errorMessage.toString())); + }); + } +} diff --git a/news-app/lib/cubits/Bookmark/bookmarkCubit.dart b/news-app/lib/cubits/Bookmark/bookmarkCubit.dart new file mode 100644 index 00000000..2d89aef0 --- /dev/null +++ b/news-app/lib/cubits/Bookmark/bookmarkCubit.dart @@ -0,0 +1,111 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Bookmark/bookmarkRepository.dart'; +import 'package:news/utils/strings.dart'; + +abstract class BookmarkState {} + +class BookmarkInitial extends BookmarkState {} + +class BookmarkFetchInProgress extends BookmarkState {} + +class BookmarkFetchSuccess extends BookmarkState { + final List bookmark; + final int totalBookmarkCount; + final bool hasMoreFetchError; + final bool hasMore; + + BookmarkFetchSuccess({required this.bookmark, required this.totalBookmarkCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class BookmarkFetchFailure extends BookmarkState { + final String errorMessage; + + BookmarkFetchFailure(this.errorMessage); +} + +class BookmarkCubit extends Cubit { + final BookmarkRepository bookmarkRepository; + int perPageLimit = 25; + + BookmarkCubit(this.bookmarkRepository) : super(BookmarkInitial()); + + void getBookmark({required String langId}) async { + emit(BookmarkFetchInProgress()); + + try { + final result = await bookmarkRepository.getBookmark(limit: perPageLimit.toString(), offset: "0", langId: langId); + + if (result[ERROR]) { + final message = result[MESSAGE]; + if (message == "No Data Found") { + emit(BookmarkFetchSuccess(bookmark: [], totalBookmarkCount: 0, hasMoreFetchError: false, hasMore: false)); + } else { + emit(BookmarkFetchFailure(message)); + } + return; + } + + final bookmarks = result['Bookmark'] as List; + final total = result[TOTAL] as int; + + emit(BookmarkFetchSuccess(bookmark: bookmarks, totalBookmarkCount: total, hasMoreFetchError: false, hasMore: bookmarks.length < total)); + } catch (e) { + final error = e.toString(); + emit(error == "No Data Found" ? BookmarkFetchSuccess(bookmark: [], totalBookmarkCount: 0, hasMoreFetchError: false, hasMore: false) : BookmarkFetchFailure(error)); + } + } + + bool hasMoreBookmark() { + return (state is BookmarkFetchSuccess) ? (state as BookmarkFetchSuccess).hasMore : false; + } + + void getMoreBookmark({required String langId}) async { + if (state is BookmarkFetchSuccess) { + try { + final result = await bookmarkRepository.getBookmark(limit: perPageLimit.toString(), offset: (state as BookmarkFetchSuccess).bookmark.length.toString(), langId: langId); + List updatedResults = (state as BookmarkFetchSuccess).bookmark; + updatedResults.addAll(result['Bookmark'] as List); + emit(BookmarkFetchSuccess(bookmark: updatedResults, totalBookmarkCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL])); + } catch (e) { + emit(BookmarkFetchSuccess( + bookmark: (state as BookmarkFetchSuccess).bookmark, + hasMoreFetchError: (e.toString() == "No Data Found") ? false : true, + totalBookmarkCount: (state as BookmarkFetchSuccess).totalBookmarkCount, + hasMore: (state as BookmarkFetchSuccess).hasMore)); + } + } + } + + void addBookmarkNews(NewsModel model) { + if (state is BookmarkFetchSuccess) { + List bookmarklist = []; + bookmarklist.insert(0, model); + bookmarklist.addAll((state as BookmarkFetchSuccess).bookmark); + + emit(BookmarkFetchSuccess( + bookmark: List.from(bookmarklist), hasMoreFetchError: true, totalBookmarkCount: (state as BookmarkFetchSuccess).totalBookmarkCount, hasMore: (state as BookmarkFetchSuccess).hasMore)); + } + } + + void removeBookmarkNews(NewsModel model) { + if (state is BookmarkFetchSuccess) { + final bookmark = (state as BookmarkFetchSuccess).bookmark; + bookmark.removeWhere(((element) => (element.id == model.id || element.newsId == model.id))); + emit(BookmarkFetchSuccess( + bookmark: List.from(bookmark), hasMoreFetchError: true, totalBookmarkCount: (state as BookmarkFetchSuccess).totalBookmarkCount, hasMore: (state as BookmarkFetchSuccess).hasMore)); + } + } + + bool isNewsBookmark(String newsId) { + if (state is BookmarkFetchSuccess) { + final bookmark = (state as BookmarkFetchSuccess).bookmark; + return (bookmark.isNotEmpty) ? (bookmark.indexWhere((element) => (element.newsId == newsId || element.id == newsId)) != -1) : false; + } + return false; + } + + void resetState() { + emit(BookmarkFetchInProgress()); + } +} diff --git a/news-app/lib/cubits/ConnectivityCubit.dart b/news-app/lib/cubits/ConnectivityCubit.dart new file mode 100644 index 00000000..0eb95129 --- /dev/null +++ b/news-app/lib/cubits/ConnectivityCubit.dart @@ -0,0 +1,79 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ConnectivityCubit extends Cubit { + final ConnectivityService _connectivityService; + late StreamSubscription _subscription; + + ConnectivityCubit(this._connectivityService) : super(ConnectivityInitial()) { + _startMonitoring(); + } + + void _startMonitoring() { + _subscription = _connectivityService.connectivityStream.listen((result) { + if (result == ConnectivityResult.none) { + emit(ConnectivityDisconnected()); + } else { + emit(ConnectivityConnected(result)); + } + }); + } + + Future checkInitialConnection() async { + final result = await _connectivityService.checkConnectivity(); + if (result == ConnectivityResult.none) { + emit(ConnectivityDisconnected()); + } else { + emit(ConnectivityConnected(result as ConnectivityResult)); + } + } + + @override + Future close() { + _subscription.cancel(); + return super.close(); + } +} + +abstract class ConnectivityState extends Equatable { + const ConnectivityState(); + + @override + List get props => []; +} + +class ConnectivityInitial extends ConnectivityState {} + +class ConnectivityConnected extends ConnectivityState { + final ConnectivityResult result; + + const ConnectivityConnected(this.result); + + @override + List get props => [result]; +} + +class ConnectivityDisconnected extends ConnectivityState {} + +class ConnectivityService { + static final ConnectivityService _instance = ConnectivityService._internal(); + final Connectivity _connectivity = Connectivity(); + final StreamController _controller = StreamController.broadcast(); + + factory ConnectivityService() => _instance; + + ConnectivityService._internal() { + _connectivity.onConnectivityChanged.listen((result) { + _controller.add(result.first); // Notify all listeners + }); + } + + Stream get connectivityStream => _controller.stream; + + Future> checkConnectivity() async { + return await _connectivity.checkConnectivity(); + } +} diff --git a/news-app/lib/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart b/news-app/lib/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart new file mode 100644 index 00000000..218ddc0d --- /dev/null +++ b/news-app/lib/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart @@ -0,0 +1,55 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart'; +import 'package:news/utils/strings.dart'; + +abstract class LikeAndDisLikeState {} + +class LikeAndDisLikeInitial extends LikeAndDisLikeState {} + +class LikeAndDisLikeFetchInProgress extends LikeAndDisLikeState {} + +class LikeAndDisLikeFetchSuccess extends LikeAndDisLikeState { + final List likeAndDisLike; + final int totalLikeAndDisLikeCount; + final bool hasMoreFetchError; + final bool hasMore; + + LikeAndDisLikeFetchSuccess({required this.likeAndDisLike, required this.totalLikeAndDisLikeCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class LikeAndDisLikeFetchFailure extends LikeAndDisLikeState { + final String errorMessage; + + LikeAndDisLikeFetchFailure(this.errorMessage); +} + +class LikeAndDisLikeCubit extends Cubit { + final LikeAndDisLikeRepository likeAndDisLikeRepository; + int perPageLimit = 25; + + LikeAndDisLikeCubit(this.likeAndDisLikeRepository) : super(LikeAndDisLikeInitial()); + + void getLike({required String langId}) async { + try { + emit(LikeAndDisLikeFetchInProgress()); + final result = await likeAndDisLikeRepository.getLike(limit: perPageLimit.toString(), offset: "0", langId: langId); + emit(LikeAndDisLikeFetchSuccess( + likeAndDisLike: result['LikeAndDisLike'], totalLikeAndDisLikeCount: result[TOTAL], hasMoreFetchError: false, hasMore: (result['LikeAndDisLike'] as List).length < result[TOTAL])); + } catch (e) { + emit(LikeAndDisLikeFetchFailure(e.toString())); + } + } + + bool isNewsLikeAndDisLike(String newsId) { + if (state is LikeAndDisLikeFetchSuccess) { + final likeAndDisLike = (state as LikeAndDisLikeFetchSuccess).likeAndDisLike; + return likeAndDisLike.indexWhere((element) => (element.id == newsId || element.newsId == newsId)) != -1; + } + return false; + } + + void resetState() { + emit(LikeAndDisLikeFetchInProgress()); + } +} diff --git a/news-app/lib/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart b/news-app/lib/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart new file mode 100644 index 00000000..639deb6a --- /dev/null +++ b/news-app/lib/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart @@ -0,0 +1,38 @@ +import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/api.dart'; + +abstract class UpdateLikeAndDisLikeStatusState {} + +class UpdateLikeAndDisLikeStatusInitial extends UpdateLikeAndDisLikeStatusState {} + +class UpdateLikeAndDisLikeStatusInProgress extends UpdateLikeAndDisLikeStatusState {} + +class UpdateLikeAndDisLikeStatusSuccess extends UpdateLikeAndDisLikeStatusState { + final NewsModel news; + final bool wasLikeAndDisLikeNewsProcess; //to check that process of favorite done or not + UpdateLikeAndDisLikeStatusSuccess(this.news, this.wasLikeAndDisLikeNewsProcess); +} + +class UpdateLikeAndDisLikeStatusFailure extends UpdateLikeAndDisLikeStatusState { + final String errorMessage; + + UpdateLikeAndDisLikeStatusFailure(this.errorMessage); +} + +class UpdateLikeAndDisLikeStatusCubit extends Cubit { + final LikeAndDisLikeRepository likeAndDisLikeRepository; + + UpdateLikeAndDisLikeStatusCubit(this.likeAndDisLikeRepository) : super(UpdateLikeAndDisLikeStatusInitial()); + + void setLikeAndDisLikeNews({required NewsModel news, required String status}) { + emit(UpdateLikeAndDisLikeStatusInProgress()); + likeAndDisLikeRepository.setLike(newsId: (news.newsId != null) ? news.newsId! : news.id!, status: status).then((value) { + emit(UpdateLikeAndDisLikeStatusSuccess(news, status == "1" ? true : false)); + }).catchError((e) { + ApiMessageAndCodeException apiMessageAndCodeException = e; + emit(UpdateLikeAndDisLikeStatusFailure(apiMessageAndCodeException.errorMessage.toString())); + }); + } +} diff --git a/news-app/lib/cubits/NewsByIdCubit.dart b/news-app/lib/cubits/NewsByIdCubit.dart new file mode 100644 index 00000000..f525be3c --- /dev/null +++ b/news-app/lib/cubits/NewsByIdCubit.dart @@ -0,0 +1,39 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/NewsById/NewsByIdRepository.dart'; + +abstract class NewsByIdState {} + +class NewsByIdInitial extends NewsByIdState {} + +class NewsByIdFetchInProgress extends NewsByIdState {} + +class NewsByIdFetchSuccess extends NewsByIdState { + final List newsById; + + NewsByIdFetchSuccess({required this.newsById}); +} + +class NewsByIdFetchFailure extends NewsByIdState { + final String errorMessage; + + NewsByIdFetchFailure(this.errorMessage); +} + +class NewsByIdCubit extends Cubit { + final NewsByIdRepository _newsByIdRepository; + + NewsByIdCubit(this._newsByIdRepository) : super(NewsByIdInitial()); + + Future> getNewsById({required String newsId, required String langId}) async { + try { + emit(NewsByIdFetchInProgress()); + final result = await _newsByIdRepository.getNewsById(langId: langId, newsId: newsId); + emit(NewsByIdFetchSuccess(newsById: result['NewsById'])); + return result['NewsById']; + } catch (e) { + emit(NewsByIdFetchFailure(e.toString())); + return []; + } + } +} diff --git a/news-app/lib/cubits/NewsComment/deleteCommentCubit.dart b/news-app/lib/cubits/NewsComment/deleteCommentCubit.dart new file mode 100644 index 00000000..71f67223 --- /dev/null +++ b/news-app/lib/cubits/NewsComment/deleteCommentCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/NewsComment/DeleteComment/deleteCommRepository.dart'; +import 'package:news/utils/api.dart'; + +abstract class DeleteCommState {} + +class DeleteCommInitial extends DeleteCommState {} + +class DeleteCommInProgress extends DeleteCommState {} + +class DeleteCommSuccess extends DeleteCommState { + final String message; + + DeleteCommSuccess(this.message); +} + +class DeleteCommFailure extends DeleteCommState { + final String errorMessage; + + DeleteCommFailure(this.errorMessage); +} + +class DeleteCommCubit extends Cubit { + final DeleteCommRepository _deleteCommRepository; + + DeleteCommCubit(this._deleteCommRepository) : super(DeleteCommInitial()); + + void setDeleteComm({required String commId}) { + emit(DeleteCommInProgress()); + _deleteCommRepository.setDeleteComm(commId: commId).then((value) { + emit(DeleteCommSuccess(value["message"])); + }).catchError((e) { + ApiMessageAndCodeException apiMessageAndCodeException = e; + emit(DeleteCommFailure(apiMessageAndCodeException.errorMessage.toString())); + }); + } +} diff --git a/news-app/lib/cubits/NewsComment/flagCommentCubit.dart b/news-app/lib/cubits/NewsComment/flagCommentCubit.dart new file mode 100644 index 00000000..5eada905 --- /dev/null +++ b/news-app/lib/cubits/NewsComment/flagCommentCubit.dart @@ -0,0 +1,36 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/NewsComment/FlagComment/flagCommRepository.dart'; + +abstract class SetFlagState {} + +class SetFlagInitial extends SetFlagState {} + +class SetFlagFetchInProgress extends SetFlagState {} + +class SetFlagFetchSuccess extends SetFlagState { + String message; + + SetFlagFetchSuccess({required this.message}); +} + +class SetFlagFetchFailure extends SetFlagState { + final String errorMessage; + + SetFlagFetchFailure(this.errorMessage); +} + +class SetFlagCubit extends Cubit { + final SetFlagRepository _setFlagRepository; + + SetFlagCubit(this._setFlagRepository) : super(SetFlagInitial()); + + void setFlag({required String commId, required String newsId, required String message}) async { + try { + emit(SetFlagFetchInProgress()); + final result = await _setFlagRepository.setFlag(message: message, newsId: newsId, commId: commId); + emit(SetFlagFetchSuccess(message: result['message'])); + } catch (e) { + emit(SetFlagFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/NewsComment/likeAndDislikeCommCubit.dart b/news-app/lib/cubits/NewsComment/likeAndDislikeCommCubit.dart new file mode 100644 index 00000000..28849d29 --- /dev/null +++ b/news-app/lib/cubits/NewsComment/likeAndDislikeCommCubit.dart @@ -0,0 +1,41 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart'; +import 'package:news/utils/strings.dart'; + +abstract class LikeAndDislikeCommState {} + +class LikeAndDislikeCommInitial extends LikeAndDislikeCommState {} + +class LikeAndDislikeCommInProgress extends LikeAndDislikeCommState {} + +class LikeAndDislikeCommSuccess extends LikeAndDislikeCommState { + final CommentModel comment; + final bool wasLikeAndDislikeCommNewsProcess; + final bool fromLike; + + LikeAndDislikeCommSuccess(this.comment, this.wasLikeAndDislikeCommNewsProcess, this.fromLike); +} + +class LikeAndDislikeCommFailure extends LikeAndDislikeCommState { + final String errorMessage; + + LikeAndDislikeCommFailure(this.errorMessage); +} + +class LikeAndDislikeCommCubit extends Cubit { + final LikeAndDislikeCommRepository _likeAndDislikeCommRepository; + + LikeAndDislikeCommCubit(this._likeAndDislikeCommRepository) : super(LikeAndDislikeCommInitial()); + + void setLikeAndDislikeComm({required String langId, required String commId, required String status, required bool fromLike}) async { + try { + emit(LikeAndDislikeCommInProgress()); + final result = await _likeAndDislikeCommRepository.setLikeAndDislikeComm(langId: langId, commId: commId, status: status); + + (!result[ERROR]) ? emit(LikeAndDislikeCommSuccess(result['updatedComment'], true, fromLike)) : emit(LikeAndDislikeCommFailure(result[MESSAGE])); + } catch (e) { + emit(LikeAndDislikeCommFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/NewsComment/setCommentCubit.dart b/news-app/lib/cubits/NewsComment/setCommentCubit.dart new file mode 100644 index 00000000..308220da --- /dev/null +++ b/news-app/lib/cubits/NewsComment/setCommentCubit.dart @@ -0,0 +1,39 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/data/repositories/NewsComment/SetComment/setComRepository.dart'; +import 'package:news/utils/strings.dart'; + +abstract class SetCommentState {} + +class SetCommentInitial extends SetCommentState {} + +class SetCommentFetchInProgress extends SetCommentState {} + +class SetCommentFetchSuccess extends SetCommentState { + List setComment; + int total; + + SetCommentFetchSuccess({required this.setComment, required this.total}); +} + +class SetCommentFetchFailure extends SetCommentState { + final String errorMessage; + + SetCommentFetchFailure(this.errorMessage); +} + +class SetCommentCubit extends Cubit { + final SetCommentRepository _setCommentRepository; + + SetCommentCubit(this._setCommentRepository) : super(SetCommentInitial()); + + void setComment({required String parentId, required String newsId, required String message}) async { + emit(SetCommentFetchInProgress()); + try { + final result = await _setCommentRepository.setComment(message: message, newsId: newsId, parentId: parentId); + emit(SetCommentFetchSuccess(setComment: result['SetComment'], total: result[TOTAL])); + } catch (e) { + emit(SetCommentFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/UserPreferences/setUserPreferenceCatCubit.dart b/news-app/lib/cubits/UserPreferences/setUserPreferenceCatCubit.dart new file mode 100644 index 00000000..613dbe29 --- /dev/null +++ b/news-app/lib/cubits/UserPreferences/setUserPreferenceCatCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/SetUserPreferenceCat/setUserPrefCatRepository.dart'; + +abstract class SetUserPrefCatState {} + +class SetUserPrefCatInitial extends SetUserPrefCatState {} + +class SetUserPrefCatFetchInProgress extends SetUserPrefCatState {} + +class SetUserPrefCatFetchSuccess extends SetUserPrefCatState { + var setUserPrefCat; + + SetUserPrefCatFetchSuccess({required this.setUserPrefCat}); +} + +class SetUserPrefCatFetchFailure extends SetUserPrefCatState { + final String errorMessage; + + SetUserPrefCatFetchFailure(this.errorMessage); +} + +class SetUserPrefCatCubit extends Cubit { + final SetUserPrefCatRepository _setUserPrefCatRepository; + + SetUserPrefCatCubit(this._setUserPrefCatRepository) : super(SetUserPrefCatInitial()); + + void setUserPrefCat({required String catId}) async { + emit(SetUserPrefCatFetchInProgress()); + try { + final result = await _setUserPrefCatRepository.setUserPrefCat(catId: catId); + + emit(SetUserPrefCatFetchSuccess(setUserPrefCat: result['SetUserPrefCat'])); + } catch (e) { + emit(SetUserPrefCatFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/UserPreferences/userByCategoryCubit.dart b/news-app/lib/cubits/UserPreferences/userByCategoryCubit.dart new file mode 100644 index 00000000..162cc531 --- /dev/null +++ b/news-app/lib/cubits/UserPreferences/userByCategoryCubit.dart @@ -0,0 +1,36 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/UserByCategory/userByCatRepository.dart'; + +abstract class UserByCatState {} + +class UserByCatInitial extends UserByCatState {} + +class UserByCatFetchInProgress extends UserByCatState {} + +class UserByCatFetchSuccess extends UserByCatState { + var userByCat; + + UserByCatFetchSuccess({required this.userByCat}); +} + +class UserByCatFetchFailure extends UserByCatState { + final String errorMessage; + + UserByCatFetchFailure(this.errorMessage); +} + +class UserByCatCubit extends Cubit { + final UserByCatRepository _userByCatRepository; + + UserByCatCubit(this._userByCatRepository) : super(UserByCatInitial()); + + void getUserById() async { + emit(UserByCatFetchInProgress()); + try { + final result = await _userByCatRepository.getUserById(); + emit(UserByCatFetchSuccess(userByCat: result['UserByCat']['user_category'])); + } catch (e) { + emit(UserByCatFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/adSpacesNewsDetailsCubit.dart b/news-app/lib/cubits/adSpacesNewsDetailsCubit.dart new file mode 100644 index 00000000..8e4c062c --- /dev/null +++ b/news-app/lib/cubits/adSpacesNewsDetailsCubit.dart @@ -0,0 +1,41 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/adSpaceModel.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +abstract class AdSpacesNewsDetailsState {} + +class AdSpacesNewsDetailsInitial extends AdSpacesNewsDetailsState {} + +class AdSpacesNewsDetailsFetchInProgress extends AdSpacesNewsDetailsState {} + +class AdSpacesNewsDetailsFetchSuccess extends AdSpacesNewsDetailsState { + final AdSpaceModel? adSpaceTopData; + final AdSpaceModel? adSpaceBottomData; + + AdSpacesNewsDetailsFetchSuccess({this.adSpaceTopData, this.adSpaceBottomData}); +} + +class AdSpacesNewsDetailsFetchFailure extends AdSpacesNewsDetailsState { + final String errorMessage; + + AdSpacesNewsDetailsFetchFailure(this.errorMessage); +} + +class AdSpacesNewsDetailsCubit extends Cubit { + AdSpacesNewsDetailsCubit() : super(AdSpacesNewsDetailsInitial()); + + void getAdspaceForNewsDetails({required String langId}) async { + emit(AdSpacesNewsDetailsFetchInProgress()); + try { + final body = {LANGUAGE_ID: langId}; + final Map result = await Api.sendApiRequest(body: body, url: Api.getAdsNewsDetailsApi); + final Map resultData = result[DATA]; + emit(AdSpacesNewsDetailsFetchSuccess( + adSpaceTopData: (resultData.containsKey('ad_spaces_top')) ? (AdSpaceModel.fromJson(resultData['ad_spaces_top'])) : null, + adSpaceBottomData: (resultData.containsKey('ad_spaces_bottom')) ? AdSpaceModel.fromJson(resultData['ad_spaces_bottom']) : null)); + } catch (e) { + emit(AdSpacesNewsDetailsFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/appLocalizationCubit.dart b/news-app/lib/cubits/appLocalizationCubit.dart new file mode 100644 index 00000000..51c4def5 --- /dev/null +++ b/news-app/lib/cubits/appLocalizationCubit.dart @@ -0,0 +1,26 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; + +class AppLocalizationState { + String languageCode; + String id; + int isRTL; + + AppLocalizationState( + this.languageCode, + this.id, + this.isRTL, + ); +} + +class AppLocalizationCubit extends Cubit { + final SettingsLocalDataRepository _settingsRepository; + + AppLocalizationCubit(this._settingsRepository) + : super(AppLocalizationState(_settingsRepository.getCurrentLanguageCode(), _settingsRepository.getCurrentLanguageId(), _settingsRepository.getCurrentLanguageRTL())); + + void changeLanguage(String lanCode, String lanId, int lanRTL) { + _settingsRepository.setLanguagePreferences(code: lanCode, id: lanId, rtl: lanRTL); + emit(AppLocalizationState(lanCode, lanId, lanRTL)); + } +} diff --git a/news-app/lib/cubits/appSystemSettingCubit.dart b/news-app/lib/cubits/appSystemSettingCubit.dart new file mode 100644 index 00000000..f213c945 --- /dev/null +++ b/news-app/lib/cubits/appSystemSettingCubit.dart @@ -0,0 +1,254 @@ +import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/app/app.dart'; +import 'package:news/data/models/AppSystemSettingModel.dart'; +import 'package:news/data/repositories/AppSystemSetting/systemRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/uiUtils.dart'; + +abstract class AppConfigurationState {} + +class AppConfigurationInitial extends AppConfigurationState { + @override + List get props => []; +} + +class AppConfigurationFetchInProgress extends AppConfigurationState { + @override + List get props => []; +} + +class AppConfigurationFetchSuccess extends AppConfigurationState { + final AppSystemSettingModel appConfiguration; + + AppConfigurationFetchSuccess({required this.appConfiguration}); + + @override + List get props => [appConfiguration]; +} + +class AppConfigurationFetchFailure extends AppConfigurationState { + final String errorMessage; + + AppConfigurationFetchFailure(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class AppConfigurationCubit extends Cubit { + final SystemRepository _systemRepository; + + AppConfigurationCubit(this._systemRepository) : super(AppConfigurationInitial()); + + fetchAppConfiguration() async { + emit(AppConfigurationFetchInProgress()); + try { + final appConfiguration = AppSystemSettingModel.fromJson(await _systemRepository.fetchSettings()); + emit(AppConfigurationFetchSuccess(appConfiguration: appConfiguration)); + } catch (e) { + emit(AppConfigurationFetchFailure(e.toString())); + } + } + + AppSystemSettingModel getAppConfiguration() { + return (state is AppConfigurationFetchSuccess) ? (state as AppConfigurationFetchSuccess).appConfiguration : AppSystemSettingModel.fromJson({}); + } + + String? getBreakingNewsMode() { + return ((state is AppConfigurationFetchSuccess)) ? getAppConfiguration().breakNewsMode : ""; + } + + String? getLiveStreamMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().liveStreamMode : ""; + } + + String? getCategoryMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().catMode : ""; + } + + String? getSubCatMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().subCatMode : ""; + } + + String? getCommentsMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().commentMode : ""; + } + + String? getInAppAdsMode() { + return (state is AppConfigurationFetchSuccess) ? ((Platform.isAndroid) ? getAppConfiguration().inAppAdsMode : getAppConfiguration().iosInAppAdsMode) : ""; + } + + String? getLocationWiseNewsMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().locationWiseNewsMode : ""; + } + + String? getWeatherMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().weatherMode : ""; + } + + String? getMaintenanceMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().maintenanceMode : "0"; + } + + String? checkAdsType() { + if (getInAppAdsMode() == "1") { + return (Platform.isIOS) ? getIOSAdsType() : getAdsType(); + } + return null; + } + + String? getAdsType() { + if (state is AppConfigurationFetchSuccess) { + switch (getAppConfiguration().adsType) { + case "1": + return "google"; + case "3": + return "unity"; + default: + return ""; + } + } + return ""; + } + + String? getIOSAdsType() { + if (state is AppConfigurationFetchSuccess) { + switch (getAppConfiguration().iosAdsType) { + case "1": + return "google"; + case "3": + return "unity"; + default: + return ""; + } + } + return ""; + } + + String? bannerId() { + if (state is AppConfigurationFetchSuccess) { + if (Platform.isAndroid && getInAppAdsMode() != "0") { + if (getAdsType() == "google") return getAppConfiguration().goBannerId; + if (getAdsType() == "unity") return getAppConfiguration().unityBannerId; + } + if (Platform.isIOS && getInAppAdsMode() != "0") { + if (getIOSAdsType() == "google") return getAppConfiguration().goIOSBannerId; + if (getIOSAdsType() == "unity") return getAppConfiguration().unityIOSBannerId; + } + } + return ""; + } + + String? rewardId() { + if (state is AppConfigurationFetchSuccess) { + if (Platform.isAndroid && getInAppAdsMode() != "0") { + if (getAdsType() == "google") return getAppConfiguration().goRewardedId; + if (getAdsType() == "unity") return getAppConfiguration().unityRewardedId; + } + if (Platform.isIOS && getInAppAdsMode() != "0") { + if (getIOSAdsType() == "google") return getAppConfiguration().goIOSRewardedId; + if (getIOSAdsType() == "unity") return getAppConfiguration().unityIOSRewardedId; + } + } + return ""; + } + + String? nativeId() { + if (state is AppConfigurationFetchSuccess) { + if (Platform.isAndroid && getInAppAdsMode() != "0") { + if (getAdsType() == "google") return getAppConfiguration().goNativeId; + if (getAdsType() == "unity") return ""; //no native ads in unity + } + if (Platform.isIOS && getInAppAdsMode() != "0") { + if (getIOSAdsType() == "google") return getAppConfiguration().goIOSNativeId; + if (getIOSAdsType() == "unity") return ""; //no native ads in unity + } + } + return ""; + } + + String? interstitialId() { + if (state is AppConfigurationFetchSuccess) { + if (Platform.isAndroid && getInAppAdsMode() != "0") { + if (getAdsType() == "google") return getAppConfiguration().goInterId; + if (getAdsType() == "unity") return getAppConfiguration().unityInterId; + } + if (Platform.isIOS && getInAppAdsMode() != "0") { + if (getIOSAdsType() == "google") return getAppConfiguration().goIOSInterId; + if (getIOSAdsType() == "unity") return getAppConfiguration().unityIOSInterId; + } + } + return ""; + } + + String? unityGameId() { + return (Platform.isAndroid) ? getAppConfiguration().gameId : getAppConfiguration().iosGameId; + } + + String? getRSSFeedMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().rssFeedMode : ""; + } + + String? getMobileLoginMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().mobileLoginMode : ""; + } + + String? getCountryCode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().countryCode : "IN"; //India bydefault + } + + String? getShareAppText() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().shareAppText : ""; + } + + String? getAppstoreId() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().appstoreId : ""; + } + + String? getiOSAppLink() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().iosAppLink : ""; + } + + String? getAndroidAppLink() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().androidAppLink : ""; + } + + VideoViewType? getVideoTypePreference() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().videoTypePreference : VideoViewType.normal; + } + + String? getForceUpdateMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().forceUpdateMode : ""; + } + + bool needsUpdate(String enforceVersion) { + final List currentVersion = packageInfo.version.split('.').map((String number) => int.parse(number)).toList(); + final List enforcedVersion = enforceVersion.split('.').map((String number) => int.parse(number)).toList(); + + for (int i = 0; i < 3; i++) { + if (enforcedVersion[i] > currentVersion[i]) { + return true; + } else if (currentVersion[i] > enforcedVersion[i]) { + return false; + } + } + return false; + } + + String getGeminiAPiKey() { + return ((state is AppConfigurationFetchSuccess)) ? UiUtils.decryptKey(geminiKey: getAppConfiguration().googleGeminiApiKey) : ""; + } + + bool isUpdateRequired() { + if (state is AppConfigurationFetchSuccess) { + AppSystemSettingModel appConfig = (state as AppConfigurationFetchSuccess).appConfiguration; + if (defaultTargetPlatform == TargetPlatform.android && needsUpdate(appConfig.androidAppVersion ?? "") || + defaultTargetPlatform == TargetPlatform.iOS && needsUpdate(appConfig.iosAppVersion ?? "")) { + return true; + } + } + return false; + } +} diff --git a/news-app/lib/cubits/breakingNewsCubit.dart b/news-app/lib/cubits/breakingNewsCubit.dart new file mode 100644 index 00000000..9be06d4f --- /dev/null +++ b/news-app/lib/cubits/breakingNewsCubit.dart @@ -0,0 +1,40 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/repositories/BreakingNews/breakNewsRepository.dart'; +import 'package:news/utils/strings.dart'; + +abstract class BreakingNewsState {} + +class BreakingNewsInitial extends BreakingNewsState {} + +class BreakingNewsFetchInProgress extends BreakingNewsState {} + +class BreakingNewsFetchSuccess extends BreakingNewsState { + final List breakingNews; + + BreakingNewsFetchSuccess({required this.breakingNews}); +} + +class BreakingNewsFetchFailure extends BreakingNewsState { + final String errorMessage; + + BreakingNewsFetchFailure(this.errorMessage); +} + +class BreakingNewsCubit extends Cubit { + final BreakingNewsRepository _breakingNewsRepository; + + BreakingNewsCubit(this._breakingNewsRepository) : super(BreakingNewsInitial()); + + Future> getBreakingNews({required String langId}) async { + emit(BreakingNewsFetchInProgress()); + try { + final result = await _breakingNewsRepository.getBreakingNews(langId: langId); + (!result[ERROR]) ? emit(BreakingNewsFetchSuccess(breakingNews: result['BreakingNews'])) : emit(BreakingNewsFetchFailure(result[MESSAGE])); + return (!result[ERROR]) ? result['BreakingNews'] : []; + } catch (e) { + emit(BreakingNewsFetchFailure(e.toString())); + return []; + } + } +} diff --git a/news-app/lib/cubits/categoryCubit.dart b/news-app/lib/cubits/categoryCubit.dart new file mode 100644 index 00000000..100e4d04 --- /dev/null +++ b/news-app/lib/cubits/categoryCubit.dart @@ -0,0 +1,112 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/repositories/Category/categoryRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class CategoryState {} + +class CategoryInitial extends CategoryState {} + +class CategoryFetchInProgress extends CategoryState {} + +class CategoryFetchSuccess extends CategoryState { + final List category; + final int totalCategoryCount; + final bool hasMoreFetchError; + final bool hasMore; + + CategoryFetchSuccess( + {required this.category, + required this.totalCategoryCount, + required this.hasMoreFetchError, + required this.hasMore}); +} + +class CategoryFetchFailure extends CategoryState { + final String errorMessage; + + CategoryFetchFailure(this.errorMessage); +} + +class CategoryCubit extends Cubit { + final CategoryRepository _categoryRepository; + + CategoryCubit(this._categoryRepository) : super(CategoryInitial()); + + void getCategory({required String langId}) async { + try { + emit(CategoryFetchInProgress()); + int catLimit = 20; + final result = await _categoryRepository.getCategory( + limit: catLimit.toString(), offset: "0", langId: langId); + (!result[ERROR]) + ? emit(CategoryFetchSuccess( + category: result['Category'], + totalCategoryCount: result[TOTAL], + hasMoreFetchError: false, + hasMore: (result['Category'] as List).length < + result[TOTAL])) + : emit(CategoryFetchFailure(result[MESSAGE])); + } catch (e) { + emit(CategoryFetchFailure(e.toString())); + } + } + + // Load categories if not already loaded or failed + void loadIfFailed({required String langId}) { + if (state is CategoryInitial || state is CategoryFetchFailure) { + getCategory(langId: langId); + } + } + + bool hasMoreCategory() { + return (state is CategoryFetchSuccess) + ? (state as CategoryFetchSuccess).hasMore + : false; + } + + void getMoreCategory({required String langId}) async { + if (state is CategoryFetchSuccess) { + try { + final result = await _categoryRepository.getCategory( + limit: limitOfAPIData.toString(), + langId: langId, + offset: (state as CategoryFetchSuccess).category.length.toString()); + if (!result[ERROR]) { + List updatedResults = + (state as CategoryFetchSuccess).category; + updatedResults.addAll(result['Category'] as List); + emit(CategoryFetchSuccess( + category: updatedResults, + totalCategoryCount: result[TOTAL], + hasMoreFetchError: false, + hasMore: updatedResults.length < result[TOTAL])); + } else { + emit(CategoryFetchFailure(result[MESSAGE])); + } + } catch (e) { + emit(CategoryFetchSuccess( + category: (state as CategoryFetchSuccess).category, + hasMoreFetchError: true, + totalCategoryCount: + (state as CategoryFetchSuccess).totalCategoryCount, + hasMore: (state as CategoryFetchSuccess).hasMore)); + } + } + } + + List getCatList() { + return (state is CategoryFetchSuccess) + ? (state as CategoryFetchSuccess).category + : []; + } + + int getCategoryIndex({required String categoryName}) { + return (state is CategoryFetchSuccess) + ? (state as CategoryFetchSuccess) + .category + .indexWhere((element) => element.categoryName == categoryName) + : 0; + } +} diff --git a/news-app/lib/cubits/commentNewsCubit.dart b/news-app/lib/cubits/commentNewsCubit.dart new file mode 100644 index 00000000..36169274 --- /dev/null +++ b/news-app/lib/cubits/commentNewsCubit.dart @@ -0,0 +1,111 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/data/repositories/CommentNews/commNewsRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class CommentNewsState {} + +class CommentNewsInitial extends CommentNewsState {} + +class CommentNewsFetchInProgress extends CommentNewsState {} + +class CommentNewsFetchSuccess extends CommentNewsState { + final List commentNews; + final int totalCommentNewsCount; + final bool hasMoreFetchError; + final bool hasMore; + + CommentNewsFetchSuccess({required this.commentNews, required this.totalCommentNewsCount, required this.hasMoreFetchError, required this.hasMore}); + CommentNewsFetchSuccess copyWith({List? commentNews, int? totalCommentNewsCount, bool? hasMoreFetchError, bool? hasMore}) { + return CommentNewsFetchSuccess( + commentNews: commentNews ?? this.commentNews, + totalCommentNewsCount: totalCommentNewsCount ?? this.totalCommentNewsCount, + hasMoreFetchError: hasMoreFetchError ?? this.hasMoreFetchError, + hasMore: hasMore ?? this.hasMore); + } +} + +class CommentNewsFetchFailure extends CommentNewsState { + final String errorMessage; + + CommentNewsFetchFailure(this.errorMessage); +} + +class CommentNewsCubit extends Cubit { + final CommentNewsRepository _commentNewsRepository; + + CommentNewsCubit(this._commentNewsRepository) : super(CommentNewsInitial()); + + void getCommentNews({required String newsId}) async { + try { + emit(CommentNewsFetchInProgress()); + final result = await _commentNewsRepository.getCommentNews(limit: limitOfAPIData.toString(), offset: "0", newsId: newsId); + + (!result[ERROR]) + ? emit(CommentNewsFetchSuccess( + commentNews: result['CommentNews'], totalCommentNewsCount: result[TOTAL], hasMoreFetchError: false, hasMore: ((result['CommentNews'] as List).length) < result[TOTAL])) + : emit(CommentNewsFetchFailure(result[MESSAGE])); + } catch (e) { + emit(CommentNewsFetchFailure(e.toString())); + } + } + + bool hasMoreCommentNews() { + return (state is CommentNewsFetchSuccess) ? (state as CommentNewsFetchSuccess).hasMore : false; + } + + void getMoreCommentNews({required String newsId}) async { + if (state is CommentNewsFetchSuccess) { + try { + final result = await _commentNewsRepository.getCommentNews(limit: limitOfAPIData.toString(), newsId: newsId, offset: (state as CommentNewsFetchSuccess).commentNews.length.toString()); + List updatedResults = (state as CommentNewsFetchSuccess).commentNews; + updatedResults.addAll(result['CommentNews'] as List); + emit(CommentNewsFetchSuccess(commentNews: updatedResults, totalCommentNewsCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL])); + } catch (e) { + emit(CommentNewsFetchSuccess( + commentNews: (state as CommentNewsFetchSuccess).commentNews, + hasMoreFetchError: true, + totalCommentNewsCount: (state as CommentNewsFetchSuccess).totalCommentNewsCount, + hasMore: (state as CommentNewsFetchSuccess).hasMore)); + } + } + } + + emitSuccessState(List commments) { + emit((state as CommentNewsFetchSuccess).copyWith(commentNews: commments)); + } + + void commentUpdateList(List commentList, int total) { + if (state is CommentNewsFetchSuccess || state is CommentNewsFetchFailure) { + bool haseMore = (state is CommentNewsFetchSuccess) ? (state as CommentNewsFetchSuccess).hasMore : false; + emit(CommentNewsFetchSuccess(commentNews: commentList, hasMore: haseMore, hasMoreFetchError: false, totalCommentNewsCount: total)); + } + } + + void deleteComment(int index) { + if (state is CommentNewsFetchSuccess) { + List commentList = List.from((state as CommentNewsFetchSuccess).commentNews)..removeAt(index); + + emit(CommentNewsFetchSuccess( + commentNews: commentList, + hasMore: (state as CommentNewsFetchSuccess).hasMore, + hasMoreFetchError: false, + totalCommentNewsCount: (state as CommentNewsFetchSuccess).totalCommentNewsCount - 1)); + } + } + + void deleteCommentReply(String commentId, int index) { + if (state is CommentNewsFetchSuccess) { + List commentList = (state as CommentNewsFetchSuccess).commentNews; + commentList.forEach((element) { + if (element.id! == commentId) { + int commIndex = commentList.indexWhere((model) => model.id == commentId); + commentList[commIndex].replyComList!.removeAt(index); + } + }); + emit(CommentNewsFetchSuccess( + commentNews: commentList, hasMore: (state as CommentNewsFetchSuccess).hasMore, hasMoreFetchError: false, totalCommentNewsCount: (state as CommentNewsFetchSuccess).totalCommentNewsCount)); + } + } +} diff --git a/news-app/lib/cubits/deleteImageId.dart b/news-app/lib/cubits/deleteImageId.dart new file mode 100644 index 00000000..93b07334 --- /dev/null +++ b/news-app/lib/cubits/deleteImageId.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../data/repositories/DeleteImageId/deleteImageRepository.dart'; +import '../utils/api.dart'; + +abstract class DeleteImageState {} + +class DeleteImageInitial extends DeleteImageState {} + +class DeleteImageInProgress extends DeleteImageState {} + +class DeleteImageSuccess extends DeleteImageState { + final String message; + + DeleteImageSuccess(this.message); +} + +class DeleteImageFailure extends DeleteImageState { + final String errorMessage; + + DeleteImageFailure(this.errorMessage); +} + +class DeleteImageCubit extends Cubit { + final DeleteImageRepository _deleteImageRepository; + + DeleteImageCubit(this._deleteImageRepository) : super(DeleteImageInitial()); + + void setDeleteImage({required String imageId}) { + emit(DeleteImageInProgress()); + _deleteImageRepository.setDeleteImage(imageId: imageId).then((value) { + emit(DeleteImageSuccess(value["message"])); + }).catchError((e) { + ApiMessageAndCodeException apiMessageAndCodeException = e; + emit(DeleteImageFailure(apiMessageAndCodeException.errorMessage.toString())); + }); + } +} diff --git a/news-app/lib/cubits/deleteUserNewsCubit.dart b/news-app/lib/cubits/deleteUserNewsCubit.dart new file mode 100644 index 00000000..d444d1b0 --- /dev/null +++ b/news-app/lib/cubits/deleteUserNewsCubit.dart @@ -0,0 +1,44 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../data/repositories/DeleteUserNews/deleteUserNewsRepository.dart'; +import '../utils/api.dart'; + +abstract class DeleteUserNewsState {} + +class DeleteUserNewsInitial extends DeleteUserNewsState {} + +class DeleteUserNewsInProgress extends DeleteUserNewsState {} + +class DeleteUserNewsSuccess extends DeleteUserNewsState { + final String message; + + DeleteUserNewsSuccess(this.message); +} + +class DeleteUserNewsFailure extends DeleteUserNewsState { + final String errorMessage; + + DeleteUserNewsFailure(this.errorMessage); +} + +class DeleteUserNewsCubit extends Cubit { + final DeleteUserNewsRepository _deleteUserNewsRepository; + + DeleteUserNewsCubit(this._deleteUserNewsRepository) : super(DeleteUserNewsInitial()); + + void setDeleteUserNews({ + required String newsId, + }) { + emit(DeleteUserNewsInProgress()); + _deleteUserNewsRepository + .setDeleteUserNews( + newsId: newsId, + ) + .then((value) { + emit(DeleteUserNewsSuccess(value["message"])); + }).catchError((e) { + ApiMessageAndCodeException apiMessageAndCodeException = e; + emit(DeleteUserNewsFailure(apiMessageAndCodeException.errorMessage.toString())); + }); + } +} diff --git a/news-app/lib/cubits/featureSectionCubit.dart b/news-app/lib/cubits/featureSectionCubit.dart new file mode 100644 index 00000000..1acea8c2 --- /dev/null +++ b/news-app/lib/cubits/featureSectionCubit.dart @@ -0,0 +1,71 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/FeatureSectionModel.dart'; +import 'package:news/data/repositories/FeatureSection/sectionRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class SectionState {} + +class SectionInitial extends SectionState {} + +class SectionFetchInProgress extends SectionState {} + +class SectionFetchSuccess extends SectionState { + final List section; + final int totalCount; + final bool hasMoreFetchError; + final bool hasMore; + SectionFetchSuccess({required this.section, required this.totalCount, required this.hasMore, required this.hasMoreFetchError}); +} + +class SectionFetchFailure extends SectionState { + final String errorMessage; + + SectionFetchFailure(this.errorMessage); +} + +class SectionCubit extends Cubit { + final SectionRepository _sectionRepository; + + SectionCubit(this._sectionRepository) : super(SectionInitial()); + + void getSection({required String langId, String? latitude, String? longitude}) async { + try { + emit(SectionFetchInProgress()); + final result = await _sectionRepository.getSection(langId: langId, latitude: latitude, longitude: longitude, limit: limitOfAPIData.toString(), offset: "0", sectionOffset: "0"); + (!result[ERROR]) + ? emit(SectionFetchSuccess( + section: result['Section'], + totalCount: result[TOTAL], + hasMore: result['Section'].length < result[TOTAL], + hasMoreFetchError: false, + )) + : emit(SectionFetchFailure(result[MESSAGE])); + } catch (e) { + emit(SectionFetchFailure(e.toString())); + } + } + + bool hasMoreSections() { + return (state is SectionFetchSuccess) ? (state as SectionFetchSuccess).hasMore : false; + } + + void getMoreSections({required String langId, String? latitude, String? longitude}) async { + if (state is SectionFetchSuccess) { + try { + final result = await _sectionRepository.getSection( + langId: langId, latitude: latitude, longitude: longitude, limit: limitOfAPIData.toString(), offset: "0", sectionOffset: (state as SectionFetchSuccess).section.length.toString()); + if (!result[ERROR]) { + List updatedResults = (state as SectionFetchSuccess).section; + updatedResults.addAll(result['Section'] as List); + emit(SectionFetchSuccess(section: updatedResults, totalCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL])); + } else { + emit(SectionFetchFailure(result[MESSAGE])); + } + } catch (e) { + emit(SectionFetchSuccess( + section: (state as SectionFetchSuccess).section, hasMoreFetchError: true, totalCount: (state as SectionFetchSuccess).totalCount, hasMore: (state as SectionFetchSuccess).hasMore)); + } + } + } +} diff --git a/news-app/lib/cubits/generalNewsCubit.dart b/news-app/lib/cubits/generalNewsCubit.dart new file mode 100644 index 00000000..e758e086 --- /dev/null +++ b/news-app/lib/cubits/generalNewsCubit.dart @@ -0,0 +1,77 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +abstract class GeneralNewsState {} + +class GeneralNewsInitial extends GeneralNewsState {} + +class GeneralNewsFetchInProgress extends GeneralNewsState {} + +class GeneralNewsFetchSuccess extends GeneralNewsState { + final List generalNews; + final int totalCount; + final bool hasMoreFetchError; + final bool hasMore; + + GeneralNewsFetchSuccess({required this.totalCount, required this.hasMoreFetchError, required this.hasMore, required this.generalNews}); +} + +class GeneralNewsFetchFailure extends GeneralNewsState { + final String errorMessage; + + GeneralNewsFetchFailure(this.errorMessage); +} + +class GeneralNewsCubit extends Cubit { + GeneralNewsCubit() : super(GeneralNewsInitial()); + + Future getGeneralNews({required String langId, String? latitude, String? longitude, int? offset, String? newsSlug}) async { + emit(GeneralNewsInitial()); + try { + final body = {LANGUAGE_ID: langId, LIMIT: 20}; + + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + if (offset != null) body[OFFSET] = offset; + if (newsSlug != null && newsSlug != "null" && newsSlug.trim().isNotEmpty) body[SLUG] = newsSlug; // used in case of native link , details screen redirection + + final result = await Api.sendApiRequest(body: body, url: Api.getNewsApi); + if (!result[ERROR]) { + emit(GeneralNewsFetchSuccess( + generalNews: (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList(), totalCount: result[TOTAL], hasMoreFetchError: false, hasMore: result[DATA].length < result[TOTAL])); + } else { + emit(GeneralNewsFetchFailure(result[MESSAGE])); + } + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + bool hasMoreGeneralNews() { + return (state is GeneralNewsFetchSuccess) ? (state as GeneralNewsFetchSuccess).hasMore : false; + } + + void getMoreGeneralNews({required String langId, String? latitude, String? longitude}) async { + if (state is GeneralNewsFetchSuccess) { + try { + final result = await getGeneralNews(langId: langId, latitude: latitude, longitude: longitude, offset: (state as GeneralNewsFetchSuccess).generalNews.length); + if (!result[ERROR]) { + List updatedResults = (state as GeneralNewsFetchSuccess).generalNews; + updatedResults.addAll((result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList()); + emit(GeneralNewsFetchSuccess(generalNews: updatedResults, totalCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL])); + } else { + emit(GeneralNewsFetchFailure(result[MESSAGE])); + } + } catch (e) { + emit(GeneralNewsFetchSuccess( + generalNews: (state as GeneralNewsFetchSuccess).generalNews, + hasMoreFetchError: true, + totalCount: (state as GeneralNewsFetchSuccess).totalCount, + hasMore: (state as GeneralNewsFetchSuccess).hasMore)); + } + } + } +} diff --git a/news-app/lib/cubits/getSurveyAnswerCubit.dart b/news-app/lib/cubits/getSurveyAnswerCubit.dart new file mode 100644 index 00000000..545b0191 --- /dev/null +++ b/news-app/lib/cubits/getSurveyAnswerCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/GetSurveyAnswer/getSurveyAnsRepository.dart'; + +abstract class GetSurveyAnsState {} + +class GetSurveyAnsInitial extends GetSurveyAnsState {} + +class GetSurveyAnsFetchInProgress extends GetSurveyAnsState {} + +class GetSurveyAnsFetchSuccess extends GetSurveyAnsState { + final List getSurveyAns; + + GetSurveyAnsFetchSuccess({required this.getSurveyAns}); +} + +class GetSurveyAnsFetchFailure extends GetSurveyAnsState { + final String errorMessage; + + GetSurveyAnsFetchFailure(this.errorMessage); +} + +class GetSurveyAnsCubit extends Cubit { + final GetSurveyAnsRepository _getSurveyAnsRepository; + + GetSurveyAnsCubit(this._getSurveyAnsRepository) : super(GetSurveyAnsInitial()); + + void getSurveyAns({required String langId}) async { + try { + emit(GetSurveyAnsFetchInProgress()); + final result = await _getSurveyAnsRepository.getSurveyAns(langId: langId); + emit(GetSurveyAnsFetchSuccess(getSurveyAns: result['GetSurveyAns'])); + } catch (e) { + emit(GetSurveyAnsFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/getUserDataByIdCubit.dart b/news-app/lib/cubits/getUserDataByIdCubit.dart new file mode 100644 index 00000000..92ac2dc0 --- /dev/null +++ b/news-app/lib/cubits/getUserDataByIdCubit.dart @@ -0,0 +1,35 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/GetUserById/getUserByIdRepository.dart'; + +abstract class GetUserByIdState {} + +class GetUserByIdInitial extends GetUserByIdState {} + +class GetUserByIdFetchInProgress extends GetUserByIdState {} + +class GetUserByIdFetchSuccess extends GetUserByIdState { + var result; + + GetUserByIdFetchSuccess({required this.result}); +} + +class GetUserByIdFetchFailure extends GetUserByIdState { + final String errorMessage; + + GetUserByIdFetchFailure(this.errorMessage); +} + +class GetUserByIdCubit extends Cubit { + final GetUserByIdRepository _getUserByIdRepository; + + GetUserByIdCubit(this._getUserByIdRepository) : super(GetUserByIdInitial()); + + void getUserById() { + emit(GetUserByIdFetchInProgress()); + _getUserByIdRepository.getUserById().then((value) { + emit(GetUserByIdFetchSuccess(result: value)); + }).catchError((e) { + emit(GetUserByIdFetchFailure(e.toString())); + }); + } +} diff --git a/news-app/lib/cubits/getUserDraftedNewsCubit.dart b/news-app/lib/cubits/getUserDraftedNewsCubit.dart new file mode 100644 index 00000000..b00269c5 --- /dev/null +++ b/news-app/lib/cubits/getUserDraftedNewsCubit.dart @@ -0,0 +1,76 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +abstract class GetUserDraftedNewsState {} + +class GetUserDraftedNewsInitial extends GetUserDraftedNewsState {} + +class GetUserDraftedNewsFetchInProgress extends GetUserDraftedNewsState {} + +class GetUserDraftedNewsFetchSuccess extends GetUserDraftedNewsState { + final List GetUserDraftedNews; + final int totalGetUserDraftedNewsCount; + final bool hasMoreFetchError; + final bool hasMore; + + GetUserDraftedNewsFetchSuccess({required this.GetUserDraftedNews, required this.totalGetUserDraftedNewsCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class GetUserDraftedNewsFetchFailure extends GetUserDraftedNewsState { + final String errorMessage; + + GetUserDraftedNewsFetchFailure(this.errorMessage); +} + +class GetUserDraftedNewsCubit extends Cubit { + GetUserDraftedNewsCubit() : super(GetUserDraftedNewsInitial()); + + void getGetUserDraftedNews({int? userId}) async { + try { + emit(GetUserDraftedNewsFetchInProgress()); + final apiUrl = "${Api.getDraftNewsApi}/${userId}"; + final result = await Api.sendApiRequest(body: null, url: apiUrl); + (!result[ERROR]) + ? emit(GetUserDraftedNewsFetchSuccess( + GetUserDraftedNews: (result[DATA][NEWS][DATA] as List).map((e) => NewsModel.fromJson(e)).toList(), + totalGetUserDraftedNewsCount: result[DATA][NEWS][TOTAL], + hasMoreFetchError: false, + hasMore: (result[DATA][NEWS][DATA] as List).length < result[DATA][NEWS][TOTAL])) + : emit(GetUserDraftedNewsFetchFailure(result[MESSAGE])); + } catch (e) { + emit(GetUserDraftedNewsFetchFailure(e.toString())); + } + } + + bool hasMoreGetUserDraftedNews() { + return (state is GetUserDraftedNewsFetchSuccess) ? (state as GetUserDraftedNewsFetchSuccess).hasMore : false; + } + + void deleteNews(int index) { + if (state is GetUserDraftedNewsFetchSuccess) { + List newsList = List.from((state as GetUserDraftedNewsFetchSuccess).GetUserDraftedNews)..removeAt(index); + + emit(GetUserDraftedNewsFetchSuccess( + GetUserDraftedNews: newsList, + hasMore: (state as GetUserDraftedNewsFetchSuccess).hasMore, + hasMoreFetchError: false, + totalGetUserDraftedNewsCount: (state as GetUserDraftedNewsFetchSuccess).totalGetUserDraftedNewsCount - 1)); + } + } + + void deleteImageId(int index) { + if (state is GetUserDraftedNewsFetchSuccess) { + List newsList = (state as GetUserDraftedNewsFetchSuccess).GetUserDraftedNews; + + newsList[index].imageDataList!.removeAt(index); + + emit(GetUserDraftedNewsFetchSuccess( + GetUserDraftedNews: newsList, + hasMore: (state as GetUserDraftedNewsFetchSuccess).hasMore, + hasMoreFetchError: false, + totalGetUserDraftedNewsCount: (state as GetUserDraftedNewsFetchSuccess).totalGetUserDraftedNewsCount)); + } + } +} diff --git a/news-app/lib/cubits/getUserNewsCubit.dart b/news-app/lib/cubits/getUserNewsCubit.dart new file mode 100644 index 00000000..4e3f31cc --- /dev/null +++ b/news-app/lib/cubits/getUserNewsCubit.dart @@ -0,0 +1,87 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/GetUserNews/getUserNewsRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class GetUserNewsState {} + +class GetUserNewsInitial extends GetUserNewsState {} + +class GetUserNewsFetchInProgress extends GetUserNewsState {} + +class GetUserNewsFetchSuccess extends GetUserNewsState { + final List getUserNews; + final int totalGetUserNewsCount; + final bool hasMoreFetchError; + final bool hasMore; + + GetUserNewsFetchSuccess({required this.getUserNews, required this.totalGetUserNewsCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class GetUserNewsFetchFailure extends GetUserNewsState { + final String errorMessage; + + GetUserNewsFetchFailure(this.errorMessage); +} + +class GetUserNewsCubit extends Cubit { + final GetUserNewsRepository _getUserNewsRepository; + + GetUserNewsCubit(this._getUserNewsRepository) : super(GetUserNewsInitial()); + + void getGetUserNews({String? latitude, String? longitude}) async { + try { + emit(GetUserNewsFetchInProgress()); + final result = await _getUserNewsRepository.getGetUserNews(limit: limitOfAPIData.toString(), offset: "0", latitude: latitude, longitude: longitude); + (!result[ERROR]) + ? emit(GetUserNewsFetchSuccess( + getUserNews: result['GetUserNews'], totalGetUserNewsCount: result[TOTAL], hasMoreFetchError: false, hasMore: (result['GetUserNews'] as List).length < result[TOTAL])) + : emit(GetUserNewsFetchFailure(result[MESSAGE])); + } catch (e) { + emit(GetUserNewsFetchFailure(e.toString())); + } + } + + bool hasMoreGetUserNews() { + return (state is GetUserNewsFetchSuccess) ? (state as GetUserNewsFetchSuccess).hasMore : false; + } + + void getMoreGetUserNews({String? latitude, String? longitude}) async { + if (state is GetUserNewsFetchSuccess) { + try { + final result = await _getUserNewsRepository.getGetUserNews( + limit: limitOfAPIData.toString(), offset: (state as GetUserNewsFetchSuccess).getUserNews.length.toString(), latitude: latitude, longitude: longitude); + List updatedResults = (state as GetUserNewsFetchSuccess).getUserNews; + updatedResults.addAll(result['GetUserNews'] as List); + emit(GetUserNewsFetchSuccess(getUserNews: updatedResults, totalGetUserNewsCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL])); + } catch (e) { + emit(GetUserNewsFetchSuccess( + getUserNews: (state as GetUserNewsFetchSuccess).getUserNews, + hasMoreFetchError: true, + totalGetUserNewsCount: (state as GetUserNewsFetchSuccess).totalGetUserNewsCount, + hasMore: (state as GetUserNewsFetchSuccess).hasMore)); + } + } + } + + void deleteNews(int index) { + if (state is GetUserNewsFetchSuccess) { + List newsList = List.from((state as GetUserNewsFetchSuccess).getUserNews)..removeAt(index); + + emit(GetUserNewsFetchSuccess( + getUserNews: newsList, hasMore: (state as GetUserNewsFetchSuccess).hasMore, hasMoreFetchError: false, totalGetUserNewsCount: (state as GetUserNewsFetchSuccess).totalGetUserNewsCount - 1)); + } + } + + void deleteImageId(int index) { + if (state is GetUserNewsFetchSuccess) { + List newsList = (state as GetUserNewsFetchSuccess).getUserNews; + + newsList[index].imageDataList!.removeAt(index); + + emit(GetUserNewsFetchSuccess( + getUserNews: newsList, hasMore: (state as GetUserNewsFetchSuccess).hasMore, hasMoreFetchError: false, totalGetUserNewsCount: (state as GetUserNewsFetchSuccess).totalGetUserNewsCount)); + } + } +} diff --git a/news-app/lib/cubits/languageCubit.dart b/news-app/lib/cubits/languageCubit.dart new file mode 100644 index 00000000..a6a6c83a --- /dev/null +++ b/news-app/lib/cubits/languageCubit.dart @@ -0,0 +1,47 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/appLanguageModel.dart'; +import 'package:news/data/repositories/language/languageRepository.dart'; + +abstract class LanguageState {} + +class LanguageInitial extends LanguageState {} + +class LanguageFetchInProgress extends LanguageState {} + +class LanguageFetchSuccess extends LanguageState { + final List language; + + LanguageFetchSuccess({required this.language}); +} + +class LanguageFetchFailure extends LanguageState { + final String errorMessage; + + LanguageFetchFailure(this.errorMessage); +} + +class LanguageCubit extends Cubit { + final LanguageRepository _languageRepository; + + LanguageCubit(this._languageRepository) : super(LanguageInitial()); + + Future> getLanguage() async { + try { + emit(LanguageFetchInProgress()); + final result = await _languageRepository.getLanguage(); + emit(LanguageFetchSuccess(language: result['Language'])); + return result['Language']; + } catch (e) { + emit(LanguageFetchFailure(e.toString())); + return []; + } + } + + List langList() { + return (state is LanguageFetchSuccess) ? (state as LanguageFetchSuccess).language : []; + } + + int getLanguageIndex({required String langName}) { + return (state is LanguageFetchSuccess) ? (state as LanguageFetchSuccess).language.indexWhere((element) => element.language == langName) : 0; + } +} diff --git a/news-app/lib/cubits/languageJsonCubit.dart b/news-app/lib/cubits/languageJsonCubit.dart new file mode 100644 index 00000000..dd680841 --- /dev/null +++ b/news-app/lib/cubits/languageJsonCubit.dart @@ -0,0 +1,57 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/LanguageJson/languageJsonRepository.dart'; +import 'package:news/utils/appLanguages.dart'; + +abstract class LanguageJsonState {} + +class LanguageJsonInitial extends LanguageJsonState {} + +class LanguageJsonFetchInProgress extends LanguageJsonState {} + +class LanguageJsonFetchSuccess extends LanguageJsonState { + Map languageJson; + + LanguageJsonFetchSuccess({required this.languageJson}); +} + +class LanguageJsonFetchFailure extends LanguageJsonState { + final String errorMessage; + + LanguageJsonFetchFailure(this.errorMessage); +} + +class LanguageJsonCubit extends Cubit { + final LanguageJsonRepository _languageJsonRepository; + + LanguageJsonCubit(this._languageJsonRepository) : super(LanguageJsonInitial()); + + void fetchCurrentLanguageAndLabels(String currentLanguage) async { + try { + emit(LanguageJsonFetchInProgress()); + + await _languageJsonRepository.fetchLanguageLabels(currentLanguage).then((value) { + emit(LanguageJsonFetchSuccess(languageJson: value)); + }); + } catch (e) { + emit(LanguageJsonFetchSuccess(languageJson: appLanguageLabelKeys)); + } + } + + void getLanguageJson({required String lanCode}) async { + try { + emit(LanguageJsonFetchInProgress()); + final result = await _languageJsonRepository.getLanguageJson(lanCode: lanCode); + emit(LanguageJsonFetchSuccess(languageJson: result)); + } catch (e) { + emit(LanguageJsonFetchSuccess(languageJson: appLanguageLabelKeys)); + } + } + + String getTranslatedLabels(String label) { + if (state is LanguageJsonFetchSuccess) { + return (state as LanguageJsonFetchSuccess).languageJson[label] ?? appLanguageLabelKeys[label] ?? label; + } else { + return appLanguageLabelKeys[label] ?? label; + } + } +} diff --git a/news-app/lib/cubits/liveStreamCubit.dart b/news-app/lib/cubits/liveStreamCubit.dart new file mode 100644 index 00000000..ef291e8f --- /dev/null +++ b/news-app/lib/cubits/liveStreamCubit.dart @@ -0,0 +1,39 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/LiveStreamingModel.dart'; +import 'package:news/data/repositories/LiveStream/liveStreamRepository.dart'; +import 'package:news/utils/strings.dart'; + +abstract class LiveStreamState {} + +class LiveStreamInitial extends LiveStreamState {} + +class LiveStreamFetchInProgress extends LiveStreamState {} + +class LiveStreamFetchSuccess extends LiveStreamState { + final List liveStream; + + LiveStreamFetchSuccess({required this.liveStream}); +} + +class LiveStreamFetchFailure extends LiveStreamState { + final String errorMessage; + + LiveStreamFetchFailure(this.errorMessage); +} + +class LiveStreamCubit extends Cubit { + final LiveStreamRepository _liveStreamRepository; + + LiveStreamCubit(this._liveStreamRepository) : super(LiveStreamInitial()); + + void getLiveStream({required String langId}) async { + try { + emit(LiveStreamFetchInProgress()); + final result = await _liveStreamRepository.getLiveStream(langId: langId); + + (!result[ERROR]) ? emit(LiveStreamFetchSuccess(liveStream: result['LiveStream'])) : emit(LiveStreamFetchFailure(result[MESSAGE])); + } catch (e) { + emit(LiveStreamFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/locationCityCubit.dart b/news-app/lib/cubits/locationCityCubit.dart new file mode 100644 index 00000000..c36c633f --- /dev/null +++ b/news-app/lib/cubits/locationCityCubit.dart @@ -0,0 +1,73 @@ +import 'package:news/utils/api.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/locationCityModel.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class LocationCityState {} + +class LocationCityInitial extends LocationCityState {} + +class LocationCityFetchInProgress extends LocationCityState {} + +class LocationCityFetchSuccess extends LocationCityState { + final List locationCity; + final int totalLocations; + final bool hasMoreFetchError; + final bool hasMore; + + LocationCityFetchSuccess({required this.locationCity, required this.totalLocations, required this.hasMoreFetchError, required this.hasMore}); +} + +class LocationCityFetchFailure extends LocationCityState { + final String errorMessage; + + LocationCityFetchFailure(this.errorMessage); +} + +class LocationCityCubit extends Cubit { + LocationCityCubit() : super(LocationCityInitial()); + + void getLocationCity() async { + try { + final result = await Api.sendApiRequest(body: {LIMIT: limitOfAPIData, OFFSET: 0}, url: Api.getLocationCityApi); + emit(LocationCityFetchSuccess( + totalLocations: result[TOTAL], + locationCity: (result[DATA] as List).map((e) => LocationCityModel.fromJson(e)).toList(), + hasMore: (result[DATA] as List).map((e) => LocationCityModel.fromJson(e)).toList().length < result[TOTAL], + hasMoreFetchError: false)); + } catch (e) { + emit(LocationCityFetchFailure(e.toString())); + } + } + + void getMoreLocationCity() async { + if (state is LocationCityFetchSuccess) { + try { + await Api.sendApiRequest(body: {LIMIT: limitOfAPIData, OFFSET: (state as LocationCityFetchSuccess).locationCity.length.toString()}, url: Api.getLocationCityApi).then((value) { + List updatedResults = (state as LocationCityFetchSuccess).locationCity; + updatedResults.addAll((value[DATA] as List).map((e) => LocationCityModel.fromJson(e)).toList()); + emit(LocationCityFetchSuccess(locationCity: updatedResults, totalLocations: value[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < value[TOTAL])); + }); + } catch (e) { + emit(LocationCityFetchSuccess( + locationCity: (state as LocationCityFetchSuccess).locationCity, + totalLocations: (state as LocationCityFetchSuccess).totalLocations, + hasMoreFetchError: true, + hasMore: (state as LocationCityFetchSuccess).hasMore)); + } + } + } + + bool hasMoreLocation() { + return (state is LocationCityFetchSuccess) ? (state as LocationCityFetchSuccess).hasMore : false; + } + + List getLocationCityList() { + return (state is LocationCityFetchSuccess) ? (state as LocationCityFetchSuccess).locationCity : []; + } + + int getLocationIndex({required String locationName}) { + return (state is LocationCityFetchSuccess) ? (state as LocationCityFetchSuccess).locationCity.indexWhere((element) => element.locationName == locationName) : 0; + } +} diff --git a/news-app/lib/cubits/otherPagesCubit.dart b/news-app/lib/cubits/otherPagesCubit.dart new file mode 100644 index 00000000..ff0409ec --- /dev/null +++ b/news-app/lib/cubits/otherPagesCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/OtherPageModel.dart'; +import 'package:news/data/repositories/OtherPages/otherPagesRepository.dart'; + +abstract class OtherPageState {} + +class OtherPageInitial extends OtherPageState {} + +class OtherPageFetchInProgress extends OtherPageState {} + +class OtherPageFetchSuccess extends OtherPageState { + final List otherPage; + + OtherPageFetchSuccess({required this.otherPage}); +} + +class OtherPageFetchFailure extends OtherPageState { + final String errorMessage; + + OtherPageFetchFailure(this.errorMessage); +} + +class OtherPageCubit extends Cubit { + final OtherPageRepository _otherPageRepository; + + OtherPageCubit(this._otherPageRepository) : super(OtherPageInitial()); + + void getOtherPage({required String langId}) async { + emit(OtherPageFetchInProgress()); + try { + final result = await _otherPageRepository.getOtherPage(langId: langId); + emit(OtherPageFetchSuccess(otherPage: result['OtherPage'])); + } catch (e) { + emit(OtherPageFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/privacyTermsCubit.dart b/news-app/lib/cubits/privacyTermsCubit.dart new file mode 100644 index 00000000..951ad3b0 --- /dev/null +++ b/news-app/lib/cubits/privacyTermsCubit.dart @@ -0,0 +1,38 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/OtherPageModel.dart'; +import 'package:news/data/repositories/OtherPages/otherPagesRepository.dart'; + +abstract class PrivacyTermsState {} + +class PrivacyTermsInitial extends PrivacyTermsState {} + +class PrivacyTermsFetchInProgress extends PrivacyTermsState {} + +class PrivacyTermsFetchSuccess extends PrivacyTermsState { + final OtherPageModel termsPolicy; + final OtherPageModel privacyPolicy; + + PrivacyTermsFetchSuccess({required this.termsPolicy, required this.privacyPolicy}); +} + +class PrivacyTermsFetchFailure extends PrivacyTermsState { + final String errorMessage; + + PrivacyTermsFetchFailure(this.errorMessage); +} + +class PrivacyTermsCubit extends Cubit { + final OtherPageRepository _otherPageRepository; + + PrivacyTermsCubit(this._otherPageRepository) : super(PrivacyTermsInitial()); + + void getPrivacyTerms({required String langId}) async { + emit(PrivacyTermsFetchInProgress()); + try { + final Map result = await _otherPageRepository.getPrivacyTermsPage(langId: langId); + emit(PrivacyTermsFetchSuccess(privacyPolicy: OtherPageModel.fromPrivacyTermsJson(result['privacy_policy']), termsPolicy: OtherPageModel.fromPrivacyTermsJson(result['terms_policy']))); + } catch (e) { + emit(PrivacyTermsFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/relatedNewsCubit.dart b/news-app/lib/cubits/relatedNewsCubit.dart new file mode 100644 index 00000000..3b6a7355 --- /dev/null +++ b/news-app/lib/cubits/relatedNewsCubit.dart @@ -0,0 +1,77 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/RelatedNews/relatedNewsRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class RelatedNewsState {} + +class RelatedNewsInitial extends RelatedNewsState {} + +class RelatedNewsFetchInProgress extends RelatedNewsState {} + +class RelatedNewsFetchSuccess extends RelatedNewsState { + final List relatedNews; + final int totalRelatedNewsCount; + final bool hasMoreFetchError; + final bool hasMore; + + RelatedNewsFetchSuccess({required this.relatedNews, required this.totalRelatedNewsCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class RelatedNewsFetchFailure extends RelatedNewsState { + final String errorMessage; + + RelatedNewsFetchFailure(this.errorMessage); +} + +class RelatedNewsCubit extends Cubit { + final RelatedNewsRepository _relatedNewsRepository; + + RelatedNewsCubit(this._relatedNewsRepository) : super(RelatedNewsInitial()); + + void getRelatedNews({required String langId, String? catId, String? subCatId}) async { + try { + emit(RelatedNewsFetchInProgress()); + final result = await _relatedNewsRepository.getRelatedNews(perPage: limitOfAPIData.toString(), offset: "0", langId: langId, catId: catId, subCatId: subCatId); + emit(RelatedNewsFetchSuccess( + relatedNews: result['RelatedNews'], totalRelatedNewsCount: result[TOTAL], hasMoreFetchError: false, hasMore: (result['RelatedNews'] as List).length < result[TOTAL])); + } catch (e) { + emit(RelatedNewsFetchFailure(e.toString())); + } + } + + bool hasMoreRelatedNews() { + return (state is RelatedNewsFetchSuccess) ? (state as RelatedNewsFetchSuccess).hasMore : false; + } + + void getMoreRelatedNews({required String langId, String? catId, String? subCatId, String? latitude, String? longitude}) async { + if (state is RelatedNewsFetchSuccess) { + try { + final result = await _relatedNewsRepository.getRelatedNews( + perPage: limitOfAPIData.toString(), + offset: (state as RelatedNewsFetchSuccess).relatedNews.length.toString(), + langId: langId, + subCatId: subCatId, + catId: catId, + latitude: latitude, + longitude: longitude); + List updatedResults = (state as RelatedNewsFetchSuccess).relatedNews; + updatedResults.addAll(result['RelatedNews'] as List); + emit(RelatedNewsFetchSuccess( + relatedNews: updatedResults, + totalRelatedNewsCount: result[TOTAL], + hasMoreFetchError: false, + hasMore: updatedResults.length < result[TOTAL], + )); + } catch (e) { + emit(RelatedNewsFetchSuccess( + relatedNews: (state as RelatedNewsFetchSuccess).relatedNews, + hasMoreFetchError: true, + totalRelatedNewsCount: (state as RelatedNewsFetchSuccess).totalRelatedNewsCount, + hasMore: (state as RelatedNewsFetchSuccess).hasMore, + )); + } + } + } +} diff --git a/news-app/lib/cubits/rssFeedCubit.dart b/news-app/lib/cubits/rssFeedCubit.dart new file mode 100644 index 00000000..6f1307bc --- /dev/null +++ b/news-app/lib/cubits/rssFeedCubit.dart @@ -0,0 +1,71 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/RSSFeedModel.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +abstract class RSSFeedState {} + +class RSSFeedInitial extends RSSFeedState {} + +class RSSFeedFetchInProgress extends RSSFeedState {} + +class RSSFeedFetchSuccess extends RSSFeedState { + final List RSSFeed; + final int totalRSSFeedCount; + final bool hasMoreFetchError; + final bool hasMore; + + RSSFeedFetchSuccess({required this.RSSFeed, required this.totalRSSFeedCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class RSSFeedFetchFailure extends RSSFeedState { + final String errorMessage; + + RSSFeedFetchFailure(this.errorMessage); +} + +class RSSFeedCubit extends Cubit { + RSSFeedCubit() : super(RSSFeedInitial()); + int limit = 10; + + void getRSSFeed({required String langId, String? categoryId, String? subCategoryId}) async { + try { + emit(RSSFeedFetchInProgress()); + + final result = await Api.sendApiRequest( + body: {LANGUAGE_ID: langId, if (categoryId != null) CATEGORY_ID: categoryId, if (subCategoryId != null) SUBCAT_ID: subCategoryId, LIMIT: limit, OFFSET: 0}, url: Api.rssFeedApi); + + (!result[ERROR]) + ? emit(RSSFeedFetchSuccess( + RSSFeed: (result[DATA] as List).map((e) => RSSFeedModel.fromJson(e)).toList(), totalRSSFeedCount: result[TOTAL], hasMoreFetchError: false, hasMore: result.length < result[TOTAL])) + : emit(RSSFeedFetchFailure(result[MESSAGE])); + } catch (e) { + emit(RSSFeedFetchFailure(e.toString())); + } + } + + bool hasMoreRSSFeed() { + return (state is RSSFeedFetchSuccess) ? (state as RSSFeedFetchSuccess).hasMore : false; + } + + void getMoreRSSFeed({required String langId}) async { + if (state is RSSFeedFetchSuccess) { + try { + final result = await Api.sendApiRequest(body: {LANGUAGE_ID: langId, LIMIT: limit, OFFSET: (state as RSSFeedFetchSuccess).RSSFeed.length}, url: Api.rssFeedApi); + if (!result[ERROR]) { + List updatedResults = (state as RSSFeedFetchSuccess).RSSFeed; + updatedResults.addAll((result[DATA] as List).map((e) => RSSFeedModel.fromJson(e)).toList()); + emit(RSSFeedFetchSuccess(RSSFeed: updatedResults, totalRSSFeedCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL])); + } else { + emit(RSSFeedFetchFailure(result[MESSAGE])); + } + } catch (e) { + emit(RSSFeedFetchSuccess( + RSSFeed: (state as RSSFeedFetchSuccess).RSSFeed, + hasMoreFetchError: true, + totalRSSFeedCount: (state as RSSFeedFetchSuccess).totalRSSFeedCount, + hasMore: (state as RSSFeedFetchSuccess).hasMore)); + } + } + } +} diff --git a/news-app/lib/cubits/sectionByIdCubit.dart b/news-app/lib/cubits/sectionByIdCubit.dart new file mode 100644 index 00000000..30b0edae --- /dev/null +++ b/news-app/lib/cubits/sectionByIdCubit.dart @@ -0,0 +1,132 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/SectionById/sectionByIdRepository.dart'; +import 'package:news/utils/strings.dart'; + +abstract class SectionByIdState {} + +class SectionByIdInitial extends SectionByIdState {} + +class SectionByIdFetchInProgress extends SectionByIdState {} + +class SectionByIdFetchSuccess extends SectionByIdState { + final List newsModel; + final List breakNewsModel; + final int totalCount; + final String type; + final bool hasMoreFetchError; + final bool hasMore; + + SectionByIdFetchSuccess({required this.newsModel, required this.breakNewsModel, required this.totalCount, required this.type, required this.hasMore, required this.hasMoreFetchError}); +} + +class SectionByIdFetchFailure extends SectionByIdState { + final String errorMessage; + + SectionByIdFetchFailure(this.errorMessage); +} + +class SectionByIdCubit extends Cubit { + final SectionByIdRepository _sectionByIdRepository; + final int limitOfFeaturedSectionData = 10; + + SectionByIdCubit(this._sectionByIdRepository) : super(SectionByIdInitial()); + + void getSectionById({required String langId, required String sectionId, String? latitude, String? longitude, String? limit, String? offset}) async { + try { + emit(SectionByIdFetchInProgress()); + final result = await _sectionByIdRepository.getSectionById( + offset: offset ?? "0", limit: limit ?? limitOfFeaturedSectionData.toString(), langId: langId, sectionId: sectionId, latitude: latitude, longitude: longitude); + if (!result[ERROR]) { + int totalSections = (result[DATA][0].newsType == "news" || result[DATA][0].newsType == "user_choice") + ? result[DATA][0].newsTotal! + : result[DATA][0].newsType == "breaking_news" + ? result[DATA][0].breakNewsTotal! + : result[DATA][0].videosTotal!; + List newsSection = (result[DATA][0].newsType == "news" || result[DATA][0].newsType == "user_choice") + ? result[DATA][0].news! + : result[DATA][0].videosType == "news" + ? result[DATA][0].videos! + : []; + List brNewsSection = result[DATA][0].newsType == "breaking_news" + ? result[DATA][0].breakNews! + : result[DATA][0].videosType == "breaking_news" + ? result[DATA][0].breakVideos! + : []; + + emit(SectionByIdFetchSuccess( + newsModel: newsSection, + breakNewsModel: brNewsSection, + totalCount: totalSections, + type: result[DATA][0].newsType!, + hasMore: ((result[DATA][0].newsType! == NEWS) ? (newsSection.length < totalSections) : (brNewsSection.length < totalSections)), + hasMoreFetchError: false)); + } else { + emit(SectionByIdFetchFailure(result[MESSAGE])); + } + } catch (e) { + if (!isClosed) emit(SectionByIdFetchFailure(e.toString())); //isClosed checked to resolve Bad state issue of Bloc + } + } + + bool hasMoreSections() { + return (state is SectionByIdFetchSuccess) ? (state as SectionByIdFetchSuccess).hasMore : false; + } + + void getMoreSectionById({required String langId, required String sectionId, String? latitude, String? longitude, String? limit, String? offset}) async { + if (state is SectionByIdFetchSuccess) { + try { + final result = await _sectionByIdRepository.getSectionById( + sectionId: sectionId, + latitude: latitude, + longitude: longitude, + limit: limit ?? limitOfFeaturedSectionData.toString(), + offset: (state as SectionByIdFetchSuccess).newsModel.length.toString(), + langId: langId); + List updatedResults = []; + List updatedBrResults = []; + + if (result[DATA][0].newsType! == NEWS || result[DATA][0].videosType == NEWS) { + updatedResults = (state as SectionByIdFetchSuccess).newsModel; + List newValues = (result[DATA][0].newsType == "news" || result[DATA][0].newsType == "user_choice") + ? result[DATA][0].news ?? [] + : result[DATA][0].videosType == "news" + ? result[DATA][0].videos ?? [] + : []; + updatedResults.addAll(newValues); + } else { + updatedBrResults = (state as SectionByIdFetchSuccess).breakNewsModel; + List newValues = result[DATA][0].newsType == "breaking_news" + ? result[DATA][0].breakNews! + : result[DATA][0].videosType == "breaking_news" + ? result[DATA][0].breakVideos! + : []; + updatedBrResults.addAll(newValues); + } + + int totalCount = (result[DATA][0].newsType == "news" || result[DATA][0].newsType == "user_choice") + ? result[DATA][0].newsTotal! + : result[DATA][0].newsType == "breaking_news" + ? result[DATA][0].breakNewsTotal! + : result[DATA][0].videosTotal!; + + emit(SectionByIdFetchSuccess( + newsModel: updatedResults, + breakNewsModel: updatedBrResults, + type: result[DATA][0].newsType!, + totalCount: totalCount, + hasMoreFetchError: false, + hasMore: (result[DATA][0].newsType! == NEWS || result[DATA][0].videosType == NEWS) ? (updatedResults.length < totalCount) : (updatedBrResults.length < totalCount))); + } catch (e) { + emit(SectionByIdFetchSuccess( + type: (state as SectionByIdFetchSuccess).type, + breakNewsModel: (state as SectionByIdFetchSuccess).breakNewsModel, + newsModel: (state as SectionByIdFetchSuccess).newsModel, + hasMoreFetchError: (e.toString() == "No Data Found") ? false : true, + totalCount: (state as SectionByIdFetchSuccess).totalCount, + hasMore: (state as SectionByIdFetchSuccess).hasMore)); + } + } + } +} diff --git a/news-app/lib/cubits/setNewsViewsCubit.dart b/news-app/lib/cubits/setNewsViewsCubit.dart new file mode 100644 index 00000000..f5979111 --- /dev/null +++ b/news-app/lib/cubits/setNewsViewsCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/SetNewsViews/setNewsViewsRepository.dart'; +import 'package:news/utils/api.dart'; + +abstract class SetNewsViewsState {} + +class SetNewsViewsInitial extends SetNewsViewsState {} + +class SetNewsViewsInProgress extends SetNewsViewsState {} + +class SetNewsViewsSuccess extends SetNewsViewsState { + final String message; + + SetNewsViewsSuccess(this.message); +} + +class SetNewsViewsFailure extends SetNewsViewsState { + final String errorMessage; + + SetNewsViewsFailure(this.errorMessage); +} + +class SetNewsViewsCubit extends Cubit { + final SetNewsViewsRepository setNewsViewsRepository; + + SetNewsViewsCubit(this.setNewsViewsRepository) : super(SetNewsViewsInitial()); + + void setNewsViews({required String newsId, required bool isBreakingNews}) { + emit(SetNewsViewsInProgress()); + setNewsViewsRepository.setNewsViews(newsId: newsId, isBreakingNews: isBreakingNews).then((value) { + emit(SetNewsViewsSuccess(value["message"])); + }).catchError((e) { + ApiMessageAndCodeException apiMessageAndCodeException = e; + emit(SetNewsViewsFailure(apiMessageAndCodeException.errorMessage.toString())); + }); + } +} diff --git a/news-app/lib/cubits/setSurveyAnswerCubit.dart b/news-app/lib/cubits/setSurveyAnswerCubit.dart new file mode 100644 index 00000000..6d6d9d0d --- /dev/null +++ b/news-app/lib/cubits/setSurveyAnswerCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/SetSurveyAnswer/setSurveyAnsRepository.dart'; + +abstract class SetSurveyAnsState {} + +class SetSurveyAnsInitial extends SetSurveyAnsState {} + +class SetSurveyAnsFetchInProgress extends SetSurveyAnsState {} + +class SetSurveyAnsFetchSuccess extends SetSurveyAnsState { + var setSurveyAns; + + SetSurveyAnsFetchSuccess({required this.setSurveyAns}); +} + +class SetSurveyAnsFetchFailure extends SetSurveyAnsState { + final String errorMessage; + + SetSurveyAnsFetchFailure(this.errorMessage); +} + +class SetSurveyAnsCubit extends Cubit { + final SetSurveyAnsRepository _setSurveyAnsRepository; + + SetSurveyAnsCubit(this._setSurveyAnsRepository) : super(SetSurveyAnsInitial()); + + void setSurveyAns({required String queId, required String optId}) async { + try { + emit(SetSurveyAnsFetchInProgress()); + final result = await _setSurveyAnsRepository.setSurveyAns(queId: queId, optId: optId); + + emit(SetSurveyAnsFetchSuccess(setSurveyAns: result['SetSurveyAns'])); + } catch (e) { + emit(SetSurveyAnsFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/settingCubit.dart b/news-app/lib/cubits/settingCubit.dart new file mode 100644 index 00000000..bd91fbe0 --- /dev/null +++ b/news-app/lib/cubits/settingCubit.dart @@ -0,0 +1,41 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/SettingsModel.dart'; +import 'package:news/data/repositories/Settings/settingRepository.dart'; + +class SettingsState { + final SettingsModel? settingsModel; + + SettingsState({this.settingsModel}); +} + +class SettingsCubit extends Cubit { + final SettingsRepository _settingsRepository; + + SettingsCubit(this._settingsRepository) : super(SettingsState()) { + _getCurrentSettings(); + } + + void _getCurrentSettings() { + emit(SettingsState(settingsModel: SettingsModel.fromJson(_settingsRepository.getCurrentSettings()))); + } + + SettingsModel getSettings() { + return state.settingsModel!; + } + + void changeShowIntroSlider(bool value) { + _settingsRepository.changeIntroSlider(value); + emit(SettingsState(settingsModel: state.settingsModel!.copyWith(showIntroSlider: value))); + } + + void changeFcmToken(String value) { + _settingsRepository.changeFcmToken(value); + emit(SettingsState(settingsModel: state.settingsModel!.copyWith(token: value))); + } + + void changeNotification(bool value) { + //set HiveBoxKey value for Enabled Notifications + _settingsRepository.changeNotification(value); + emit(SettingsState(settingsModel: state.settingsModel!.copyWith(notification: value))); + } +} diff --git a/news-app/lib/cubits/slugCheckCubit.dart b/news-app/lib/cubits/slugCheckCubit.dart new file mode 100644 index 00000000..377e7882 --- /dev/null +++ b/news-app/lib/cubits/slugCheckCubit.dart @@ -0,0 +1,38 @@ +import 'package:news/utils/api.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class SlugCheckState {} + +class SlugCheckInitial extends SlugCheckState {} + +class SlugCheckFetchInProgress extends SlugCheckState {} + +class SlugCheckFetchSuccess extends SlugCheckState { + String message; + SlugCheckFetchSuccess({required this.message}); +} + +class SlugCheckFetchFailure extends SlugCheckState { + final String errorMessage; + + SlugCheckFetchFailure(this.errorMessage); +} + +class SlugCheckCubit extends Cubit { + SlugCheckCubit() : super(SlugCheckInitial()); + + Future checkSlugAvailability({required String slug, required String langId}) async { + try { + final result = await Api.sendApiRequest(body: {SLUG: slug, LANGUAGE_ID: langId, LIMIT: limitOfAPIData, OFFSET: 0}, url: Api.slugCheckApi); + if (result[ERROR] == true) { + emit(SlugCheckFetchFailure(result[MESSAGE].toString())); + } else { + emit(SlugCheckFetchSuccess(message: result[MESSAGE])); + } + } catch (e) { + emit(SlugCheckFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/slugNewsCubit.dart b/news-app/lib/cubits/slugNewsCubit.dart new file mode 100644 index 00000000..200640bf --- /dev/null +++ b/news-app/lib/cubits/slugNewsCubit.dart @@ -0,0 +1,49 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +abstract class SlugNewsState {} + +class SlugNewsInitial extends SlugNewsState {} + +class SlugNewsFetchInProgress extends SlugNewsState {} + +class SlugNewsFetchSuccess extends SlugNewsState { + final List generalNews; + final int totalCount; + + SlugNewsFetchSuccess({required this.totalCount, required this.generalNews}); +} + +class SlugNewsFetchFailure extends SlugNewsState { + final String errorMessage; + + SlugNewsFetchFailure(this.errorMessage); +} + +class SlugNewsCubit extends Cubit { + SlugNewsCubit() : super(SlugNewsInitial()); + + Future getSlugNews({required String langId, String? latitude, String? longitude, int? offset, String? newsSlug}) async { + emit(SlugNewsInitial()); + try { + final body = {LANGUAGE_ID: langId, LIMIT: 20}; + + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + if (offset != null) body[OFFSET] = offset; + if (newsSlug != null && newsSlug != "null" && newsSlug.trim().isNotEmpty) body[SLUG] = newsSlug; // used in case of native link , details screen redirection + + final result = await Api.sendApiRequest(body: body, url: Api.getNewsApi); + if (!result[ERROR]) { + emit(SlugNewsFetchSuccess(generalNews: (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList(), totalCount: result[TOTAL])); + } else { + emit(SlugNewsFetchFailure(result[MESSAGE])); + } + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/cubits/subCatNewsCubit.dart b/news-app/lib/cubits/subCatNewsCubit.dart new file mode 100644 index 00000000..7d3d6f88 --- /dev/null +++ b/news-app/lib/cubits/subCatNewsCubit.dart @@ -0,0 +1,81 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/SubCatNews/subCatRepository.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class SubCatNewsState {} + +class SubCatNewsInitial extends SubCatNewsState {} + +class SubCatNewsFetchInProgress extends SubCatNewsState {} + +class SubCatNewsFetchSuccess extends SubCatNewsState { + final List subCatNews; + final int totalSubCatNewsCount; + final bool hasMoreFetchError; + final bool hasMore; + final bool isFirst; + + SubCatNewsFetchSuccess({required this.subCatNews, required this.totalSubCatNewsCount, required this.hasMoreFetchError, required this.hasMore, required this.isFirst}); +} + +class SubCatNewsFetchFailure extends SubCatNewsState { + final String errorMessage; + + SubCatNewsFetchFailure(this.errorMessage); +} + +class SubCatNewsCubit extends Cubit { + final SubCatNewsRepository _subCatNewsRepository; + + SubCatNewsCubit(this._subCatNewsRepository) : super(SubCatNewsInitial()); + + void getSubCatNews({String? catId, String? subCatId, String? latitude, String? longitude, required String langId}) async { + try { + emit(SubCatNewsFetchInProgress()); + final result = + await _subCatNewsRepository.getSubCatNews(limit: limitOfAPIData.toString(), offset: "0", subCatId: subCatId, langId: langId, catId: catId, latitude: latitude, longitude: longitude); + (!result[ERROR]) + ? emit(SubCatNewsFetchSuccess( + subCatNews: result['SubCatNews'], + totalSubCatNewsCount: result[TOTAL], + hasMoreFetchError: false, + hasMore: (result['SubCatNews'] as List).length < result[TOTAL], + isFirst: true)) + : emit(SubCatNewsFetchFailure(ErrorMessageKeys.noDataMessage)); + } catch (e) { + if (!isClosed) emit(SubCatNewsFetchFailure(e.toString())); + } + } + + bool hasMoreSubCatNews() { + return (state is SubCatNewsFetchSuccess) ? (state as SubCatNewsFetchSuccess).hasMore : false; + } + + void getMoreSubCatNews({String? catId, String? subCatId, String? latitude, String? longitude, required String langId}) async { + if (state is SubCatNewsFetchSuccess) { + try { + final result = await _subCatNewsRepository.getSubCatNews( + limit: limitOfAPIData.toString(), + offset: (state as SubCatNewsFetchSuccess).subCatNews.length.toString(), + langId: langId, + catId: catId, + subCatId: subCatId, + latitude: latitude, + longitude: longitude); + List updatedResults = (state as SubCatNewsFetchSuccess).subCatNews; + updatedResults.addAll(result['SubCatNews'] as List); + emit(SubCatNewsFetchSuccess(subCatNews: updatedResults, totalSubCatNewsCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL], isFirst: false)); + } catch (e) { + emit(SubCatNewsFetchSuccess( + subCatNews: (state as SubCatNewsFetchSuccess).subCatNews, + hasMoreFetchError: true, + totalSubCatNewsCount: (state as SubCatNewsFetchSuccess).totalSubCatNewsCount, + hasMore: (state as SubCatNewsFetchSuccess).hasMore, + isFirst: false)); + } + } + } +} diff --git a/news-app/lib/cubits/subCategoryCubit.dart b/news-app/lib/cubits/subCategoryCubit.dart new file mode 100644 index 00000000..4705a482 --- /dev/null +++ b/news-app/lib/cubits/subCategoryCubit.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/repositories/SubCategory/subCatRepository.dart'; + +abstract class SubCategoryState {} + +class SubCategoryInitial extends SubCategoryState {} + +class SubCategoryFetchInProgress extends SubCategoryState {} + +class SubCategoryFetchSuccess extends SubCategoryState { + final List subCategory; + + SubCategoryFetchSuccess({required this.subCategory}); +} + +class SubCategoryFetchFailure extends SubCategoryState { + final String errorMessage; + + SubCategoryFetchFailure(this.errorMessage); +} + +class SubCategoryCubit extends Cubit { + final SubCategoryRepository _subCategoryRepository; + + SubCategoryCubit(this._subCategoryRepository) : super(SubCategoryInitial()); + + void getSubCategory({required BuildContext context, required String catId, required String langId}) async { + try { + emit(SubCategoryFetchInProgress()); + final result = await _subCategoryRepository.getSubCategory(context: context, catId: catId, langId: langId); + + emit(SubCategoryFetchSuccess(subCategory: result['SubCategory'])); + } catch (e) { + emit(SubCategoryFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/surveyQuestionCubit.dart b/news-app/lib/cubits/surveyQuestionCubit.dart new file mode 100644 index 00000000..bda105df --- /dev/null +++ b/news-app/lib/cubits/surveyQuestionCubit.dart @@ -0,0 +1,53 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/SurveyQuestion/surveyQueRepository.dart'; + +abstract class SurveyQuestionState {} + +class SurveyQuestionInitial extends SurveyQuestionState {} + +class SurveyQuestionFetchInProgress extends SurveyQuestionState {} + +class SurveyQuestionFetchSuccess extends SurveyQuestionState { + final List surveyQuestion; + + SurveyQuestionFetchSuccess({required this.surveyQuestion}); +} + +class SurveyQuestionFetchFailure extends SurveyQuestionState { + final String errorMessage; + + SurveyQuestionFetchFailure(this.errorMessage); +} + +class SurveyQuestionCubit extends Cubit { + final SurveyQuestionRepository _surveyQuestionRepository; + + SurveyQuestionCubit(this._surveyQuestionRepository) : super(SurveyQuestionInitial()); + + Future getSurveyQuestion({required String langId}) async { + try { + emit(SurveyQuestionFetchInProgress()); + await _surveyQuestionRepository.getSurveyQuestion(langId: langId).then((value) { + emit(SurveyQuestionFetchSuccess(surveyQuestion: value['SurveyQuestion'])); + }); + } catch (e) { + emit(SurveyQuestionFetchFailure(e.toString())); + } + } + + void removeQuestion(String index) { + if (state is SurveyQuestionFetchSuccess) { + List queList = List.from((state as SurveyQuestionFetchSuccess).surveyQuestion)..removeWhere((element) => element.id! == index); + emit(SurveyQuestionFetchSuccess(surveyQuestion: queList)); + } + } + + List surveyList() { + return (state is SurveyQuestionFetchSuccess) ? (state as SurveyQuestionFetchSuccess).surveyQuestion : []; + } + + String getSurveyQuestionIndex({required String questionTitle}) { + return (state is! SurveyQuestionFetchSuccess) ? "0" : (state as SurveyQuestionFetchSuccess).surveyQuestion.where((element) => element.question == questionTitle).first.id!; + } +} diff --git a/news-app/lib/cubits/tagCubit.dart b/news-app/lib/cubits/tagCubit.dart new file mode 100644 index 00000000..a3d3d23a --- /dev/null +++ b/news-app/lib/cubits/tagCubit.dart @@ -0,0 +1,92 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/TagModel.dart'; +import 'package:news/data/repositories/Tag/tagRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class TagState {} + +class TagInitial extends TagState {} + +class TagFetchInProgress extends TagState {} + +class TagFetchSuccess extends TagState { + final List tag; + final int total; + final bool hasMoreFetchError; + final bool hasMore; + + TagFetchSuccess( + {required this.tag, + required this.total, + required this.hasMoreFetchError, + required this.hasMore}); +} + +class TagFetchFailure extends TagState { + final String errorMessage; + + TagFetchFailure(this.errorMessage); +} + +class TagCubit extends Cubit { + final TagRepository _tagRepository; + + TagCubit(this._tagRepository) : super(TagInitial()); + + void getTags({required String langId}) async { + try { + emit(TagFetchInProgress()); + final result = await _tagRepository.getTag( + langId: langId, limit: limitOfAPIData.toString(), offset: "0"); + + emit(TagFetchSuccess( + tag: result['Tag'], + total: result[TOTAL], + hasMoreFetchError: false, + hasMore: ((result['Tag'] as List).length < result[TOTAL]))); + } catch (e) { + emit(TagFetchFailure(e.toString())); + } + } + + // Load tags if not already loaded or failed + void loadIfFailed({required String langId}) { + if (state is TagInitial || state is TagFetchFailure) { + getTags(langId: langId); + } + } + + void getMoreTags({required String langId}) async { + if (state is TagFetchSuccess) { + try { + await _tagRepository + .getTag( + langId: langId, + limit: limitOfAPIData.toString(), + offset: (state as TagFetchSuccess).tag.length.toString()) + .then((value) { + List updatedResults = (state as TagFetchSuccess).tag; + updatedResults.addAll(value['Tag']); + emit(TagFetchSuccess( + tag: updatedResults, + total: value[TOTAL], + hasMoreFetchError: false, + hasMore: (updatedResults.length < value[TOTAL]))); + }); + } catch (e) { + emit(TagFetchSuccess( + tag: (state as TagFetchSuccess).tag, + total: (state as TagFetchSuccess).total, + hasMoreFetchError: true, + hasMore: (state as TagFetchSuccess).hasMore)); + } + } + } + + bool hasMoreTags() { + return (state is TagFetchSuccess) + ? (state as TagFetchSuccess).hasMore + : false; + } +} diff --git a/news-app/lib/cubits/tagNewsCubit.dart b/news-app/lib/cubits/tagNewsCubit.dart new file mode 100644 index 00000000..bd70a3b7 --- /dev/null +++ b/news-app/lib/cubits/tagNewsCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/TagNews/tagNewsRepository.dart'; + +abstract class TagNewsState {} + +class TagNewsInitial extends TagNewsState {} + +class TagNewsFetchInProgress extends TagNewsState {} + +class TagNewsFetchSuccess extends TagNewsState { + final List tagNews; + + TagNewsFetchSuccess({required this.tagNews}); +} + +class TagNewsFetchFailure extends TagNewsState { + final String errorMessage; + + TagNewsFetchFailure(this.errorMessage); +} + +class TagNewsCubit extends Cubit { + final TagNewsRepository _tagNewsRepository; + + TagNewsCubit(this._tagNewsRepository) : super(TagNewsInitial()); + + void getTagNews({required String tagId, required String langId, String? latitude, String? longitude}) async { + try { + emit(TagNewsFetchInProgress()); + final result = await _tagNewsRepository.getTagNews(langId: langId, tagId: tagId, latitude: latitude, longitude: longitude); + emit(TagNewsFetchSuccess(tagNews: result['TagNews'])); + } catch (e) { + emit(TagNewsFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/themeCubit.dart b/news-app/lib/cubits/themeCubit.dart new file mode 100644 index 00000000..3fbc8739 --- /dev/null +++ b/news-app/lib/cubits/themeCubit.dart @@ -0,0 +1,21 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; + +import 'package:news/ui/styles/appTheme.dart'; + +import 'package:news/utils/uiUtils.dart'; + +class ThemeState { + final AppTheme appTheme; + ThemeState(this.appTheme); +} + +class ThemeCubit extends Cubit { + SettingsLocalDataRepository settingsRepository; + ThemeCubit(this.settingsRepository) : super(ThemeState(UiUtils.getAppThemeFromLabel(settingsRepository.getCurrentTheme()))); + + void changeTheme(AppTheme appTheme) { + settingsRepository.setCurrentTheme(UiUtils.getThemeLabelFromAppTheme(appTheme)); + emit(ThemeState(appTheme)); + } +} diff --git a/news-app/lib/cubits/updateBottomsheetContentCubit.dart b/news-app/lib/cubits/updateBottomsheetContentCubit.dart new file mode 100644 index 00000000..5514fa0a --- /dev/null +++ b/news-app/lib/cubits/updateBottomsheetContentCubit.dart @@ -0,0 +1,63 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/models/TagModel.dart'; +import 'package:news/data/models/appLanguageModel.dart'; +import 'package:news/data/models/locationCityModel.dart'; + +abstract class BottomSheetEvent {} + +class UpdateBottomSheetContent extends BottomSheetEvent { + final List newData; + final List newTagsData; + final List newLanguagesData; + + UpdateBottomSheetContent(this.newData, this.newTagsData, this.newLanguagesData); +} + +// Define the state for your bottom sheet Cubit +class BottomSheetState { + final List locationData; + final List tagsData; + final List languageData; + final List categoryData; + + BottomSheetState(this.locationData, this.tagsData, this.languageData, this.categoryData); +} + +// Define the Cubit itself +class BottomSheetCubit extends Cubit { + BottomSheetCubit() : super(BottomSheetState([], [], [], [])); + // Access the data field within the cubit + + List currentLocationData = []; + List currentTagData = []; + List currentLanguageData = []; + List currentCategoryData = []; + + getAllLatestContent({required bool isTag, required bool isLocation, required bool isLanguage, required bool isCategory}) { + if (!isLocation) currentLocationData = state.locationData; + if (!isTag) currentTagData = state.tagsData; + if (!isLanguage) currentLanguageData = state.languageData; + if (!isCategory) currentCategoryData = state.categoryData; + } + + void updateLocationContent(List newData) { + getAllLatestContent(isTag: false, isLocation: true, isLanguage: false, isCategory: false); + emit(BottomSheetState(newData, currentTagData, currentLanguageData, currentCategoryData)); + } + + void updateTagsContent(List newTagsData) { + getAllLatestContent(isTag: true, isLocation: false, isLanguage: false, isCategory: false); + emit(BottomSheetState(currentLocationData, newTagsData, currentLanguageData, currentCategoryData)); + } + + void updateLanguageContent(List newLanguagesData) { + getAllLatestContent(isTag: false, isLocation: false, isLanguage: true, isCategory: false); + emit(BottomSheetState(currentLocationData, currentTagData, newLanguagesData, currentCategoryData)); + } + + void updateCategoryContent(List newCategoryData) { + getAllLatestContent(isTag: false, isLocation: false, isLanguage: false, isCategory: true); + emit(BottomSheetState(currentLocationData, currentTagData, currentLanguageData, newCategoryData)); + } +} diff --git a/news-app/lib/cubits/videosCubit.dart b/news-app/lib/cubits/videosCubit.dart new file mode 100644 index 00000000..bc07372c --- /dev/null +++ b/news-app/lib/cubits/videosCubit.dart @@ -0,0 +1,63 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Videos/videosRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class VideoState {} + +class VideoInitial extends VideoState {} + +class VideoFetchInProgress extends VideoState {} + +class VideoFetchSuccess extends VideoState { + final List video; + final int totalVideoCount; + final bool hasMoreFetchError; + final bool hasMore; + + VideoFetchSuccess({required this.video, required this.totalVideoCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class VideoFetchFailure extends VideoState { + final String errorMessage; + + VideoFetchFailure(this.errorMessage); +} + +class VideoCubit extends Cubit { + final VideoRepository _videoRepository; + + VideoCubit(this._videoRepository) : super(VideoInitial()); + + void getVideo({required String langId, String? latitude, String? longitude}) async { + try { + emit(VideoFetchInProgress()); + final result = await _videoRepository.getVideo(limit: limitOfAPIData.toString(), offset: "0", langId: langId, latitude: latitude, longitude: longitude); + (!result[ERROR]) + ? emit(VideoFetchSuccess(video: result['Video'], totalVideoCount: result[TOTAL], hasMoreFetchError: false, hasMore: (result['Video'] as List).length < result[TOTAL])) + : emit(VideoFetchFailure(result[MESSAGE])); + } catch (e) { + emit(VideoFetchFailure(e.toString())); + } + } + + bool hasMoreVideo() { + return (state is VideoFetchSuccess) ? (state as VideoFetchSuccess).hasMore : false; + } + + void getMoreVideo({required String langId, String? latitude, String? longitude}) async { + if (state is VideoFetchSuccess) { + try { + final result = + await _videoRepository.getVideo(langId: langId, limit: limitOfAPIData.toString(), offset: (state as VideoFetchSuccess).video.length.toString(), latitude: latitude, longitude: longitude); + List updatedResults = (state as VideoFetchSuccess).video; + updatedResults.addAll(result['Video'] as List); + emit(VideoFetchSuccess(video: updatedResults, totalVideoCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL])); + } catch (e) { + emit(VideoFetchSuccess( + video: (state as VideoFetchSuccess).video, hasMoreFetchError: true, totalVideoCount: (state as VideoFetchSuccess).totalVideoCount, hasMore: (state as VideoFetchSuccess).hasMore)); + } + } + } +} diff --git a/news-app/lib/cubits/weatherCubit.dart b/news-app/lib/cubits/weatherCubit.dart new file mode 100644 index 00000000..3efc32a7 --- /dev/null +++ b/news-app/lib/cubits/weatherCubit.dart @@ -0,0 +1,41 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/WeatherData.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; + +abstract class WeatherState {} + +class WeatherInitial extends WeatherState {} + +class WeatherFetchInProgress extends WeatherState {} + +class WeatherFetchSuccess extends WeatherState { + final WeatherDetails weatherData; + + WeatherFetchSuccess({required this.weatherData}); +} + +class WeatherFetchFailure extends WeatherState { + final String errorMessage; + + WeatherFetchFailure(this.errorMessage); +} + +class WeatherCubit extends Cubit { + WeatherCubit() : super(WeatherInitial()); + + void getWeatherDetails({required String langId, String? lat, String? lon}) async { + try { + emit(WeatherFetchInProgress()); + + final weatherResponse = await Dio().get('https://api.weatherapi.com/v1/forecast.json?key=d0f2f4dbecc043e78d6123135212408&q=${lat.toString()},${lon.toString()}&days=1&alerts=no&lang=$langId'); + if (weatherResponse.statusCode == 200) { + emit(WeatherFetchSuccess(weatherData: WeatherDetails.fromJson(Map.from(weatherResponse.data)))); + } else { + emit(WeatherFetchFailure(weatherResponse.statusMessage ?? ErrorMessageKeys.defaultErrorMessage)); + } + } catch (e) { + emit(WeatherFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/data/models/AppSystemSettingModel.dart b/news-app/lib/data/models/AppSystemSettingModel.dart new file mode 100644 index 00000000..3795a69b --- /dev/null +++ b/news-app/lib/data/models/AppSystemSettingModel.dart @@ -0,0 +1,124 @@ +import 'package:news/data/models/appLanguageModel.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +class AppSystemSettingModel { + String? breakNewsMode, liveStreamMode, catMode, subCatMode, commentMode, inAppAdsMode, iosInAppAdsMode, adsType, iosAdsType; + String? goRewardedId, goInterId, goBannerId, goNativeId; + String? goIOSRewardedId, goIOSInterId, goIOSBannerId, goIOSNativeId; + String? gameId, iosGameId; + String? unityRewardedId, unityInterId, unityBannerId, unityIOSRewardedId, unityIOSInterId, unityIOSBannerId; + String? locationWiseNewsMode, weatherMode, maintenanceMode, forceUpdateMode; + LanguageModel? defaultLangDataModel; + String? rssFeedMode, mobileLoginMode, countryCode, shareAppText, appstoreId, androidAppLink, iosAppLink; + VideoViewType? videoTypePreference; + String? androidAppVersion, iosAppVersion, googleGeminiApiKey; + + AppSystemSettingModel( + {this.breakNewsMode, + this.liveStreamMode, + this.catMode, + this.subCatMode, + this.commentMode, + this.inAppAdsMode, + this.iosInAppAdsMode, + this.adsType, + this.iosAdsType, + this.goRewardedId, + this.goBannerId, + this.goInterId, + this.goNativeId, + this.goIOSBannerId, + this.goIOSInterId, + this.goIOSNativeId, + this.goIOSRewardedId, + this.gameId, + this.iosGameId, + this.unityRewardedId, + this.unityInterId, + this.unityBannerId, + this.unityIOSRewardedId, + this.unityIOSInterId, + this.unityIOSBannerId, + this.defaultLangDataModel, + this.locationWiseNewsMode, + this.weatherMode, + this.maintenanceMode, + this.rssFeedMode, + this.mobileLoginMode, + this.countryCode, + this.shareAppText, + this.appstoreId, + this.androidAppLink, + this.iosAppLink, + this.videoTypePreference, + this.forceUpdateMode, + this.androidAppVersion, + this.iosAppVersion, + this.googleGeminiApiKey}); + + factory AppSystemSettingModel.fromJson(Map json) { + var defaultList = (json[DEFAULT_LANG]); + + LanguageModel defaultLangData; + if (defaultList == null && defaultList.isEmpty) { + defaultLangData = LanguageModel(); + } else { + defaultLangData = LanguageModel.fromJson(defaultList); + } + + return AppSystemSettingModel( + breakNewsMode: json[BREAK_NEWS_MODE], + liveStreamMode: json[LIVE_STREAM_MODE], + catMode: json[CATEGORY_MODE], + subCatMode: json[SUBCAT_MODE], + commentMode: json[COMM_MODE], + inAppAdsMode: json[ADS_MODE], + iosInAppAdsMode: json[IOS_ADS_MODE], + adsType: json[ADS_TYPE], + iosAdsType: json[IOS_ADS_TYPE], + goRewardedId: json[GO_REWARDED_ID], + goInterId: json[GO_INTER_ID], + goBannerId: json[GO_BANNER_ID], + goNativeId: json[GO_NATIVE_ID], + goIOSRewardedId: json[IOS_GO_REWARDED_ID], + goIOSNativeId: json[IOS_GO_NATIVE_ID], + goIOSInterId: json[IOS_GO_INTER_ID], + goIOSBannerId: json[IOS_GO_BANNER_ID], + gameId: json[U_AND_GAME_ID], + iosGameId: json[IOS_U_GAME_ID], + unityRewardedId: json[U_REWARDED_ID], + unityInterId: json[U_INTER_ID], + unityBannerId: json[U_BANNER_ID], + unityIOSRewardedId: json[IOS_U_REWARDED_ID], + unityIOSInterId: json[IOS_U_INTER_ID], + unityIOSBannerId: json[IOS_U_BANNER_ID], + defaultLangDataModel: defaultLangData, + locationWiseNewsMode: json[LOCATION_WISE_NEWS_MODE], + weatherMode: json[WEATHER_MODE], + maintenanceMode: json[MAINTENANCE_MODE], + rssFeedMode: json[RSS_FEED_MODE], + mobileLoginMode: json[MOBILE_LOGIN_MODE], + countryCode: json[COUNTRY_CODE], + shareAppText: json[SHARE_APP_TEXT], + appstoreId: json[APPSTORE_ID], + androidAppLink: json[WEB_SETTING][ANDROID_APP_LINK], + iosAppLink: json[WEB_SETTING][IOS_APP_LINK], + videoTypePreference: fromVideoTypeJson(json[VIDEO_TYPE_PREFERENCE]), + forceUpdateMode: json[FORCE_UPDT_APP_MODE] ?? "0", + androidAppVersion: json[ANDROID_APP_VERSION] ?? '', + iosAppVersion: json[IOS_APP_VERSION] ?? '', + googleGeminiApiKey: json[GEMINI_API_KEY]); + } + + static VideoViewType? fromVideoTypeJson(String? value) { + switch (value) { + case 'normal_style': + return VideoViewType.normal; + case 'page_style': + return VideoViewType.page; + default: + return null; + } + } +} diff --git a/news-app/lib/data/models/AuthModel.dart b/news-app/lib/data/models/AuthModel.dart new file mode 100644 index 00000000..9ae786ae --- /dev/null +++ b/news-app/lib/data/models/AuthModel.dart @@ -0,0 +1,34 @@ +import 'package:news/data/models/authorModel.dart'; +import 'package:news/utils/strings.dart'; + +class AuthModel { + String? id; + String? name; + String? email; + String? mobile; + String? profile; + String? type; + String? status; + String? isFirstLogin; // 0 - new user, 1 - existing user + String? role; + String? jwtToken; + int? isAuthor; + Author? authorDetails; + + AuthModel({this.id, this.name, this.email, this.mobile, this.profile, this.type, this.status, this.isFirstLogin, this.role, this.jwtToken, this.authorDetails, this.isAuthor = 0}); + + AuthModel.fromJson(Map json) { + id = json[ID].toString(); + name = json[NAME] ?? ""; + email = json[EMAIL] ?? ""; + mobile = json[MOBILE] ?? ""; + profile = json[PROFILE] ?? ""; + type = json[TYPE] ?? ""; + status = json[STATUS].toString(); + isFirstLogin = (json[IS_LOGIN] != null) ? json[IS_LOGIN].toString() : ""; + role = json[ROLE].toString(); + jwtToken = json[TOKEN] ?? ""; + isAuthor = json[IS_AUTHOR]; + authorDetails = (isAuthor == 1 && json[AUTHOR] != null) ? Author.fromJson(json[AUTHOR]) : null; + } +} diff --git a/news-app/lib/data/models/BreakingNewsModel.dart b/news-app/lib/data/models/BreakingNewsModel.dart new file mode 100644 index 00000000..28cc7243 --- /dev/null +++ b/news-app/lib/data/models/BreakingNewsModel.dart @@ -0,0 +1,19 @@ +import 'package:news/utils/strings.dart'; + +class BreakingNewsModel { + String? id, image, title, desc, contentType, contentValue, totalViews, slug; + + BreakingNewsModel({this.id, this.image, this.title, this.desc, this.contentValue, this.contentType, this.totalViews, this.slug}); + + factory BreakingNewsModel.fromJson(Map json) { + return BreakingNewsModel( + id: json[ID].toString(), + image: json[IMAGE], + title: json[TITLE], + desc: json[DESCRIPTION] ?? '', + contentValue: json[CONTENT_VALUE], + contentType: json[CONTENT_TYPE], + totalViews: json[TOTAL_VIEWS].toString(), + slug: json[SLUG]); + } +} diff --git a/news-app/lib/data/models/CategoryModel.dart b/news-app/lib/data/models/CategoryModel.dart new file mode 100644 index 00000000..e041031a --- /dev/null +++ b/news-app/lib/data/models/CategoryModel.dart @@ -0,0 +1,26 @@ +import 'package:news/utils/strings.dart'; + +class CategoryModel { + String? id, image, categoryName; + List? subData; + + CategoryModel({this.id, this.image, this.categoryName, this.subData}); + + factory CategoryModel.fromJson(Map json) { + var subList = (json.containsKey(SUBCATEGORIES)) ? (json[SUBCATEGORIES] as List) : []; + List subCatData = []; + subCatData = (subList.isEmpty) ? [] : subList.map((data) => SubCategoryModel.fromJson(data)).toList(); + + return CategoryModel(id: json[ID].toString(), image: json[IMAGE] ?? "", categoryName: json[CATEGORY_NAME], subData: subCatData); + } +} + +class SubCategoryModel { + String? id, categoryId, subCatName; + + SubCategoryModel({this.id, this.categoryId, this.subCatName}); + + factory SubCategoryModel.fromJson(Map json) { + return SubCategoryModel(id: json[ID].toString(), categoryId: json[CATEGORY_ID].toString(), subCatName: json[SUBCAT_NAME] ?? json[SUBCATEGORY]); + } +} diff --git a/news-app/lib/data/models/CommentModel.dart b/news-app/lib/data/models/CommentModel.dart new file mode 100644 index 00000000..fca56d3c --- /dev/null +++ b/news-app/lib/data/models/CommentModel.dart @@ -0,0 +1,60 @@ +import 'package:news/data/models/AuthModel.dart'; +import 'package:news/utils/strings.dart'; + +class CommentModel { + String? id, message, profile, date, name, status, like, dislike, totalLikes, totalDislikes, userId; + List? replyComList; + + CommentModel({this.id, this.message, this.profile, this.date, this.name, this.replyComList, this.status, this.like, this.dislike, this.totalLikes, this.totalDislikes, this.userId}); + + factory CommentModel.fromJson(Map json) { + var replyList = (json[REPLY] as List); + List replyData = []; + if (replyList.isEmpty) { + replyList = []; + } else { + replyData = replyList.map((data) => ReplyModel.fromJson(data)).toList(); + } + + var userDetails = AuthModel.fromJson(json[USER]); + + return CommentModel( + id: json[ID].toString(), + message: json[MESSAGE], + profile: userDetails.profile, + name: userDetails.name, + date: json[DATE], + status: json[STATUS].toString(), + replyComList: replyData, + like: json[LIKE].toString(), + dislike: json[DISLIKE].toString(), + totalDislikes: json[TOTAL_DISLIKE].toString(), + totalLikes: json[TOTAL_LIKE].toString(), + userId: json[USER_ID].toString()); + } +} + +class ReplyModel { + String? id, message, profile, date, name, userId, parentId, newsId, status, like, dislike, totalLikes, totalDislikes; + + ReplyModel({this.id, this.message, this.profile, this.date, this.name, this.userId, this.parentId, this.status, this.newsId, this.like, this.dislike, this.totalLikes, this.totalDislikes}); + + factory ReplyModel.fromJson(Map json) { + var userDetails = AuthModel.fromJson(json[USER]); + + return ReplyModel( + id: json[ID].toString(), + message: json[MESSAGE], + profile: userDetails.profile, + name: userDetails.name, + date: json[DATE], + userId: json[USER_ID].toString(), + parentId: json[PARENT_ID].toString(), + newsId: json[NEWS_ID].toString(), + status: json[STATUS].toString(), + like: json[LIKE].toString(), + dislike: json[DISLIKE].toString(), + totalDislikes: json[TOTAL_DISLIKE].toString(), + totalLikes: json[TOTAL_LIKE].toString()); + } +} diff --git a/news-app/lib/data/models/FeatureSectionModel.dart b/news-app/lib/data/models/FeatureSectionModel.dart new file mode 100644 index 00000000..ea77f8ce --- /dev/null +++ b/news-app/lib/data/models/FeatureSectionModel.dart @@ -0,0 +1,97 @@ +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/models/adSpaceModel.dart'; +import 'package:news/utils/strings.dart'; + +class FeatureSectionModel { + String? id, languageId, title, shortDescription, newsType, videosType, categoryIds, subcategoryIds, newsIds, styleApp, createdAt, status, summarizedDesc; + int? newsTotal, breakNewsTotal, videosTotal; + List? news; + List? breakNews, breakVideos; + List? videos; + AdSpaceModel? adSpaceDetails; + + FeatureSectionModel( + {this.id, + this.languageId, + this.title, + this.shortDescription, + this.newsType, + this.videosType, + this.categoryIds, + this.subcategoryIds, + this.newsIds, + this.styleApp, + this.createdAt, + this.newsTotal, + this.breakNewsTotal, + this.videosTotal, + this.news, + this.breakNews, + this.videos, + this.breakVideos, + this.adSpaceDetails, + this.summarizedDesc}); + + factory FeatureSectionModel.fromJson(Map json) { + List newsData = []; + if (json.containsKey(NEWS)) { + var newsList = (json[NEWS] as List); + if (newsList.isEmpty) { + newsList = []; + } else { + newsData = newsList.map((data) => NewsModel.fromJson(data)).toList(); + } + } + + List breakNewsData = []; + if (json.containsKey(BREAKING_NEWS)) { + var breakNewsList = (json[BREAKING_NEWS] as List); + if (breakNewsList.isEmpty) { + breakNewsList = []; + } else { + breakNewsData = breakNewsList.map((data) => BreakingNewsModel.fromJson(data)).toList(); + } + } + + List videosData = []; + List breakVideosData = []; + if (json.containsKey(VIDEOS)) { + var videosList = (json[VIDEOS] as List); + if (videosList.isEmpty) { + videosList = []; + } else { + if (json[VIDEOS_TYPE] == 'news') { + videosData = videosList.map((data) => NewsModel.fromVideos(data)).toList(); + } else { + breakVideosData = videosList.map((data) => BreakingNewsModel.fromJson(data)).toList(); + } + } + } + AdSpaceModel? adSpaceData; + if (json.containsKey(AD_SPACES)) { + adSpaceData = AdSpaceModel.fromJson(json[AD_SPACES]); + } + + return FeatureSectionModel( + id: json[ID].toString(), + languageId: json[LANGUAGE_ID].toString(), + title: json[TITLE], + shortDescription: json[SHORT_DESC], + newsType: json[NEWS_TYPE], + videosType: json[VIDEOS_TYPE], + categoryIds: json[CAT_IDS], + subcategoryIds: json[SUBCAT_IDS], + newsIds: json[NEWS_IDS], + styleApp: json[STYLE_APP], + newsTotal: json[NEWS_TOTAL], + breakNewsTotal: json[BREAK_NEWS_TOTAL], + videosTotal: json[VIDEOS_TOTAL], + summarizedDesc: json[SUMM_DESCRIPTION], + news: newsData, + breakNews: breakNewsData, + videos: videosData, + breakVideos: breakVideosData, + adSpaceDetails: adSpaceData); + } +} diff --git a/news-app/lib/data/models/LiveStreamingModel.dart b/news-app/lib/data/models/LiveStreamingModel.dart new file mode 100644 index 00000000..33d5000f --- /dev/null +++ b/news-app/lib/data/models/LiveStreamingModel.dart @@ -0,0 +1,11 @@ +import 'package:news/utils/strings.dart'; + +class LiveStreamingModel { + String? id, image, title, type, url, updatedDate; + + LiveStreamingModel({this.id, this.image, this.title, this.type, this.url, this.updatedDate}); + + factory LiveStreamingModel.fromJson(Map json) { + return LiveStreamingModel(id: json[ID].toString(), image: json[IMAGE], title: json[TITLE], type: json[TYPE], url: json[URL], updatedDate: json[UPDATED_DATE]); + } +} diff --git a/news-app/lib/data/models/NewsModel.dart b/news-app/lib/data/models/NewsModel.dart new file mode 100644 index 00000000..b6380d90 --- /dev/null +++ b/news-app/lib/data/models/NewsModel.dart @@ -0,0 +1,196 @@ +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/models/authorModel.dart'; +import 'package:news/data/models/locationCityModel.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/data/models/OptionModel.dart'; + +class NewsModel { + String? id, userId, newsId, categoryId, title, date, contentType, contentValue, image, desc, categoryName, dateSent, totalLikes, like, shortDesc; + String? bookmark, keyName, tagId, tagName, subCatId, img, subCatName, showTill, langId, totalViews, locationId, locationName, metaKeyword, metaTitle, metaDescription, slug, publishDate; + + List? imageDataList; + + bool? history = false; + String? question, status, type, sourceType; + List? optionDataList; + int? from, isExpired, isCommentEnabled; + Author? authorDetails; + UserAuthorModel? userAthorDetails; + + NewsModel( + {this.id, + this.userId, + this.newsId, + this.categoryId, + this.title, + this.date, + this.contentType, + this.contentValue, + this.image, + this.desc, + this.categoryName, + this.dateSent, + this.imageDataList, + this.totalLikes, + this.like, + this.keyName, + this.tagName, + this.subCatId, + this.tagId, + this.history, + this.optionDataList, + this.question, + this.status, + this.type, + this.from, + this.img, + this.subCatName, + this.showTill, + this.bookmark, + this.langId, + this.totalViews, + this.locationId, + this.locationName, + this.metaTitle, + this.metaDescription, + this.metaKeyword, + this.slug, + this.publishDate, + this.isExpired, + this.isCommentEnabled, + this.sourceType, + this.shortDesc, + this.authorDetails, + this.userAthorDetails}); + + factory NewsModel.history(String history) { + return NewsModel(title: history, history: true); + } + + factory NewsModel.fromSurvey(Map json) { + List optionList = (json[OPTION] as List).map((data) => OptionModel.fromJson(data)).toList(); + + return NewsModel(id: json[ID].toString(), question: json[QUESTION], status: json[STATUS].toString(), optionDataList: optionList, type: "survey", from: 1); + } + + factory NewsModel.fromVideos(Map json) { + String? tagName; + + tagName = (json[TAG] == null) ? "" : json[TAG]; + return NewsModel( + id: json[ID].toString(), + newsId: json[ID].toString(), + //for bookmark get/set + desc: json[DESCRIPTION] ?? '', + date: json[DATE], + image: json[IMAGE], + title: json[TITLE], + contentType: json[CONTENT_TYPE], + contentValue: json[CONTENT_VALUE], + tagId: json[TAG_ID], + tagName: tagName, + categoryName: json[CATEGORY_NAME] ?? '', + sourceType: json[SOURCE_TYPE], + shortDesc: json[SUMM_DESCRIPTION] ?? ''); + } + + factory NewsModel.fromJson(Map json) { + bool isAuthor = (json[USER] == null) ? false : (json[USER][IS_AUTHOR] == 1); + + String? tagName; + + tagName = (json[TAG] == null) ? "" : json[TAG]; + + List imageData = []; + var imageList = (json.containsKey(IMAGES)) + ? json[IMAGES] as List + : (json.containsKey(IMAGE_DATA)) + ? json[IMAGE_DATA] as List + : []; + imageList = (imageList.isEmpty) ? [] : imageList.map((data) => ImageDataModel.fromJson(data)).toList(); + if (imageList.isNotEmpty) imageData = imageList as List; + var categoryName = ''; + try { + categoryName = (json.containsKey(CATEGORY_NAME)) + ? json[CATEGORY_NAME] + : (json.containsKey(CATEGORY)) + ? CategoryModel.fromJson(json[CATEGORY]).categoryName + : ''; + } catch (e) {} + + var subcategoryName = + (json.containsKey(SUBCAT_NAME)) ? json[SUBCAT_NAME] : ((json.containsKey(SUBCATEGORY) && json[SUBCATEGORY] != null) ? SubCategoryModel.fromJson(json[SUBCATEGORY]).subCatName : ''); + + return NewsModel( + id: json[ID].toString(), + userId: json[USER_ID].toString(), + newsId: (json[NEWS_ID] != null && json[NEWS_ID].toString().trim().isNotEmpty) ? json[NEWS_ID].toString() : json[ID].toString(), + //incase of null newsId in Response + categoryId: json[CATEGORY_ID].toString(), + title: json[TITLE], + date: json[DATE], + contentType: json[CONTENT_TYPE], + contentValue: json[CONTENT_VALUE], + image: json[IMAGE], + desc: json[DESCRIPTION] ?? '', + categoryName: categoryName, + dateSent: json[DATE_SENT], + imageDataList: imageData, + totalLikes: json[TOTAL_LIKE].toString(), + like: json[LIKE].toString(), + bookmark: json[BOOKMARK].toString(), + tagId: json[TAG_ID], + tagName: tagName, + subCatId: json[SUBCAT_ID].toString(), + history: false, + type: "news", + img: "", + subCatName: subcategoryName, + showTill: json[SHOW_TILL], + langId: json[LANGUAGE_ID].toString(), + totalViews: json[TOTAL_VIEWS].toString(), + locationId: (json.containsKey(LOCATION) && json[LOCATION] != null) ? LocationCityModel.fromJson(json[LOCATION]).id.toString() : json[LOCATION_ID].toString(), + locationName: (json.containsKey(LOCATION) && json[LOCATION] != null) ? LocationCityModel.fromJson(json[LOCATION]).locationName : json[LOCATION_NAME], + metaKeyword: json[META_KEYWORD], + metaTitle: json[META_TITLE], + metaDescription: json[META_DESC], + slug: json[SLUG], + publishDate: json[PUBLISHED_DATE], + isExpired: json[IS_EXPIRED] ?? 0, + status: json[STATUS].toString(), + isCommentEnabled: json[IS_COMMENT_ENABLED] ?? 1, + shortDesc: json[SUMM_DESCRIPTION] ?? '', + userAthorDetails: (json[USER] == null) ? null : UserAuthorModel.fromJson(json[USER]), + authorDetails: (isAuthor && json[AUTHOR] != null) ? Author.fromJson(json[AUTHOR]) : null); + } +} + +class ImageDataModel { + String? id; + String? otherImage; + + ImageDataModel({this.otherImage, this.id}); + + factory ImageDataModel.fromJson(Map json) { + return ImageDataModel(otherImage: json[OTHER_IMAGE], id: json[ID].toString()); + } +} + +class UserAuthorModel { + String? id; + String? name; + String? profile; + int? isAuthor; + Author? authorData; + + UserAuthorModel({this.id, this.name, this.profile, this.isAuthor, this.authorData}); + + factory UserAuthorModel.fromJson(Map json) { + return UserAuthorModel( + id: json[ID].toString(), + name: json[NAME], + profile: json[PROFILE].toString(), + isAuthor: json[IS_AUTHOR] ?? 0, + authorData: (json.containsKey(AUTHOR) && json[AUTHOR] != null) ? Author.fromJson(json[AUTHOR]) : null); + } +} diff --git a/news-app/lib/data/models/OptionModel.dart b/news-app/lib/data/models/OptionModel.dart new file mode 100644 index 00000000..4759d666 --- /dev/null +++ b/news-app/lib/data/models/OptionModel.dart @@ -0,0 +1,20 @@ +import 'package:news/utils/strings.dart'; + +class OptionModel { + String? id; + String? options; + String? counter; + double? percentage; + String? questionId; + + OptionModel({this.id, this.options, this.counter, this.percentage, this.questionId}); + + factory OptionModel.fromJson(Map json) { + return OptionModel( + id: json[ID].toString(), + options: json[OPTIONS], + counter: json[COUNTER].toString(), + percentage: (json[PERCENTAGE].runtimeType == int) ? double.parse(json[PERCENTAGE].toString()) : json[PERCENTAGE], + questionId: json[QUESTION_ID].toString()); + } +} diff --git a/news-app/lib/data/models/OtherPageModel.dart b/news-app/lib/data/models/OtherPageModel.dart new file mode 100644 index 00000000..ce6e7914 --- /dev/null +++ b/news-app/lib/data/models/OtherPageModel.dart @@ -0,0 +1,15 @@ +import 'package:news/utils/strings.dart'; + +class OtherPageModel { + String? id, pageContent, title, image; +//slug,meta_description,meta_keywords not in use. + OtherPageModel({this.id, this.pageContent, this.title, this.image}); + + factory OtherPageModel.fromJson(Map json) { + return OtherPageModel(id: json[ID].toString(), pageContent: json[PAGE_CONTENT], title: json[TITLE], image: json[PAGE_ICON]); + } + + factory OtherPageModel.fromPrivacyTermsJson(Map json) { + return OtherPageModel(id: json[ID].toString().toString(), pageContent: json[PAGE_CONTENT], title: json[TITLE]); + } +} diff --git a/news-app/lib/data/models/RSSFeedModel.dart b/news-app/lib/data/models/RSSFeedModel.dart new file mode 100644 index 00000000..9680933a --- /dev/null +++ b/news-app/lib/data/models/RSSFeedModel.dart @@ -0,0 +1,23 @@ +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/utils/strings.dart'; + +class RSSFeedModel { + String? id, feedName, feedUrl, categoryId, categoryName, subCatName, tagName; + + RSSFeedModel({this.id, this.feedName, this.feedUrl, this.categoryId, this.tagName, this.categoryName, this.subCatName}); + + factory RSSFeedModel.fromJson(Map json) { + String? tagName; + + tagName = (json[TAG] == null) ? "" : json[TAG]; + var categoryName = (json.containsKey(CATEGORY_NAME)) + ? json[CATEGORY_NAME] + : (json.containsKey(CATEGORY) && (json[CATEGORY] != null)) + ? CategoryModel.fromJson(json[CATEGORY]).categoryName + : ''; + var subcategoryName = + (json.containsKey(SUBCAT_NAME)) ? json[SUBCAT_NAME] : ((json.containsKey(SUBCATEGORY) && json[SUBCATEGORY] != null) ? SubCategoryModel.fromJson(json[SUBCATEGORY]).subCatName : ''); + + return RSSFeedModel(id: json[ID].toString(), feedName: json[FEED_NAME].toString(), feedUrl: json[FEED_URL], tagName: tagName, categoryName: categoryName, subCatName: subcategoryName); + } +} diff --git a/news-app/lib/data/models/SettingsModel.dart b/news-app/lib/data/models/SettingsModel.dart new file mode 100644 index 00000000..e2b4bd03 --- /dev/null +++ b/news-app/lib/data/models/SettingsModel.dart @@ -0,0 +1,42 @@ +class SettingsModel { + bool showIntroSlider; + bool notification; + String languageCode; + String theme; + String token; + + SettingsModel({ + required this.languageCode, + required this.showIntroSlider, + required this.theme, + required this.notification, + required this.token, + }); + + static SettingsModel fromJson(var settingsJson) { + //to see the json response go to getCurrentSettings() function in settingsRepository + return SettingsModel( + theme: settingsJson['theme'], + showIntroSlider: settingsJson['showIntroSlider'], + notification: settingsJson['notification'], + languageCode: settingsJson['languageCode'], + token: settingsJson['token'], + ); + } + + SettingsModel copyWith({ + String? theme, + bool? showIntroSlider, + bool? notification, + String? languageCode, + String? token, + }) { + return SettingsModel( + theme: theme ?? this.theme, + notification: notification ?? this.notification, + showIntroSlider: showIntroSlider ?? this.showIntroSlider, + languageCode: languageCode ?? this.languageCode, + token: token ?? this.token, + ); + } +} diff --git a/news-app/lib/data/models/TagModel.dart b/news-app/lib/data/models/TagModel.dart new file mode 100644 index 00000000..3fec8755 --- /dev/null +++ b/news-app/lib/data/models/TagModel.dart @@ -0,0 +1,11 @@ +import 'package:news/utils/strings.dart'; + +class TagModel { + String? id, tagName; + + TagModel({this.id, this.tagName}); + + factory TagModel.fromJson(Map json) { + return TagModel(id: json[ID].toString(), tagName: json[TAGNAME]); + } +} diff --git a/news-app/lib/data/models/WeatherData.dart b/news-app/lib/data/models/WeatherData.dart new file mode 100644 index 00000000..096db8dd --- /dev/null +++ b/news-app/lib/data/models/WeatherData.dart @@ -0,0 +1,24 @@ +class WeatherDetails { + String? name; + String? region; + double? tempC; + String? text; + String? icon; + double? minTempC; + double? maxTempC; + String? country; + + WeatherDetails({this.name, this.region, this.tempC, this.text, this.icon, this.maxTempC, this.minTempC, this.country}); + + factory WeatherDetails.fromJson(Map json) { + return WeatherDetails( + name: json["location"]["name"], + region: json["location"]["region"], + country: json["location"]["country"], + tempC: json["current"]["temp_c"], + text: json["current"]["condition"]["text"], + icon: json["current"]["condition"]["icon"], + maxTempC: json["forecast"]["forecastday"][0]["day"]["maxtemp_c"], + minTempC: json["forecast"]["forecastday"][0]["day"]["mintemp_c"]); + } +} diff --git a/news-app/lib/data/models/adSpaceModel.dart b/news-app/lib/data/models/adSpaceModel.dart new file mode 100644 index 00000000..c1dd6123 --- /dev/null +++ b/news-app/lib/data/models/adSpaceModel.dart @@ -0,0 +1,13 @@ +import 'package:news/utils/strings.dart'; + +class AdSpaceModel { + String? id, adSpace, adFeaturedSectionId, adImage, adUrl; + + AdSpaceModel({this.id, this.adSpace, this.adFeaturedSectionId, this.adImage, this.adUrl}); + +//place Ad just above mentioned adFeaturedSectionId + + factory AdSpaceModel.fromJson(Map json) { + return AdSpaceModel(id: json[ID].toString(), adSpace: json[AD_SPACE], adFeaturedSectionId: json[AD_FEATURED_SECTION_ID].toString(), adImage: json[AD_IMAGE], adUrl: json[AD_URL]); + } +} diff --git a/news-app/lib/data/models/appLanguageModel.dart b/news-app/lib/data/models/appLanguageModel.dart new file mode 100644 index 00000000..ca4fab03 --- /dev/null +++ b/news-app/lib/data/models/appLanguageModel.dart @@ -0,0 +1,16 @@ +import 'package:news/utils/strings.dart'; + +class LanguageModel { + String? id, language, languageDisplayName, image, code; + int? isRTL; + LanguageModel({this.id, this.image, this.language, this.languageDisplayName, this.code, this.isRTL}); + factory LanguageModel.fromJson(Map json) { + return LanguageModel( + id: json[ID].toString(), + image: json[IMAGE], + language: json[LANGUAGE], + languageDisplayName: (json[DISPLAY_NAME_LANG] != "") ? json[DISPLAY_NAME_LANG] : json[LANGUAGE], + code: json[CODE], + isRTL: (json[ISRTL].runtimeType == int) ? json[ISRTL] : int.tryParse(json[ISRTL])); + } +} diff --git a/news-app/lib/data/models/authorModel.dart b/news-app/lib/data/models/authorModel.dart new file mode 100644 index 00000000..6a811503 --- /dev/null +++ b/news-app/lib/data/models/authorModel.dart @@ -0,0 +1,49 @@ +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +class Author { + final int? id; + final int? userId; + final String? bio; + final String? telegramLink; + final String? linkedinLink; + final String? facebookLink; + final String? whatsappLink; + final AuthorStatus? status; + + Author({ + this.id, + this.userId, + this.bio, + this.telegramLink, + this.linkedinLink, + this.facebookLink, + this.whatsappLink, + this.status, + }); + + factory Author.fromJson(Map json) { + return Author( + id: json['id'] as int?, + userId: json['user_id'] as int?, + bio: json['bio'] ?? "", + telegramLink: json['telegram_link'] ?? "", + linkedinLink: json['linkedin_link'] ?? "", + facebookLink: json['facebook_link'] ?? "", + whatsappLink: json['whatsapp_link'] ?? "", + status: fromAuthorStatus(json[STATUS])); + } +} + +AuthorStatus? fromAuthorStatus(String? value) { + switch (value) { + case 'pending': + return AuthorStatus.pending; + case 'approved': + return AuthorStatus.approved; + case 'rejected': + return AuthorStatus.rejected; + default: + return null; + } +} diff --git a/news-app/lib/data/models/locationCityModel.dart b/news-app/lib/data/models/locationCityModel.dart new file mode 100644 index 00000000..2bfc1789 --- /dev/null +++ b/news-app/lib/data/models/locationCityModel.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/strings.dart'; + +class LocationCityModel { + final String id; + final String locationName; + final String latitude; + final String longitude; + + LocationCityModel({required this.latitude, required this.longitude, required this.id, required this.locationName}); + + factory LocationCityModel.fromJson(Map json) { + return LocationCityModel(id: json[ID].toString(), locationName: json[LOCATION_NAME], latitude: json[LATITUDE].toString(), longitude: json[LONGITUDE].toString()); + } +} diff --git a/news-app/lib/data/repositories/AddNews/addNewsRemoteDataSource.dart b/news-app/lib/data/repositories/AddNews/addNewsRemoteDataSource.dart new file mode 100644 index 00000000..cacfc287 --- /dev/null +++ b/news-app/lib/data/repositories/AddNews/addNewsRemoteDataSource.dart @@ -0,0 +1,78 @@ +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class AddNewsRemoteDataSource { + //to update fcmId of user's + Future addNewsData( + {required BuildContext context, + required String actionType, + required String catId, + required String title, + required String conTypeId, + required String conType, + required String langId, + File? image, + String? newsId, + String? subCatId, + String? showTill, + String? tagId, + String? url, + String? desc, + String? summDescription, + String? locationId, + File? videoUpload, + List? otherImage, + String? publishDate, + required String metaTitle, + required String metaDescription, + required String metaKeyword, + required String slug, + required int isDraft}) async { + try { + Map body = { + ACTION_TYPE: actionType, + CATEGORY_ID: catId, + TITLE: title, + CONTENT_TYPE: conTypeId, + LANGUAGE_ID: langId, + META_TITLE: metaTitle, + META_DESC: metaDescription, + META_KEYWORD: metaKeyword, + SLUG: slug, + SUMM_DESCRIPTION: summDescription, + IS_DRAFT_KEY: isDraft + }; + Map result = {}; + + if (image != null) body[IMAGE] = await MultipartFile.fromFile(image.path); + if (newsId != null) body[NEWS_ID] = newsId; // in case of update news only + if (subCatId != null) body[SUBCAT_ID] = subCatId; + if (showTill != null) body[SHOW_TILL] = showTill; + if (tagId != null) body[TAG_ID] = tagId; + if (desc != null) body[DESCRIPTION] = desc; + if (locationId != null) body[LOCATION_ID] = locationId; + if (publishDate != null) body[PUBLISHED_DATE] = publishDate; + + if (url != null && (conType == UiUtils.getTranslatedLabel(context, 'videoOtherUrlLbl') || conType == UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl'))) { + body[CONTENT_DATA] = url; + } else if (conType == UiUtils.getTranslatedLabel(context, 'videoUploadLbl')) { + if (videoUpload != null) body[CONTENT_DATA] = await MultipartFile.fromFile(videoUpload.path); + } + + if (otherImage!.isNotEmpty) { + for (var i = 0; i < otherImage.length; i++) { + body["ofile[$i]"] = await MultipartFile.fromFile(otherImage[i].path); + } + } + + result = await Api.sendApiRequest(body: body, url: Api.setNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/AddNews/addNewsRepository.dart b/news-app/lib/data/repositories/AddNews/addNewsRepository.dart new file mode 100644 index 00000000..b276ae8e --- /dev/null +++ b/news-app/lib/data/repositories/AddNews/addNewsRepository.dart @@ -0,0 +1,69 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:news/data/repositories/AddNews/addNewsRemoteDataSource.dart'; + +class AddNewsRepository { + static final AddNewsRepository _addNewsRepository = AddNewsRepository._internal(); + + late AddNewsRemoteDataSource _addNewsRemoteDataSource; + + factory AddNewsRepository() { + _addNewsRepository._addNewsRemoteDataSource = AddNewsRemoteDataSource(); + return _addNewsRepository; + } + + AddNewsRepository._internal(); + + Future> addNews( + {required BuildContext context, + required String actionType, + required String catId, + required String title, + required String conTypeId, + required String conType, + required String langId, + File? image, + String? newsId, + String? subCatId, + String? showTill, + String? tagId, + String? url, + String? desc, + String? summDescription, + String? locationId, + File? videoUpload, + List? otherImage, + String? publishDate, + required String metaTitle, + required String metaDescription, + required String metaKeyword, + required String slug, + required int isDraft}) async { + final result = await _addNewsRemoteDataSource.addNewsData( + context: context, + actionType: actionType, + newsId: newsId, + catId: catId, + langId: langId, + conType: conType, + conTypeId: conTypeId, + image: image, + title: title, + subCatId: subCatId, + showTill: showTill, + desc: desc, + otherImage: otherImage, + tagId: tagId, + url: url, + videoUpload: videoUpload, + locationId: locationId, + metaTitle: metaTitle, + metaDescription: metaDescription, + metaKeyword: metaKeyword, + slug: slug, + publishDate: publishDate ?? null, + summDescription: summDescription, + isDraft: isDraft); + return result; + } +} diff --git a/news-app/lib/data/repositories/AppSystemSetting/systemRepository.dart b/news-app/lib/data/repositories/AppSystemSetting/systemRepository.dart new file mode 100644 index 00000000..7d5caa08 --- /dev/null +++ b/news-app/lib/data/repositories/AppSystemSetting/systemRepository.dart @@ -0,0 +1,13 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SystemRepository { + Future fetchSettings() async { + try { + final result = await Api.sendApiRequest(url: Api.getSettingApi, body: null, isGet: true); + return result[DATA]; + } catch (e) { + throw ApiException(e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/Auth/authLocalDataSource.dart b/news-app/lib/data/repositories/Auth/authLocalDataSource.dart new file mode 100644 index 00000000..a14b03a3 --- /dev/null +++ b/news-app/lib/data/repositories/Auth/authLocalDataSource.dart @@ -0,0 +1,119 @@ +import 'package:hive/hive.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; + +//AuthLocalDataSource will communicate with local database (hive) +class AuthLocalDataSource { + bool? checkIsAuth() { + return Hive.box(authBoxKey).get(isLogInKey, defaultValue: false) ?? false; + } + + String? getId() { + return Hive.box(authBoxKey).get(userIdKey, defaultValue: "0"); + } + + Future setId(String? id) async { + Hive.box(authBoxKey).put(userIdKey, id); + } + + String? getName() { + return Hive.box(authBoxKey).get(userNameKey, defaultValue: ""); + } + + Future setName(String? name) async { + Hive.box(authBoxKey).put(userNameKey, name); + } + + String? getEmail() { + return Hive.box(authBoxKey).get(userEmailKey, defaultValue: ""); + } + + Future setEmail(String? email) async { + Hive.box(authBoxKey).put(userEmailKey, email); + } + + String? getMobile() { + return Hive.box(authBoxKey).get(userMobKey, defaultValue: ""); + } + + Future setMobile(String? mobile) async { + Hive.box(authBoxKey).put(userMobKey, mobile); + } + + String? getType() { + return Hive.box(authBoxKey).get(userTypeKey, defaultValue: ""); + } + + Future setType(String? type) async { + Hive.box(authBoxKey).put(userTypeKey, type); + } + + String? getProfile() { + return Hive.box(authBoxKey).get(userProfileKey, defaultValue: ""); + } + + Future setProfile(String? image) async { + Hive.box(authBoxKey).put(userProfileKey, image); + } + + String? getStatus() { + return Hive.box(authBoxKey).get(userStatusKey, defaultValue: ""); + } + + Future setStatus(String? status) async { + Hive.box(authBoxKey).put(userStatusKey, status); + } + + String getJWTtoken() { + return Hive.box(authBoxKey).get(jwtTokenKey, defaultValue: ""); + } + + Future setJWTtoken(String? jwtToken) async { + Hive.box(authBoxKey).put(jwtTokenKey, jwtToken); + } + + Future changeAuthStatus(bool? authStatus) async { + Hive.box(authBoxKey).put(isLogInKey, authStatus); + } + + String? getAuthorWhatsappLink() { + return Hive.box(authBoxKey).get(authorWhatsappLinkKey, defaultValue: ""); + } + + String? getAuthorFacebookLink() { + return Hive.box(authBoxKey).get(authorfacebookLinkKey, defaultValue: ""); + } + + String? getAuthorTelegramLink() { + return Hive.box(authBoxKey).get(authorTelegramLinkKey, defaultValue: ""); + } + + String? getAuthorLinkedInLink() { + return Hive.box(authBoxKey).get(authorLinkedInLinkKey, defaultValue: ""); + } + + Future setSocialMediaLinks(String? whatsappLink, facebookLink, telegramLink, linkedInLink) async { + Hive.box(authBoxKey).put(authorWhatsappLinkKey, whatsappLink); + Hive.box(authBoxKey).put(authorfacebookLinkKey, facebookLink); + Hive.box(authBoxKey).put(authorTelegramLinkKey, telegramLink); + Hive.box(authBoxKey).put(authorLinkedInLinkKey, linkedInLink); + } + + String getAuthorBio() { + return Hive.box(authBoxKey).get(authorBioKey, defaultValue: ""); + } + + Future setAuthorBio(String? authorBio) async { + Hive.box(authBoxKey).put(authorBioKey, authorBio); + } + + AuthorStatus getAuthorStatus() { + final value = Hive.box(authBoxKey).get(authorStatusKey, defaultValue: AuthorStatus.rejected.name); + + return AuthorStatus.values.byName(value); + } + + Future setAuthorStatus(AuthorStatus authorStatus) async { + Hive.box(authBoxKey).put(authorStatusKey, authorStatus.name); + } +} diff --git a/news-app/lib/data/repositories/Auth/authRemoteDataSource.dart b/news-app/lib/data/repositories/Auth/authRemoteDataSource.dart new file mode 100644 index 00000000..fd7ecb3c --- /dev/null +++ b/news-app/lib/data/repositories/Auth/authRemoteDataSource.dart @@ -0,0 +1,299 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:crypto/crypto.dart'; +import 'package:flutter/material.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter_login_facebook/flutter_login_facebook.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class AuthRemoteDataSource { + final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; + final GoogleSignIn _googleSignIn = GoogleSignIn(scopes: ["profile", "email"]); + final _facebookSignin = FacebookLogin(); + + Future loginAuth({ + required String firebaseId, + required String name, + required String email, + required String type, + required String profile, + required String mobile, + }) async { + try { + final Map body = {FIREBASE_ID: firebaseId, NAME: name, TYPE: type, EMAIL: email}; + if (profile != "") body[PROFILE] = profile; + if (mobile != "") body[MOBILE] = mobile; + + var result = await Api.sendApiRequest(body: body, url: Api.getUserSignUpApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future deleteUserAcc() async { + try { + final result = await Api.sendApiRequest(body: {}, url: Api.userDeleteApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + //to update fcmId of user's + Future updateUserData( + {String? name, String? mobile, String? email, String? filePath, String? authorBio, String? whatsappLink, String? facebookLink, String? telegramLink, String? linkedInLink}) async { + try { + Map body = {}; + Map result = {}; + + if (name != null) body[NAME] = name; + if (mobile != null) body[MOBILE] = mobile; + if (email != null) body[EMAIL] = email; + if (filePath != null && filePath.isNotEmpty) body[PROFILE] = await MultipartFile.fromFile(filePath); + if (authorBio != null) body[AUTHOR_BIO] = authorBio; + if (whatsappLink != null) body[AUTHOR_WHATSAPP_LINK] = whatsappLink; + if (telegramLink != null) body[AUTHOR_TELEGRAM_LINK] = telegramLink; + if (facebookLink != null) body[AUTHOR_FACEBOOK_LINK] = facebookLink; + if (linkedInLink != null) body[AUTHOR_LINKEDIN_LINK] = linkedInLink; + result = await Api.sendApiRequest(body: body, url: Api.setUpdateProfileApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future registerToken({required String fcmId, required BuildContext context}) async { + try { + final body = {TOKEN: fcmId, LANGUAGE_ID: context.read().state.id}; //Pass languageId for specific language Notifications + + String latitude = SettingsLocalDataRepository().getLocationCityValues().first; + String longitude = SettingsLocalDataRepository().getLocationCityValues().last; + + if (latitude != '' && latitude != "null") body[LATITUDE] = latitude; + if (longitude != '' && longitude != "null") body[LONGITUDE] = longitude; + + final result = await Api.sendApiRequest(body: body, url: Api.setRegisterToken); + return result; + } on SocketException catch (e) { + throw SocketException(e.toString()); + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future> socialSignInUser({required AuthProviders authProvider, required BuildContext context, String? email, String? password, String? otp, String? verifiedId}) async { + Map result = {}; + + try { + switch (authProvider) { + case AuthProviders.gmail: + UserCredential? userCredential = await signInWithGoogle(context); + if (userCredential != null) { + result['user'] = userCredential.user!; + return result; + } else { + throw ApiMessageAndCodeException(errorMessage: UiUtils.getTranslatedLabel(context, 'somethingMSg')); + } + + case AuthProviders.mobile: + UserCredential? userCredential = await signInWithPhone(context: context, otp: otp!, verifiedId: verifiedId!); + if (userCredential != null) { + result['user'] = userCredential.user!; + return result; + } else { + throw ApiMessageAndCodeException(errorMessage: UiUtils.getTranslatedLabel(context, 'somethingMSg')); + } + case AuthProviders.fb: + final faceBookAuthResult = await signInWithFacebook(); + if (faceBookAuthResult != null) { + result['user'] = faceBookAuthResult.user!; + return result; + } else { + throw ApiMessageAndCodeException(errorMessage: UiUtils.getTranslatedLabel(context, 'somethingMSg')); + } + case AuthProviders.apple: + UserCredential? userCredential = await signInWithApple(context); + if (userCredential != null) { + result['user'] = userCredential.user!; + return result; + } else { + throw ApiMessageAndCodeException(errorMessage: UiUtils.getTranslatedLabel(context, 'somethingMSg')); + } + case AuthProviders.email: + final userCredential = await signInWithEmailPassword(email: email!, password: password!, context: context); + if (userCredential != null) { + result['user'] = userCredential.user!; + return result; + } else { + return {}; + } + } + } on SocketException catch (_) { + throw ApiMessageAndCodeException(errorMessage: UiUtils.getTranslatedLabel(context, 'internetmsg')); + } on FirebaseAuthException catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future signInWithPhone({required BuildContext context, required String otp, required String verifiedId}) async { + String code = otp.trim(); + + if (code.length == 6) { + //As OTP is of Fixed length 6 + try { + final PhoneAuthCredential credential = PhoneAuthProvider.credential(verificationId: verifiedId, smsCode: otp); + final UserCredential authResult = await _firebaseAuth.signInWithCredential(credential); + final User? user = authResult.user; + if (user != null) { + assert(!user.isAnonymous); + + final User? currentUser = _firebaseAuth.currentUser; + assert(user.uid == currentUser?.uid); + showSnackBar(UiUtils.getTranslatedLabel(context, 'otpMsg'), context); + return authResult; + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'otpError'), context); + return null; + } + } on FirebaseAuthException catch (authError) { + if (authError.code == 'invalidVerificationCode') { + showSnackBar(UiUtils.getTranslatedLabel(context, 'invalidVerificationCode'), context); + return null; + } else { + showSnackBar(authError.message.toString(), context); + return null; + } + } on FirebaseException catch (e) { + showSnackBar(e.message.toString(), context); + return null; + } catch (e) { + showSnackBar(e.toString(), context); + return null; + } + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'enterOtpTxt'), context); + return null; + } + } + + //sign in with email and password in firebase + Future signInWithEmailPassword({required String email, required String password, required BuildContext context}) async { + try { + final UserCredential userCredential = await _firebaseAuth.signInWithEmailAndPassword(email: email, password: password); + + return userCredential; + } on FirebaseAuthException catch (authError) { + if (authError.code == 'userNotFound') { + showSnackBar(UiUtils.getTranslatedLabel(context, 'userNotFound'), context); + } else if (authError.code == 'wrongPassword') { + showSnackBar(UiUtils.getTranslatedLabel(context, 'wrongPassword'), context); + } else { + throw ApiMessageAndCodeException(errorMessage: authError.message!); + } + } on FirebaseException catch (e) { + showSnackBar(e.toString(), context); + } catch (e) { + String errorMessage = e.toString(); + showSnackBar(errorMessage, context); + } + return null; + } + + //signIn using google account + Future signInWithGoogle(BuildContext context) async { + final GoogleSignInAccount? googleUser = await _googleSignIn.signIn(); + if (googleUser == null) { + ApiMessageAndCodeException(errorMessage: UiUtils.getTranslatedLabel(context, 'somethingMSg')); + return null; + } + final GoogleSignInAuthentication googleAuth = await googleUser.authentication; + + final AuthCredential credential = GoogleAuthProvider.credential(accessToken: googleAuth.accessToken, idToken: googleAuth.idToken); + final UserCredential userCredential = await _firebaseAuth.signInWithCredential(credential); + return userCredential; + } + + Future signInWithFacebook() async { + final res = await _facebookSignin.logIn(permissions: [FacebookPermission.publicProfile, FacebookPermission.email]); + +// Check result status + switch (res.status) { + case FacebookLoginStatus.success: + // Send access token to server for validation and auth + final FacebookAccessToken? accessToken = res.accessToken; + AuthCredential authCredential = FacebookAuthProvider.credential(accessToken!.token); + final UserCredential userCredential = await _firebaseAuth.signInWithCredential(authCredential); + return userCredential; + case FacebookLoginStatus.cancel: + return null; + + case FacebookLoginStatus.error: + return null; + } + } + + String sha256ofString(String input) { + final bytes = utf8.encode(input); + final digest = sha256.convert(bytes); + return digest.toString(); + } + + Future signInWithApple(BuildContext context) async { + try { + final rawNonce = generateNonce(); + final nonce = sha256ofString(rawNonce); + + final appleCredential = await SignInWithApple.getAppleIDCredential(scopes: [AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName], nonce: nonce); + + final oauthCredential = OAuthProvider("apple.com").credential(idToken: appleCredential.identityToken, rawNonce: rawNonce, accessToken: appleCredential.authorizationCode); + + final UserCredential authResult = await FirebaseAuth.instance.signInWithCredential(oauthCredential); + + return authResult; + } on FirebaseAuthException catch (authError) { + showSnackBar(authError.message!, context); + return null; + } on FirebaseException catch (e) { + showSnackBar(e.toString(), context); + return null; + } catch (e) { + String errorMessage = e.toString(); + + if (errorMessage == "Null check operator used on a null value") { + //if user goes back from selecting Account + //in case of User gmail not selected & back to Login screen + showSnackBar(UiUtils.getTranslatedLabel(context, 'cancelLogin'), context); + return null; + } else { + showSnackBar(errorMessage, context); + return null; + } + } + } + + Future signOut(AuthProviders? authProvider) async { + _firebaseAuth.signOut(); + if (authProvider == AuthProviders.gmail) { + _googleSignIn.signOut(); + } else if (authProvider == AuthProviders.fb) { + _facebookSignin.logOut(); + } else { + _firebaseAuth.signOut(); + } + } +} diff --git a/news-app/lib/data/repositories/Auth/authRepository.dart b/news-app/lib/data/repositories/Auth/authRepository.dart new file mode 100644 index 00000000..ec0f7968 --- /dev/null +++ b/news-app/lib/data/repositories/Auth/authRepository.dart @@ -0,0 +1,171 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:news/data/models/authorModel.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/data/repositories/Auth/authLocalDataSource.dart'; +import 'package:news/data/repositories/Auth/authRemoteDataSource.dart'; + +class AuthRepository { + static final AuthRepository _authRepository = AuthRepository._internal(); + late AuthLocalDataSource _authLocalDataSource; + late AuthRemoteDataSource _authRemoteDataSource; + + factory AuthRepository() { + _authRepository._authLocalDataSource = AuthLocalDataSource(); + _authRepository._authRemoteDataSource = AuthRemoteDataSource(); + return _authRepository; + } + + AuthRepository._internal(); + + AuthLocalDataSource get authLocalDataSource => _authLocalDataSource; + + //to get auth detials stored in hive box + getLocalAuthDetails() { + return { + "isLogIn": _authLocalDataSource.checkIsAuth(), + ID: _authLocalDataSource.getId(), + NAME: _authLocalDataSource.getName(), + EMAIL: _authLocalDataSource.getEmail(), + MOBILE: _authLocalDataSource.getMobile(), + TYPE: _authLocalDataSource.getType(), + PROFILE: _authLocalDataSource.getProfile(), + STATUS: _authLocalDataSource.getStatus(), + TOKEN: _authLocalDataSource.getJWTtoken(), + AUTHOR_BIO: _authLocalDataSource.getAuthorBio(), + AUTHOR_STATUS: _authLocalDataSource.getAuthorStatus().name, + AUTHOR_WHATSAPP_LINK: _authLocalDataSource.getAuthorWhatsappLink(), + AUTHOR_FACEBOOK_LINK: _authLocalDataSource.getAuthorFacebookLink(), + AUTHOR_TELEGRAM_LINK: _authLocalDataSource.getAuthorTelegramLink(), + AUTHOR_LINKEDIN_LINK: _authLocalDataSource.getAuthorLinkedInLink() + }; + } + + setLocalAuthDetails( + {required bool authStatus, + required String id, + required String name, + required String email, + required String mobile, + required String type, + required String profile, + required String status, + required String role, + required String jwtToken, + required int isAuthor, + Author? authorDetails}) { + _authLocalDataSource.changeAuthStatus(authStatus); + _authLocalDataSource.setId(id); + _authLocalDataSource.setName(name); + _authLocalDataSource.setEmail(email); + _authLocalDataSource.setMobile(mobile); + _authLocalDataSource.setType(type); + _authLocalDataSource.setProfile(profile); + _authLocalDataSource.setStatus(status); + _authLocalDataSource.setJWTtoken(jwtToken); + _authLocalDataSource.setAuthorStatus((authorDetails != null) ? authorDetails.status! : AuthorStatus.rejected); + _authLocalDataSource.setAuthorBio((authorDetails != null) ? authorDetails.bio : ""); + _authLocalDataSource.setSocialMediaLinks((authorDetails != null) ? authorDetails.whatsappLink : "", (authorDetails != null) ? authorDetails.facebookLink : "", + (authorDetails != null) ? authorDetails.telegramLink : "", (authorDetails != null) ? authorDetails.linkedinLink : ""); + } + + //First we signin user with given provider then add user details + Future> signInUser({required BuildContext context, required AuthProviders authProvider, String? email, String? password, String? otp, String? verifiedId}) async { + try { + final result = await _authRemoteDataSource.socialSignInUser(context: context, authProvider: authProvider, email: email, password: password, verifiedId: verifiedId, otp: otp); + final user = result['user'] as User; + var providerData = user.providerData[0]; + if (authProvider == AuthProviders.email && !user.emailVerified) { + throw ApiException(UiUtils.getTranslatedLabel(context, 'verifyEmailMsg')); + } + + Map userDataTest = await _authRemoteDataSource.loginAuth( + mobile: providerData.phoneNumber ?? "", + email: providerData.email ?? "", + firebaseId: user.uid, + name: providerData.displayName ?? "", + profile: providerData.photoURL ?? "", + type: authProvider.name); + if (!userDataTest[ERROR]) { + if (userDataTest[DATA][STATUS].toString() != "0") { + setLocalAuthDetails( + type: userDataTest[DATA][TYPE] ?? "", + profile: userDataTest[DATA][PROFILE] ?? "", + name: userDataTest[DATA][NAME] ?? "", + email: userDataTest[DATA][EMAIL] ?? "", + authStatus: true, + id: (userDataTest[DATA][ID].toString() != "") ? userDataTest[DATA][ID].toString() : "0", + mobile: userDataTest[DATA][MOBILE] ?? "", + role: userDataTest[DATA][ROLE].toString(), + status: userDataTest[DATA][STATUS].toString(), + jwtToken: userDataTest[DATA][TOKEN] ?? "", + isAuthor: userDataTest[DATA][IS_AUTHOR] ?? 0, + authorDetails: (userDataTest[DATA][IS_AUTHOR] == 1 && userDataTest[DATA][AUTHOR] != null) ? Author.fromJson(userDataTest[DATA][AUTHOR]) : null); + } + return userDataTest; + } else { + signOut(authProvider); + throw ApiMessageAndCodeException(errorMessage: userDataTest[MESSAGE]); + } + } catch (e) { + signOut(authProvider); + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future updateUserData( + {String? name, String? mobile, String? email, String? filePath, String? authorBio, String? whatsappLink, String? facebookLink, String? telegramLink, String? linkedInLink}) async { + final result = await _authRemoteDataSource.updateUserData( + email: email, + name: name, + mobile: mobile, + filePath: filePath, + authorBio: authorBio, + whatsappLink: whatsappLink, + facebookLink: facebookLink, + telegramLink: telegramLink, + linkedInLink: linkedInLink); + if (name != null) _authLocalDataSource.setName(name); + if (mobile != null) _authLocalDataSource.setMobile(mobile); + if (email != null) _authLocalDataSource.setEmail(email); + if (filePath != null && filePath.isNotEmpty) _authLocalDataSource.setProfile(result[PROFILE]); + if (authorBio != null) _authLocalDataSource.setAuthorBio(authorBio); + if (whatsappLink != null || facebookLink != null || telegramLink != null || linkedInLink != null) _authLocalDataSource.setSocialMediaLinks(whatsappLink, facebookLink, telegramLink, linkedInLink); + + return result; + } + + Future> registerToken({required String fcmId, required BuildContext context}) async { + final result = await _authRemoteDataSource.registerToken(fcmId: fcmId, context: context); + return result; + } + + //to delete my account + Future deleteUser() async { + final result = await _authRemoteDataSource.deleteUserAcc(); + return result; + } + + Future signOut(AuthProviders authProvider) async { + _authRemoteDataSource.signOut(authProvider); + await _authLocalDataSource.changeAuthStatus(false); + await _authLocalDataSource.setId("0"); + await _authLocalDataSource.setName(""); + await _authLocalDataSource.setEmail(""); + await _authLocalDataSource.setMobile(""); + await _authLocalDataSource.setType(""); + await _authLocalDataSource.setProfile(""); + await _authLocalDataSource.setStatus(""); + await _authLocalDataSource.setJWTtoken(""); + await _authLocalDataSource.setAuthorBio(""); + await _authLocalDataSource.setAuthorStatus(AuthorStatus.rejected); + await _authLocalDataSource.setSocialMediaLinks("", "", "", ""); + await Hive.box(authBoxKey).clear(); + } +} diff --git a/news-app/lib/data/repositories/Bookmark/bookmarkRemoteDataSource.dart b/news-app/lib/data/repositories/Bookmark/bookmarkRemoteDataSource.dart new file mode 100644 index 00000000..92de512c --- /dev/null +++ b/news-app/lib/data/repositories/Bookmark/bookmarkRemoteDataSource.dart @@ -0,0 +1,24 @@ +import 'package:news/utils/strings.dart'; +import 'package:news/utils/api.dart'; + +class BookmarkRemoteDataSource { + Future getBookmark({required String langId, required String offset, required String perPage}) async { + try { + final body = {LANGUAGE_ID: langId, OFFSET: offset, LIMIT: perPage}; + final result = await Api.sendApiRequest(body: body, url: Api.getBookmarkApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future addBookmark({required String newsId, required String status}) async { + try { + final body = {NEWS_ID: newsId, STATUS: status}; + final result = await Api.sendApiRequest(body: body, url: Api.setBookmarkApi); + return result[DATA]; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/Bookmark/bookmarkRepository.dart b/news-app/lib/data/repositories/Bookmark/bookmarkRepository.dart new file mode 100644 index 00000000..5e064499 --- /dev/null +++ b/news-app/lib/data/repositories/Bookmark/bookmarkRepository.dart @@ -0,0 +1,27 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Bookmark/bookmarkRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class BookmarkRepository { + static final BookmarkRepository _bookmarkRepository = BookmarkRepository._internal(); + late BookmarkRemoteDataSource _bookmarkRemoteDataSource; + + factory BookmarkRepository() { + _bookmarkRepository._bookmarkRemoteDataSource = BookmarkRemoteDataSource(); + return _bookmarkRepository; + } + + BookmarkRepository._internal(); + + Future> getBookmark({required String offset, required String limit, required String langId}) async { + final result = await _bookmarkRemoteDataSource.getBookmark(perPage: limit, offset: offset, langId: langId); + return (result[ERROR]) + ? {ERROR: result[ERROR], MESSAGE: result[MESSAGE]} + : {ERROR: result[ERROR], "total": result[TOTAL], "Bookmark": (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList()}; + } + + Future setBookmark({required String newsId, required String status}) async { + final result = await _bookmarkRemoteDataSource.addBookmark(status: status, newsId: newsId); + return result; + } +} diff --git a/news-app/lib/data/repositories/BreakingNews/breakNewsRemoteDataSource.dart b/news-app/lib/data/repositories/BreakingNews/breakNewsRemoteDataSource.dart new file mode 100644 index 00000000..b772355f --- /dev/null +++ b/news-app/lib/data/repositories/BreakingNews/breakNewsRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class BreakingNewsRemoteDataSource { + Future getBreakingNews({required String langId}) async { + try { + final body = {LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getBreakingNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/BreakingNews/breakNewsRepository.dart b/news-app/lib/data/repositories/BreakingNews/breakNewsRepository.dart new file mode 100644 index 00000000..a0d33ed9 --- /dev/null +++ b/news-app/lib/data/repositories/BreakingNews/breakNewsRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/repositories/BreakingNews/breakNewsRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class BreakingNewsRepository { + static final BreakingNewsRepository _breakingNewsRepository = BreakingNewsRepository._internal(); + + late BreakingNewsRemoteDataSource _breakingNewsRemoteDataSource; + + factory BreakingNewsRepository() { + _breakingNewsRepository._breakingNewsRemoteDataSource = BreakingNewsRemoteDataSource(); + return _breakingNewsRepository; + } + + BreakingNewsRepository._internal(); + + Future getBreakingNews({required String langId}) async { + final result = await _breakingNewsRemoteDataSource.getBreakingNews(langId: langId); + + return (result[ERROR]) ? {ERROR: result[ERROR], MESSAGE: result[MESSAGE]} : {ERROR: result[ERROR], "BreakingNews": (result[DATA] as List).map((e) => BreakingNewsModel.fromJson(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/Category/categoryRemoteDataSource.dart b/news-app/lib/data/repositories/Category/categoryRemoteDataSource.dart new file mode 100644 index 00000000..6a8070d7 --- /dev/null +++ b/news-app/lib/data/repositories/Category/categoryRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class CategoryRemoteDataSource { + Future getCategory({required String limit, required String offset, required String langId}) async { + try { + final body = {LIMIT: limit, OFFSET: offset, LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getCatApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/Category/categoryRepository.dart b/news-app/lib/data/repositories/Category/categoryRepository.dart new file mode 100644 index 00000000..548ffe34 --- /dev/null +++ b/news-app/lib/data/repositories/Category/categoryRepository.dart @@ -0,0 +1,24 @@ +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/repositories/Category/categoryRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class CategoryRepository { + static final CategoryRepository _notificationRepository = CategoryRepository._internal(); + + late CategoryRemoteDataSource _notificationRemoteDataSource; + + factory CategoryRepository() { + _notificationRepository._notificationRemoteDataSource = CategoryRemoteDataSource(); + return _notificationRepository; + } + + CategoryRepository._internal(); + + Future> getCategory({required String offset, required String limit, required String langId}) async { + final result = await _notificationRemoteDataSource.getCategory(limit: limit, offset: offset, langId: langId); + + return (result[ERROR]) + ? {ERROR: result[ERROR], MESSAGE: result[MESSAGE]} + : {ERROR: result[ERROR], "total": result[TOTAL], "Category": (result[DATA] as List).map((e) => CategoryModel.fromJson(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/CommentNews/commNewsRemoteDataSource.dart b/news-app/lib/data/repositories/CommentNews/commNewsRemoteDataSource.dart new file mode 100644 index 00000000..c0c2f447 --- /dev/null +++ b/news-app/lib/data/repositories/CommentNews/commNewsRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class CommentNewsRemoteDataSource { + Future getCommentNews({required String limit, required String offset, required String newsId}) async { + try { + final body = {LIMIT: limit, OFFSET: offset, NEWS_ID: newsId}; + final result = await Api.sendApiRequest(body: body, url: Api.getCommentByNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/CommentNews/commNewsRepository.dart b/news-app/lib/data/repositories/CommentNews/commNewsRepository.dart new file mode 100644 index 00000000..72eb78fe --- /dev/null +++ b/news-app/lib/data/repositories/CommentNews/commNewsRepository.dart @@ -0,0 +1,30 @@ +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/data/repositories/CommentNews/commNewsRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class CommentNewsRepository { + static final CommentNewsRepository _commentNewsRepository = CommentNewsRepository._internal(); + + late CommentNewsRemoteDataSource _commentNewsRemoteDataSource; + + factory CommentNewsRepository() { + _commentNewsRepository._commentNewsRemoteDataSource = CommentNewsRemoteDataSource(); + return _commentNewsRepository; + } + + CommentNewsRepository._internal(); + + Future> getCommentNews({required String offset, required String limit, required String newsId}) async { + final result = await _commentNewsRemoteDataSource.getCommentNews(limit: limit, offset: offset, newsId: newsId); + if (result[ERROR]) { + return {ERROR: result[ERROR], MESSAGE: result[MESSAGE]}; + } else { + final List commentsList = (result[DATA] as List).map((e) => CommentModel.fromJson(e)).toList(); + final List replyList = []; + for (var i in commentsList) { + replyList.addAll(i.replyComList as List); + } + return {ERROR: result[ERROR], "total": result[TOTAL], "CommentNews": commentsList}; + } + } +} diff --git a/news-app/lib/data/repositories/DeleteImageId/deleteImageIdRemoteDataSource.dart b/news-app/lib/data/repositories/DeleteImageId/deleteImageIdRemoteDataSource.dart new file mode 100644 index 00000000..9e56c5ae --- /dev/null +++ b/news-app/lib/data/repositories/DeleteImageId/deleteImageIdRemoteDataSource.dart @@ -0,0 +1,19 @@ +import '../../../utils/api.dart'; +import '../../../utils/strings.dart'; + +class DeleteImageRemoteDataSource { + Future deleteImage({required String imageId}) async { + try { + final body = { + ID: imageId, + }; + final result = await Api.sendApiRequest( + body: body, + url: Api.setDeleteImageApi, + ); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/DeleteImageId/deleteImageRepository.dart b/news-app/lib/data/repositories/DeleteImageId/deleteImageRepository.dart new file mode 100644 index 00000000..410365d3 --- /dev/null +++ b/news-app/lib/data/repositories/DeleteImageId/deleteImageRepository.dart @@ -0,0 +1,20 @@ +import 'deleteImageIdRemoteDataSource.dart'; + +class DeleteImageRepository { + static final DeleteImageRepository _deleteImageRepository = DeleteImageRepository._internal(); + late DeleteImageRemoteDataSource _deleteImageRemoteDataSource; + + factory DeleteImageRepository() { + _deleteImageRepository._deleteImageRemoteDataSource = DeleteImageRemoteDataSource(); + return _deleteImageRepository; + } + + DeleteImageRepository._internal(); + + Future setDeleteImage({ + required String imageId, + }) async { + final result = await _deleteImageRemoteDataSource.deleteImage(imageId: imageId); + return result; + } +} diff --git a/news-app/lib/data/repositories/DeleteUserNews/deleteUserNewsRemoteDataSource.dart b/news-app/lib/data/repositories/DeleteUserNews/deleteUserNewsRemoteDataSource.dart new file mode 100644 index 00000000..00b091ef --- /dev/null +++ b/news-app/lib/data/repositories/DeleteUserNews/deleteUserNewsRemoteDataSource.dart @@ -0,0 +1,19 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class DeleteUserNewsRemoteDataSource { + Future deleteUserNews({required String newsId}) async { + try { + final body = { + ID: newsId, + }; + final result = await Api.sendApiRequest( + body: body, + url: Api.setDeleteNewsApi, + ); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/DeleteUserNews/deleteUserNewsRepository.dart b/news-app/lib/data/repositories/DeleteUserNews/deleteUserNewsRepository.dart new file mode 100644 index 00000000..4282544f --- /dev/null +++ b/news-app/lib/data/repositories/DeleteUserNews/deleteUserNewsRepository.dart @@ -0,0 +1,20 @@ +import 'deleteUserNewsRemoteDataSource.dart'; + +class DeleteUserNewsRepository { + static final DeleteUserNewsRepository _deleteUserNewsRepository = DeleteUserNewsRepository._internal(); + late DeleteUserNewsRemoteDataSource _deleteUserNewsRemoteDataSource; + + factory DeleteUserNewsRepository() { + _deleteUserNewsRepository._deleteUserNewsRemoteDataSource = DeleteUserNewsRemoteDataSource(); + return _deleteUserNewsRepository; + } + + DeleteUserNewsRepository._internal(); + + Future setDeleteUserNews({ + required String newsId, + }) async { + final result = await _deleteUserNewsRemoteDataSource.deleteUserNews(newsId: newsId); + return result; + } +} diff --git a/news-app/lib/data/repositories/DeleteUserNotification/deleteUserNotiRemoteDataSource.dart b/news-app/lib/data/repositories/DeleteUserNotification/deleteUserNotiRemoteDataSource.dart new file mode 100644 index 00000000..52133dfe --- /dev/null +++ b/news-app/lib/data/repositories/DeleteUserNotification/deleteUserNotiRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class DeleteUserNotiRemoteDataSource { + Future deleteUserNotification({required String id}) async { + try { + final body = {ID: id}; + final result = await Api.sendApiRequest(body: body, url: Api.deleteUserNotiApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/DeleteUserNotification/deleteUserNotiRepository.dart b/news-app/lib/data/repositories/DeleteUserNotification/deleteUserNotiRepository.dart new file mode 100644 index 00000000..7d7e2afc --- /dev/null +++ b/news-app/lib/data/repositories/DeleteUserNotification/deleteUserNotiRepository.dart @@ -0,0 +1,18 @@ +import 'package:news/data/repositories/DeleteUserNotification/deleteUserNotiRemoteDataSource.dart'; + +class DeleteUserNotiRepository { + static final DeleteUserNotiRepository _deleteUserNotiRepository = DeleteUserNotiRepository._internal(); + late DeleteUserNotiRemoteDataSource _deleteUserNotiRemoteDataSource; + + factory DeleteUserNotiRepository() { + _deleteUserNotiRepository._deleteUserNotiRemoteDataSource = DeleteUserNotiRemoteDataSource(); + return _deleteUserNotiRepository; + } + + DeleteUserNotiRepository._internal(); + + Future deleteUserNotification({required String id}) async { + final result = await _deleteUserNotiRemoteDataSource.deleteUserNotification(id: id); + return result; + } +} diff --git a/news-app/lib/data/repositories/FeatureSection/sectionRemoteDataSource.dart b/news-app/lib/data/repositories/FeatureSection/sectionRemoteDataSource.dart new file mode 100644 index 00000000..3f11cae8 --- /dev/null +++ b/news-app/lib/data/repositories/FeatureSection/sectionRemoteDataSource.dart @@ -0,0 +1,19 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +class SectionRemoteDataSource { + Future getSections({required String langId, String? latitude, String? longitude, String? limit, String? offset, String? sectionOffset}) async { + try { + final body = {LANGUAGE_ID: langId, LIMIT: limit, OFFSET: offset, SECTION_LIMIT: limitOfSectionsData, SECTION_OFFSET: sectionOffset}; + + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + + final result = await Api.sendApiRequest(body: body, url: Api.getFeatureSectionApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/FeatureSection/sectionRepository.dart b/news-app/lib/data/repositories/FeatureSection/sectionRepository.dart new file mode 100644 index 00000000..81aebd15 --- /dev/null +++ b/news-app/lib/data/repositories/FeatureSection/sectionRepository.dart @@ -0,0 +1,24 @@ +import 'package:news/data/models/FeatureSectionModel.dart'; +import 'package:news/data/repositories/FeatureSection/sectionRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class SectionRepository { + static final SectionRepository _sectionRepository = SectionRepository._internal(); + + late SectionRemoteDataSource _sectionRemoteDataSource; + + factory SectionRepository() { + _sectionRepository._sectionRemoteDataSource = SectionRemoteDataSource(); + return _sectionRepository; + } + + SectionRepository._internal(); + + Future> getSection({required String langId, String? latitude, String? longitude, String? limit, String? offset, String? sectionOffset}) async { + final result = await _sectionRemoteDataSource.getSections(langId: langId, latitude: latitude, longitude: longitude, limit: limit, offset: offset, sectionOffset: sectionOffset); + + return (result[ERROR]) + ? {ERROR: result[ERROR], MESSAGE: result[MESSAGE]} + : {ERROR: result[ERROR], "Section": (result[DATA] as List).map((e) => FeatureSectionModel.fromJson(e)).toList(), TOTAL: result[TOTAL]}; + } +} diff --git a/news-app/lib/data/repositories/GetSurveyAnswer/getSurveyAnsRemoteDataSource.dart b/news-app/lib/data/repositories/GetSurveyAnswer/getSurveyAnsRemoteDataSource.dart new file mode 100644 index 00000000..559c00f8 --- /dev/null +++ b/news-app/lib/data/repositories/GetSurveyAnswer/getSurveyAnsRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/strings.dart'; +import 'package:news/utils/api.dart'; + +class GetSurveyAnsRemoteDataSource { + Future getSurveyAns({required String langId}) async { + try { + final body = {LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getQueResultApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/GetSurveyAnswer/getSurveyAnsRepository.dart b/news-app/lib/data/repositories/GetSurveyAnswer/getSurveyAnsRepository.dart new file mode 100644 index 00000000..47b53755 --- /dev/null +++ b/news-app/lib/data/repositories/GetSurveyAnswer/getSurveyAnsRepository.dart @@ -0,0 +1,20 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/GetSurveyAnswer/getSurveyAnsRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class GetSurveyAnsRepository { + static final GetSurveyAnsRepository _getSurveyAnsRepository = GetSurveyAnsRepository._internal(); + + late GetSurveyAnsRemoteDataSource _getSurveyAnsRemoteDataSource; + + factory GetSurveyAnsRepository() { + _getSurveyAnsRepository._getSurveyAnsRemoteDataSource = GetSurveyAnsRemoteDataSource(); + return _getSurveyAnsRepository; + } + + GetSurveyAnsRepository._internal(); + Future> getSurveyAns({required String langId}) async { + final result = await _getSurveyAnsRemoteDataSource.getSurveyAns(langId: langId); + return {"GetSurveyAns": (result[DATA] as List).map((e) => NewsModel.fromSurvey(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/GetUserById/getUserByIdDataSource.dart b/news-app/lib/data/repositories/GetUserById/getUserByIdDataSource.dart new file mode 100644 index 00000000..b49cb59f --- /dev/null +++ b/news-app/lib/data/repositories/GetUserById/getUserByIdDataSource.dart @@ -0,0 +1,12 @@ +import 'package:news/utils/api.dart'; + +class GetUserByIdRemoteDataSource { + Future getUserById() async { + try { + final result = await Api.sendApiRequest(body: {}, url: Api.getUserByIdApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/GetUserById/getUserByIdRepository.dart b/news-app/lib/data/repositories/GetUserById/getUserByIdRepository.dart new file mode 100644 index 00000000..9824555e --- /dev/null +++ b/news-app/lib/data/repositories/GetUserById/getUserByIdRepository.dart @@ -0,0 +1,20 @@ +import 'package:news/data/repositories/GetUserById/getUserByIdDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class GetUserByIdRepository { + static final GetUserByIdRepository _getUserByIdRepository = GetUserByIdRepository._internal(); + + late GetUserByIdRemoteDataSource _getUserByIdRemoteDataSource; + + factory GetUserByIdRepository() { + _getUserByIdRepository._getUserByIdRemoteDataSource = GetUserByIdRemoteDataSource(); + return _getUserByIdRepository; + } + + GetUserByIdRepository._internal(); + + Future> getUserById() async { + final result = await _getUserByIdRemoteDataSource.getUserById(); + return result[DATA]; + } +} diff --git a/news-app/lib/data/repositories/GetUserNews/getUserNewsRemoteDataSource.dart b/news-app/lib/data/repositories/GetUserNews/getUserNewsRemoteDataSource.dart new file mode 100644 index 00000000..b60bc614 --- /dev/null +++ b/news-app/lib/data/repositories/GetUserNews/getUserNewsRemoteDataSource.dart @@ -0,0 +1,16 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class GetUserNewsRemoteDataSource { + Future getGetUserNews({required String limit, required String offset, String? latitude, String? longitude}) async { + try { + final body = {LIMIT: limit, OFFSET: offset, USER_NEWS: 1}; + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + final result = await Api.sendApiRequest(body: body, url: Api.getNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/GetUserNews/getUserNewsRepository.dart b/news-app/lib/data/repositories/GetUserNews/getUserNewsRepository.dart new file mode 100644 index 00000000..e5556a5b --- /dev/null +++ b/news-app/lib/data/repositories/GetUserNews/getUserNewsRepository.dart @@ -0,0 +1,24 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/GetUserNews/getUserNewsRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class GetUserNewsRepository { + static final GetUserNewsRepository _getUserNewsRepository = GetUserNewsRepository._internal(); + + late GetUserNewsRemoteDataSource _getUserNewsRemoteDataSource; + + factory GetUserNewsRepository() { + _getUserNewsRepository._getUserNewsRemoteDataSource = GetUserNewsRemoteDataSource(); + return _getUserNewsRepository; + } + + GetUserNewsRepository._internal(); + + Future getGetUserNews({required String offset, required String limit, String? latitude, String? longitude}) async { + final result = await _getUserNewsRemoteDataSource.getGetUserNews(limit: limit, offset: offset, latitude: latitude, longitude: longitude); + + return (result[ERROR]) + ? {ERROR: result[ERROR], MESSAGE: result[MESSAGE]} + : {ERROR: result[ERROR], "total": result[TOTAL], "GetUserNews": (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/LanguageJson/languageJsonRemoteDataRepo.dart b/news-app/lib/data/repositories/LanguageJson/languageJsonRemoteDataRepo.dart new file mode 100644 index 00000000..f9b0a4b6 --- /dev/null +++ b/news-app/lib/data/repositories/LanguageJson/languageJsonRemoteDataRepo.dart @@ -0,0 +1,15 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class LanguageJsonRemoteDataSource { + Future getLanguageJson({required String lanCode}) async { + try { + final body = {CODE: lanCode}; + final result = await Api.sendApiRequest(body: body, url: Api.getLangJsonDataApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/LanguageJson/languageJsonRepository.dart b/news-app/lib/data/repositories/LanguageJson/languageJsonRepository.dart new file mode 100644 index 00000000..33f10b82 --- /dev/null +++ b/news-app/lib/data/repositories/LanguageJson/languageJsonRepository.dart @@ -0,0 +1,40 @@ +import 'package:hive/hive.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; +import 'package:news/utils/strings.dart'; +import 'languageJsonRemoteDataRepo.dart'; + +class LanguageJsonRepository { + static final LanguageJsonRepository _languageRepository = LanguageJsonRepository._internal(); + + late LanguageJsonRemoteDataSource _languageRemoteDataSource; + + factory LanguageJsonRepository() { + _languageRepository._languageRemoteDataSource = LanguageJsonRemoteDataSource(); + return _languageRepository; + } + + LanguageJsonRepository._internal(); + + Future getLanguageJson({required String lanCode}) async { + try { + final result = await _languageRemoteDataSource.getLanguageJson(lanCode: lanCode); + return result[DATA]; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future> fetchLanguageLabels(String langCode) async { + try { + Map languageLabelsJson = {}; + await getLanguageJson(lanCode: langCode).then((value) async { + languageLabelsJson = value as Map; + await Hive.box(settingsBoxKey).put(langCode, languageLabelsJson); + }); + return languageLabelsJson; + } catch (e) { + throw ApiException(e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsDataSource.dart b/news-app/lib/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsDataSource.dart new file mode 100644 index 00000000..e442ea80 --- /dev/null +++ b/news-app/lib/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsDataSource.dart @@ -0,0 +1,26 @@ +import 'package:news/utils/strings.dart'; +import 'package:news/utils/api.dart'; + +class LikeAndDisLikeRemoteDataSource { + Future getLike({required String langId, required String offset, required String perPage}) async { + try { + final body = {LANGUAGE_ID: langId, OFFSET: offset, LIMIT: perPage}; + + final result = await Api.sendApiRequest(body: body, url: Api.getLikeNewsApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future addAndRemoveLike({required String newsId, required String status}) async { + try { + final body = {NEWS_ID: newsId, STATUS: status}; + final result = await Api.sendApiRequest(body: body, url: Api.setLikesDislikesApi); + return result[DATA]; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart b/news-app/lib/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart new file mode 100644 index 00000000..65218b9f --- /dev/null +++ b/news-app/lib/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart @@ -0,0 +1,26 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class LikeAndDisLikeRepository { + static final LikeAndDisLikeRepository _LikeAndDisLikeRepository = LikeAndDisLikeRepository._internal(); + late LikeAndDisLikeRemoteDataSource _LikeAndDisLikeRemoteDataSource; + + factory LikeAndDisLikeRepository() { + _LikeAndDisLikeRepository._LikeAndDisLikeRemoteDataSource = LikeAndDisLikeRemoteDataSource(); + return _LikeAndDisLikeRepository; + } + + LikeAndDisLikeRepository._internal(); + + Future> getLike({required String offset, required String limit, required String langId}) async { + final result = await _LikeAndDisLikeRemoteDataSource.getLike(perPage: limit, offset: offset, langId: langId); + + return {"total": result[TOTAL], "LikeAndDisLike": (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList()}; + } + + Future setLike({required String newsId, required String status}) async { + final result = await _LikeAndDisLikeRemoteDataSource.addAndRemoveLike(status: status, newsId: newsId); + return result; + } +} diff --git a/news-app/lib/data/repositories/LiveStream/liveRemoteDataSource.dart b/news-app/lib/data/repositories/LiveStream/liveRemoteDataSource.dart new file mode 100644 index 00000000..12873197 --- /dev/null +++ b/news-app/lib/data/repositories/LiveStream/liveRemoteDataSource.dart @@ -0,0 +1,15 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class LiveStreamRemoteDataSource { + Future getLiveStreams({required String langId}) async { + try { + final body = {LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getLiveStreamingApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/LiveStream/liveStreamRepository.dart b/news-app/lib/data/repositories/LiveStream/liveStreamRepository.dart new file mode 100644 index 00000000..e1d962a3 --- /dev/null +++ b/news-app/lib/data/repositories/LiveStream/liveStreamRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/models/LiveStreamingModel.dart'; +import 'package:news/data/repositories/LiveStream/liveRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class LiveStreamRepository { + static final LiveStreamRepository _liveStreamRepository = LiveStreamRepository._internal(); + + late LiveStreamRemoteDataSource _liveStreamRemoteDataSource; + + factory LiveStreamRepository() { + _liveStreamRepository._liveStreamRemoteDataSource = LiveStreamRemoteDataSource(); + return _liveStreamRepository; + } + + LiveStreamRepository._internal(); + + Future getLiveStream({required String langId}) async { + final result = await _liveStreamRemoteDataSource.getLiveStreams(langId: langId); + + return (result[ERROR]) ? {ERROR: result[ERROR], MESSAGE: result[MESSAGE]} : {ERROR: result[ERROR], "LiveStream": (result[DATA] as List).map((e) => LiveStreamingModel.fromJson(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/NewsById/NewsByIdRemoteDataSource.dart b/news-app/lib/data/repositories/NewsById/NewsByIdRemoteDataSource.dart new file mode 100644 index 00000000..fc72a1ce --- /dev/null +++ b/news-app/lib/data/repositories/NewsById/NewsByIdRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class NewsByIdRemoteDataSource { + Future getNewsById({required String newsId, required String langId}) async { + try { + final body = {LANGUAGE_ID: langId, ID: newsId}; + final result = await Api.sendApiRequest(body: body, url: Api.getNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/NewsById/NewsByIdRepository.dart b/news-app/lib/data/repositories/NewsById/NewsByIdRepository.dart new file mode 100644 index 00000000..794652a8 --- /dev/null +++ b/news-app/lib/data/repositories/NewsById/NewsByIdRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/NewsById/NewsByIdRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class NewsByIdRepository { + static final NewsByIdRepository _newsByIdRepository = NewsByIdRepository._internal(); + + late NewsByIdRemoteDataSource _newsByIdRemoteDataSource; + + factory NewsByIdRepository() { + _newsByIdRepository._newsByIdRemoteDataSource = NewsByIdRemoteDataSource(); + return _newsByIdRepository; + } + + NewsByIdRepository._internal(); + + Future> getNewsById({required String newsId, required String langId}) async { + final result = await _newsByIdRemoteDataSource.getNewsById(newsId: newsId, langId: langId); + + return {"NewsById": (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/NewsComment/DeleteComment/deleteCommDataSource.dart b/news-app/lib/data/repositories/NewsComment/DeleteComment/deleteCommDataSource.dart new file mode 100644 index 00000000..f108bc7f --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/DeleteComment/deleteCommDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class DeleteCommRemoteDataSource { + Future deleteComm({required String commId}) async { + try { + final body = {COMMENT_ID: commId}; + final result = await Api.sendApiRequest(body: body, url: Api.setCommentDeleteApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/NewsComment/DeleteComment/deleteCommRepository.dart b/news-app/lib/data/repositories/NewsComment/DeleteComment/deleteCommRepository.dart new file mode 100644 index 00000000..df815582 --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/DeleteComment/deleteCommRepository.dart @@ -0,0 +1,18 @@ +import 'package:news/data/repositories/NewsComment/DeleteComment/deleteCommDataSource.dart'; + +class DeleteCommRepository { + static final DeleteCommRepository _deleteCommRepository = DeleteCommRepository._internal(); + late DeleteCommRemoteDataSource _deleteCommRemoteDataSource; + + factory DeleteCommRepository() { + _deleteCommRepository._deleteCommRemoteDataSource = DeleteCommRemoteDataSource(); + return _deleteCommRepository; + } + + DeleteCommRepository._internal(); + + Future setDeleteComm({required String commId}) async { + final result = await _deleteCommRemoteDataSource.deleteComm(commId: commId); + return result; + } +} diff --git a/news-app/lib/data/repositories/NewsComment/FlagComment/flagCommRemoteDataSource.dart b/news-app/lib/data/repositories/NewsComment/FlagComment/flagCommRemoteDataSource.dart new file mode 100644 index 00000000..e9d9497f --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/FlagComment/flagCommRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SetFlagRemoteDataSource { + Future setFlag({required String commId, required String newsId, required String message}) async { + try { + final body = {COMMENT_ID: commId, NEWS_ID: newsId, MESSAGE: message}; + final result = await Api.sendApiRequest(body: body, url: Api.setFlagApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/NewsComment/FlagComment/flagCommRepository.dart b/news-app/lib/data/repositories/NewsComment/FlagComment/flagCommRepository.dart new file mode 100644 index 00000000..9d6a77cd --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/FlagComment/flagCommRepository.dart @@ -0,0 +1,19 @@ +import 'package:news/data/repositories/NewsComment/FlagComment/flagCommRemoteDataSource.dart'; + +class SetFlagRepository { + static final SetFlagRepository _setFlagRepository = SetFlagRepository._internal(); + + late SetFlagRemoteDataSource _setFlagRemoteDataSource; + + factory SetFlagRepository() { + _setFlagRepository._setFlagRemoteDataSource = SetFlagRemoteDataSource(); + return _setFlagRepository; + } + + SetFlagRepository._internal(); + + Future> setFlag({required String commId, required String newsId, required String message}) async { + final result = await _setFlagRemoteDataSource.setFlag(commId: commId, newsId: newsId, message: message); + return result; + } +} diff --git a/news-app/lib/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommDataSource.dart b/news-app/lib/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommDataSource.dart new file mode 100644 index 00000000..ef0ac1a2 --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class LikeAndDislikeCommRemoteDataSource { + Future likeAndDislikeComm({required String langId, required String commId, required String status}) async { + try { + final body = {LANGUAGE_ID: langId, COMMENT_ID: commId, STATUS: status}; + final result = await Api.sendApiRequest(body: body, url: Api.setLikeDislikeComApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart b/news-app/lib/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart new file mode 100644 index 00000000..0caea4db --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart @@ -0,0 +1,35 @@ +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class LikeAndDislikeCommRepository { + static final LikeAndDislikeCommRepository _likeAndDislikeCommRepository = LikeAndDislikeCommRepository._internal(); + late LikeAndDislikeCommRemoteDataSource _likeAndDislikeCommRemoteDataSource; + + factory LikeAndDislikeCommRepository() { + _likeAndDislikeCommRepository._likeAndDislikeCommRemoteDataSource = LikeAndDislikeCommRemoteDataSource(); + return _likeAndDislikeCommRepository; + } + + LikeAndDislikeCommRepository._internal(); + + Future> setLikeAndDislikeComm({required String langId, required String commId, required String status}) async { + final result = await _likeAndDislikeCommRemoteDataSource.likeAndDislikeComm(langId: langId, commId: commId, status: status); + + if (result[ERROR]) { + return {ERROR: result[ERROR], MESSAGE: result[MESSAGE]}; + } else { + final List commentsList = (result[DATA] as List).map((e) => CommentModel.fromJson(e)).toList(); + CommentModel? updatedComment; + + commentsList.forEach((element) { + if (element.id! == commId) { + updatedComment = element; + } else if (element.replyComList!.any((sublist) => sublist.id == commId) == true) { + updatedComment = element; + } + }); + return {ERROR: result[ERROR], "total": result[TOTAL], "updatedComment": updatedComment ?? CommentModel.fromJson({})}; + } + } +} diff --git a/news-app/lib/data/repositories/NewsComment/SetComment/setComRemoteDataSource.dart b/news-app/lib/data/repositories/NewsComment/SetComment/setComRemoteDataSource.dart new file mode 100644 index 00000000..70ec853a --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/SetComment/setComRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SetCommentRemoteDataSource { + Future setComment({required String parentId, required String newsId, required String message}) async { + try { + final body = {PARENT_ID: parentId, NEWS_ID: newsId, MESSAGE: message}; + final result = await Api.sendApiRequest(body: body, url: Api.setCommentApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/NewsComment/SetComment/setComRepository.dart b/news-app/lib/data/repositories/NewsComment/SetComment/setComRepository.dart new file mode 100644 index 00000000..e6260da2 --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/SetComment/setComRepository.dart @@ -0,0 +1,19 @@ +import 'package:news/data/repositories/NewsComment/SetComment/setComRemoteDataSource.dart'; +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/utils/strings.dart'; + +class SetCommentRepository { + static final SetCommentRepository _setCommentRepository = SetCommentRepository._internal(); + + late SetCommentRemoteDataSource _setCommentRemoteDataSource; + + factory SetCommentRepository() { + _setCommentRepository._setCommentRemoteDataSource = SetCommentRemoteDataSource(); + return _setCommentRepository; + } + SetCommentRepository._internal(); + Future> setComment({required String parentId, required String newsId, required String message}) async { + final result = await _setCommentRemoteDataSource.setComment(parentId: parentId, newsId: newsId, message: message); + return {"SetComment": (result[DATA] as List).map((e) => CommentModel.fromJson(e)).toList(), "total": result[TOTAL]}; + } +} diff --git a/news-app/lib/data/repositories/OtherPages/otherPageRemoteDataSorce.dart b/news-app/lib/data/repositories/OtherPages/otherPageRemoteDataSorce.dart new file mode 100644 index 00000000..3a173d4d --- /dev/null +++ b/news-app/lib/data/repositories/OtherPages/otherPageRemoteDataSorce.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class OtherPageRemoteDataSource { + Future getOtherPages({required String langId}) async { + try { + final body = {LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getPagesApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/OtherPages/otherPagesRepository.dart b/news-app/lib/data/repositories/OtherPages/otherPagesRepository.dart new file mode 100644 index 00000000..4fa1a8df --- /dev/null +++ b/news-app/lib/data/repositories/OtherPages/otherPagesRepository.dart @@ -0,0 +1,34 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/data/models/OtherPageModel.dart'; +import 'package:news/data/repositories/OtherPages/otherPageRemoteDataSorce.dart'; + +class OtherPageRepository { + static final OtherPageRepository _otherPageRepository = OtherPageRepository._internal(); + + late OtherPageRemoteDataSource _otherPageRemoteDataSource; + + factory OtherPageRepository() { + _otherPageRepository._otherPageRemoteDataSource = OtherPageRemoteDataSource(); + return _otherPageRepository; + } + + OtherPageRepository._internal(); + + Future> getOtherPage({required String langId}) async { + final result = await _otherPageRemoteDataSource.getOtherPages(langId: langId); + + return {"OtherPage": (result[DATA] as List).map((e) => OtherPageModel.fromJson(e)).toList()}; + } + + //get only privacy policy & Terms Conditions + Future> getPrivacyTermsPage({required String langId}) async { + try { + final body = {LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getPolicyPagesApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/RelatedNews/relatedNewsDataSource.dart b/news-app/lib/data/repositories/RelatedNews/relatedNewsDataSource.dart new file mode 100644 index 00000000..f68c2bc4 --- /dev/null +++ b/news-app/lib/data/repositories/RelatedNews/relatedNewsDataSource.dart @@ -0,0 +1,18 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class RelatedNewsRemoteDataSource { + Future getRelatedNews({required String langId, String? catId, String? subCatId, String? latitude, String? longitude, required String offset, required String perPage}) async { + try { + final body = {LANGUAGE_ID: langId, OFFSET: offset, LIMIT: perPage, MERGE_TAG: 1}; //merge_tag used to get related news according to tagId along with cat or Subcat Id + (subCatId != "0" && subCatId != '') ? body[SUBCAT_ID] = subCatId! : body[CATEGORY_ID] = catId!; + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + + final result = await Api.sendApiRequest(body: body, url: Api.getNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/RelatedNews/relatedNewsRepository.dart b/news-app/lib/data/repositories/RelatedNews/relatedNewsRepository.dart new file mode 100644 index 00000000..035c42f9 --- /dev/null +++ b/news-app/lib/data/repositories/RelatedNews/relatedNewsRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/repositories/RelatedNews/relatedNewsDataSource.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/strings.dart'; + +class RelatedNewsRepository { + static final RelatedNewsRepository _relatedNewsRepository = RelatedNewsRepository._internal(); + + late RelatedNewsRemoteDataSource _relatedNewsRemoteDataSource; + + factory RelatedNewsRepository() { + _relatedNewsRepository._relatedNewsRemoteDataSource = RelatedNewsRemoteDataSource(); + return _relatedNewsRepository; + } + + RelatedNewsRepository._internal(); + + Future> getRelatedNews({required String langId, required String offset, required String perPage, String? catId, String? subCatId, String? latitude, String? longitude}) async { + final result = await _relatedNewsRemoteDataSource.getRelatedNews(langId: langId, catId: catId, subCatId: subCatId, latitude: latitude, longitude: longitude, offset: offset, perPage: perPage); + + return {"RelatedNews": (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList(), "total": result[TOTAL]}; + } +} diff --git a/news-app/lib/data/repositories/SectionById/sectionByIdRemoteDataSource.dart b/news-app/lib/data/repositories/SectionById/sectionByIdRemoteDataSource.dart new file mode 100644 index 00000000..8586bb1a --- /dev/null +++ b/news-app/lib/data/repositories/SectionById/sectionByIdRemoteDataSource.dart @@ -0,0 +1,22 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SectionByIdRemoteDataSource { + Future getSectionById({required String langId, required String limit, required String offset, required String sectionId, String? latitude, String? longitude}) async { + try { + final body = {LANGUAGE_ID: langId, SECTION_ID: sectionId}; + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + + if (sectionId.isNotEmpty) { + body[SECTION_ID] = sectionId; + } + body[LIMIT] = limit; + body[OFFSET] = offset; + final result = await Api.sendApiRequest(body: body, url: Api.getFeatureSectionApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/SectionById/sectionByIdRepository.dart b/news-app/lib/data/repositories/SectionById/sectionByIdRepository.dart new file mode 100644 index 00000000..6fe7222d --- /dev/null +++ b/news-app/lib/data/repositories/SectionById/sectionByIdRepository.dart @@ -0,0 +1,25 @@ +import 'package:news/data/models/FeatureSectionModel.dart'; +import 'package:news/data/repositories/SectionById/sectionByIdRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class SectionByIdRepository { + static final SectionByIdRepository _sectionByIdRepository = SectionByIdRepository._internal(); + + late SectionByIdRemoteDataSource _sectionByIdRemoteDataSource; + + factory SectionByIdRepository() { + _sectionByIdRepository._sectionByIdRemoteDataSource = SectionByIdRemoteDataSource(); + return _sectionByIdRepository; + } + + SectionByIdRepository._internal(); + Future> getSectionById({required String langId, required String sectionId, required String limit, required String offset, String? latitude, String? longitude}) async { + final result = await _sectionByIdRemoteDataSource.getSectionById(langId: langId, sectionId: sectionId, latitude: latitude, longitude: longitude, limit: limit, offset: offset); + + if ((result[ERROR])) { + return {ERROR: result[ERROR], MESSAGE: result[MESSAGE]}; + } else { + return {ERROR: result[ERROR], DATA: (result[DATA] as List).map((e) => FeatureSectionModel.fromJson(e)).toList()}; + } + } +} diff --git a/news-app/lib/data/repositories/SetNewsViews/setNewsViewsDataRemoteSource.dart b/news-app/lib/data/repositories/SetNewsViews/setNewsViewsDataRemoteSource.dart new file mode 100644 index 00000000..aaf73efe --- /dev/null +++ b/news-app/lib/data/repositories/SetNewsViews/setNewsViewsDataRemoteSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SetNewsViewsDataRemoteDataSource { + Future setNewsViews({required String newsId, required bool isBreakingNews}) async { + try { + final body = {if (isBreakingNews) BR_NEWS_ID: newsId, if (!isBreakingNews) NEWS_ID: newsId}; + final result = await Api.sendApiRequest(body: body, url: (isBreakingNews) ? Api.setBreakingNewsViewApi : Api.setNewsViewApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/SetNewsViews/setNewsViewsRepository.dart b/news-app/lib/data/repositories/SetNewsViews/setNewsViewsRepository.dart new file mode 100644 index 00000000..4d24abeb --- /dev/null +++ b/news-app/lib/data/repositories/SetNewsViews/setNewsViewsRepository.dart @@ -0,0 +1,19 @@ +import 'package:news/data/repositories/SetNewsViews/setNewsViewsDataRemoteSource.dart'; + +class SetNewsViewsRepository { + static final SetNewsViewsRepository setNewsViewsRepository = SetNewsViewsRepository._internal(); + + late SetNewsViewsDataRemoteDataSource setNewsViewsDataRemoteDataSource; + + factory SetNewsViewsRepository() { + setNewsViewsRepository.setNewsViewsDataRemoteDataSource = SetNewsViewsDataRemoteDataSource(); + return setNewsViewsRepository; + } + + SetNewsViewsRepository._internal(); + + Future> setNewsViews({required String newsId, required bool isBreakingNews}) async { + final result = await setNewsViewsDataRemoteDataSource.setNewsViews(newsId: newsId, isBreakingNews: isBreakingNews); + return result; + } +} diff --git a/news-app/lib/data/repositories/SetSurveyAnswer/setSurveyAnsDataRemoteSource.dart b/news-app/lib/data/repositories/SetSurveyAnswer/setSurveyAnsDataRemoteSource.dart new file mode 100644 index 00000000..31cdc070 --- /dev/null +++ b/news-app/lib/data/repositories/SetSurveyAnswer/setSurveyAnsDataRemoteSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SetSurveyAnsRemoteDataSource { + Future setSurveyAns({required String queId, required String optId}) async { + try { + final body = {QUESTION_ID: queId, OPTION_ID: optId}; + final result = await Api.sendApiRequest(body: body, url: Api.setQueResultApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/SetSurveyAnswer/setSurveyAnsRepository.dart b/news-app/lib/data/repositories/SetSurveyAnswer/setSurveyAnsRepository.dart new file mode 100644 index 00000000..6c75b4eb --- /dev/null +++ b/news-app/lib/data/repositories/SetSurveyAnswer/setSurveyAnsRepository.dart @@ -0,0 +1,21 @@ +import 'package:news/data/repositories/SetSurveyAnswer/setSurveyAnsDataRemoteSource.dart'; +import 'package:news/utils/strings.dart'; + +class SetSurveyAnsRepository { + static final SetSurveyAnsRepository _setSurveyAnsRepository = SetSurveyAnsRepository._internal(); + + late SetSurveyAnsRemoteDataSource _setSurveyAnsRemoteDataSource; + + factory SetSurveyAnsRepository() { + _setSurveyAnsRepository._setSurveyAnsRemoteDataSource = SetSurveyAnsRemoteDataSource(); + return _setSurveyAnsRepository; + } + + SetSurveyAnsRepository._internal(); + + Future> setSurveyAns({required String queId, required String optId}) async { + final result = await _setSurveyAnsRemoteDataSource.setSurveyAns(optId: optId, queId: queId); + + return {"SetSurveyAns": result[DATA]}; + } +} diff --git a/news-app/lib/data/repositories/SetUserPreferenceCat/setUserPrefCatRemoteDataSource.dart b/news-app/lib/data/repositories/SetUserPreferenceCat/setUserPrefCatRemoteDataSource.dart new file mode 100644 index 00000000..98d9db16 --- /dev/null +++ b/news-app/lib/data/repositories/SetUserPreferenceCat/setUserPrefCatRemoteDataSource.dart @@ -0,0 +1,15 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SetUserPrefCatRemoteDataSource { + Future setUserPrefCat({required String catId}) async { + try { + final body = {CATEGORY_ID: catId}; + final result = await Api.sendApiRequest(body: body, url: Api.setUserCatApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/SetUserPreferenceCat/setUserPrefCatRepository.dart b/news-app/lib/data/repositories/SetUserPreferenceCat/setUserPrefCatRepository.dart new file mode 100644 index 00000000..57bf348c --- /dev/null +++ b/news-app/lib/data/repositories/SetUserPreferenceCat/setUserPrefCatRepository.dart @@ -0,0 +1,21 @@ +import 'package:news/data/repositories/SetUserPreferenceCat/setUserPrefCatRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class SetUserPrefCatRepository { + static final SetUserPrefCatRepository _setUserPrefCatRepository = SetUserPrefCatRepository._internal(); + + late SetUserPrefCatRemoteDataSource _setUserPrefCatRemoteDataSource; + + factory SetUserPrefCatRepository() { + _setUserPrefCatRepository._setUserPrefCatRemoteDataSource = SetUserPrefCatRemoteDataSource(); + return _setUserPrefCatRepository; + } + + SetUserPrefCatRepository._internal(); + + Future> setUserPrefCat({required String catId}) async { + final result = await _setUserPrefCatRemoteDataSource.setUserPrefCat(catId: catId); + + return {"SetUserPrefCat": result[DATA]}; + } +} diff --git a/news-app/lib/data/repositories/Settings/settingRepository.dart b/news-app/lib/data/repositories/Settings/settingRepository.dart new file mode 100644 index 00000000..fc84378f --- /dev/null +++ b/news-app/lib/data/repositories/Settings/settingRepository.dart @@ -0,0 +1,29 @@ +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; + +class SettingsRepository { + static final SettingsRepository _settingsRepository = SettingsRepository._internal(); + late SettingsLocalDataRepository _settingsLocalDataSource; + + factory SettingsRepository() { + _settingsRepository._settingsLocalDataSource = SettingsLocalDataRepository(); + return _settingsRepository; + } + + SettingsRepository._internal(); + + Map getCurrentSettings() { + return { + "showIntroSlider": _settingsLocalDataSource.getIntroSlider(), + "languageCode": _settingsLocalDataSource.getCurrentLanguageCode(), + "theme": _settingsLocalDataSource.getCurrentTheme(), + "notification": _settingsLocalDataSource.getNotification(), + "token": _settingsLocalDataSource.getFcmToken() + }; + } + + void changeIntroSlider(bool value) => _settingsLocalDataSource.setIntroSlider(value); + + void changeFcmToken(String value) => _settingsLocalDataSource.setFcmToken(value); + + void changeNotification(bool value) => _settingsLocalDataSource.setNotification(value); +} diff --git a/news-app/lib/data/repositories/Settings/settingsLocalDataRepository.dart b/news-app/lib/data/repositories/Settings/settingsLocalDataRepository.dart new file mode 100644 index 00000000..c2c317c5 --- /dev/null +++ b/news-app/lib/data/repositories/Settings/settingsLocalDataRepository.dart @@ -0,0 +1,75 @@ +import 'package:flutter/src/foundation/change_notifier.dart'; +import 'package:hive_flutter/adapters.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; +import 'package:news/utils/uiUtils.dart'; + +class SettingsLocalDataRepository { + Future setLanguagePreferences({required String code, required String id, required int rtl}) async { + final box = Hive.box(settingsBoxKey); + await Future.wait([ + box.put(currentLanguageCodeKey, code), + box.put(currentLanguageIDKey, id), + box.put(currentLanguageRTLKey, rtl), + ]); + + UiUtils.checkIfValidLocale(langCode: code); // only if you want to validate locale after setting + } + + String getCurrentLanguageCode() { + return Hive.box(settingsBoxKey).get(currentLanguageCodeKey) ?? ""; + } + + String getCurrentLanguageId() { + return Hive.box(settingsBoxKey).get(currentLanguageIDKey) ?? ''; + } + + int getCurrentLanguageRTL() { + return Hive.box(settingsBoxKey).get(currentLanguageRTLKey) ?? 0; + } + + Future setIntroSlider(bool value) async { + Hive.box(settingsBoxKey).put(introSliderKey, value); + } + + bool getIntroSlider() { + return Hive.box(settingsBoxKey).get(introSliderKey) ?? true; + } + + Future setFcmToken(String value) async { + Hive.box(settingsBoxKey).put(tokenKey, value); + } + + String getFcmToken() { + return Hive.box(settingsBoxKey).get(tokenKey) ?? ""; + } + + Future setCurrentTheme(String value) async { + Hive.box(settingsBoxKey).put(currentThemeKey, value); + } + + String getCurrentTheme() { + return Hive.box(settingsBoxKey).get(currentThemeKey) ?? ""; + } + + Future setNotification(bool value) async { + Hive.box(settingsBoxKey).put(notificationKey, value); + } + + bool getNotification() { + return Hive.box(settingsBoxKey).get(notificationKey) ?? true; + } + + Future setLocationCityKeys(double? latitude, double? longitude) async { + Hive.box(locationCityBoxKey).put(latitudeKey, latitude); + Hive.box(locationCityBoxKey).put(longitudeKey, longitude); + } + + Set getLocationCityValues() { + Set locationValues = {Hive.box(locationCityBoxKey).get(latitudeKey).toString(), Hive.box(locationCityBoxKey).get(longitudeKey).toString()}; + return locationValues; + } + + ValueListenable getVideoScreenStyle() { + return Hive.box(videoPreferenceKey).listenable(); + } +} diff --git a/news-app/lib/data/repositories/SubCatNews/subCatNewsRemoteDataSource.dart b/news-app/lib/data/repositories/SubCatNews/subCatNewsRemoteDataSource.dart new file mode 100644 index 00000000..7365236c --- /dev/null +++ b/news-app/lib/data/repositories/SubCatNews/subCatNewsRemoteDataSource.dart @@ -0,0 +1,19 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SubCatNewsRemoteDataSource { + Future getSubCatNews({required String limit, required String offset, String? catId, String? subCatId, String? latitude, String? longitude, required String langId}) async { + try { + final body = {LIMIT: limit, OFFSET: offset, LANGUAGE_ID: langId}; + if (catId != null) body[CATEGORY_ID] = catId; + if (subCatId != null) body[SUBCAT_ID] = subCatId; + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + + final result = await Api.sendApiRequest(body: body, url: Api.getNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/SubCatNews/subCatRepository.dart b/news-app/lib/data/repositories/SubCatNews/subCatRepository.dart new file mode 100644 index 00000000..15eaf2bd --- /dev/null +++ b/news-app/lib/data/repositories/SubCatNews/subCatRepository.dart @@ -0,0 +1,26 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/SubCatNews/subCatNewsRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class SubCatNewsRepository { + static final SubCatNewsRepository _subCatNewsRepository = SubCatNewsRepository._internal(); + + late SubCatNewsRemoteDataSource _subCatNewsRemoteDataSource; + + factory SubCatNewsRepository() { + _subCatNewsRepository._subCatNewsRemoteDataSource = SubCatNewsRemoteDataSource(); + return _subCatNewsRepository; + } + + SubCatNewsRepository._internal(); + + Future> getSubCatNews({required String offset, required String limit, String? catId, String? subCatId, String? latitude, String? longitude, required String langId}) async { + final result = await _subCatNewsRemoteDataSource.getSubCatNews(limit: limit, offset: offset, langId: langId, subCatId: subCatId, catId: catId, latitude: latitude, longitude: longitude); + + if (result[ERROR]) { + return {ERROR: result[ERROR]}; + } else { + return {ERROR: result[ERROR], "total": result[TOTAL], "SubCatNews": (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList()}; + } + } +} diff --git a/news-app/lib/data/repositories/SubCategory/subCatRemoteDataSource.dart b/news-app/lib/data/repositories/SubCategory/subCatRemoteDataSource.dart new file mode 100644 index 00000000..3312237f --- /dev/null +++ b/news-app/lib/data/repositories/SubCategory/subCatRemoteDataSource.dart @@ -0,0 +1,15 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SubCategoryRemoteDataSource { + Future getSubCategory({required String catId, required String langId}) async { + try { + final body = {CATEGORY_ID: catId, LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getSubCategoryApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/SubCategory/subCatRepository.dart b/news-app/lib/data/repositories/SubCategory/subCatRepository.dart new file mode 100644 index 00000000..c1f02817 --- /dev/null +++ b/news-app/lib/data/repositories/SubCategory/subCatRepository.dart @@ -0,0 +1,29 @@ +import 'package:flutter/cupertino.dart'; +import 'package:news/data/repositories/SubCategory/subCatRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/data/models/CategoryModel.dart'; + +class SubCategoryRepository { + static final SubCategoryRepository _subCategoryRepository = SubCategoryRepository._internal(); + + late SubCategoryRemoteDataSource _subCategoryRemoteDataSource; + + factory SubCategoryRepository() { + _subCategoryRepository._subCategoryRemoteDataSource = SubCategoryRemoteDataSource(); + return _subCategoryRepository; + } + + SubCategoryRepository._internal(); + + Future> getSubCategory({required BuildContext context, required String catId, required String langId}) async { + final result = await _subCategoryRemoteDataSource.getSubCategory(langId: langId, catId: catId); + + List subCatList = []; + + subCatList.insert(0, SubCategoryModel(id: "0", subCatName: UiUtils.getTranslatedLabel(context, 'allLbl'))); + + subCatList.addAll((result[DATA] as List).map((e) => SubCategoryModel.fromJson(e)).toList()); + return {"SubCategory": subCatList}; + } +} diff --git a/news-app/lib/data/repositories/SurveyQuestion/surveyQueRemoteDataSource.dart b/news-app/lib/data/repositories/SurveyQuestion/surveyQueRemoteDataSource.dart new file mode 100644 index 00000000..b25bf609 --- /dev/null +++ b/news-app/lib/data/repositories/SurveyQuestion/surveyQueRemoteDataSource.dart @@ -0,0 +1,15 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SurveyQuestionRemoteDataSource { + Future getSurveyQuestions({required String langId}) async { + try { + final body = {LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getQueApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/SurveyQuestion/surveyQueRepository.dart b/news-app/lib/data/repositories/SurveyQuestion/surveyQueRepository.dart new file mode 100644 index 00000000..7fbe3d30 --- /dev/null +++ b/news-app/lib/data/repositories/SurveyQuestion/surveyQueRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/SurveyQuestion/surveyQueRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class SurveyQuestionRepository { + static final SurveyQuestionRepository _surveyQuestionRepository = SurveyQuestionRepository._internal(); + + late SurveyQuestionRemoteDataSource _surveyQuestionRemoteDataSource; + + factory SurveyQuestionRepository() { + _surveyQuestionRepository._surveyQuestionRemoteDataSource = SurveyQuestionRemoteDataSource(); + return _surveyQuestionRepository; + } + + SurveyQuestionRepository._internal(); + + Future> getSurveyQuestion({required String langId}) async { + final result = await _surveyQuestionRemoteDataSource.getSurveyQuestions(langId: langId); + + return {"SurveyQuestion": (result[DATA] as List).map((e) => NewsModel.fromSurvey(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/Tag/tagRemoteDataSource.dart b/news-app/lib/data/repositories/Tag/tagRemoteDataSource.dart new file mode 100644 index 00000000..a4e56e5a --- /dev/null +++ b/news-app/lib/data/repositories/Tag/tagRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class TagRemoteDataSource { + Future getTag({required String langId, required String offset, required String limit}) async { + try { + final body = {LANGUAGE_ID: langId, LIMIT: limit, OFFSET: offset}; + final result = await Api.sendApiRequest(body: body, url: Api.getTagsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/Tag/tagRepository.dart b/news-app/lib/data/repositories/Tag/tagRepository.dart new file mode 100644 index 00000000..73f66da9 --- /dev/null +++ b/news-app/lib/data/repositories/Tag/tagRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/models/TagModel.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/data/repositories/Tag/tagRemoteDataSource.dart'; + +class TagRepository { + static final TagRepository _tagRepository = TagRepository._internal(); + + late TagRemoteDataSource _tagRemoteDataSource; + + factory TagRepository() { + _tagRepository._tagRemoteDataSource = TagRemoteDataSource(); + return _tagRepository; + } + + TagRepository._internal(); + + Future> getTag({required String langId, required String offset, required String limit}) async { + final result = await _tagRemoteDataSource.getTag(langId: langId, limit: limit, offset: offset); + + return {"Tag": (result[DATA] as List).map((e) => TagModel.fromJson(e)).toList(), "total": result[TOTAL]}; + } +} diff --git a/news-app/lib/data/repositories/TagNews/tagNewsDataSource.dart b/news-app/lib/data/repositories/TagNews/tagNewsDataSource.dart new file mode 100644 index 00000000..3fff694e --- /dev/null +++ b/news-app/lib/data/repositories/TagNews/tagNewsDataSource.dart @@ -0,0 +1,17 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class TagNewsRemoteDataSource { + Future getTagNews({required String tagId, required String langId, String? latitude, String? longitude}) async { + try { + final body = {TAG_ID: tagId, LANGUAGE_ID: langId}; + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + + final result = await Api.sendApiRequest(body: body, url: Api.getNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/TagNews/tagNewsRepository.dart b/news-app/lib/data/repositories/TagNews/tagNewsRepository.dart new file mode 100644 index 00000000..448a70e0 --- /dev/null +++ b/news-app/lib/data/repositories/TagNews/tagNewsRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/TagNews/tagNewsDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class TagNewsRepository { + static final TagNewsRepository _tagNewsRepository = TagNewsRepository._internal(); + + late TagNewsRemoteDataSource _tagNewsRemoteDataSource; + + factory TagNewsRepository() { + _tagNewsRepository._tagNewsRemoteDataSource = TagNewsRemoteDataSource(); + return _tagNewsRepository; + } + + TagNewsRepository._internal(); + + Future> getTagNews({required String tagId, required String langId, String? latitude, String? longitude}) async { + final result = await _tagNewsRemoteDataSource.getTagNews(tagId: tagId, langId: langId, latitude: latitude, longitude: longitude); + + return {"TagNews": (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/UserByCategory/userByCatRemoteDataSource.dart b/news-app/lib/data/repositories/UserByCategory/userByCatRemoteDataSource.dart new file mode 100644 index 00000000..8eb6eec1 --- /dev/null +++ b/news-app/lib/data/repositories/UserByCategory/userByCatRemoteDataSource.dart @@ -0,0 +1,12 @@ +import 'package:news/utils/api.dart'; + +class UserByCatRemoteDataSource { + Future getUserById() async { + try { + final result = await Api.sendApiRequest(body: {}, url: Api.getUserByIdApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/UserByCategory/userByCatRepository.dart b/news-app/lib/data/repositories/UserByCategory/userByCatRepository.dart new file mode 100644 index 00000000..f4d6b26e --- /dev/null +++ b/news-app/lib/data/repositories/UserByCategory/userByCatRepository.dart @@ -0,0 +1,21 @@ +import 'package:news/data/repositories/UserByCategory/userByCatRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class UserByCatRepository { + static final UserByCatRepository _userByCatRepository = UserByCatRepository._internal(); + + late UserByCatRemoteDataSource _userByCatRemoteDataSource; + + factory UserByCatRepository() { + _userByCatRepository._userByCatRemoteDataSource = UserByCatRemoteDataSource(); + return _userByCatRepository; + } + + UserByCatRepository._internal(); + + Future> getUserById() async { + final result = await _userByCatRemoteDataSource.getUserById(); + + return {"UserByCat": result[DATA]}; + } +} diff --git a/news-app/lib/data/repositories/Videos/videoRemoteDataSource.dart b/news-app/lib/data/repositories/Videos/videoRemoteDataSource.dart new file mode 100644 index 00000000..d00dceab --- /dev/null +++ b/news-app/lib/data/repositories/Videos/videoRemoteDataSource.dart @@ -0,0 +1,18 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class VideoRemoteDataSource { + Future getVideos({required String limit, required String offset, required String langId, String? latitude, String? longitude}) async { + try { + final body = {LIMIT: limit, OFFSET: offset, LANGUAGE_ID: langId}; + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + + final result = await Api.sendApiRequest(body: body, url: Api.getVideosApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/Videos/videosRepository.dart b/news-app/lib/data/repositories/Videos/videosRepository.dart new file mode 100644 index 00000000..cde40824 --- /dev/null +++ b/news-app/lib/data/repositories/Videos/videosRepository.dart @@ -0,0 +1,23 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/data/repositories/Videos/videoRemoteDataSource.dart'; + +class VideoRepository { + static final VideoRepository _videoRepository = VideoRepository._internal(); + + late VideoRemoteDataSource _videoRemoteDataSource; + + factory VideoRepository() { + _videoRepository._videoRemoteDataSource = VideoRemoteDataSource(); + return _videoRepository; + } + + VideoRepository._internal(); + + Future> getVideo({required String offset, required String limit, required String langId, String? latitude, String? longitude}) async { + final result = await _videoRemoteDataSource.getVideos(limit: limit, offset: offset, langId: langId, latitude: latitude, longitude: longitude); + return (result[ERROR]) + ? {ERROR: result[ERROR], MESSAGE: result[MESSAGE]} + : {ERROR: result[ERROR], "total": result[TOTAL], "Video": (result[DATA] as List).map((e) => NewsModel.fromVideos(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/language/languageRemoteDataSource.dart b/news-app/lib/data/repositories/language/languageRemoteDataSource.dart new file mode 100644 index 00000000..4db0ebbb --- /dev/null +++ b/news-app/lib/data/repositories/language/languageRemoteDataSource.dart @@ -0,0 +1,12 @@ +import 'package:news/utils/api.dart'; + +class LanguageRemoteDataSource { + Future getLanguages() async { + try { + final result = await Api.sendApiRequest(url: Api.getLanguagesApi, body: {}); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/language/languageRepository.dart b/news-app/lib/data/repositories/language/languageRepository.dart new file mode 100644 index 00000000..9c2311a6 --- /dev/null +++ b/news-app/lib/data/repositories/language/languageRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/models/appLanguageModel.dart'; +import 'package:news/data/repositories/language/languageRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class LanguageRepository { + static final LanguageRepository _languageRepository = LanguageRepository._internal(); + + late LanguageRemoteDataSource _languageRemoteDataSource; + + factory LanguageRepository() { + _languageRepository._languageRemoteDataSource = LanguageRemoteDataSource(); + return _languageRepository; + } + + LanguageRepository._internal(); + + Future> getLanguage() async { + final result = await _languageRemoteDataSource.getLanguages(); + + return {"Language": (result[DATA] as List).map((e) => LanguageModel.fromJson(e)).toList()}; + } +} diff --git a/news-app/lib/main.dart b/news-app/lib/main.dart new file mode 100644 index 00000000..7c965efd --- /dev/null +++ b/news-app/lib/main.dart @@ -0,0 +1,5 @@ +import 'app/app.dart'; + +Future main() async { + await initializeApp(); +} diff --git a/news-app/lib/ui/screens/AddEditNews/AddNews.dart b/news-app/lib/ui/screens/AddEditNews/AddNews.dart new file mode 100644 index 00000000..33c00392 --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/AddNews.dart @@ -0,0 +1,1428 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:flutter/cupertino.dart'; +import 'package:intl/intl.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:news/cubits/AddNewsCubit.dart'; +import 'package:news/cubits/GetUserDraftedNewsCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/cubits/deleteImageId.dart'; +import 'package:news/cubits/getUserNewsCubit.dart'; +import 'package:news/cubits/languageCubit.dart'; +import 'package:news/cubits/locationCityCubit.dart'; +import 'package:news/cubits/slugCheckCubit.dart'; +import 'package:news/cubits/tagCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/categoryCubit.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/updateBottomsheetContentCubit.dart'; +import 'package:news/data/models/TagModel.dart'; +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/models/appLanguageModel.dart'; +import 'package:news/data/models/locationCityModel.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/ui/screens/AddEditNews/Widgets/customBottomsheet.dart'; +import 'package:news/ui/screens/AddEditNews/Widgets/geminiService.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/AddEditNews/NewsDescription.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/validators.dart'; + +class AddNews extends StatefulWidget { + NewsModel? model; + bool isEdit; + String from; + AddNews({super.key, this.model, required this.isEdit, required this.from}); + + @override + _AddNewsState createState() => _AddNewsState(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => AddNews(model: arguments['model'], isEdit: arguments['isEdit'], from: arguments['from'])); + } +} + +class _AddNewsState extends State { + final GlobalKey _scaffoldKey = GlobalKey(); + final GlobalKey _formkey = GlobalKey(); + String catSel = "", subCatSel = "", conType = "", conTypeId = "standard_post", langId = "", langName = "", locationSel = "", publishDate = ""; + String? title, catSelId, subSelId, showTill, url, desc, summDesc, locationSelId, metaKeyword, metaDescription, metaTitle, slug; + int? catIndex, locationIndex, isDraft; + List tagsName = [], tagsId = []; + Map contentType = {}; + List otherImage = []; + File? image, videoUpload; + bool isNext = false, isDescLoading = true, isMetaKeyword = false, isMetaDescription = false, isMetaTitle = false, isSlug = false; + TextEditingController titleC = TextEditingController(), + urlC = TextEditingController(), + metaTitleC = TextEditingController(), + metaDescriptionC = TextEditingController(), + metaKeywordC = TextEditingController(), + slugC = TextEditingController(); + List categories = []; + List locationCities = []; + + var now = DateTime.now(); + var formatter = DateFormat('yyyy-MM-dd'); + String currentDate = ""; + DateTime? selectedPublishDate; + DateTime? selectedShowTillDate; + + clearText() { + setState(() { + catSel = ""; + subCatSel = ""; + locationSel = ""; + publishDate = ""; + conType = UiUtils.getTranslatedLabel(context, 'stdPostLbl'); + title = catSelId = subSelId = showTill = url = catIndex = locationSelId = locationIndex = image = videoUpload = desc = null; + conTypeId = 'standard_post'; + tagsName = tagsId = []; + otherImage = []; + isNext = false; + titleC.clear(); + urlC.clear(); + metaTitleC.clear(); + metaDescriptionC.clear(); + metaKeywordC.clear(); + slugC.clear(); + }); + } + + setContentType() { + contentType = { + "standard_post": UiUtils.getTranslatedLabel(context, 'stdPostLbl'), + "video_youtube": UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl'), + "video_other": UiUtils.getTranslatedLabel(context, 'videoOtherUrlLbl'), + "video_upload": UiUtils.getTranslatedLabel(context, 'videoUploadLbl'), + }; + } + + addDataFromModel() { + if (widget.model != null) { + Future.delayed(Duration.zero, () { + setState(() { + setContentType(); + title = titleC.text = widget.model!.title!; + catSel = widget.model!.categoryName!; + catSelId = widget.model!.categoryId!; + subCatSel = widget.model!.subCatName!; + subSelId = widget.model!.subCatId!; + langId = widget.model!.langId!; + updateDropdownsForLanguage(); + for (final entry in contentType.entries) { + if (entry.key == widget.model!.contentType!) { + conType = entry.value; + conTypeId = entry.key; + } + } + if (conTypeId == "video_youtube" || conTypeId == "video_other" || conTypeId == "video_upload") urlC.text = widget.model!.contentValue!; + if (widget.model!.tagName! != "") tagsName = widget.model!.tagName!.split(','); + if (widget.model!.tagId! != "") tagsId = (widget.model!.tagId!.contains(",")) ? widget.model!.tagId!.split(",") : [widget.model!.tagId!]; + if (widget.model!.showTill != "0000-00-00" && widget.model!.showTill != null) showTill = widget.model!.showTill!; + if (widget.model!.publishDate != "0000-00-00" || widget.model!.publishDate != "") publishDate = widget.model!.publishDate ?? ""; + desc = widget.model!.desc ?? ""; + summDesc = widget.model!.shortDesc ?? ""; + metaTitleC.text = metaTitle = widget.model!.metaTitle ?? ""; + metaDescriptionC.text = metaDescription = widget.model!.metaDescription ?? ""; + metaKeywordC.text = metaKeyword = widget.model!.metaKeyword ?? ""; + slugC.text = slug = widget.model!.slug!; + locationSelId = widget.model!.locationId; + locationSel = widget.model!.locationName ?? ""; + }); + }); + } + } + + @override + void initState() { + getStandardPostLabel(); + getCategory(); + getTag(); + getLanguageData(); + if (context.read().getLocationWiseNewsMode() == "1") getLocationCities(); + if (widget.isEdit) addDataFromModel(); + currentDate = formatter.format(now); + super.initState(); + } + + @override + void dispose() { + titleC.dispose(); + urlC.dispose(); + metaTitleC.dispose(); + metaDescriptionC.dispose(); + metaKeywordC.dispose(); + slugC.dispose(); + + super.dispose(); + } + + Future getStandardPostLabel() async { + conType = UiUtils.getTranslatedLabel(context, 'stdPostLbl'); + setState(() {}); + } + + Future getLanguageData() async { + Future.delayed(Duration.zero, () { + if (widget.isEdit) { + context.read().getLanguage().then((value) { + for (int i = 0; i < value.length; i++) { + if (widget.model!.langId! == value[i].id) { + setState(() => langName = value[i].language!); + } + } + }); + } else { + context.read().getLanguage(); + } + }); + } + + void getCategory({String? languageId}) { + context.read().getCategory(langId: languageId ?? (context.read().state.id)); + } + + void getTag({String? languageId}) { + Future.delayed(Duration.zero, () { + context.read().getTags(langId: languageId ?? (context.read().state.id)); + }); + } + + void getLocationCities() { + Future.delayed(Duration.zero, () { + context.read().getLocationCity(); + }); + } + + getAppBar() { + if (!isNext) { + 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.isEdit) ? '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: () { + if (!isNext) { + Navigator.of(context).pop(); + } else { + setState(() => isNext = 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: 'step1Of2Lbl', textStyle: Theme.of(context).textTheme.bodySmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)))) + ], + ), + )); + } + } + + showLanguageModalBottomSheet(BuildContext context) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return CustomBottomsheet( + context: context, + titleTxt: 'chooseLanLbl', + language_id: langId, + listLength: context.read().langList().length, + listViewChild: (context, index) { + return langListItem(index, context.read().langList()); + }); + }); + } + + Widget languageSelName() { + return BlocConsumer( + listener: (context, state) { + if (state is LanguageFetchSuccess) { + context.read().updateLanguageContent(state.language); + } + }, + builder: (context, state) { + if (state is LanguageFetchSuccess && state.language.isNotEmpty) { + if (state.language.length == 1) { + langId = state.language.first.id!; + langName = state.language.first.language!; + } + return Padding( + padding: const EdgeInsets.only(top: 18.0), + child: InkWell( + onTap: () => showLanguageModalBottomSheet(context), + child: UiUtils.setRowWithContainer( + context: context, + firstChild: CustomTextLabel( + text: langName == "" ? 'chooseLanLbl' : langName, + textStyle: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(color: langName == "" ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer)), + isContentTypeUpload: false)), + ); + } + return const SizedBox.shrink(); + }, + ); + } + + Widget catSelectionName() { + if (langId.isNotEmpty) { + return BlocConsumer( + listener: (context, state) { + if (catSel != "" || (widget.isEdit && widget.model!.categoryName != null)) catIndex = context.read().getCategoryIndex(categoryName: catSel); + }, + builder: (context, state) { + if (state is CategoryFetchSuccess) { + if (state.category.isNotEmpty && state.category.length == 1) catSelId = state.category.first.id; + if (state.category.isNotEmpty && state.category.length == 1) catIndex = 0; + if (widget.isEdit) catIndex = context.read().getCatList().indexWhere((element) => element.id == widget.model!.categoryId!); + + context.read().updateCategoryContent(state.category); + + return Padding( + padding: const EdgeInsets.only(top: 18.0), + child: InkWell( + onTap: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return CustomBottomsheet( + context: context, + titleTxt: 'selCatLbl', + listLength: state.category.length, + language_id: langId, + listViewChild: (context, index) { + return catListItem(index, state.category); + }); + }), + child: UiUtils.setRowWithContainer( + context: context, + firstChild: CustomTextLabel( + text: (state.category.length == 1) + ? catSel = state.category.first.categoryName! + : (catSel == "") + ? '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 const SizedBox.shrink(); + }, + ); + } else { + return const SizedBox.shrink(); + } + } + + Widget subCatSelectionName() { + if ((subCatSel != "" && widget.isEdit) || + (catIndex != null) && (!catIndex!.isNegative && context.read().getCatList().isNotEmpty) && (context.read().getCatList()[catIndex!].subData!.isNotEmpty)) { + return BlocBuilder( + builder: (context, state) { + return Padding( + padding: const EdgeInsets.only(top: 18.0), + child: InkWell( + onTap: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return CustomBottomsheet( + context: context, + titleTxt: 'selSubCatLbl', + language_id: langId, + listLength: context.read().getCatList()[catIndex!].subData!.length, + listViewChild: (context, index) => subCatListItem(index, context.read().getCatList())); + }), + child: UiUtils.setRowWithContainer( + context: context, + firstChild: CustomTextLabel( + text: (subCatSel == "") ? '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 contentTypeSelName() { + return Padding( + padding: const EdgeInsets.only(top: 18.0), + child: InkWell( + onTap: () => contentTypeBottomSheet(), + child: UiUtils.setRowWithContainer( + context: context, + firstChild: CustomTextLabel( + text: conType == "" ? 'contentTypeLbl' : conType, + textStyle: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(color: conType == "" ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer)), + isContentTypeUpload: false), + ), + ); + } + + Widget contentVideoUpload() { + return conType == UiUtils.getTranslatedLabel(context, 'videoUploadLbl') + ? Padding( + padding: const EdgeInsets.only(top: 18.0), + child: InkWell( + splashColor: Colors.transparent, + onTap: () => _getFromGalleryVideo(), + child: UiUtils.setRowWithContainer( + context: context, + firstChild: Expanded( + child: CustomTextLabel( + text: videoUpload == null ? 'uploadVideoLbl' : videoUpload!.path.split('/').last, + maxLines: 2, + softWrap: true, + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith( + overflow: TextOverflow.ellipsis, + color: videoUpload == null ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer))), + isContentTypeUpload: true)), + ) + : const SizedBox.shrink(); + } + + Widget contentUrlForVideoUpload() { + if (conTypeId == "video_upload" && videoUpload == null && urlC.text.isNotEmpty) { + return Container( + width: double.maxFinite, + margin: const EdgeInsetsDirectional.only(top: 18), + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + child: InkWell( + onTap: () async { + //open url in newsVideo screen + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": widget.model, "otherVideos": []}); + }, + child: Wrap( + alignment: WrapAlignment.center, + spacing: 10, + runSpacing: 25, + children: [ + const Icon(Icons.play_circle_fill), + CustomTextLabel( + text: 'previewLbl', + maxLines: 3, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer)) + ], + ), + )); + } else { + return const SizedBox.shrink(); + } + } + + Widget contentUrlSet() { + if (conType == UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl') || conType == UiUtils.getTranslatedLabel(context, 'videoOtherUrlLbl')) { + return Container( + width: double.maxFinite, + margin: const EdgeInsetsDirectional.only(top: 18), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + child: TextFormField( + textInputAction: TextInputAction.next, + maxLines: 1, + controller: urlC, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer), + validator: (val) => conType == UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl') ? Validators.youtubeUrlValidation(val!, context) : Validators.urlValidation(val!, context), + onChanged: (String value) => setState(() => url = value), + onTapOutside: (val) => FocusScope.of(context).unfocus(), + decoration: InputDecoration( + hintText: conType == UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl') ? UiUtils.getTranslatedLabel(context, 'youtubeUrlLbl') : UiUtils.getTranslatedLabel(context, 'otherUrlLbl'), + hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)), + filled: true, + fillColor: UiUtils.getColorScheme(context).surface, + contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + focusedBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10.0)), + enabledBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10.0))), + )); + } else { + return const SizedBox.shrink(); + } + } + + contentTypeBottomSheet() { + showModalBottomSheet( + context: context, + elevation: 3.0, + isScrollControlled: true, + //it will be closed only when user click On Save button & not by clicking anywhere else in screen + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(30), topRight: Radius.circular(30))), + enableDrag: false, + builder: (BuildContext context) => 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: 'selContentTypeLbl', textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold, color: UiUtils.getColorScheme(context).primaryContainer)), + Padding( + padding: const EdgeInsetsDirectional.only(top: 10.0, bottom: 15.0), + child: Column( + children: contentType.entries.map((entry) { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () { + if (conType != entry.value || conTypeId != entry.key) { + urlC.clear(); + conType = entry.value; + conTypeId = entry.key; + } + if (widget.isEdit && conTypeId == widget.model!.contentType) { + urlC.text = widget.model!.contentValue!; + } + setState(() {}); + Navigator.pop(context); + }, + child: + UiUtils.setBottomsheetContainer(entryId: entry.value, listItem: entry.value, compareTo: (widget.isEdit) ? widget.model!.contentType! : conType, context: context))); + }).toList())) + ], + ))); + } + + Widget newsTitleName() { + return Container( + width: double.maxFinite, + margin: const EdgeInsetsDirectional.only(top: 7), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + child: TextFormField( + textInputAction: TextInputAction.next, + maxLines: 1, + controller: titleC, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer), + validator: (val) => Validators.titleValidation(val!, context), + onChanged: (String value) => setState(() => title = value), + onTapOutside: (val) => FocusScope.of(context).unfocus(), + decoration: InputDecoration( + hintText: UiUtils.getTranslatedLabel(context, 'titleLbl'), + hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)), + filled: true, + fillColor: UiUtils.getColorScheme(context).surface, + contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + focusedBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10.0)), + enabledBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10.0))))); + } + + Widget tagSelectionName() { + return BlocConsumer(listener: (context, state) { + if (state is TagFetchSuccess) { + context.read().updateTagsContent(state.tag); + } + }, builder: (context, state) { + if ((state is TagFetchSuccess && state.total > 0) || (widget.isEdit && tagsName.isNotEmpty)) { + return Padding( + padding: const EdgeInsets.only(top: 18.0), + child: InkWell( + onTap: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return CustomBottomsheet( + context: context, + titleTxt: 'selTagLbl', + language_id: langId, + listLength: (state as TagFetchSuccess).tag.length, + listViewChild: (context, index) => tagListItem(index, state.tag)); + }), + child: Container( + width: double.maxFinite, + constraints: const BoxConstraints(minHeight: 55), + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 7), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + child: tagsId.isEmpty + ? CustomTextLabel(text: 'tagLbl', textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6))) + : SizedBox( + height: MediaQuery.of(context).size.height * 0.06, + child: ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: tagsName.length, + itemBuilder: (context, index) { + return Padding( + padding: EdgeInsetsDirectional.only(start: index != 0 ? 10.0 : 0), + child: Stack( + children: [ + Container( + margin: const EdgeInsetsDirectional.only(end: 7.5, top: 7.5), + padding: const EdgeInsetsDirectional.all(7.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(5.0), color: UiUtils.getColorScheme(context).primaryContainer), + alignment: Alignment.center, + child: CustomTextLabel(text: tagsName[index], textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).surface)), + ), + Positioned.directional( + textDirection: Directionality.of(context), + end: 0, + child: Container( + height: 15, + width: 15, + alignment: Alignment.center, + margin: const EdgeInsets.all(3.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(25.0), color: Theme.of(context).primaryColor), + child: InkWell( + child: Icon(Icons.close, size: 11, color: UiUtils.getColorScheme(context).surface), + onTap: () { + setState(() { + tagsName.remove(tagsName[index]); + tagsId.remove(tagsId[index]); + }); + }, + ))) + ], + ), + ); + }, + ), + ), + ), + ), + ); + } else { + return const SizedBox.shrink(); + } + }); + } + + Widget locationCitySelectionName() { + return BlocConsumer( + listener: (context, state) { + if (locationSel != "" || (widget.isEdit && widget.model!.locationId != null)) locationIndex = context.read().getLocationIndex(locationName: locationSel); + if (state is LocationCityFetchSuccess) { + context.read().updateLocationContent(state.locationCity); + } + }, + builder: (context, state) { + if (state is LocationCityFetchSuccess) { + if (state.locationCity.isNotEmpty && state.locationCity.length == 1) { + locationSelId = state.locationCity.first.id; + locationIndex = 0; + } + return Padding( + padding: const EdgeInsets.only(top: 18.0), + child: InkWell( + onTap: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return CustomBottomsheet( + context: context, + titleTxt: 'selLocationLbl', + language_id: langId, + listLength: state.locationCity.length, + listViewChild: (context, index) { + return locationCityListItem(index, state.locationCity); + }); + }), + child: UiUtils.setRowWithContainer( + context: context, + firstChild: CustomTextLabel( + text: (state.locationCity.length == 1) + ? locationSel = state.locationCity.first.locationName + : (locationSel == "") + ? 'selLocationLbl' + : locationSel, + textStyle: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(color: locationSel == "" ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer)), + isContentTypeUpload: false), + ), + ); + } + return const SizedBox.shrink(); + }, + ); + } + + Widget showTillSelDate() { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () async { + DateTime? showTillDate = await showDatePicker( + context: context, initialDate: DateTime.now().add(const Duration(days: 1)), firstDate: DateTime.now().subtract(const Duration(days: -1)), lastDate: DateTime(DateTime.now().year + 1)); + + if (showTillDate != null) { + //pickedDate output format => 2021-03-10 00:00:00.000 + String formattedDate = DateFormat('yyyy-MM-dd').format(showTillDate); + setState(() => showTill = formattedDate); + + selectedShowTillDate = showTillDate; + if (selectedPublishDate != null && selectedShowTillDate!.isBefore(selectedPublishDate!)) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'dateConfirmation'), context); + return; + } + } + }, + child: Container( + width: double.maxFinite, + margin: const EdgeInsetsDirectional.only(top: 7), + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomTextLabel( + text: showTill == null ? 'showTilledDate' : showTill!, + textStyle: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(color: showTill == null ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer)), + Align(alignment: Alignment.centerRight, child: Icon(Icons.calendar_month_outlined, color: UiUtils.getColorScheme(context).primaryContainer)) + ], + ), + ), + ), + ); + } + + void _showPicker() { + UiUtils().showUploadImageBottomsheet(context: context, onCamera: _getFromCamera, onGallery: _getFromGallery); + } + + _getFromCamera() async { + try { + XFile? pickedFile = await ImagePicker().pickImage(source: ImageSource.camera); + if (pickedFile != null) { + setState(() { + image = File(pickedFile.path); + }); + Navigator.of(context).pop(); //pop dialog + } + } catch (e) {} + } + + _getFromGallery() async { + XFile? pickedFile = await ImagePicker().pickImage( + source: ImageSource.gallery, + maxWidth: 1800, + maxHeight: 1800, + ); + if (pickedFile != null) { + setState(() { + image = File(pickedFile.path); + Navigator.of(context).pop(); + }); + } + } + + _getFromGalleryOther() async { + List? pickedFileList = await ImagePicker().pickMultiImage(maxWidth: 1800, maxHeight: 1800); + for (int i = 0; i < pickedFileList.length; i++) { + otherImage.add(File(pickedFileList[i].path)); + } + setState(() {}); + } + + _getFromGalleryVideo() async { + final XFile? file = await ImagePicker().pickVideo(source: ImageSource.gallery, maxDuration: const Duration(seconds: 10)); + if (file != null) { + setState(() => videoUpload = File(file.path)); + } + } + + Widget uploadMainImage() { + return InkWell( + onTap: () => _showPicker(), + child: (widget.isEdit || image != null) + ? Padding( + padding: const EdgeInsets.only(top: 25), + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: (image == null) + ? CustomNetworkImage(networkImageUrl: widget.model!.image!, width: double.maxFinite, height: 125, fit: BoxFit.cover, isVideo: false) + : Image.file(image!, height: 125, width: double.maxFinite, fit: BoxFit.fill)), + ) + : Container( + height: 125, + width: double.maxFinite, + padding: const EdgeInsets.only(top: 25), + child: UiUtils.dottedRRectBorder( + childWidget: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Icon(Icons.image, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + Padding( + padding: const EdgeInsetsDirectional.only(start: 10), + child: CustomTextLabel( + text: 'uploadMainImageLbl', textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5))), + ) + ])), + ), + ); + } + + Widget uploadOtherImage() { + return otherImage.isEmpty + ? InkWell( + onTap: () => _getFromGalleryOther(), + child: Container( + height: 125, + width: double.maxFinite, + padding: const EdgeInsets.only(top: 25), + child: UiUtils.dottedRRectBorder( + childWidget: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Icon(Icons.image, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + Padding( + padding: const EdgeInsetsDirectional.only(start: 10), + child: CustomTextLabel( + text: 'uploadOtherImageLbl', + textAlign: TextAlign.center, + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5))), + ) + ]))), + ) + : Padding( + padding: const EdgeInsets.only(top: 25), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: () => _getFromGalleryOther(), + child: SizedBox( + height: 125, + width: 95, + child: UiUtils.dottedRRectBorder( + childWidget: Column(crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ + Icon(Icons.image, size: 15, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + CustomTextLabel( + text: 'uploadOtherImageLbl', + textAlign: TextAlign.center, + textStyle: Theme.of(context).textTheme.bodySmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5)), + ) + ])), + ), + ), + Expanded( + child: SizedBox( + height: 125, + child: ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: otherImage.length, + itemBuilder: (context, index) { + return Stack(clipBehavior: Clip.none, children: [ + Padding( + padding: const EdgeInsetsDirectional.only(start: 10), + child: ClipRRect(borderRadius: BorderRadius.circular(10), child: Image.file(otherImage[index], height: 125, width: 95, fit: BoxFit.fill))), + Positioned.directional( + textDirection: Directionality.of(context), + end: 0, + top: 0, + child: Container( + height: 18, + width: 18, + alignment: Alignment.center, + margin: const EdgeInsets.all(3.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(25.0), color: Theme.of(context).primaryColor), + child: InkWell( + child: Icon(Icons.close, size: 13, color: UiUtils.getColorScheme(context).surface), + onTap: () { + otherImage.removeAt(index); //remove currently uploaded image + setState(() {}); + }, + ))) + ]); + }, + )), + ) + ], + ), + ); + } + + Widget modelOtherImage() { + return widget.model!.imageDataList!.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(top: 25), + child: SizedBox( + height: 125, + child: ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: widget.model!.imageDataList!.length, + itemBuilder: (context, index) { + return Container( + margin: EdgeInsets.zero, + child: Stack( + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(top: 10, end: 8), + child: ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: CustomNetworkImage(networkImageUrl: widget.model!.imageDataList![index].otherImage!, isVideo: false, fit: BoxFit.cover, height: 125, width: 95)), + ), + BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is DeleteImageSuccess) { + context.read().deleteImageId(index); + showSnackBar(state.message, context); + setState(() {}); + } + }, + builder: (context, state) { + return Positioned.directional( + textDirection: Directionality.of(context), + end: 0, + child: Container( + height: 18, + width: 18, + alignment: Alignment.center, + margin: const EdgeInsets.all(3.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(25.0), color: Theme.of(context).primaryColor), + child: InkWell( + child: Icon(Icons.close, size: 13, color: UiUtils.getColorScheme(context).surface), + onTap: () { + context.read().setDeleteImage(imageId: widget.model!.imageDataList![index].id!); + setState(() {}); + }, + ))); + }) + ], + ), + ); + }, + ))) + : const SizedBox.shrink(); + } + + Widget publishDateSelection() { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () async { + DateTime? pickedDate = await showDatePicker(context: context, initialDate: DateTime.now(), firstDate: DateTime.now(), lastDate: DateTime(DateTime.now().year + 1)); + if (pickedDate != null) { + setState(() => publishDate = DateFormat('yyyy-MM-dd').format(pickedDate)); + } + selectedPublishDate = pickedDate; + }, + child: Container( + margin: const EdgeInsets.only(top: 7), + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: UiUtils.getColorScheme(context).surface), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomTextLabel( + text: publishDate.isNotEmpty ? publishDate : 'publishDate', + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(publishDate.isEmpty ? 0.6 : 1))), + Icon(Icons.edit_calendar_rounded, color: UiUtils.getColorScheme(context).primaryContainer) + ], + ), + ), + ), + ); + } + + void onSubmitButton() async { + FocusScope.of(context).unfocus(); + final form = _formkey.currentState; + form!.save(); + + //check for the ones, which are not validated in form above + if (langName.isEmpty) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'chooseLanLbl'), context); + return; + } + if (catSelId == null) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'plzSelCatLbl'), context); + return; + } + if (conType == UiUtils.getTranslatedLabel(context, 'videoUploadLbl')) { + if (!widget.isEdit && videoUpload == null) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'plzUploadVideoLbl'), context); + return; + } + } + if ((conType == UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl') || conType == UiUtils.getTranslatedLabel(context, 'videoOtherUrlLbl')) && urlC.text.contains("/shorts")) { + //do not allow to add link of Youtube shorts as of now + showSnackBar(UiUtils.getTranslatedLabel(context, 'plzValidUrlLbl'), context); + urlC.clear(); + return; + } + //validate other or Youtube URL & set type accordingly + if (conType == UiUtils.getTranslatedLabel(context, 'videoOtherUrlLbl') && (urlC.text.contains("youtube") || urlC.text.contains("youtu.be"))) { + conType = UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl'); + conTypeId = "video_youtube"; + } else if (conType == UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl') && (!urlC.text.contains("youtube") && !urlC.text.contains("youtu.be"))) { + conType = UiUtils.getTranslatedLabel(context, 'videoOtherUrlLbl'); + conTypeId = "video_other"; + } + if (selectedPublishDate != null && selectedShowTillDate != null && selectedShowTillDate!.isBefore(selectedPublishDate!)) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'dateConfirmation'), context); + } + + if (form.validate()) { + if (!widget.isEdit && image == null) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'plzAddMainImageLbl'), context); + return; + } + + //validate slug here + if (!widget.isEdit && context.read().state is! SlugCheckFetchSuccess) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'slugUsedAlready'), context); + return; + } + setState(() => isNext = true); + } + } + + Widget nextBtn() { + return Padding( + padding: const EdgeInsetsDirectional.only(top: 10, bottom: 20, start: 20, end: 20), + 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: 'nxt', + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).surface, fontWeight: FontWeight.w600, fontSize: 18, letterSpacing: 0.6))), + onTap: () => onSubmitButton(), + )); + } + + validateFunc(String description, String summDescription) { + desc = description; + summDesc = summDescription; + validateForm(); + } + + isDraftNews(int isDrafted) { + //set isDraft here + isDraft = isDrafted; + } + + Widget tagListItem(int index, List tagList) { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () { + if (!tagsId.contains(tagList[index].id!)) { + setState(() { + tagsName.add(tagList[index].tagName!); + tagsId.add(tagList[index].id!); + }); + } else { + setState(() { + tagsName.remove(tagList[index].tagName!); + tagsId.remove(tagList[index].id!); + }); + } + Navigator.pop(context); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5.0), + color: tagsId.isNotEmpty + ? tagsId.contains(tagList[index].id!) + ? Theme.of(context).primaryColor + : UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.1) + : UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.1)), + padding: const EdgeInsets.all(10.0), + alignment: Alignment.center, + child: CustomTextLabel( + text: tagList[index].tagName!, + textStyle: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(color: (tagsId.contains(tagList[index].id!)) ? UiUtils.getColorScheme(context).secondary : UiUtils.getColorScheme(context).primaryContainer)), + ), + ), + ); + } + + Widget subCatListItem(int index, List catList) { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () { + setState(() { + subCatSel = catList[catIndex!].subData![index].subCatName!; + subSelId = catList[catIndex!].subData![index].id!; + }); + Navigator.pop(context); + }, + child: + UiUtils.setBottomsheetContainer(entryId: subSelId ?? "0", listItem: catList[catIndex!].subData![index].subCatName!, compareTo: catList[catIndex!].subData![index].id!, context: context)), + ); + } + + void updateDropdownsForLanguage() { + getCategory(languageId: langId); + getTag(languageId: langId); + } + + Widget langListItem(int index, List langList) { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () { + langId = langList[index].id!; + langName = langList[index].language!; + catSel = ""; + catSelId = null; + tagsName.clear(); + tagsId.clear(); + //load categories according to language selected + updateDropdownsForLanguage(); + setState(() {}); + Navigator.pop(context); + }, + child: UiUtils.setBottomsheetContainer(entryId: langId, listItem: langList[index].language!, compareTo: langList[index].id!, context: context), + ), + ); + } + + Widget catListItem(int index, List catList) { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () { + setState(() { + subSelId = null; + subCatSel = ""; + catSel = catList[index].categoryName!; + catSelId = catList[index].id!; + catIndex = index; + }); + Navigator.pop(context); + }, + child: UiUtils.setBottomsheetContainer(entryId: catSelId ?? "0", listItem: catList[index].categoryName!, compareTo: catList[index].id!, context: context), + ), + ); + } + + Widget locationCityListItem(int index, List locationCityList) { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () { + setState(() { + locationSel = locationCityList[index].locationName; + locationSelId = locationCityList[index].id; + locationIndex = index; + }); + Navigator.pop(context); + }, + child: UiUtils.setBottomsheetContainer(entryId: locationSelId ?? "0", listItem: locationCityList[index].locationName, compareTo: locationCityList[index].id, context: context), + ), + ); + } + + @override + Widget build(BuildContext context) { + setContentType(); + return Scaffold( + bottomNavigationBar: !isNext ? nextBtn() : null, + key: _scaffoldKey, + appBar: getAppBar(), + body: BlocConsumer( + bloc: context.read(), + listener: (context, state) async { + if (state is AddNewsFetchFailure) showSnackBar(state.errorMessage, context); + if (state is AddNewsFetchSuccess) { + if (!widget.isEdit) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'newsCreatedSuccessfully'), context, durationInMiliSeconds: 3000); + if (widget.from == "myNews") { + FocusScope.of(context).unfocus(); + clearText(); + Navigator.of(context).pop(); + } else { + Navigator.of(context).pushReplacementNamed(Routes.manageUserNews).whenComplete(() { + FocusScope.of(context).unfocus(); + clearText(); + }); + } + } else { + Future.delayed(Duration.zero, () { + context + .read() + .getGetUserNews(latitude: SettingsLocalDataRepository().getLocationCityValues().first, longitude: SettingsLocalDataRepository().getLocationCityValues().last); + }).then((value) { + context.read().getGetUserDraftedNews(); + showSnackBar(state.addNews["message"], context); + Navigator.of(context).pop(); + }); + } + } + }, + builder: (context, state) { + return Form( + key: _formkey, + child: Stack(children: [ + if (state is AddNewsFetchInProgress) Center(child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)), + !isNext + ? PopScope( + canPop: (!isNext) ? true : false, + onPopInvoked: (bool isTrue) { + setState(() => isNext = false); + }, + child: SingleChildScrollView( + padding: const EdgeInsetsDirectional.only(start: 20, end: 20, bottom: 20), + physics: const AlwaysScrollableScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + newsTitleName(), + languageSelName(), + catSelectionName(), + subCatSelectionName(), + contentTypeSelName(), + if (widget.isEdit) contentUrlForVideoUpload(), + contentVideoUpload(), + contentUrlSet(), + tagSelectionName(), + locationCitySelectionName(), + publishDateSelection(), + showTillSelDate(), + uploadMainImage(), + uploadOtherImage(), + if (widget.isEdit) modelOtherImage(), + webNewsDetails() + ], + )), + ) + : NewsDescription(desc ?? "", summDesc ?? "", updateParent, validateFunc, (widget.isEdit) ? 2 : 1, isDraftNews) + ])); + }), + ); + } + + Widget customTextFormFieldWithWarningLabel({required TextEditingController controller, required String hintText, required String warningText, required int maxLines}) { + return Column( + children: [ + Container( + width: double.maxFinite, + margin: const EdgeInsetsDirectional.symmetric(vertical: 7), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + child: TextFormField( + textInputAction: (maxLines > 1) ? TextInputAction.newline : TextInputAction.next, + maxLines: maxLines, + controller: controller, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer), + validator: (val) { + switch (hintText) { + case 'slugLbl': + return Validators.slugValidation(val!, context); + } + return null; + }, + onChanged: (String value) => setState(() { + switch (hintText) { + case 'metaTitleLbl': + metaTitle = value; + break; + case 'metaDescriptionLbl': + metaDescription = value; + break; + case 'metaKeywordLbl': + metaKeyword = value; + break; + case 'slugLbl': + slugC.text = slugC.text.replaceAll(' ', '-'); + slug = slugC.text; + //call APi to check Availability of Entered slug + if (slugC.text.trim().isNotEmpty) { + context.read().checkSlugAvailability(slug: slugC.text, langId: context.read().state.id); + } + break; + } + }), + onTapOutside: (val) => FocusScope.of(context).unfocus(), + decoration: InputDecoration( + hintText: UiUtils.getTranslatedLabel(context, hintText), + hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)), + filled: true, + fillColor: UiUtils.getColorScheme(context).surface, + contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + focusedBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10.0)), + enabledBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10.0))))), + ((!isMetaTitle && hintText == 'metaTitleLbl') || + (!isMetaDescription && hintText == 'metaDescriptionLbl') || + (!isMetaKeyword && hintText == 'metaKeywordLbl') || + (!isSlug && hintText == 'slugLbl')) + ? Container( + padding: const EdgeInsets.symmetric(vertical: 5), + decoration: BoxDecoration(border: Border.all(color: warningColor), borderRadius: BorderRadius.circular(10)), + child: Row( + children: [ + const Padding(padding: EdgeInsets.all(3.0), child: Icon(Icons.info_rounded, color: warningColor)), + Expanded(child: CustomTextLabel(text: warningText, textAlign: TextAlign.left)), + IconButton( + padding: EdgeInsets.zero, + onPressed: (() { + switch (hintText) { + case 'metaTitleLbl': + setState(() => isMetaTitle = true); + break; + case 'metaDescriptionLbl': + setState(() => isMetaDescription = true); + break; + case 'metaKeywordLbl': + setState(() => isMetaKeyword = true); + break; + case 'slugLbl': + setState(() => isSlug = true); + break; + } + }), + icon: const Icon(Icons.close_rounded, color: borderColor, size: 18)) + ], + ), + ) + : const SizedBox.shrink() + ], + ); + } + + Widget webNewsDetails() { + return Container( + margin: const EdgeInsets.symmetric(vertical: 10), + child: Column(children: [ + if (!widget.isEdit) autoGenerateMetaInfoButton(), + customTextFormFieldWithWarningLabel(controller: metaTitleC, hintText: 'metaTitleLbl', warningText: 'metaTitleWarningLbl', maxLines: 1), + customTextFormFieldWithWarningLabel(controller: metaDescriptionC, hintText: 'metaDescriptionLbl', warningText: 'metaDescriptionWarningLbl', maxLines: 2), + customTextFormFieldWithWarningLabel(controller: metaKeywordC, hintText: 'metaKeywordLbl', warningText: 'metaKeywordWarningLbl', maxLines: 1), + customTextFormFieldWithWarningLabel(controller: slugC, hintText: 'slugLbl', warningText: 'slugWarningLbl', maxLines: 1) + ])); + } + + bool isProcessing = false; + Widget autoGenerateMetaInfoButton() { + return Padding( + padding: EdgeInsets.symmetric(vertical: 5), + child: (isProcessing) + ? Container( + height: MediaQuery.of(context).size.height * 0.04, + width: MediaQuery.of(context).size.width * 0.48, + child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor), + ) + : CustomTextButton( + onTap: () async { + if ((title ?? '').isEmpty) return showSnackBar(UiUtils.getTranslatedLabel(context, "newsTitleReqLbl"), context); + + if (langId.isEmpty) return showSnackBar(UiUtils.getTranslatedLabel(context, "chooseLanLbl"), context); + setState(() { + isProcessing = true; + }); + try { + final meta = await GeminiService.generateMetaInfo(title: title ?? "", language: langName, languageCode: langId, apiKey: context.read().getGeminiAPiKey()); + + metaTitle = metaTitleC.text = meta["meta_title"]; + metaDescription = metaDescriptionC.text = meta["meta_description"]; + metaKeyword = metaKeywordC.text = meta["meta_keywords"]; + slug = slugC.text = meta["slug"]; + + setState(() { + isProcessing = false; + }); + showSnackBar(UiUtils.getTranslatedLabel(context, "metaInformationApplied"), context); + } catch (e) { + setState(() { + isProcessing = false; + }); + showSnackBar(UiUtils.getTranslatedLabel(context, "failedToGenerateMetaInfo"), context); + } + }, + textWidget: Container( + height: MediaQuery.of(context).size.height * 0.04, + width: MediaQuery.of(context).size.width * 0.48, + decoration: BoxDecoration(color: UiUtils.getColorScheme(context).primaryContainer, borderRadius: BorderRadius.circular(7.0)), + child: Center( + child: CustomTextLabel(text: UiUtils.getTranslatedLabel(context, 'autoGenerateMetaInfo'), textStyle: TextStyle(color: UiUtils.getColorScheme(context).secondary)), + ), + ), + buttonStyle: ButtonStyle(overlayColor: WidgetStateProperty.all(Colors.transparent), foregroundColor: WidgetStateProperty.all(UiUtils.getColorScheme(context).onPrimary))), + ); + } + + updateParent(String description, String summDescription, bool next) { + setState(() { + desc = description; + summDescription = summDescription; + isNext = next; + }); + } + + validateForm() async { + if (await InternetConnectivity.isNetworkAvailable()) { + context.read().addNews( + context: context, + newsId: (widget.isEdit) ? widget.model!.id! : null, + actionType: (widget.isEdit) ? "2" : "1", + catId: catSelId!, + title: title!, + conTypeId: conTypeId, + conType: conType, + image: image, + langId: langId, + subCatId: subSelId, + showTill: showTill, + desc: desc, + otherImage: otherImage, + tagId: tagsId.isNotEmpty ? tagsId.join(',') : null, + url: urlC.text.isNotEmpty ? urlC.text : null, + videoUpload: videoUpload, + locationId: locationSelId, + metaTitle: metaTitle ?? "", + metaDescription: metaDescription ?? "", + metaKeyword: metaKeyword ?? "", + slug: slug!, + publishDate: publishDate.isEmpty ? null : publishDate, + summDescription: summDesc, + isDraft: isDraft ?? 0); + getCategory(); //get default language categories again for Categories Tab + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + } + } +} diff --git a/news-app/lib/ui/screens/AddEditNews/ManageUserNews.dart b/news-app/lib/ui/screens/AddEditNews/ManageUserNews.dart new file mode 100644 index 00000000..2721926f --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/ManageUserNews.dart @@ -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 with TickerProviderStateMixin { + final bool _isButtonExtended = true; + late final ScrollController controller = ScrollController()..addListener(hasMoreNewsScrollListener); + late final ScrollController draftController = ScrollController()..addListener(hasMoreDraftedNewsScrollListener); + + Set get locationValue => SettingsLocalDataRepository().getLocationCityValues(); + + @override + void initState() { + getNews(); + getUserDraftedNews(); + super.initState(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + void getUserDraftedNews() { + context.read().getGetUserDraftedNews(userId: int.parse(context.read().getUserId())); + } + + void getNews() { + context.read().getGetUserNews(latitude: locationValue.first, longitude: locationValue.last); + } + + void getMoreNews() { + context.read().getMoreGetUserNews(latitude: locationValue.first, longitude: locationValue.last); + } + + void hasMoreNewsScrollListener() { + if (controller.position.maxScrollExtent == controller.offset) { + if (context.read().hasMoreGetUserNews()) { + getMoreNews(); + } else {} + } + } + + void hasMoreDraftedNewsScrollListener() { + if (draftController.position.maxScrollExtent == draftController.offset) { + if (context.read().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), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/AddEditNews/NewsDescription.dart b/news-app/lib/ui/screens/AddEditNews/NewsDescription.dart new file mode 100644 index 00000000..cd198313 --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/NewsDescription.dart @@ -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 { + 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().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), + ), + ), + ) + ], + ); + } +} diff --git a/news-app/lib/ui/screens/AddEditNews/Widgets/UserNewsWidgets.dart b/news-app/lib/ui/screens/AddEditNews/Widgets/UserNewsWidgets.dart new file mode 100644 index 00000000..1d96300f --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/Widgets/UserNewsWidgets.dart @@ -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().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().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().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().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: [ + 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( + bloc: context.read(), + listener: (context, state) { + if (state is DeleteUserNewsSuccess) { + context.read().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().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(); + } +} diff --git a/news-app/lib/ui/screens/AddEditNews/Widgets/customBottomsheet.dart b/news-app/lib/ui/screens/AddEditNews/Widgets/customBottomsheet.dart new file mode 100644 index 00000000..1da60606 --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/Widgets/customBottomsheet.dart @@ -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 { + 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().hasMoreCategory()) { + context.read().getMoreCategory(langId: widget.language_id); + } + break; + case 'selTagLbl': + if (context.read().hasMoreTags()) { + context.read().getMoreTags(langId: widget.language_id); + } + break; + case 'selLocationLbl': + if (context.read().hasMoreLocation()) { + context.read().getMoreLocationCity(); + } + break; + } + } + } + + @override + Widget build(BuildContext context) { + return Builder( + builder: (BuildContext context) => BlocBuilder( + 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)), + ], + )); + }); + }, + )); + } +} diff --git a/news-app/lib/ui/screens/AddEditNews/Widgets/geminiService.dart b/news-app/lib/ui/screens/AddEditNews/Widgets/geminiService.dart new file mode 100644 index 00000000..7d160551 --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/Widgets/geminiService.dart @@ -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> _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 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> 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 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) + "..."; + } + } +} diff --git a/news-app/lib/ui/screens/AddEditNews/Widgets/userAllNews.dart b/news-app/lib/ui/screens/AddEditNews/Widgets/userAllNews.dart new file mode 100644 index 00000000..be9a33d0 --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/Widgets/userAllNews.dart @@ -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(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; + }); + } +} diff --git a/news-app/lib/ui/screens/AddEditNews/Widgets/userDrafterNews.dart b/news-app/lib/ui/screens/AddEditNews/Widgets/userDrafterNews.dart new file mode 100644 index 00000000..c5cfdf34 --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/Widgets/userDrafterNews.dart @@ -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(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; + }); + } +} diff --git a/news-app/lib/ui/screens/BookmarkScreen.dart b/news-app/lib/ui/screens/BookmarkScreen.dart new file mode 100644 index 00000000..12ce4e3b --- /dev/null +++ b/news-app/lib/ui/screens/BookmarkScreen.dart @@ -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 { + late final ScrollController _controller = ScrollController()..addListener(hasMoreBookmarkScrollListener); + + @override + void initState() { + super.initState(); + getBookMark(); + } + + void getBookMark() async { + if (await InternetConnectivity.isNetworkAvailable()) { + context.read().getBookmark(langId: context.read().state.id); + } + } + + void hasMoreBookmarkScrollListener() { + if (_controller.position.maxScrollExtent == _controller.offset) { + if (context.read().hasMoreBookmark()) { + context.read().getMoreBookmark(langId: context.read().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( + 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().getMoreBookmark(langId: context.read().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}); + })); + } +} diff --git a/news-app/lib/ui/screens/CategoryScreen.dart b/news-app/lib/ui/screens/CategoryScreen.dart new file mode 100644 index 00000000..ec826827 --- /dev/null +++ b/news-app/lib/ui/screens/CategoryScreen.dart @@ -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 { + late final ScrollController _categoryScrollController = ScrollController()..addListener(hasMoreCategoryScrollListener); + + void getCategory() { + Future.delayed(Duration.zero, () { + context.read().getCategory(langId: context.read().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().hasMoreCategory()) { + context.read().getMoreCategory(langId: context.read().state.id); + } else {} + } + } + + Widget _buildCategory() { + return BlocBuilder( + 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()); + } +} diff --git a/news-app/lib/ui/screens/HomePage/HomePage.dart b/news-app/lib/ui/screens/HomePage/HomePage.dart new file mode 100644 index 00000000..8b8ef379 --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/HomePage.dart @@ -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 with TickerProviderStateMixin { + final GlobalKey _refreshIndicatorKey = GlobalKey(); + 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 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().getSection(langId: languageId, latitude: locationValue.first, longitude: locationValue.last); + }).whenComplete(() => getGeneralNews()); + } + + void getLiveStreamData() { + Future.delayed(Duration.zero, () { + context.read().getLiveStream(langId: languageId); + }); + } + + void getBookmark() { + Future.delayed(Duration.zero, () { + context.read().getBookmark(langId: languageId); + }); + } + + void getLikeNews() { + Future.delayed(Duration.zero, () { + context.read().getLike(langId: languageId); + }); + } + + void getUserData() { + Future.delayed(Duration.zero, () { + context.read().getUserById(); + }); + } + + checkForAppUpdate() { + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (context.read().state is AppConfigurationFetchSuccess) { + if (context.read().isUpdateRequired()) { + openUpdateDialog(); + } + } + }); + } + + openUpdateDialog() { + bool isForceUpdate = (context.read().getForceUpdateMode() != "" && context.read().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().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().getSettings().token != '') { + context.read().registerToken(fcmId: context.read().getSettings().token, context: context); + context.read().changeFcmToken(context.read().getSettings().token); + } + } else { + SettingsLocalDataRepository().setLocationCityKeys(null, null); + } + + if (appConfig.getWeatherMode() == "1") { + getWeatherData(); + } + } + + Future getWeatherData() async { + if (lat != null && lon != null) { + context.read().getWeatherDetails(langId: (Hive.box(settingsBoxKey).get(currentLanguageCodeKey)), lat: lat.toString(), lon: lon.toString()); + } + } + + void getBreakingNews() { + Future.delayed(Duration.zero, () { + context.read().getBreakingNews(langId: languageId); + }); + } + + void getGeneralNews() { + context.read().getGeneralNews(langId: languageId, latitude: locationValue.first, longitude: locationValue.last); + } + + @override + void initState() { + super.initState(); + appConfig = context.read(); + authConfig = context.read(); + languageId = context.read().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().hasMoreSections()) { + context.read().getMoreSections(langId: languageId, latitude: locationValue.first, longitude: locationValue.last); + } else {} + } + } + + Widget breakingNewsMarquee() { + return BlocBuilder(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(builder: (context, newsState) { + return BlocBuilder(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().state is GeneralNewsFetchSuccess) { + return sectionData(newsModelList: (context.read().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? 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 _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().getOtherPage(langId: languageId); + }); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: RefreshIndicator( + key: _refreshIndicatorKey, + onRefresh: () => _refresh(), + child: BlocListener( + bloc: context.read(), + 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(builder: (context, state) { + if (state is WeatherFetchSuccess) { + return WeatherDataView(weatherData: state.weatherData); + } + return SizedBox.shrink(); + }), + breakingNewsMarquee(), + getSectionList() + ], + ), + ), + ), + ))), + ); + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/CommonSectionTitle.dart b/news-app/lib/ui/screens/HomePage/Widgets/CommonSectionTitle.dart new file mode 100644 index 00000000..69619c6b --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/CommonSectionTitle.dart @@ -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(), + ); +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/GeneralNewsRandomStyle.dart b/news-app/lib/ui/screens/HomePage/Widgets/GeneralNewsRandomStyle.dart new file mode 100644 index 00000000..c4d16b08 --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/GeneralNewsRandomStyle.dart @@ -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 modelList; + + const GeneralNewsRandomStyle({super.key, required this.modelList}); + + @override + GeneralNewsRandomStyleState createState() => GeneralNewsRandomStyleState(); +} + +class GeneralNewsRandomStyleState extends State { + late final ScrollController scrollController = ScrollController()..addListener(hasMoreGeneralNewsListener); + late int counter; //counter will handle unique index in both list & grid + late List 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().hasMoreGeneralNews()) { + context.read().getMoreGeneralNews( + langId: context.read().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 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 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)), + ], + )) + ], + )), + ); + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/LiveWithSearchView.dart b/news-app/lib/ui/screens/HomePage/Widgets/LiveWithSearchView.dart new file mode 100644 index 00000000..f733fb46 --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/LiveWithSearchView.dart @@ -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 { + Widget liveWithSearchView() { + return BlocBuilder( + bloc: context.read(), + 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().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().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(); + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/SectionShimmer.dart b/news-app/lib/ui/screens/HomePage/Widgets/SectionShimmer.dart new file mode 100644 index 00000000..3881a63d --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/SectionShimmer.dart @@ -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)), + ); + }), + ])); +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle1.dart b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle1.dart new file mode 100644 index 00000000..4b5fffdb --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle1.dart @@ -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 { + 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: [ + 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 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 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 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: [ + 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 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( + (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 map(List list, Function handler) { + List 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; + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle2.dart b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle2.dart new file mode 100644 index 00000000..3805586f --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle2.dart @@ -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().getInAppAdsMode() == "1" && + (context.read().getAdsType() != "unity" || context.read().getIOSAdsType() != "unity")) + nativeAdsShow(context: context, index: index), + InkWell( + onTap: () { + //interstitial ads + UiUtils.showInterstitialAds(context: context); + if (model.newsType == 'news' || model.newsType == "user_choice") { + List 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 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 newsList = []; + List 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)), + ], + )) + ], + ), + ), + ], + ), + ); + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle3.dart b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle3.dart new file mode 100644 index 00000000..c4d8700a --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle3.dart @@ -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().getInAppAdsMode() == "1" && + (context.read().getAdsType() != "unity" || context.read().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: [ + 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 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 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().getInAppAdsMode() == "1" && + (context.read().getAdsType() != "unity" || context.read().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: [ + 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 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 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(); + } + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle4.dart b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle4.dart new file mode 100644 index 00000000..c6ef1d38 --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle4.dart @@ -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 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 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 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 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)); + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle5.dart b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle5.dart new file mode 100644 index 00000000..a15f6d79 --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle5.dart @@ -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 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 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 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 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 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 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 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 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(); + } + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle6.dart b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle6.dart new file mode 100644 index 00000000..3c5903ef --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle6.dart @@ -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 { + 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().hasMoreSections() && !(context.read().state is SectionByIdFetchInProgress)) { + context.read().getMoreSectionById(langId: context.read().state.id, sectionId: widget.model.id!); + } + } + } + + @override + Widget build(BuildContext context) { + return style6Data(); + } + + void getSectionDataById() { + Future.delayed(Duration.zero, () { + context.read().getSectionById( + langId: context.read().state.id, + sectionId: widget.model.id!, + latitude: SettingsLocalDataRepository().getLocationCityValues().first, + longitude: SettingsLocalDataRepository().getLocationCityValues().last); + }); + } + + Widget style6Data() { + return BlocBuilder(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? newsData, List? 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? allNewsList, + List? 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 newsList = []; + List 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 newsList = List.from(allNewsList ?? [])..removeAt(index); + // List 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 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 breakList = List.from(breakingNewsList); + breakList.removeAt(index); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": breakingNewsModel, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false}); + } + } + }, + ); + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/WeatherData.dart b/news-app/lib/ui/screens/HomePage/Widgets/WeatherData.dart new file mode 100644 index 00000000..5c0df327 --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/WeatherData.dart @@ -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 { + late Future _future; + + @override + void initState() { + super.initState(); + _future = UiUtils.checkIfValidLocale(langCode: Hive.box(settingsBoxKey).get(currentLanguageCodeKey)); + } + + Widget weatherDataView() { + return FutureBuilder( + 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: [ + Expanded( + flex: 3, + child: Column( + children: [ + 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: [ + 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: [ + 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: [ + 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(); + } +} diff --git a/news-app/lib/ui/screens/ImagePreviewScreen.dart b/news-app/lib/ui/screens/ImagePreviewScreen.dart new file mode 100644 index 00000000..2df9c6e4 --- /dev/null +++ b/news-app/lib/ui/screens/ImagePreviewScreen.dart @@ -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 imgList; + + const ImagePreview({super.key, required this.index, required this.imgList}); + + @override + State createState() => StatePreview(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => ImagePreview(index: arguments['index'], imgList: arguments['imgList'])); + } +} + +class StatePreview extends State { + final GlobalKey _scaffoldKey = GlobalKey(); + 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: [ + 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 _buildDots(BuildContext context) { + List 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)), + ); + } +} diff --git a/news-app/lib/ui/screens/LiveStreaming.dart b/news-app/lib/ui/screens/LiveStreaming.dart new file mode 100644 index 00000000..3bf04694 --- /dev/null +++ b/news-app/lib/ui/screens/LiveStreaming.dart @@ -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 liveNews; + + LiveStreaming({super.key, required this.liveNews}); + + @override + State createState() => StateLive(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => LiveStreaming(liveNews: arguments['liveNews'])); + } +} + +class StateLive extends State { + @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 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), + ); + } +} diff --git a/news-app/lib/ui/screens/ManagePreference.dart b/news-app/lib/ui/screens/ManagePreference.dart new file mode 100644 index 00000000..88e1c3de --- /dev/null +++ b/news-app/lib/ui/screens/ManagePreference.dart @@ -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 createState() { + return StateManagePref(); + } + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => ManagePref(from: arguments['from'])); + } +} + +class StateManagePref extends State with TickerProviderStateMixin { + final GlobalKey _scaffoldKey = GlobalKey(); + String catId = ""; + List selectedChoices = []; + String selCatId = ""; + + static int _count = 0; + List _checks = []; + late final ScrollController _categoryScrollController = ScrollController()..addListener(hasMoreCategoryScrollListener); + + @override + void initState() { + super.initState(); + + getUserData(); + } + + void getUserData() { + Future.delayed(Duration.zero, () { + context.read().getUserById(); + }); + } + + @override + void dispose() { + _categoryScrollController.dispose(); + super.dispose(); + } + + void hasMoreCategoryScrollListener() { + if (_categoryScrollController.offset >= _categoryScrollController.position.maxScrollExtent && !_categoryScrollController.position.outOfRange) { + if (context.read().hasMoreCategory()) { + context.read().getMoreCategory(langId: context.read().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( + bloc: context.read(), + 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().getCategory(langId: context.read().state.id); + } + }, + builder: (context, state) { + return BlocConsumer( + bloc: context.read(), + 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( + bloc: context.read(), + 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().getUserId() != "0") { + if (selectedChoices.isEmpty) { + //no preference selected + Navigator.of(context).pushNamedAndRemoveUntil(Routes.home, (Route route) => false); + return; + } else if (selectedChoices.length == 1) { + setState(() { + selCatId = selectedChoices.join(); + }); + } else { + setState(() { + selCatId = selectedChoices.join(','); + }); + } + context.read().setUserPrefCat(catId: selCatId); + } else { + UiUtils.loginRequired(context); + } + }), + ); + }); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/NewsDetailScreen.dart b/news-app/lib/ui/screens/NewsDetail/NewsDetailScreen.dart new file mode 100644 index 00000000..8b542351 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/NewsDetailScreen.dart @@ -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? newsList; + final BreakingNewsModel? breakModel; + final List? 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; + 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 { + final PageController pageController = PageController(); + + bool isScrollLocked = false; + + void _setScrollLock(bool lock) { + setState(() { + isScrollLocked = lock; + }); + } + + @override + void initState() { + if (context.read().getInAppAdsMode() == "1") { + if (context.read().checkAdsType() == "google") { + createGoogleInterstitialAd(context); + createGoogleRewardedAd(context); + } else { + if (context.read().unityGameId() != null) { + UnityAds.init( + gameId: context.read().unityGameId()!, + testMode: true, //set it to False @Deployement + onComplete: () { + loadUnityInterAd(context.read().interstitialId()!); + loadUnityRewardAd(context.read().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().getInAppAdsMode() == "1") { + if (context.read().checkAdsType() == "google") { + showGoogleRewardedAd(context); + } else { + showUnityRewardAds(context.read().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()); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/CommentView.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/CommentView.dart new file mode 100644 index 00000000..41531e10 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/CommentView.dart @@ -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 { + 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(builder: (context, state) { + if (state is Authenticated && context.read().getProfile() != "") { + return CircleAvatar(backgroundImage: NetworkImage(context.read().getProfile())); + } else { + return UiUtils.setFixedSizeboxForProfilePicture(childWidget: const Icon(Icons.account_circle, size: 35)); + } + })), + BlocListener( + bloc: context.read(), + listener: (context, state) { + if (state is SetCommentFetchSuccess) { + context.read().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().getUserId() != "0") + ? context.read().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: [ + (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(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().getUserId() != "0") { + context + .read() + .setLikeAndDislikeComm(langId: context.read().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().getUserId() != "0") { + context + .read() + .setLikeAndDislikeComm(langId: context.read().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().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( + 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().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(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()); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/ImageView.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/ImageView.dart new file mode 100644 index 00000000..605afcd1 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/ImageView.dart @@ -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 { + List 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 map(List list, Function handler) { + List 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(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()]); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/InterstitialAds/googleInterstitialAds.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/InterstitialAds/googleInterstitialAds.dart new file mode 100644 index 00000000..28b44e21 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/InterstitialAds/googleInterstitialAds.dart @@ -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: ['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().interstitialId() != "") { + InterstitialAd.load( + adUnitId: context.read().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; +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/InterstitialAds/unityInterstitialAds.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/InterstitialAds/unityInterstitialAds.dart new file mode 100644 index 00000000..62c63a95 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/InterstitialAds/unityInterstitialAds.dart @@ -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); + }, + ); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/NewsSubDetailsScreen.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/NewsSubDetailsScreen.dart new file mode 100644 index 00000000..6fff766e --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/NewsSubDetailsScreen.dart @@ -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 { + 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().getInAppAdsMode() == "1") bannerAdsInitialized(); + Future.delayed(Duration.zero, () { + context.read().getAdspaceForNewsDetails(langId: context.read().state.id); + }); + } + + setNewsViews({required bool isBreakingNews}) { + Future.delayed(Duration.zero, () { + context.read().setNewsViews(newsId: isBreakingNews ? widget.breakModel!.id! : (newsModel!.newsId ?? newsModel!.id)!, isBreakingNews: isBreakingNews); + }); + } + + getComments() { + if (!widget.isFromBreak && context.read().getCommentsMode() == "1") { + Future.delayed(Duration.zero, () { + context.read().getCommentNews(newsId: (newsModel!.newsId != null && newsModel!.newsId!.trim().isNotEmpty) ? newsModel!.newsId! : newsModel!.id!); + }); + } + } + + getRelatedNews() { + if (!widget.isFromBreak) { + Future.delayed(Duration.zero, () { + context.read().getRelatedNews( + langId: context.read().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().checkAdsType() == "unity") { + UnityAds.init( + gameId: context.read().unityGameId()!, + testMode: true, //set it to false @Deployment + onComplete: () {}, + onFailed: (error, message) {}); + } + + if (context.read().checkAdsType() == "google") { + _createBottomBannerAd(); + } + } + + void _createBottomBannerAd() { + if (context.read().bannerId() != "") { + _bannerAd = BannerAd( + adUnitId: context.read().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().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: [ + 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( + 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( + 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().hasMoreCommentNews()) { + context.read().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: [ + 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().getInAppAdsMode() == "1") || _bannerAd != null) Flexible(child: setBannerAd(context, _bannerAd)) + ]), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/ReplyCommentView.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/ReplyCommentView.dart new file mode 100644 index 00000000..31c022a8 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/ReplyCommentView.dart @@ -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 { + 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( + 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: [ + 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().getUserId() != "0" + ? Padding( + padding: const EdgeInsetsDirectional.only(top: 10.0), + child: Row( + children: [ + Expanded( + flex: 1, + child: context.read().getProfile() != "" + ? UiUtils.setFixedSizeboxForProfilePicture(childWidget: CircleAvatar(backgroundImage: NetworkImage(context.read().getProfile()), radius: 32)) + : UiUtils.setFixedSizeboxForProfilePicture(childWidget: const Icon(Icons.account_circle, size: 35))), + BlocListener( + bloc: context.read(), + listener: (context, state) { + if (state is SetCommentFetchSuccess) { + context.read().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().getUserId() != "0") { + context.read().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: [ + 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(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().getUserId() != "0") + ? context.read().setLikeAndDislikeComm( + langId: context.read().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().getUserId() != "0") + ? context.read().setLikeAndDislikeComm( + langId: context.read().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().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(builder: (context, state) { + if (state is CommentNewsFetchSuccess && state.commentNews.isNotEmpty) { + return BlocListener( + 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().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()); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/RerwardAds/googleRewardAds.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/RerwardAds/googleRewardAds.dart new file mode 100644 index 00000000..4aa4f632 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/RerwardAds/googleRewardAds.dart @@ -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: ['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().rewardId() != "") { + RewardedAd.load( + adUnitId: context.read().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().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; + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/RerwardAds/unityRewardAds.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/RerwardAds/unityRewardAds.dart new file mode 100644 index 00000000..840576a2 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/RerwardAds/unityRewardAds.dart @@ -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); + }, + ); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/ShowMoreNewsList.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/ShowMoreNewsList.dart new file mode 100644 index 00000000..016d7313 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/ShowMoreNewsList.dart @@ -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; + return CupertinoPageRoute(builder: (_) => ShowMoreNewsList(model: arguments['model'])); + } +} + +class ShowMoreNewsListState extends State { + late final ScrollController controller = ScrollController()..addListener(hasMoreRelatedNewsScrollListener); + Set get locationValue => SettingsLocalDataRepository().getLocationCityValues(); + + void hasMoreRelatedNewsScrollListener() { + if (controller.position.maxScrollExtent == controller.offset) { + if (context.read().hasMoreRelatedNews()) { + context.read().getMoreRelatedNews( + langId: context.read().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 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().getMoreRelatedNews( + langId: context.read().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().getRelatedNews( + langId: context.read().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( + 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)); + }, + ), + ); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/backBtn.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/backBtn.dart new file mode 100644 index 00000000..c3f834c4 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/backBtn.dart @@ -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); + })); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/dateView.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/dateView.dart new file mode 100644 index 00000000..f6f01281 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/dateView.dart @@ -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)) + ], + ); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportCom.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportCom.dart new file mode 100644 index 00000000..f6379c5b --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportCom.dart @@ -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: [ + if (context.read().getUserId() == model.userId!) + InkWell( + onTap: () async { + if (context.read().getUserId() != "0") { + context.read().setDeleteComm(commId: model.id!); + } + }, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + child: Row( + children: [ + CustomTextLabel( + text: 'deleteTxt', + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold)), + const Spacer(), + BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is DeleteCommSuccess) { + context.read().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().getUserId() != model.userId!) + Padding( + padding: const EdgeInsets.only(top: 15), + child: Row(children: [ + 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().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().getUserId() != model.userId!) + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + 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( + bloc: context.read(), + 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().getUserId() != "0") { + if (reportC.text.trim().isNotEmpty) { + context.read().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))); + }) + ], + ), + ], + ))); + }); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportReplyComm.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportReplyComm.dart new file mode 100644 index 00000000..126e5162 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportReplyComm.dart @@ -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: [ + if (context.read().getUserId() == model.replyComList![replyIndex].userId!) + InkWell( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + onTap: () async { + if (context.read().getUserId() != "0") { + context.read().setDeleteComm(commId: model.replyComList![replyIndex].id!); + } + }, + child: Row( + children: [ + CustomTextLabel( + text: 'deleteTxt', + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold)), + const Spacer(), + BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is DeleteCommSuccess) { + context.read().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().getUserId() != model.replyComList![replyIndex].userId!) + Padding( + padding: const EdgeInsets.only(top: 15), + child: Row( + children: [ + 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().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().getUserId() != model.replyComList![replyIndex].userId!) + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + 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( + bloc: context.read(), + 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().getUserId() != "0") { + if (reportC.text.trim().isNotEmpty) { + context.read().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))); + }) + ], + ), + ], + ))); + }); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/descView.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/descView.dart new file mode 100644 index 00000000..9d5edc8a --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/descView.dart @@ -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() == "") || (element.toString() == "")) { + return FittedBox( + fit: BoxFit.fill, + child: Container( + height: 220, + width: MediaQuery.of(context).size.width, + color: Colors.transparent, + child: (element.toString() == "") ? NewsDetailsVideo(src: element.attributes["src"], type: "1") : NewsDetailsVideo(type: "2", src: element.outerHtml)), + ); + } + return null; + }, + )); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/horizontalBtnList.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/horizontalBtnList.dart new file mode 100644 index 00000000..b93edd52 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/horizontalBtnList.dart @@ -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().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( + bloc: context.read(), + builder: (context, bookmarkState) { + bool isBookmark = context.read().isNewsBookmark(model!.newsId!); + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateBookmarkStatusSuccess) { + if (state.wasBookmarkNewsProcess) { + context.read().addBookmarkNews(state.news); + } else { + context.read().removeBookmarkNews(state.news); + } + } + isBookmark = context.read().isNewsBookmark(model.newsId!); + }), + builder: (context, state) { + return InkWell( + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateBookmarkStatusInProgress) { + return; + } + context.read().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( + 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: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 30), + child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + 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))) + ], + ); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/likeBtn.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/likeBtn.dart new file mode 100644 index 00000000..88cefc3d --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/likeBtn.dart @@ -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().isNewsLikeAndDisLike(model.newsId!); + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is LikeAndDisLikeFetchSuccess) { + isLike = context.read().isNewsLikeAndDisLike(model.newsId!); + } + }), + builder: (context, likeAndDislikeState) { + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateLikeAndDisLikeStatusSuccess) { + context.read().getLike(langId: context.read().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().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().getUserId() != "0") { + if (state is UpdateLikeAndDisLikeStatusInProgress) { + return; + } + context.read().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)), + ), + ), + ) + ], + )); + }); + }); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/relatedNewsList.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/relatedNewsList.dart new file mode 100644 index 00000000..9c1a3273 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/relatedNewsList.dart @@ -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 { + int sliderIndex = 0; + List relatedList = []; + + Widget getRelatedList() { + return BlocConsumer( + bloc: context.read(), + 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 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 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 map(List list, Function handler) { + List 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 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(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(); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/setBannderAds.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/setBannderAds.dart new file mode 100644 index 00000000..fecf317f --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/setBannderAds.dart @@ -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().bannerId() != "") { + switch (context.read().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().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(); + } + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/tagView.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/tagView.dart new file mode 100644 index 00000000..c8b80f29 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/tagView.dart @@ -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 tagList = []; + + if (model.tagName! != "") { + final tagName = model.tagName!; + tagList = tagName.split(','); + } + + List 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 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)); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/titleView.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/titleView.dart new file mode 100644 index 00000000..23fd2e51 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/titleView.dart @@ -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))); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/videoBtn.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/videoBtn.dart new file mode 100644 index 00000000..c804378b --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/videoBtn.dart @@ -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(); + } +} diff --git a/news-app/lib/ui/screens/NewsDetailsVideo.dart b/news-app/lib/ui/screens/NewsDetailsVideo.dart new file mode 100644 index 00000000..f96a5fb5 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetailsVideo.dart @@ -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 createState() => StateNewsDetailsVideo(); +} + +class StateNewsDetailsVideo extends State { + final GlobalKey _scaffoldKey = GlobalKey(); + bool _isNetworkAvail = true; + var iframe; + + @override + void initState() { + super.initState(); + + checkNetwork(); + if ((widget.type == "1") || (widget.type == "3")) { + iframe = ''' + + + + '''; + } else { + iframe = ''' + + + + '''; + } + } + + 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)), + ); + } +} diff --git a/news-app/lib/ui/screens/NewsVideo.dart b/news-app/lib/ui/screens/NewsVideo.dart new file mode 100644 index 00000000..10e28355 --- /dev/null +++ b/news-app/lib/ui/screens/NewsVideo.dart @@ -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 createState() => StateVideo(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => NewsVideo(from: arguments['from'], liveModel: arguments['liveModel'], model: arguments['model'], breakModel: arguments['breakModel'])); + } +} + +class StateVideo extends State { + 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(); + } +} diff --git a/news-app/lib/ui/screens/PrivacyPolicyScreen.dart b/news-app/lib/ui/screens/PrivacyPolicyScreen.dart new file mode 100644 index 00000000..8582c7e4 --- /dev/null +++ b/news-app/lib/ui/screens/PrivacyPolicyScreen.dart @@ -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 createState() { + return StatePrivacy(); + } + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute( + builder: (_) => PrivacyPolicy( + from: arguments['from'], + title: arguments['title'], + desc: arguments['desc'], + )); + } +} + +class StatePrivacy extends State 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'; + } + }, + ), + )); + } +} diff --git a/news-app/lib/ui/screens/Profile/ProfileScreen.dart b/news-app/lib/ui/screens/Profile/ProfileScreen.dart new file mode 100644 index 00000000..44743361 --- /dev/null +++ b/news-app/lib/ui/screens/Profile/ProfileScreen.dart @@ -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 { + 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().getOtherPage(langId: context.read().state.id); + }); + } + + Widget pagesBuild() { + return BlocBuilder(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().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().changeTheme(AppTheme.Light); + UiUtils.setUIOverlayStyle(appTheme: AppTheme.Light); + //for non-appbar screens + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + } + } + } + + bool getTheme() { + return (context.read().state.appTheme == AppTheme.Dark) ? true : false; + } + + bool getNotification() { + if (context.read().state.settingsModel!.notification == true) { + return true; + } else { + return false; + } + } + + switchNotification(bool value) { + if (!value) { + FirebaseMessaging.instance.deleteToken().then((value) async { + context.read().registerToken(fcmId: '', context: context); + }); + } else { + FirebaseMessaging.instance.getToken().then((value) async { + context.read().registerToken(fcmId: value!, context: context); + context.read().changeFcmToken(value); + }); + } + + context.read().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().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().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().getShareAppText()}\n\n${context.read().getAndroidAppLink()}\n"; + bool isIOSAppLive = context.read().getiOSAppLink()?.trim().isNotEmpty ?? false; + if (isIOSAppLive) str += "\n\n${context.read().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().deleteUser().then((value) { + showSnackBar(value["message"], context); + for (int i = 0; i < AuthProviders.values.length; i++) { + if (AuthProviders.values[i].name == context.read().getType()) { + context.read().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().getType()) { + context.read().signOut(AuthProviders.values[i]).then((value) { + Navigator.of(context).pushNamedAndRemoveUntil(Routes.login, (route) => false); + }); + } + } + } else { + throw showSnackBar('${error.message}', context); + } + } catch (e) {} + } + + Future _openStoreListing() => _inAppReview.openStoreListing(appStoreId: context.read().getAppstoreId(), microsoftStoreId: 'microsoftStoreId'); + + Widget setHeader() { + return BlocBuilder(builder: (context, authState) { + if (authState is Authenticated && context.read().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: [ + 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: [ + 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( + 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: [ + //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( + 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( + 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( + builder: (context, state) { + return ListView( + padding: const EdgeInsetsDirectional.only(top: 10.0), + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + children: [ + if (context.read().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().getUserId() != "0") setDrawerItem('bookmarkLbl', Icons.bookmarks_rounded, false, true, false, 3), + // if (context.read().getUserId() != "0") setDrawerItem('notificationLbl', Icons.notifications, false, true, false, 4), + if (context.read().getUserId() != "0" && isAuthor) + // context.read().getRole() != "0") + setDrawerItem('createNewsLbl', Icons.add_box_rounded, false, true, false, 5), + if (context.read().getUserId() != "0" && isAuthor) + // context.read().getRole() != "0") + setDrawerItem('manageNewsLbl', Icons.edit_document, false, true, false, 6), + if (context.read().getUserId() != "0") setDrawerItem('managePreferences', Icons.thumbs_up_down_rounded, false, true, false, 7), + if (context.read().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().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().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: [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)), + ); + } +} diff --git a/news-app/lib/ui/screens/Profile/Widgets/customAlertDialog.dart b/news-app/lib/ui/screens/Profile/Widgets/customAlertDialog.dart new file mode 100644 index 00000000..ef9fb32b --- /dev/null +++ b/news-app/lib/ui/screens/Profile/Widgets/customAlertDialog.dart @@ -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: [ + 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)) + ]), + )), + ], + ); + } +} diff --git a/news-app/lib/ui/screens/Profile/userProfile.dart b/news-app/lib/ui/screens/Profile/userProfile.dart new file mode 100644 index 00000000..5b3268f0 --- /dev/null +++ b/news-app/lib/ui/screens/Profile/userProfile.dart @@ -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 { + final _formKey = GlobalKey(); + 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().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().getProfile(); + if (isThisUserAnAuthor) { + authorBioC = TextEditingController(text: (authLocalDataSource.getAuthorBio().isEmpty) ? context.read().getAuthorBio() : authLocalDataSource.getAuthorBio()); + whatsappLinkC = + TextEditingController(text: (authLocalDataSource.getAuthorWhatsappLink()!.isEmpty) ? context.read().getAuthorWhatsappLink() : authLocalDataSource.getAuthorWhatsappLink()); + telegramLinkC = + TextEditingController(text: (authLocalDataSource.getAuthorTelegramLink()!.isEmpty) ? context.read().getAuthorTelegramLink() : authLocalDataSource.getAuthorTelegramLink()); + linkedInLinkC = + TextEditingController(text: (authLocalDataSource.getAuthorLinkedInLink()!.isEmpty) ? context.read().getAuthorLinkedInLink() : authLocalDataSource.getAuthorLinkedInLink()); + facebookLinkC = + TextEditingController(text: (authLocalDataSource.getAuthorFacebookLink()!.isEmpty) ? context.read().getAuthorFacebookLink() : authLocalDataSource.getAuthorFacebookLink()); + } + } + + getLanguageList() { + Future.delayed(Duration.zero, () { + context.read().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( + 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().requestToBecomeAuthor(); + setPendingStatusControllerValues(); + } + if (widget.from == "login") { + if (context.read().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().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().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().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().getType() != loginMbl && context.read().getMobile().isNotEmpty) { + mobile = " "; + } + try { + context.read().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); + } +} diff --git a/news-app/lib/ui/screens/PushNotificationService.dart b/news-app/lib/ui/screens/PushNotificationService.dart new file mode 100644 index 00000000..d47df17b --- /dev/null +++ b/news-app/lib/ui/screens/PushNotificationService.dart @@ -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 _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().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().getNewsById(newsId: payload, langId: context.read().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 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().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().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()?.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 _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() + ?.startForegroundService(1, 'plain title', 'plain body', notificationDetails: androidNotificationDetails, payload: ''); + } + + selectNotificationPayload(String? payload) async { + if (payload != null && payload.isNotEmpty && payload != "0") { + context.read().getNewsById(newsId: payload, langId: context.read().state.id).then((value) { + UiUtils.rootNavigatorKey.currentState!.pushNamed(Routes.newsDetails, arguments: {"model": value[0], "isFromBreak": false, "fromShowMore": false}); + }); + } + } +} + +Future _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 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(bigPicturePath, hideThumbnail: false)]); + var platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics, iOS: darwinNotificationDetails); + if (msg != "") await flutterLocalNotificationsPlugin.show(1, title, msg, platformChannelSpecifics, payload: type); +} + +Future 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); +} diff --git a/news-app/lib/ui/screens/RSSFeedDetailsScreen.dart b/news-app/lib/ui/screens/RSSFeedDetailsScreen.dart new file mode 100644 index 00000000..b88a8071 --- /dev/null +++ b/news-app/lib/ui/screens/RSSFeedDetailsScreen.dart @@ -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; + return CupertinoPageRoute(builder: (_) => RSSFeedDetailsScreen(feedUrl: arguments['feedUrl'])); + } +} + +class _RSSFeedDetailsScreenState extends State { + List _feedItems = []; + bool isFeedLoaded = false; + + @override + void initState() { + super.initState(); + fetchFeed(); + } + + Widget buildFeedItem(Map 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 _launchURL(String url) async { + if (await canLaunch(url)) { + await launch(url); + } else { + throw 'Could not launch $url'; + } + } + + Future 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]); + }, + )), + ); + } +} diff --git a/news-app/lib/ui/screens/RSSFeedScreen.dart b/news-app/lib/ui/screens/RSSFeedScreen.dart new file mode 100644 index 00000000..95baa493 --- /dev/null +++ b/news-app/lib/ui/screens/RSSFeedScreen.dart @@ -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 { + final Set 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( + 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().getRSSFeed(langId: context.read().state.id, categoryId: catSelId, subCategoryId: subCatSelId); + }); + } + } + + void hasMoreRssFeedScrollListener() { + if (_controller.position.maxScrollExtent == _controller.offset) { + if (context.read().hasMoreRSSFeed()) { + context.read().getMoreRSSFeed(langId: context.read().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( + 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(listener: (context, state) { + if (catSel != "") catIndex = context.read().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().state.id, + listLength: context.read().getCatList().length, + listViewChild: (context, index) { + return catListItem(index, context.read().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 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 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().getCatList().isNotEmpty) && + (context.read().getCatList()[catIndex!].subData!.isNotEmpty)) { + return BlocBuilder( + 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().state.id, + listLength: context.read().getCatList()[catIndex!].subData!.length, + listViewChild: (context, index) => subCatListItem(index, context.read().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), + ))); + } +} diff --git a/news-app/lib/ui/screens/Search.dart b/news-app/lib/ui/screens/Search.dart new file mode 100644 index 00000000..317bf5e4 --- /dev/null +++ b/news-app/lib/ui/screens/Search.dart @@ -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 with TickerProviderStateMixin { + final TextEditingController _controller = TextEditingController(); + final GlobalKey _scaffoldKey = GlobalKey(); + int pos = 0; + List searchList = []; + final List _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 filterSelectedCategories = []; + List filterSelectedTags = []; + DateTime? filterSelectedDate; + DurationFilter? filterDurationFilter; + + Timer? _debounce; + List history = []; + + List 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().loadIfFailed(langId: context.read().state.id); + context.read().loadIfFailed(langId: context.read().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> 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().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 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().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 items = []; + List 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>( + future: getHistory(), + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { + final List entities = snapshot.data!; + final List 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: [ + 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: [ + 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? 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}); + } + }); + }, + ); + } +} diff --git a/news-app/lib/ui/screens/SectionMoreNews/SectionMoreBreakNewsList.dart b/news-app/lib/ui/screens/SectionMoreNews/SectionMoreBreakNewsList.dart new file mode 100644 index 00000000..d3bb6197 --- /dev/null +++ b/news-app/lib/ui/screens/SectionMoreNews/SectionMoreBreakNewsList.dart @@ -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 createState() { + return _SectionBreakingNewsState(); + } + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => SectionMoreBreakingNewsList(sectionId: arguments['sectionId'], title: arguments['title'])); + } +} + +class _SectionBreakingNewsState extends State { + late final ScrollController controller = ScrollController()..addListener(hasMoreSectionScrollListener); + Set get locationValue => SettingsLocalDataRepository().getLocationCityValues(); + + @override + void initState() { + getSectionByData(); + super.initState(); + } + + void getSectionByData() { + Future.delayed(Duration.zero, () { + context.read().getSectionById(langId: context.read().state.id, sectionId: widget.sectionId, latitude: locationValue.first, longitude: locationValue.last); + }); + } + + void hasMoreSectionScrollListener() { + if (controller.position.maxScrollExtent == controller.offset) { + if (context.read().hasMoreSections() && !(context.read().state is SectionByIdFetchInProgress)) { + context.read().getMoreSectionById(langId: context.read().state.id, sectionId: widget.sectionId); + } + } + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + _buildSectionBreakingNewsContainer({required BreakingNewsModel model, required String type, required int index, required List 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( + builder: (context, state) { + if (state is SectionByIdFetchSuccess) { + return Padding( + padding: const EdgeInsetsDirectional.all(10.0), + child: RefreshIndicator( + onRefresh: () async { + context + .read() + .getSectionById(sectionId: widget.sectionId, langId: context.read().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); + }, + ), + ); + } +} diff --git a/news-app/lib/ui/screens/SectionMoreNews/SectionMoreNewsList.dart b/news-app/lib/ui/screens/SectionMoreNews/SectionMoreNewsList.dart new file mode 100644 index 00000000..bad6e4a9 --- /dev/null +++ b/news-app/lib/ui/screens/SectionMoreNews/SectionMoreNewsList.dart @@ -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 createState() { + return _SectionNewsState(); + } + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => SectionMoreNewsList(sectionId: arguments['sectionId'], title: arguments['title'])); + } +} + +class _SectionNewsState extends State { + late final ScrollController controller = ScrollController()..addListener(hasMoreSectionScrollListener); + Set get locationValue => SettingsLocalDataRepository().getLocationCityValues(); + + @override + void initState() { + getSectionByData(); + super.initState(); + } + + void getSectionByData() { + Future.delayed(Duration.zero, () { + context.read().getSectionById(langId: context.read().state.id, sectionId: widget.sectionId, latitude: locationValue.first, longitude: locationValue.last); + }); + } + + void hasMoreSectionScrollListener() { + if (controller.position.maxScrollExtent == controller.offset) { + if (context.read().hasMoreSections() && !(context.read().state is SectionByIdFetchInProgress)) { + context.read().getMoreSectionById(langId: context.read().state.id, sectionId: widget.sectionId); + } + } + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + _buildSectionNewsContainer({required NewsModel model, required String type, required int index, required List 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( + builder: (context, state) { + if (state is SectionByIdFetchSuccess) { + return Padding( + padding: const EdgeInsetsDirectional.symmetric(vertical: 10), + child: RefreshIndicator( + onRefresh: () async { + context + .read() + .getSectionById(sectionId: widget.sectionId, langId: context.read().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); + }, + ), + ); + } +} diff --git a/news-app/lib/ui/screens/SubCategory/SubCategoryScreen.dart b/news-app/lib/ui/screens/SubCategory/SubCategoryScreen.dart new file mode 100644 index 00000000..6fd672d4 --- /dev/null +++ b/news-app/lib/ui/screens/SubCategory/SubCategoryScreen.dart @@ -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; + return CupertinoPageRoute(builder: (_) => SubCategoryScreen(catId: arguments['catId'], catName: arguments['catName'])); + } +} + +class SubCategoryScreenState extends State with TickerProviderStateMixin { + final List> _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().getSubCategory(context: context, langId: context.read().state.id, catId: widget.catId); + }); + } + + Widget subcatData() { + return BlocConsumer( + bloc: context.read(), + 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(builder: (context, state) { + if (state is SubCategoryFetchFailure || context.read().getSubCatMode() != "1") { + return BlocProvider(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.generate(_tc!.length, (int index) { + return BlocProvider( + create: (_) => SubCatNewsCubit(SubCatNewsRepository()), + child: SubCatNewsList(from: index == 0 ? 1 : 2, subCatId: index == 0 ? null : _tabs[index]['subCatId'], catId: widget.catId)); + })); + } + return ShimmerNewsList(isNews: true); + }), + ); + } +} diff --git a/news-app/lib/ui/screens/SubCategory/Widgets/SubCatNewsList.dart b/news-app/lib/ui/screens/SubCategory/Widgets/SubCatNewsList.dart new file mode 100644 index 00000000..008b43ab --- /dev/null +++ b/news-app/lib/ui/screens/SubCategory/Widgets/SubCatNewsList.dart @@ -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; + return CupertinoPageRoute(builder: (_) => SubCatNewsList(from: arguments['from'], catId: arguments['catId'], subCatId: arguments['subCatId'])); + } +} + +class SubCatNewsListState extends State with AutomaticKeepAliveClientMixin { + List combineList = []; + int totalSurveyQue = 0; + Set 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().getUserId() != "0") { + context.read().getSurveyQuestion(langId: context.read().state.id).whenComplete(() { + getSubcategoryNews(); + }); + } else { + getSubcategoryNews(); + } + }); + } + + void hasMoreNotiScrollListener() { + if (controller.position.maxScrollExtent == controller.offset) { + if (context.read().hasMoreSubCatNews()) { + context.read().getMoreSubCatNews( + langId: context.read().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( + bloc: context.read(), + 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().surveyList().isNotEmpty && context.read().surveyList().length > cur) { + combineList.add(context.read().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 newsList) { + setTotalSurveyQueCount(); + return RefreshIndicator( + onRefresh: () async { + combineList.clear(); + if (context.read().getUserId() != "0") { + context.read().getSurveyQuestion(langId: context.read().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().getSubCatNews( + langId: context.read().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 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().getMoreSubCatNews( + langId: context.read().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().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; +} diff --git a/news-app/lib/ui/screens/SubCategory/Widgets/categoryShimmer.dart b/news-app/lib/ui/screens/SubCategory/Widgets/categoryShimmer.dart new file mode 100644 index 00000000..ed45c7e3 --- /dev/null +++ b/news-app/lib/ui/screens/SubCategory/Widgets/categoryShimmer.dart @@ -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()), + )); +} diff --git a/news-app/lib/ui/screens/SubCategory/Widgets/showSurveyQuestion.dart b/news-app/lib/ui/screens/SubCategory/Widgets/showSurveyQuestion.dart new file mode 100644 index 00000000..6a13ac8f --- /dev/null +++ b/news-app/lib/ui/screens/SubCategory/Widgets/showSurveyQuestion.dart @@ -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 { + String? optSel; + + @override + Widget build(BuildContext context) { + return showSurveyQue(); + } + + Widget showSurveyQue() { + return BlocConsumer( + bloc: context.read(), + 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().removeQuestion(widget.surveyId); + context.read().getSurveyAns(langId: context.read().state.id); + } + }, + builder: (context, state) { + return BlocConsumer( + bloc: context.read(), + 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().getSurveyQuestionIndex(questionTitle: widget.model.question!); + context.read().setSurveyAns(optId: optSel!, queId: currentIndex); + await Future.delayed(Duration(seconds: 2)); + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'optSel'), context); + } + }), + ) + ], + ))); + }); + }); + } +} diff --git a/news-app/lib/ui/screens/SubCategory/Widgets/showSurveyResult.dart b/news-app/lib/ui/screens/SubCategory/Widgets/showSurveyResult.dart new file mode 100644 index 00000000..139cc33c --- /dev/null +++ b/news-app/lib/ui/screens/SubCategory/Widgets/showSurveyResult.dart @@ -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))); + })), + ], + ))); +} diff --git a/news-app/lib/ui/screens/TagNewsScreen.dart b/news-app/lib/ui/screens/TagNewsScreen.dart new file mode 100644 index 00000000..a54c3374 --- /dev/null +++ b/news-app/lib/ui/screens/TagNewsScreen.dart @@ -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; + return CupertinoPageRoute(builder: (_) => NewsTag(tagId: arguments['tagId'], tagName: arguments['tagName'])); + } +} + +class NewsTagState extends State { + @override + void initState() { + super.initState(); + getTagWiseNews(); + } + + getTagWiseNews() { + Future.delayed(Duration.zero, () { + context.read().getTagNews( + tagId: widget.tagId, + langId: context.read().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 tagList = []; + List 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().isNewsLikeAndDisLike(model.id!); + return Padding( + padding: EdgeInsetsDirectional.only(top: index == 0 ? 0 : MediaQuery.of(context).size.height / 25.0), + child: Column(children: [ + InkWell( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + 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( + bloc: context.read(), + builder: (context, bookmarkState) { + bool isBookmark = context.read().isNewsBookmark(model.id!); + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateBookmarkStatusSuccess) { + if (state.wasBookmarkNewsProcess) { + context.read().addBookmarkNews(state.news); + } else { + context.read().removeBookmarkNews(state.news); + } + } + }), + builder: (context, state) { + return InkWell( + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateBookmarkStatusInProgress) { + return; + } + context.read().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( + bloc: context.read(), + listener: ((context, state) { + isLike = context.read().isNewsLikeAndDisLike(model.id!); + }), + builder: (context, likeAndDislikeState) { + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateLikeAndDisLikeStatusSuccess) { + context.read().getLike(langId: context.read().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().isNewsLikeAndDisLike(model.id!); + return InkWell( + splashColor: Colors.transparent, + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateLikeAndDisLikeStatusInProgress) { + return; + } + context.read().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(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); + }); + } +} diff --git a/news-app/lib/ui/screens/Videos/VideoScreen.dart b/news-app/lib/ui/screens/Videos/VideoScreen.dart new file mode 100644 index 00000000..8b0ed1f6 --- /dev/null +++ b/news-app/lib/ui/screens/Videos/VideoScreen.dart @@ -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 route(RouteSettings routeSettings) { + return CupertinoPageRoute(builder: (_) => const VideoScreen()); + } +} + +class VideoScreenState extends State { + 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().getVideo(langId: context.read().state.id, latitude: latitude, longitude: longitude); + }); + } + + @override + void initState() { + videoViewType = context.read().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().hasMoreVideo()) { + context.read().getMoreVideo(langId: context.read().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(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().getMoreVideo( + langId: context.read().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 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 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 { + @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 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), + ) + ], + ), + ); + } +} diff --git a/news-app/lib/ui/screens/Videos/Widgets/otherVideosCard.dart b/news-app/lib/ui/screens/Videos/Widgets/otherVideosCard.dart new file mode 100644 index 00000000..67bd6c98 --- /dev/null +++ b/news-app/lib/ui/screens/Videos/Widgets/otherVideosCard.dart @@ -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 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) + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/Videos/Widgets/videoCard.dart b/news-app/lib/ui/screens/Videos/Widgets/videoCard.dart new file mode 100644 index 00000000..4d71b737 --- /dev/null +++ b/news-app/lib/ui/screens/Videos/Widgets/videoCard.dart @@ -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 createState() => VideoCardState(); +} + +class VideoCardState extends State { + late NewsModel? model; + late BreakingNewsModel? brModel; + late LiveStreamingModel? liveModel; + bool isLiveVideo = false, isBreakingVideo = false; + List? tagList = []; + List? 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().isNewsLikeAndDisLike(widget.model?.newsId ?? "0"); + + return BlocProvider( + create: (context) => UpdateLikeAndDisLikeStatusCubit(LikeAndDisLikeRepository()), + child: BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is LikeAndDisLikeFetchSuccess) { + isLike = context.read().isNewsLikeAndDisLike(model?.newsId ?? "0"); + } else { + isLike = false; //in case of failue - no other likes found + } + }), + builder: (context, likeAndDislikeState) { + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateLikeAndDisLikeStatusSuccess) { + context.read().getLike(langId: context.read().state.id); + } + }), + builder: (context, state) { + return InkWell( + splashColor: Colors.transparent, + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateLikeAndDisLikeStatusInProgress) { + return; + } + context.read().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( + bloc: context.read(), + builder: (context, bookmarkState) { + bool isBookmark = context.read().isNewsBookmark(model?.id ?? "0"); + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateBookmarkStatusSuccess) { + (state.wasBookmarkNewsProcess) ? context.read().addBookmarkNews(state.news) : context.read().removeBookmarkNews(state.news); + setState(() {}); + } + }), + builder: (context, state) { + return InkWell( + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateBookmarkStatusInProgress) return; + context.read().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); + } +} diff --git a/news-app/lib/ui/screens/Videos/videoDetailsScreen.dart b/news-app/lib/ui/screens/Videos/videoDetailsScreen.dart new file mode 100644 index 00000000..6a399807 --- /dev/null +++ b/news-app/lib/ui/screens/Videos/videoDetailsScreen.dart @@ -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? otherVideos; + List? otherBreakingVideos; + List? otherLiveVideos; + + VideoDetailsScreen({super.key, this.model, required this.from, this.liveModel, this.breakModel, required this.otherVideos, this.otherLiveVideos, this.otherBreakingVideos}); + + @override + State createState() => VideoDetailsState(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + 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 { + 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(), + ], + ), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/auth/ForgotPassword.dart b/news-app/lib/ui/screens/auth/ForgotPassword.dart new file mode 100644 index 00000000..3f4a17be --- /dev/null +++ b/news-app/lib/ui/screens/auth/ForgotPassword.dart @@ -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 { + TextEditingController emailC = TextEditingController(); + final FirebaseAuth _auth = FirebaseAuth.instance; + final GlobalKey _formkey = GlobalKey(); + 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()], + ), + ))); + } +} diff --git a/news-app/lib/ui/screens/auth/RequestOtpScreen.dart b/news-app/lib/ui/screens/auth/RequestOtpScreen.dart new file mode 100644 index 00000000..4690cdbb --- /dev/null +++ b/news-app/lib/ui/screens/auth/RequestOtpScreen.dart @@ -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 { + TextEditingController phoneC = TextEditingController(); + String? phone, conCode; + final GlobalKey _formkey = GlobalKey(); + 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: [ + 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: [ + 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: [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().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 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; + } +} diff --git a/news-app/lib/ui/screens/auth/VerifyOtpScreen.dart b/news-app/lib/ui/screens/auth/VerifyOtpScreen.dart new file mode 100644 index 00000000..bf6061d9 --- /dev/null +++ b/news-app/lib/ui/screens/auth/VerifyOtpScreen.dart @@ -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; + return CupertinoPageRoute( + builder: (_) => VerifyOtp( + verifyId: arguments['verifyId'], + countryCode: arguments['countryCode'], + mono: arguments['mono'], + )); + } +} + +class VerifyOtpState extends State { + final GlobalKey _scaffoldKey = GlobalKey(); + String? otp; + final GlobalKey _formkey = GlobalKey(); + 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( + bloc: context.read(), + listener: (context, state) async { + if (state is SocialSignUpFailure) { + showSnackBar(state.errorMessage, context); + } + }, + builder: (context, state) { + return Stack( + children: [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: [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: [ + 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().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; + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/bottomComBtn.dart b/news-app/lib/ui/screens/auth/Widgets/bottomComBtn.dart new file mode 100644 index 00000000..ef9c114f --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/bottomComBtn.dart @@ -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()); + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/fieldFocusChange.dart b/news-app/lib/ui/screens/auth/Widgets/fieldFocusChange.dart new file mode 100644 index 00000000..dae4eac5 --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/fieldFocusChange.dart @@ -0,0 +1,6 @@ +import 'package:flutter/cupertino.dart'; + +fieldFocusChange(BuildContext context, FocusNode currentFocus, FocusNode nextFocus) { + currentFocus.unfocus(); + FocusScope.of(context).requestFocus(nextFocus); +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setConfimPass.dart b/news-app/lib/ui/screens/auth/Widgets/setConfimPass.dart new file mode 100644 index 00000000..42ebb9cc --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setConfimPass.dart @@ -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 createState() { + return _SetConfPassState(); + } +} + +class _SetConfPassState extends State { + 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), + ), + ), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setDivider.dart b/news-app/lib/ui/screens/auth/Widgets/setDivider.dart new file mode 100644 index 00000000..04510b3f --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setDivider.dart @@ -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)), + ], + )); + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setEmail.dart b/news-app/lib/ui/screens/auth/Widgets/setEmail.dart new file mode 100644 index 00000000..559a8f7a --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setEmail.dart @@ -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), + ), + ), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setForgotPass.dart b/news-app/lib/ui/screens/auth/Widgets/setForgotPass.dart new file mode 100644 index 00000000..24d127b1 --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setForgotPass.dart @@ -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')))); +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setLoginAndSignUpBtn.dart b/news-app/lib/ui/screens/auth/Widgets/setLoginAndSignUpBtn.dart new file mode 100644 index 00000000..d1c5d750 --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setLoginAndSignUpBtn.dart @@ -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()), + ); + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setName.dart b/news-app/lib/ui/screens/auth/Widgets/setName.dart new file mode 100644 index 00000000..5cfc7ebe --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setName.dart @@ -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), + ), + ), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setPassword.dart b/news-app/lib/ui/screens/auth/Widgets/setPassword.dart new file mode 100644 index 00000000..47c6ceb1 --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setPassword.dart @@ -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 createState() { + return _SetPassState(); + } +} + +class _SetPassState extends State { + 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), + ), + ), + ), + ); + }); + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setTermPolicy.dart b/news-app/lib/ui/screens/auth/Widgets/setTermPolicy.dart new file mode 100644 index 00000000..3a20d5c7 --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setTermPolicy.dart @@ -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}); + }), + ), + ]), + )); +} diff --git a/news-app/lib/ui/screens/auth/Widgets/svgPictureWidget.dart b/news-app/lib/ui/screens/auth/Widgets/svgPictureWidget.dart new file mode 100644 index 00000000..d33a55e6 --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/svgPictureWidget.dart @@ -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); + } +} diff --git a/news-app/lib/ui/screens/auth/loginScreen.dart b/news-app/lib/ui/screens/auth/loginScreen.dart new file mode 100644 index 00000000..b93cb027 --- /dev/null +++ b/news-app/lib/ui/screens/auth/loginScreen.dart @@ -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; + return CupertinoPageRoute(builder: (_) => LoginScreen(isFromApp: arguments['isFromApp'] ?? false)); + } + } +} + +class LoginScreenState extends State with TickerProviderStateMixin { + final GlobalKey _formkey = GlobalKey(); + 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().getPrivacyTerms(langId: context.read().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( + bloc: context.read(), + listener: (context, state) async { + if (state is SocialSignUpFailure) { + showSnackBar(state.errorMessage, context); + } + if (state is SocialSignUpSuccess) { + context.read().checkAuthStatus(); + if (state.authModel.status == "0") { + showSnackBar(UiUtils.getTranslatedLabel(context, 'deactiveMsg'), context); + } else { + FirebaseMessaging.instance.getToken().then((token) async { + if (token != null) { + context.read().registerToken(fcmId: token, context: context); + if (token != context.read().getSettings().token) { + context.read().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: [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().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(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().socialSignUpUser(authProvider: AuthProviders.gmail, context: context); + } + }, + img: 'google_button'), + if (fblogInEnabled) + BottomCommButton( + onTap: () { + if (!isPolicyAvailable) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'addTCFirst'), context); + } else { + context.read().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().socialSignUpUser(authProvider: AuthProviders.apple, context: context); + } + }, + img: 'apple_logo', + btnCaption: 'apple', + btnColor: UiUtils.getColorScheme(context).primaryContainer), + if (context.read().getMobileLoginMode() != "" && context.read().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(), + ); + } +} diff --git a/news-app/lib/ui/screens/authorDetailsScreen.dart b/news-app/lib/ui/screens/authorDetailsScreen.dart new file mode 100644 index 00000000..f445f957 --- /dev/null +++ b/news-app/lib/ui/screens/authorDetailsScreen.dart @@ -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 createState() => _AuthorDetailsScreenState(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => AuthorDetailsScreen(authorId: arguments['authorId'])); + } +} + +class _AuthorDetailsScreenState extends State { + 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().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( + 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().getNewsById(newsId: newsId, langId: context.read().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}); + } +} diff --git a/news-app/lib/ui/screens/dashBoard/dashBoardScreen.dart b/news-app/lib/ui/screens/dashBoard/dashBoardScreen.dart new file mode 100644 index 00000000..eb37b5c8 --- /dev/null +++ b/news-app/lib/ui/screens/dashBoard/dashBoardScreen.dart @@ -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? 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 { + List fragments = []; + DateTime? currentBackPressTime; + int _selectedIndex = 0; + List iconList = []; + List itemName = []; + bool shouldPopScope = false; + + @override + void initState() { + homeScreenKey = GlobalKey(); + iconList = [ + Icons.home_rounded, + Icons.video_collection_rounded, + //Add only if Category Mode is enabled From Admin panel. + if (context.read().getCategoryMode() == "1") Icons.grid_view_rounded, + if (context.read().getRSSFeedMode() == "1") Icons.rss_feed_rounded, + Icons.settings_rounded + ]; + itemName = [ + 'homeLbl', + 'videosLbl', + if (context.read().getCategoryMode() == "1") 'categoryLbl', + if (context.read().getRSSFeedMode() == "1") 'rssFeed', + 'profile' + ]; + fragments = [ + HomeScreen(key: homeScreenKey), + const VideoScreen(), + //Add only if Category Mode is enabled From Admin panel. + if (context.read().getCategoryMode() == "1") const CategoryScreen(), + if (context.read().getRSSFeedMode() == "1") RSSFeedScreen(), + const ProfileScreen(), + ]; + if ((isShared != null && isShared == true) && routeSettingsName != null && newsSlug != null) initDynamicLinks(); + checkForPengingNotifications(); + checkMaintenanceMode(); + + super.initState(); + } + + void checkMaintenanceMode() { + if (context.read().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().getNewsById(newsId: notificationNewsId!, langId: context.read().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().state.id; + if (context.read().langList().isNotEmpty) langIdPass = context.read().langList().firstWhere((e) => e.code == langCodeShared).id; + UiUtils.rootNavigatorKey.currentContext?.read().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().getBreakingNews(langId: UiUtils.rootNavigatorKey.currentContext!.read().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 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().state.appTheme); //set UiOverlayStyle according to selected theme + return PopScope( + canPop: shouldPopScope, + onPopInvoked: onWillPop, + child: BlocConsumer( + listener: (context, state) { + if (state is Authenticated) { + Future.delayed(Duration.zero, () { + context.read().getBookmark(langId: context.read().state.id); + context.read().getLike(langId: context.read().state.id); + }); + } + }, + builder: (context, state) { + return Scaffold( + bottomNavigationBar: bottomBar(), + body: BlocBuilder( + builder: (context, connectivityStatus) { + if (connectivityStatus is ConnectivityDisconnected) { + return ErrorContainerWidget(errorMsg: UiUtils.getTranslatedLabel(context, 'internetmsg'), onRetry: () {}); + } else { + return IndexedStack(index: _selectedIndex, children: fragments); + } + }, + ), + ); + }, + ), + ); + } +} diff --git a/news-app/lib/ui/screens/filter/FilterBottomSheet.dart b/news-app/lib/ui/screens/filter/FilterBottomSheet.dart new file mode 100644 index 00000000..1553ca9a --- /dev/null +++ b/news-app/lib/ui/screens/filter/FilterBottomSheet.dart @@ -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 selectedCategories; + final List 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 createState() => _FilterBottomSheetState(); +} + +class _FilterBottomSheetState extends State { + late final ValueNotifier selectedFilterTabIndex = ValueNotifier(0); + late List selectedCategories; + late List selectedTags; + DateTime? selectedDate; + + List 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( + 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( + 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().hasMoreCategory()) { + context.read().getMoreCategory(langId: context.read().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( + 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().hasMoreTags()) { + context.read().getMoreTags(langId: context.read().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( + 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 = []; + selectedTags = []; + 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 showFilterBottomSheet({ + required BuildContext context, + required bool isCategoryModeON, + required NewsFilterData initialFilters, +}) async { + return await showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => FilterBottomSheet( + isCategoryModeON: isCategoryModeON, + initialFilters: initialFilters, + )); +} diff --git a/news-app/lib/ui/screens/filter/widgets/custom_date_selector.dart b/news-app/lib/ui/screens/filter/widgets/custom_date_selector.dart new file mode 100644 index 00000000..9fd0b764 --- /dev/null +++ b/news-app/lib/ui/screens/filter/widgets/custom_date_selector.dart @@ -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 { + 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 { + 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, + ), + ), + ), + ), + ); + }, + ), + ), + ], + ); + } +} diff --git a/news-app/lib/ui/screens/filter/widgets/duration_filter_widget.dart b/news-app/lib/ui/screens/filter/widgets/duration_filter_widget.dart new file mode 100644 index 00000000..16e1917e --- /dev/null +++ b/news-app/lib/ui/screens/filter/widgets/duration_filter_widget.dart @@ -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 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 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 filters; + final void Function(DurationFilter? filter)? onSelected; + const DurationFilterWidget({super.key, required this.filters, this.onSelected, this.selectedFilter}); + + @override + State createState() => _DurationFilterWidgetState(); +} + +class _DurationFilterWidgetState extends State { + 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)), + ), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/introSlider.dart b/news-app/lib/ui/screens/introSlider.dart new file mode 100644 index 00000000..241dd86a --- /dev/null +++ b/news-app/lib/ui/screens/introSlider.dart @@ -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 with TickerProviderStateMixin { + PageController pageController = PageController(); + + int currentIndex = 0; + + late List 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().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.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))); + } +} diff --git a/news-app/lib/ui/screens/languageList.dart b/news-app/lib/ui/screens/languageList.dart new file mode 100644 index 00000000..ba073036 --- /dev/null +++ b/news-app/lib/ui/screens/languageList.dart @@ -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; + return CupertinoPageRoute(builder: (_) => LanguageList(from: arguments['from'])); + } +} + +class LanguageListState extends State { + 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().getLanguage(); + }); + } + + void setLatitudeLongitude() { + latitude = SettingsLocalDataRepository().getLocationCityValues().first; + longitude = SettingsLocalDataRepository().getLocationCityValues().last; + } + + Widget getLangList() { + return BlocBuilder(builder: (context, stateLocale) { + return BlocBuilder(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( + bloc: context.read(), + listener: (context, state) { + if (state is LanguageJsonFetchSuccess) { + final langId = selLanId ?? context.read().state.id; + + UiUtils.setDynamicStringValue( + context.read().state.languageCode, + jsonEncode(state.languageJson), + ).then((_) { + // Update languageId + homeScreenKey?.currentState?.languageId = langId; + + // Fetch general data + context.read().getOtherPage(langId: langId); + context.read().getSection(langId: langId, latitude: latitude, longitude: longitude); + context.read().getLiveStream(langId: langId); + context.read().getGeneralNews(langId: langId, latitude: latitude, longitude: longitude); + context.read().getVideo(langId: langId, latitude: latitude, longitude: longitude); + context.read().getCategory(langId: langId); + + // Conditional based on config + final config = context.read(); + if (config.getWeatherMode() == "1") { + homeScreenKey?.currentState?.getWeatherData(); + } + if (config.getRSSFeedMode() == "1") { + context.read().getRSSFeed(langId: langId); + } + if (config.getBreakingNewsMode() == "1") { + context.read().getBreakingNews(langId: langId); + } + + // If user is logged in + final auth = context.read(); + if (auth.getUserId() != "0") { + context.read().getLike(langId: langId); + context.read().getBookmark(langId: langId); + updateUserLanguageWithFCMid(); + } + }); + + if (widget.from != null && widget.from == "firstLogin" && context.read().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().state.languageCode != selLanCode) { + context.read().changeLanguage(selLanCode!, selLanId!, selLanRTL!); + context.read().getLanguageJson(lanCode: selLanCode!); + } else { + if (widget.from != null && widget.from == "firstLogin" && context.read().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().getSettings().token.trim(); + + if (currentFCMId.isEmpty) { + final newToken = await FirebaseMessaging.instance.getToken(); + if (newToken != null) { + currentFCMId = newToken; + context.read().registerToken(fcmId: currentFCMId, context: context); + } + } else { + context.read().registerToken(fcmId: currentFCMId, context: context); + } + + context.read().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()); + } +} diff --git a/news-app/lib/ui/screens/maintenanceScreen.dart b/news-app/lib/ui/screens/maintenanceScreen.dart new file mode 100644 index 00000000..85986002 --- /dev/null +++ b/news-app/lib/ui/screens/maintenanceScreen.dart @@ -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 { + @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( + 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()); + }, + ), + )); + } +} diff --git a/news-app/lib/ui/screens/splashScreen.dart b/news-app/lib/ui/screens/splashScreen.dart new file mode 100644 index 00000000..7aee602d --- /dev/null +++ b/news-app/lib/ui/screens/splashScreen.dart @@ -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 with TickerProviderStateMixin { + @override + void initState() { + super.initState(); + fetchAppConfigurations(); + } + + fetchAppConfigurations() { + context.read().fetchAppConfiguration(); + } + + fetchLanguages({required AppConfigurationFetchSuccess state}) async { + String currentLanguage = Hive.box(settingsBoxKey).get(currentLanguageCodeKey) ?? ""; + if (currentLanguage == "" && state.appConfiguration.defaultLangDataModel != null) { + context + .read() + .changeLanguage(state.appConfiguration.defaultLangDataModel!.code!, state.appConfiguration.defaultLangDataModel!.id!, state.appConfiguration.defaultLangDataModel!.isRTL!); + context.read().fetchCurrentLanguageAndLabels(state.appConfiguration.defaultLangDataModel!.code!); + } else { + context.read().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 navigationPage() async { + Future.delayed(const Duration(seconds: 4), () async { + final currentSettings = context.read().state.settingsModel; + if (context.read().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( + bloc: context.read(), + listener: (context, state) { + if (state is AppConfigurationFetchSuccess) { + fetchLanguages(state: state); + } + }, + builder: (context, state) { + return BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is LanguageJsonFetchSuccess) { + navigationPage(); + context.read().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)); +} diff --git a/news-app/lib/ui/styles/appTheme.dart b/news-app/lib/ui/styles/appTheme.dart new file mode 100644 index 00000000..68ad13b7 --- /dev/null +++ b/news-app/lib/ui/styles/appTheme.dart @@ -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), +}; diff --git a/news-app/lib/ui/styles/colors.dart b/news-app/lib/ui/styles/colors.dart new file mode 100644 index 00000000..1e4610b3 --- /dev/null +++ b/news-app/lib/ui/styles/colors.dart @@ -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); diff --git a/news-app/lib/ui/widgets/NewsItem.dart b/news-app/lib/ui/widgets/NewsItem.dart new file mode 100644 index 00000000..468275c8 --- /dev/null +++ b/news-app/lib/ui/widgets/NewsItem.dart @@ -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 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 { + late BannerAd bannerAd; + + @override + void initState() { + super.initState(); + if (context.read().getInAppAdsMode() == "1" && + (context.read().getAdsType() != "unity" || context.read().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 tagList, required List 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( + bloc: context.read(), + builder: (context, bookmarkState) { + bool isBookmark = context.read().isNewsBookmark(widget.model.id!); + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateBookmarkStatusSuccess) { + (state.wasBookmarkNewsProcess) ? context.read().addBookmarkNews(state.news) : context.read().removeBookmarkNews(state.news); + } + }), + builder: (context, state) { + return InkWell( + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateBookmarkStatusInProgress) return; + context.read().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().isNewsLikeAndDisLike(widget.model.id!); + + return BlocProvider( + create: (context) => UpdateLikeAndDisLikeStatusCubit(LikeAndDisLikeRepository()), + child: BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is LikeAndDisLikeFetchSuccess) { + isLike = context.read().isNewsLikeAndDisLike(widget.model.id!); + } + }), + builder: (context, likeAndDislikeState) { + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateLikeAndDisLikeStatusSuccess) { + context.read().getLike(langId: context.read().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().getUserId() != "0") { + if (state is UpdateLikeAndDisLikeStatusInProgress) { + return; + } + context.read().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().getInAppAdsMode() == "1" && + (context.read().getAdsType() != "unity" || context.read().getIOSAdsType() != "unity")) + nativeAdsShow(context: context, index: widget.index), + NewsCard( + newsDetail: widget.model, + showTags: true, + onTap: () { + List 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(); + } +} diff --git a/news-app/lib/ui/widgets/SnackBarWidget.dart b/news-app/lib/ui/widgets/SnackBarWidget.dart new file mode 100644 index 00000000..23a99dbb --- /dev/null +++ b/news-app/lib/ui/widgets/SnackBarWidget.dart @@ -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, + ), + ); +} diff --git a/news-app/lib/ui/widgets/adSpaces.dart b/news-app/lib/ui/widgets/adSpaces.dart new file mode 100644 index 00000000..5fadf959 --- /dev/null +++ b/news-app/lib/ui/widgets/adSpaces.dart @@ -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), + ), + ])), + ); + } +} diff --git a/news-app/lib/ui/widgets/breakingNewsItem.dart b/news-app/lib/ui/widgets/breakingNewsItem.dart new file mode 100644 index 00000000..fbf22458 --- /dev/null +++ b/news-app/lib/ui/widgets/breakingNewsItem.dart @@ -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 breakNewsList; + + const BreakNewsItem({super.key, required this.model, required this.index, required this.breakNewsList}); + + @override + BreakNewsItemState createState() => BreakNewsItemState(); +} + +class BreakNewsItemState extends State { + late BannerAd _bannerAd; + @override + void initState() { + if (context.read().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().getInAppAdsMode() == "1") nativeAdsShow(), + InkWell( + child: Column( + children: [ + 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 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().bannerId() != "") { + _bannerAd = BannerAd( + adUnitId: context.read().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().getInAppAdsMode() == "1" && + context.read().checkAdsType() != null && + context.read().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().checkAdsType() == "google" && (context.read().bannerId() != "") ? bannerAdsShow() : SizedBox.shrink())); + } else { + return const SizedBox.shrink(); + } + } + + @override + Widget build(BuildContext context) { + return newsData(); + } +} diff --git a/news-app/lib/ui/widgets/breakingVideoItem.dart b/news-app/lib/ui/widgets/breakingVideoItem.dart new file mode 100644 index 00000000..602f9060 --- /dev/null +++ b/news-app/lib/ui/widgets/breakingVideoItem.dart @@ -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 { + Widget videoData(BreakingNewsModel video) { + return Padding( + padding: const EdgeInsets.only(top: 15.0), + child: Column( + children: [ + 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); + } +} diff --git a/news-app/lib/ui/widgets/customAppBar.dart b/news-app/lib/ui/widgets/customAppBar.dart new file mode 100644 index 00000000..60b04815 --- /dev/null +++ b/news-app/lib/ui/widgets/customAppBar.dart @@ -0,0 +1,45 @@ +import 'dart:core'; +import 'package:flutter/material.dart'; +import 'package:news/ui/widgets/customBackBtn.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; + +class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { + final double height; + final double? horizontalPad; + final bool isBackBtn, isConvertText; + final String label; + final List? actionWidget; + + const CustomAppBar({Key? key, required this.height, required this.isBackBtn, required this.label, required this.isConvertText, this.horizontalPad, this.actionWidget}) : super(key: key); + + @override + Widget build(BuildContext context) { + return PreferredSize( + preferredSize: Size(double.infinity, height), + child: UiUtils.applyBoxShadow( + context: context, + child: AppBar( + leading: (isBackBtn) ? CustomBackButton(horizontalPadding: horizontalPad ?? 15) : null, + actions: actionWidget, + titleSpacing: 0.0, + automaticallyImplyLeading: false, + centerTitle: false, + elevation: 0, + backgroundColor: Colors.transparent, + title: Padding( + padding: EdgeInsetsDirectional.only(start: isBackBtn ? 0 : 20), + child: !isConvertText + ? Text(label, style: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5)) + : CustomTextLabel( + text: label, + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5), + ), + ), + ), + )); + } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); +} diff --git a/news-app/lib/ui/widgets/customBackBtn.dart b/news-app/lib/ui/widgets/customBackBtn.dart new file mode 100644 index 00000000..59cff78e --- /dev/null +++ b/news-app/lib/ui/widgets/customBackBtn.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:news/utils/uiUtils.dart'; + +class CustomBackButton extends StatelessWidget { + final Function? onTap; + final double? horizontalPadding; + + const CustomBackButton({super.key, this.onTap, this.horizontalPadding}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: horizontalPadding ?? 0), + child: InkWell( + onTap: () => onTap ?? Navigator.of(context).pop(), + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + child: Icon(Icons.arrow_back, color: UiUtils.getColorScheme(context).primaryContainer))); + } +} diff --git a/news-app/lib/ui/widgets/customTextBtn.dart b/news-app/lib/ui/widgets/customTextBtn.dart new file mode 100644 index 00000000..76208ecc --- /dev/null +++ b/news-app/lib/ui/widgets/customTextBtn.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/styles/colors.dart'; + +class CustomTextButton extends StatelessWidget { + final Function onTap; + final Color? color; + final String? text; + final ButtonStyle? buttonStyle; + final Widget? textWidget; + + const CustomTextButton({super.key, required this.onTap, this.color, this.text, this.buttonStyle, this.textWidget}); + + @override + Widget build(BuildContext context) { + return TextButton( + style: buttonStyle ?? ButtonStyle(overlayColor: WidgetStateProperty.all(Colors.transparent), foregroundColor: WidgetStateProperty.all(darkSecondaryColor)), + onPressed: () { + onTap(); + }, + child: textWidget ?? CustomTextLabel(text: text!, textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(color: color, fontWeight: FontWeight.normal)), + ); + } +} diff --git a/news-app/lib/ui/widgets/customTextLabel.dart b/news-app/lib/ui/widgets/customTextLabel.dart new file mode 100644 index 00000000..11ab9fcd --- /dev/null +++ b/news-app/lib/ui/widgets/customTextLabel.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/languageJsonCubit.dart'; + +class CustomTextLabel extends StatelessWidget { + final String text; + final TextStyle? textStyle; + final TextAlign? textAlign; + final int? maxLines; + final TextOverflow? overflow; + final bool? softWrap; + + const CustomTextLabel({super.key, required this.text, this.textStyle, this.textAlign, this.maxLines, this.overflow, this.softWrap}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Text(context.read().getTranslatedLabels(text), maxLines: maxLines, overflow: overflow, softWrap: softWrap, style: textStyle, textAlign: textAlign); + }, + ); + } +} diff --git a/news-app/lib/ui/widgets/errorContainerWidget.dart b/news-app/lib/ui/widgets/errorContainerWidget.dart new file mode 100644 index 00000000..3d10e833 --- /dev/null +++ b/news-app/lib/ui/widgets/errorContainerWidget.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:lottie/lottie.dart'; +import 'package:news/ui/widgets/customTextBtn.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; + +class ErrorContainerWidget extends StatelessWidget { + final String errorMsg; + final Function onRetry; + const ErrorContainerWidget({super.key, required this.errorMsg, required this.onRetry}); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.center, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + (errorMsg.contains(UiUtils.getTranslatedLabel(context, 'internetmsg'))) + ? Container( + width: MediaQuery.of(context).size.width * (0.7), + margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top + MediaQuery.of(context).size.height * (0.05)), + height: MediaQuery.of(context).size.height * (0.4), + child: Lottie.asset("assets/animations/noInternet.json")) + : SizedBox(height: MediaQuery.of(context).size.height * (0.2)), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: CustomTextLabel( + text: errorMsg, + textAlign: TextAlign.center, + maxLines: 4, + overflow: TextOverflow.ellipsis, + textStyle: TextStyle(color: Theme.of(context).colorScheme.primaryContainer, fontSize: 16, fontWeight: FontWeight.w300))), + SizedBox(height: MediaQuery.of(context).size.height * (0.035)), + CustomTextButton( + onTap: () { + onRetry.call(); + }, + buttonStyle: ButtonStyle( + backgroundColor: WidgetStateProperty.all(UiUtils.getColorScheme(context).primaryContainer), + shape: WidgetStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)))), + textWidget: CustomTextLabel( + text: 'RetryLbl', + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).surface, fontWeight: FontWeight.w600, fontSize: 21, letterSpacing: 0.6), + )), + SizedBox(height: MediaQuery.of(context).size.height * (0.15)), + ], + )); + } +} diff --git a/news-app/lib/ui/widgets/loadingScreen.dart b/news-app/lib/ui/widgets/loadingScreen.dart new file mode 100644 index 00000000..100834a3 --- /dev/null +++ b/news-app/lib/ui/widgets/loadingScreen.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/breakingNewsCubit.dart'; +import 'package:news/cubits/languageCubit.dart'; +import 'package:news/cubits/slugNewsCubit.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/utils/uiUtils.dart'; + +//TODO: if change in API calls or variables, change the same in Dashboard screen > initDynamicLinks() +class LoadingScreen extends StatefulWidget { + String routeSettingsName; + String newsSlug; + + LoadingScreen({required this.routeSettingsName, required this.newsSlug, Key? key}) : super(key: key); + + @override + _LoadingScreenState createState() => _LoadingScreenState(); +} + +class _LoadingScreenState extends State { + ValueNotifier isLoading = ValueNotifier(true); + + @override + void initState() { + super.initState(); + + context.read().getLanguage().then( + (value) { + fetchData(); + }, + ); + isLoading.addListener(() { + if (!isLoading.value) { + Navigator.pop(context); + } + }); + } + + Future fetchData() async { + await Future.delayed(Duration(seconds: 2)); // Simulate API delay + if (widget.routeSettingsName.contains('/news/')) { + String langCodeShared = widget.routeSettingsName.split("/")[1]; + String? langIdPass = UiUtils.rootNavigatorKey.currentContext!.read().state.id; + if (context.read().langList().isNotEmpty) langIdPass = context.read().langList().firstWhere((e) => e.code == langCodeShared).id; + UiUtils.rootNavigatorKey.currentContext?.read().getSlugNews(langId: langIdPass ?? "0", newsSlug: widget.newsSlug).then((value) { + if (value[DATA] != null) { + NewsModel? model = (value[DATA] as List).map((e) => NewsModel.fromJson(e)).toList().first; + Navigator.popAndPushNamed(context, Routes.newsDetails, + arguments: {"model": model, "slug": widget.newsSlug, "isFromBreak": widget.routeSettingsName.contains('/breaking-news/') ? true : false, "fromShowMore": false}); + } else { + setState(() { + isLoading.value = false; + }); + } + }); + } else if (widget.routeSettingsName.contains('/breaking-news/')) { + UiUtils.rootNavigatorKey.currentContext?.read().getBreakingNews(langId: UiUtils.rootNavigatorKey.currentContext!.read().state.id).then((value) { + BreakingNewsModel? brModel = value[0]; + Navigator.of(context).pushReplacementNamed(Routes.newsDetails, arguments: {"breakModel": brModel, "slug": widget.newsSlug, "isFromBreak": true, "fromShowMore": false}); + }); + } + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: isLoading, + builder: (context, value, child) { + return Scaffold(body: Center(child: CircularProgressIndicator())); + }); + } +} diff --git a/news-app/lib/ui/widgets/networkImage.dart b/news-app/lib/ui/widgets/networkImage.dart new file mode 100644 index 00000000..aafde7c7 --- /dev/null +++ b/news-app/lib/ui/widgets/networkImage.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:news/utils/uiUtils.dart'; + +class CustomNetworkImage extends StatelessWidget { + final String networkImageUrl; + final double? width, height; + final BoxFit? fit; + final bool? isVideo; + final Widget? errorBuilder; + + const CustomNetworkImage({super.key, required this.networkImageUrl, this.width, this.height, this.fit, this.isVideo, this.errorBuilder}); + + @override + Widget build(BuildContext context) { + return FadeInImage.assetNetwork( + fadeInDuration: const Duration(milliseconds: 150), + fadeOutCurve: Curves.bounceOut, + image: networkImageUrl, + width: width ?? 100, + height: height ?? 100, + fit: fit ?? BoxFit.cover, + imageErrorBuilder: (context, error, stackTrace) { + return Image.asset(UiUtils.getPlaceholderPngPath(), width: width ?? 100, height: height ?? 100); + }, + placeholderErrorBuilder: (context, error, stackTrace) { + return Image.asset(UiUtils.getPlaceholderPngPath(), width: width ?? 100, height: height ?? 100); + }, + placeholderFit: fit ?? BoxFit.cover, + placeholder: UiUtils.getPlaceholderPngPath()); + } +} diff --git a/news-app/lib/ui/widgets/newsCard.dart b/news-app/lib/ui/widgets/newsCard.dart new file mode 100644 index 00000000..d955ebbd --- /dev/null +++ b/news-app/lib/ui/widgets/newsCard.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/Bookmark/UpdateBookmarkCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/utils/uiUtils.dart'; + +class NewsCard extends StatefulWidget { + NewsModel newsDetail; + bool showViews; + bool showTags; + Function()? onTap; + NewsCard({super.key, required this.newsDetail, this.showViews = false, this.showTags = false, required this.onTap}); + + @override + State createState() => NewsCardState(); +} + +class NewsCardState extends State { + List tagList = []; + List tagId = []; + late NewsModel newsData; + + @override + void initState() { + super.initState(); + + newsData = widget.newsDetail; + + if (newsData.tagName! != "") { + final tagName = newsData.tagName!; + tagList = tagName.split(','); + } + + if (newsData.tagId != null && newsData.tagId! != "") { + tagId = newsData.tagId!.split(","); + } + } + + @override + Widget build(BuildContext context) { + return Container( + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration(color: UiUtils.getColorScheme(context).surface, borderRadius: BorderRadius.circular(8)), + child: GestureDetector( + onTap: widget.onTap, + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8, + children: [setImage(), setContent()], + ), + (widget.showViews) ? setBookmarkIconButton() : SizedBox.shrink() + ], + ), + ), + ); + } + + Widget setContent() { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8, + children: [ + //title + CustomTextLabel(text: widget.newsDetail.title!, textStyle: TextStyle(fontWeight: FontWeight.bold)), + Row( + spacing: 10, + children: [(widget.newsDetail.date != null && widget.newsDetail.date!.isNotEmpty) ? setDate() : SizedBox.shrink(), (widget.showViews) ? setViews() : SizedBox.shrink()], + ), + ], + ), + ); + } + + Widget setBookmarkIconButton() { + return BlocListener( + listener: (context, state) { + if (state is UpdateBookmarkStatusSuccess) { + context.read().getBookmark(langId: context.read().state.id); + } + }, + child: Positioned.directional( + textDirection: Directionality.of(context), + end: 10, + top: 10, + child: GestureDetector( + onTap: () { + context.read().setBookmarkNews(news: widget.newsDetail, status: "0"); + }, + child: Container( + height: 30, + width: 30, + decoration: BoxDecoration(borderRadius: const BorderRadius.all(Radius.circular(15)), color: UiUtils.getColorScheme(context).surface), + child: Icon(Icons.bookmark_outlined, color: UiUtils.getColorScheme(context).onPrimary, size: 20)))), + ); + } + + Widget setViews() { + return Row(spacing: 8, children: [ + Icon(Icons.remove_red_eye_rounded, size: 20), + CustomTextLabel(text: "${widget.newsDetail.totalViews} ${UiUtils.getTranslatedLabel(context, 'viewsLbl')}", textStyle: TextStyle(fontSize: 12)) + ]); + } + + Widget setDate() { + return Row( + spacing: 8, + children: [Icon(Icons.calendar_month_rounded, size: 20), CustomTextLabel(text: UiUtils.formatDate(widget.newsDetail.date ?? ''), textStyle: TextStyle(fontSize: 12))], + ); + } + + Widget setTags() { + return Positioned.directional( + textDirection: Directionality.of(context), + bottom: 15.0, + start: 7.0, + child: widget.newsDetail.tagName! != "" + ? SizedBox( + height: 30.0, + child: ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: tagList.length, + itemBuilder: (context, index) { + return Padding( + padding: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 5.5), + child: InkWell( + child: Container( + height: 25.0, + width: 65, + alignment: Alignment.center, + padding: const EdgeInsetsDirectional.only(start: 3.0, end: 3.0, top: 1.0, bottom: 1.0), + decoration: BoxDecoration(borderRadius: const BorderRadius.all(Radius.circular(15)), color: UiUtils.getColorScheme(context).secondary.withOpacity(0.85)), + child: CustomTextLabel( + text: tagList[index], + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontSize: 12), + overflow: TextOverflow.ellipsis, + softWrap: true)), + onTap: () async { + Navigator.of(context).pushNamed(Routes.tagScreen, arguments: {"tagId": tagId[index], "tagName": tagList[index]}); + }, + ), + ); + })) + : const SizedBox.shrink()); + } + + Widget setImage() { + return Stack( + children: [ + Container( + height: 192, + color: borderColor, + child: ShaderMask( + shaderCallback: (rect) => + LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [darkSecondaryColor.withOpacity(0.6), darkSecondaryColor.withOpacity(0.6)]).createShader(rect), + blendMode: BlendMode.darken, + child: Container( + color: primaryColor.withAlpha(5), + width: double.maxFinite, + height: MediaQuery.of(context).size.height / 3.3, + child: CustomNetworkImage(width: double.maxFinite, networkImageUrl: widget.newsDetail.image ?? ''), + ))), + //tags + if (widget.showTags) setTags() + ], + ); + } +} diff --git a/news-app/lib/ui/widgets/shimmerNewsList.dart b/news-app/lib/ui/widgets/shimmerNewsList.dart new file mode 100644 index 00000000..4ebe5dae --- /dev/null +++ b/news-app/lib/ui/widgets/shimmerNewsList.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +class ShimmerNewsList extends StatelessWidget { + bool isNews = true; + + ShimmerNewsList({Key? key, required this.isNews}) : super(key: key); + + @override + Widget build(BuildContext context) { + //videos,Subcategory,Bookmarks & TagsPage + return Shimmer.fromColors( + baseColor: Colors.grey.withOpacity(0.6), + highlightColor: Colors.grey, + child: SingleChildScrollView( + padding: const EdgeInsetsDirectional.only(top: 0.0), + child: ListView.builder( + shrinkWrap: true, + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: (_, i) => Padding( + padding: EdgeInsetsDirectional.only(top: i == 0 ? 0 : 15.0), + child: Padding( + padding: EdgeInsetsDirectional.only(top: MediaQuery.of(context).size.height / 35.0, start: 15.0, end: 15.0), + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: Container( + height: MediaQuery.of(context).size.height / 4.2, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Colors.grey.withOpacity(0.6)), + )), + (!isNews) + ? const SizedBox.shrink() + : Positioned.directional( + textDirection: Directionality.of(context), + bottom: 10.0, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: MediaQuery.of(context).size.width / 1.15, + height: 10.0, + margin: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Colors.grey.withOpacity(0.6)), + ), + Container( + width: MediaQuery.of(context).size.width / 2.0, + height: 10.0, + margin: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Colors.grey.withOpacity(0.6)), + ), + ], + ), + ), + ], + ), + )), + itemCount: 6, + ), + )); + } +} + +Widget iconButtons({required BuildContext context}) { + return Container( + width: MediaQuery.of(context).size.width / 20.0, + height: 20.0, + padding: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15.0), color: Colors.grey.withOpacity(0.6)), + ); +} diff --git a/news-app/lib/ui/widgets/videoItem.dart b/news-app/lib/ui/widgets/videoItem.dart new file mode 100644 index 00000000..ee6e810c --- /dev/null +++ b/news-app/lib/ui/widgets/videoItem.dart @@ -0,0 +1,215 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/strings.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/Bookmark/UpdateBookmarkCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Bookmark/bookmarkRepository.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:html/parser.dart'; + +class VideoItem extends StatefulWidget { + final NewsModel model; + + VideoItem({super.key, required this.model}); + + @override + VideoItemState createState() => VideoItemState(); +} + +class VideoItemState extends State { + String formattedDescription = ""; + + @override + void initState() { + super.initState(); + } + + void dispose() async { + super.dispose(); + } + + void checkAndSetDescription({required String descr}) { + formattedDescription = ""; + + // Parse HTML and extract plain text + formattedDescription = parse(descr).body?.text ?? ''; + } + + Widget videoData(NewsModel video) { + checkAndSetDescription(descr: video.shortDesc ?? video.desc ?? ''); + return Padding( + padding: EdgeInsets.zero, + child: Column( + children: [ + ClipRRect( + child: Stack( + alignment: Alignment.center, + children: [ + ShaderMask( + shaderCallback: (Rect bounds) { + return LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.transparent, darkSecondaryColor]).createShader(bounds); + }, + blendMode: BlendMode.darken, + child: CustomNetworkImage( + networkImageUrl: (video.contentType == 'video_youtube' && video.contentValue!.isNotEmpty) + ? 'https://img.youtube.com/vi/${YoutubePlayer.convertUrlToId(video.contentValue!)!}/0.jpg' + : video.image!, + fit: BoxFit.cover, + width: double.maxFinite, + height: (Platform.isIOS) ? MediaQuery.of(context).size.height / 1.29 : MediaQuery.of(context).size.height / 1.25, + isVideo: true)), + CircleAvatar(radius: 30, backgroundColor: Colors.black45, child: Icon(Icons.play_arrow, size: 40, color: Colors.white)), + Positioned.directional( + textDirection: Directionality.of(context), + bottom: 25.0, + start: 0, + height: MediaQuery.of(context).size.height / 8.4, + width: MediaQuery.of(context).size.width / 1.3, + child: Padding( + padding: const EdgeInsets.only(left: 10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Align( + alignment: Alignment.centerLeft, + child: CustomTextLabel( + text: video.title!, + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: secondaryColor, fontWeight: FontWeight.bold), + maxLines: 2, + overflow: TextOverflow.ellipsis)), + Align( + alignment: Alignment.centerLeft, + child: CustomTextLabel( + text: formattedDescription.trim(), textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: secondaryColor), maxLines: 3, overflow: TextOverflow.ellipsis), + ) + ], + ), + )), + Positioned.directional( + textDirection: Directionality.of(context), + bottom: 10.0, + end: 10.0, + height: MediaQuery.of(context).size.height / 6, + width: MediaQuery.of(context).size.width / 8, + child: Container( + padding: const EdgeInsets.only(bottom: 5.0), + child: Column( + children: [ + if (video.sourceType == NEWS) //user can like or bookmark videos of source type news, and not Breaking News + Column(children: [ + likeButton(), + const SizedBox(height: 15), + BlocProvider( + create: (context) => UpdateBookmarkStatusCubit(BookmarkRepository()), + child: BlocBuilder( + bloc: context.read(), + builder: (context, bookmarkState) { + bool isBookmark = context.read().isNewsBookmark(video.id!); + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateBookmarkStatusSuccess) { + (state.wasBookmarkNewsProcess) ? context.read().addBookmarkNews(state.news) : context.read().removeBookmarkNews(state.news); + setState(() {}); + } + }), + builder: (context, state) { + return InkWell( + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateBookmarkStatusInProgress) return; + context.read().setBookmarkNews(news: video, status: (isBookmark) ? "0" : "1"); + } else { + UiUtils.loginRequired(context); + } + }, + child: state is UpdateBookmarkStatusInProgress + ? SizedBox(height: 15, width: 15, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)) + : Icon(isBookmark ? Icons.bookmark_added_rounded : Icons.bookmark_add_outlined, color: secondaryColor)); + }); + }), + ), + const SizedBox(height: 15), + ]), + InkWell( + onTap: () async { + (await InternetConnectivity.isNetworkAvailable()) + ? UiUtils.shareNews(context: context, slug: video.slug ?? "", title: video.title!, isVideo: true, videoId: video.id!, isBreakingNews: false, isNews: false) + : showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + }, + splashColor: Colors.transparent, + child: const Icon(Icons.share_rounded, color: secondaryColor)) + ], + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return (widget.model.contentValue!.isNotEmpty) ? videoData(widget.model) : const SizedBox.shrink(); + } + + Widget likeButton() { + bool isLike = context.read().isNewsLikeAndDisLike(widget.model.newsId!); + + return BlocProvider( + create: (context) => UpdateLikeAndDisLikeStatusCubit(LikeAndDisLikeRepository()), + child: BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is LikeAndDisLikeFetchSuccess) { + isLike = context.read().isNewsLikeAndDisLike(widget.model.newsId!); + } + }), + builder: (context, likeAndDislikeState) { + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateLikeAndDisLikeStatusSuccess) { + context.read().getLike(langId: context.read().state.id); + } + }), + builder: (context, state) { + return InkWell( + splashColor: Colors.transparent, + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateLikeAndDisLikeStatusInProgress) { + return; + } + context.read().setLikeAndDisLikeNews( + news: widget.model, + status: (isLike) ? "0" : "1", + ); + } else { + UiUtils.loginRequired(context); + } + }, + child: Container( + child: (state is UpdateLikeAndDisLikeStatusInProgress) + ? SizedBox(height: 15, width: 15, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)) + : ((isLike) ? const Icon(Icons.thumb_up_alt, size: 25, color: secondaryColor) : const Icon(Icons.thumb_up_off_alt, size: 25, color: secondaryColor)))); + }); + })); + } +} diff --git a/news-app/lib/ui/widgets/videoPlayContainer.dart b/news-app/lib/ui/widgets/videoPlayContainer.dart new file mode 100644 index 00000000..f554e72f --- /dev/null +++ b/news-app/lib/ui/widgets/videoPlayContainer.dart @@ -0,0 +1,86 @@ +import 'package:flick_video_player/flick_video_player.dart'; +import 'package:flutter/material.dart'; +import 'package:video_player/video_player.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; + +class VideoPlayContainer extends StatefulWidget { + final String contentType, contentValue; + final bool autoPlay; + + Duration lastPosition; + + VideoPlayContainer({super.key, required this.contentType, required this.contentValue, this.autoPlay = true, this.lastPosition = Duration.zero}); + @override + VideoPlayContainerState createState() => VideoPlayContainerState(); +} + +class VideoPlayContainerState extends State { + bool playVideo = false; + + FlickManager? flickManager; + YoutubePlayerController? _yc; + + late VideoPlayerController _controller; + late final WebViewController webViewController; + + @override + void initState() { + super.initState(); + initialisePlayer(); + } + + @override + void dispose() async { + try { + if (widget.contentType == "video_upload") { + // pause only if controller still valid + if (_controller.value.isInitialized && _controller.value.isPlaying) { + _controller.pause(); + } + + // exit fullscreen safely + flickManager?.flickControlManager?.exitFullscreen(); + + // dispose only the flick manager + flickManager?.dispose(); + flickManager = null; + } else if (widget.contentValue == "video_youtube") { + _yc?.dispose(); + } + } catch (e) {} + + super.dispose(); // always last + } + + initialisePlayer() { + if (widget.contentValue != "") { + if (widget.contentType == "video_upload") { + _controller = VideoPlayerController.networkUrl(Uri.parse(widget.contentValue)); + flickManager = FlickManager( + videoPlayerController: _controller, + autoPlay: widget.autoPlay, + onVideoEnd: () { + widget.lastPosition = Duration.zero; + }); + } else if (widget.contentType == "video_youtube" || widget.contentType == "url_youtube") { + _yc = YoutubePlayerController(initialVideoId: YoutubePlayer.convertUrlToId(widget.contentValue) ?? "", flags: YoutubePlayerFlags(autoPlay: widget.autoPlay)); + } else if (widget.contentType == "video_other") { + webViewController = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..loadRequest(Uri.parse(widget.contentValue)); + } + } + } + + @override + Widget build(BuildContext context) { + return (widget.contentType == "video_upload") + ? FlickVideoPlayer(flickManager: flickManager!) + : (widget.contentType == "video_youtube" || widget.contentType == "url_youtube") + ? YoutubePlayer(controller: _yc!, showVideoProgressIndicator: true, progressIndicatorColor: Theme.of(context).primaryColor) + : widget.contentType == "video_other" + ? Center(child: WebViewWidget(controller: webViewController)) + : const SizedBox.shrink(); + } +} diff --git a/news-app/lib/ui/widgets/videoShimmer.dart b/news-app/lib/ui/widgets/videoShimmer.dart new file mode 100644 index 00000000..11296a79 --- /dev/null +++ b/news-app/lib/ui/widgets/videoShimmer.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +videoShimmer(BuildContext context) { + return Shimmer.fromColors( + baseColor: Colors.grey.withOpacity(0.6), + highlightColor: Colors.grey, + child: SingleChildScrollView( + padding: const EdgeInsetsDirectional.only(top: 15.0), + child: ListView.builder( + shrinkWrap: true, + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: (_, i) => Padding( + padding: EdgeInsetsDirectional.only(top: i == 0 ? 0 : 15.0), + child: Stack(children: [Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Colors.grey.withOpacity(0.6)), height: 215.0)])), + itemCount: 6), + )); +} diff --git a/news-app/lib/utils/ErrorMessageKeys.dart b/news-app/lib/utils/ErrorMessageKeys.dart new file mode 100644 index 00000000..cbe4da5a --- /dev/null +++ b/news-app/lib/utils/ErrorMessageKeys.dart @@ -0,0 +1,7 @@ +class ErrorMessageKeys { + static const String defaultErrorMessage = "Something went wrong. Please try again later!"; + static const String noInternet = "No internet"; + static const String serverDownMessage = "Our service is temporarily unavailable due to maintenance or high traffic. Please check back in a little while."; + static const String noDataMessage = "No Data Found"; + static const String requestAgainMessage = "The Request cannot be fulfilled for now.\nPlease Try again later"; +} diff --git a/news-app/lib/utils/api.dart b/news-app/lib/utils/api.dart new file mode 100644 index 00000000..044587d3 --- /dev/null +++ b/news-app/lib/utils/api.dart @@ -0,0 +1,165 @@ +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:news/data/repositories/Auth/authLocalDataSource.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/constant.dart'; + +class ApiMessageAndCodeException implements Exception { + final String errorMessage; + + ApiMessageAndCodeException({required this.errorMessage}); + + Map toError() => {"message": errorMessage}; + + @override + String toString() => errorMessage; +} + +class ApiException implements Exception { + String errorMessage; + + ApiException(this.errorMessage); + + @override + String toString() { + return errorMessage; + } +} + +class Api { + static String getToken() { + String token = AuthLocalDataSource().getJWTtoken(); + return (token.trim().isNotEmpty) ? token : ""; + } + + static Map get headers => {"Authorization": 'Bearer ${getToken()}'}; + + //all apis list + static String getUserSignUpApi = 'user_signup'; + static String getNewsApi = 'get_news'; + static String getSettingApi = 'get_settings'; + static String getCatApi = 'get_category'; + static String setBookmarkApi = 'set_bookmark'; + static String getBookmarkApi = 'get_bookmark'; + static String setCommentApi = 'set_comment'; + static String getCommentByNewsApi = 'get_comment_by_news'; + static String getBreakingNewsApi = 'get_breaking_news'; + static String setUpdateProfileApi = 'update_profile'; + static String setRegisterToken = 'register_token'; + static String setUserCatApi = 'set_user_category'; + static String getUserByIdApi = 'get_user_by_id'; + static String setCommentDeleteApi = 'delete_comment'; + static String setLikesDislikesApi = 'set_like_dislike'; + static String setFlagApi = 'set_flag'; + static String getLiveStreamingApi = 'get_live_streaming'; + static String getSubCategoryApi = 'get_subcategory_by_category'; + static String setLikeDislikeComApi = 'set_comment_like_dislike'; + static String deleteUserNotiApi = 'delete_user_notification'; + static String getQueApi = 'get_question'; + static String getQueResultApi = 'get_question_result'; + static String setQueResultApi = 'set_question_result'; + static String userDeleteApi = 'delete_user'; + static String getTagsApi = 'get_tag'; + static String setNewsApi = 'set_news'; + static String setDeleteNewsApi = 'delete_news'; + static String setDeleteImageApi = 'delete_news_images'; + static String getVideosApi = 'get_videos'; + static String getLanguagesApi = 'get_languages_list'; + static String getLangJsonDataApi = 'get_language_json_data'; + static String getPagesApi = 'get_pages'; + static String getPolicyPagesApi = 'get_policy_pages'; + static String getFeatureSectionApi = 'get_featured_sections'; + static String getLikeNewsApi = 'get_like'; + static String setNewsViewApi = 'set_news_view'; + static String setBreakingNewsViewApi = 'set_breaking_news_view'; + static String getAdsNewsDetailsApi = 'get_ad_space_news_details'; + static String getLocationCityApi = 'get_location'; + static String slugCheckApi = 'check_slug_availability'; + static String rssFeedApi = 'get_rss_feed'; + static String becomeAnAuthorApi = 'become_author'; + static String getAuthorNewsApi = 'get_authors_news'; + static String getDraftNewsApi = 'get_user_drafted_news'; + static String geminiMetaInfoApi = 'https://generativelanguage.googleapis.com/v1beta/models/'; + + static FormData? toFormData(dynamic data) { + if (data == null) return null; + + if (data is Map) { + return FormData.fromMap(data, ListFormat.multiCompatible); + } + + if (data is List) { + final formData = FormData(); + for (var value in data) { + formData.fields.add(MapEntry("list[]", value.toString())); + } + return formData; + } + + // Raw value (int/string/etc.) → cannot convert → return null + return null; + } + + static Future> sendApiRequest({required dynamic body, required String url, bool isGet = false}) async { + try { + if (!await InternetConnectivity.isNetworkAvailable()) { + throw const SocketException(ErrorMessageKeys.noInternet); + } + + final Dio dio = Dio(); + final apiUrl = "$databaseUrl$url"; + + // Auto-detect GET for endpoints starting with "get_" + final shouldUseGet = isGet || url.startsWith('get_'); + + // Convert body to query parameters for GET requests + Map? queryParams; + if (shouldUseGet && body != null && body is Map) { + queryParams = body; + } + + // Convert only if possible (for POST) + final convertedBody = toFormData(body); + + final response = (shouldUseGet) + ? await dio.get( + apiUrl, + queryParameters: queryParams ?? {}, + options: Options(headers: headers), + ) + : await dio.post( + apiUrl, + data: convertedBody ?? body, + options: Options(headers: headers), + ); + + if (response.data['error'] == 'true') { + throw ApiException(response.data['message']); + } + + return Map.from(response.data); + } on DioException catch (e) { + print('DEBUG API ERROR: ${e.response?.statusCode} - ${e.message} - URL: $url'); + print('DEBUG API RESPONSE: ${e.response?.data}'); + if (e.response?.statusCode == 503) { + throw ApiException(ErrorMessageKeys.serverDownMessage); + } else if (e.response?.statusCode == 404) { + throw ApiException(ErrorMessageKeys.requestAgainMessage); + } + + throw ApiException( + e.error is SocketException ? ErrorMessageKeys.noInternet : ErrorMessageKeys.defaultErrorMessage, + ); + } on SocketException catch (e) { + print('DEBUG SOCKET ERROR: ${e.message}'); + throw SocketException(e.message); + } on ApiException catch (e) { + print('DEBUG API EXCEPTION: ${e.errorMessage}'); + throw ApiException(e.errorMessage); + } catch (e) { + print('DEBUG CATCH ALL ERROR: $e'); + throw ApiException(ErrorMessageKeys.defaultErrorMessage); + } + } +} diff --git a/news-app/lib/utils/appLanguages.dart b/news-app/lib/utils/appLanguages.dart new file mode 100644 index 00000000..30f6d9ec --- /dev/null +++ b/news-app/lib/utils/appLanguages.dart @@ -0,0 +1,244 @@ +const Map appLanguageLabelKeys = { + "somethingMSg": "Something went wrong. Please try again after some time", + "bookmarkLbl": "Bookmarks", + "loginLbl": "Login", + "welTitle1": "Always Up-to-Date", + "welTitle2": "Bookmark & Share", + "welTitle3": "New Categories", + "welDes1": "Receive notifications for the most recent news updates and many more.", + "welDes2": "Save and easily share news with your friends using our intuitive news app feature.", + "welDes3": "Enjoy expertly tailored news, crafted exclusively for your interests.", + "nameLbl": "Name", + "emailLbl": "Email", + "passLbl": "Password", + "confpassLbl": "Confirm Password", + "priPolicy": "Privacy Policy", + "andLbl": " and ", + "termLbl": "Terms of Service", + "forgotPassLbl": "Forgot Password ?", + "internetmsg": "Internet Connection not available", + "loginMsg": "Login Successfully", + "loginNowLbl": "Login Now", + "logoutLbl": "Logout", + "cancelBtn": "Cancel", + "noNews": "News Not Available", + "exitWR": "Double tap back button to exit", + "shareLbl": "Share", + "deactiveMsg": "You are deactivated by admin", + "bookmarkNotAvail": "Bookmarks Not Available", + "notiNotAvail": "Notifications Not Available", + "notificationLbl": "Notifications", + "logoutTxt": "Are you sure you want to Logout?", + "yesLbl": "Yes", + "noLbl": "No", + "frgtPassHead": "Enter the email address associated with your account", + "forgotPassSub": "We will email you a link to reset your password", + "submitBtn": "Submit", + "verifyEmailMsg": "Please first verify your email address!!!", + "passReset": "Password reset link has been sent to your mail", + "profileUpdateMsg": "Profile Data Updated Successfully", + "bookmarkLogin": "Please Login to Access Your Bookmarks !!", + "preferenceSave": "Your preference saved!!", + "managePreferences": "Manage Preferences", + "loginReqMsg": "Login Required...", + "firstFillData": "Please First Fill Data...!", + "deleteTxt": "Delete", + "reportTxt": "Report", + "nameRequired": "Name is Required", + "nameLength": "Name should be atleast 2 character long", + "emailRequired": "email address is Required", + "emailValid": "Please enter a valid email Address!", + "pwdRequired": "Password is Required", + "confPassRequired": "Confirm Password is Required", + "confPassNotMatch": "Confirm Password not match", + "photoLibLbl": "Photo Library", + "cameraLbl": "Camera", + "verifSentMail": "Verification email sent to ", + "cancelLogin": "Login cancelled by the user.", + "loginTxt": "Log In", + "loginBtn": "Login", + "signupBtn": "Sign Up", + "otpVerifyLbl": "OTP Verification", + "enterMblLbl": "Enter Your Mobile Number", + "receiveDigitLbl": "You'll Receive 6 digit code for phone number verification", + "reqOtpLbl": "Request OTP", + "otpSentLbl": "OTP has been sent to ", + "resendCodeLbl": "Resend Code in", + "mobileLbl": "Mobile", + "darkModeLbl": "Dark Mode", + "changeLang": "Change Language", + "rateUs": "Rate Us", + "shareApp": "Share App", + "weatherLbl": "Weather Forecast", + "categoryLbl": "Categories", + "allLbl": "All", + "comLbl": "Comment ", + "saveLbl": "Save", + "txtSizeLbl": "Text Size", + "speakLoudLbl": "Speak Loud", + "likeLbl": "likes", + "comsLbl": "Comments", + "shareThoghtLbl": "Share Your Thoughts.", + "repliesLbl": "Replies", + "publicReply": "Add a public reply...", + "personalLbl": "Personal", + "newsLbl": "News", + "plzLbl": "Please", + "fastTrendNewsLbl": "Porque Mereces Verdaderas Respuestas! ", + "enterOtpTxt": "Please Enter OTP", + "otpError": "Error validating OTP, try again", + "otpMsg": "OTP verified successfully", + "resendLbl": "Resend OTP", + "otpTimeoutLbl": "Otp Retrieval Timeout!!!", + "mblRequired": "Mobile number is Required", + "mblValid": "Please enter a valid mobile number!", + "codeSent": "Code Sent Successfully!!!", + "relatedNews": "You might also like", + "optSel": "Please Select One Option!!!", + "madeBy": "Made by", + "skip": "Skip", + "nxt": "Next", + "signInTab": "Sign In", + "agreeTermPolicyLbl": "By Logging In, you agree to our", + "addTCFirst": "Please Ask Admin to Add Privacy Policy & Terms and Conditions first !!", + "orLbl": "or Log In with", + "signupDescr": "Create\nan Account", + "firstAccLbl": "First to access", + "allFunLbl": "all Functions", + "chooseLanLbl": "Select Language", + "videosLbl": "Videos", + "search": "Search", + "searchHomeNews": "Search News, Categories, etc.", + "viewMore": "View More", + "viewFullCoverage": "View full Coverage", + "updateName": "Update your Name", + "loginDescr": "Let's Sign \nYou In", + "logoutAcc": "Logout Account", + "deleteAcc": "Delete Account", + "deleteAlertTitle": "Re-Login", + "deleteRelogin": "To Delete your Account, You need to Login again.\nAfter that you will be able to Delete your Account.", + "deleteConfirm": "Are you sure?\nDo You Really Want to Delete Your Account?", + "pwdLength": "Password should be more than 6 character long", + "userNotFound": "No user found for given email.", + "wrongPassword": "Wrong password provided for that user.", + "weakPassword": "The password provided is too weak.", + "emailAlreadyInUse": "The account already exists for that email.", + "invalidPhoneNumber": "The provided phone number is not valid.", + "invalidVerificationCode": "The sms verification code used to create the phone auth credential is invalid.", + "ago": "ago", + "years": "years", + "months": "months", + "minutes": "minutes", + "seconds": "seconds", + "hours": "hours", + "days": "days", + "justNow": "just now", + "about": "about", + "liveVideosLbl": "Live Videos", + "stdPostLbl": "Standard Post", + "videoYoutubeLbl": "Video (Youtube)", + "videoOtherUrlLbl": "Video (Other Url)", + "videoUploadLbl": "Video (Upload)", + "createNewsLbl": "Create News", + "step1Of2Lbl": "Step 1 of 2", + "catLbl": "Category", + "plzSelCatLbl": "Please select category", + "subcatLbl": "SubCategory", + "contentTypeLbl": "Content Type", + "uploadVideoLbl": "Upload Video", + "youtubeUrlLbl": "Youtube Url", + "otherUrlLbl": "Other Url", + "selContentTypeLbl": "Select Content Type", + "titleLbl": "Title", + "tagLbl": "Tag", + "showTilledDate": "Show Till Date", + "uploadMainImageLbl": "Upload Main Image", + "uploadOtherImageLbl": "Upload Other Image", + "plzUploadVideoLbl": "Please upload video!!!", + "plzAddMainImageLbl": "Please add main image!!!", + "selTagLbl": "Select Tag", + "selSubCatLbl": "Select Sub Category", + "selCatLbl": "Select Category", + "editNewsLbl": "Edit News", + "doYouReallyNewsLbl": "Do You Really Want to Delete this News?", + "delNewsLbl": "Delete News", + "newsTitleReqLbl": "News title is required!!!", + "plzAddValidTitleLbl": "Please add valid news title!!!", + "urlReqLbl": "Url is required!!!", + "plzValidUrlLbl": "Please add valid url!!!", + "manageNewsLbl": "Manage News", + "step2of2Lbl": "Step 2 of 2", + "descLbl": "Description", + "RetryLbl": "Retry", + "previewLbl": "Preview", + "sponsoredLbl": "Sponsored", + "searchForLbl": "Search Result for", + "readLessLbl": "Read less", + "readMoreLbl": "Read more", + "myProfile": "My Profile", + "editProfile": "Edit Profile", + "noComments": "Be the First One to Comment !!!", + "minute": "minute", + "read": "read", + "selLocationLbl": "Select Location", + "metaKeywordLbl": "Meta Keyword", + "metaTitleLbl": "Meta Title", + "metaDescriptionLbl": "Meta Description", + "slugLbl": "Slug", + "metaTitleWarningLbl": "Meta Title length should not exceed 60 characters.", + "metaDescriptionWarningLbl": "Meta Description length should between 50 to 160 characters.", + "metaKeywordWarningLbl": "Meta Keywords are not more than 10 keyword phrases & should be comma separated.", + "slugWarningLbl": "Slug only accept lowercase letters, numbers, and hyphens. No spaces or special characters allowed.", + "metaTitleRequired": "Meta title is Required", + "metaDescriptionRequired": "Meta Description is Required", + "metaKeywordRequired": "Meta Keyword is Required", + "slugRequired": "Slug is Required", + "slugValid": "Please enter valid Slug!", + "slugUsedAlready": "This slug is already in use. Please add any other slug.", + "maintenanceMessageLbl": "We're Under Maintenance\nPlease try again later.", + "notificationLogin": "Please Login to view Your Notifications !!", + "publishDate": "Publish Date", + "dateConfirmation": "Please change Show till date , as it can't be set before Publish date", + "expiredKey": "expired", + "deactivatedKey": "deactivated", + "clearFilter": "Clear filter", + "FilterBy": "Filter by", + "rssFeed": "RSS Feed", + "profile": "Profile", + "homeLbl": "Home", + "replyLbl": "Reply", + "disabledCommentsMsg": "Comments are disabled for this News by admin", + "last": "Last", + "today": "Today", + "clear": "Clear", + "apply": "Apply", + "date": "Date", + "continueWith": "Continue with", + "google": "Google", + "apple": "Apple", + "fb": "Facebook", + "didntGetCode": "Didn't get Code?", + "viewsLbl": "Views", + "recentVidLbl": "Recent News Videos", + "forceUpdateTitleLbl": "Update Required", + "newVersionAvailableTitleLbl": "New Version Available !!!", + "newVersionAvailableDescLbl": "Would you like to update now ?", + "forceUpdateDescLbl": "A new version of this app is available with important improvements and features. To continue using the app, please update to the latest version.", + "exitLbl": "Exit", + "newsCreatedSuccessfully": "Your news has been created successfully! It will be visible to others after admin approval.", + "summarizedDescription": "Summarized Description", + "clickSummarizeDescription": "Click 'Summarize Description' to generate a summary of your content", + "enterDescriptionFirst": "Add description First to generate Summarized Description", + "authorReviewPendingLbl": "Pending Review", + "becomeAuthorLbl": "Become an Author", + "authorLbl": "Author", + "saveAsDraftLbl": "Save As Draft", + "manageNewsAllLbl": "All News", + "manageNewsDraftLbl": "Draft News", + "publishBtnLbl": "Publish", + "addYourBioHintLbl": "Add your bio", + "addLinkHereHintLbl": "Add {type} Link here", + "noteForAuthorLbl": "You can add social media links & Bio only if you are the author.", + "socialMediaLinksLbl": "Social media links", + "followLbl": "Follow :" +}; diff --git a/news-app/lib/utils/constant.dart b/news-app/lib/utils/constant.dart new file mode 100644 index 00000000..c907c67b --- /dev/null +++ b/news-app/lib/utils/constant.dart @@ -0,0 +1,38 @@ +//Please add your admin panel url here and make sure you do not add '/' at the end of the url + +const String baseUrl = "https://news.ajsmartsolutions.space"; + +const String databaseUrl = "$baseUrl/api/"; //Do not change + +const String shareNavigationWebUrl = "https://newspaper.ajsmartsolutions.space"; //with http:// OR https:// + +const int limitOfSectionsData = 5; + +const int limitOfAPIData = 10; +const int limitOfStyle1 = 3; +const int limitOfAllOtherStyle = 20; + +//Facebook Login enable/disable +const bool fblogInEnabled = false; + +//set value for survey show after news data +const int surveyShow = 4; + +//set value for native ads show after news data +const int nativeAdsIndex = 3; + +//set value for interstitial ads show after news data +const int interstitialAdsIndex = 2; + +//set value for reward ads show after news data +const int rewardAdsIndex = 4; + +const String appName = 'elCaribe'; + +enum VideoViewType { normal, page } + +enum ContentType { youtube, uploaded, live } + +enum AuthorStatus { approved, pending, rejected } + +enum AuthorLayoutType { list, grid } diff --git a/news-app/lib/utils/hiveBoxKeys.dart b/news-app/lib/utils/hiveBoxKeys.dart new file mode 100644 index 00000000..9baeacb9 --- /dev/null +++ b/news-app/lib/utils/hiveBoxKeys.dart @@ -0,0 +1,39 @@ +//Authbox keys +const String authBoxKey = "authBox"; +const String jwtTokenKey = "jwtToken"; +const String isLogInKey = "isLogIn"; +const String userIdKey = "userId"; +const String userNameKey = "userName"; +const String userMobKey = "userMob"; +const String userEmailKey = "userEmail"; +const String userProfileKey = "userProfile"; +const String userRoleKey = "userRole"; +const String userStatusKey = "userStatus"; +const String userTypeKey = "Type"; +const String authorWhatsappLinkKey = "whatsAppLink"; +const String authorTelegramLinkKey = "telegramLink"; +const String authorfacebookLinkKey = "facebookLink"; +const String authorLinkedInLinkKey = "linkedInLink"; +const String authorBioKey = "authorBio"; +const String authorStatusKey = "authorStatus"; + +//Settings box keys +const String settingsBoxKey = "settings"; +const String currentLanguageCodeKey = "currentLanguageCode"; +const String currentLanguageIDKey = "currentLanguageId"; +const String currentLanguageRTLKey = "currentLanguageRTL"; +const String introSliderKey = "introSlider"; +const String currentThemeKey = "currentTheme"; +const String notificationKey = "notification"; +const String tokenKey = "token"; +const String historyListKey = "historyList"; +const String notificationEnabledKey = "notificationEnabled"; + +//Location box keys +const String locationCityBoxKey = "locationCity"; +const String latitudeKey = "Latitude"; +const String longitudeKey = "Longitude"; + +///VideoPreference +const String videoPreferenceKey = "videoPreference"; +const String videoStyle = 'videoStyle'; diff --git a/news-app/lib/utils/internetConnectivity.dart b/news-app/lib/utils/internetConnectivity.dart new file mode 100644 index 00000000..7b54aba7 --- /dev/null +++ b/news-app/lib/utils/internetConnectivity.dart @@ -0,0 +1,13 @@ +import 'package:connectivity_plus/connectivity_plus.dart'; + +class InternetConnectivity { + static Future isNetworkAvailable() async { + final List connectivityResult = await Connectivity().checkConnectivity(); + if (connectivityResult.contains(ConnectivityResult.mobile)) { + return true; + } else if (connectivityResult.contains(ConnectivityResult.wifi)) { + return true; + } + return false; + } +} diff --git a/news-app/lib/utils/labelKeys.dart b/news-app/lib/utils/labelKeys.dart new file mode 100644 index 00000000..02eb406b --- /dev/null +++ b/news-app/lib/utils/labelKeys.dart @@ -0,0 +1,2 @@ +const String lightThemeKey = "lightTheme"; +const String darkThemeKey = "darkTheme"; diff --git a/news-app/lib/utils/strings.dart b/news-app/lib/utils/strings.dart new file mode 100644 index 00000000..961e5b39 --- /dev/null +++ b/news-app/lib/utils/strings.dart @@ -0,0 +1,165 @@ +const String ID = "id"; +const String DATA = "data"; +const String NAME = "name"; +const String EMAIL = "email"; +const String TYPE = "type"; +const String URL = "url"; +const String STATUS = "status"; +const String FCM_ID = "fcm_id"; +const String PROFILE = "profile"; +const String ROLE = "role"; +const String MOBILE = "mobile"; +const String FIREBASE_ID = "firebase_id"; +const String CATEGORY_ID = "category_id"; +const String CONTENT_TYPE = "content_type"; +const String SHOW_TILL = "show_till"; +const String CONTENT_VALUE = "content_value"; +const String DATE = "date"; +const String TITLE = "title"; +const String DESCRIPTION = "description"; +const String IMAGE = "image"; +const String CATEGORY_NAME = "category_name"; +const String NEWS_ID = "news_id"; +const String BR_NEWS_ID = "breaking_news_id"; +const String USER_ID = "user_id"; +const String USER = "user"; +const String OFFSET = "offset"; +const String LIMIT = "limit"; +const String USER_NEWS = "get_user_news"; +const String MESSAGE = "message"; +const String COUNTER = "counter"; +const String DATE_SENT = "date_sent"; +const String OTHER_IMAGE = "other_image"; +const String IMAGES = "images"; +const String IMAGE_DATA = "image_data"; +const String COMMENT_ID = "comment_id"; +const String TOTAL_LIKE = "total_like"; +const String LIKE = "like"; +const String BOOKMARK = "bookmark"; +const String SUBCAT_NAME = "subcategory_name"; +const String SUBCAT_ID = "subcategory_id"; +const String DISLIKE = "dislike"; +const String TOTAL_DISLIKE = "total_dislike"; +const String PARENT_ID = "parent_id"; +const String REPLY = "reply"; +const String SEARCH = "search"; +const String TAGNAME = "tag_name"; +const String CATEGORY = "category"; +const String SUBCATEGORY = "sub_category"; +const String SUBCATEGORIES = "sub_categories"; +const String TAG_ID = "tag_id"; +const String TAG = "tag_name"; +const String QUESTION = "question"; +const String QUESTION_ID = "question_id"; +const String OPTION_ID = "option_id"; +const String OPTION = "survey_options"; +const String OPTIONS = "options"; +const String PERCENTAGE = "percentage"; +const String CATEGORY_MODE = "category_mode"; +const String BREAK_NEWS_MODE = "breaking_news_mode"; +const String COMM_MODE = "comments_mode"; +const String LIVE_STREAM_MODE = "live_streaming_mode"; +const String SUBCAT_MODE = "subcategory_mode"; +const String GO_REWARDED_ID = "google_rewarded_video_id"; +const String GO_INTER_ID = "google_interstitial_id"; +const String GO_BANNER_ID = "google_banner_id"; +const String GO_NATIVE_ID = "google_native_unit_id"; +const String IOS_GO_REWARDED_ID = "ios_google_rewarded_video_id"; +const String IOS_GO_INTER_ID = "ios_google_interstitial_id"; +const String IOS_GO_BANNER_ID = "ios_google_banner_id"; +const String IOS_GO_NATIVE_ID = "ios_google_native_unit_id"; +const String U_REWARDED_ID = "unity_rewarded_video_id"; +const String U_INTER_ID = "unity_interstitial_id"; +const String U_BANNER_ID = "unity_banner_id"; +const String U_AND_GAME_ID = "android_game_id"; +const String IOS_U_REWARDED_ID = "ios_unity_rewarded_video_id"; +const String IOS_U_INTER_ID = "ios_unity_interstitial_id"; +const String IOS_U_BANNER_ID = "ios_unity_banner_id"; +const String IOS_U_GAME_ID = "ios_game_id"; +const String ADS_MODE = "in_app_ads_mode"; +const String IOS_ADS_MODE = "ios_in_app_ads_mode"; +const String ADS_TYPE = "ads_type"; +const String IOS_ADS_TYPE = "ios_ads_type"; +const String LOCATION_WISE_NEWS_MODE = "location_news_mode"; +const String WEATHER_MODE = "weather_mode"; +const String MAINTENANCE_MODE = "maintenance_mode"; +const String LOCATION = "location"; +const String LOCATION_ID = "location_id"; +const String LOCATION_NAME = "location_name"; +const String LATITUDE = "latitude"; +const String LONGITUDE = "longitude"; +const String TOKEN = "token"; +const String LANGUAGE = "language"; +const String CODE = "code"; +const String DEFAULT_LANG = "default_language"; +const String PAGE_CONTENT = "page_content"; +const String ISRTL = "isRTL"; +const String LANGUAGE_ID = "language_id"; +const String SECTION_ID = "section_id"; +const String PAGE_ICON = "page_icon"; +const String NEWS = "news"; +const String BREAKING_NEWS = "breaking_news"; +const String VIDEOS = "videos"; +const String SHORT_DESC = "short_description"; +const String NEWS_TYPE = "news_type"; +const String VIDEOS_TYPE = "videos_type"; +const String FILTER_TYPE = "filter_type"; +const String CAT_IDS = "category_ids"; +const String SUBCAT_IDS = "subcategory_ids"; +const String NEWS_IDS = "news_ids"; +const String STYLE_APP = "style_app"; +const String NEWS_TOTAL = "news_total"; +const String TOTAL = "total"; +const String BREAK_NEWS_TOTAL = "breaking_news_total"; +const String VIDEOS_TOTAL = "videos_total"; +const String IS_LOGIN = "is_login"; +const String TOTAL_VIEWS = "total_views"; +const String DISPLAY_NAME_LANG = "display_name"; +const String AD_SPACES = "ad_spaces"; +const String AD_SPACE = "ad_space"; +const String AD_FEATURED_SECTION_ID = "ad_featured_section_id"; +const String AD_IMAGE = "ad_image"; +const String AD_URL = "ad_url"; +const String ACTION_TYPE = "action_type"; +const String CONTENT_DATA = "content_data"; +const String META_TITLE = "meta_title"; +const String META_DESC = "meta_description"; +const String META_KEYWORD = "meta_keyword"; +const String SLUG = "slug"; +const String ERROR = "error"; +const String PUBLISHED_DATE = "published_date"; +const String IS_EXPIRED = "is_expired"; +const String FEED_NAME = "feed_name"; +const String FEED_URL = "feed_url"; +const String RSS_FEED_MODE = "rss_feed_mode"; +const String MOBILE_LOGIN_MODE = "mobile_login_mode"; +const String COUNTRY_CODE = "country_code"; +const String SHARE_APP_TEXT = "shareapp_text"; +const String APPSTORE_ID = "app_store_id"; +const String ANDROID_APP_LINK = "android_app_link"; +const String IOS_APP_LINK = "ios_app_link"; +const String WEB_SETTING = "web_setting"; +const String MERGE_TAG = "merge_tag"; +const String IS_COMMENT_ENABLED = "is_comment"; +const String LAST_N_DAYS = "last_n_days"; +const String YEAR = "year"; +const String VIDEO_TYPE_PREFERENCE = "video_type_preference"; +const String SOURCE_TYPE = "source_type"; +const String SECTION_OFFSET = "section_offset"; +const String SECTION_LIMIT = "section_limit"; +const String UPDATED_DATE = "updated_at"; +const String FORCE_UPDT_APP_MODE = 'force_update_app_mode'; +const String ANDROID_APP_VERSION = 'android_app_version'; +const String IOS_APP_VERSION = 'ios_app_version'; +const String SUMM_DESCRIPTION = 'summarized_description'; +const String GEMINI_API_KEY = 'google_gemini_api_key'; +const String IS_DRAFT_KEY = 'is_draft'; +const String IS_AUTHOR = 'is_author'; +const String AUTHOR = 'author'; +const String AUTHOR_BIO = 'bio'; +const String AUTHOR_STATUS = "author_status"; +const String AUTHOR_WHATSAPP_LINK = 'whatsapp_link'; +const String AUTHOR_TELEGRAM_LINK = 'telegram_link'; +const String AUTHOR_FACEBOOK_LINK = 'facebook_link'; +const String AUTHOR_LINKEDIN_LINK = 'linkedin_link'; +const String AUTHOR_ID = "author_id"; diff --git a/news-app/lib/utils/uiUtils.dart b/news-app/lib/utils/uiUtils.dart new file mode 100644 index 00000000..884026c6 --- /dev/null +++ b/news-app/lib/utils/uiUtils.dart @@ -0,0 +1,507 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:dotted_border/dotted_border.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:hive/hive.dart'; +import 'package:intl/intl.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/cubits/languageJsonCubit.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/InterstitialAds/googleInterstitialAds.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/InterstitialAds/unityInterstitialAds.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/labelKeys.dart'; +import 'package:news/ui/styles/appTheme.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class UiUtils { + static GlobalKey rootNavigatorKey = GlobalKey(); + + static Future setDynamicStringValue(String key, String value) async { + Hive.box(settingsBoxKey).put(key, value); + } + + static Future setDynamicListValue(String key, String value) async { + List? valueList = getDynamicListValue(key); + if (!valueList.contains(value)) { + if (valueList.length > 4) valueList.removeAt(0); + valueList.add(value); + + Hive.box(settingsBoxKey).put(key, valueList); + } + } + + static List getDynamicListValue(String key) { + return Hive.box(settingsBoxKey).get(key) ?? []; + } + + static String getSvgImagePath(String imageName) { + return "assets/images/svgImage/$imageName.svg"; + } + + static String getPlaceholderPngPath() { + return "assets/images/placeholder.png"; + } + + static ColorScheme getColorScheme(BuildContext context) { + return Theme.of(context).colorScheme; + } + +// get app theme + static String getThemeLabelFromAppTheme(AppTheme appTheme) { + if (appTheme == AppTheme.Dark) { + return darkThemeKey; + } + return lightThemeKey; + } + + static AppTheme getAppThemeFromLabel(String label) { + return (label == darkThemeKey) ? AppTheme.Dark : AppTheme.Light; + } + + static String getTranslatedLabel(BuildContext context, String labelKey) { + return context.read().getTranslatedLabels(labelKey); + } + + static Future loginRequired(BuildContext context) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'loginReqMsg'), context); + + Future.delayed(const Duration(milliseconds: 1000), () { + return Navigator.of(context).pushNamed(Routes.login, arguments: {"isFromApp": true}); //pass isFromApp to get back to specified screen + }); + return Future(() => null); + } + + static Widget showCircularProgress(bool isProgress, Color color) { + if (isProgress) { + return Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation(color))); + } + return const SizedBox.shrink(); + } + + showUploadImageBottomsheet({required BuildContext context, required VoidCallback? onCamera, required VoidCallback? onGallery}) { + return showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder(side: BorderSide(color: Colors.transparent), borderRadius: BorderRadius.only(topLeft: Radius.circular(10), topRight: Radius.circular(10))), + builder: (BuildContext buildContext) { + return SafeArea( + child: Wrap( + children: [ + ListTile( + leading: SvgPictureWidget(assetName: 'gallaryIcon', assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), BlendMode.srcIn)), + title: CustomTextLabel(text: 'photoLibLbl', textStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer, fontSize: 16, fontWeight: FontWeight.w400)), + onTap: onGallery), + ListTile( + leading: SvgPictureWidget(assetName: 'cameraIcon', assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), BlendMode.srcIn)), + title: CustomTextLabel(text: 'cameraLbl', textStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer, fontSize: 16, fontWeight: FontWeight.w400)), + onTap: onCamera), + ], + ), + ); + }); + } + + static String? convertToAgo(BuildContext context, DateTime input, int from) { + //from - 0 : NewsItem,details, bookmarks & sectionStyle5 , 1 : Comments, 2: Notifications, 3: SectionStyle6 + Duration diff = DateTime.now().difference(input); + if (Intl.defaultLocale != null || Intl.defaultLocale!.isEmpty) Intl.defaultLocale = 'en'; + initializeDateFormatting(); //locale according to location + final langCode = Hive.box(settingsBoxKey).get(currentLanguageCodeKey); + bool isNegative = diff.isNegative; + if (diff.inDays >= 365 && from == 1) { + double years = calculateYearsDifference(input, DateTime.now()); + return "${years.toStringAsFixed(0)} ${getTranslatedLabel(context, 'years')} ${getTranslatedLabel(context, 'ago')}"; + } else { + if (diff.inDays >= 1 || (isNegative && diff.inDays < 1)) { + if (from == 0) { + var newFormat = DateFormat("MMM dd, yyyy", langCode); + final newsDate1 = newFormat.format(input); + return newsDate1; + } else if (from == 1) { + int months = calculateMonthsDifference(input, DateTime.now()); + if ((months < 12 && diff.inDays >= 30) && !isNegative) { + return "${months.toStringAsFixed(0)} ${getTranslatedLabel(context, 'months')} ${getTranslatedLabel(context, 'ago')}"; + } else if ((diff.inHours >= 1 && diff.inHours < 24)) { + return "${getTranslatedLabel(context, 'about')} ${diff.inHours} ${getTranslatedLabel(context, 'hours')} ${input.minute} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}"; + } else if ((isNegative && diff.inMinutes < 1)) { + return "${getTranslatedLabel(context, 'about')} ${input.minute} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}"; + } else { + return "${diff.inDays} ${getTranslatedLabel(context, 'days')} ${getTranslatedLabel(context, 'ago')}"; + } + } else if (from == 2) { + var newFormat = DateFormat("dd MMMM yyyy", langCode); + final newsDate1 = newFormat.format(input); + return newsDate1; + } else if (from == 3) { + var newFormat = DateFormat("MMMM dd, yyyy", langCode); + final newNewsDate = newFormat.format(input); + return newNewsDate; + } + } else if (diff.inHours >= 1 || (isNegative && diff.inMinutes < 1)) { + if (input.minute == 00) { + return "${diff.inHours} ${getTranslatedLabel(context, 'hours')} ${getTranslatedLabel(context, 'ago')}"; + } else { + if (from == 2) { + return "${getTranslatedLabel(context, 'about')} ${diff.inHours} ${getTranslatedLabel(context, 'hours')} ${input.minute} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}"; + } else { + return "${diff.inHours} ${getTranslatedLabel(context, 'hours')} ${input.minute} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}"; + } + } + } else if (diff.inMinutes >= 1 || (isNegative && diff.inMinutes < 1)) { + return "${diff.inMinutes} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}"; + } else if (diff.inSeconds >= 1) { + return "${diff.inSeconds} ${getTranslatedLabel(context, 'seconds')} ${getTranslatedLabel(context, 'ago')}"; + } else { + return getTranslatedLabel(context, 'justNow'); + } + } + return null; + } + + static int calculateMonthsDifference(DateTime startDate, DateTime endDate) { + int yearsDifference = endDate.year - startDate.year; + int monthsDifference = endDate.month - startDate.month; + + return yearsDifference * 12 + monthsDifference; + } + + static double calculateYearsDifference(DateTime startDate, DateTime endDate) { + int monthsDifference = calculateMonthsDifference(startDate, endDate); + return monthsDifference / 12; + } + + /// Pluggable date picker theme builder for consistent styling across the app + /// Usage: Theme(data: Theme.of(context).copyWith(datePickerTheme: UiUtils.buildDatePickerTheme(context))) + static DatePickerThemeData buildDatePickerTheme(BuildContext context) { + final colorScheme = getColorScheme(context); + + return DatePickerThemeData( + // Base styling + dayStyle: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + + // Header configuration + headerBackgroundColor: colorScheme.primary, + headerForegroundColor: colorScheme.onPrimary, + + // Today/Current date styling - ensures proper visibility + todayForegroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return colorScheme.onPrimary; // White text when selected + } + return colorScheme.primary; // Primary color when not selected - fixes white color issue + }), + todayBackgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return colorScheme.primary; // Primary background when selected + } + return Colors.transparent; // No background when not selected + }), + + // Regular day styling + dayForegroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return colorScheme.onPrimary; // White text on selected days + } + if (states.contains(WidgetState.disabled)) { + return colorScheme.onSurface.withOpacity(0.38); // Disabled state + } + return colorScheme.onSurface; // Normal text color + }), + dayBackgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return colorScheme.primary; // Primary background for selected + } + return Colors.transparent; // Transparent for unselected + }), + + // Year picker styling + yearForegroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return colorScheme.onPrimary; + } + return colorScheme.onSurface; + }), + yearBackgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return colorScheme.primary; + } + return Colors.transparent; + }), + + // Weekday header styling + weekdayStyle: TextStyle(color: colorScheme.onSurface.withOpacity(0.6), fontWeight: FontWeight.w500, fontSize: 12), + ); + } + + /// Pluggable themed date picker wrapper that applies consistent styling + /// Fixes the currentDate white color issue across the entire app + /// Usage: UiUtils.showThemedDatePicker(context: context, ...) + static Future showThemedDatePicker({ + required BuildContext context, + required DateTime initialDate, + required DateTime firstDate, + required DateTime lastDate, + DateTime? currentDate, + String? helpText, + String? cancelText, + String? confirmText, + Locale? locale, + bool useRootNavigator = true, + }) async { + return await showDialog( + context: context, + useRootNavigator: useRootNavigator, + builder: (BuildContext context) { + return Theme( + data: Theme.of(context).copyWith( + datePickerTheme: buildDatePickerTheme(context), + ), + child: DatePickerDialog( + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + currentDate: currentDate, + helpText: helpText, + cancelText: cancelText, + confirmText: confirmText, + ), + ); + }, + ); + } + + static setUIOverlayStyle({required AppTheme appTheme}) { + appTheme == AppTheme.Light + ? SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor: backgroundColor.withOpacity(0.8), statusBarBrightness: Brightness.light, statusBarIconBrightness: Brightness.dark)) + : SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle(statusBarColor: darkSecondaryColor.withOpacity(0.8), statusBarBrightness: Brightness.dark, statusBarIconBrightness: Brightness.light)); + } + + static userLogOut({required BuildContext contxt}) { + for (int i = 0; i < AuthProviders.values.length; i++) { + if (AuthProviders.values[i].name == contxt.read().getType()) { + contxt.read().resetState(); + contxt.read().resetState(); + contxt.read().signOut(AuthProviders.values[i]).then((value) { + Navigator.of(contxt).pushNamedAndRemoveUntil(Routes.login, (route) => false); + }); + } + } + } + +//widget for User Profile Picture in Comments + static Widget setFixedSizeboxForProfilePicture({required Widget childWidget}) { + return SizedBox(height: 35, width: 35, child: childWidget); + } + + static Future isValidLocale(String locale) async { + try { + await initializeDateFormatting(locale, null); + DateFormat('EEEE', locale).format(DateTime.now()); // test formatting + return true; + } catch (e) { + return false; + } + } + + static Future setValidDefaultLocale(String? locale) async { + if (locale == null || locale.isEmpty || !(await isValidLocale(locale))) { + Intl.defaultLocale = 'en'; + } else { + Intl.defaultLocale = locale; + } + Hive.box(settingsBoxKey).put(currentLanguageCodeKey, Intl.defaultLocale); + } + + static Future checkIfValidLocale({required String langCode}) async { + await setValidDefaultLocale(langCode); //pass langCode here + } + + //Add & Edit News Screen + //roundedRectangle dashed border widget + static Widget dottedRRectBorder({required Widget childWidget}) { + return DottedBorder(options: RoundedRectDottedBorderOptions(radius: const Radius.circular(10), dashPattern: const [6, 3]), child: ClipRRect(child: Center(child: childWidget))); + } + + static Widget dropdownArrow({required BuildContext context}) { + return Align(alignment: Alignment.centerRight, child: Icon(Icons.keyboard_arrow_down_outlined, color: getColorScheme(context).primaryContainer)); + } + + static Widget setRowWithContainer({required BuildContext context, required Widget firstChild, required bool isContentTypeUpload}) { + return Container( + width: double.maxFinite, + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 15), + decoration: BoxDecoration(color: getColorScheme(context).surface, borderRadius: BorderRadius.circular(10.0)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + firstChild, + (isContentTypeUpload) + ? Padding( + padding: const EdgeInsetsDirectional.only(start: 20.0), + child: Align(alignment: Alignment.centerRight, child: Icon(Icons.file_upload_outlined, color: getColorScheme(context).primaryContainer))) + : dropdownArrow(context: context) + ], + ), + ); + } + + static Widget setBottomsheetContainer({required String listItem, required String compareTo, required BuildContext context, required String entryId}) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5.0), color: (compareTo != "" && compareTo == entryId) ? Theme.of(context).primaryColor : getColorScheme(context).primaryContainer.withOpacity(0.1)), + padding: const EdgeInsets.all(10.0), + alignment: Alignment.center, + child: CustomTextLabel( + text: listItem, + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: (compareTo == entryId) ? getColorScheme(context).secondary : getColorScheme(context).primaryContainer))); + } + + static Widget setTopPaddingParent({required Widget childWidget}) { + return Padding(padding: const EdgeInsets.only(top: 10.0), child: childWidget); + } + +//Home Screen - featured sections + static Widget setPlayButton({required BuildContext context, double heightVal = 40}) { + return Container( + alignment: Alignment.center, + height: heightVal, + width: heightVal, + decoration: BoxDecoration(shape: BoxShape.circle, color: Theme.of(context).primaryColor), + child: const Icon(Icons.play_arrow_sharp, size: 25, color: secondaryColor)); + } + + //Native Ads + static BannerAd createBannerAd({required BuildContext context}) { + return BannerAd( + adUnitId: context.read().bannerId()!, + request: const AdRequest(), + size: AdSize.mediumRectangle, + listener: BannerAdListener( + onAdLoaded: (_) => debugPrint("native ad is Loaded !!!"), + onAdFailedToLoad: (ad, err) { + ad.dispose(); + }, + onAdOpened: (Ad ad) => debugPrint('Native ad opened.'), + // Called when an ad opens an overlay that covers the screen. + onAdClosed: (Ad ad) => debugPrint('Native ad closed.'), + // Called when an ad removes an overlay that covers the screen. + onAdImpression: (Ad ad) => debugPrint('Native ad impression.'))); + } + + static Widget bannerAdsShow({required BuildContext context}) { + return AdWidget(key: UniqueKey(), ad: createBannerAd(context: context)..load()); + } + + //Interstitial Ads + static showInterstitialAds({required BuildContext context}) { + if (context.read().getInAppAdsMode() == "1") { + if (context.read().checkAdsType() == "google") { + showGoogleInterstitialAd(context); + } else { + showUnityInterstitialAds(context.read().interstitialId()!); + } + } + } + + static void shareNews( + {required BuildContext context, required String title, required String slug, required bool isBreakingNews, required bool isNews, required bool isVideo, required String videoId}) { + String contentType = ""; + if (isVideo) contentType = "video-news"; + if (isNews) contentType = "news"; + if (isBreakingNews) contentType = "breaking-news"; + + String shareLink = "$shareNavigationWebUrl/${context.read().state.languageCode}/$contentType/$slug?language_id=${context.read().state.id}&share=true"; + + String str = "$title\n\n$appName\n\n${context.read().getShareAppText()}\n\n${context.read().getAndroidAppLink()}\n"; + + bool isIOSAppLive = context.read().getiOSAppLink()?.trim().isNotEmpty ?? false; + if (isIOSAppLive) str += "\n\n${context.read().getiOSAppLink()}"; + str = shareLink + "\n\n" + str; + Share.share(str, subject: appName, sharePositionOrigin: Rect.fromLTWH(0, 0, MediaQuery.of(context).size.width, MediaQuery.of(context).size.height / 2)); + } + + //calculate time in Minutes to Read News Article + static int calculateReadingTime(String text) { + const wordsPerMinute = 200; + final wordCount = text.trim().split(' ').length; + final readTime = (wordCount / wordsPerMinute).ceil(); + return readTime; + } + + static Widget applyBoxShadow({required BuildContext context, Widget? child}) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).canvasColor, + boxShadow: [ + BoxShadow(color: Colors.black.withOpacity(0.1), offset: Offset(0, 3), blurRadius: 6), + ], + ), + child: child); + } + + static String formatDate(String date) { + DateTime dateTime = DateTime.parse(date); + final DateFormat formatter = DateFormat("MMMM d,yyyy"); + return formatter.format(dateTime); + } + + static gotoStores(BuildContext context) async { + String iosLink = context.read().getiOSAppLink() ?? ""; + String androidLink = context.read().getAndroidAppLink() ?? ""; + + if (await canLaunchUrl(Uri.parse((Platform.isIOS) ? iosLink : androidLink))) { + await launchUrl(Uri.parse((Platform.isIOS) ? iosLink : androidLink), mode: LaunchMode.externalApplication); + } + if (Navigator.of(context).canPop()) Navigator.of(context).pop(false); + } + + static String decryptKey({required geminiKey}) { + // Decode Base64 to bytes + Uint8List bytes = base64.decode(geminiKey); + + // Convert bytes to String (if it's text) + String decodedString = utf8.decode(bytes); + return decodedString; + } +} + +Widget nativeAdsShow({required BuildContext context, required int index}) { + if (context.read().getInAppAdsMode() == "1" && + context.read().checkAdsType() != null && + (context.read().getIOSAdsType() != "unity" || context.read().getAdsType() != "unity") && + index != 0 && + index % nativeAdsIndex == 0) { + return Padding( + padding: const EdgeInsets.only(bottom: 15.0), + child: Container( + padding: const EdgeInsets.all(7.0), + height: 300, + width: double.infinity, + decoration: BoxDecoration(color: Colors.white.withOpacity(0.5), borderRadius: BorderRadius.circular(10.0)), + child: context.read().checkAdsType() == "google" && (context.read().bannerId() != "") + ? UiUtils.bannerAdsShow(context: context) + : SizedBox.shrink())); + } else { + return const SizedBox.shrink(); + } +} diff --git a/news-app/lib/utils/validators.dart b/news-app/lib/utils/validators.dart new file mode 100644 index 00000000..f291df63 --- /dev/null +++ b/news-app/lib/utils/validators.dart @@ -0,0 +1,119 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:news/utils/uiUtils.dart'; + +class Validators { + //name validation check + static String? nameValidation(String value, BuildContext context) { + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'nameRequired'); + } + if (value.length <= 1) { + return UiUtils.getTranslatedLabel(context, 'nameLength'); + } + return null; + } + +//email validation check + static String? emailValidation(String value, BuildContext context) { + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'emailRequired'); + } else if (!RegExp(r"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)" + r"*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+" + r"[a-z0-9](?:[a-z0-9-]*[a-z0-9])?") + .hasMatch(value)) { + return UiUtils.getTranslatedLabel(context, 'emailValid'); + } else { + return null; + } + } + +//password validation check + static String? passValidation(String value, BuildContext context) { + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'pwdRequired'); + } else if (value.length <= 5) { + return UiUtils.getTranslatedLabel(context, 'pwdLength'); + } else { + return null; + } + } + + static String? mobValidation(String value, BuildContext context) { + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'mblRequired'); + } + if (value.length < 9 || value.length > 16) { + return UiUtils.getTranslatedLabel(context, 'mblValid'); + } + return null; + } + + static String? titleValidation(String value, BuildContext context) { + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'newsTitleReqLbl'); + } else if (value.length < 2) { + return UiUtils.getTranslatedLabel(context, 'plzAddValidTitleLbl'); + } + return null; + } + + static String? youtubeUrlValidation(String value, BuildContext context) { + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'urlReqLbl'); + } else { + bool isValidURL = RegExp(r'^(((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?)').hasMatch(value); + if (!isValidURL) return UiUtils.getTranslatedLabel(context, 'plzValidUrlLbl'); + } + + return null; + } + + static String? urlValidation(String value, BuildContext context) { + bool? test; + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'urlReqLbl'); + } else { + validUrl(value).then((result) { + test = result; + if (test!) { + return UiUtils.getTranslatedLabel(context, 'plzValidUrlLbl'); + } + }); + } + + return null; + } + + static Future validUrl(String value) async { + await Dio().head(value).then((value) { + return (value.statusCode == 200) ? false : true; + }); + return false; + } + + //name validation check + static String? emptyFieldValidation(String value, String hintText, BuildContext context) { + if (value.isEmpty) { + switch (hintText) { + case 'metaTitleLbl': + return UiUtils.getTranslatedLabel(context, 'metaTitleRequired'); + case 'metaDescriptionLbl': + return UiUtils.getTranslatedLabel(context, 'metaDescriptionRequired'); + case 'metaKeywordLbl': + return UiUtils.getTranslatedLabel(context, 'metaKeywordRequired'); + } + } + return null; + } + + static String? slugValidation(String value, BuildContext context) { + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'slugRequired'); + } else if (!RegExp("^[a-z0-9]+(?:-[a-z0-9]+)*").hasMatch(value)) { + return UiUtils.getTranslatedLabel(context, 'slugValid'); + } else { + return null; + } + } +} diff --git a/news-app/pubspec.lock b/news-app/pubspec.lock new file mode 100644 index 00000000..319c8447 --- /dev/null +++ b/news-app/pubspec.lock @@ -0,0 +1,1698 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "8a1f5f3020ef2a74fb93f7ab3ef127a8feea33a7a2276279113660784ee7516a" + url: "https://pub.dev" + source: hosted + version: "1.3.64" + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + audio_session: + dependency: transitive + description: + name: audio_session + sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + bloc: + dependency: transitive + description: + name: bloc + sha256: a2cebb899f91d36eeeaa55c7b20b5915db5a9df1b8fd4a3c9c825e22e474537d + url: "https://pub.dev" + source: hosted + version: "9.1.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + cached_network_image: + dependency: transitive + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" + chewie: + dependency: transitive + description: + name: chewie + sha256: "44bcfc5f0dfd1de290c87c9d86a61308b3282a70b63435d5557cfd60f54a69ca" + url: "https://pub.dev" + source: hosted + version: "1.13.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + connectivity_plus: + dependency: "direct main" + description: + name: connectivity_plus + sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + country_code_picker: + dependency: "direct main" + description: + name: country_code_picker + sha256: f0411f4833b6f98e8b7215f4fa3813bcc88e50f13925f70a170dbd36e3e447f5 + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" + url: "https://pub.dev" + source: hosted + version: "0.3.5+1" + crypto: + dependency: "direct main" + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + cupertino_icons: + dependency: transitive + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + diacritic: + dependency: transitive + description: + name: diacritic + sha256: "12981945ec38931748836cd76f2b38773118d0baef3c68404bdfde9566147876" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + dio: + dependency: "direct main" + description: + name: dio + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + url: "https://pub.dev" + source: hosted + version: "5.9.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + dotted_border: + dependency: "direct main" + description: + name: dotted_border + sha256: "99b091ec6891ba0c5331fdc2b502993c7c108f898995739a73c6845d71dad70c" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810 + url: "https://pub.dev" + source: hosted + version: "8.3.7" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" + source: hosted + version: "0.9.5" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: e54fb3ba57de041d832574126a37726eedf0f57400869f1942b0ca8ce4a6e209 + url: "https://pub.dev" + source: hosted + version: "6.1.2" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "421f95dc553cb283ed9d4d140e719800c0331d49ed37b962e513c9d1d61b090b" + url: "https://pub.dev" + source: hosted + version: "8.1.4" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: a064ffee202f7d42d62e2c01775899d4ffcb83c602af07632f206acd46a0964e + url: "https://pub.dev" + source: hosted + version: "6.1.0" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "1f2dfd9f535d81f8b06d7a50ecda6eac1e6922191ed42e09ca2c84bd2288927c" + url: "https://pub.dev" + source: hosted + version: "4.2.1" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: ff18fabb0ad0ed3595d2f2c85007ecc794aadecdff5b3bb1460b7ee47cded398 + url: "https://pub.dev" + source: hosted + version: "3.3.0" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "22086f857d2340f5d973776cfd542d3fb30cf98e1c643c3aa4a7520bb12745bb" + url: "https://pub.dev" + source: hosted + version: "16.0.4" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: a59920cbf2eb7c83d34a5f354331210ffec116b216dc72d864d8b8eb983ca398 + url: "https://pub.dev" + source: hosted + version: "4.7.4" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "1183e40e6fd2a279a628951cc3b639fcf5ffe7589902632db645011eb70ebefb" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flex_color_picker: + dependency: transitive + description: + name: flex_color_picker + sha256: a0979dd61f21b634717b98eb4ceaed2bfe009fe020ce8597aaf164b9eeb57aaa + url: "https://pub.dev" + source: hosted + version: "3.8.0" + flex_seed_scheme: + dependency: transitive + description: + name: flex_seed_scheme + sha256: a3183753bbcfc3af106224bff3ab3e1844b73f58062136b7499919f49f3667e7 + url: "https://pub.dev" + source: hosted + version: "4.0.1" + flick_video_player: + dependency: "direct main" + description: + name: flick_video_player + sha256: f011cc28c6e4932485ac3a9d09ccacd25d9d430c99090480ccad2f5ebca9366e + url: "https://pub.dev" + source: hosted + version: "0.9.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38 + url: "https://pub.dev" + source: hosted + version: "9.1.1" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + flutter_inappwebview: + dependency: "direct main" + description: + name: flutter_inappwebview + sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" + url: "https://pub.dev" + source: hosted + version: "6.1.5" + flutter_inappwebview_android: + dependency: transitive + description: + name: flutter_inappwebview_android + sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + flutter_inappwebview_internal_annotations: + dependency: transitive + description: + name: flutter_inappwebview_internal_annotations + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_inappwebview_ios: + dependency: transitive + description: + name: flutter_inappwebview_ios + sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_macos: + dependency: transitive + description: + name: flutter_inappwebview_macos + sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + name: flutter_inappwebview_platform_interface + sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 + url: "https://pub.dev" + source: hosted + version: "1.3.0+1" + flutter_inappwebview_web: + dependency: transitive + description: + name: flutter_inappwebview_web + sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_windows: + dependency: transitive + description: + name: flutter_inappwebview_windows + sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + flutter_keyboard_visibility: + dependency: transitive + description: + name: flutter_keyboard_visibility + sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_keyboard_visibility_linux: + dependency: transitive + description: + name: flutter_keyboard_visibility_linux + sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_macos: + dependency: transitive + description: + name: flutter_keyboard_visibility_macos + sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_platform_interface: + dependency: transitive + description: + name: flutter_keyboard_visibility_platform_interface + sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_web: + dependency: transitive + description: + name: flutter_keyboard_visibility_web + sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_windows: + dependency: transitive + description: + name: flutter_keyboard_visibility_windows + sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875" + url: "https://pub.dev" + source: hosted + version: "19.5.0" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe" + url: "https://pub.dev" + source: hosted + version: "9.1.0" + flutter_local_notifications_windows: + dependency: transitive + description: + name: flutter_local_notifications_windows + sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + flutter_login_facebook: + dependency: "direct main" + description: + name: flutter_login_facebook + sha256: a20cbd9bd50b7399609a4f22dbc2fc2ab792c4a2c17d27b98c4dfa960a7505c5 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 + url: "https://pub.dev" + source: hosted + version: "2.0.33" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_tts: + dependency: "direct main" + description: + name: flutter_tts + sha256: bdf2fc4483e74450dc9fc6fe6a9b6a5663e108d4d0dad3324a22c8e26bf48af4 + url: "https://pub.dev" + source: hosted + version: "4.2.3" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_widget_from_html: + dependency: "direct main" + description: + name: flutter_widget_from_html + sha256: "7f1daefcd3009c43c7e7fb37501e6bb752d79aa7bfad0085fb0444da14e89bd0" + url: "https://pub.dev" + source: hosted + version: "0.17.1" + flutter_widget_from_html_core: + dependency: transitive + description: + name: flutter_widget_from_html_core + sha256: "1120ee6ed3509ceff2d55aa6c6cbc7b6b1291434422de2411b5a59364dd6ff03" + url: "https://pub.dev" + source: hosted + version: "0.17.0" + fwfh_cached_network_image: + dependency: transitive + description: + name: fwfh_cached_network_image + sha256: "484cb5f8047f02cfac0654fca5832bfa91bb715fd7fc651c04eb7454187c4af8" + url: "https://pub.dev" + source: hosted + version: "0.16.1" + fwfh_chewie: + dependency: transitive + description: + name: fwfh_chewie + sha256: ae74fc26798b0e74f3983f7b851e74c63b9eeb2d3015ecd4b829096b2c3f8818 + url: "https://pub.dev" + source: hosted + version: "0.16.1" + fwfh_just_audio: + dependency: transitive + description: + name: fwfh_just_audio + sha256: dfd622a0dfe049ac647423a2a8afa7f057d9b2b93d92710b624e3d370b1ac69a + url: "https://pub.dev" + source: hosted + version: "0.17.0" + fwfh_svg: + dependency: transitive + description: + name: fwfh_svg + sha256: "2e6bb241179eeeb1a7941e05c8c923b05d332d36a9085233e7bf110ea7deb915" + url: "https://pub.dev" + source: hosted + version: "0.16.1" + fwfh_url_launcher: + dependency: transitive + description: + name: fwfh_url_launcher + sha256: c38aa8fb373fda3a89b951fa260b539f623f6edb45eee7874cb8b492471af881 + url: "https://pub.dev" + source: hosted + version: "0.16.1" + fwfh_webview: + dependency: transitive + description: + name: fwfh_webview + sha256: f71b0aa16e15d82f3c017f33560201ff5ae04e91e970cab5d12d3bcf970b870c + url: "https://pub.dev" + source: hosted + version: "0.15.6" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" + url: "https://pub.dev" + source: hosted + version: "0.3.3+1" + google_mobile_ads: + dependency: "direct main" + description: + name: google_mobile_ads + sha256: a4f59019f2c32769fb6c60ed8aa321e9c21a36297e2c4f23452b3e779a3e7a26 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + sha256: d0a2c3bcb06e607bb11e4daca48bd4b6120f0bbc4015ccebbe757d24ea60ed2a + url: "https://pub.dev" + source: hosted + version: "6.3.0" + google_sign_in_android: + dependency: transitive + description: + name: google_sign_in_android + sha256: d5e23c56a4b84b6427552f1cf3f98f716db3b1d1a647f16b96dbb5b93afa2805 + url: "https://pub.dev" + source: hosted + version: "6.2.1" + google_sign_in_ios: + dependency: transitive + description: + name: google_sign_in_ios + sha256: "102005f498ce18442e7158f6791033bbc15ad2dcc0afa4cf4752e2722a516c96" + url: "https://pub.dev" + source: hosted + version: "5.9.0" + google_sign_in_platform_interface: + dependency: transitive + description: + name: google_sign_in_platform_interface + sha256: "5f6f79cf139c197261adb6ac024577518ae48fdff8e53205c5373b5f6430a8aa" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + google_sign_in_web: + dependency: transitive + description: + name: google_sign_in_web + sha256: "460547beb4962b7623ac0fb8122d6b8268c951cf0b646dd150d60498430e4ded" + url: "https://pub.dev" + source: hosted + version: "0.12.4+4" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc + url: "https://pub.dev" + source: hosted + version: "1.1.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + html_editor_plus: + dependency: "direct main" + description: + name: html_editor_plus + sha256: d6d00275951b2a8b22b0e5128386514077420a673174cffcb6246c51e7bd146f + url: "https://pub.dev" + source: hosted + version: "0.0.5" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "5e9bf126c37c117cf8094215373c6d561117a3cfb50ebc5add1a61dc6e224677" + url: "https://pub.dev" + source: hosted + version: "0.8.13+10" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "956c16a42c0c708f914021666ffcd8265dde36e673c9fa68c81f7d085d9774ad" + url: "https://pub.dev" + source: hosted + version: "0.8.13+3" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" + url: "https://pub.dev" + source: hosted + version: "0.2.2+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.dev" + source: hosted + version: "0.2.2" + in_app_review: + dependency: "direct main" + description: + name: in_app_review + sha256: ab26ac54dbd802896af78c670b265eaeab7ecddd6af4d0751e9604b60574817f + url: "https://pub.dev" + source: hosted + version: "2.0.11" + in_app_review_platform_interface: + dependency: transitive + description: + name: in_app_review_platform_interface + sha256: fed2c755f2125caa9ae10495a3c163aa7fab5af3585a9c62ef4a6920c5b45f10 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + infinite_listview: + dependency: transitive + description: + name: infinite_listview + sha256: f6062c1720eb59be553dfa6b89813d3e8dd2f054538445aaa5edaddfa5195ce6 + url: "https://pub.dev" + source: hosted + version: "1.1.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + just_audio: + dependency: transitive + description: + name: just_audio + sha256: "9694e4734f515f2a052493d1d7e0d6de219ee0427c7c29492e246ff32a219908" + url: "https://pub.dev" + source: hosted + version: "0.10.5" + just_audio_platform_interface: + dependency: transitive + description: + name: just_audio_platform_interface + sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + just_audio_web: + dependency: transitive + description: + name: just_audio_web + sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" + url: "https://pub.dev" + source: hosted + version: "0.4.16" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + list_ext: + dependency: transitive + description: + name: list_ext + sha256: a38869a6911830f39ace7534972d01e573e17917955b26215e54d5797eb8d5cd + url: "https://pub.dev" + source: hosted + version: "1.0.6" + location: + dependency: "direct main" + description: + name: location + sha256: b080053c181c7d152c43dd576eec6436c40e25f326933051c330da563ddd5333 + url: "https://pub.dev" + source: hosted + version: "8.0.1" + location_platform_interface: + dependency: transitive + description: + name: location_platform_interface + sha256: ca8700bb3f6b1e8b2afbd86bd78b2280d116c613ca7bfa1d4d7b64eba357d749 + url: "https://pub.dev" + source: hosted + version: "6.0.1" + location_web: + dependency: transitive + description: + name: location_web + sha256: b8e3add5efe0d65c5e692b7a135d80a4015c580d3ea646fa71973e97668dd868 + url: "https://pub.dev" + source: hosted + version: "6.0.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + lottie: + dependency: "direct main" + description: + name: lottie + sha256: "8ae0be46dbd9e19641791dc12ee480d34e1fd3f84c749adc05f3ad9342b71b95" + url: "https://pub.dev" + source: hosted + version: "3.3.2" + marqueer: + dependency: "direct main" + description: + name: marqueer + sha256: "86b2a2e34b3bbd80be0c6fd4c60ebe6cc737f49fab79f862faa307f1cb18605c" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + modal_bottom_sheet: + dependency: "direct overridden" + description: + name: modal_bottom_sheet + sha256: eac66ef8cb0461bf069a38c5eb0fa728cee525a531a8304bd3f7b2185407c67e + url: "https://pub.dev" + source: hosted + version: "3.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + numberpicker: + dependency: transitive + description: + name: numberpicker + sha256: "4c129154944b0f6b133e693f8749c3f8bfb67c4d07ef9dcab48b595c22d1f156" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d + url: "https://pub.dev" + source: hosted + version: "9.0.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.dev" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + percent_indicator: + dependency: "direct main" + description: + name: percent_indicator + sha256: "157d29133bbc6ecb11f923d36e7960a96a3f28837549a20b65e5135729f0f9fd" + url: "https://pub.dev" + source: hosted + version: "4.2.5" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" + photo_view: + dependency: "direct main" + description: + name: photo_view + sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e" + url: "https://pub.dev" + source: hosted + version: "0.15.0" + pin_input_text_field: + dependency: transitive + description: + name: pin_input_text_field + sha256: f45683032283d30b670ec343781660655e3e1953438b281a0bc6e2d358486236 + url: "https://pub.dev" + source: hosted + version: "4.5.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointer_interceptor: + dependency: transitive + description: + name: pointer_interceptor + sha256: "57210410680379aea8b1b7ed6ae0c3ad349bfd56fe845b8ea934a53344b9d523" + url: "https://pub.dev" + source: hosted + version: "0.10.1+2" + pointer_interceptor_ios: + dependency: transitive + description: + name: pointer_interceptor_ios + sha256: "03c5fa5896080963ab4917eeffda8d28c90f22863a496fb5ba13bc10943e40e4" + url: "https://pub.dev" + source: hosted + version: "0.10.1+1" + pointer_interceptor_platform_interface: + dependency: transitive + description: + name: pointer_interceptor_platform_interface + sha256: "0597b0560e14354baeb23f8375cd612e8bd4841bf8306ecb71fcd0bb78552506" + url: "https://pub.dev" + source: hosted + version: "0.10.0+1" + pointer_interceptor_web: + dependency: transitive + description: + name: pointer_interceptor_web + sha256: "460b600e71de6fcea2b3d5f662c92293c049c4319e27f0829310e5a953b3ee2a" + url: "https://pub.dev" + source: hosted + version: "0.10.3" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + provider: + dependency: transitive + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840" + url: "https://pub.dev" + source: hosted + version: "12.0.1" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sign_in_with_apple: + dependency: "direct main" + description: + name: sign_in_with_apple + sha256: "8bd875c8e8748272749eb6d25b896f768e7e9d60988446d543fe85a37a2392b8" + url: "https://pub.dev" + source: hosted + version: "7.0.1" + sign_in_with_apple_platform_interface: + dependency: transitive + description: + name: sign_in_with_apple_platform_interface + sha256: "981bca52cf3bb9c3ad7ef44aace2d543e5c468bb713fd8dda4275ff76dfa6659" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + sign_in_with_apple_web: + dependency: transitive + description: + name: sign_in_with_apple_web + sha256: f316400827f52cafcf50d00e1a2e8a0abc534ca1264e856a81c5f06bd5b10fed + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + sms_autofill: + dependency: "direct main" + description: + name: sms_autofill + sha256: c65836abe9c1f62ce411bb78d5546a09ece4297558070b1bd871db1db283aaf9 + url: "https://pub.dev" + source: hosted + version: "2.4.1" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 + url: "https://pub.dev" + source: hosted + version: "2.4.2+2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" + source: hosted + version: "0.7.7" + timezone: + dependency: transitive + description: + name: timezone + sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 + url: "https://pub.dev" + source: hosted + version: "0.10.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + unity_ads_plugin: + dependency: "direct main" + description: + name: unity_ads_plugin + sha256: f35ddd27a3323a462a2ccf20721da99a0b13a4335f11a0cdadbb03db1d17a57b + url: "https://pub.dev" + source: hosted + version: "0.3.28" + universal_html: + dependency: transitive + description: + name: universal_html + sha256: c0bcae5c733c60f26c7dfc88b10b0fd27cbcc45cb7492311cdaa6067e21c9cd4 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" + url: "https://pub.dev" + source: hosted + version: "6.3.28" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad + url: "https://pub.dev" + source: hosted + version: "6.3.6" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + uuid: + dependency: transitive + description: + name: uuid + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + url: "https://pub.dev" + source: hosted + version: "4.5.2" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: "096bc28ce10d131be80dfb00c223024eb0fba301315a406728ab43dd99c45bdf" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: d74b66f283afff135d5be0ceccca2ca74dff7df1e9b1eaca6bd4699875d3ae60 + url: "https://pub.dev" + source: hosted + version: "2.8.22" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: e4d33b79a064498c6eb3a6a492b6a5012573d4943c28d566caf1a6c0840fe78d + url: "https://pub.dev" + source: hosted + version: "2.8.8" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "57c5d73173f76d801129d0531c2774052c5a7c11ccb962f1830630decd9f24ec" + url: "https://pub.dev" + source: hosted + version: "6.6.0" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + visibility_detector: + dependency: transitive + description: + name: visibility_detector + sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 + url: "https://pub.dev" + source: hosted + version: "0.4.0+2" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" + wakelock_plus: + dependency: transitive + description: + name: wakelock_plus + sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + webview_flutter: + dependency: transitive + description: + name: webview_flutter + sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba + url: "https://pub.dev" + source: hosted + version: "4.13.0" + webview_flutter_android: + dependency: "direct overridden" + description: + name: webview_flutter_android + sha256: eeeb3fcd5f0ff9f8446c9f4bbc18a99b809e40297528a3395597d03aafb9f510 + url: "https://pub.dev" + source: hosted + version: "4.10.11" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0" + url: "https://pub.dev" + source: hosted + version: "2.14.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: e49f378ed066efb13fc36186bbe0bd2425630d4ea0dbc71a18fdd0e4d8ed8ebc + url: "https://pub.dev" + source: hosted + version: "3.23.5" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: "direct main" + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + xml2json: + dependency: "direct main" + description: + name: xml2json + sha256: "8a7ae63b76676f083d81287b61f03222952609c0507893bc62e60a4a588a9702" + url: "https://pub.dev" + source: hosted + version: "6.2.7" + youtube_parser: + dependency: "direct main" + description: + name: youtube_parser + sha256: "52ac55275c685ccb51099d5fcf2ab601469ab1667163b47e7f7000ddd226816f" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + youtube_player_flutter: + dependency: "direct main" + description: + name: youtube_player_flutter + sha256: e64eeebaa5f7dc1d55d103cc9abf05f87d8013bae0d3b6a11aad5d33a2f7f5b4 + url: "https://pub.dev" + source: hosted + version: "9.1.3" +sdks: + dart: ">=3.10.0 <4.0.0" + flutter: ">=3.38.0" diff --git a/news-app/pubspec.yaml b/news-app/pubspec.yaml new file mode 100644 index 00000000..38f1b85a --- /dev/null +++ b/news-app/pubspec.yaml @@ -0,0 +1,93 @@ +name: news +# news +description: A new Flutter project. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +version: 1.0.0+1 + +environment: + sdk: ">=3.4.3 <4.0.0" + +dependencies: + flutter: + sdk: flutter + + flutter_svg: ^2.0.7 + connectivity_plus: ^7.0.0 + country_code_picker: ^3.0.0 + sms_autofill: ^2.0.1 + location: ^8.0.0 + intl: ^0.20.1 + firebase_core: ^4.1.0 + firebase_messaging: ^16.0.1 + firebase_auth: ^6.0.2 + google_sign_in: ^6.2.1 + photo_view: ^0.15.0 + url_launcher: ^6.0.10 + shimmer: ^3.0.0 + share_plus: ^12.0.1 + flutter_tts: ^4.0.2 + image_picker: ^1.0.1 + file_picker: ^8.0.6 + video_player: ^2.9.1 + flick_video_player: ^0.9.0 + youtube_player_flutter: ^9.0.1 + youtube_parser: ^2.1.4 + in_app_review: ^2.0.3 + flutter_local_notifications: ^19.4.1 + path_provider: ^2.1.5 + flutter_login_facebook: ^2.0.0 + sign_in_with_apple: ^7.0.1 + crypto: ^3.0.1 + percent_indicator: ^4.0.0 + google_mobile_ads: ^6.0.0 + flutter_widget_from_html: ^0.17.1 + unity_ads_plugin: ^0.3.15 + flutter_inappwebview: ^6.1.5 + flutter_bloc: ^9.0.0 + dio: ^5.0.1 + hive: ^2.2.3 + hive_flutter: ^1.1.0 + equatable: ^2.0.5 + lottie: ^3.1.0 + dotted_border: ^3.1.0 + marqueer: ^2.0.1 + + xml: ^6.5.0 + http: ^1.2.2 + xml2json: ^6.2.5 + + package_info_plus: ^9.0.0 + html_editor_plus: ^0.0.5 + +dependency_overrides: + modal_bottom_sheet: ^3.0.0-pre + webview_flutter_android: ^4.3.3 + +dev_dependencies: + flutter_test: + sdk: flutter + + +flutter: + uses-material-design: true + assets: + - assets/images/ + - assets/images/svgImage/ + - assets/languages/ + - assets/animations/ + + fonts: + - family: Roboto + fonts: + - asset: assets/font/Roboto-Regular.ttf + weight: 400 + - asset: assets/font/Roboto-Light.ttf + weight: 300 + - asset: assets/font/Roboto-Medium.ttf + weight: 500 + - asset: assets/font/Roboto-Bold.ttf + weight: 700 \ No newline at end of file diff --git a/news-app/test/widget_test.dart b/news-app/test/widget_test.dart new file mode 100644 index 00000000..1777be42 --- /dev/null +++ b/news-app/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:news/app/app.dart'; + + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}