├── .gitignore ├── screenshots ├── Rish1.jpg ├── Rish2.jpg ├── Rish3.jpg ├── Rish4.jpg └── logo.png ├── DostiPak └── lib │ ├── widgets │ ├── my_circular_progress.dart │ ├── default_card_border.dart │ ├── app_logo.dart │ ├── show_scaffold_msg.dart │ ├── cicle_button.dart │ ├── svg_icon.dart │ ├── loading_card.dart │ ├── users_grid.dart │ ├── processing.dart │ ├── notification_counter.dart │ ├── build_title.dart │ ├── vip_account_card.dart │ ├── default_button.dart │ ├── no_data.dart │ ├── badge.dart │ ├── sign_out_button_card.dart │ ├── user_gallery.dart │ ├── terms_of_service_row.dart │ ├── delete_account_button.dart │ ├── show_like_or_dislike.dart │ ├── app_section_card.dart │ ├── gallery_image_card.dart │ ├── profile_statistics_card.dart │ ├── image_source_sheet.dart │ ├── profile_basic_info_card.dart │ └── git_source_sheet.dart │ ├── models │ └── app_model.dart │ ├── screens │ ├── blocked_account_screen.dart │ ├── passport_screen.dart │ ├── update_app_screen.dart │ ├── splash_screen.dart │ ├── sign_in_screen.dart │ ├── verification_code_screen.dart │ ├── about_us_screen.dart │ ├── enable_location_screen.dart │ ├── delete_account_screen.dart │ ├── notifications_screen.dart │ ├── profile_visits_screen.dart │ ├── profile_likes_screen.dart │ └── disliked_profile_screen.dart │ ├── generated_plugin_registrant.dart │ ├── tabs │ ├── profile_tab.dart │ └── matches_tab.dart │ ├── datas │ ├── app_info.dart │ └── user.dart │ ├── helpers │ ├── app_ad_helper.dart │ ├── app_notifications.dart │ ├── app_localizations.dart │ └── app_helper.dart │ ├── api │ ├── conversations_api.dart │ ├── visits_api.dart │ ├── messages_api.dart │ ├── users_api.dart │ ├── matches_api.dart │ ├── notifications_api.dart │ ├── dislikes_api.dart │ └── likes_api.dart │ ├── dialogs │ ├── progress_dialog.dart │ ├── show_me_dialog.dart │ ├── flag_user_dialog.dart │ └── common_dialogs.dart │ ├── constants │ └── constants.dart │ └── main.dart ├── CHANGELOG.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /screenshots/Rish1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABDULKARIMALBAIK/DostiPak/HEAD/screenshots/Rish1.jpg -------------------------------------------------------------------------------- /screenshots/Rish2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABDULKARIMALBAIK/DostiPak/HEAD/screenshots/Rish2.jpg -------------------------------------------------------------------------------- /screenshots/Rish3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABDULKARIMALBAIK/DostiPak/HEAD/screenshots/Rish3.jpg -------------------------------------------------------------------------------- /screenshots/Rish4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABDULKARIMALBAIK/DostiPak/HEAD/screenshots/Rish4.jpg -------------------------------------------------------------------------------- /screenshots/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ABDULKARIMALBAIK/DostiPak/HEAD/screenshots/logo.png -------------------------------------------------------------------------------- /DostiPak/lib/widgets/my_circular_progress.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class MyCircularProgress extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Center( 7 | child: CircularProgressIndicator(valueColor: 8 | AlwaysStoppedAnimation(Theme.of(context).primaryColor)), 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/default_card_border.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | /// Default Card border 5 | RoundedRectangleBorder defaultCardBorder() { 6 | return RoundedRectangleBorder( 7 | borderRadius: BorderRadius.only( 8 | bottomLeft: Radius.circular(28.0), 9 | topRight: Radius.circular(28.0), 10 | topLeft: Radius.circular(8.0), 11 | bottomRight: Radius.circular(8.0), 12 | )); 13 | } 14 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/app_logo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppLogo extends StatelessWidget { 4 | // Variable 5 | final double? width; 6 | final double? height; 7 | 8 | AppLogo({this.width, this.height}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Center( 13 | child: Image.asset("assets/images/app_logo_dark_new.png", 14 | width: width ?? 120, height: height ?? 120)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/show_scaffold_msg.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void showScaffoldMessage({ 4 | required BuildContext context, 5 | required String message, 6 | Color? bgcolor, 7 | Duration? duration, 8 | }) { 9 | ScaffoldMessenger.of(context).showSnackBar(SnackBar( 10 | content: Text(message, style: TextStyle(fontSize: 18)), 11 | duration: duration ?? Duration(seconds: 4), 12 | backgroundColor: bgcolor ?? Theme.of(context).primaryColor, 13 | )); 14 | } 15 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/cicle_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | Widget cicleButton( 4 | {required Widget icon, 5 | required Color bgColor, 6 | required Function()? onTap, 7 | double? padding}) { 8 | return GestureDetector( 9 | child: Container( 10 | padding: EdgeInsets.all(padding ?? 5), 11 | decoration: BoxDecoration( 12 | shape: BoxShape.circle, 13 | color: bgColor, 14 | ), 15 | child: icon), 16 | onTap: onTap, 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/svg_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | 4 | class SvgIcon extends StatelessWidget { 5 | // Variables 6 | final String assetName; 7 | final double? width; 8 | final double? height; 9 | final Color? color; 10 | 11 | const SvgIcon(this.assetName, {this.width, this.height, this.color}); 12 | @override 13 | Widget build(BuildContext context) { 14 | return SvgPicture.asset( 15 | assetName, 16 | width: width ?? 23, height: height ?? 23, color: color ?? Colors.grey); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [2.0.0] 2 | 3 | Add many of features and improvments to the app : 4 | 5 | - Typing feature 6 | - Online/Offline feature 7 | - change authentication from phone number to email and password 8 | - send voice message 9 | - send sticker message 10 | - Add wallet to user 11 | - using in app purchase 12 | - Add oneSignal feature 13 | - Display ads by google mobile ads package 14 | - update image picker dialog 15 | - New logo icon and using flutter_native_splash package 16 | - change icons and make new buttons 17 | 18 | 19 | ## [1.0.0] 20 | 21 | Old version of dostiPak with all old features 22 | 23 | 24 | -------------------------------------------------------------------------------- /DostiPak/lib/models/app_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:scoped_model/scoped_model.dart'; 2 | import 'package:rishtpak/datas/app_info.dart'; 3 | 4 | class AppModel extends Model { 5 | // Variables 6 | late AppInfo appInfo; 7 | 8 | /// Create Singleton factory for [AppModel] 9 | /// 10 | static final AppModel _appModel = new AppModel._internal(); 11 | factory AppModel() { 12 | return _appModel; 13 | } 14 | AppModel._internal(); 15 | // End 16 | 17 | /// Set data to AppInfo object 18 | void setAppInfo(Map appDoc) { 19 | this.appInfo = AppInfo.fromDocument(appDoc); 20 | notifyListeners(); 21 | print('AppInfo object -> updated!'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/loading_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/widgets/default_card_border.dart'; 3 | import 'package:shimmer/shimmer.dart'; 4 | 5 | class LoadingCard extends StatelessWidget { 6 | final double? iconSize; 7 | 8 | const LoadingCard({this.iconSize}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Card( 13 | color: Colors.grey[200], 14 | clipBehavior: Clip.antiAlias, 15 | shape: defaultCardBorder(), 16 | child: Shimmer.fromColors( 17 | child: Icon(Icons.favorite_border, size: iconSize ?? 150), 18 | baseColor: Colors.grey.withAlpha(70), 19 | highlightColor: Theme.of(context).accentColor), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/users_grid.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class UsersGrid extends StatelessWidget { 4 | // Variables 5 | final ScrollController? gridViewController; 6 | final int itemCount; 7 | final Widget Function(BuildContext, int) itemBuilder; 8 | 9 | UsersGrid( 10 | { 11 | this.gridViewController, 12 | required this.itemCount, 13 | required this.itemBuilder}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return GridView.builder( 18 | controller: gridViewController, 19 | shrinkWrap: true, 20 | itemCount: itemCount, 21 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 22 | crossAxisCount: 2, 23 | mainAxisSpacing: 0, 24 | crossAxisSpacing: 0, 25 | childAspectRatio: 250 / 320, 26 | ), 27 | itemBuilder: itemBuilder, 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/processing.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/helpers/app_localizations.dart'; 3 | import 'package:rishtpak/widgets/my_circular_progress.dart'; 4 | 5 | class Processing extends StatelessWidget { 6 | final String? text; 7 | 8 | const Processing({this.text}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final i18n = AppLocalizations.of(context); 13 | return Center( 14 | child: Column( 15 | mainAxisSize: MainAxisSize.min, 16 | children: [ 17 | MyCircularProgress(), 18 | SizedBox(height: 10), 19 | Text(text ?? i18n.translate("processing"), style: TextStyle(fontSize: 18, 20 | fontWeight: FontWeight.w500)), 21 | SizedBox(height: 5), 22 | Text(i18n.translate("please_wait"), style: TextStyle(fontSize: 16)), 23 | ], 24 | ), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/notification_counter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class NotificationCounter extends StatelessWidget { 4 | // Variables 5 | final Widget icon; 6 | final int counter; 7 | 8 | NotificationCounter({required this.icon, required this.counter}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Stack( 13 | children: [ 14 | icon, 15 | new Positioned( 16 | right: 0, 17 | child: new Container( 18 | padding: EdgeInsets.all(3), 19 | decoration: new BoxDecoration( 20 | color: Theme.of(context).primaryColor, //Colors.red 21 | shape: BoxShape.circle, 22 | ), 23 | child: new Text( 24 | '$counter', 25 | style: new TextStyle(color: Colors.white, fontSize: 15), 26 | textAlign: TextAlign.center, 27 | ), 28 | ), 29 | ) 30 | ], 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/build_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/widgets/svg_icon.dart'; 3 | 4 | class BuildTitle extends StatelessWidget { 5 | final String? svgIconName; 6 | final String title; 7 | 8 | const BuildTitle({this.svgIconName, required this.title}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | /// Title 13 | return Padding( 14 | padding: const EdgeInsets.all(8.0), 15 | child: Row( 16 | children: [ 17 | // Display SVG icon 18 | if (svgIconName != null) 19 | SvgIcon("assets/icons/$svgIconName.svg", 20 | color: Theme.of(context).primaryColor, width: 30, height: 30), 21 | 22 | Padding( 23 | padding: const EdgeInsets.all(8.0), 24 | child: Text(title, 25 | style: TextStyle( 26 | fontSize: 18, 27 | color: Theme.of(context).primaryColor, 28 | fontWeight: FontWeight.w600)), 29 | ) 30 | ], 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/vip_account_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/dialogs/vip_dialog.dart'; 3 | import 'package:rishtpak/helpers/app_localizations.dart'; 4 | import 'package:rishtpak/widgets/default_card_border.dart'; 5 | 6 | class VipAccountCard extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | /// Initialization 10 | final i18n = AppLocalizations.of(context); 11 | 12 | return Card( 13 | clipBehavior: Clip.antiAlias, 14 | elevation: 4.0, 15 | shape: defaultCardBorder(), 16 | child: ListTile( 17 | leading: Image.asset("assets/images/crow_badge_small.png", 18 | width: 35, height: 35), 19 | title: Text(i18n.translate("recharge"), 20 | style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), 21 | trailing: Icon(Icons.arrow_forward), 22 | onTap: () { 23 | /// Show VIP dialog 24 | showDialog(context: context, 25 | builder: (context) => VipDialog()); 26 | }, 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/default_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DefaultButton extends StatelessWidget { 4 | // Variables 5 | final Widget child; 6 | final VoidCallback onPressed; 7 | final double? width; 8 | final double? height; 9 | 10 | DefaultButton( 11 | {required this.child, required this.onPressed, this.width, this.height}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return SizedBox( 16 | width: width, 17 | height: height ?? 45, 18 | child: ElevatedButton( 19 | child: child, 20 | style: ButtonStyle( 21 | backgroundColor: MaterialStateProperty.all(Theme.of(context).primaryColor), 22 | textStyle: MaterialStateProperty.all( 23 | TextStyle(color: Colors.white) 24 | ), 25 | shape: MaterialStateProperty.all( 26 | RoundedRectangleBorder( 27 | borderRadius: BorderRadius.circular(28), 28 | ) 29 | ) 30 | ), 31 | onPressed: onPressed, 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/no_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/widgets/svg_icon.dart'; 3 | 4 | class NoData extends StatelessWidget { 5 | // Variables 6 | final String? svgName; 7 | final Widget? icon; 8 | final String text; 9 | 10 | const NoData( 11 | { 12 | this.svgName, 13 | this.icon, 14 | required this.text}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | // Handle icon 19 | late Widget _icon; 20 | // Check svgName 21 | if (svgName != null) { 22 | // Get SVG icon 23 | _icon = SvgIcon("assets/icons/$svgName.svg", 24 | width: 100, height: 100, color: Theme.of(context).primaryColor); 25 | } else { 26 | _icon = icon!; 27 | } 28 | 29 | return Center( 30 | child: Column( 31 | mainAxisAlignment: MainAxisAlignment.center, 32 | children: [ 33 | // Show icon 34 | _icon, 35 | Text(text, 36 | style: TextStyle(fontSize: 18), textAlign: TextAlign.center), 37 | ], 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ABDULKARIM ALBAIK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /DostiPak/lib/widgets/badge.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // Badge type 4 | //enum BadgeType { circle, rectangle } 5 | 6 | class Badge extends StatelessWidget { 7 | // Variables 8 | final Widget? icon; 9 | final String? text; 10 | final TextStyle? textStyle; 11 | final Color? bgColor; 12 | final EdgeInsetsGeometry? padding; 13 | const Badge({this.icon, this.text, this.bgColor, this.textStyle, this.padding}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | decoration: BoxDecoration( 19 | color: Theme.of(context).primaryColor, //bgColor ?? Theme.of(context).primaryColor 20 | borderRadius: BorderRadius.circular(15.0)), 21 | padding: padding ?? EdgeInsets.all(6.0), 22 | child: Row( 23 | mainAxisSize: MainAxisSize.min, 24 | children: [ 25 | icon ?? Container(width: 0, height: 0), 26 | icon != null ? SizedBox(width: 5) : Container(width: 0, height: 0), 27 | Text(text ?? "", style: textStyle ?? TextStyle(color: Colors.white)), 28 | ], 29 | )); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/sign_out_button_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/helpers/app_localizations.dart'; 3 | import 'package:rishtpak/models/user_model.dart'; 4 | import 'package:rishtpak/screens/sign_in_screen.dart'; 5 | import 'package:rishtpak/widgets/default_card_border.dart'; 6 | 7 | class SignOutButtonCard extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | final i18n = AppLocalizations.of(context); 11 | return Card( 12 | clipBehavior: Clip.antiAlias, 13 | elevation: 4.0, 14 | shape: defaultCardBorder(), 15 | child: ListTile( 16 | leading: Icon(Icons.exit_to_app), 17 | title: Text(i18n.translate("sign_out"), style: TextStyle(fontSize: 18)), 18 | trailing: Icon(Icons.arrow_forward), 19 | onTap: () { 20 | // Log out button 21 | UserModel().signOut().then((_) { 22 | /// Go to login screen 23 | Future(() { 24 | Navigator.of(context).popUntil((route) => route.isFirst); 25 | Navigator.of(context).pushReplacement( 26 | MaterialPageRoute(builder: (context) => SignInScreen())); 27 | }); 28 | }); 29 | }, 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DostiPak/lib/screens/blocked_account_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:rishtpak/models/app_model.dart'; 2 | import 'package:rishtpak/helpers/app_localizations.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class BlockedAccountScreen extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | final i18n = AppLocalizations.of(context); 9 | return Scaffold( 10 | body: Center( 11 | child: Column( 12 | mainAxisAlignment: MainAxisAlignment.center, 13 | children: [ 14 | CircleAvatar( 15 | radius: 50, 16 | backgroundColor: Theme.of(context).primaryColor, 17 | child: Icon(Icons.lock_outline, size: 60, color: Colors.white), 18 | ), 19 | Text(i18n.translate("oops")), 20 | Text(i18n.translate("your_account_was_blocked"), 21 | style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), 22 | Text(i18n.translate("please_contact_support_to_active_it")), 23 | SizedBox(height: 10), 24 | Text(AppModel().appInfo.appEmail, 25 | style: TextStyle( 26 | color: Theme.of(context).primaryColor, fontSize: 18), 27 | textAlign: TextAlign.center), 28 | ], 29 | ), 30 | )); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /DostiPak/lib/generated_plugin_registrant.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // ignore_for_file: lines_longer_than_80_chars 6 | 7 | import 'package:audioplayers/audioplayers_web.dart'; 8 | import 'package:cloud_firestore_web/cloud_firestore_web.dart'; 9 | import 'package:firebase_auth_web/firebase_auth_web.dart'; 10 | import 'package:firebase_core_web/firebase_core_web.dart'; 11 | import 'package:firebase_messaging_web/firebase_messaging_web.dart'; 12 | import 'package:firebase_storage_web/firebase_storage_web.dart'; 13 | import 'package:geolocator_web/geolocator_web.dart'; 14 | import 'package:image_picker_for_web/image_picker_for_web.dart'; 15 | import 'package:location_web/location_web.dart'; 16 | import 'package:url_launcher_web/url_launcher_web.dart'; 17 | 18 | import 'package:flutter_web_plugins/flutter_web_plugins.dart'; 19 | 20 | // ignore: public_member_api_docs 21 | void registerPlugins(Registrar registrar) { 22 | AudioplayersPlugin.registerWith(registrar); 23 | FirebaseFirestoreWeb.registerWith(registrar); 24 | FirebaseAuthWeb.registerWith(registrar); 25 | FirebaseCoreWeb.registerWith(registrar); 26 | FirebaseMessagingWeb.registerWith(registrar); 27 | FirebaseStorageWeb.registerWith(registrar); 28 | GeolocatorPlugin.registerWith(registrar); 29 | ImagePickerPlugin.registerWith(registrar); 30 | LocationWebPlugin.registerWith(registrar); 31 | UrlLauncherPlugin.registerWith(registrar); 32 | registrar.registerMessageHandler(); 33 | } 34 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/user_gallery.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/models/user_model.dart'; 3 | import 'package:rishtpak/widgets/gallery_image_card.dart'; 4 | 5 | class UserGallery extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return GridView.builder( 9 | physics: ScrollPhysics(), 10 | itemCount: 9, 11 | shrinkWrap: true, 12 | gridDelegate: 13 | SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), 14 | itemBuilder: (context, index) { 15 | /// Local variables 16 | String? imageUrl; 17 | BoxFit boxFit = BoxFit.none; 18 | 19 | dynamic imageProvider = 20 | AssetImage("assets/images/camera.png"); 21 | 22 | if (!UserModel().userIsVip && index > 3) { 23 | imageProvider = AssetImage("assets/images/crow_badge_small.png"); 24 | } 25 | 26 | /// Check gallery 27 | if (UserModel().user.userGallery != null) { 28 | // Check image index 29 | if (UserModel().user.userGallery!['image_$index'] != null) { 30 | // Get image link 31 | imageUrl = UserModel().user.userGallery!['image_$index']; 32 | // Get image provider 33 | imageProvider = 34 | NetworkImage(UserModel().user.userGallery!['image_$index']); 35 | // Set boxFit 36 | boxFit = BoxFit.cover; 37 | } 38 | } 39 | /// Show image widget 40 | return GalleryImageCard( 41 | imageProvider: imageProvider, 42 | boxFit: boxFit, 43 | imageUrl: imageUrl, 44 | index: index, 45 | ); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/terms_of_service_row.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/helpers/app_helper.dart'; 3 | import 'package:rishtpak/helpers/app_localizations.dart'; 4 | 5 | class TermsOfServiceRow extends StatelessWidget { 6 | // Params 7 | final Color color; 8 | 9 | TermsOfServiceRow({this.color = Colors.white}); 10 | 11 | // Private variables 12 | final _appHelper = AppHelper(); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | final i18n = AppLocalizations.of(context); 17 | 18 | return Row( 19 | mainAxisAlignment: MainAxisAlignment.center, 20 | children: [ 21 | GestureDetector( 22 | child: Text( 23 | i18n.translate("terms_of_service"), 24 | style: TextStyle( 25 | color: color, 26 | fontSize: 17, 27 | decoration: TextDecoration.underline, 28 | fontWeight: FontWeight.bold), 29 | ), 30 | onTap: () { 31 | // Open terms of service page in browser 32 | _appHelper.openTermsPage(); 33 | }, 34 | ), 35 | Text( 36 | ' | ', 37 | style: TextStyle( 38 | color: color, fontSize: 17, fontWeight: FontWeight.bold), 39 | ), 40 | GestureDetector( 41 | child: Text( 42 | i18n.translate("privacy_policy"), 43 | style: TextStyle( 44 | color: color, 45 | fontSize: 17, 46 | decoration: TextDecoration.underline, 47 | fontWeight: FontWeight.bold), 48 | ), 49 | onTap: () { 50 | // Open privacy policy page in browser 51 | _appHelper.openPrivacyPage(); 52 | }, 53 | ), 54 | ], 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /DostiPak/lib/screens/passport_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:rishtpak/constants/constants.dart'; 4 | import 'package:rishtpak/helpers/app_localizations.dart'; 5 | import 'package:rishtpak/models/user_model.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:place_picker/entities/localization_item.dart'; 8 | import 'package:place_picker/place_picker.dart'; 9 | 10 | class PassportScreen extends StatelessWidget { 11 | 12 | // Get Google Maps API KEY 13 | String _getGoogleMapsAPIkey() { 14 | // Check the current Platform 15 | if (Platform.isAndroid) { 16 | // For Android 17 | return ANDROID_MAPS_API_KEY; 18 | } else if (Platform.isIOS) { 19 | // For iOS 20 | return IOS_MAPS_API_KEY; 21 | } else { 22 | return "Unknown platform"; 23 | } 24 | 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | /// Initialization 30 | final _i18n = AppLocalizations.of(context); 31 | 32 | return Scaffold( 33 | appBar: AppBar( 34 | title: Text(_i18n.translate('travel_to_any_country_or_city')), 35 | ), 36 | body: PlacePicker( 37 | _getGoogleMapsAPIkey(), 38 | displayLocation: LatLng( 39 | UserModel().user.userGeoPoint.latitude, 40 | UserModel().user.userGeoPoint.longitude, 41 | ), 42 | localizationItem: LocalizationItem( 43 | languageCode: _i18n.translate('lang'), 44 | nearBy: _i18n.translate('nearby_places'), 45 | findingPlace: _i18n.translate('finding_place'), 46 | noResultsFound: _i18n.translate('no_results_found'), 47 | unnamedLocation: _i18n.translate('unnamed_location'), 48 | tapToSelectLocation: 49 | _i18n.translate('tap_here_to_select_this_location'), 50 | ), 51 | ), 52 | ); 53 | } 54 | } -------------------------------------------------------------------------------- /DostiPak/lib/tabs/profile_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/models/user_model.dart'; 3 | import 'package:rishtpak/widgets/app_section_card.dart'; 4 | import 'package:rishtpak/widgets/delete_account_button.dart'; 5 | import 'package:rishtpak/widgets/profile_basic_info_card.dart'; 6 | import 'package:rishtpak/widgets/profile_statistics_card.dart'; 7 | import 'package:rishtpak/widgets/sign_out_button_card.dart'; 8 | import 'package:rishtpak/widgets/vip_account_card.dart'; 9 | import 'package:scoped_model/scoped_model.dart'; 10 | 11 | class ProfileTab extends StatelessWidget { 12 | // Variables 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return SingleChildScrollView( 17 | padding: const EdgeInsets.all(8.0), 18 | child: ScopedModelDescendant( 19 | builder: (context, child, userModel) { 20 | return Column( 21 | crossAxisAlignment: CrossAxisAlignment.start, 22 | children: [ 23 | /// Basic profile info 24 | ProfileBasicInfoCard(), 25 | 26 | SizedBox(height: 10), 27 | 28 | /// Profile Statistics Card 29 | ProfileStatisticsCard(), 30 | 31 | SizedBox(height: 10), 32 | 33 | /// Show VIP dialog 34 | if(UserModel().user.userGender != "affiliate") //Only Male and female 35 | VipAccountCard(), 36 | 37 | SizedBox(height: 10), 38 | 39 | /// App Section Card 40 | AppSectionCard(), 41 | 42 | SizedBox(height: 20), 43 | 44 | /// Sign out button card 45 | SignOutButtonCard(), 46 | 47 | SizedBox(height: 25), 48 | 49 | /// Delete Account Button 50 | DeleteAccountButton(), 51 | 52 | SizedBox(height: 25), 53 | 54 | ], 55 | ); 56 | }), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/delete_account_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/dialogs/common_dialogs.dart'; 3 | import 'package:rishtpak/helpers/app_localizations.dart'; 4 | import 'package:rishtpak/models/user_model.dart'; 5 | import 'package:rishtpak/screens/delete_account_screen.dart'; 6 | import 'package:rishtpak/widgets/default_button.dart'; 7 | 8 | class DeleteAccountButton extends StatelessWidget { 9 | @override 10 | Widget build(BuildContext context) { 11 | final i18n = AppLocalizations.of(context); 12 | return Center( 13 | child: DefaultButton( 14 | child: Text(i18n.translate("delete_account"), 15 | style: TextStyle(fontSize: 18)), 16 | onPressed: () { 17 | /// Delete account 18 | /// 19 | /// Confirm dialog 20 | infoDialog(context, 21 | icon: CircleAvatar( 22 | backgroundColor: Colors.red, 23 | child: Icon(Icons.close, color: Colors.white), 24 | ), 25 | title: '${i18n.translate("delete_account")} ?', 26 | message: i18n.translate( 27 | 'all_your_profile_data_will_be_permanently_deleted'), 28 | negativeText: i18n.translate("CANCEL"), 29 | positiveText: i18n.translate("DELETE"), 30 | negativeAction: () => Navigator.of(context).pop(), 31 | positiveAction: () async { 32 | // Close confirm dialog 33 | Navigator.of(context).pop(); 34 | 35 | // Log out first 36 | UserModel().signOut().then((_) { 37 | /// Go to delete account screen 38 | Future(() { 39 | Navigator.of(context).popUntil((route) => route.isFirst); 40 | Navigator.of(context).pushReplacement(MaterialPageRoute( 41 | builder: (context) => DeleteAccountScreen())); 42 | }); 43 | }); 44 | }); 45 | }, 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /DostiPak/lib/datas/app_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:rishtpak/constants/constants.dart'; 2 | 3 | class AppInfo { 4 | /// Variables 5 | final int androidAppCurrentVersion; 6 | final int iosAppCurrentVersion; 7 | final String androidPackageName; 8 | final String iOsAppId; 9 | final String appEmail; 10 | final String privacyPolicyUrl; 11 | final String termsOfServicesUrl; 12 | final String firebaseServerKey; 13 | final List subscriptionIds; 14 | final double freeAccountMaxDistance; 15 | final double vipAccountMaxDistance; 16 | 17 | /// Constructor 18 | AppInfo( 19 | { 20 | required this.androidAppCurrentVersion, 21 | required this.iosAppCurrentVersion, 22 | required this.androidPackageName, 23 | required this.iOsAppId, 24 | required this.appEmail, 25 | required this.privacyPolicyUrl, 26 | required this.termsOfServicesUrl, 27 | required this.firebaseServerKey, 28 | required this.subscriptionIds, 29 | required this.freeAccountMaxDistance, 30 | required this.vipAccountMaxDistance, 31 | }); 32 | 33 | /// factory AppInfo object 34 | factory AppInfo.fromDocument(Map doc) { 35 | return AppInfo( 36 | androidAppCurrentVersion: doc[ANDROID_APP_CURRENT_VERSION] ?? 1, 37 | iosAppCurrentVersion: doc[IOS_APP_CURRENT_VERSION] ?? 1, 38 | androidPackageName: doc[ANDROID_PACKAGE_NAME] ?? '', 39 | iOsAppId: doc[IOS_APP_ID] ?? '', 40 | appEmail: doc[APP_EMAIL] ?? '', 41 | privacyPolicyUrl: doc[PRIVACY_POLICY_URL] ?? '', 42 | termsOfServicesUrl: doc[TERMS_OF_SERVICE_URL] ?? '', 43 | firebaseServerKey: doc[FIREBASE_SERVER_KEY] ?? '', 44 | subscriptionIds: List.from(doc[STORE_SUBSCRIPTION_IDS] ?? []), 45 | freeAccountMaxDistance: doc[FREE_ACCOUNT_MAX_DISTANCE] == null 46 | ? 100 47 | : doc[FREE_ACCOUNT_MAX_DISTANCE].toDouble(), 48 | vipAccountMaxDistance: doc[VIP_ACCOUNT_MAX_DISTANCE] == null 49 | ? 200 50 | : doc[VIP_ACCOUNT_MAX_DISTANCE].toDouble(), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/show_like_or_dislike.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/plugins/swipe_stack/swipe_stack.dart'; 3 | 4 | class ShowLikeOrDislike extends StatelessWidget { 5 | // Variables 6 | final SwiperPosition position; 7 | 8 | ShowLikeOrDislike({required this.position}); 9 | 10 | Widget _likedUser() { 11 | return Positioned( 12 | top: 50, 13 | left: 20, 14 | child: RotationTransition( 15 | turns: new AlwaysStoppedAnimation(-15 / 360), 16 | child: Container( 17 | padding: const EdgeInsets.all(8), 18 | decoration: BoxDecoration( 19 | border: Border.all(color: Colors.green, width: 4), 20 | borderRadius: BorderRadius.circular(8), 21 | ), 22 | child: Text('LIKE', 23 | style: TextStyle( 24 | fontSize: 50, 25 | color: Colors.green, 26 | fontWeight: FontWeight.bold)), 27 | ), 28 | ), 29 | ); 30 | } 31 | 32 | Widget _dislikedUser() { 33 | return Positioned( 34 | top: 50, 35 | right: 20, 36 | child: RotationTransition( 37 | turns: new AlwaysStoppedAnimation(15 / 360), 38 | child: Container( 39 | padding: const EdgeInsets.all(8), 40 | decoration: BoxDecoration( 41 | border: Border.all(color: Colors.red, width: 4), 42 | borderRadius: BorderRadius.circular(8), 43 | ), 44 | child: Text('DISLIKE', 45 | style: TextStyle( 46 | fontSize: 50, 47 | color: Colors.red, 48 | fontWeight: FontWeight.bold)), 49 | ), 50 | ), 51 | ); 52 | } 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | Widget content = Positioned(child: Container()); 57 | 58 | /// Control swipe position 59 | switch (position) { 60 | case SwiperPosition.None: 61 | break; 62 | case SwiperPosition.Left: 63 | content = _dislikedUser(); 64 | break; 65 | case SwiperPosition.Right: 66 | content = _likedUser(); 67 | break; 68 | } 69 | return content; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /DostiPak/lib/helpers/app_ad_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:rishtpak/constants/constants.dart'; 4 | import 'package:rishtpak/models/user_model.dart'; 5 | import 'package:google_mobile_ads/google_mobile_ads.dart'; 6 | 7 | class AppAdHelper { 8 | // Local Variables 9 | static InterstitialAd? _interstitialAd; 10 | // 11 | 12 | // Get Interstitial Ad ID 13 | static String get _interstitialID { 14 | if (Platform.isAndroid) { 15 | return ANDROID_INTERSTITIAL_ID; 16 | } else if (Platform.isIOS) { 17 | return IOS_INTERSTITIAL_ID; 18 | } else { 19 | throw new UnsupportedError("Unsupported platform"); 20 | } 21 | } 22 | 23 | // Ad Event Listener 24 | static final AdListener _adListener = AdListener( 25 | // Called when an ad is successfully received. 26 | onAdLoaded: (Ad ad) { 27 | print('Ad loaded.'); 28 | _interstitialAd?.show(); 29 | }, 30 | // Called when an ad request failed. 31 | onAdFailedToLoad: (Ad ad, LoadAdError error) { 32 | ad.dispose(); 33 | print('Ad failed to load: $error'); 34 | }, 35 | // Called when an ad opens an overlay that covers the screen. 36 | onAdOpened: (Ad ad) => print('Ad opened.'), 37 | // Called when an ad removes an overlay that covers the screen. 38 | onAdClosed: (Ad ad) { 39 | ad.load(); 40 | print('Ad closed.'); 41 | }, 42 | // Called when an ad is in the process of leaving the application. 43 | onApplicationExit: (Ad ad) => print('Left application.'), 44 | ); 45 | 46 | // Create Interstitial Ad 47 | static Future _createInterstitialAd() async { 48 | _interstitialAd = InterstitialAd( 49 | adUnitId: _interstitialID, 50 | request: AdRequest(), 51 | listener: _adListener); 52 | // Load InterstitialAd Ad 53 | _interstitialAd?.load(); 54 | } 55 | 56 | // Show Interstitial Ads for Non VIP Users 57 | static Future showInterstitialAd() async { 58 | /// Check User VIP Status 59 | if (!UserModel().userIsVip) { 60 | _createInterstitialAd(); 61 | } else { 62 | print('User is VIP'); 63 | } 64 | } 65 | 66 | // Dispose Interstitial Ad 67 | static void disposeInterstitialAd() { 68 | _interstitialAd?.dispose(); 69 | _interstitialAd = null; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /DostiPak/lib/helpers/app_notifications.dart: -------------------------------------------------------------------------------- 1 | import 'package:rishtpak/datas/user.dart'; 2 | import 'package:rishtpak/dialogs/common_dialogs.dart'; 3 | import 'package:rishtpak/models/user_model.dart'; 4 | import 'package:rishtpak/screens/profile_likes_screen.dart'; 5 | import 'package:rishtpak/screens/profile_screen.dart'; 6 | import 'package:rishtpak/screens/profile_visits_screen.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | class AppNotifications { 10 | /// Handle notification click for push 11 | /// and database notifications 12 | Future onNotificationClick( 13 | BuildContext context, { 14 | required String nType, 15 | required String nSenderId, 16 | required String nMessage, 17 | // Call Info object 18 | String? nCallInfo, 19 | }) async { 20 | /// Control notification type 21 | switch (nType) { 22 | case 'like': 23 | 24 | /// Check user VIP account 25 | if (UserModel().userIsVip) { 26 | /// Go direct to user profile 27 | _goToProfileScreen(context, nSenderId); 28 | } else { 29 | /// Go Profile Likes Screen 30 | Navigator.of(context).push( 31 | MaterialPageRoute(builder: (context) => ProfileLikesScreen())); 32 | } 33 | break; 34 | case 'visit': 35 | 36 | /// Check user VIP account 37 | if (UserModel().userIsVip) { 38 | /// Go direct to user profile 39 | _goToProfileScreen(context, nSenderId); 40 | } else { 41 | /// Go Profile Visits Screen 42 | Navigator.of(context).push( 43 | MaterialPageRoute(builder: (context) => ProfileVisitsScreen())); 44 | } 45 | break; 46 | 47 | case 'alert': 48 | 49 | /// Show dialog info 50 | Future(() { 51 | infoDialog(context, message: nMessage); 52 | }); 53 | 54 | break; 55 | 56 | } 57 | } 58 | 59 | /// Navigate to profile screen 60 | void _goToProfileScreen(BuildContext context, userSenderId) async { 61 | /// Get updated user info 62 | final User user = await UserModel().getUserObject(userSenderId); 63 | 64 | /// Go direct to profile 65 | Navigator.of(context).push( 66 | MaterialPageRoute(builder: (context) => ProfileScreen(user: user))); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /DostiPak/lib/api/conversations_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:rishtpak/constants/constants.dart'; 4 | import 'package:rishtpak/models/user_model.dart'; 5 | 6 | class ConversationsApi { 7 | /// Get firestore instance 8 | /// 9 | final _firestore = FirebaseFirestore.instance; 10 | 11 | /// Save last conversation in database 12 | Future saveConversation({ 13 | required String type, 14 | required String senderId, 15 | required String receiverId, 16 | required String userPhotoLink, 17 | required String userFullName, 18 | required String textMsg, 19 | required bool isRead, 20 | }) async { 21 | await _firestore 22 | .collection(C_CONNECTIONS) 23 | .doc(senderId) 24 | .collection(C_CONVERSATIONS) 25 | .doc(receiverId) 26 | .set({ 27 | USER_ID: receiverId, 28 | USER_PROFILE_PHOTO: userPhotoLink, 29 | USER_FULLNAME: userFullName, 30 | MESSAGE_TYPE: type, 31 | LAST_MESSAGE: textMsg, 32 | MESSAGE_READ: isRead, 33 | TIMESTAMP: DateTime.now(), 34 | }).then((value) { 35 | debugPrint('saveConversation() -> succes'); 36 | }).catchError((e) { 37 | print('saveConversation() -> error: $e'); 38 | }); 39 | } 40 | 41 | /// Get stream conversations for current user 42 | Stream getConversations() { 43 | return _firestore 44 | .collection(C_CONNECTIONS) 45 | .doc(UserModel().user.userId) 46 | .collection(C_CONVERSATIONS) 47 | .orderBy(TIMESTAMP, descending: true) 48 | .snapshots(); 49 | } 50 | 51 | /// Delete current user conversation 52 | Future deleteConverce(String withUserId, 53 | {bool isDoubleDel = false}) async { 54 | // For current user 55 | await _firestore 56 | .collection(C_CONNECTIONS) 57 | .doc(UserModel().user.userId) 58 | .collection(C_CONVERSATIONS) 59 | .doc(withUserId) 60 | .delete(); 61 | // Delete the current user id from onother user conversation list 62 | if (isDoubleDel) { 63 | await _firestore 64 | .collection(C_CONNECTIONS) 65 | .doc(withUserId) 66 | .collection(C_CONVERSATIONS) 67 | .doc(UserModel().user.userId) 68 | .delete(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /DostiPak/lib/helpers/app_localizations.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:convert'; 3 | 4 | import 'package:rishtpak/constants/constants.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/services.dart'; 7 | 8 | class AppLocalizations { 9 | 10 | final Locale locale; 11 | 12 | // Constructor 13 | AppLocalizations(this.locale); 14 | 15 | // Helper method to keep the code in widgets concise 16 | // Localizations are accessed using an InheritedWidget "of syntax" 17 | static AppLocalizations of(BuildContext context) { 18 | return Localizations.of(context, AppLocalizations)!; 19 | } 20 | 21 | 22 | // Localized strings map 23 | late Map _localizedStrings; 24 | 25 | // Load the json language file from the "lang folder" 26 | Future load() async { 27 | final String jsonLang = 28 | await rootBundle.loadString('assets/lang/${locale.languageCode}.json'); 29 | // Decode string result 30 | final Map langMap = json.decode(jsonLang); 31 | _localizedStrings = 32 | langMap.map((key, value) => MapEntry(key, value.toString())); 33 | 34 | return true; 35 | } 36 | 37 | // Translate method - will be called from every widget which needs a localized text 38 | String translate(String key) { 39 | return _localizedStrings[key] ?? ''; 40 | } 41 | 42 | 43 | // Static member to have a simple access to the delegate from the MaterialApp 44 | static const LocalizationsDelegate delegate = 45 | _AppLocalizationsDelegate(); 46 | } 47 | 48 | 49 | 50 | // LocalizationsDelegate is a factory for a set of localized resources 51 | // In this case, the localized strings will be gotten in an AppLocalizations 52 | 53 | class _AppLocalizationsDelegate extends LocalizationsDelegate { 54 | 55 | // This delegate will never change (it doesn't even have fields) 56 | const _AppLocalizationsDelegate(); 57 | 58 | @override 59 | bool isSupported(Locale locale) { 60 | return SUPPORTED_LOCALES.contains(locale); 61 | } 62 | 63 | @override 64 | Future load(Locale locale) async { 65 | // Init AppLocalizations class where the json loading actually runs 66 | final AppLocalizations localizations = new AppLocalizations(locale); 67 | await localizations.load(); 68 | return localizations; 69 | } 70 | 71 | @override 72 | bool shouldReload(LocalizationsDelegate old) => false; 73 | 74 | } -------------------------------------------------------------------------------- /DostiPak/lib/screens/update_app_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:rishtpak/constants/constants.dart'; 5 | import 'package:rishtpak/helpers/app_localizations.dart'; 6 | import 'package:rishtpak/helpers/app_helper.dart'; 7 | import 'package:rishtpak/widgets/app_logo.dart'; 8 | 9 | class UpdateAppScreen extends StatelessWidget { 10 | // Variables 11 | final AppHelper _appHelper = AppHelper(); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | final _i18n = AppLocalizations.of(context); 16 | return Scaffold( 17 | appBar: AppBar( 18 | title: Text(_i18n.translate('update_application')), 19 | ), 20 | body: SingleChildScrollView( 21 | padding: const EdgeInsets.all(25), 22 | child: Center( 23 | child: Column( 24 | mainAxisAlignment: MainAxisAlignment.center, 25 | children: [ 26 | /// App logo 27 | AppLogo(), 28 | SizedBox(height: 10), 29 | 30 | /// App name 31 | Text(APP_NAME, 32 | style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold)), 33 | SizedBox(height: 20), 34 | Text(_i18n.translate('app_new_version'), 35 | style: TextStyle( 36 | fontSize: 20, 37 | color: Theme.of(context).primaryColor, 38 | fontWeight: FontWeight.bold), 39 | textAlign: TextAlign.center), 40 | SizedBox(height: 10), 41 | Text(_i18n.translate("please_install_it_now"), 42 | style: TextStyle(fontSize: 18), textAlign: TextAlign.center), 43 | SizedBox(height: 5), 44 | Text(_i18n.translate("don_worry_your_data_will_not_be_lost"), 45 | style: TextStyle(fontSize: 18), textAlign: TextAlign.center), 46 | Divider(thickness: 1), 47 | Text(_i18n.translate("click_this_button_to_install"), 48 | style: TextStyle(fontSize: 18), textAlign: TextAlign.center), 49 | GestureDetector( 50 | child: Image.asset(Platform.isAndroid 51 | ? "assets/images/google_play_badge.png" 52 | : "assets/images/apple_store_badge.png"), 53 | onTap: () { 54 | /// Open app store - Google Play / Apple Store 55 | _appHelper.openAppStore(); 56 | }, 57 | ) 58 | ], 59 | ), 60 | ), 61 | ), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/app_section_card.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:rishtpak/helpers/app_helper.dart'; 5 | import 'package:rishtpak/helpers/app_localizations.dart'; 6 | import 'package:rishtpak/screens/about_us_screen.dart'; 7 | import 'package:rishtpak/widgets/default_card_border.dart'; 8 | import 'package:rishtpak/widgets/svg_icon.dart'; 9 | 10 | class AppSectionCard extends StatelessWidget { 11 | // Variables 12 | final AppHelper _appHelper = AppHelper(); 13 | // Text style 14 | final _textStyle = TextStyle( 15 | color: Colors.black, 16 | fontSize: 16.0, 17 | fontWeight: FontWeight.w500, 18 | ); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | /// Initialization 23 | final i18n = AppLocalizations.of(context); 24 | 25 | return Card( 26 | elevation: 4.0, 27 | shape: defaultCardBorder(), 28 | child: Column( 29 | crossAxisAlignment: CrossAxisAlignment.start, 30 | children: [ 31 | Padding( 32 | padding: const EdgeInsets.all(8.0), 33 | child: Text(i18n.translate("application"), 34 | style: TextStyle(fontSize: 20, color: Colors.grey), 35 | textAlign: TextAlign.left), 36 | ), 37 | ListTile( 38 | leading: Icon(Icons.info_outline), 39 | title: Text(i18n.translate("about_us"), style: _textStyle), 40 | onTap: () { 41 | /// Go to About us screen 42 | Navigator.of(context) 43 | .push(MaterialPageRoute(builder: (context) => AboutScreen())); 44 | }, 45 | ), 46 | Divider(height: 0), 47 | ListTile( 48 | leading: Icon(Icons.share), 49 | title: 50 | Text(i18n.translate("share_with_friends"), style: _textStyle), 51 | onTap: () async { 52 | /// Share app 53 | _appHelper.shareApp(); 54 | }, 55 | ), 56 | Divider(height: 0), 57 | ListTile( 58 | leading: 59 | SvgIcon("assets/icons/star_icon.svg", width: 22, height: 22), 60 | title: Text( 61 | i18n.translate(Platform.isAndroid 62 | ? "rate_on_play_store" 63 | : "rate_on_app_store"), 64 | style: _textStyle), 65 | onTap: () async { 66 | /// Rate app 67 | _appHelper.reviewApp(); 68 | }, 69 | ), 70 | Divider(height: 0), 71 | ListTile( 72 | leading: 73 | SvgIcon("assets/icons/lock_icon.svg", width: 22, height: 22), 74 | title: Text(i18n.translate("privacy_policy"), style: _textStyle), 75 | onTap: () async { 76 | /// Go to privacy policy 77 | _appHelper.openPrivacyPage(); 78 | }, 79 | ), 80 | Divider(height: 0), 81 | ListTile( 82 | leading: Icon(Icons.copyright_outlined, color: Colors.grey), 83 | title: Text(i18n.translate("terms_of_service"), style: _textStyle), 84 | onTap: () async { 85 | /// Go to privacy policy 86 | _appHelper.openTermsPage(); 87 | }, 88 | ), 89 | ], 90 | ), 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /DostiPak/lib/dialogs/progress_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ProgressDialog { 4 | // Paramiteres 5 | final BuildContext context; 6 | bool isDismissible = true; 7 | 8 | // Local variables 9 | late BuildContext _dismissingContext; 10 | 11 | // Constructor 12 | ProgressDialog(this.context, {this.isDismissible = true}); 13 | 14 | // Show progress dialog 15 | Future show(String message) async { 16 | try { 17 | showDialog( 18 | context: context, 19 | barrierDismissible: isDismissible, 20 | builder: (BuildContext context) { 21 | _dismissingContext = context; 22 | return WillPopScope( 23 | onWillPop: () async => isDismissible, 24 | child: Dialog( 25 | backgroundColor: Colors.white, 26 | insetAnimationCurve: Curves.easeInOut, 27 | insetAnimationDuration: Duration(milliseconds: 100), 28 | elevation: 8.0, 29 | shape: RoundedRectangleBorder( 30 | borderRadius: BorderRadius.all(Radius.circular(8.8))), 31 | child: _dialog(message)), 32 | ); 33 | }, 34 | ); 35 | // Delaying the function for 200 milliseconds 36 | // [Default transitionDuration of DialogRoute] 37 | await Future.delayed(Duration(milliseconds: 200)); 38 | debugPrint('show progress dialog() -> sucess'); 39 | return true; 40 | } catch (err) { 41 | debugPrint('Exception while showing the progress dialog'); 42 | debugPrint(err.toString()); 43 | return false; 44 | } 45 | } 46 | 47 | // Build progress dialog 48 | Widget _dialog(String message) { 49 | return Container( 50 | padding: const EdgeInsets.all(8.0), 51 | child: Column( 52 | mainAxisSize: MainAxisSize.min, 53 | children: [ 54 | // Row body 55 | Row( 56 | mainAxisSize: MainAxisSize.min, 57 | children: [ 58 | const SizedBox(width: 8.0), 59 | // Show progress indicator 60 | Padding( 61 | padding: const EdgeInsets.all(8.0), 62 | child: Center( 63 | child: CircularProgressIndicator( 64 | valueColor: AlwaysStoppedAnimation( 65 | Theme.of(context).primaryColor)), 66 | ), 67 | ), 68 | const SizedBox(width: 8.0), 69 | // Show text 70 | Expanded( 71 | child: Text( 72 | message, 73 | textAlign: TextAlign.left, 74 | style: TextStyle( 75 | color: Colors.black, fontSize: 18.0, 76 | fontWeight: FontWeight.w600), 77 | ), 78 | ), 79 | const SizedBox(width: 8.0) 80 | ], 81 | ), 82 | ], 83 | ), 84 | ); 85 | } 86 | 87 | // Hide progress dialog 88 | Future hide() async { 89 | try { 90 | Navigator.of(_dismissingContext).pop(); 91 | debugPrint('ProgressDialog dismissed'); 92 | return Future.value(true); 93 | } catch (err) { 94 | debugPrint('Seems there is an issue hiding dialog'); 95 | debugPrint(err.toString()); 96 | return Future.value(false); 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /DostiPak/lib/datas/user.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:rishtpak/constants/constants.dart'; 3 | 4 | class User { 5 | /// User info 6 | //NEW 7 | final double userWallet; 8 | final bool userOnline; 9 | final Map? userTyping; 10 | //NEW 11 | final String userId; 12 | final String userProfilePhoto; 13 | final String userFullname; 14 | final String userGender; 15 | final int userBirthDay; 16 | final int userBirthMonth; 17 | final int userBirthYear; 18 | final String userSchool; 19 | final String userJobTitle; 20 | final String userBio; 21 | final String userPhoneNumber; 22 | final String userEmail; 23 | final String userCountry; 24 | final String userLocality; 25 | final GeoPoint userGeoPoint; 26 | final String userStatus; 27 | final bool userIsVerified; 28 | final String userLevel; 29 | final DateTime userRegDate; 30 | final DateTime userLastLogin; 31 | final String userDeviceToken; 32 | final int userTotalLikes; 33 | final int userTotalVisits; 34 | final int userTotalDisliked; 35 | final Map? userGallery; 36 | final Map? userSettings; 37 | 38 | // Constructor 39 | User({ 40 | required this.userWallet, 41 | required this.userOnline, 42 | required this.userTyping, 43 | required this.userId, 44 | required this.userProfilePhoto, 45 | required this.userFullname, 46 | required this.userGender, 47 | required this.userBirthDay, 48 | required this.userBirthMonth, 49 | required this.userBirthYear, 50 | required this.userSchool, 51 | required this.userJobTitle, 52 | required this.userBio, 53 | required this.userPhoneNumber, 54 | required this.userEmail, 55 | required this.userGallery, 56 | required this.userCountry, 57 | required this.userLocality, 58 | required this.userGeoPoint, 59 | required this.userSettings, 60 | required this.userStatus, 61 | required this.userLevel, 62 | required this.userIsVerified, 63 | required this.userRegDate, 64 | required this.userLastLogin, 65 | required this.userDeviceToken, 66 | required this.userTotalLikes, 67 | required this.userTotalVisits, 68 | required this.userTotalDisliked, 69 | }); 70 | 71 | /// factory user object 72 | factory User.fromDocument(Map doc) { 73 | return User( 74 | userWallet: double.parse(doc[USER_WALLET].toString()), 75 | userOnline: doc[USER_ONLINE], 76 | userTyping: doc[USER_TYPING], 77 | userId: doc[USER_ID], 78 | userProfilePhoto: doc[USER_PROFILE_PHOTO], 79 | userFullname: doc[USER_FULLNAME], 80 | userGender: doc[USER_GENDER], 81 | userBirthDay: doc[USER_BIRTH_DAY], 82 | userBirthMonth: doc[USER_BIRTH_MONTH], 83 | userBirthYear: doc[USER_BIRTH_YEAR], 84 | userSchool: doc[USER_SCHOOL] ?? '', 85 | userJobTitle: doc[USER_JOB_TITLE] ?? '', 86 | userBio: doc[USER_BIO] ?? '', 87 | userPhoneNumber: doc[USER_PHONE_NUMBER] ?? '', 88 | userEmail: doc[USER_EMAIL] ?? '', 89 | userGallery: doc[USER_GALLERY], 90 | userCountry: doc[USER_COUNTRY] ?? '', 91 | userLocality: doc[USER_LOCALITY] ?? '', 92 | userGeoPoint: doc[USER_GEO_POINT]['geopoint'], 93 | userSettings: doc[USER_SETTINGS], 94 | userStatus: doc[USER_STATUS], 95 | userIsVerified: doc[USER_IS_VERIFIED] ?? false, 96 | userLevel: doc[USER_LEVEL], 97 | userRegDate: doc[USER_REG_DATE].toDate(), // Firestore Timestamp 98 | userLastLogin: doc[USER_LAST_LOGIN].toDate(), // Firestore Timestamp 99 | userDeviceToken: doc[USER_DEVICE_TOKEN], 100 | userTotalLikes: doc[USER_TOTAL_LIKES] ?? 0, 101 | userTotalVisits: doc[USER_TOTAL_VISITS] ?? 0, 102 | userTotalDisliked: doc[USER_TOTAL_DISLIKED] ?? 0, 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /DostiPak/lib/screens/splash_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:rishtpak/screens/blocked_account_screen.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:rishtpak/constants/constants.dart'; 6 | import 'package:rishtpak/helpers/app_localizations.dart'; 7 | import 'package:rishtpak/helpers/app_helper.dart'; 8 | import 'package:rishtpak/screens/update_app_screen.dart'; 9 | import 'package:rishtpak/widgets/app_logo.dart'; 10 | import 'package:rishtpak/widgets/my_circular_progress.dart'; 11 | import 'package:rishtpak/models/user_model.dart'; 12 | import 'package:rishtpak/screens/home_screen.dart'; 13 | import 'package:rishtpak/screens/sign_up_screen.dart'; 14 | import 'package:rishtpak/screens/sign_in_screen.dart'; 15 | 16 | class SplashScreen extends StatefulWidget { 17 | @override 18 | _SplashScreenState createState() => _SplashScreenState(); 19 | } 20 | 21 | class _SplashScreenState extends State { 22 | // Variables 23 | final AppHelper _appHelper = AppHelper(); 24 | late AppLocalizations _i18n; 25 | final _scaffoldkey = GlobalKey(); 26 | 27 | /// Navigate to next page 28 | void _nextScreen(screen) { 29 | // Go to next page route 30 | Future(() { 31 | Navigator.of(context).pushAndRemoveUntil( 32 | MaterialPageRoute(builder: (context) => screen), (route) => false); 33 | }); 34 | } 35 | 36 | @override 37 | void initState() { 38 | super.initState(); 39 | _appHelper.getAppStoreVersion().then((storeVersion) async { 40 | print('storeVersion: $storeVersion'); 41 | 42 | // Get hard coded App current version 43 | int appCurrentVersion = 1; 44 | // Check Platform 45 | if (Platform.isAndroid) { 46 | // Get Android version number 47 | appCurrentVersion = ANDROID_APP_VERSION_NUMBER; 48 | } else if (Platform.isIOS) { 49 | // Get iOS version number 50 | appCurrentVersion = IOS_APP_VERSION_NUMBER; 51 | } 52 | 53 | /// Compare both versions 54 | if (storeVersion > appCurrentVersion) { 55 | /// Go to update app screen 56 | _nextScreen(UpdateAppScreen()); 57 | debugPrint("Go to update screen"); 58 | } else { 59 | /// Authenticate User Account 60 | UserModel().authUserAccount( 61 | context: context, 62 | scaffoldkey: _scaffoldkey, 63 | signInScreen: () => _nextScreen(SignInScreen()), 64 | signUpScreen: () => _nextScreen(SignUpScreen()), 65 | homeScreen: () => _nextScreen(HomeScreen()), 66 | blockedScreen: () => _nextScreen(BlockedAccountScreen())); 67 | } 68 | }); 69 | } 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | _i18n = AppLocalizations.of(context); 74 | return Scaffold( 75 | key: _scaffoldkey, 76 | body: SafeArea( 77 | child: Container( 78 | color: Colors.white, 79 | child: Center( 80 | child: SingleChildScrollView( 81 | child: Column( 82 | mainAxisAlignment: MainAxisAlignment.center, 83 | children: [ 84 | 85 | AppLogo(), 86 | SizedBox(height: 10), 87 | 88 | Text( 89 | APP_NAME, 90 | style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold)), 91 | SizedBox(height: 5), 92 | 93 | Text(_i18n.translate("app_short_description"), 94 | textAlign: TextAlign.center, 95 | style: TextStyle(fontSize: 18, color: Colors.grey)), 96 | SizedBox(height: 20), 97 | 98 | MyCircularProgress() 99 | ], 100 | ), 101 | ), 102 | ), 103 | ), 104 | ), 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /DostiPak/lib/screens/sign_in_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:rishtpak/constants/constants.dart'; 2 | import 'package:rishtpak/screens/phone_number_screen.dart'; 3 | import 'package:rishtpak/widgets/app_logo.dart'; 4 | import 'package:rishtpak/widgets/default_button.dart'; 5 | import 'package:rishtpak/widgets/terms_of_service_row.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:rishtpak/helpers/app_localizations.dart'; 8 | 9 | 10 | class SignInScreen extends StatefulWidget { 11 | @override 12 | _SignInScreenState createState() => _SignInScreenState(); 13 | } 14 | 15 | class _SignInScreenState extends State { 16 | // Variables 17 | final _scaffoldKey = GlobalKey(); 18 | late AppLocalizations _i18n; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | /// Initialization 23 | _i18n = AppLocalizations.of(context); 24 | 25 | return Scaffold( 26 | key: _scaffoldKey, 27 | backgroundColor: Colors.transparent, 28 | body: Container( 29 | decoration: BoxDecoration( 30 | image: DecorationImage( 31 | image: AssetImage("assets/images/background_image.jpg"), 32 | fit: BoxFit.fill, 33 | repeat: ImageRepeat.repeatY), 34 | ), 35 | child: Container( 36 | decoration: BoxDecoration( 37 | gradient: LinearGradient( 38 | begin: Alignment.bottomRight, 39 | colors: [ 40 | Theme.of(context).primaryColor, 41 | Colors.black.withOpacity(.4)])), 42 | child: Column( 43 | mainAxisAlignment: MainAxisAlignment.center, 44 | children: [ 45 | 46 | /// App logo 47 | AppLogo(), 48 | SizedBox(height: 10), 49 | 50 | /// App name 51 | Text(APP_NAME, 52 | style: TextStyle( 53 | fontSize: 22, 54 | fontWeight: FontWeight.bold, 55 | color: Colors.white)), 56 | SizedBox(height: 20), 57 | 58 | Text(_i18n.translate("welcome_back"), 59 | textAlign: TextAlign.center, 60 | style: TextStyle(fontSize: 18, color: Colors.white)), 61 | SizedBox(height: 5), 62 | 63 | 64 | Text(_i18n.translate("app_short_description"), 65 | textAlign: TextAlign.center, 66 | style: TextStyle(fontSize: 18, color: Colors.white)), 67 | SizedBox(height: 22), 68 | 69 | /// Sign in 70 | Padding( 71 | padding: const EdgeInsets.symmetric(horizontal: 30), 72 | child: SizedBox( 73 | width: double.maxFinite, 74 | child: DefaultButton( 75 | child: Text( 76 | _i18n.translate("sign_in_with_email_and_password"), 77 | style: TextStyle(fontSize: 18)), 78 | onPressed: () { 79 | /// Go to phone number screen 80 | Navigator.of(context).push(MaterialPageRoute( 81 | builder: (context) => PhoneNumberScreen())); 82 | }, 83 | ), 84 | ), 85 | ), 86 | SizedBox(height: 15), 87 | 88 | 89 | 90 | // Terms of Service section 91 | Text( 92 | _i18n.translate("by_tapping_log_in_you_agree_with_our"), 93 | style: TextStyle( 94 | color: Colors.white, fontWeight: FontWeight.bold), 95 | textAlign: TextAlign.center, 96 | ), 97 | SizedBox( 98 | height: 7, 99 | ), 100 | 101 | 102 | TermsOfServiceRow(), 103 | SizedBox(height: 15), 104 | ], 105 | ), 106 | ), 107 | ), 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /DostiPak/lib/api/visits_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:rishtpak/api/notifications_api.dart'; 3 | import 'package:rishtpak/constants/constants.dart'; 4 | import 'package:rishtpak/models/user_model.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class VisitsApi { 8 | /// FINAL VARIABLES 9 | /// 10 | final _firestore = FirebaseFirestore.instance; 11 | final _notificationsApi = NotificationsApi(); 12 | 13 | /// Save visit in database 14 | Future _saveVisit({ 15 | required String visitedUserId, 16 | required String userDeviceToken, 17 | required String nMessage, 18 | }) async { 19 | _firestore.collection(C_VISITS).add({ 20 | VISITED_USER_ID: visitedUserId, 21 | VISITED_BY_USER_ID: UserModel().user.userId, 22 | TIMESTAMP: FieldValue.serverTimestamp() 23 | }).then((_) async { 24 | /// Update user total visits 25 | await UserModel().updateUserData( 26 | userId: visitedUserId, 27 | data: {USER_TOTAL_VISITS: FieldValue.increment(1)}); 28 | 29 | /// Save notification in database 30 | await _notificationsApi.saveNotification( 31 | nReceiverId: visitedUserId, 32 | nType: 'visit', 33 | nMessage: nMessage, 34 | ); 35 | 36 | /// Send push notification 37 | await _notificationsApi.sendPushNotification( 38 | nTitle: APP_NAME, 39 | nBody: nMessage, 40 | nType: 'visit', 41 | nSenderId: UserModel().user.userId, 42 | nUserDeviceToken: userDeviceToken); 43 | }); 44 | } 45 | 46 | /// View user profile and increment visits 47 | Future visitUserProfile( 48 | {required String visitedUserId, 49 | required String userDeviceToken, 50 | required String nMessage}) async { 51 | /// Check visit profile id: if current user does not record 52 | if (visitedUserId == UserModel().user.userId) return; 53 | 54 | /// Check if current user already visited profile 55 | _firestore 56 | .collection(C_VISITS) 57 | .where(VISITED_BY_USER_ID, isEqualTo: UserModel().user.userId) 58 | .where(VISITED_USER_ID, isEqualTo: visitedUserId) 59 | .get() 60 | .then((QuerySnapshot snapshot) async { 61 | if (snapshot.docs.isEmpty) { 62 | _saveVisit( 63 | visitedUserId: visitedUserId, 64 | userDeviceToken: userDeviceToken, 65 | nMessage: nMessage); 66 | debugPrint('visitUserProfile() -> success'); 67 | } else { 68 | print('You already visited the user'); 69 | } 70 | }).catchError((e) { 71 | print('visitUserProfile() -> error: $e'); 72 | }); 73 | } 74 | 75 | /// Get users who visited current user profile 76 | Future> getUserVisits( 77 | {bool loadMore = false, DocumentSnapshot? userLastDoc}) async { 78 | /// Build query 79 | Query usersQuery = _firestore 80 | .collection(C_VISITS) 81 | .where(VISITED_USER_ID, isEqualTo: UserModel().user.userId); 82 | 83 | /// Check loadMore 84 | if (loadMore) { 85 | usersQuery = usersQuery.startAfterDocument(userLastDoc!); 86 | } 87 | 88 | /// Finalize query and limit data 89 | usersQuery = usersQuery.orderBy(TIMESTAMP, descending: true); 90 | usersQuery = usersQuery.limit(20); 91 | 92 | final QuerySnapshot querySnapshot = await usersQuery.get().catchError((e) { 93 | print('getUserVisits() -> error: $e'); 94 | }); 95 | 96 | return querySnapshot.docs; 97 | } 98 | 99 | Future deleteVisitedUsers() async { 100 | _firestore 101 | .collection(C_VISITS) 102 | .where(VISITED_BY_USER_ID, isEqualTo: UserModel().user.userId) 103 | .get() 104 | .then((QuerySnapshot snapshot) async { 105 | /// Check docs 106 | if (snapshot.docs.isNotEmpty) { 107 | // Loop docs to be deleted 108 | snapshot.docs.forEach((doc) async { 109 | await doc.reference.delete(); 110 | }); 111 | debugPrint('deleteVisitedUsers() -> deleted'); 112 | } 113 | }); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /DostiPak/lib/api/messages_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:rishtpak/api/conversations_api.dart'; 3 | import 'package:firebase_storage/firebase_storage.dart'; 4 | import 'package:rishtpak/constants/constants.dart'; 5 | import 'package:rishtpak/models/user_model.dart'; 6 | 7 | class MessagesApi { 8 | /// FINAL VARIABLES 9 | /// 10 | final _firestore = FirebaseFirestore.instance; 11 | final _conversationsApi = ConversationsApi(); 12 | 13 | /// Get stream messages for current user 14 | Stream getMessages(String withUserId) { 15 | return _firestore 16 | .collection(C_MESSAGES) 17 | .doc(UserModel().user.userId) 18 | .collection(withUserId) 19 | .orderBy(TIMESTAMP) 20 | .snapshots(); 21 | } 22 | 23 | /// Save chat message 24 | Future saveMessage({ 25 | required String type, 26 | required String senderId, 27 | required String receiverId, 28 | required String fromUserId, 29 | required String userPhotoLink, 30 | required String userFullName, 31 | required String textMsg, 32 | required String imgLink, 33 | required String urlAudioPath, 34 | required String urlGifPath, 35 | required String urlStickerPath, 36 | required bool isRead, 37 | }) async { 38 | 39 | /// Save message 40 | await _firestore 41 | .collection(C_MESSAGES) 42 | .doc(senderId) 43 | .collection(receiverId) 44 | .doc() 45 | .set({ 46 | USER_ID: fromUserId, 47 | MESSAGE_TYPE: type, 48 | MESSAGE_TEXT: textMsg, 49 | MESSAGE_IMG_LINK: imgLink, 50 | MESSAGE_AUDIO_LINK: urlAudioPath, 51 | MESSAGE_GIF_LINK: urlGifPath, 52 | MESSAGE_STICKER_LINK: urlStickerPath, 53 | TIMESTAMP: DateTime.now(), 54 | }); 55 | 56 | /// Save last conversation 57 | await _conversationsApi.saveConversation( 58 | type: type, 59 | senderId: senderId, 60 | receiverId: receiverId, 61 | userPhotoLink: userPhotoLink, 62 | userFullName: userFullName, 63 | textMsg: textMsg, 64 | isRead: isRead); 65 | } 66 | 67 | /// Delete current user chat 68 | Future deleteChat(String withUserId, {bool isDoubleDel = false}) async { 69 | /// Get Chat for current user 70 | /// 71 | final List _messages01 = (await _firestore 72 | .collection(C_MESSAGES) 73 | .doc(UserModel().user.userId) 74 | .collection(withUserId) 75 | .get()) 76 | .docs; 77 | 78 | // Check messages sent by current user to be deleted 79 | if (_messages01.isNotEmpty) { 80 | // Loop messages to be deleted 81 | _messages01.forEach((msg) async { 82 | // Check msg type 83 | if (msg[MESSAGE_TYPE] == 'image' && 84 | msg[USER_ID] == UserModel().user.userId) { 85 | /// Delete uploaded images by current user 86 | await FirebaseStorage.instance 87 | .refFromURL(msg[MESSAGE_IMG_LINK]) 88 | .delete(); 89 | } 90 | await msg.reference.delete(); 91 | }); 92 | 93 | // Delete current user conversation 94 | if (!isDoubleDel) { 95 | _conversationsApi.deleteConverce(withUserId); 96 | } 97 | } 98 | 99 | /// Check param 100 | if (isDoubleDel) { 101 | /// Get messages sent by onother user to be deleted 102 | final List _messages02 = (await _firestore 103 | .collection(C_MESSAGES) 104 | .doc(withUserId) 105 | .collection(UserModel().user.userId) 106 | .get()) 107 | .docs; 108 | 109 | // Check messages 110 | if (_messages02.isNotEmpty) { 111 | // Loop messages to be deleted 112 | _messages02.forEach((msg) async { 113 | // Check msg type 114 | if (msg[MESSAGE_TYPE] == 'image' && msg[USER_ID] == withUserId) { 115 | /// Delete uploaded images by onother user 116 | await FirebaseStorage.instance 117 | .refFromURL(msg[MESSAGE_IMG_LINK]) 118 | .delete(); 119 | } 120 | await msg.reference.delete(); 121 | }); 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /DostiPak/lib/api/users_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:rishtpak/constants/constants.dart'; 3 | import 'package:rishtpak/models/user_model.dart'; 4 | 5 | class UsersApi { 6 | /// Get firestore instance 7 | /// 8 | final _firestore = FirebaseFirestore.instance; 9 | 10 | /// Get all users 11 | Future> getUsers() async { 12 | 13 | /// Build Users query 14 | Query usersQuery = _firestore 15 | .collection(C_USERS) 16 | .where(USER_ID , isNotEqualTo: UserModel().user.userId) //NEW 17 | .where(USER_STATUS, isEqualTo: 'active') 18 | .where(USER_LEVEL, isEqualTo: 'user'); 19 | 20 | 21 | 22 | // Filter the User Gender 23 | usersQuery = UserModel().filterUserGender(usersQuery); 24 | 25 | 26 | 27 | //NEW Edit (No passport) 28 | 29 | // // Instance of Geoflutterfire 30 | // final Geoflutterfire geo = new Geoflutterfire(); 31 | // 32 | // /// Get user settings 33 | final Map? settings = UserModel().user.userSettings; 34 | // 35 | // // Get user geo center 36 | // final GeoFirePoint center = geo.point( 37 | // latitude: UserModel().user.userGeoPoint.latitude, 38 | // longitude: UserModel().user.userGeoPoint.longitude); 39 | // 40 | // final allUsers = await geo 41 | // .collection(collectionRef: usersQuery) 42 | // .within( 43 | // center: center, 44 | // radius: settings![USER_MAX_DISTANCE].toDouble(), 45 | // field: USER_GEO_POINT, 46 | // strictMode: true) 47 | // .first; 48 | // 49 | // // Remove the current user profile - If choosed to see everyone 50 | // if (allUsers.isNotEmpty) { 51 | // allUsers.removeWhere( 52 | // (userDoc) => userDoc[USER_ID] == UserModel().user.userId); 53 | // } 54 | 55 | //NEW Edit (No passport) 56 | 57 | 58 | 59 | //NEW 60 | List users = (await usersQuery.get()).docs; 61 | //NEW 62 | 63 | 64 | 65 | /// Remove Disliked Users in list 66 | // if (dislikedUsers.isNotEmpty) { 67 | // 68 | // dislikedUsers.forEach((dislikedUser) { 69 | // 70 | // //NEW 71 | // 72 | // users.removeWhere( 73 | // (userDoc) => userDoc[USER_ID] == dislikedUser[DISLIKED_USER_ID]); 74 | // 75 | // // allUsers.removeWhere( 76 | // // (userDoc) => userDoc[USER_ID] == dislikedUser[DISLIKED_USER_ID]); 77 | // 78 | // //NEW 79 | // }); 80 | // } 81 | 82 | 83 | 84 | // Get Liked Profiles 85 | // final List likedProfiles = (await _firestore 86 | // .collection(C_LIKES) 87 | // .where(LIKED_BY_USER_ID, isEqualTo: UserModel().user.userId) 88 | // .get()).docs; 89 | 90 | 91 | 92 | // Remove Liked Profiles 93 | // if (likedProfiles.isNotEmpty) { 94 | // likedProfiles.forEach((likedUser) { 95 | // 96 | // //NEW 97 | // 98 | // users.removeWhere( 99 | // (userDoc) => userDoc[USER_ID] == likedUser[LIKED_USER_ID]); 100 | // 101 | // // allUsers.removeWhere( 102 | // // (userDoc) => userDoc[USER_ID] == likedUser[LIKED_USER_ID]); 103 | // 104 | // //NEW 105 | // }); 106 | // 107 | // } 108 | 109 | 110 | 111 | 112 | 113 | /// Sort by newest 114 | //NEW (allUsers) 115 | users.sort((a, b) { 116 | final DateTime userRegDateA = a[USER_REG_DATE].toDate(); 117 | final DateTime userRegDateB = b[USER_REG_DATE].toDate(); 118 | return userRegDateA.compareTo(userRegDateB); 119 | }); 120 | //NEW 121 | 122 | 123 | 124 | 125 | final int minAge = settings![USER_MIN_AGE]; 126 | final int maxAge = settings[USER_MAX_AGE]; 127 | 128 | 129 | 130 | // Filter Profile Ages (allUsers) 131 | return users.where((DocumentSnapshot user) { 132 | 133 | // Get User Birthday 134 | final DateTime userBirthday = DateTime( 135 | user[USER_BIRTH_YEAR], 136 | user[USER_BIRTH_MONTH], 137 | user[USER_BIRTH_DAY]); 138 | 139 | /// Get user profile age to filter 140 | final int profileAge = UserModel().calculateUserAge(userBirthday); 141 | 142 | // Return result 143 | return profileAge >= minAge && profileAge <= maxAge; 144 | 145 | }).toList(); 146 | 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /DostiPak/lib/api/matches_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:rishtpak/constants/constants.dart'; 3 | import 'package:rishtpak/models/user_model.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class MatchesApi { 7 | /// Get firestore instance 8 | /// 9 | final _firestore = FirebaseFirestore.instance; 10 | 11 | /// Save match 12 | Future _saveMatch({ 13 | required String docUserId, 14 | required String matchedWithUserId, 15 | }) async { 16 | await _firestore 17 | .collection(C_CONNECTIONS) 18 | .doc(docUserId) 19 | .collection(C_MATCHES) 20 | .doc(matchedWithUserId) 21 | .set({TIMESTAMP: FieldValue.serverTimestamp()}); 22 | } 23 | 24 | /// Get current user matches 25 | Future> getMatches() async { 26 | final QuerySnapshot query = await _firestore 27 | .collection(C_CONNECTIONS) 28 | .doc(UserModel().user.userId) 29 | .collection(C_MATCHES) 30 | .orderBy(TIMESTAMP, descending: true) 31 | .get(); 32 | return query.docs; 33 | } 34 | 35 | /// Delete match 36 | Future deleteMatch(String matchedUserId) async { 37 | // Delete match for current user 38 | await _firestore 39 | .collection(C_CONNECTIONS) 40 | .doc(UserModel().user.userId) 41 | .collection(C_MATCHES) 42 | .doc(matchedUserId) 43 | .delete(); 44 | // Delete the current user id from matched user list 45 | await _firestore 46 | .collection(C_CONNECTIONS) 47 | .doc(matchedUserId) 48 | .collection(C_MATCHES) 49 | .doc(UserModel().user.userId) 50 | .delete(); 51 | } 52 | 53 | /// Check if It's Match - when onother user already liked current one 54 | Future checkMatch( 55 | {required String userId, required Function(bool) onMatchResult}) async { 56 | _firestore 57 | .collection(C_LIKES) 58 | .where(LIKED_USER_ID, isEqualTo: UserModel().user.userId) 59 | .where(LIKED_BY_USER_ID, isEqualTo: userId) 60 | .get() 61 | .then((QuerySnapshot snapshot) async { 62 | if (snapshot.docs.isNotEmpty) { 63 | /// It's Match - show dialog 64 | onMatchResult(true); 65 | 66 | /// Save match for current user 67 | await _saveMatch( 68 | docUserId: UserModel().user.userId, 69 | matchedWithUserId: userId, 70 | ); 71 | 72 | /// Save match copy for matched user 73 | await _saveMatch( 74 | docUserId: userId, 75 | matchedWithUserId: UserModel().user.userId, 76 | ); 77 | debugPrint('checkMatch() -> true'); 78 | } else { 79 | onMatchResult(false); 80 | debugPrint('checkMatch() -> false'); 81 | } 82 | }).catchError((e) { 83 | print('checkMatch() -> error: $e'); 84 | }); 85 | } 86 | 87 | 88 | Future addMatchingUser({required String userId}) async { 89 | 90 | _firestore 91 | .collection(C_CONNECTIONS) 92 | .doc(UserModel().user.userId) 93 | .collection(C_MATCHES) 94 | .doc(userId) 95 | .get() 96 | .then((snapshot) async{ 97 | 98 | if(snapshot.exists) { 99 | print('user is exists already !'); 100 | } 101 | else { 102 | 103 | /// Save match for current user 104 | await _saveMatch( 105 | docUserId: UserModel().user.userId, 106 | matchedWithUserId: userId, 107 | ); 108 | 109 | debugPrint('checkMatch() -> true'); 110 | } 111 | 112 | 113 | }); 114 | } 115 | 116 | 117 | Future removeMatchingUser({required String userId}) async { 118 | 119 | _firestore 120 | .collection(C_CONNECTIONS) 121 | .doc(UserModel().user.userId) 122 | .collection(C_MATCHES) 123 | .doc(userId) 124 | .get() 125 | .then((snapshot) async{ 126 | 127 | if(snapshot.exists) { 128 | 129 | await _firestore 130 | .collection(C_CONNECTIONS) 131 | .doc(UserModel().user.userId) 132 | .collection(C_MATCHES) 133 | .doc(userId) 134 | .delete() 135 | .then((value) => print('matched user is deleted')); 136 | 137 | } 138 | else { 139 | print('user is not exists !'); 140 | } 141 | 142 | 143 | }); 144 | } 145 | 146 | } 147 | 148 | -------------------------------------------------------------------------------- /DostiPak/lib/screens/verification_code_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:rishtpak/dialogs/common_dialogs.dart'; 2 | import 'package:rishtpak/dialogs/progress_dialog.dart'; 3 | import 'package:rishtpak/helpers/app_helper.dart'; 4 | import 'package:rishtpak/models/user_model.dart'; 5 | import 'package:rishtpak/plugins/otp_screen/otp_screen.dart'; 6 | import 'package:rishtpak/screens/enable_location_screen.dart'; 7 | import 'package:rishtpak/screens/home_screen.dart'; 8 | import 'package:rishtpak/screens/sign_up_screen.dart'; 9 | import 'package:rishtpak/widgets/svg_icon.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:rishtpak/helpers/app_localizations.dart'; 12 | 13 | class VerificationCodeScreen extends StatefulWidget { 14 | // Variables 15 | final String verificationId; 16 | 17 | // Constructor 18 | VerificationCodeScreen({ 19 | required this.verificationId, 20 | }); 21 | 22 | @override 23 | _VerificationCodeScreenState createState() => _VerificationCodeScreenState(); 24 | } 25 | 26 | class _VerificationCodeScreenState extends State { 27 | // Variables 28 | late AppLocalizations _i18n; 29 | late ProgressDialog _pr; 30 | final _scaffoldkey = GlobalKey(); 31 | 32 | /// Navigate to next page 33 | void _nextScreen(screen) { 34 | // Go to next page route 35 | Future(() { 36 | Navigator.of(context).pushAndRemoveUntil( 37 | MaterialPageRoute(builder: (context) => screen), (route) => false); 38 | }); 39 | } 40 | 41 | /// Go to enable location or GPS screen 42 | void goToEnableLocationOrGpsScreen(String action) { 43 | // Navigate 44 | _nextScreen(EnableLocationScreen(action: action)); 45 | } 46 | 47 | /// logic to validate otp return [null] when success else error [String] 48 | Future validateOtp(String otp) async { 49 | /// Handle entered verification code here 50 | /// 51 | /// Show progress dialog 52 | _pr.show(_i18n.translate("processing")); 53 | 54 | await UserModel().signInWithOTP( 55 | verificationId: widget.verificationId, 56 | otp: otp, 57 | checkUserAccount: () { 58 | /// Auth user account 59 | UserModel().authUserAccount( 60 | context: context, 61 | scaffoldkey: _scaffoldkey, 62 | homeScreen: () { 63 | /// Go to home screen 64 | _nextScreen(HomeScreen()); 65 | }, signUpScreen: () async { 66 | // AppHelper instance 67 | final AppHelper appHelper = new AppHelper(); 68 | 69 | /// Check location permission 70 | await appHelper.checkLocationPermission(onGpsDisabled: () { 71 | /// Go to Enable GPS screen 72 | goToEnableLocationOrGpsScreen('GPS'); 73 | }, onDenied: () { 74 | /// Go to enable location screen 75 | goToEnableLocationOrGpsScreen('location'); 76 | }, onGranted: () { 77 | /// Go to sign up screen 78 | _nextScreen(SignUpScreen()); 79 | }); 80 | }); 81 | }, 82 | onError: () async { 83 | // Hide dialog 84 | await _pr.hide(); 85 | // Show error message to user 86 | errorDialog(context, 87 | message: _i18n.translate("we_were_unable_to_verify_your_number")); 88 | }); 89 | 90 | // Hide progress dialog 91 | await _pr.hide(); 92 | 93 | return null; 94 | } 95 | 96 | @override 97 | Widget build(BuildContext context) { 98 | /// Initialization 99 | _i18n = AppLocalizations.of(context); 100 | _pr = ProgressDialog(context, isDismissible: false); 101 | 102 | return Scaffold( 103 | key: _scaffoldkey, 104 | body: OtpScreen.withGradientBackground( 105 | topColor: Theme.of(context).primaryColor, 106 | bottomColor: Theme.of(context).primaryColor.withOpacity(.7), 107 | otpLength: 6, 108 | validateOtp: validateOtp, 109 | routeCallback: (context) {}, 110 | icon: CircleAvatar( 111 | radius: 50, 112 | backgroundColor: Colors.white, 113 | child: SvgIcon("assets/icons/phone_icon.svg", 114 | width: 40, height: 40, color: Theme.of(context).primaryColor), 115 | ), 116 | title: _i18n.translate("verification_code"), 117 | subTitle: _i18n.translate("please_enter_the_sms_code_sent"), 118 | ), 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /DostiPak/lib/screens/about_us_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/constants/constants.dart'; 3 | import 'package:rishtpak/helpers/app_helper.dart'; 4 | import 'package:rishtpak/helpers/app_localizations.dart'; 5 | import 'package:rishtpak/models/app_model.dart'; 6 | import 'package:rishtpak/widgets/app_logo.dart'; 7 | 8 | class AboutScreen extends StatelessWidget { 9 | // Variables 10 | final AppHelper _appHelper = AppHelper(); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | final i18n = AppLocalizations.of(context); 15 | return Scaffold( 16 | appBar: AppBar( 17 | title: Text(i18n.translate('about_us')), 18 | ), 19 | body: SingleChildScrollView( 20 | padding: 21 | const EdgeInsets.only(top: 25, left: 25, right: 25, bottom: 65), 22 | child: Center( 23 | child: Column( 24 | children: [ 25 | /// App icon 26 | AppLogo(), 27 | SizedBox(height: 10), 28 | 29 | /// App name 30 | Text( 31 | APP_NAME, 32 | style: TextStyle( 33 | fontSize: 25, 34 | fontWeight: FontWeight.bold, 35 | ), 36 | textAlign: TextAlign.center, 37 | ), 38 | SizedBox(height: 5), 39 | 40 | 41 | // slogan 42 | Text(i18n.translate('app_short_description'), 43 | style: TextStyle( 44 | color: Colors.grey, 45 | fontSize: 18, 46 | )), 47 | SizedBox(height: 15), 48 | 49 | 50 | // App description 51 | Text(i18n.translate('about_us_description'), 52 | style: TextStyle( 53 | fontSize: 18, 54 | ), 55 | textAlign: TextAlign.center), 56 | 57 | 58 | // Share app button 59 | SizedBox(height: 10), 60 | TextButton.icon( 61 | style: ButtonStyle( 62 | backgroundColor: MaterialStateProperty.all(Theme.of(context).primaryColor) 63 | ), 64 | icon: Icon(Icons.share, color: Colors.white), 65 | label: Text(i18n.translate('share_app'), 66 | style: TextStyle( 67 | color: Colors.white, 68 | fontSize: 18, 69 | )), 70 | onPressed: () async { 71 | /// Share app 72 | _appHelper.shareApp(); 73 | }, 74 | ), 75 | SizedBox(height: 10), 76 | 77 | 78 | 79 | // App version name 80 | Text(APP_VERSION_NAME, 81 | style: TextStyle( 82 | color: Colors.grey, 83 | fontSize: 20, 84 | fontWeight: FontWeight.bold)), 85 | Divider(height: 30, thickness: 1), 86 | Column( 87 | crossAxisAlignment: CrossAxisAlignment.center, 88 | children: [ 89 | // Contact 90 | Text(i18n.translate('do_you_have_a_question'), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), 91 | SizedBox(height: 10), 92 | 93 | Text(i18n.translate('send_your_message_to_our_email_address'), 94 | style: TextStyle(fontSize: 18), 95 | textAlign: TextAlign.center), 96 | 97 | 98 | Text(AppModel().appInfo.appEmail, 99 | style: TextStyle( 100 | fontSize: 18, 101 | fontWeight: FontWeight.bold, 102 | color: Theme.of(context).primaryColor)), 103 | 104 | 105 | // About us 106 | SizedBox(height: 15), 107 | GestureDetector( 108 | child: Text( 109 | i18n.translate("about_us_link"), 110 | style: TextStyle( 111 | color: Theme.of(context).primaryColor, 112 | fontSize: 17, 113 | decoration: TextDecoration.underline, 114 | fontWeight: FontWeight.bold), 115 | ), 116 | onTap: () { 117 | // Open privacy policy page in browser 118 | _appHelper.openAboutUs(); 119 | }, 120 | ), 121 | 122 | ], 123 | ), 124 | ], 125 | ), 126 | ), 127 | ), 128 | ); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /DostiPak/lib/screens/enable_location_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:rishtpak/dialogs/common_dialogs.dart'; 2 | import 'package:rishtpak/helpers/app_localizations.dart'; 3 | import 'package:rishtpak/screens/sign_up_screen.dart'; 4 | import 'package:rishtpak/widgets/svg_icon.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:geolocator/geolocator.dart'; 7 | 8 | class EnableLocationScreen extends StatefulWidget { 9 | // Variables 10 | final String action; 11 | 12 | EnableLocationScreen({required this.action}); 13 | 14 | @override 15 | _EnableLocationScreenState createState() => _EnableLocationScreenState(); 16 | } 17 | 18 | class _EnableLocationScreenState extends State { 19 | // Variables 20 | late AppLocalizations _i18n; 21 | 22 | @override 23 | void didChangeDependencies() { 24 | super.didChangeDependencies(); 25 | 26 | /// Show turn on GPS or Location dialog 27 | Future(() { 28 | /// Show dialog to enable GPS 29 | infoDialog(context, 30 | icon: SvgIcon("assets/icons/location_point_icon.svg", 31 | color: Colors.white), 32 | title: _i18n.translate( 33 | widget.action == 'GPS' ? "enable_GPS" : "enable_location"), 34 | message: _i18n.translate(widget.action == 'GPS' 35 | ? "we_were_unable_to_get_your_current_location_please_enable_gps_to_continue" 36 | : "you_need_to_enable_location_permission_to_use_this_app"), 37 | positiveText: _i18n.translate("ENABLE"), positiveAction: () { 38 | // Execute action 39 | widget.action == 'GPS' 40 | ? Geolocator.openLocationSettings() 41 | : Geolocator.openAppSettings(); 42 | // Close dialog 43 | Navigator.of(context).pop(); 44 | }); 45 | }); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | _i18n = AppLocalizations.of(context); 51 | return Scaffold( 52 | appBar: AppBar( 53 | title: Text(_i18n.translate( 54 | widget.action == 'GPS' ? "enable_GPS" : "enable_location")), 55 | ), 56 | body: SingleChildScrollView( 57 | padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 50), 58 | child: Center( 59 | child: Column( 60 | mainAxisAlignment: MainAxisAlignment.center, 61 | children: [ 62 | CircleAvatar( 63 | radius: 50, 64 | child: Icon(Icons.location_on, size: 60, color: Colors.white), 65 | backgroundColor: Theme.of(context).primaryColor, 66 | ), 67 | // TIP 68 | SizedBox(height: 5), 69 | Padding( 70 | padding: const EdgeInsets.all(8.0), 71 | child: Text( 72 | _i18n.translate(widget.action == 'GPS' 73 | ? "we_were_unable_to_get_your_current_location_please_enable_gps_to_continue" 74 | : "you_need_to_enable_location_permission_to_use_this_app"), 75 | style: TextStyle(fontSize: 18), 76 | textAlign: TextAlign.center), 77 | ), 78 | SizedBox(height: 5), 79 | Text( 80 | _i18n.translate(widget.action == 'GPS' 81 | ? "did_you_enable_GPS" 82 | : "did_you_enable_location"), 83 | style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), 84 | Text(_i18n.translate("click_continue"), 85 | style: TextStyle(fontSize: 18)), 86 | SizedBox(height: 15), 87 | SizedBox( 88 | height: 45, 89 | width: double.maxFinite, 90 | child: TextButton( 91 | style: ButtonStyle( 92 | backgroundColor: MaterialStateProperty.all(Theme.of(context).primaryColor), 93 | shape: MaterialStateProperty.all( 94 | RoundedRectangleBorder( 95 | borderRadius: BorderRadius.circular(25), 96 | ) 97 | ) 98 | ), 99 | child: Text(_i18n.translate("CONTINUE"), 100 | style: TextStyle(fontSize: 18, color: Colors.white)), 101 | onPressed: () { 102 | /// Go to SignUp Screen 103 | Navigator.of(context).pushAndRemoveUntil( 104 | MaterialPageRoute(builder: (context) => SignUpScreen()), 105 | (route) => false); 106 | }, 107 | ), 108 | ), 109 | ], 110 | ), 111 | ), 112 | ), 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /DostiPak/lib/api/notifications_api.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:rishtpak/constants/constants.dart'; 5 | import 'package:rishtpak/models/user_model.dart'; 6 | import 'package:rishtpak/models/app_model.dart'; 7 | import 'package:http/http.dart' as http; 8 | 9 | class NotificationsApi { 10 | /// FINAL VARIABLES 11 | /// 12 | /// Firestore instance 13 | final _firestore = FirebaseFirestore.instance; 14 | 15 | /// Save notification in database 16 | Future saveNotification({ 17 | required String nReceiverId, 18 | required String nType, 19 | required String nMessage, 20 | }) async { 21 | _firestore.collection(C_NOTIFICATIONS).add({ 22 | N_SENDER_ID: UserModel().user.userId, 23 | N_SENDER_FULLNAME: UserModel().user.userFullname, 24 | N_SENDER_PHOTO_LINK: UserModel().user.userProfilePhoto, 25 | N_RECEIVER_ID: nReceiverId, 26 | N_TYPE: nType, 27 | N_MESSAGE: nMessage, 28 | N_READ: false, 29 | TIMESTAMP: FieldValue.serverTimestamp() 30 | }).then((_) { 31 | print('saveNotification() -> success'); 32 | }); 33 | } 34 | 35 | /// Notify Current User after purchasing VIP subscription 36 | Future onPurchaseNotification({ 37 | required String nMessage, 38 | }) async { 39 | _firestore.collection(C_NOTIFICATIONS).add({ 40 | N_SENDER_FULLNAME: APP_NAME, 41 | N_RECEIVER_ID: UserModel().user.userId, 42 | N_TYPE: 'alert', 43 | N_MESSAGE: nMessage, 44 | N_READ: false, 45 | TIMESTAMP: FieldValue.serverTimestamp() 46 | }).then((_) { 47 | print('saveNotification() -> success'); 48 | }); 49 | } 50 | 51 | /// Get stream notifications for current user 52 | Stream getNotifications() { 53 | /// Build query 54 | return _firestore 55 | .collection(C_NOTIFICATIONS) 56 | .where(N_RECEIVER_ID, isEqualTo: UserModel().user.userId) 57 | 58 | /// here 59 | .orderBy(TIMESTAMP, descending: true) 60 | .snapshots(); 61 | } 62 | 63 | /// Delete current user notifications 64 | Future deleteUserNotifications() async { 65 | await _firestore 66 | .collection(C_NOTIFICATIONS) 67 | .where(N_RECEIVER_ID, isEqualTo: UserModel().user.userId) 68 | .get() 69 | .then((QuerySnapshot snapshot) async { 70 | // Check result 71 | if (snapshot.docs.isEmpty) return; 72 | 73 | /// Loop notifications and delete one by one 74 | for (DocumentSnapshot doc in snapshot.docs) { 75 | await doc.reference.delete(); 76 | } 77 | 78 | print('deleteUserNotifications() -> deleted'); 79 | }); 80 | } 81 | 82 | Future deleteUserSentNotifications() async { 83 | _firestore 84 | .collection(C_NOTIFICATIONS) 85 | .where(N_SENDER_ID, isEqualTo: UserModel().user.userId) 86 | .get() 87 | .then((QuerySnapshot snapshot) async { 88 | // Check result 89 | if (snapshot.docs.isEmpty) return; 90 | 91 | /// Loop notifications 92 | for (DocumentSnapshot doc in snapshot.docs) { 93 | await doc.reference.delete(); 94 | } 95 | print('deleteUserSentNotifications() -> deleted'); 96 | }); 97 | } 98 | 99 | /// Send push notification method 100 | Future sendPushNotification({ 101 | required String nTitle, 102 | required String nBody, 103 | required String nType, 104 | required String nSenderId, 105 | required String nUserDeviceToken, 106 | // Call Info Map Data 107 | Map? nCallInfo, 108 | }) async { 109 | // Variables 110 | final Uri url = Uri.parse('https://fcm.googleapis.com/fcm/send'); 111 | 112 | await http.post(url, 113 | headers: { 114 | 'Content-Type': 'application/json', 115 | 'Authorization': 'key=${AppModel().appInfo.firebaseServerKey}', 116 | }, 117 | body: jsonEncode( 118 | { 119 | 'notification': { 120 | 'title': nTitle, 121 | 'body': nBody, 122 | 'color': '#987dfa', 123 | 'sound': "default" 124 | }, 125 | 'priority': 'high', 126 | 'data': { 127 | 'click_action': 'FLUTTER_NOTIFICATION_CLICK', 128 | N_TYPE: nType, 129 | N_SENDER_ID: nSenderId, 130 | 'call_info': nCallInfo, // Call Info Data 131 | 'status': 'done' 132 | }, 133 | 'to': nUserDeviceToken, 134 | }, 135 | ), 136 | ).then((http.Response response) { 137 | if (response.statusCode == 200) { 138 | print('sendPushNotification() -> success'); 139 | } 140 | }).catchError((error) { 141 | print('sendPushNotification() -> error: $error'); 142 | }); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /DostiPak/lib/screens/delete_account_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:rishtpak/api/conversations_api.dart'; 3 | import 'package:rishtpak/api/dislikes_api.dart'; 4 | import 'package:rishtpak/api/likes_api.dart'; 5 | import 'package:rishtpak/api/matches_api.dart'; 6 | import 'package:rishtpak/api/messages_api.dart'; 7 | import 'package:rishtpak/api/notifications_api.dart'; 8 | import 'package:rishtpak/api/visits_api.dart'; 9 | import 'package:rishtpak/constants/constants.dart'; 10 | import 'package:rishtpak/helpers/app_localizations.dart'; 11 | import 'package:rishtpak/models/user_model.dart'; 12 | import 'package:rishtpak/screens/sign_in_screen.dart'; 13 | import 'package:rishtpak/widgets/processing.dart'; 14 | import 'package:firebase_storage/firebase_storage.dart'; 15 | import 'package:flutter/material.dart'; 16 | 17 | class DeleteAccountScreen extends StatefulWidget { 18 | @override 19 | _DeleteAccountScreenState createState() => _DeleteAccountScreenState(); 20 | } 21 | 22 | class _DeleteAccountScreenState extends State { 23 | // Variables 24 | final _firestore = FirebaseFirestore.instance; 25 | final _storageRef = FirebaseStorage.instance; 26 | 27 | /// Api instances 28 | final _notificationsApi = NotificationsApi(); 29 | final _conversationsApi = ConversationsApi(); 30 | final _messagesApi = MessagesApi(); 31 | final _matchesApi = MatchesApi(); 32 | final _likesApi = LikesApi(); 33 | final _dislikesApi = DislikesApi(); 34 | final _visitsApi = VisitsApi(); 35 | 36 | /// DELETE USER ACCOUNT 37 | /// 38 | Future _deleteUserAccount() async { 39 | /// 40 | /// DELETE ALL USER TRANSACTIONS FROM DATABASE AND STORAGE 41 | /// 42 | /// DELETE CURRENT USER PROFILE 43 | await _firestore.collection(C_USERS).doc(UserModel().user.userId).delete(); 44 | debugPrint('Profile account -> deleted...'); 45 | 46 | // Get user uploaded profile image links 47 | final List _userImagesRef = 48 | UserModel().getUserProfileImages(UserModel().user); 49 | 50 | /// DELETE PROFILE IMAGE AND GALLERY 51 | /// 52 | /// Loop user profile images to be deleted from storage 53 | _userImagesRef.forEach((imgUrl) async { 54 | // Delete profile image and gallery 55 | await _storageRef.refFromURL(imgUrl).delete(); 56 | }); 57 | debugPrint('Profile images -> deleted...'); 58 | 59 | /// DELETE USER MATCHES 60 | /// 61 | // Get user matches 62 | final List _matches = await _matchesApi.getMatches(); 63 | // Check matches 64 | if (_matches.isNotEmpty) { 65 | // Loop matches to be deleted 66 | _matches.forEach((match) async { 67 | await _matchesApi.deleteMatch(match.id); 68 | }); 69 | } 70 | debugPrint('Matches -> deleted...'); 71 | 72 | /// DELETE USER CONVERSATIONS AND CHAT MESSAGES 73 | /// 74 | /// Get user conversations 75 | final List _conversations = 76 | (await _conversationsApi.getConversations().first).docs; 77 | // Check conversations 78 | if (_conversations.isNotEmpty) { 79 | // Loop conversations to be deleted 80 | _conversations.forEach((converse) async { 81 | await _conversationsApi.deleteConverce(converse.id, isDoubleDel: true); 82 | // Delete chat for current user and for other users 83 | await _messagesApi.deleteChat(converse.id, isDoubleDel: true); 84 | }); 85 | } 86 | debugPrint('Conversations -> deleted...'); 87 | 88 | /// DELETE USER ID FROM LIKES 89 | /// 90 | await _likesApi.deleteLikedUsers(); 91 | 92 | await _likesApi.deleteLikedMeUsers(); 93 | 94 | /// DELETE USER ID FROM DISLIKES 95 | /// 96 | await _dislikesApi.deleteDislikedUsers(); 97 | 98 | await _dislikesApi.deleteDislikedMeUsers(); 99 | 100 | /// DELETE VISITED USERS 101 | await _visitsApi.deleteVisitedUsers(); 102 | 103 | /// DELETE NOTIFICATIONS RECEIVED BY USER 104 | /// 105 | await _notificationsApi.deleteUserNotifications(); 106 | 107 | /// DELETE NOTIFICATIONS SENT BY USER 108 | /// 109 | await _notificationsApi.deleteUserSentNotifications(); 110 | } 111 | 112 | @override 113 | void initState() { 114 | super.initState(); 115 | // Start deleting user account 116 | _deleteUserAccount().then((_) { 117 | // Go to sign in screen 118 | Future(() { 119 | Navigator.of(context).popUntil((route) => route.isFirst); 120 | Navigator.of(context).pushReplacement(MaterialPageRoute( 121 | builder: (context) => SignInScreen()) 122 | ); 123 | }); 124 | }); 125 | } 126 | 127 | @override 128 | Widget build(BuildContext context) { 129 | final i18n = AppLocalizations.of(context); 130 | return Scaffold( 131 | body: Processing( 132 | text: i18n.translate("deleting_your_account"), 133 | ), 134 | ); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/gallery_image_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/dialogs/common_dialogs.dart'; 3 | import 'package:rishtpak/dialogs/progress_dialog.dart'; 4 | import 'package:rishtpak/dialogs/vip_dialog.dart'; 5 | import 'package:rishtpak/helpers/app_localizations.dart'; 6 | import 'package:rishtpak/models/user_model.dart'; 7 | import 'package:rishtpak/widgets/default_card_border.dart'; 8 | import 'package:rishtpak/widgets/image_source_sheet.dart'; 9 | 10 | class GalleryImageCard extends StatelessWidget { 11 | // Variables 12 | final ImageProvider imageProvider; 13 | final String? imageUrl; 14 | final BoxFit boxFit; 15 | final int index; 16 | 17 | const GalleryImageCard({ 18 | required this.imageProvider, 19 | required this.imageUrl, 20 | required this.boxFit, 21 | required this.index, 22 | }); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return GestureDetector( 27 | child: Stack( 28 | children: [ 29 | Card( 30 | clipBehavior: Clip.antiAlias, 31 | shape: defaultCardBorder(), 32 | color: Colors.grey[300], 33 | child: Center( 34 | child: Container( 35 | decoration: BoxDecoration( 36 | image: DecorationImage( 37 | fit: boxFit, image: imageProvider), 38 | color: Colors.grey.withAlpha(70), 39 | ), 40 | ), 41 | ), 42 | ), 43 | Positioned( 44 | child: IconButton( 45 | icon: CircleAvatar( 46 | radius: 15, 47 | backgroundColor: Theme.of(context).primaryColor, 48 | child: Icon(imageUrl == null ? Icons.add : Icons.close, 49 | color: Colors.white), 50 | ), 51 | onPressed: () { 52 | /// Check image url to exe action 53 | if (imageUrl == null) { 54 | /// Add or update image 55 | _selectImage(context); 56 | } else { 57 | /// Delete image from gallery 58 | _deleteGalleryImage(context); 59 | } 60 | }), 61 | right: 8, 62 | bottom: 5, 63 | ) 64 | ], 65 | ), 66 | onTap: () { 67 | /// Add or update image 68 | _selectImage(context); 69 | }, 70 | ); 71 | } 72 | 73 | /// Get image from camera / gallery 74 | void _selectImage(BuildContext context) async { 75 | /// Initialization 76 | final i18n = AppLocalizations.of(context); 77 | final pr = ProgressDialog(context, isDismissible: false); 78 | 79 | /// Check user vip account 80 | if (!UserModel().userIsVip && index > 3) { 81 | /// Show VIP dialog 82 | showDialog(context: context, 83 | builder: (context) => VipDialog()); 84 | debugPrint('You need to activate vip account'); 85 | return; 86 | } 87 | 88 | await showModalBottomSheet( 89 | context: context, 90 | builder: (context) => ImageSourceSheet( 91 | onImageSelected: (image) async { 92 | if (image != null) { 93 | /// Show progress dialog 94 | pr.show(i18n.translate("processing")); 95 | 96 | /// Update gallery image 97 | await UserModel().updateProfileImage( 98 | imageFile: image, 99 | oldImageUrl: imageUrl, 100 | path: 'gallery', 101 | index: index); 102 | // Hide dialog 103 | pr.hide(); 104 | // close modal 105 | Navigator.of(context).pop(); 106 | } 107 | }, 108 | )); 109 | } 110 | 111 | /// Delete image from gallery 112 | void _deleteGalleryImage(BuildContext context) async { 113 | /// Initialization 114 | final i18n = AppLocalizations.of(context); 115 | final pr = ProgressDialog(context, isDismissible: false); 116 | 117 | /// Check user vip account 118 | if (!UserModel().userIsVip && index > 3) { 119 | /// Show VIP dialog 120 | showDialog(context: context, 121 | builder: (context) => VipDialog()); 122 | debugPrint('You need to activate vip account'); 123 | return; 124 | } 125 | 126 | /// Confirm before 127 | confirmDialog(context, 128 | message: i18n.translate("photo_will_be_deleted"), 129 | negativeAction: () => Navigator.of(context).pop(), 130 | positiveText: i18n.translate("DELETE"), 131 | positiveAction: () async { 132 | // Show processing dialog 133 | pr.show(i18n.translate("processing")); 134 | 135 | /// Delete image 136 | await UserModel() 137 | .deleteGalleryImage(imageUrl: imageUrl!, index: index); 138 | 139 | // Hide progress dialog 140 | pr.hide(); 141 | // Hide confirm dialog 142 | Navigator.of(context).pop(); 143 | }); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /DostiPak/lib/api/dislikes_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:rishtpak/api/likes_api.dart'; 3 | import 'package:rishtpak/models/user_model.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:rishtpak/constants/constants.dart'; 6 | 7 | class DislikesApi { 8 | /// Get firestore instance 9 | /// 10 | final _firestore = FirebaseFirestore.instance; 11 | 12 | Future _saveDislike(String dislikedUserId) async { 13 | _firestore.collection(C_DISLIKES).add({ 14 | DISLIKED_USER_ID: dislikedUserId, 15 | DISLIKED_BY_USER_ID: UserModel().user.userId, 16 | TIMESTAMP: FieldValue.serverTimestamp() 17 | }).then((_) { 18 | /// Update current user total disliked profiles 19 | UserModel().updateUserData( 20 | userId: UserModel().user.userId, 21 | data: {USER_TOTAL_DISLIKED: FieldValue.increment(1)}); 22 | }); 23 | } 24 | 25 | Future dislikeUser( 26 | {required String dislikedUserId, required Function(bool) onDislikeResult}) async { 27 | /// Check if current user already disliked profile 28 | _firestore 29 | .collection(C_DISLIKES) 30 | .where(DISLIKED_BY_USER_ID, isEqualTo: UserModel().user.userId) 31 | .where(DISLIKED_USER_ID, isEqualTo: dislikedUserId) 32 | .get() 33 | .then((QuerySnapshot snapshot) async { 34 | if (snapshot.docs.isEmpty) { 35 | 36 | // Dislike user 37 | _saveDislike(dislikedUserId); 38 | onDislikeResult(true); 39 | debugPrint('dislikeUser() -> success'); 40 | } 41 | else { 42 | onDislikeResult(false); 43 | debugPrint('You already disliked the user'); 44 | } 45 | }).catchError((e) { 46 | print('dislikeUser() -> error: $e'); 47 | }); 48 | } 49 | 50 | /// Get disliked profiles for current user 51 | Future> getDislikedUsers({ 52 | required bool withLimit, 53 | bool loadMore = false, 54 | DocumentSnapshot? userLastDoc, 55 | }) async { 56 | /// Build query 57 | Query usersQuery = _firestore 58 | .collection(C_DISLIKES) 59 | .where(DISLIKED_BY_USER_ID, isEqualTo: UserModel().user.userId); 60 | 61 | /// Finalize query 62 | usersQuery = usersQuery.orderBy(TIMESTAMP, descending: true); 63 | 64 | /// Check load loadMore 65 | if (loadMore) { 66 | usersQuery = usersQuery.startAfterDocument(userLastDoc!); 67 | } 68 | 69 | // Check limit param 70 | if (withLimit) { 71 | usersQuery = usersQuery.limit(20); 72 | } 73 | 74 | 75 | final QuerySnapshot querySnapshot = await usersQuery.get().catchError((e) { 76 | print('getDislikedUsers() -> error: $e'); 77 | }); 78 | 79 | return querySnapshot.docs; 80 | } 81 | 82 | /// Undo disliked profile: if current user decides to like it again 83 | Future deleteDislikedUser(String dislikedUserId) async { 84 | _firestore 85 | .collection(C_DISLIKES) 86 | .where(DISLIKED_USER_ID, isEqualTo: dislikedUserId) 87 | .where(DISLIKED_BY_USER_ID, isEqualTo: UserModel().user.userId) 88 | .get() 89 | .then((QuerySnapshot snapshot) async { 90 | /// Check if doc exists 91 | if (snapshot.docs.isNotEmpty) { 92 | // Get doc and delete it 93 | final ref = snapshot.docs.first; 94 | await ref.reference.delete().then((value) => print('remove user in first place')); 95 | 96 | /// Decrement current user total dislikes 97 | final int currentUserDislikes = UserModel().user.userTotalDisliked - 1; 98 | 99 | await UserModel().updateUserData( 100 | userId: UserModel().user.userId, 101 | data: {USER_TOTAL_DISLIKED: currentUserDislikes}); 102 | debugPrint('deleteDislike() -> success'); 103 | } else { 104 | debugPrint('deleteDislike() -> doc does not exists'); 105 | } 106 | }).catchError((e) { 107 | print('deleteDislike() -> error: $e'); 108 | }); 109 | } 110 | 111 | Future deleteDislikedUsers() async { 112 | _firestore 113 | .collection(C_DISLIKES) 114 | .where(DISLIKED_BY_USER_ID, isEqualTo: UserModel().user.userId) 115 | .get() 116 | .then((QuerySnapshot snapshot) async { 117 | /// Check docs 118 | if (snapshot.docs.isNotEmpty) { 119 | // Loop docs to be deleted 120 | snapshot.docs.forEach((doc) async { 121 | await doc.reference.delete(); 122 | }); 123 | debugPrint('deleteDislikedUsers() -> deleted'); 124 | } 125 | }); 126 | } 127 | 128 | Future deleteDislikedMeUsers() async { 129 | _firestore 130 | .collection(C_DISLIKES) 131 | .where(DISLIKED_USER_ID, isEqualTo: UserModel().user.userId) 132 | .get() 133 | .then((QuerySnapshot snapshot) async { 134 | /// Check docs 135 | if (snapshot.docs.isNotEmpty) { 136 | // Loop docs to be deleted 137 | snapshot.docs.forEach((doc) async { 138 | await doc.reference.delete(); 139 | }); 140 | debugPrint('deleteDislikedMeUsers() -> deleted'); 141 | } 142 | }); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /DostiPak/lib/dialogs/show_me_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:rishtpak/constants/constants.dart'; 2 | import 'package:rishtpak/helpers/app_localizations.dart'; 3 | import 'package:rishtpak/models/user_model.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class ShowMeDialog extends StatefulWidget { 7 | @override 8 | _ShowMeDialogState createState() => _ShowMeDialogState(); 9 | } 10 | 11 | class _ShowMeDialogState extends State { 12 | // Variables 13 | final Map? _userSettings = UserModel().user.userSettings; 14 | String _selectedOption = ""; 15 | String _selectedOptionKey = ""; 16 | late AppLocalizations _i18n; 17 | 18 | @override 19 | void didChangeDependencies() { 20 | super.didChangeDependencies(); 21 | // Variables 22 | final i18n = AppLocalizations.of(context); 23 | final String? showMe = _userSettings?[USER_SHOW_ME]; 24 | // Check option 25 | if (showMe != null) { 26 | setState(() { 27 | _selectedOption = i18n.translate(showMe); 28 | }); 29 | } 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | // Initialization 35 | _i18n = AppLocalizations.of(context); 36 | // Map options 37 | final Map mapOptions = { 38 | "women": _i18n.translate("women"), 39 | "men": _i18n.translate("men"), 40 | "everyone": _i18n.translate("everyone"), 41 | }; 42 | 43 | return Dialog( 44 | shape: RoundedRectangleBorder( 45 | borderRadius: BorderRadius.circular(8.0), 46 | ), 47 | child: _dialogContent(context, mapOptions), 48 | elevation: 3, 49 | ); 50 | } 51 | 52 | // Build dialog 53 | Widget _dialogContent(BuildContext context, Map mapOptions) { 54 | return Column( 55 | mainAxisSize: MainAxisSize.min, 56 | children: [ 57 | Padding( 58 | padding: const EdgeInsets.all(16), 59 | child: Row( 60 | children: [ 61 | Icon(Icons.wc), 62 | SizedBox(width: 5), 63 | Text( 64 | _i18n.translate("show_me"), 65 | style: TextStyle( 66 | fontWeight: FontWeight.bold, 67 | fontSize: 20, 68 | ), 69 | ), 70 | ], 71 | ), 72 | ), 73 | Divider( 74 | color: Colors.black, 75 | height: 5, 76 | ), 77 | Flexible( 78 | fit: FlexFit.loose, 79 | child: SingleChildScrollView( 80 | child: Column( 81 | mainAxisSize: MainAxisSize.min, 82 | children: mapOptions.entries.map((option) { 83 | return RadioListTile( 84 | selected: _selectedOption == option.value ? true : false, 85 | title: Text(option.value), 86 | activeColor: Theme.of(context).primaryColor, 87 | value: option.value, 88 | groupValue: _selectedOption, 89 | onChanged: (value) { 90 | setState(() { 91 | _selectedOption = value.toString(); 92 | _selectedOptionKey = option.key; 93 | }); 94 | print('Selected option: $value'); 95 | }); 96 | }).toList()), 97 | ), 98 | ), 99 | Divider( 100 | color: Colors.black, 101 | height: 5, 102 | ), 103 | Builder( 104 | builder: (context) { 105 | return Padding( 106 | padding: const EdgeInsets.all(5), 107 | child: Row( 108 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 109 | children: [ 110 | TextButton( 111 | child: Text(_i18n.translate("CANCEL")), 112 | onPressed: () { 113 | Navigator.of(context).pop(); 114 | }, 115 | ), 116 | TextButton( 117 | child: Text(_i18n.translate("SAVE"), 118 | style: TextStyle(color: Theme.of(context).primaryColor)), 119 | onPressed: _selectedOption == '' 120 | ? null 121 | : () async { 122 | /// Save option 123 | await UserModel().updateUserData( 124 | userId: UserModel().user.userId, 125 | data: { 126 | '$USER_SETTINGS.$USER_SHOW_ME': 127 | _selectedOptionKey 128 | }); 129 | 130 | // Close dialog 131 | Navigator.of(context).pop(); 132 | debugPrint('Show me option() -> saved'); 133 | }, 134 | ), 135 | ], 136 | ), 137 | ); 138 | }, 139 | ) 140 | ], 141 | ); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /DostiPak/lib/dialogs/flag_user_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:rishtpak/dialogs/common_dialogs.dart'; 2 | import 'package:rishtpak/dialogs/progress_dialog.dart'; 3 | import 'package:rishtpak/helpers/app_localizations.dart'; 4 | import 'package:rishtpak/models/user_model.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class FlagUserDialog extends StatefulWidget { 8 | // Variables 9 | final String flaggedUserId; 10 | 11 | FlagUserDialog({required this.flaggedUserId}); 12 | 13 | @override 14 | _FlagUserDialogState createState() => _FlagUserDialogState(); 15 | } 16 | 17 | class _FlagUserDialogState extends State { 18 | // Variables 19 | String _selectedFlagOption = ""; 20 | late ProgressDialog _pr; 21 | late AppLocalizations _i18n; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | // Initialization 26 | _i18n = AppLocalizations.of(context); 27 | _pr = new ProgressDialog(context); 28 | 29 | // Get flag option list 30 | final List flagOptions = [ 31 | _i18n.translate("sexual_content"), 32 | _i18n.translate("abusive_content"), 33 | _i18n.translate("violent_content"), 34 | _i18n.translate("inappropriate_content"), 35 | _i18n.translate("spam_or_misleading"), 36 | ]; 37 | 38 | return Dialog( 39 | shape: RoundedRectangleBorder( 40 | borderRadius: BorderRadius.circular(8.0), 41 | ), 42 | child: _dialogContent(context, flagOptions), 43 | elevation: 3, 44 | ); 45 | } 46 | 47 | // Build dialog 48 | Widget _dialogContent(BuildContext context, List flagOptions) { 49 | return Column( 50 | mainAxisSize: MainAxisSize.min, 51 | children: [ 52 | Padding( 53 | padding: const EdgeInsets.all(16), 54 | child: Row( 55 | children: [ 56 | Icon(Icons.flag_outlined), 57 | SizedBox(width: 5), 58 | Text( 59 | _i18n.translate("flag_user"), 60 | style: TextStyle( 61 | fontWeight: FontWeight.bold, 62 | fontSize: 20, 63 | ), 64 | ), 65 | ], 66 | ), 67 | ), 68 | Divider( 69 | color: Colors.black, 70 | height: 5, 71 | ), 72 | Flexible( 73 | fit: FlexFit.loose, 74 | child: SingleChildScrollView( 75 | child: Column( 76 | mainAxisSize: MainAxisSize.min, 77 | children: flagOptions.map((selectedOption) { 78 | return RadioListTile( 79 | selected: 80 | _selectedFlagOption == selectedOption ? true : false, 81 | title: Text(selectedOption), 82 | activeColor: Theme.of(context).primaryColor, 83 | value: selectedOption, 84 | groupValue: _selectedFlagOption, 85 | onChanged: (value) { 86 | setState(() { 87 | _selectedFlagOption = value.toString(); 88 | }); 89 | print('Selected option: $_selectedFlagOption'); 90 | }); 91 | }).toList()), 92 | ), 93 | ), 94 | Divider( 95 | color: Colors.black, 96 | height: 5, 97 | ), 98 | Builder( 99 | builder: (context) { 100 | return Padding( 101 | padding: const EdgeInsets.all(5), 102 | child: Row( 103 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 104 | children: [ 105 | TextButton( 106 | child: Text(_i18n.translate("CLOSE")), 107 | onPressed: () { 108 | Navigator.of(context).pop(); 109 | }, 110 | ), 111 | TextButton( 112 | child: Text( 113 | _i18n.translate("FLAG"), 114 | style: TextStyle(color: Theme.of(context).primaryColor)), 115 | onPressed: _selectedFlagOption == '' 116 | ? null 117 | : () async { 118 | // Show processing dialog 119 | _pr.show(_i18n.translate("processing")); 120 | 121 | /// Flag profile 122 | await UserModel().flagUserProfile( 123 | flaggedUserId: widget.flaggedUserId, 124 | reason: _selectedFlagOption); 125 | 126 | // Close progress 127 | _pr.hide(); 128 | debugPrint('flagUserProfile() -> success'); 129 | 130 | String message = _i18n.translate( 131 | "thank_you_the_profile_will_be_reviewed"); 132 | // Show success dialog 133 | successDialog(context, message: message); 134 | }, 135 | ), 136 | ], 137 | ), 138 | ); 139 | }, 140 | ) 141 | ], 142 | ); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /DostiPak/lib/dialogs/common_dialogs.dart: -------------------------------------------------------------------------------- 1 | import 'package:rishtpak/helpers/app_localizations.dart'; 2 | import 'package:rishtpak/widgets/default_card_border.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | /// Success Dialog 6 | void successDialog( 7 | BuildContext context, { 8 | required String message, 9 | Widget? icon, 10 | String? title, 11 | String? negativeText, 12 | VoidCallback? negativeAction, 13 | String? positiveText, 14 | VoidCallback? positiveAction, 15 | }) { 16 | _buildDialog(context, "success", 17 | message: message, 18 | icon: icon, 19 | title: title, 20 | negativeText: negativeText, 21 | negativeAction: negativeAction, 22 | positiveText: positiveText, 23 | positiveAction: positiveAction); 24 | } 25 | 26 | /// Error Dialog 27 | void errorDialog( 28 | BuildContext context, { 29 | required String message, 30 | Widget? icon, 31 | String? title, 32 | String? negativeText, 33 | VoidCallback? negativeAction, 34 | String? positiveText, 35 | VoidCallback? positiveAction, 36 | }) { 37 | _buildDialog(context, "error", 38 | message: message, 39 | icon: icon, 40 | title: title, 41 | negativeText: negativeText, 42 | negativeAction: negativeAction, 43 | positiveText: positiveText, 44 | positiveAction: positiveAction); 45 | } 46 | 47 | /// Confirm Dialog 48 | void confirmDialog( 49 | BuildContext context, { 50 | required String message, 51 | Widget? icon, 52 | String? title, 53 | String? negativeText, 54 | VoidCallback? negativeAction, 55 | String? positiveText, 56 | VoidCallback? positiveAction, 57 | }) { 58 | _buildDialog(context, "confirm", 59 | icon: icon, 60 | title: title, 61 | message: message, 62 | negativeText: negativeText, 63 | negativeAction: negativeAction, 64 | positiveText: positiveText, 65 | positiveAction: positiveAction); 66 | } 67 | 68 | /// Confirm Dialog 69 | void infoDialog( 70 | BuildContext context, { 71 | required String message, 72 | Widget? icon, 73 | String? title, 74 | String? negativeText, 75 | VoidCallback? negativeAction, 76 | String? positiveText, 77 | VoidCallback? positiveAction, 78 | }) { 79 | _buildDialog(context, "info", 80 | icon: icon, 81 | title: title, 82 | message: message, 83 | negativeText: negativeText, 84 | negativeAction: negativeAction, 85 | positiveText: positiveText, 86 | positiveAction: positiveAction); 87 | } 88 | 89 | /// Build dialog 90 | void _buildDialog( 91 | BuildContext context, 92 | String type, { 93 | required Widget? icon, 94 | required String? title, 95 | required String message, 96 | required String? negativeText, 97 | required VoidCallback? negativeAction, 98 | required String? positiveText, 99 | required VoidCallback? positiveAction, 100 | }) { 101 | // Variables 102 | final i18n = AppLocalizations.of(context); 103 | final _textStyle = 104 | TextStyle(fontSize: 18, color: Theme.of(context).primaryColor); 105 | late Widget _icon; 106 | late String _title; 107 | 108 | // Control type 109 | switch (type) { 110 | case "success": 111 | _icon = icon ?? 112 | CircleAvatar( 113 | backgroundColor: Colors.green, 114 | child: Icon(Icons.check, color: Colors.white), 115 | ); 116 | _title = title ?? i18n.translate("success"); 117 | break; 118 | case "error": 119 | _icon = icon ?? 120 | CircleAvatar( 121 | backgroundColor: Colors.red, 122 | child: Icon(Icons.close, color: Colors.white), 123 | ); 124 | _title = title ?? i18n.translate("error"); 125 | break; 126 | case "confirm": 127 | _icon = icon ?? 128 | CircleAvatar( 129 | backgroundColor: Colors.amber, 130 | child: Icon(Icons.help_outline, color: Colors.white), 131 | ); 132 | _title = title ?? i18n.translate("are_you_sure"); 133 | break; 134 | 135 | case "info": 136 | _icon = icon ?? 137 | CircleAvatar( 138 | backgroundColor: Colors.blue, 139 | child: Icon(Icons.info_outline, color: Colors.white), 140 | ); 141 | _title = title ?? i18n.translate("information"); 142 | break; 143 | } 144 | 145 | showDialog( 146 | context: context, 147 | barrierDismissible: false, 148 | builder: (context) { 149 | return AlertDialog( 150 | shape: defaultCardBorder(), 151 | title: Row( 152 | children: [ 153 | _icon, 154 | SizedBox(width: 10), 155 | Expanded(child: Text(_title, style: TextStyle(fontSize: 22))) 156 | ], 157 | ), 158 | content: Text( 159 | message, 160 | style: TextStyle(fontSize: 18), 161 | ), 162 | actions: [ 163 | /// Negative button 164 | negativeAction == null 165 | ? Container(width: 0, height: 0) 166 | : TextButton( 167 | onPressed: negativeAction, 168 | child: Text(negativeText ?? i18n.translate("CANCEL"), 169 | style: TextStyle(fontSize: 18, color: Colors.grey))), 170 | 171 | /// Positive button 172 | TextButton( 173 | onPressed: positiveAction ?? () => Navigator.of(context).pop(), 174 | child: Text(positiveText ?? i18n.translate("OK"), 175 | style: _textStyle)), 176 | ], 177 | ); 178 | }); 179 | } 180 | -------------------------------------------------------------------------------- /DostiPak/lib/screens/notifications_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:rishtpak/api/notifications_api.dart'; 3 | import 'package:rishtpak/constants/constants.dart'; 4 | import 'package:rishtpak/dialogs/common_dialogs.dart'; 5 | import 'package:rishtpak/dialogs/progress_dialog.dart'; 6 | import 'package:rishtpak/helpers/app_localizations.dart'; 7 | import 'package:rishtpak/helpers/app_notifications.dart'; 8 | import 'package:rishtpak/widgets/badge.dart'; 9 | import 'package:rishtpak/widgets/no_data.dart'; 10 | import 'package:rishtpak/widgets/processing.dart'; 11 | import 'package:rishtpak/widgets/svg_icon.dart'; 12 | import 'package:flutter/material.dart'; 13 | import 'package:timeago/timeago.dart' as timeago; 14 | 15 | class NotificationsScreen extends StatelessWidget { 16 | // Variables 17 | final _notificationsApi = NotificationsApi(); 18 | final _appNotifications = AppNotifications(); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | /// Initialization 23 | final i18n = AppLocalizations.of(context); 24 | final pr = ProgressDialog(context); 25 | 26 | return Scaffold( 27 | appBar: AppBar( 28 | title: Text(i18n.translate("notifications")), 29 | actions: [ 30 | IconButton( 31 | icon: SvgIcon("assets/icons/trash_icon.svg"), 32 | onPressed: () async { 33 | /// Delete all Notifications 34 | /// 35 | /// Show confirm dialog 36 | confirmDialog(context, 37 | message: 38 | i18n.translate("all_notifications_will_be_deleted"), 39 | negativeAction: () => Navigator.of(context).pop(), 40 | positiveText: i18n.translate("DELETE"), 41 | positiveAction: () async { 42 | // Show processing dialog 43 | pr.show(i18n.translate("processing")); 44 | 45 | /// Delete 46 | await _notificationsApi.deleteUserNotifications(); 47 | 48 | // Hide progress dialog 49 | pr.hide(); 50 | // Hide confirm dialog 51 | Navigator.of(context).pop(); 52 | }); 53 | }) 54 | ], 55 | ), 56 | body: StreamBuilder( 57 | stream: _notificationsApi.getNotifications(), 58 | builder: (context, snapshot) { 59 | /// Check data 60 | if (!snapshot.hasData) { 61 | return Processing(text: i18n.translate("loading")); 62 | } else if (snapshot.data!.docs.isEmpty) { 63 | /// No notification 64 | return NoData( 65 | svgName: 'bell_icon', 66 | text: i18n.translate("no_notification")); 67 | } else { 68 | return ListView.separated( 69 | shrinkWrap: true, 70 | separatorBuilder: (context, index) => Divider(height: 10), 71 | itemCount: snapshot.data!.docs.length, 72 | itemBuilder: ((context, index) { 73 | /// Get notification DocumentSnapshot 74 | final DocumentSnapshot notification = 75 | snapshot.data!.docs[index]; 76 | final String? nType = notification[N_TYPE]; 77 | // Handle notification icon 78 | late ImageProvider bgImage; 79 | if (nType == 'alert') { 80 | bgImage = AssetImage('assets/images/app_logo.jpg'); 81 | } else { 82 | bgImage = NetworkImage(notification[N_SENDER_PHOTO_LINK]); 83 | } 84 | 85 | /// Show notification 86 | return Container( 87 | color: !notification[N_READ] 88 | ? Theme.of(context).primaryColor.withAlpha(40) 89 | : null, 90 | child: ListTile( 91 | leading: CircleAvatar( 92 | backgroundColor: Theme.of(context).primaryColor, 93 | backgroundImage: bgImage 94 | ), 95 | title: Text( 96 | notification[N_TYPE] == 'alert' 97 | ? notification[N_SENDER_FULLNAME] 98 | : notification[N_SENDER_FULLNAME].split(" ")[0], 99 | style: TextStyle(fontSize: 18)), 100 | subtitle: Text("${notification[N_MESSAGE]}\n" 101 | "${timeago.format(notification[TIMESTAMP].toDate())}"), 102 | trailing: !notification[N_READ] 103 | ? Badge(text: i18n.translate("new")) 104 | : null, 105 | onTap: () async { 106 | /// Set notification read = true 107 | await notification.reference.update({N_READ: true}); 108 | 109 | /// Handle notification click 110 | _appNotifications.onNotificationClick(context, 111 | nType: notification.data()?[N_TYPE] ?? '', 112 | nSenderId: notification.data()?[N_SENDER_ID] ?? '', 113 | nMessage: notification.data()?[N_MESSAGE] ?? ''); 114 | }, 115 | ), 116 | ); 117 | }), 118 | ); 119 | } 120 | }), 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/profile_statistics_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/dialogs/vip_dialog.dart'; 3 | import 'package:rishtpak/helpers/app_helper.dart'; 4 | import 'package:rishtpak/helpers/app_localizations.dart'; 5 | import 'package:rishtpak/models/user_model.dart'; 6 | import 'package:rishtpak/screens/disliked_profile_screen.dart'; 7 | import 'package:rishtpak/screens/profile_likes_screen.dart'; 8 | import 'package:rishtpak/screens/profile_visits_screen.dart'; 9 | import 'package:rishtpak/widgets/default_card_border.dart'; 10 | import 'package:rishtpak/widgets/svg_icon.dart'; 11 | 12 | class ProfileStatisticsCard extends StatelessWidget { 13 | // Text style 14 | final _textStyle = TextStyle( 15 | color: Colors.black, 16 | fontSize: 16.0, 17 | fontWeight: FontWeight.w500, 18 | ); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | /// Initialization 23 | final i18n = AppLocalizations.of(context); 24 | 25 | return Card( 26 | elevation: 4.0, 27 | color: Colors.grey[100], 28 | shape: defaultCardBorder(), 29 | child: Column( 30 | children: [ 31 | 32 | 33 | //Likes 34 | ListTile( 35 | leading: SvgIcon("assets/icons/heart_icon.svg", 36 | width: 22, height: 22, color: Theme.of(context).primaryColor), 37 | title: Text(i18n.translate("LIKES"), style: _textStyle), 38 | trailing: _counter(context, UserModel().user.userTotalLikes), 39 | onTap: () { 40 | 41 | //Check user is verified 42 | if(UserModel().user.userWallet > 0.0){ 43 | /// Go to profile likes screen 44 | Navigator.of(context).push( 45 | MaterialPageRoute( 46 | builder: (context) => ProfileLikesScreen())); 47 | } 48 | else { 49 | /// Show VIP dialog 50 | showDialog( 51 | context: context, 52 | builder: (context) => VipDialog()); 53 | } 54 | 55 | 56 | }, 57 | ), 58 | Divider(height: 0), 59 | 60 | 61 | 62 | //Visits 63 | ListTile( 64 | leading: SvgIcon("assets/icons/eye_icon.svg", 65 | width: 31, height: 31, color: Theme.of(context).primaryColor), 66 | title: Text(i18n.translate("VISITS"), style: _textStyle), 67 | trailing: _counter(context, UserModel().user.userTotalVisits), 68 | onTap: () { 69 | 70 | //Check user is verified 71 | if(UserModel().user.userWallet > 0.0){ 72 | /// Go to profile visits screen 73 | Navigator.of(context).push( 74 | MaterialPageRoute( 75 | builder: (context) => ProfileVisitsScreen())); 76 | } 77 | else { 78 | /// Show VIP dialog 79 | showDialog( 80 | context: context, 81 | builder: (context) => VipDialog()); 82 | } 83 | 84 | }, 85 | ), 86 | Divider(height: 0), 87 | 88 | 89 | 90 | //Dislikes 91 | ListTile( 92 | leading: SvgIcon("assets/icons/close_icon.svg", 93 | width: 25, height: 25, color: Theme.of(context).primaryColor), 94 | title: Text(i18n.translate("DISLIKED_PROFILES"), style: _textStyle), 95 | trailing: _counter(context, UserModel().user.userTotalDisliked), 96 | onTap: () { 97 | 98 | //Check user is verified 99 | if(UserModel().user.userWallet > 0.0){ 100 | /// Go to disliked profile screen 101 | Navigator.of(context).push( 102 | MaterialPageRoute( 103 | builder: (context) => DislikedProfilesScreen())); 104 | } 105 | else { 106 | /// Show VIP dialog 107 | showDialog( 108 | context: context, 109 | builder: (context) => VipDialog()); 110 | } 111 | 112 | }, 113 | ), 114 | Divider(height: 0), 115 | 116 | 117 | 118 | //Watch video 119 | ListTile( 120 | leading: Image.asset( 121 | "assets/icons/icon_video.png", 122 | width: 25, 123 | height: 25, 124 | fit: BoxFit.cover), 125 | title: Text(i18n.translate("watch_video"), style: _textStyle), 126 | onTap: () async{ 127 | /// Open link 128 | await AppHelper().openWatchVideo(); 129 | }, 130 | ), 131 | Divider(height: 0), 132 | 133 | 134 | //Earn money 135 | ListTile( 136 | leading: Image.asset( 137 | "assets/icons/icon_money.png", 138 | width: 25, 139 | height: 25, 140 | fit: BoxFit.cover), 141 | title: Text(i18n.translate("earn_money"), style: _textStyle), 142 | onTap: () async{ 143 | /// Open link 144 | await AppHelper().openEarnMoney(); 145 | }, 146 | ), 147 | 148 | 149 | ], 150 | ), 151 | ); 152 | } 153 | 154 | Widget _counter(BuildContext context, int value) { 155 | return Container( 156 | decoration: BoxDecoration( 157 | color: Theme.of(context).primaryColor, //.withAlpha(85), 158 | shape: BoxShape.circle), 159 | padding: const EdgeInsets.all(6.0), 160 | child: Text(value.toString(), style: TextStyle(color: Colors.white))); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /DostiPak/lib/api/likes_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:rishtpak/api/dislikes_api.dart'; 3 | import 'package:rishtpak/api/notifications_api.dart'; 4 | import 'package:rishtpak/constants/constants.dart'; 5 | import 'package:rishtpak/models/user_model.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | class LikesApi { 9 | /// FINAL VARIABLES 10 | /// 11 | final _firestore = FirebaseFirestore.instance; 12 | final _notificationsApi = NotificationsApi(); 13 | 14 | /// Save liked user 15 | Future _saveLike({ 16 | required String likedUserId, 17 | required String userDeviceToken, 18 | required String nMessage, 19 | }) async { 20 | _firestore.collection(C_LIKES).add({ 21 | LIKED_USER_ID: likedUserId, 22 | LIKED_BY_USER_ID: UserModel().user.userId, 23 | TIMESTAMP: FieldValue.serverTimestamp() 24 | }).then((_) async { 25 | /// Update user total likes 26 | await UserModel().updateUserData( 27 | userId: likedUserId, 28 | data: {USER_TOTAL_LIKES: FieldValue.increment(1)}); 29 | 30 | /// Save notification in database 31 | await _notificationsApi.saveNotification( 32 | nReceiverId: likedUserId, 33 | nType: 'like', 34 | nMessage: nMessage, 35 | ); 36 | 37 | /// Send push notification 38 | await _notificationsApi.sendPushNotification( 39 | nTitle: APP_NAME, 40 | nBody: nMessage, 41 | nType: 'like', 42 | nSenderId: UserModel().user.userId, 43 | nUserDeviceToken: userDeviceToken); 44 | }); 45 | } 46 | 47 | /// Like user profile 48 | Future likeUser( 49 | {required String likedUserId, 50 | required String userDeviceToken, 51 | required String nMessage, 52 | required Function(bool) onLikeResult}) async { 53 | /// Check if current user already liked profile 54 | _firestore 55 | .collection(C_LIKES) 56 | .where(LIKED_BY_USER_ID, isEqualTo: UserModel().user.userId) 57 | .where(LIKED_USER_ID, isEqualTo: likedUserId) 58 | .get() 59 | .then((QuerySnapshot snapshot) async { 60 | if (snapshot.docs.isEmpty) { 61 | onLikeResult(true); 62 | 63 | // Like user 64 | await _saveLike( 65 | likedUserId: likedUserId, 66 | nMessage: nMessage, 67 | userDeviceToken: userDeviceToken); 68 | debugPrint('likeUser() -> success'); 69 | } else { 70 | onLikeResult(false); 71 | debugPrint('You already liked the user'); 72 | } 73 | }).catchError((e) { 74 | print('likeUser() -> error: $e'); 75 | }); 76 | } 77 | 78 | 79 | 80 | /// Get Liked Profiles 81 | Future> getLikedProfiles() async{ 82 | List likedProfiles = (await _firestore 83 | .collection(C_LIKES) 84 | .where(LIKED_BY_USER_ID, isEqualTo: UserModel().user.userId) 85 | .get()).docs; 86 | 87 | return likedProfiles; 88 | } 89 | 90 | 91 | 92 | /// Get users who liked current user profile 93 | Future> getLikedMeUsers( 94 | {bool loadMore = false, 95 | DocumentSnapshot? userLastDoc}) async { 96 | /// Build Users query 97 | Query usersQuery = _firestore 98 | .collection(C_LIKES) 99 | .where(LIKED_USER_ID, isEqualTo: UserModel().user.userId); 100 | 101 | /// Check load more 102 | if (loadMore) { 103 | usersQuery = usersQuery.startAfterDocument(userLastDoc!); 104 | } 105 | 106 | /// Finalize query and Limit data 107 | usersQuery = usersQuery.orderBy(TIMESTAMP, descending: true); 108 | usersQuery = usersQuery.limit(20); 109 | 110 | final QuerySnapshot querySnapshot = await usersQuery.get().catchError((e) { 111 | print('getLikedMeUsers() -> error: $e'); 112 | }); 113 | 114 | return querySnapshot.docs; 115 | } 116 | 117 | /// Delete liked profile: when current user decides to dislike it 118 | Future deleteLike(String likedUserId) async { 119 | _firestore 120 | .collection(C_LIKES) 121 | .where(LIKED_USER_ID, isEqualTo: likedUserId) 122 | .where(LIKED_BY_USER_ID, isEqualTo: UserModel().user.userId) 123 | .get() 124 | .then((QuerySnapshot snapshot) async { 125 | final ref = snapshot.docs.first; 126 | await ref.reference.delete().then((value) => print('liked user is remove it')); 127 | 128 | }).catchError((e) { 129 | print('deleteLike() -> error: $e'); 130 | }); 131 | } 132 | 133 | // Delete liked profile ids by current user 134 | Future deleteLikedUsers() async { 135 | _firestore 136 | .collection(C_LIKES) 137 | .where(LIKED_BY_USER_ID, isEqualTo: UserModel().user.userId) 138 | .get() 139 | .then((QuerySnapshot snapshot) async { 140 | /// Check docs 141 | if (snapshot.docs.isNotEmpty) { 142 | // Loop docs to be deleted 143 | snapshot.docs.forEach((doc) async { 144 | await doc.reference.delete(); 145 | }); 146 | debugPrint('deleteLikedUsers() -> deleted'); 147 | } 148 | }); 149 | } 150 | 151 | // Delete user id from profiles who liked the current user 152 | Future deleteLikedMeUsers() async { 153 | _firestore 154 | .collection(C_LIKES) 155 | .where(LIKED_USER_ID, isEqualTo: UserModel().user.userId) 156 | .get() 157 | .then((QuerySnapshot snapshot) async { 158 | /// Check docs 159 | if (snapshot.docs.isNotEmpty) { 160 | // Loop docs to be deleted 161 | snapshot.docs.forEach((doc) async { 162 | await doc.reference.delete(); 163 | }); 164 | debugPrint('deleteLikedMeUsers() -> deleted'); 165 | } 166 | }); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dosti Pak # 2 | 3 | Dating app to make connection between people and start new love story to lovers. 4 | 5 | This app is using Firebase to fetch data and display it on the screen. 6 | 7 | 8 |
9 | logoPhoto 10 |
11 | 12 |
13 | check_commit 14 | check_repo 15 | license 16 | repo_size 17 |
18 | 19 | 20 | 21 | ## Screenshots ## 22 | 23 |
24 | photo1 25 | photo2 26 | photo3 27 | photo3 28 |
29 | 30 | 31 | 32 | ### What is this repository for? ### 33 | 34 | * Quick summary 35 | * Version 36 | * [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo) 37 | 38 | ### How do I get set up? ### 39 | ** Flutter Complete Dating App for Android & iOS with Admin Panel is a full functional application that is ready to go production, however you need to follow some Steps to build your app release and publish it on your Google Play Store or Apple Store account. 40 | 41 | ### App Features 42 | 43 | - VIP Subscriptions - (In-App purchases) 44 | - Admob Interstials Ads 45 | - Multi-language support 46 | - Push notifications - (New like, visit and message) 47 | - Swipe Left/Right - (like tinder profile cards) 48 | - It's a Match dialog 49 | - Chat with text and image 50 | - Sign in with phone number 51 | - Report User Profile feature 52 | - Profile statistics (Total likes, visits, dislikes) 53 | - Share the app with friends 54 | - Rate app on app stores 55 | - Get location by GPS 56 | - Show Users based on geolocation distance radius in (km) 57 | - Update location 58 | - Change max distance radius in (km) 59 | - Change age range filter 60 | - Alert user to enable GPS 61 | - Redirect user to enable location permission in device settings 62 | - Redirect user to blocked account screen 63 | - Redirect user to update app 64 | - Sign out button 65 | - Delete Account button 66 | - Hide user profile 67 | - Show me Filter: Men, Women or Everyone 68 | - Passport feature - Latest update 69 | - Backend with Cloud Firestore 70 | - Firebase Free Account Support 71 | - Typing feature 72 | - Online/Offline feature 73 | - change authentication from phone number to email and password 74 | - send voice message 75 | - send sticker message 76 | - Add wallet to user 77 | - using in app purchase 78 | - Add oneSignal feature 79 | - Display ads by google mobile ads package 80 | - update image picker dialog 81 | - New logo icon and using flutter_native_splash package 82 | - change icons and make new buttons 83 | 84 | ### App Screens 85 | - Splash Screen 86 | - Sign In Screen 87 | - Login with Phone Number Screen 88 | - Verification Code Screen (OTP) 89 | - Sign Up Screen 90 | - Home Screen 91 | - Profile Screen 92 | - Profile Likes Screen 93 | - Profile Visits Screen 94 | - Disliked Profiles Screen 95 | - Edit Profile Screen 96 | - Notifications Screen 97 | - Chat Screen 98 | - It’s Match Dialog 99 | - About Us Screen 100 | - Blocked Account Screen 101 | - Enable Location Screen 102 | - Settings Screen 103 | - Update App Screen 104 | - Delete Account Screen 105 | - Passport Screen 106 | 107 | ### Tabs 108 | 109 | - Discover Tab 110 | - Matches Tab 111 | - Conversations Tab 112 | - Profile Tab 113 | 114 | ### Icons 115 | 116 | - Beautiful outline SVG icons used in project 117 | 118 | ### 02) Project requirements - 119 | 120 | - Flutter SDK Version: 2.0.6 or later - Null Safety Support 121 | - Dart Version: 2.12.0 or later - Null Safety Support 122 | - Android target SDK version: 30 or later 123 | - Recommended Editors: Visual Studio Code/ Android Studio/ or Xcode for iOS 124 | 125 | 126 | ### Get Started with Flutter - top 127 | What is flutter? 128 | 129 | Flutter is an open-source cross-platform mobile application SDK developed by Google. 130 | It is used to develop applications for Android, iOS , Web , Linux, Mac, Windows, Google Fuchsia, and the web from a single codebase. Flutter is written in Dart language. 131 | 132 | Why Flutter? 133 | 134 | Flutter saves companies the need to employ different iOS and Android developers to build the same app since it supports cross-platforms development. 135 | 136 | Development Platforms 137 | 138 | After having a basic idea of what flutter is, now it's time to setup your dating_app project. 139 | 140 | Flutter requires Flutter SDK to be installed first. 141 | Please check this link https://flutter.dev/docs/get-started/install for official documentation on steps how to install Flutter SDK on your Operating System and set up your favourable editor. 142 | 143 | ### 04) Open Dating App Project - top 144 | How to open project using the Visual Studio Code; - (For other IDEs please follow their official instructions) 145 | 146 | Start the Visual Studio Code. or your favorite IDE Editor 147 | Select File > Open Folder from the main menu and then choose > Flutter Dating App > SOURCE CODES > dating_app folder. 148 | Run the this command: flutter pub get on your Terminal Editor to get flutter packages. 149 | -------------------------------------------------------------------------------- /DostiPak/lib/constants/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// APP SETINGS INFO CONSTANTS - SECTION /// 4 | /// 5 | const String APP_NAME = "Dosti Pak"; 6 | const Color APP_PRIMARY_COLOR = Colors.indigo; 7 | const Color APP_ACCENT_COLOR = Colors.indigoAccent; 8 | const String APP_VERSION_NAME = "v2.0.0"; 9 | const int ANDROID_APP_VERSION_NUMBER = 2; // Google Play Version Number 10 | const int IOS_APP_VERSION_NUMBER = 2; // App Store Version Number 11 | // 12 | // Add Google Maps - API KEY required for Passport feature 13 | // 14 | const String ANDROID_MAPS_API_KEY = "YOUR ANDROID API KEY"; 15 | const String IOS_MAPS_API_KEY = "YOUR IOS API KEY"; 16 | // 17 | // GOOGLE ADMOB INTERSTITIAL IDS 18 | // 19 | // For Android Platform 20 | const String ANDROID_INTERSTITIAL_ID = "...................."; 21 | // For IOS Platform 22 | const String IOS_INTERSTITIAL_ID = "YOUR iOS AD ID"; 23 | 24 | /// List of Supported Locales 25 | /// Add your new supported Locale to the array list. 26 | /// 27 | /// E.g: Locale('fr'), Locale('es'), 28 | /// 29 | const List SUPPORTED_LOCALES = [ 30 | Locale('en'), 31 | ]; 32 | /// 33 | /// END APP SETINGS - SECTION 34 | 35 | 36 | /// 37 | /// DATABASE COLLECTIONS FIELD - SECTION 38 | /// 39 | /// FIREBASE MESSAGING TOPIC 40 | const NOTIFY_USERS = "NOTIFY_USERS"; 41 | 42 | /// DATABASE COLLECTION NAMES USED IN APP 43 | /// 44 | const String C_APP_INFO = "AppInfo"; 45 | const String C_USERS = "Users"; 46 | const String C_FLAGGED_USERS = "FlaggedUsers"; 47 | const String C_CONNECTIONS = "Connections"; 48 | const String C_MATCHES = "Matches"; 49 | const String C_CONVERSATIONS = "Conversations"; 50 | const String C_LIKES = "Likes"; 51 | const String C_VISITS = "Visits"; 52 | const String C_DISLIKES = "Dislikes"; 53 | const String C_MESSAGES = "Messages"; 54 | const String C_NOTIFICATIONS = "Notifications"; 55 | 56 | /// DATABASE FIELDS FOR AppInfo COLLECTION /// 57 | /// 58 | const String ANDROID_APP_CURRENT_VERSION = "android_app_current_version"; 59 | const String IOS_APP_CURRENT_VERSION = "ios_app_current_version"; 60 | const String ANDROID_PACKAGE_NAME = "android_package_name"; 61 | const String IOS_APP_ID = "ios_app_id"; 62 | const String APP_EMAIL = "app_email"; 63 | const String PRIVACY_POLICY_URL = "privacy_policy_url"; 64 | const String TERMS_OF_SERVICE_URL = "terms_of_service_url"; 65 | const String FIREBASE_SERVER_KEY = "firebase_server_key"; 66 | const String STORE_SUBSCRIPTION_IDS = "store_subscription_ids"; 67 | const String FREE_ACCOUNT_MAX_DISTANCE = "free_account_max_distance"; 68 | const String VIP_ACCOUNT_MAX_DISTANCE = "vip_account_max_distance"; 69 | // Admob variables 70 | const String ADMOB_APP_ID = "admob_app_id"; 71 | const String ADMOB_INTERSTITIAL_AD_ID = "admob_interstitial_ad_id"; 72 | 73 | /// DATABASE FIELDS FOR USER COLLECTION /// 74 | /// 75 | 76 | //NEW 77 | const String USER_WALLET = "user_wallet"; 78 | const String USER_ONLINE = "user_online"; 79 | const String USER_TYPING = "user_typing"; 80 | //NEW 81 | const String USER_ID = "user_id"; 82 | const String USER_PROFILE_PHOTO = "user_photo_link"; 83 | const String USER_FULLNAME = "user_fullname"; 84 | const String USER_GENDER = "user_gender"; 85 | const String USER_BIRTH_DAY = "user_birth_day"; 86 | const String USER_BIRTH_MONTH = "user_birth_month"; 87 | const String USER_BIRTH_YEAR = "user_birth_year"; 88 | const String USER_SCHOOL = "user_school"; 89 | const String USER_JOB_TITLE = "user_job_title"; 90 | const String USER_BIO = "user_bio"; 91 | const String USER_PHONE_NUMBER = "user_phone_number"; 92 | const String USER_EMAIL = "user_email"; 93 | const String USER_GALLERY = "user_gallery"; 94 | const String USER_COUNTRY = "user_country"; 95 | const String USER_LOCALITY = "user_locality"; 96 | const String USER_GEO_POINT = "user_geo_point"; 97 | const String USER_SETTINGS = "user_settings"; 98 | const String USER_STATUS = "user_status"; 99 | const String USER_IS_VERIFIED = "user_is_verified"; 100 | const String USER_LEVEL = "user_level"; 101 | const String USER_REG_DATE = "user_reg_date"; 102 | const String USER_LAST_LOGIN = "user_last_login"; 103 | const String USER_DEVICE_TOKEN = "user_device_token"; 104 | const String USER_TOTAL_LIKES = "user_total_likes"; 105 | const String USER_TOTAL_VISITS = "user_total_visits"; 106 | const String USER_TOTAL_DISLIKED = "user_total_disliked"; 107 | // User Setting map - fields 108 | const String USER_MIN_AGE = "user_min_age"; 109 | const String USER_MAX_AGE = "user_max_age"; 110 | const String USER_MAX_DISTANCE = "user_max_distance"; 111 | const String USER_SHOW_ME = "user_show_me"; 112 | 113 | 114 | /// DATABASE FIELDS FOR FlaggedUsers COLLECTION /// 115 | /// 116 | const String FLAGGED_USER_ID = "flagged_user_id"; 117 | const String FLAG_REASON = "flag_reason"; 118 | const String FLAGGED_BY_USER_ID = "flagged_by_user_id"; 119 | 120 | /// DATABASE FIELDS FOR Messages and Conversations COLLECTION /// 121 | /// 122 | const String MESSAGE_TEXT = "message_text"; 123 | const String MESSAGE_TYPE = "message_type"; 124 | const String MESSAGE_IMG_LINK = "message_img_link"; 125 | const String MESSAGE_AUDIO_LINK = "message_audio_link"; 126 | const String MESSAGE_GIF_LINK = "message_gif_link"; 127 | const String MESSAGE_STICKER_LINK = "message_sticker_link"; 128 | const String MESSAGE_READ = "message_read"; 129 | const String LAST_MESSAGE = "last_message"; 130 | 131 | /// DATABASE FIELDS FOR Notifications COLLECTION /// 132 | /// 133 | const N_SENDER_ID = "n_sender_id"; 134 | const N_SENDER_FULLNAME = "n_sender_fullname"; 135 | const N_SENDER_PHOTO_LINK = "n_sender_photo_link"; 136 | const N_RECEIVER_ID = "n_receiver_id"; 137 | const N_TYPE = "n_type"; 138 | const N_MESSAGE = "n_message"; 139 | const N_READ = "n_read"; 140 | 141 | /// DATABASE FIELDS FOR Likes COLLECTION 142 | /// 143 | const String LIKED_USER_ID = 'liked_user_id'; 144 | const String LIKED_BY_USER_ID = 'liked_by_user_id'; 145 | const String LIKE_TYPE = 'like_type'; 146 | 147 | /// DATABASE FIELDS FOR Dislikes COLLECTION 148 | /// 149 | const String DISLIKED_USER_ID = 'disliked_user_id'; 150 | const String DISLIKED_BY_USER_ID = 'disliked_by_user_id'; 151 | 152 | /// DATABASE FIELDS FOR Visits COLLECTION 153 | /// 154 | const String VISITED_USER_ID = 'visited_user_id'; 155 | const String VISITED_BY_USER_ID = 'visited_by_user_id'; 156 | 157 | /// DATABASE SHARED FIELDS FOR COLLECTION 158 | /// 159 | const String TIMESTAMP = "timestamp"; 160 | 161 | 162 | 163 | ///Remove Ads (NEW) 164 | const bool REMOVE_ADS = true; 165 | bool isFirstTime = true; 166 | 167 | -------------------------------------------------------------------------------- /DostiPak/lib/screens/profile_visits_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:rishtpak/api/visits_api.dart'; 3 | import 'package:rishtpak/constants/constants.dart'; 4 | import 'package:rishtpak/datas/user.dart'; 5 | import 'package:rishtpak/dialogs/vip_dialog.dart'; 6 | import 'package:rishtpak/helpers/app_localizations.dart'; 7 | import 'package:rishtpak/models/user_model.dart'; 8 | import 'package:rishtpak/screens/profile_screen.dart'; 9 | import 'package:rishtpak/widgets/build_title.dart'; 10 | import 'package:rishtpak/widgets/loading_card.dart'; 11 | import 'package:rishtpak/widgets/no_data.dart'; 12 | import 'package:rishtpak/widgets/processing.dart'; 13 | import 'package:rishtpak/widgets/profile_card.dart'; 14 | import 'package:rishtpak/widgets/users_grid.dart'; 15 | import 'package:flutter/material.dart'; 16 | 17 | class ProfileVisitsScreen extends StatefulWidget { 18 | @override 19 | _ProfileVisitsScreenState createState() => _ProfileVisitsScreenState(); 20 | } 21 | 22 | class _ProfileVisitsScreenState extends State { 23 | 24 | // Variables 25 | final ScrollController _gridViewController = new ScrollController(); 26 | final VisitsApi _visitsApi = VisitsApi(); 27 | late AppLocalizations _i18n; 28 | List? _userVisits; 29 | late DocumentSnapshot _userLastDoc; 30 | bool _loadMore = true; 31 | 32 | /// Load more users 33 | void _loadMoreUsersListener() async { 34 | _gridViewController.addListener(() { 35 | if (_gridViewController.position.pixels == 36 | _gridViewController.position.maxScrollExtent) { 37 | /// Load more users 38 | if (_loadMore) { 39 | _visitsApi 40 | .getUserVisits(loadMore: true, userLastDoc: _userLastDoc) 41 | .then((users) { 42 | /// Update users list 43 | if (users.isNotEmpty) { 44 | _updateUserList(users); 45 | } else { 46 | setState(() => _loadMore = false); 47 | } 48 | print('load more users: ${users.length}'); 49 | }); 50 | } else { 51 | print('No more users'); 52 | } 53 | } 54 | }); 55 | } 56 | 57 | /// Update list 58 | void _updateUserList(List users) { 59 | if (mounted) { 60 | setState(() { 61 | _userVisits!.addAll(users); 62 | if (users.isNotEmpty) { 63 | _userLastDoc = users.last; 64 | } 65 | }); 66 | } 67 | } 68 | 69 | @override 70 | void initState() { 71 | super.initState(); 72 | _visitsApi.getUserVisits().then((users) { 73 | // Check result 74 | if (users.isNotEmpty) { 75 | if (mounted) { 76 | setState(() { 77 | _userVisits = users; 78 | _userLastDoc = users.last; 79 | }); 80 | } 81 | } else { 82 | setState(() => _userVisits = []); 83 | } 84 | }); 85 | 86 | /// Listener 87 | _loadMoreUsersListener(); 88 | } 89 | 90 | @override 91 | void dispose() { 92 | _gridViewController.dispose(); 93 | super.dispose(); 94 | } 95 | 96 | @override 97 | Widget build(BuildContext context) { 98 | /// Initialization 99 | _i18n = AppLocalizations.of(context); 100 | 101 | return Scaffold( 102 | appBar: AppBar( 103 | title: Text(_i18n.translate("visits")), 104 | ), 105 | body: Column( 106 | children: [ 107 | /// Header Title 108 | BuildTitle( 109 | svgIconName: "eye_icon", 110 | title: _i18n.translate("users_who_visited_you"), 111 | ), 112 | /// Matches 113 | Expanded(child: _showProfiles()) 114 | ], 115 | )); 116 | } 117 | 118 | /// Show profiles 119 | Widget _showProfiles() { 120 | if (_userVisits == null) { 121 | return Processing(text: _i18n.translate("loading")); 122 | } else if (_userVisits!.isEmpty) { 123 | // No data 124 | return NoData(svgName: 'eye_icon', text: _i18n.translate("no_visit")); 125 | } else { 126 | /// Show users 127 | return UsersGrid( 128 | gridViewController: _gridViewController, 129 | itemCount: _userVisits!.length + 1, /// Workaround for loading more 130 | itemBuilder: (context, index) { 131 | /// Validate fake index 132 | if (index < _userVisits!.length) { 133 | /// Get user id 134 | final userId = _userVisits![index][VISITED_BY_USER_ID]; 135 | 136 | /// Load profile 137 | return FutureBuilder( 138 | future: UserModel().getUser(userId), 139 | builder: (context, snapshot) { 140 | /// Chech result 141 | if (!snapshot.hasData) return LoadingCard(); 142 | /// Get user object 143 | final User user = 144 | User.fromDocument(snapshot.data!.data()!); 145 | /// Show user card 146 | return GestureDetector( 147 | child: ProfileCard(user: user, page: 'require_vip' , showLiking: false, isLiked: false, isDisLiked: false,), 148 | onTap: () { 149 | /// Check vip account 150 | if (UserModel().user.userIsVerified || UserModel().user.userWallet > 0.0) { 151 | /// Go to profile screen - using showDialog to 152 | /// prevents reloading getUser FutureBuilder 153 | showDialog(context: context, 154 | barrierDismissible: false, 155 | builder: (context) { 156 | return ProfileScreen( 157 | user: user, 158 | hideDislikeButton: true 159 | ); 160 | } 161 | ); 162 | /// Increment user visits an push notification 163 | _visitsApi.visitUserProfile( 164 | visitedUserId: user.userId, 165 | userDeviceToken: user.userDeviceToken, 166 | nMessage: "${UserModel().user.userFullname.split(' ')[0]}, " 167 | "${_i18n.translate("visited_your_profile_click_and_see")}", 168 | ); 169 | } else { 170 | /// Show VIP dialog 171 | showDialog(context: context, 172 | builder: (context) => VipDialog()); 173 | } 174 | }, 175 | ); 176 | }); 177 | } else { 178 | return Container(); 179 | } 180 | }, 181 | ); 182 | } 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /DostiPak/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:country_code_picker/country_localizations.dart'; 4 | import 'package:firebase_messaging/firebase_messaging.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_localizations/flutter_localizations.dart'; 7 | import 'package:google_mobile_ads/google_mobile_ads.dart'; 8 | import 'package:in_app_purchase/in_app_purchase.dart'; 9 | import 'package:onesignal_flutter/onesignal_flutter.dart'; 10 | import 'package:rishtpak/helpers/app_localizations.dart'; 11 | import 'package:rishtpak/models/app_model.dart'; 12 | import 'package:rishtpak/models/user_model.dart'; 13 | import 'package:rishtpak/screens/splash_screen.dart'; 14 | import 'package:scoped_model/scoped_model.dart'; 15 | import 'package:firebase_core/firebase_core.dart'; 16 | import 'constants/constants.dart'; 17 | 18 | void main() async { 19 | // For play billing library 2.0 on Android, it is mandatory to call 20 | // [enablePendingPurchases](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder.html#enablependingpurchases) 21 | // as part of initializing the app. 22 | InAppPurchaseConnection.enablePendingPurchases(); 23 | // Initialized before calling runApp to init firebase app 24 | WidgetsFlutterBinding.ensureInitialized(); 25 | 26 | 27 | // Initialize firebase app 28 | await Firebase.initializeApp(); 29 | 30 | 31 | // Initialize Google Mobile Ads SDK 32 | await MobileAds.instance.initialize().then((InitializationStatus status) { 33 | print('Initialization done: ${status.adapterStatuses}'); 34 | MobileAds.instance.updateRequestConfiguration( 35 | RequestConfiguration( 36 | tagForChildDirectedTreatment: TagForChildDirectedTreatment.unspecified, 37 | testDeviceIds: ['']), //when you run first time you will get your test id in logs then update it here ["test id"] 38 | ); 39 | }); 40 | 41 | /// Update the iOS foreground notification presentation options to allow 42 | /// heads up notifications. 43 | /// Check iOS device 44 | if (Platform.isIOS) { 45 | await FirebaseMessaging.instance 46 | .setForegroundNotificationPresentationOptions( 47 | alert: true, 48 | badge: true, 49 | sound: true, 50 | ); 51 | } 52 | 53 | 54 | 55 | /// One Signal setup 56 | // Remove this method to stop OneSignal Debugging 57 | // OneSignal.shared.setLogLevel(OSLogLevel.verbose, OSLogLevel.none); 58 | 59 | OneSignal.shared.setAppId(".............................."); 60 | 61 | // The promptForPushNotificationsWithUserResponse function will show the iOS push notification prompt. We recommend removing the following code and instead using an In-App Message to prompt for notification permission 62 | OneSignal.shared.promptUserForPushNotificationPermission().then((accepted) { 63 | print("Accepted permission: $accepted"); 64 | }); 65 | 66 | 67 | 68 | 69 | OneSignal.shared.setNotificationWillShowInForegroundHandler((OSNotificationReceivedEvent event) { 70 | // Will be called whenever a notification is received in foreground 71 | // Display Notification, pass null param for not displaying the notification 72 | event.complete(event.notification); 73 | }); 74 | 75 | OneSignal.shared.setNotificationOpenedHandler((OSNotificationOpenedResult result) { 76 | // Will be called whenever a notification is opened/button pressed. 77 | }); 78 | 79 | OneSignal.shared.setPermissionObserver((OSPermissionStateChanges changes) { 80 | // Will be called whenever the permission changes 81 | // (ie. user taps Allow on the permission prompt in iOS) 82 | }); 83 | 84 | OneSignal.shared.setSubscriptionObserver((OSSubscriptionStateChanges changes) { 85 | // Will be called whenever the subscription changes 86 | // (ie. user gets registered with OneSignal and gets a user ID) 87 | }); 88 | 89 | OneSignal.shared.setEmailSubscriptionObserver((OSEmailSubscriptionStateChanges emailChanges) { 90 | // Will be called whenever then user's email subscription changes 91 | // (ie. OneSignal.setEmail(email) is called and the user gets registered 92 | }); 93 | 94 | 95 | 96 | 97 | runApp(MyApp()); 98 | } 99 | 100 | // Define the Navigator global key state to be used when the build context is not available! 101 | final GlobalKey navigatorKey = GlobalKey(); 102 | 103 | class MyApp extends StatelessWidget { 104 | 105 | @override 106 | Widget build(BuildContext context) { 107 | return ScopedModel( 108 | model: AppModel(), 109 | child: ScopedModel( 110 | model: UserModel(), 111 | child: MaterialApp( 112 | navigatorKey: navigatorKey, 113 | title: APP_NAME, 114 | debugShowCheckedModeBanner: false, 115 | 116 | /// Setup translations 117 | localizationsDelegates: [ 118 | // AppLocalizations is where the lang translations is loaded 119 | AppLocalizations.delegate, 120 | CountryLocalizations.delegate, 121 | GlobalMaterialLocalizations.delegate, 122 | GlobalWidgetsLocalizations.delegate, 123 | ], 124 | supportedLocales: SUPPORTED_LOCALES, 125 | 126 | /// Returns a locale which will be used by the app 127 | localeResolutionCallback: (locale, supportedLocales) { 128 | // Check if the current device locale is supported 129 | for (var supportedLocale in supportedLocales) { 130 | if (supportedLocale.languageCode == locale!.languageCode) { 131 | return supportedLocale; 132 | } 133 | } 134 | 135 | /// If the locale of the device is not supported, use the first one 136 | /// from the list (English, in this case). 137 | return supportedLocales.first; 138 | }, 139 | home: SplashScreen(), 140 | theme: _appTheme(), 141 | ), 142 | ), 143 | ); 144 | } 145 | 146 | 147 | 148 | 149 | } 150 | 151 | /// App theme 152 | ThemeData _appTheme() { 153 | return ThemeData( 154 | primaryColor: APP_PRIMARY_COLOR, 155 | accentColor: APP_ACCENT_COLOR, 156 | scaffoldBackgroundColor: Colors.white, 157 | inputDecorationTheme: InputDecorationTheme( 158 | errorStyle: TextStyle(fontSize: 16), 159 | border: OutlineInputBorder( 160 | borderRadius: BorderRadius.circular(28), 161 | )), 162 | appBarTheme: AppBarTheme( 163 | color: Colors.white, 164 | elevation: Platform.isIOS ? 0 : 4.0, 165 | iconTheme: IconThemeData(color: Colors.black), 166 | brightness: Brightness.light, 167 | textTheme: TextTheme( 168 | headline6: TextStyle(color: Colors.grey, fontSize: 18), 169 | ), 170 | ), 171 | ); 172 | } 173 | -------------------------------------------------------------------------------- /DostiPak/lib/helpers/app_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:rishtpak/constants/constants.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:geocoding/geocoding.dart'; 6 | import 'package:geolocator/geolocator.dart'; 7 | import 'package:share/share.dart'; 8 | import 'package:url_launcher/url_launcher.dart'; 9 | import 'package:cloud_firestore/cloud_firestore.dart'; 10 | import 'package:geoflutterfire/geoflutterfire.dart'; 11 | import 'package:rishtpak/models/user_model.dart'; 12 | import 'package:rishtpak/models/app_model.dart'; 13 | 14 | class AppHelper { 15 | /// Local variables 16 | final _firestore = FirebaseFirestore.instance; 17 | 18 | /// Check and request location permission 19 | Future checkLocationPermission( 20 | {required VoidCallback onGpsDisabled, 21 | required VoidCallback onDenied, 22 | required VoidCallback onGranted}) async { 23 | /// Check if GPS is enabled 24 | if (!(await Geolocator.isLocationServiceEnabled())) { 25 | // Callback function 26 | onGpsDisabled(); 27 | debugPrint('onGpsDisabled() -> disabled'); 28 | } else { 29 | /// Request permission 30 | final LocationPermission permission = 31 | await Geolocator.requestPermission(); 32 | 33 | switch (permission) { 34 | case LocationPermission.denied: 35 | onDenied(); 36 | debugPrint('permission: denied'); 37 | break; 38 | case LocationPermission.deniedForever: 39 | onDenied(); 40 | debugPrint('permission: deniedForever'); 41 | break; 42 | case LocationPermission.whileInUse: 43 | onGranted(); 44 | debugPrint('permission: whileInUse'); 45 | break; 46 | case LocationPermission.always: 47 | onGranted(); 48 | debugPrint('permission: always'); 49 | break; 50 | } 51 | } 52 | } 53 | 54 | /// Get User location from formatted address 55 | Future getUserAddress(double latitude, double longitude) async { 56 | /// Place object containing formatted address info 57 | Placemark place; 58 | 59 | /// Get Placemark to retrieve user location 60 | final List places = 61 | await placemarkFromCoordinates(latitude, longitude); 62 | 63 | /// Get and returns the first place 64 | place = places.first; 65 | 66 | return place; 67 | } 68 | 69 | /// Get distance between current user and another user 70 | /// Returns distance in (Kilometers - KM) 71 | int getDistanceBetweenUsers( 72 | {required double userLat, required double userLong}) { 73 | /// Get instance of [Geoflutterfire] 74 | final Geoflutterfire geo = new Geoflutterfire(); 75 | 76 | /// Set current user location [GeoFirePoint] 77 | final GeoFirePoint center = geo.point( 78 | latitude: UserModel().user.userGeoPoint.latitude, 79 | longitude: UserModel().user.userGeoPoint.longitude); 80 | 81 | /// Return distance (double) between users then round to int 82 | return center.distance(lat: userLat, lng: userLong).round(); 83 | } 84 | 85 | /// Get app store URL - Google Play / Apple Store 86 | String get _appStoreUrl { 87 | String url = ""; 88 | final String androidPackageName = AppModel().appInfo.androidPackageName; 89 | final String iOsAppId = AppModel().appInfo.iOsAppId; 90 | // Check device OS 91 | if (Platform.isAndroid) { 92 | url = "............................................."; 93 | } else if (Platform.isIOS) { 94 | url = ".........................................."; 95 | } 96 | return url; 97 | } 98 | 99 | /// Get app current version from Cloud Firestore Database, 100 | /// that is the same with Google Play Store / Apple Store app version 101 | Future getAppStoreVersion() async { 102 | final DocumentSnapshot appInfo = 103 | await _firestore.collection(C_APP_INFO).doc('settings').get(); 104 | // Update AppInfo object 105 | AppModel().setAppInfo(appInfo.data()!); 106 | // Check Platform 107 | if (Platform.isAndroid) { 108 | return appInfo.data()?[ANDROID_APP_CURRENT_VERSION] ?? 1; 109 | } else if (Platform.isIOS) { 110 | return appInfo.data()?[IOS_APP_CURRENT_VERSION] ?? 1; 111 | } 112 | return 1; 113 | } 114 | 115 | /// Update app info data in database 116 | Future updateAppInfo(Map data) async { 117 | // Update app data 118 | _firestore.collection(C_APP_INFO).doc('settings').update(data); 119 | } 120 | 121 | /// Share app method 122 | Future shareApp() async { 123 | Share.share(_appStoreUrl); 124 | } 125 | 126 | /// Review app method 127 | Future reviewApp() async { 128 | // Check OS and get correct url 129 | final String url = 130 | Platform.isIOS ? "......................" : _appStoreUrl; 131 | if (await canLaunch(url)) { 132 | await launch(url); 133 | } else { 134 | throw "Could not launch $url"; 135 | } 136 | } 137 | 138 | /// Open app store - Google Play / Apple Store 139 | Future openAppStore() async { 140 | if (await canLaunch(_appStoreUrl)) { 141 | await launch(_appStoreUrl); 142 | } else { 143 | throw "Could not open app store url...."; 144 | } 145 | } 146 | 147 | 148 | 149 | /// Open About us in Browser 150 | Future openAboutUs() async { 151 | if (await canLaunch(".......................................")) { 152 | await launch("................................."); 153 | } 154 | else { 155 | throw "Could not launch url"; 156 | } 157 | } 158 | 159 | 160 | 161 | /// Open watch video in Browser 162 | Future openWatchVideo() async { 163 | if (await canLaunch("................................")) { 164 | await launch("............................"); 165 | } 166 | else { 167 | throw "Could not launch url"; 168 | } 169 | } 170 | 171 | 172 | /// Open earn money in Browser 173 | Future openEarnMoney() async { 174 | if (await canLaunch("..................................")) { 175 | await launch("..........................."); 176 | } 177 | else { 178 | throw "Could not launch url"; 179 | } 180 | } 181 | 182 | 183 | /// Open Privacy Policy Page in Browser 184 | Future openPrivacyPage() async { 185 | if (await canLaunch(AppModel().appInfo.privacyPolicyUrl)) { 186 | await launch(AppModel().appInfo.privacyPolicyUrl); 187 | } else { 188 | throw "Could not launch url"; 189 | } 190 | } 191 | 192 | /// Open Terms of Services in Browser 193 | Future openTermsPage() async { 194 | // Try to launch 195 | if (await canLaunch(AppModel().appInfo.termsOfServicesUrl)) { 196 | await launch(AppModel().appInfo.termsOfServicesUrl); 197 | } else { 198 | throw "Could not launch url"; 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/image_source_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:image_cropper/image_cropper.dart'; 5 | import 'package:image_picker/image_picker.dart'; 6 | import 'package:rishtpak/helpers/app_localizations.dart'; 7 | import 'package:rishtpak/widgets/svg_icon.dart'; 8 | 9 | class ImageSourceSheet extends StatelessWidget { 10 | // Constructor 11 | ImageSourceSheet({required this.onImageSelected}); 12 | 13 | // Callback function to return image file 14 | final Function(File?) onImageSelected; 15 | // ImagePicker instance 16 | final picker = ImagePicker(); 17 | 18 | Future selectedImage(BuildContext context, File? image) async { 19 | // init i18n 20 | final i18n = AppLocalizations.of(context); 21 | 22 | // Check file 23 | if (image != null) { 24 | final croppedImage = await ImageCropper.cropImage( 25 | sourcePath: image.path, 26 | aspectRatioPresets: [CropAspectRatioPreset.square], 27 | maxWidth: 400, 28 | maxHeight: 400, 29 | androidUiSettings: AndroidUiSettings( 30 | toolbarTitle: i18n.translate("edit_crop_image"), 31 | toolbarColor: Theme.of(context).primaryColor, 32 | toolbarWidgetColor: Colors.white, 33 | )); 34 | onImageSelected(croppedImage); 35 | } 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | final i18n = AppLocalizations.of(context); 41 | return BottomSheet( 42 | onClosing: () {}, 43 | builder: ((context) => Column( 44 | mainAxisAlignment: MainAxisAlignment.center, 45 | crossAxisAlignment: CrossAxisAlignment.center, 46 | mainAxisSize: MainAxisSize.min, 47 | children: [ 48 | 49 | SizedBox(height: 10,), 50 | 51 | Container( 52 | padding: const EdgeInsets.all(18), 53 | width: 200, 54 | height: 5, 55 | decoration: BoxDecoration(borderRadius: BorderRadius.circular(300) ,color: Colors.grey,), 56 | ), 57 | 58 | SizedBox(height: 15,), 59 | 60 | 61 | 62 | Text(i18n.translate("media") , style: TextStyle(color: Theme.of(context).primaryColor , fontSize: 24),), 63 | SizedBox(height: 15,), 64 | 65 | 66 | Padding( 67 | padding: const EdgeInsets.all(8.0), 68 | child: Text(i18n.translate("choose_your_image") , style: TextStyle(color: Colors.grey.withOpacity(0.7) , fontSize: 18),), 69 | ), 70 | SizedBox(height: 5,), 71 | 72 | 73 | Padding( 74 | padding: const EdgeInsets.all(16.0), 75 | child: Row( 76 | mainAxisSize: MainAxisSize.max, 77 | mainAxisAlignment: MainAxisAlignment.center, 78 | crossAxisAlignment: CrossAxisAlignment.center, 79 | children: [ 80 | 81 | /// Select image from gallery 82 | ElevatedButton.icon( 83 | icon: Icon(Icons.photo_size_select_actual_outlined, color: Colors.white, size: 28), 84 | label: Text(i18n.translate("gallery"), style: TextStyle(fontSize: 20 , color: Colors.white)), 85 | style: ElevatedButton.styleFrom( 86 | primary: Theme.of(context).primaryColor, 87 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(400)) 88 | ), 89 | onPressed: () async { 90 | 91 | // Get image from device gallery 92 | final pickedFile = await picker.getImage(source: ImageSource.gallery,); 93 | 94 | if (pickedFile == null) return; 95 | selectedImage(context, File(pickedFile.path)); 96 | }, 97 | ), 98 | 99 | SizedBox(width: 25,), 100 | 101 | /// Capture image from camera 102 | OutlinedButton.icon( 103 | // icon: SvgIcon("assets/icons/camera_icon.svg", width: 24, height: 24), 104 | icon: Icon(Icons.camera, color: Theme.of(context).primaryColor, size: 28), 105 | label: Text(i18n.translate("camera"), style: TextStyle(fontSize: 20 , color: Theme.of(context).primaryColor)), 106 | style: OutlinedButton.styleFrom( 107 | backgroundColor: Colors.white, 108 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(400)), 109 | primary: Theme.of(context).primaryColor, 110 | ), 111 | onPressed: () async { 112 | // Capture image from camera 113 | final pickedFile = await picker.getImage( 114 | source: ImageSource.camera, 115 | ); 116 | if (pickedFile == null) return; 117 | selectedImage(context, File(pickedFile.path)); 118 | }, 119 | ), 120 | ], 121 | ), 122 | ), 123 | ], 124 | ))); 125 | } 126 | } 127 | 128 | 129 | 130 | //Column( 131 | // mainAxisSize: MainAxisSize.min, 132 | // children: [ 133 | // /// Select image from gallery 134 | // TextButton.icon( 135 | // icon: Icon(Icons.photo_library, color: Colors.grey, size: 27), 136 | // label: Text(i18n.translate("gallery"), style: TextStyle(fontSize: 16)), 137 | // onPressed: () async { 138 | // // Get image from device gallery 139 | // final pickedFile = await picker.getImage( 140 | // source: ImageSource.gallery, 141 | // ); 142 | // if (pickedFile == null) return; 143 | // selectedImage(context, File(pickedFile.path)); 144 | // }, 145 | // ), 146 | // 147 | // /// Capture image from camera 148 | // TextButton.icon( 149 | // icon: SvgIcon("assets/icons/camera_icon.svg", 150 | // width: 20, height: 20), 151 | // label: Text(i18n.translate("camera"), 152 | // style: TextStyle(fontSize: 16)), 153 | // onPressed: () async { 154 | // // Capture image from camera 155 | // final pickedFile = await picker.getImage( 156 | // source: ImageSource.camera, 157 | // ); 158 | // if (pickedFile == null) return; 159 | // selectedImage(context, File(pickedFile.path)); 160 | // }, 161 | // ), 162 | // ], 163 | // ) 164 | -------------------------------------------------------------------------------- /DostiPak/lib/screens/profile_likes_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:rishtpak/api/likes_api.dart'; 3 | import 'package:rishtpak/api/visits_api.dart'; 4 | import 'package:rishtpak/constants/constants.dart'; 5 | import 'package:rishtpak/datas/user.dart'; 6 | import 'package:rishtpak/dialogs/vip_dialog.dart'; 7 | import 'package:rishtpak/helpers/app_localizations.dart'; 8 | import 'package:rishtpak/models/user_model.dart'; 9 | import 'package:rishtpak/screens/profile_screen.dart'; 10 | import 'package:rishtpak/widgets/build_title.dart'; 11 | import 'package:rishtpak/widgets/loading_card.dart'; 12 | import 'package:rishtpak/widgets/no_data.dart'; 13 | import 'package:rishtpak/widgets/processing.dart'; 14 | import 'package:rishtpak/widgets/profile_card.dart'; 15 | import 'package:rishtpak/widgets/users_grid.dart'; 16 | import 'package:flutter/material.dart'; 17 | 18 | class ProfileLikesScreen extends StatefulWidget { 19 | @override 20 | _ProfileLikesScreenState createState() => _ProfileLikesScreenState(); 21 | } 22 | 23 | class _ProfileLikesScreenState extends State { 24 | // Variables 25 | final ScrollController _gridViewController = new ScrollController(); 26 | final LikesApi _likesApi = LikesApi(); 27 | final VisitsApi _visitsApi = VisitsApi(); 28 | late AppLocalizations _i18n; 29 | List? _likedMeUsers; 30 | late DocumentSnapshot _userLastDoc; 31 | bool _loadMore = true; 32 | 33 | /// Load more users 34 | void _loadMoreUsersListener() async { 35 | _gridViewController.addListener(() { 36 | if (_gridViewController.position.pixels == 37 | _gridViewController.position.maxScrollExtent) { 38 | /// Load more users 39 | if (_loadMore) { 40 | _likesApi 41 | .getLikedMeUsers(loadMore: true, userLastDoc: _userLastDoc) 42 | .then((users) { 43 | /// Update users list 44 | if (users.isNotEmpty) { 45 | _updateUsersList(users); 46 | } else { 47 | setState(() => _loadMore = false); 48 | } 49 | print('load more users: ${users.length}'); 50 | }); 51 | } else { 52 | print('No more users'); 53 | } 54 | } 55 | }); 56 | } 57 | 58 | /// Update list 59 | void _updateUsersList(List users) { 60 | if (mounted) { 61 | setState(() { 62 | _likedMeUsers!.addAll(users); 63 | if (users.isNotEmpty) { 64 | _userLastDoc = users.last; 65 | } 66 | }); 67 | } 68 | } 69 | 70 | @override 71 | void initState() { 72 | super.initState(); 73 | _likesApi.getLikedMeUsers().then((users) { 74 | // Check result 75 | if (users.isNotEmpty) { 76 | if (mounted) { 77 | setState(() { 78 | _likedMeUsers = users; 79 | _userLastDoc = users.last; 80 | }); 81 | } 82 | } else { 83 | setState(() => _likedMeUsers = []); 84 | } 85 | }); 86 | 87 | /// Listener 88 | _loadMoreUsersListener(); 89 | } 90 | 91 | @override 92 | void dispose() { 93 | _gridViewController.dispose(); 94 | super.dispose(); 95 | } 96 | 97 | @override 98 | Widget build(BuildContext context) { 99 | /// Initialization 100 | _i18n = AppLocalizations.of(context); 101 | 102 | return Scaffold( 103 | appBar: AppBar( 104 | title: Text(_i18n.translate("likes")), 105 | ), 106 | body: Column( 107 | children: [ 108 | /// Header Title 109 | BuildTitle( 110 | svgIconName: "heart_icon", 111 | title: _i18n.translate("users_who_liked_you"), 112 | ), 113 | 114 | /// Matches 115 | Expanded(child: _showProfiles()) 116 | ], 117 | )); 118 | } 119 | 120 | /// Show profiles 121 | Widget _showProfiles() { 122 | if (_likedMeUsers == null) { 123 | return Processing(text: _i18n.translate("loading")); 124 | } 125 | else if (_likedMeUsers!.isEmpty) { 126 | // No data 127 | return NoData(svgName: 'heart_icon', text: _i18n.translate("no_like")); 128 | } 129 | else { 130 | /// Show users 131 | return UsersGrid( 132 | gridViewController: _gridViewController, 133 | itemCount: _likedMeUsers!.length + 1, 134 | 135 | /// Workaround for loading more 136 | itemBuilder: (context, index) { 137 | /// Validate fake index 138 | if (index < _likedMeUsers!.length) { 139 | /// Get user id 140 | final userId = _likedMeUsers![index][LIKED_BY_USER_ID]; 141 | 142 | /// Load profile 143 | return FutureBuilder( 144 | future: UserModel().getUser(userId), 145 | builder: (context, snapshot) { 146 | /// Check result 147 | if (!snapshot.hasData) return LoadingCard(); 148 | 149 | /// Get user object 150 | final User user = User.fromDocument(snapshot.data!.data()!); 151 | 152 | /// Show user card 153 | return GestureDetector( 154 | child: ProfileCard(user: user, page: 'require_vip' , showLiking: false, isLiked: false, isDisLiked: false,), 155 | onTap: () { 156 | /// Check vip account 157 | if (UserModel().user.userIsVerified || UserModel().user.userWallet > 0.0) { 158 | /// Go to profile screen - using showDialog to 159 | /// prevents reloading getUser FutureBuilder 160 | showDialog(context: context, 161 | barrierDismissible: false, 162 | builder: (context) { 163 | return ProfileScreen( 164 | user: user, 165 | hideDislikeButton: true 166 | ); 167 | } 168 | ); 169 | /// Increment user visits an push notification 170 | _visitsApi.visitUserProfile( 171 | visitedUserId: user.userId, 172 | userDeviceToken: user.userDeviceToken, 173 | nMessage: "${UserModel().user.userFullname.split(' ')[0]}, " 174 | "${_i18n.translate("visited_your_profile_click_and_see")}", 175 | ); 176 | } 177 | else { 178 | /// Show VIP dialog 179 | showDialog(context: context, 180 | builder: (context) => VipDialog()); 181 | } 182 | }, 183 | ); 184 | }); 185 | } 186 | else { 187 | return Container(); 188 | } 189 | }, 190 | ); 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /DostiPak/lib/screens/disliked_profile_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:rishtpak/api/dislikes_api.dart'; 3 | import 'package:rishtpak/api/visits_api.dart'; 4 | import 'package:rishtpak/constants/constants.dart'; 5 | import 'package:rishtpak/datas/user.dart'; 6 | import 'package:rishtpak/dialogs/vip_dialog.dart'; 7 | import 'package:rishtpak/helpers/app_localizations.dart'; 8 | import 'package:rishtpak/models/user_model.dart'; 9 | import 'package:rishtpak/screens/profile_screen.dart'; 10 | import 'package:rishtpak/widgets/build_title.dart'; 11 | import 'package:rishtpak/widgets/loading_card.dart'; 12 | import 'package:rishtpak/widgets/no_data.dart'; 13 | import 'package:rishtpak/widgets/processing.dart'; 14 | import 'package:rishtpak/widgets/profile_card.dart'; 15 | import 'package:rishtpak/widgets/users_grid.dart'; 16 | import 'package:flutter/material.dart'; 17 | 18 | class DislikedProfilesScreen extends StatefulWidget { 19 | @override 20 | _DislikedProfilesScreenState createState() => _DislikedProfilesScreenState(); 21 | } 22 | 23 | class _DislikedProfilesScreenState extends State { 24 | // Variables 25 | final ScrollController _gridViewController = new ScrollController(); 26 | final DislikesApi _dislikesApi = DislikesApi(); 27 | final VisitsApi _visitsApi = VisitsApi(); 28 | late AppLocalizations _i18n; 29 | List? _dislikedUsers; 30 | late DocumentSnapshot _userLastDoc; 31 | bool _loadMore = true; 32 | 33 | /// Load more users 34 | void _loadMoreUsersListener() async { 35 | _gridViewController.addListener(() { 36 | if (_gridViewController.position.pixels == 37 | _gridViewController.position.maxScrollExtent) { 38 | /// Load more users 39 | if (_loadMore) { 40 | _dislikesApi 41 | .getDislikedUsers( 42 | withLimit: true, loadMore: true, userLastDoc: _userLastDoc) 43 | .then((users) { 44 | /// Update user list 45 | if (users.isNotEmpty) { 46 | _updateUserList(users); 47 | } else { 48 | setState(() => _loadMore = false); 49 | } 50 | print('load more users: ${users.length}'); 51 | }); 52 | } else { 53 | print('No more users'); 54 | } 55 | } 56 | }); 57 | } 58 | 59 | /// Update list 60 | void _updateUserList(List users) { 61 | if (mounted) { 62 | setState(() { 63 | _dislikedUsers!.addAll(users); 64 | if (users.isNotEmpty) { 65 | _userLastDoc = users.last; 66 | } 67 | }); 68 | } 69 | } 70 | 71 | @override 72 | void initState() { 73 | super.initState(); 74 | _dislikesApi.getDislikedUsers(withLimit: true).then((users) { 75 | // Check result 76 | if (users.isNotEmpty) { 77 | if (mounted) { 78 | setState(() { 79 | _dislikedUsers = users; 80 | _userLastDoc = users.last; 81 | }); 82 | } 83 | } else { 84 | setState(() => _dislikedUsers = []); 85 | } 86 | }); 87 | 88 | /// Listener 89 | _loadMoreUsersListener(); 90 | } 91 | 92 | @override 93 | void dispose() { 94 | _gridViewController.dispose(); 95 | super.dispose(); 96 | } 97 | 98 | @override 99 | Widget build(BuildContext context) { 100 | /// Initialization 101 | _i18n = AppLocalizations.of(context); 102 | 103 | return Scaffold( 104 | appBar: AppBar( 105 | title: Text(_i18n.translate("disliked_profiles")), 106 | ), 107 | body: Column( 108 | children: [ 109 | /// Header Title 110 | BuildTitle( 111 | svgIconName: "close_icon", 112 | title: _i18n.translate("profiles_you_rejected"), 113 | ), 114 | 115 | /// Matches 116 | Expanded(child: _showProfiles()) 117 | ], 118 | )); 119 | } 120 | 121 | /// Show profiles 122 | Widget _showProfiles() { 123 | if (_dislikedUsers == null) { 124 | return Processing(text: _i18n.translate("loading")); 125 | } else if (_dislikedUsers!.isEmpty) { 126 | // No data 127 | return NoData(svgName: 'close_icon', text: _i18n.translate("no_dislike")); 128 | } else { 129 | /// Show users 130 | return UsersGrid( 131 | gridViewController: _gridViewController, 132 | itemCount: _dislikedUsers!.length + 1, 133 | 134 | /// Workaround for loading more 135 | itemBuilder: (context, index) { 136 | /// Validate fake index 137 | if (index < _dislikedUsers!.length) { 138 | /// Get user id 139 | final userId = _dislikedUsers![index][DISLIKED_USER_ID]; 140 | 141 | /// Load profile 142 | return FutureBuilder( 143 | future: UserModel().getUser(userId), 144 | builder: (context, snapshot) { 145 | /// Chech result 146 | if (!snapshot.hasData) return LoadingCard(); 147 | 148 | /// Get user object 149 | final User user = User.fromDocument(snapshot.data!.data()!); 150 | 151 | /// Show user card 152 | return GestureDetector( 153 | child: ProfileCard(user: user, page: 'require_vip' , showLiking: false, isLiked: false, isDisLiked: false,), 154 | onTap: () { 155 | /// Check vip account 156 | if (UserModel().user.userIsVerified || UserModel().user.userWallet > 0.0) { 157 | /// Go to profile screen - using showDialog to 158 | /// prevents reloading getUser FutureBuilder 159 | showDialog(context: context, 160 | barrierDismissible: false, 161 | builder: (context) { 162 | return ProfileScreen( 163 | user: user, 164 | hideDislikeButton: true, 165 | fromDislikesScreen: true 166 | ); 167 | } 168 | ); 169 | /// Increment user visits an push notification 170 | _visitsApi.visitUserProfile( 171 | visitedUserId: user.userId, 172 | userDeviceToken: user.userDeviceToken, 173 | nMessage: "${UserModel().user.userFullname.split(' ')[0]}, " 174 | "${_i18n.translate("visited_your_profile_click_and_see")}", 175 | ); 176 | } else { 177 | /// Show VIP dialog 178 | showDialog(context: context, 179 | builder: (context) => VipDialog()); 180 | } 181 | }, 182 | ); 183 | }); 184 | } else { 185 | return Container(); 186 | } 187 | }, 188 | ); 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /DostiPak/lib/tabs/matches_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:google_mobile_ads/google_mobile_ads.dart'; 4 | import 'package:rishtpak/api/matches_api.dart'; 5 | import 'package:rishtpak/constants/constants.dart'; 6 | import 'package:rishtpak/datas/user.dart'; 7 | import 'package:rishtpak/helpers/app_localizations.dart'; 8 | import 'package:rishtpak/models/user_model.dart'; 9 | import 'package:rishtpak/screens/chat_screen.dart'; 10 | import 'package:rishtpak/widgets/build_title.dart'; 11 | import 'package:rishtpak/widgets/loading_card.dart'; 12 | import 'package:rishtpak/widgets/no_data.dart'; 13 | import 'package:rishtpak/widgets/processing.dart'; 14 | import 'package:rishtpak/widgets/profile_card.dart'; 15 | import 'package:rishtpak/widgets/users_grid.dart'; 16 | 17 | class MatchesTab extends StatefulWidget { 18 | @override 19 | _MatchesTabState createState() => _MatchesTabState(); 20 | } 21 | 22 | class _MatchesTabState extends State { 23 | /// Variables 24 | final MatchesApi _matchesApi = MatchesApi(); 25 | List? _matches; 26 | late AppLocalizations _i18n; 27 | 28 | late BannerAd _bannerAd; 29 | bool isBannerVisible = false; 30 | 31 | 32 | 33 | @override 34 | void initState() { 35 | super.initState(); 36 | 37 | /// Get user matches 38 | _matchesApi.getMatches().then((matches) { 39 | if (mounted) setState(() => _matches = matches); 40 | }); 41 | 42 | //Google Banner Ads 43 | _bannerAd = BannerAd( 44 | adUnitId: '.................................', //BannerAd.testAdUnitId 45 | size: AdSize.banner, //AdSize(width: 2000, height: 65) 46 | request: AdRequest(), 47 | listener: AdListener( 48 | onAdLoaded: (Ad ad) async{ 49 | 50 | print('$BannerAd loaded.'); 51 | await Future.delayed(Duration(milliseconds: 2000)); 52 | isBannerVisible = true; 53 | setState(() {}); 54 | }, 55 | onAdFailedToLoad: (Ad ad, LoadAdError error) { 56 | print('$BannerAd failedToLoad: $error'); 57 | ad.dispose(); 58 | }, 59 | onAdOpened: (Ad ad) => print('$BannerAd onAdOpened.'), 60 | onAdClosed: (Ad ad) => print('$BannerAd onAdClosed.'), 61 | ), 62 | ); 63 | 64 | _bannerAd.load(); 65 | //Google Banner Ads 66 | 67 | } 68 | 69 | @override 70 | void dispose() { 71 | // TODO: implement dispose 72 | super.dispose(); 73 | 74 | _bannerAd.dispose(); 75 | } 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | /// Initialization 80 | _i18n = AppLocalizations.of(context); 81 | 82 | //Banner Ads widget 83 | final AdWidget adWidget = AdWidget(ad: _bannerAd); 84 | 85 | return Column( 86 | children: [ 87 | 88 | /// Banner Ad 89 | Visibility( 90 | visible: isBannerVisible, 91 | child: Align( 92 | alignment: FractionalOffset.topCenter, 93 | child: Padding( 94 | padding: EdgeInsets.all(12), 95 | child: Container( 96 | alignment: Alignment.center, 97 | child: adWidget, 98 | width: _bannerAd.size.width.toDouble(), 99 | height: _bannerAd.size.height.toDouble(), 100 | ) 101 | ), 102 | ), 103 | ), 104 | 105 | 106 | /// Header 107 | BuildTitle( 108 | svgIconName: 'heart_icon', 109 | title: _i18n.translate("connections"), 110 | ), 111 | 112 | /// Show matches 113 | Expanded(child: _showMatches()), 114 | ], 115 | ); 116 | } 117 | 118 | 119 | /// Handle matches result 120 | Widget _showMatches() { 121 | /// Check result 122 | if (_matches == null) { 123 | return Processing(text: _i18n.translate("loading")); 124 | } 125 | else if (_matches!.isEmpty) { 126 | /// No match 127 | return NoData( 128 | svgName: 'heart_icon', text: _i18n.translate("no_connections")); 129 | } 130 | else { 131 | /// Load matches 132 | return UsersGrid( 133 | itemCount: _matches!.length, 134 | itemBuilder: (context, index) { 135 | /// Get match doc 136 | final DocumentSnapshot match = _matches![index]; 137 | 138 | /// Load profile 139 | return FutureBuilder( 140 | future: UserModel().getUser(match.id), 141 | builder: (context, snapshot) { 142 | /// Check result 143 | if (!snapshot.hasData) return LoadingCard(); 144 | 145 | /// Get user object 146 | final User user = User.fromDocument(snapshot.data!.data()!); 147 | 148 | /// Show user card 149 | return GestureDetector( 150 | child: ProfileCard(user: user, page: 'matches' , showLiking: false, isLiked: false, isDisLiked: false,), 151 | onTap: () { 152 | 153 | //NEW (here add user friend to typing list) 154 | _addUserToTypingList(context , user); 155 | 156 | 157 | // /// Go to chat screen 158 | // Navigator.of(context).push( 159 | // MaterialPageRoute( 160 | // builder: (context) => ChatScreen(user: user))); 161 | }); 162 | }); 163 | }, 164 | ); 165 | } 166 | } 167 | 168 | 169 | void _addUserToTypingList(BuildContext context , User user) async { 170 | 171 | 172 | FirebaseFirestore.instance 173 | .collection(C_USERS) 174 | .doc(UserModel().user.userId) 175 | .get().then((snapshot) async { 176 | 177 | Map currentUserData = snapshot.data()!; 178 | Map currentUserTypings = snapshot.data()![USER_TYPING]; 179 | 180 | 181 | DocumentSnapshot otherUser = await FirebaseFirestore.instance.collection(C_USERS).doc(user.userId).get(); 182 | 183 | Map otherUserData = otherUser.data()!; 184 | Map otherUserTypings = otherUser.data()![USER_TYPING]; 185 | 186 | 187 | 188 | if( !(currentUserTypings.containsKey(user.userId)) || !(otherUserTypings.containsKey(UserModel().user.userId)) ){ 189 | 190 | ////////////////////Add new typing data in current user 191 | 192 | currentUserTypings[user.userId] = false; 193 | // print('size of typing list ${currentUserTypings.length}'); 194 | 195 | currentUserData[USER_TYPING] = currentUserTypings; 196 | // print('size of data[USER_TYPING] list '); 197 | 198 | FirebaseFirestore.instance.collection(C_USERS).doc(UserModel().user.userId).update(currentUserData).then((value) => print('current user is updated successfully')); 199 | 200 | 201 | 202 | ////////////////////Add new typing data in other user 203 | 204 | otherUserTypings[UserModel().user.userId] = false; 205 | // print('size of typing list ${currentUserTypings.length}'); 206 | 207 | otherUserData[USER_TYPING] = otherUserTypings; 208 | // print('size of data[USER_TYPING] list '); 209 | 210 | FirebaseFirestore.instance.collection(C_USERS).doc(user.userId).update(otherUserData).then((value) => print('other user is updated successfully')); 211 | 212 | 213 | /// Go to chat screen 214 | Navigator.of(context).push(MaterialPageRoute( 215 | builder: (context) => 216 | ChatScreen(user: user))); 217 | 218 | } 219 | else { 220 | /// Go to chat screen 221 | print('pass 2'); 222 | Navigator.of(context).push(MaterialPageRoute( 223 | builder: (context) => ChatScreen(user: user))); 224 | } 225 | 226 | }); 227 | 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/profile_basic_info_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rishtpak/helpers/app_localizations.dart'; 3 | import 'package:rishtpak/models/user_model.dart'; 4 | import 'package:rishtpak/screens/edit_profile_screen.dart'; 5 | import 'package:rishtpak/screens/profile_screen.dart'; 6 | import 'package:rishtpak/screens/settings_screen.dart'; 7 | import 'package:rishtpak/widgets/cicle_button.dart'; 8 | import 'package:rishtpak/widgets/default_card_border.dart'; 9 | import 'package:rishtpak/widgets/svg_icon.dart'; 10 | 11 | class ProfileBasicInfoCard extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) { 14 | /// Initialization 15 | final i18n = AppLocalizations.of(context); 16 | // 17 | // Get User Birthday 18 | final DateTime userBirthday = DateTime( 19 | UserModel().user.userBirthYear, 20 | UserModel().user.userBirthMonth, 21 | UserModel().user.userBirthDay); 22 | // Get User Current Age 23 | final int userAge = UserModel().calculateUserAge(userBirthday); 24 | 25 | return SingleChildScrollView( 26 | scrollDirection: Axis.horizontal, 27 | physics: ScrollPhysics(), 28 | child: Card( 29 | color: Theme.of(context).primaryColor, 30 | elevation: 4.0, 31 | shape: defaultCardBorder(), 32 | child: Container( 33 | padding: const EdgeInsets.all(10.0), 34 | width: MediaQuery.of(context).size.width - 20, 35 | child: Column( 36 | crossAxisAlignment: CrossAxisAlignment.start, 37 | children: [ 38 | /// Profile image 39 | Row( 40 | mainAxisSize: MainAxisSize.max, 41 | children: [ 42 | Container( 43 | padding: const EdgeInsets.all(3.0), 44 | decoration: BoxDecoration( 45 | color: Colors.white, shape: BoxShape.circle), 46 | child: CircleAvatar( 47 | backgroundColor: Theme.of(context).primaryColor, 48 | radius: 40, 49 | backgroundImage: 50 | NetworkImage(UserModel().user.userProfilePhoto), 51 | ), 52 | ), 53 | 54 | SizedBox(width: 10), 55 | 56 | /// Profile details 57 | Flexible( 58 | child: Column( 59 | crossAxisAlignment: CrossAxisAlignment.start, 60 | mainAxisSize: MainAxisSize.min, 61 | children: [ 62 | 63 | Flexible( 64 | child: Text( 65 | "${UserModel().user.userFullname.split(' ')[0]}, " 66 | "${userAge.toString()}", 67 | overflow: TextOverflow.ellipsis, 68 | maxLines: 2, 69 | style: TextStyle( 70 | fontSize: 20, 71 | fontWeight: FontWeight.bold, 72 | color: Colors.white), 73 | ), 74 | ), 75 | SizedBox(height: 5), 76 | 77 | 78 | /// Location 79 | Row( 80 | children: [ 81 | SvgIcon("assets/icons/location_point_icon.svg", 82 | color: Colors.white), 83 | SizedBox(width: 5), 84 | Column( 85 | crossAxisAlignment: CrossAxisAlignment.start, 86 | mainAxisSize: MainAxisSize.min, 87 | children: [ 88 | // City 89 | Flexible( 90 | child: Text("${UserModel().user.userLocality},", 91 | style: TextStyle(color: Colors.white)), 92 | ), 93 | // Country 94 | Flexible( 95 | child: Text("${UserModel().user.userCountry}", 96 | style: TextStyle(color: Colors.white)), 97 | ), 98 | ], 99 | ) 100 | ], 101 | ) 102 | ], 103 | ), 104 | ), 105 | ], 106 | ), 107 | SizedBox(height: 10), 108 | 109 | /// Buttons 110 | Row( 111 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 112 | children: [ 113 | SizedBox( 114 | height: 30, 115 | child: OutlinedButton.icon( 116 | icon: Icon(Icons.remove_red_eye, color: Colors.white), 117 | label: Text(i18n.translate("view"), 118 | style: TextStyle(color: Colors.white)), 119 | style: ButtonStyle( 120 | side: MaterialStateProperty.all( 121 | BorderSide(color: Colors.white) 122 | ), 123 | shape: MaterialStateProperty.all( 124 | RoundedRectangleBorder( 125 | borderRadius: BorderRadius.circular(28), 126 | ) 127 | ) 128 | ), 129 | onPressed: () { 130 | /// Go to profile screen 131 | Navigator.of(context).push(MaterialPageRoute( 132 | builder: (context) => ProfileScreen( 133 | user: UserModel().user, showButtons: false))); 134 | }), 135 | ), 136 | cicleButton( 137 | bgColor: Theme.of(context).accentColor, 138 | padding: 13, 139 | icon: SvgIcon("assets/icons/settings_icon.svg", 140 | color: Colors.white, width: 30, height: 30), 141 | onTap: () { 142 | /// Go to profile settings 143 | Navigator.of(context).push(MaterialPageRoute( 144 | builder: (context) => SettingsScreen())); 145 | }, 146 | ), 147 | SizedBox( 148 | height: 35, 149 | child: TextButton.icon( 150 | icon: Icon(Icons.edit, color: Theme.of(context).primaryColor), 151 | style: ButtonStyle( 152 | backgroundColor: MaterialStateProperty.all( 153 | Colors.white), 154 | shape: MaterialStateProperty.all( 155 | RoundedRectangleBorder( 156 | borderRadius: BorderRadius.circular(28), 157 | ) 158 | ) 159 | ), 160 | label: Text(i18n.translate("edit"), 161 | style: TextStyle(color: Theme.of(context).primaryColor)), 162 | onPressed: () { 163 | /// Go to edit profile screen 164 | Navigator.of(context).push(MaterialPageRoute( 165 | builder: (context) => EditProfileScreen())); 166 | }), 167 | ), 168 | ], 169 | ), 170 | ], 171 | ), 172 | ), 173 | ), 174 | ); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /DostiPak/lib/widgets/git_source_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:math'; 3 | 4 | import 'package:firebase_storage/firebase_storage.dart'; 5 | import 'package:flutter/cupertino.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:image_picker/image_picker.dart'; 8 | import 'package:path_provider/path_provider.dart'; 9 | import 'package:permission_handler/permission_handler.dart'; 10 | import 'package:rishtpak/dialogs/progress_dialog.dart'; 11 | import 'package:rishtpak/helpers/app_localizations.dart'; 12 | import 'package:flutter/services.dart' show rootBundle; 13 | import 'package:path_provider/path_provider.dart' as path_provider; 14 | 15 | 16 | 17 | class GifSourceSheet extends StatelessWidget { 18 | // Constructor 19 | GifSourceSheet({required this.onGifSelected}); 20 | 21 | // Callback function to return image file 22 | final Function(String fireStorageURL) onGifSelected; 23 | // ImagePicker instance 24 | final picker = ImagePicker(); 25 | 26 | 27 | late ProgressDialog _pr; 28 | late AppLocalizations _i18n; 29 | 30 | 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | 35 | _i18n = AppLocalizations.of(context); 36 | _pr = ProgressDialog(context); 37 | 38 | 39 | return BottomSheet( 40 | onClosing: () {}, 41 | builder: ((context) => Container( 42 | width: MediaQuery.of(context).size.width, 43 | height: MediaQuery.of(context).size.height, 44 | color: Colors.white, 45 | child: Column( 46 | mainAxisAlignment: MainAxisAlignment.center, 47 | crossAxisAlignment: CrossAxisAlignment.center, 48 | mainAxisSize: MainAxisSize.min, 49 | children: [ 50 | 51 | SizedBox(height: 10,), 52 | 53 | Container( 54 | padding: const EdgeInsets.all(18), 55 | width: 200, 56 | height: 5, 57 | decoration: BoxDecoration(borderRadius: BorderRadius.circular(300) ,color: Colors.grey,), 58 | ), 59 | 60 | SizedBox(height: 10,), 61 | 62 | /// Select gif 63 | Expanded( 64 | child: GridView.count( 65 | mainAxisSpacing: 3, 66 | crossAxisCount: 4, 67 | childAspectRatio: 200/200, 68 | crossAxisSpacing: 3, 69 | children: [ 70 | 71 | //Git 1 72 | Container( 73 | width: 200, 74 | height: 200, 75 | child: GestureDetector( 76 | child: Image.asset( 77 | 'assets/images/gif1.gif', 78 | width: 200, 79 | height: 200, 80 | fit: BoxFit.cover, 81 | ), 82 | onTap: () async{ 83 | 84 | //Upload to storage 85 | Navigator.of(context).pop(); 86 | uploadToFirebase('assets/images/gif1.gif'); 87 | 88 | }, 89 | ), 90 | ), 91 | 92 | //Git 2 93 | Container( 94 | width: 200, 95 | height: 200, 96 | child: GestureDetector( 97 | child: Image.asset( 98 | 'assets/images/gif2.gif', 99 | width: 200, 100 | height: 200, 101 | fit: BoxFit.cover, 102 | ), 103 | onTap: () async{ 104 | 105 | //Upload to storage 106 | Navigator.of(context).pop(); 107 | uploadToFirebase('assets/images/gif2.gif'); 108 | 109 | }, 110 | ), 111 | ), 112 | 113 | //Git 3 114 | Container( 115 | width: 200, 116 | height: 200, 117 | child: GestureDetector( 118 | child: Image.asset( 119 | 'assets/images/gif3.gif', 120 | width: 200, 121 | height: 200, 122 | fit: BoxFit.cover, 123 | ), 124 | onTap: () async{ 125 | 126 | //Upload to storage 127 | Navigator.of(context).pop(); 128 | uploadToFirebase('assets/images/gif3.gif'); 129 | 130 | }, 131 | ), 132 | ), 133 | 134 | //Git 4 135 | Container( 136 | width: 200, 137 | height: 200, 138 | child: GestureDetector( 139 | child: Image.asset( 140 | 'assets/images/gif4.gif', 141 | width: 200, 142 | height: 200, 143 | fit: BoxFit.cover, 144 | ), 145 | onTap: () async{ 146 | 147 | //Upload to storage 148 | Navigator.of(context).pop(); 149 | uploadToFirebase('assets/images/gif4.gif'); 150 | 151 | }, 152 | ), 153 | ), 154 | 155 | //Git 5 156 | Container( 157 | width: 200, 158 | height: 200, 159 | child: GestureDetector( 160 | child: Image.asset( 161 | 'assets/images/gif5.gif', 162 | width: 200, 163 | height: 200, 164 | fit: BoxFit.cover, 165 | ), 166 | onTap: () async{ 167 | 168 | 169 | 170 | //Upload to storage 171 | Navigator.of(context).pop(); 172 | uploadToFirebase('assets/images/gif5.gif'); 173 | 174 | }, 175 | ), 176 | ), 177 | ], 178 | ), 179 | ) 180 | 181 | 182 | ], 183 | ), 184 | ))); 185 | } 186 | 187 | 188 | 189 | void uploadToFirebase(String path) async { 190 | 191 | final byteData = await rootBundle.load(path); 192 | print('butter size : ${byteData.lengthInBytes}'); 193 | 194 | 195 | var status = await Permission.storage.status; 196 | if (!status.isGranted) { 197 | await Permission.storage.request(); 198 | } 199 | else { 200 | 201 | Directory tempDir = await path_provider.getTemporaryDirectory(); 202 | final file = File('${tempDir.path}/${Random().nextInt(10000)}.gif'); 203 | await file.writeAsBytes(byteData.buffer.asUint8List( 204 | byteData.offsetInBytes, byteData.lengthInBytes)); 205 | 206 | 207 | // Show processing dialog 208 | _pr.show(_i18n.translate("sending")); 209 | 210 | // Upload to firebase storage 211 | final Reference firebaseStorageRef = FirebaseStorage.instance 212 | .ref() 213 | .child('uploads/messages/gif${DateTime 214 | .now() 215 | .millisecondsSinceEpoch 216 | .toString()}}.gif'); 217 | 218 | UploadTask task = firebaseStorageRef.putFile(file); 219 | task.then((value) async { 220 | 221 | print('##############done#########'); 222 | var gifURL = await value.ref.getDownloadURL(); 223 | String urlFile = gifURL.toString(); 224 | 225 | 226 | // save message in firebase firestore 227 | onGifSelected(urlFile); 228 | 229 | // Hide processing dialog 230 | _pr.hide(); 231 | }); 232 | 233 | 234 | // getApplicationSupportDirectory().then((pathDir) async{ 235 | // 236 | // 237 | // }); 238 | } 239 | 240 | } 241 | } --------------------------------------------------------------------------------