diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e69de29..3507709 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -0,0 +1,333 @@ +# `Contributing Guidelines` + +This documentation contains a set of guidelines to help you during the contribution process. + +We are happy to welcome all the contributions from anyone willing to improve/add new scripts to this project. Thank you for helping out and remember, **no contribution is too small.** + +[`Code of Conduct`](CODE_OF_CONDUCT.md) +--------------- + +<details> + +```css +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +nkr.nikhil.nkr@gmail.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. + +``` + +</details> + +---------- + +### Before contributing please ensure your **pull request** adheres to the following guidelines + +## STEP-0 : Key Points to remember + +- Look at the [previous](https://github.com/nixrajput/social-media-app-flutter) work and get some idea from them. +- Always maintain project folder architecture. +- Don't delete/remove any existing file or folder. +- Please don't add any License under your work. This repo already under `GPL-3.0 License`. + +## STEP-1 : **Flow** + +### `Tree of Index` + +```js +. +├── src +│ ├── config +│ │ ├── config.env +│ │ +│ ├── constants +│ │ ├── responseMessages.js +| | +│ ├── helpers +│ │ ├── catchAsyncError.js +│ │ ├── errorHandler.js +│ │ +│ ├── middlewares +│ │ ├── auth.js +│ │ ├── error.js +│ │ ├── multer.js +| | +│ ├── models +| | ├── index.js +│ │ ├── comment +│ │ │ ├── comment.js +│ │ │ +│ │ ├── device-info +│ │ │ ├── deviceInfo.js +│ │ │ +│ │ ├── post +│ │ │ ├── post.js +│ │ │ +│ │ ├── user +│ │ │ ├── user.js +│ │ │ +│ │ ├── notification +│ │ │ ├── notification.js +│ │ │ +│ │ ├── otp +│ │ │ ├── otp.js +│ │ │ +│ │ ├── hashtag +│ │ │ ├── hashtag.js +│ │ │ +│ │ ├── report +│ │ │ ├── report.js +│ │ +│ ├── modules +│ │ ├── auth +│ │ │ ├── index.js +│ │ │ ├── controllers +│ │ │ │ ├── index.js +│ │ │ │ ├── forgot-password +│ │ │ │ │ ├── forgotPassword.js +│ │ │ │ │ +│ │ │ │ ├── login +│ │ │ │ │ ├── login.js +│ │ │ │ │ +│ │ │ │ ├── logout +│ │ │ │ │ ├── logout.js +│ │ │ │ │ +│ │ │ │ ├── register +│ │ │ │ │ ├── register.js +│ │ │ │ │ +│ │ │ │ ├── reset-password +│ │ │ │ │ ├── resetPassword.js +│ │ │ │ │ +│ │ │ │ ├── verify-account +│ │ │ │ │ ├── verifyAccount.js +│ │ │ │ │ ├── verifyAccountOtp.js +│ │ │ │ │ +│ │ │ │ +│ │ │ ├── routes +│ │ │ │ ├── index.js +│ │ │ +│ │ │ +│ │ ├── admin +│ │ │ ├── index.js +│ │ │ ├── controllers +│ │ │ │ ├── index.js +│ │ │ │ ├── user +│ │ │ │ │ ├── deleteUser.js +│ │ │ │ │ ├── getAllUsers.js +│ │ │ │ │ ├── getUserDetails.js +│ │ │ │ │ ├── updateUser.js +│ │ │ │ │ ├── updateUserRole.js +│ │ │ │ │ ├── searchUser.js +│ │ │ │ │ ├── updateAccountStatus.js +│ │ │ │ │ ├── updateVerificationStatus.js +│ │ │ │ │ +│ │ │ │ ├── post +│ │ │ │ │ ├── deletePost.js +│ │ │ │ │ ├── getAllPosts.js +│ │ │ │ │ ├── getPostDetails.js +│ │ │ │ │ ├── updatePost.js +│ │ │ │ │ ├── updatePostStatus.js +│ │ │ │ │ ├── searchPost.js +│ │ │ │ +│ │ │ ├── routes +│ │ │ │ ├── index.js +│ │ │ +│ │ ├── user +| +├── .gitignore +| +├── CODE_OF_CONDUCT.md +| +├── CONTRIBUTING.md +| +├── LICENSE.md +| +├── package.json +| +├── package-lock.yaml +| +└── README.md +``` + +## STEP-2 : `Contributing` + +We'd love your contributions! Kindly follow the steps below to get started: + +0. Star <a href="https://github.com/nixrajput/social-media-app-flutter" title="this">this</a> repository. + +1. Fork <a href="https://github.com/nixrajput/social-media-app-flutter" title="this">this</a> repository. + +2. Clone the forked repository. + +```css +git clone https://github.com/<your-github-username>/social-media-app-flutter +``` + +3. Create a new branch. + +```css +git checkout -b <your_branch_name> +``` + +4. Make changes. + +5. Stage your changes and commit + +```css +git add -A + +git commit -m "<your_commit_message>" +``` + +6. Push your local commits to the remote repo. + +```css +git push -u origin <your_branch_name> +``` + +7. Create a <a href="https://github.com/nixrajput/social-media-app-flutter/pulls" title="Pull Request">Pull-Request</a>. + +8. Congratulations! 🎉 Sit and relax, you've made your contribution to <a href="https://github.com/nixrajput/social-media-app-flutter" title="Rippl! - A Social Media App">Rippl! - A Social Media App</a>. ✌️ ❤️ 💥 + +## **Note** + +- New categories, or improvements to the existing categorisation, are always welcome. +- If you have any idea or suggestions then check this [Discussion Tab](https://github.com/nixrajput/social-media-app-flutter/discussions). and put your [idea](https://github.com/nixrajput/social-media-app-flutter/discussions/categories/ideas) or [suggestions](https://github.com/nixrajput/social-media-app-flutter/discussions/categories/ideas)🏆 + +### [`Welcome to Discussions!`](https://github.com/nixrajput/social-media-app-flutter/discussions) + +[Discussion Tab](https://github.com/nixrajput/social-media-app-flutter/discussions) + +## Need more help?🤔 + +You can refer to the following articles on basics of Git and Github and also contact the Project [Admin](https://github.com/nixrajput), in case you are stuck: + +- [Forking a Repo](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) +- [Cloning a Repo](https://help.github.com/en/desktop/contributing-to-projects/creating-an-issue-or-pull-request) +- [How to create a Pull Request](https://opensource.com/article/19/7/create-pull-request-github) +- [Getting started with Git and GitHub](https://towardsdatascience.com/getting-started-with-git-and-github-6fcd0f2d4ac6) +- [Learn GitHub from Scratch](https://www.youtube.com/watch?v=BCQHnlnPusY&list=PLozRqGzj97d02YjR5JVqDwN2K0cAiT7VK) diff --git a/android/app/src/main/res/drawable-night-v21/background.png b/android/app/src/main/res/drawable-night-v21/background.png index 0d2a8da..87a950a 100644 Binary files a/android/app/src/main/res/drawable-night-v21/background.png and b/android/app/src/main/res/drawable-night-v21/background.png differ diff --git a/android/app/src/main/res/drawable-night/background.png b/android/app/src/main/res/drawable-night/background.png index 0d2a8da..87a950a 100644 Binary files a/android/app/src/main/res/drawable-night/background.png and b/android/app/src/main/res/drawable-night/background.png differ diff --git a/android/app/src/main/res/drawable-v21/background.png b/android/app/src/main/res/drawable-v21/background.png index 028ee6f..8c43167 100644 Binary files a/android/app/src/main/res/drawable-v21/background.png and b/android/app/src/main/res/drawable-v21/background.png differ diff --git a/android/app/src/main/res/drawable/background.png b/android/app/src/main/res/drawable/background.png index 028ee6f..8c43167 100644 Binary files a/android/app/src/main/res/drawable/background.png and b/android/app/src/main/res/drawable/background.png differ diff --git a/flutter_native_splash.yaml b/flutter_native_splash.yaml index 4315a5a..a5f551a 100644 --- a/flutter_native_splash.yaml +++ b/flutter_native_splash.yaml @@ -3,11 +3,11 @@ flutter_native_splash: # flutter pub run flutter_native_splash:remove #background_image: 'assets/images/splash.png' - color: "#F0F0F0" + color: "#ECECEC" image: "assets/images/logo_trans.png" #background_image_dark: "assets/dark-background.png" - color_dark: "#1C1D31" + color_dark: "#12121E" image_dark: "assets/images/logo_trans.png" fullscreen: true \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png index 028ee6f..8c43167 100644 Binary files a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png and b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png index 0d2a8da..87a950a 100644 Binary files a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png and b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png differ diff --git a/lib/apis/models/entities/phone.dart b/lib/apis/models/entities/phone.dart deleted file mode 100644 index 9f23e1c..0000000 --- a/lib/apis/models/entities/phone.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'phone.g.dart'; - -@JsonSerializable() -class Phone extends Equatable { - const Phone({ - this.countryCode, - this.phoneNo, - }); - - factory Phone.fromJson(Map<String, dynamic> json) => _$PhoneFromJson(json); - - Map<String, dynamic> toJson() => _$PhoneToJson(this); - - @JsonKey(name: 'countryCode') - final String? countryCode; - - @JsonKey(name: 'phoneNo') - final String? phoneNo; - - @override - List<Object?> get props => <Object?>[ - countryCode, - phoneNo, - ]; -} diff --git a/lib/apis/models/entities/phone.g.dart b/lib/apis/models/entities/phone.g.dart deleted file mode 100644 index 28029ce..0000000 --- a/lib/apis/models/entities/phone.g.dart +++ /dev/null @@ -1,17 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'phone.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Phone _$PhoneFromJson(Map<String, dynamic> json) => Phone( - countryCode: json['countryCode'] as String?, - phoneNo: json['phoneNo'] as String?, - ); - -Map<String, dynamic> _$PhoneToJson(Phone instance) => <String, dynamic>{ - 'countryCode': instance.countryCode, - 'phoneNo': instance.phoneNo, - }; diff --git a/lib/apis/models/entities/profile.dart b/lib/apis/models/entities/profile.dart index 2fa7f53..807bac5 100644 --- a/lib/apis/models/entities/profile.dart +++ b/lib/apis/models/entities/profile.dart @@ -1,6 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:social_media_app/apis/models/entities/media_file.dart'; -import 'package:social_media_app/apis/models/entities/phone.dart'; part 'profile.g.dart'; @@ -15,7 +14,8 @@ class Profile { required this.uname, this.avatar, this.phone, - required this.phoneVerified, + this.countryCode, + this.phoneVerified, this.gender, this.dob, this.about, @@ -40,7 +40,7 @@ class Profile { Map<String, dynamic> toJson() => _$ProfileToJson(this); @JsonKey(name: '_id') - final String id; + String id; @JsonKey(name: 'fname') String fname; @@ -58,13 +58,16 @@ class Profile { String uname; @JsonKey(name: 'avatar') - final MediaFile? avatar; + MediaFile? avatar; @JsonKey(name: 'phone') - Phone? phone; + String? phone; + + @JsonKey(name: 'countryCode') + String? countryCode; @JsonKey(name: 'phoneVerified') - bool phoneVerified; + bool? phoneVerified; @JsonKey(name: 'gender') String? gender; @@ -79,7 +82,7 @@ class Profile { String? profession; @JsonKey(name: 'location') - final String? location; + String? location; @JsonKey(name: 'website') String? website; @@ -94,7 +97,7 @@ class Profile { int followingCount; @JsonKey(name: 'role') - final String role; + String role; @JsonKey(name: 'accountPrivacy') String accountPrivacy; @@ -112,5 +115,5 @@ class Profile { bool isVerified; @JsonKey(name: 'createdAt') - final DateTime createdAt; + DateTime createdAt; } diff --git a/lib/apis/models/entities/profile.g.dart b/lib/apis/models/entities/profile.g.dart index cf3b01a..0e95a57 100644 --- a/lib/apis/models/entities/profile.g.dart +++ b/lib/apis/models/entities/profile.g.dart @@ -16,10 +16,9 @@ Profile _$ProfileFromJson(Map<String, dynamic> json) => Profile( avatar: json['avatar'] == null ? null : MediaFile.fromJson(json['avatar'] as Map<String, dynamic>), - phone: json['phone'] == null - ? null - : Phone.fromJson(json['phone'] as Map<String, dynamic>), - phoneVerified: json['phoneVerified'] as bool, + phone: json['phone'] as String?, + countryCode: json['countryCode'] as String?, + phoneVerified: json['phoneVerified'] as bool?, gender: json['gender'] as String?, dob: json['dob'] as String?, about: json['about'] as String?, @@ -47,6 +46,7 @@ Map<String, dynamic> _$ProfileToJson(Profile instance) => <String, dynamic>{ 'uname': instance.uname, 'avatar': instance.avatar, 'phone': instance.phone, + 'countryCode': instance.countryCode, 'phoneVerified': instance.phoneVerified, 'gender': instance.gender, 'dob': instance.dob, diff --git a/lib/apis/providers/api_provider.dart b/lib/apis/providers/api_provider.dart index ba07df1..8d74f11 100644 --- a/lib/apis/providers/api_provider.dart +++ b/lib/apis/providers/api_provider.dart @@ -87,6 +87,18 @@ class ApiProvider { return response; } + Future<http.Response> validateToken(String token) async { + final response = await _client.get( + Uri.parse('${baseUrl!}${AppUrls.validateTokenEndpoint}?token=$token'), + headers: { + "content-type": "application/json", + "authorization": "Bearer $token", + }, + ); + + return response; + } + /// Location Info ------------------------------------------------------------ Future<http.Response> getLocationInfo() async { @@ -175,26 +187,78 @@ class ApiProvider { return response; } - Future<http.Response> sendEmailVerificationOtp(String token) async { - final response = await _client.get( - Uri.parse(baseUrl! + AppUrls.verifyEmailEndpoint), + Future<http.Response> sendChangeEmailOtp( + String token, + Map<String, dynamic> body, + ) async { + final response = await _client.post( + Uri.parse(baseUrl! + AppUrls.changeEmailEndpoint), + headers: { + "content-type": "application/json", + "authorization": "Bearer $token", + }, + body: jsonEncode(body), + ); + + return response; + } + + Future<http.Response> changeEmail( + String token, + Map<String, dynamic> body, + ) async { + final response = await _client.put( + Uri.parse(baseUrl! + AppUrls.changeEmailEndpoint), headers: { "content-type": "application/json", "authorization": "Bearer $token", }, + body: jsonEncode(body), + ); + + return response; + } + + Future<http.Response> sendAddChangePhoneOtp( + String token, + Map<String, dynamic> body, + ) async { + final response = await _client.post( + Uri.parse(baseUrl! + AppUrls.addChangePhoneEndpoint), + headers: { + "content-type": "application/json", + "authorization": "Bearer $token", + }, + body: jsonEncode(body), + ); + + return response; + } + + Future<http.Response> addChangePhone( + String token, + Map<String, dynamic> body, + ) async { + final response = await _client.put( + Uri.parse(baseUrl! + AppUrls.addChangePhoneEndpoint), + headers: { + "content-type": "application/json", + "authorization": "Bearer $token", + }, + body: jsonEncode(body), ); return response; } - Future<http.Response> verifyEmail(String token, String otp) async { + Future<http.Response> verifyPassword(String token, String password) async { final response = await _client.post( - Uri.parse(baseUrl! + AppUrls.verifyEmailEndpoint), + Uri.parse(baseUrl! + AppUrls.verifyPasswordEndpoint), headers: { "content-type": "application/json", "authorization": "Bearer $token", }, - body: jsonEncode({'otp': otp}), + body: jsonEncode({"password": password}), ); return response; diff --git a/lib/apis/services/auth_service.dart b/lib/apis/services/auth_service.dart index 5804e0c..d43d793 100644 --- a/lib/apis/services/auth_service.dart +++ b/lib/apis/services/auth_service.dart @@ -53,6 +53,41 @@ class AuthService extends GetxService { return token; } + Future<bool> _validateToken(String token) async { + var isValid = true; + try { + final response = await _apiProvider.validateToken(token); + + final decodedData = jsonDecode(utf8.decode(response.bodyBytes)); + + AppUtils.printLog(decodedData); + + if (response.statusCode == 200) { + AppUtils.printLog(decodedData[StringValues.message]); + } else { + isValid = false; + AppUtils.printLog(decodedData[StringValues.message]); + await AppUtils.clearLoginDataFromLocalStorage(); + } + } on SocketException { + AppUtils.printLog(StringValues.internetConnError); + AppUtils.showSnackBar(StringValues.internetConnError, StringValues.error); + } on TimeoutException { + AppUtils.printLog(StringValues.connTimedOut); + AppUtils.showSnackBar(StringValues.connTimedOut, StringValues.error); + } on FormatException catch (e) { + AppUtils.printLog(StringValues.formatExcError); + AppUtils.printLog(e); + AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); + } catch (exc) { + AppUtils.printLog(StringValues.errorOccurred); + AppUtils.printLog(exc); + AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); + } + + return isValid; + } + Future<void> _logout(bool showLoading) async { AppUtils.printLog("Logout Request"); if (showLoading) AppUtils.showLoadingDialog(); @@ -121,13 +156,12 @@ class AuthService extends GetxService { if (response.statusCode == 200) { locationInfo = LocationInfo.fromJson(decodedData); } else { - AppUtils.printLog(StringValues.message); + AppUtils.printLog(decodedData[StringValues.message]); } } on SocketException { AppUtils.printLog(StringValues.internetConnError); AppUtils.showSnackBar(StringValues.internetConnError, StringValues.error); } on TimeoutException { - AppUtils.printLog(StringValues.connTimedOut); AppUtils.printLog(StringValues.connTimedOut); AppUtils.showSnackBar(StringValues.connTimedOut, StringValues.error); } on FormatException catch (e) { @@ -176,7 +210,6 @@ class AuthService extends GetxService { } on TimeoutException { AppUtils.printLog("Save LoginInfo Error"); AppUtils.printLog(StringValues.connTimedOut); - AppUtils.printLog(StringValues.connTimedOut); AppUtils.showSnackBar(StringValues.connTimedOut, StringValues.error); } on FormatException catch (e) { AppUtils.printLog("Save LoginInfo Error"); @@ -218,6 +251,8 @@ class AuthService extends GetxService { Future<void> logout({showLoading = false}) async => await _logout(showLoading); + Future<bool> validateToken(String token) async => await _validateToken(token); + @override void onInit() { _checkForInternetConnectivity(); diff --git a/lib/apis/services/theme_controller.dart b/lib/apis/services/theme_controller.dart index 38ac310..d7f142f 100644 --- a/lib/apis/services/theme_controller.dart +++ b/lib/apis/services/theme_controller.dart @@ -1,36 +1,53 @@ import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:social_media_app/constants/strings.dart'; +import 'package:social_media_app/helpers/utils.dart'; -const appThemeModes = { - StringValues.system, - StringValues.light, - StringValues.dark, -}; +enum AppThemeModes { + system, + light, + dark, +} class AppThemeController extends GetxController { + static AppThemeController get find => Get.find(); final themeData = GetStorage(); - final _themeMode = Rx(appThemeModes.first); + final _themeMode = Rx(AppThemeModes.system); - String get themeMode => _themeMode.value; + AppThemeModes get themeMode => _themeMode.value; @override void onInit() { - themeData.writeIfNull(StringValues.themeMode, appThemeModes.first); + themeData.writeIfNull(StringValues.themeMode, 'system'); getThemeMode(); super.onInit(); } - void setThemeMode(value) { + void setThemeMode(AppThemeModes value) { _themeMode(value); - themeData.write(StringValues.themeMode, value); + if (value == AppThemeModes.light) { + themeData.write(StringValues.themeMode, 'light'); + } else if (value == AppThemeModes.dark) { + themeData.write(StringValues.themeMode, 'dark'); + } else { + themeData.write(StringValues.themeMode, 'system'); + } + AppUtils.printLog('changed to ${_themeMode.value}'); update(); } - void getThemeMode() { - String themeMode = themeData.read(StringValues.themeMode); - _themeMode(themeMode); + void getThemeMode() async { + String themeMode = await themeData.read(StringValues.themeMode); + AppUtils.printLog('saved theme mode = $themeMode'); + if (themeMode == 'light') { + _themeMode(AppThemeModes.light); + } else if (themeMode == 'dark') { + _themeMode(AppThemeModes.dark); + } else { + _themeMode(AppThemeModes.system); + } + AppUtils.printLog(_themeMode.value); update(); } } diff --git a/lib/constants/colors.dart b/lib/constants/colors.dart index 0e09d83..f110f3c 100644 --- a/lib/constants/colors.dart +++ b/lib/constants/colors.dart @@ -37,7 +37,7 @@ abstract class ColorValues { static const Color lightBodyTextColor = Color(0xFF323232); static const Color lightSubtitleTextColor = Color(0xFF737373); - static const Color darkBodyTextColor = Color(0xFFE8E8E8); + static const Color darkBodyTextColor = Color(0xFFDCDCDC); static const Color darkSubtitleTextColor = Color(0xFFA0A0A0); static const Color lightBgColor = Color.fromRGBO(236, 236, 236, 1.0); diff --git a/lib/constants/strings.dart b/lib/constants/strings.dart index 13528bb..85183ee 100644 --- a/lib/constants/strings.dart +++ b/lib/constants/strings.dart @@ -28,6 +28,8 @@ abstract class StringValues { static const resetYourPassword = 'Reset your password'; static const forgotYourPassword = 'Forgot your password?'; static const verifyYourAccount = 'Verify your account'; + static const verifyPassword = 'Verify Password'; + static const enterDifferentEmail = 'Enter an email other than current email'; static const enterEmailForOtp = 'Enter your email address and an OTP will be sent to your email address if account exists'; static const enterOtpYouGet = @@ -55,12 +57,18 @@ abstract class StringValues { static const enterOldPassword = 'Enter current password'; static const enterNewPassword = 'Enter a new password'; static const enterOtp = 'Enter the OTP'; + static const enterWebsiteUrl = 'Enter your website URL'; static const enterConfirmPassword = 'Retype your password'; static const errorOccurred = 'An error occurred, please try again.'; static const unknownErrorOccurred = 'An unknown error occurred.'; - static const loginSuccessful = 'Logged in successfully.'; - static const registrationSuccessful = 'Registered successfully.'; + static const loginSuccessful = 'Logged in successfully'; + static const registrationSuccessful = + 'Account registered successfully, please verify your account'; static const logoutSuccessful = 'Logged out successfully.'; + static const enterValidUrl = 'Enter a valid website URL'; + static const enterPhoneNo = 'Enter a valid phone number'; + static const enterDifferentPhoneNo = + 'Enter a phone number other than current phone number'; static const token = 'token'; static const expiresAt = 'expiresAt'; static const loginData = 'loginData'; @@ -178,6 +186,7 @@ abstract class StringValues { static const sendUsSuggestions = 'Send us suggestions'; static const privacyPolicy = 'Privacy Policy'; static const termsOfUse = 'Terms of Use'; + static const website = 'Website'; static const nameHelpText = "Either your full name, nickname, or business name."; static const accountSettingsHelp = @@ -192,8 +201,10 @@ abstract class StringValues { static const themeSettingsHelp = "Manage application theme modes."; static const deactivateAccount = 'Deactivate Account'; static const downloadArchiveOfData = 'Download an archive of your data'; - static const changeEmail = 'Change Email address'; - static const changePhoneNo = 'Change Phone number'; + static const changeEmailAddress = 'Change Email Address'; + static const changeEmail = 'Change Email'; + static const changePhoneNo = 'Change Phone Number'; + static const changePhone = 'Change Phone'; static const verification = 'Verification'; static const applyForSelfVerify = 'Apply for self verification'; static const applyForBlueTick = 'Apply for blue tick'; @@ -203,6 +214,7 @@ abstract class StringValues { 'Help protect your account from unauthorised access by requiring a second authentication method in addition to your password.'; static const moreDetails = 'More Details'; static const websiteUrl = 'https://nixlab.co.in'; + static const portfolioUrl = 'https://nixrajput.nixlab.co.in'; static const appDownloadUrl = 'https://github.com/nixrajput/social-media-app-flutter/releases'; static const appGithubUrl = diff --git a/lib/constants/urls.dart b/lib/constants/urls.dart index 12022be..651390e 100644 --- a/lib/constants/urls.dart +++ b/lib/constants/urls.dart @@ -29,8 +29,11 @@ abstract class AppUrls { static const String updateProfileEndpoint = '/update-profile'; static const String checkUsernameEndpoint = '/check-username'; static const String changeUsernameEndpoint = '/change-username'; - static const String verifyEmailEndpoint = '/verify-email'; static const String deleteProfileEndpoint = '/delete-profile'; + static const String validateTokenEndpoint = '/validate-token'; + static const String addChangePhoneEndpoint = '/add-change-phone'; + static const String changeEmailEndpoint = '/change-email'; + static const String verifyPasswordEndpoint = '/verify-password'; static const String saveLoginInfoEndpoint = '/save-device-info'; static const String getLoginInfoEndpoint = '/get-device-info'; diff --git a/lib/global_widgets/custom_app_bar.dart b/lib/global_widgets/custom_app_bar.dart index 07c3c8b..98534a6 100644 --- a/lib/global_widgets/custom_app_bar.dart +++ b/lib/global_widgets/custom_app_bar.dart @@ -31,43 +31,36 @@ class NxAppBar extends StatelessWidget { return Container( width: Dimens.screenWidth, color: bgColor ?? Colors.transparent, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: padding ?? Dimens.edgeInsets8, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (showBackBtn == true) - GestureDetector( - onTap: RouteManagement.goToBack, - child: Icon( - CupertinoIcons.arrow_left, - color: backBtnColor ?? - Theme.of(context).textTheme.bodyText1!.color, - size: Dimens.twentyFour, + child: Padding( + padding: padding ?? Dimens.edgeInsets8, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (showBackBtn == true) + GestureDetector( + onTap: RouteManagement.goToBack, + child: Icon( + CupertinoIcons.arrow_left, + color: backBtnColor ?? + Theme.of(context).textTheme.bodyText1!.color, + size: Dimens.twentyFour, + ), + ), + if (showBackBtn == true) Dimens.boxWidth16, + if (leading != null) leading!, + if (leading != null && title != null) Dimens.boxWidth16, + if (title != null && title!.isNotEmpty) + Text( + title!, + style: titleStyle ?? + AppStyles.style20Bold.copyWith( + color: Theme.of(context).textTheme.bodyText1!.color, + height: 1.0, ), - ), - if (showBackBtn == true) Dimens.boxWidth16, - if (leading != null) leading!, - if (leading != null && title != null) Dimens.boxWidth16, - if (title != null && title!.isNotEmpty) - Text( - title!, - style: titleStyle ?? - AppStyles.style20Bold.copyWith( - color: Theme.of(context).textTheme.bodyText1!.color, - ), - ) - ], - ), - ), - if (showDivider == true) Dimens.divider, - ], + ) + ], + ), ), ); } diff --git a/lib/global_widgets/custom_list_tile.dart b/lib/global_widgets/custom_list_tile.dart index a9c5e38..b00cf4a 100644 --- a/lib/global_widgets/custom_list_tile.dart +++ b/lib/global_widgets/custom_list_tile.dart @@ -37,7 +37,7 @@ class NxListTile extends StatelessWidget { maxWidth: Dimens.screenWidth, ), decoration: BoxDecoration( - color: bgColor ?? Colors.transparent, + color: bgColor ?? Theme.of(context).dialogTheme.backgroundColor, borderRadius: borderRadius ?? BorderRadius.circular(Dimens.zero), ), child: Row( diff --git a/lib/global_widgets/primary_outlined_btn.dart b/lib/global_widgets/primary_outlined_btn.dart index 13a7d1f..648b1bb 100644 --- a/lib/global_widgets/primary_outlined_btn.dart +++ b/lib/global_widgets/primary_outlined_btn.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:social_media_app/constants/colors.dart'; import 'package:social_media_app/constants/dimens.dart'; import 'package:social_media_app/constants/styles.dart'; @@ -55,7 +54,7 @@ class NxOutlinedButton extends StatelessWidget { ), decoration: BoxDecoration( border: Border.all( - color: borderColor ?? ColorValues.primaryColor, + color: borderColor ?? Theme.of(context).textTheme.bodyText1!.color!, width: borderWidth ?? Dimens.one * 1.5, style: borderStyle ?? BorderStyle.solid, ), @@ -73,7 +72,8 @@ class NxOutlinedButton extends StatelessWidget { label, style: labelStyle ?? AppStyles.style16Bold.copyWith( - color: labelColor ?? ColorValues.primaryColor, + color: labelColor ?? + Theme.of(context).textTheme.bodyText1!.color, fontSize: fontSize ?? Dimens.sixTeen, ), ), diff --git a/lib/helpers/get_time_ago_msg.dart b/lib/helpers/get_time_ago_msg.dart new file mode 100644 index 0000000..fb5a2dd --- /dev/null +++ b/lib/helpers/get_time_ago_msg.dart @@ -0,0 +1,33 @@ +import 'package:get_time_ago/get_time_ago.dart'; + +class CustomMessages implements Messages { + @override + String prefixAgo() => ''; + + @override + String suffixAgo() => 'ago'; + + @override + String secsAgo(int seconds) => '${seconds}s'; + + @override + String minAgo(int minutes) => '1m'; + + @override + String minsAgo(int minutes) => '${minutes}m'; + + @override + String hourAgo(int minutes) => '1h'; + + @override + String hoursAgo(int hours) => '${hours}h'; + + @override + String dayAgo(int hours) => '1d'; + + @override + String daysAgo(int days) => '${days}d'; + + @override + String wordSeparator() => ' '; +} diff --git a/lib/helpers/validators.dart b/lib/helpers/validators.dart new file mode 100644 index 0000000..afb9968 --- /dev/null +++ b/lib/helpers/validators.dart @@ -0,0 +1,13 @@ +abstract class Validators { + static const String urlPattern = + r"(https?|http)://([-A-Z0-9.]+)(/[-A-Z0-9+&@#/%=~_|!:,.;]*)?(\?[A-Z0-9+&@#/%=~_|!:,.;]*)?"; + static const String emailPattern = r'\S+@\S+'; + static const String phonePattern = r'[\d-]{9,}'; + + static bool isValidUrl(String url) { + if (RegExp(urlPattern, caseSensitive: false).hasMatch(url)) { + return true; + } + return false; + } +} diff --git a/lib/main.dart b/lib/main.dart index 2cb57c8..eb08bb4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,6 +14,7 @@ import 'package:social_media_app/modules/app_update/app_update_controller.dart'; import 'package:social_media_app/modules/home/controllers/profile_controller.dart'; import 'package:social_media_app/modules/settings/controllers/login_device_info_controller.dart'; import 'package:social_media_app/routes/app_pages.dart'; +import 'package:social_media_app/translations/app_translations.dart'; void main() async { try { @@ -43,11 +44,13 @@ Future<void> initServices() async { await Get.find<AuthService>().getToken().then((value) async { Get.find<AuthService>().autoLogout(); if (value.isNotEmpty) { - var hasData = await Get.find<ProfileController>().loadProfileDetails(); - if (hasData) { - isLogin = true; - } else { - await Get.find<AuthService>().logout(); + var tokenValid = await Get.find<AuthService>().validateToken(value); + + if (tokenValid) { + var hasData = await Get.find<ProfileController>().loadProfileDetails(); + if (hasData) { + isLogin = true; + } } } isLogin @@ -72,6 +75,7 @@ class MyApp extends StatelessWidget { systemNavigationBarIconBrightness: Brightness.dark, ), ); + //AppThemeController.find.setThemeMode(AppThemeModes.light); } else { SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( @@ -82,6 +86,7 @@ class MyApp extends StatelessWidget { systemNavigationBarIconBrightness: Brightness.light, ), ); + //AppThemeController.find.setThemeMode(AppThemeModes.dark); } return GetBuilder<AppThemeController>( @@ -95,16 +100,19 @@ class MyApp extends StatelessWidget { darkTheme: AppThemes.darkTheme, getPages: AppPages.pages, initialRoute: isLogin ? AppRoutes.home : AppRoutes.welcome, + translations: AppTranslation(), + locale: Get.deviceLocale, + fallbackLocale: const Locale('en', 'US'), ), ), ); } - _handleAppTheme(mode) { - if (mode == StringValues.dark) { + _handleAppTheme(AppThemeModes mode) { + if (mode == AppThemeModes.dark) { return ThemeMode.dark; } - if (mode == StringValues.light) { + if (mode == AppThemeModes.light) { return ThemeMode.light; } return ThemeMode.system; diff --git a/lib/modules/auth/controllers/password_controller.dart b/lib/modules/auth/controllers/password_controller.dart index f8405cd..b79adb5 100644 --- a/lib/modules/auth/controllers/password_controller.dart +++ b/lib/modules/auth/controllers/password_controller.dart @@ -93,7 +93,6 @@ class PasswordController extends GetxController { _isLoading.value = false; update(); AppUtils.printLog(StringValues.connTimedOut); - AppUtils.printLog(StringValues.connTimedOut); AppUtils.showSnackBar(StringValues.connTimedOut, StringValues.error); } on FormatException catch (e) { AppUtils.closeDialog(); @@ -186,7 +185,6 @@ class PasswordController extends GetxController { _isLoading.value = false; update(); AppUtils.printLog(StringValues.connTimedOut); - AppUtils.printLog(StringValues.connTimedOut); AppUtils.showSnackBar(StringValues.connTimedOut, StringValues.error); } on FormatException catch (e) { AppUtils.closeDialog(); diff --git a/lib/modules/auth/controllers/register_controller.dart b/lib/modules/auth/controllers/register_controller.dart index ab63d09..609f477 100644 --- a/lib/modules/auth/controllers/register_controller.dart +++ b/lib/modules/auth/controllers/register_controller.dart @@ -113,7 +113,7 @@ class RegisterController extends GetxController { 'confirmPassword': confPassword, }; - AppUtils.printLog("User Registration Request..."); + AppUtils.printLog("User Registration Request"); AppUtils.showLoadingDialog(); _isLoading.value = true; update(); @@ -128,15 +128,18 @@ class RegisterController extends GetxController { AppUtils.closeDialog(); _isLoading.value = false; update(); + AppUtils.printLog("User Registration Success"); AppUtils.showSnackBar( StringValues.registrationSuccessful, StringValues.success, ); - RouteManagement.goToLoginView(); + RouteManagement.goToBack(); + RouteManagement.goToSendVerifyAccountOtpView(); } else { AppUtils.closeDialog(); _isLoading.value = false; update(); + AppUtils.printLog("User Registration Error"); AppUtils.showSnackBar( decodedData[StringValues.message], StringValues.error, @@ -146,19 +149,21 @@ class RegisterController extends GetxController { AppUtils.closeDialog(); _isLoading.value = false; update(); + AppUtils.printLog("User Registration Error"); AppUtils.printLog(StringValues.internetConnError); AppUtils.showSnackBar(StringValues.internetConnError, StringValues.error); } on TimeoutException { AppUtils.closeDialog(); _isLoading.value = false; update(); - AppUtils.printLog(StringValues.connTimedOut); + AppUtils.printLog("User Registration Error"); AppUtils.printLog(StringValues.connTimedOut); AppUtils.showSnackBar(StringValues.connTimedOut, StringValues.error); } on FormatException catch (e) { AppUtils.closeDialog(); _isLoading.value = false; update(); + AppUtils.printLog("User Registration Error"); AppUtils.printLog(StringValues.formatExcError); AppUtils.printLog(e); AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); @@ -166,6 +171,7 @@ class RegisterController extends GetxController { AppUtils.closeDialog(); _isLoading.value = false; update(); + AppUtils.printLog("User Registration Error"); AppUtils.printLog(StringValues.errorOccurred); AppUtils.printLog(exc); AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); diff --git a/lib/modules/home/controllers/profile_controller.dart b/lib/modules/home/controllers/profile_controller.dart index 99576da..947a380 100644 --- a/lib/modules/home/controllers/profile_controller.dart +++ b/lib/modules/home/controllers/profile_controller.dart @@ -59,12 +59,11 @@ class ProfileController extends GetxController { setPostData = PostResponse.fromJson(decodedPostData); _postList.clear(); _postList.addAll(_postData.value.results!); + AppUtils.printLog("Loading Profile Details From Local Storage Success"); + return true; } else { AppUtils.printLog("Profile Post Data Not Found"); } - - AppUtils.printLog("Loading Profile Details From Local Storage Success"); - return true; } else { AppUtils.printLog("Loading Profile Details From Local Storage Error"); AppUtils.printLog(StringValues.profileDetailsNotFound); diff --git a/lib/modules/home/views/tab_views/profile_tab.dart b/lib/modules/home/views/tab_views/profile_tab.dart index 3d7f889..9a26f28 100644 --- a/lib/modules/home/views/tab_views/profile_tab.dart +++ b/lib/modules/home/views/tab_views/profile_tab.dart @@ -171,6 +171,33 @@ class ProfileTabView extends StatelessWidget { style: AppStyles.style14Normal, ), Dimens.boxHeight8, + if (logic.profileDetails.user!.website != null) + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.link, + size: Dimens.sixTeen, + color: Theme.of(Get.context!).textTheme.subtitle1!.color, + ), + Dimens.boxWidth8, + InkWell( + onTap: () => AppUtils.openUrl( + Uri.parse(logic.profileDetails.user!.website!)), + child: Text( + logic.profileDetails.user!.website!.contains('https://') || + logic.profileDetails.user!.website! + .contains('http://') + ? Uri.parse(logic.profileDetails.user!.website!).host + : logic.profileDetails.user!.website!, + style: AppStyles.style13Bold.copyWith( + color: ColorValues.primaryColor, + ), + ), + ), + ], + ), + Dimens.boxHeight8, Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ diff --git a/lib/modules/home/views/widgets/post_widget.dart b/lib/modules/home/views/widgets/post_widget.dart index c4d9939..0315c3a 100644 --- a/lib/modules/home/views/widgets/post_widget.dart +++ b/lib/modules/home/views/widgets/post_widget.dart @@ -16,6 +16,7 @@ import 'package:social_media_app/global_widgets/expandable_text_widget.dart'; import 'package:social_media_app/global_widgets/primary_icon_btn.dart'; import 'package:social_media_app/global_widgets/primary_text_btn.dart'; import 'package:social_media_app/global_widgets/video_player_widget.dart'; +import 'package:social_media_app/helpers/get_time_ago_msg.dart'; import 'package:social_media_app/helpers/utils.dart'; import 'package:social_media_app/modules/home/controllers/post_controller.dart'; import 'package:social_media_app/modules/home/controllers/profile_controller.dart'; @@ -90,25 +91,11 @@ class _PostWidgetState extends State<PostWidget> { ), ], ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - GetTimeAgo.parse(widget.post.createdAt), - style: AppStyles.style12Normal.copyWith( - color: - Theme.of(Get.context!).textTheme.subtitle1!.color, - ), - ), - Dimens.boxWidth8, - NxIconButton( - icon: CupertinoIcons.ellipsis_vertical, - iconSize: Dimens.twenty, - iconColor: Theme.of(context).textTheme.bodyText1!.color, - onTap: _showHeaderOptionBottomSheet, - ), - ], + NxIconButton( + icon: CupertinoIcons.ellipsis_vertical, + iconSize: Dimens.twenty, + iconColor: Theme.of(context).textTheme.bodyText1!.color, + onTap: _showHeaderOptionBottomSheet, ), ], ), @@ -248,73 +235,80 @@ class _PostWidgetState extends State<PostWidget> { padding: Dimens.edgeInsets0_4, child: Row( crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - GestureDetector( - onTap: () => RouteManagement.goToPostPostLikedUsersView( - widget.post.id!), - child: Padding( - padding: Dimens.edgeInsets4, - child: RichText( - text: TextSpan( - children: [ - TextSpan( - text: '${widget.post.likesCount}' - .toCountingFormat(), - style: AppStyles.style14Bold.copyWith( - color: Theme.of(Get.context!) - .textTheme - .bodyText1! - .color, - ), - ), - TextSpan( - text: ' Likes', - style: AppStyles.style14Bold.copyWith( - color: Theme.of(Get.context!) - .textTheme - .subtitle1! - .color, - ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + GestureDetector( + onTap: () => RouteManagement.goToPostPostLikedUsersView( + widget.post.id!), + child: Padding( + padding: Dimens.edgeInsets4, + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: '${widget.post.likesCount}' + .toCountingFormat(), + style: AppStyles.style14Bold.copyWith( + color: Theme.of(Get.context!) + .textTheme + .bodyText1! + .color, + ), + ), + TextSpan( + text: ' Likes', + style: AppStyles.style14Bold.copyWith( + color: Theme.of(Get.context!) + .textTheme + .subtitle1! + .color, + ), + ), + ], ), - ], + ), ), ), - ), - ), - Dimens.boxWidth16, - GestureDetector( - onTap: () => - RouteManagement.goToPostCommentsView(widget.post.id!), - child: Padding( - padding: Dimens.edgeInsets4, - child: RichText( - text: TextSpan( - children: [ - TextSpan( - text: '${widget.post.commentsCount}' - .toCountingFormat(), - style: AppStyles.style14Bold.copyWith( - color: Theme.of(Get.context!) - .textTheme - .bodyText1! - .color, - ), - ), - TextSpan( - text: ' Comments', - style: AppStyles.style14Bold.copyWith( - color: Theme.of(Get.context!) - .textTheme - .subtitle1! - .color, - ), + Dimens.boxWidth16, + GestureDetector( + onTap: () => RouteManagement.goToPostCommentsView( + widget.post.id!), + child: Padding( + padding: Dimens.edgeInsets4, + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: '${widget.post.commentsCount}' + .toCountingFormat(), + style: AppStyles.style14Bold.copyWith( + color: Theme.of(Get.context!) + .textTheme + .bodyText1! + .color, + ), + ), + TextSpan( + text: ' Comments', + style: AppStyles.style14Bold.copyWith( + color: Theme.of(Get.context!) + .textTheme + .subtitle1! + .color, + ), + ), + ], ), - ], + ), ), ), - ), + ], ), + _buildPostTime(), ], ), ), @@ -368,6 +362,19 @@ class _PostWidgetState extends State<PostWidget> { ), ); + Widget _buildPostTime() { + GetTimeAgo.setCustomLocaleMessages('en', CustomMessages()); + return Text( + GetTimeAgo.parse( + widget.post.createdAt, + pattern: 'dd MMM yyyy hh:mm a', + ), + style: AppStyles.style12Normal.copyWith( + color: Theme.of(Get.context!).textTheme.subtitle1!.color, + ), + ); + } + _showHeaderOptionBottomSheet() => AppUtils.showBottomSheet( [ // ListTile( diff --git a/lib/modules/profile/bindings/edit_website_binding.dart b/lib/modules/profile/bindings/edit_website_binding.dart new file mode 100644 index 0000000..b486301 --- /dev/null +++ b/lib/modules/profile/bindings/edit_website_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import 'package:social_media_app/modules/profile/controllers/edit_website_controller.dart'; + +class EditWebsiteBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(EditWebsiteController.new); + } +} diff --git a/lib/modules/profile/controllers/edit_website_controller.dart b/lib/modules/profile/controllers/edit_website_controller.dart new file mode 100644 index 0000000..f0ce899 --- /dev/null +++ b/lib/modules/profile/controllers/edit_website_controller.dart @@ -0,0 +1,148 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:http/http.dart' as http; +import 'package:social_media_app/apis/providers/api_provider.dart'; +import 'package:social_media_app/apis/services/auth_service.dart'; +import 'package:social_media_app/constants/strings.dart'; +import 'package:social_media_app/helpers/utils.dart'; +import 'package:social_media_app/helpers/validators.dart'; +import 'package:social_media_app/modules/home/controllers/profile_controller.dart'; +import 'package:social_media_app/routes/route_management.dart'; + +class EditWebsiteController extends GetxController { + static EditWebsiteController get find => Get.find(); + + final _profile = ProfileController.find; + final _auth = AuthService.find; + + final _apiProvider = ApiProvider(http.Client()); + + final FocusScopeNode focusNode = FocusScopeNode(); + + final _isLoading = false.obs; + final _website = ''.obs; + + /// Getters + bool get isLoading => _isLoading.value; + + String get website => _website.value; + + /// Setters + set setWebsite(String value) => _website.value = value; + + void onChangeWebsite(String url) { + setWebsite = url; + update(); + } + + @override + void onInit() { + initializeFields(); + super.onInit(); + } + + void initializeFields() async { + if (_profile.profileDetails.user != null) { + var user = _profile.profileDetails.user!; + setWebsite = user.website ?? ''; + } + } + + Future<void> _updateWebsite(String website) async { + if (website.isEmpty) { + AppUtils.showSnackBar( + StringValues.enterWebsiteUrl, + StringValues.warning, + ); + return; + } + + final body = {'website': website}; + + AppUtils.printLog("Update Website Request"); + AppUtils.showLoadingDialog(); + _isLoading.value = true; + update(); + + try { + final response = await _apiProvider.updateProfile(_auth.token, body); + + final decodedData = jsonDecode(utf8.decode(response.bodyBytes)); + + if (response.statusCode == 200) { + await _profile.fetchProfileDetails(fetchPost: false); + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Update Website Success"); + RouteManagement.goToBack(); + AppUtils.showSnackBar( + decodedData[StringValues.message], + StringValues.success, + ); + } else { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Update Website Error"); + AppUtils.showSnackBar( + decodedData[StringValues.message], + StringValues.error, + ); + } + } on SocketException { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Update Website Error"); + AppUtils.printLog(StringValues.internetConnError); + AppUtils.showSnackBar(StringValues.internetConnError, StringValues.error); + } on TimeoutException { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Update Website Error"); + AppUtils.printLog(StringValues.connTimedOut); + AppUtils.showSnackBar(StringValues.connTimedOut, StringValues.error); + } on FormatException catch (e) { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Update Website Error"); + AppUtils.printLog(StringValues.formatExcError); + AppUtils.printLog(e); + AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); + } catch (exc) { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Update Website Error"); + AppUtils.printLog(StringValues.errorOccurred); + AppUtils.printLog(exc); + AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); + } + } + + Future<void> updateWebsite() async { + AppUtils.closeFocus(); + if (_website.value.isEmpty) { + return; + } + if (_website.value == _profile.profileDetails.user!.website?.trim()) { + return; + } + if (!Validators.isValidUrl(_website.value)) { + AppUtils.showSnackBar( + StringValues.enterValidUrl, + StringValues.warning, + ); + return; + } + + await _updateWebsite(_website.value.trim()); + } +} diff --git a/lib/modules/profile/views/edit_views/edit_gender_view.dart b/lib/modules/profile/views/edit_views/edit_gender_view.dart index d7696d5..ca3c68b 100644 --- a/lib/modules/profile/views/edit_views/edit_gender_view.dart +++ b/lib/modules/profile/views/edit_views/edit_gender_view.dart @@ -46,8 +46,15 @@ class EditGenderView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + /// Male + NxRadioTile( - padding: Dimens.edgeInsets16_0, + padding: Dimens.edgeInsets8, + bgColor: Theme.of(Get.context!).dialogBackgroundColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(Dimens.eight), + topRight: Radius.circular(Dimens.eight), + ), onTap: () => logic.setGender = StringValues.male, onChanged: (value) { logic.setGender = value.toString(); @@ -56,8 +63,14 @@ class EditGenderView extends StatelessWidget { value: StringValues.male, groupValue: logic.gender, ), + + Dimens.divider, + + /// Female + NxRadioTile( - padding: Dimens.edgeInsets16_0, + padding: Dimens.edgeInsets8, + bgColor: Theme.of(Get.context!).dialogBackgroundColor, onTap: () => logic.setGender = StringValues.female, onChanged: (value) { logic.setGender = value.toString(); @@ -66,8 +79,18 @@ class EditGenderView extends StatelessWidget { value: StringValues.female, groupValue: logic.gender, ), + + Dimens.divider, + + /// Others + NxRadioTile( - padding: Dimens.edgeInsets16_0, + padding: Dimens.edgeInsets8, + bgColor: Theme.of(Get.context!).dialogBackgroundColor, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(Dimens.eight), + bottomRight: Radius.circular(Dimens.eight), + ), onTap: () => logic.setGender = StringValues.others, onChanged: (value) { logic.setGender = value.toString(); diff --git a/lib/modules/profile/views/edit_views/edit_website_view.dart b/lib/modules/profile/views/edit_views/edit_website_view.dart new file mode 100644 index 0000000..8d00eb4 --- /dev/null +++ b/lib/modules/profile/views/edit_views/edit_website_view.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:social_media_app/constants/colors.dart'; +import 'package:social_media_app/constants/dimens.dart'; +import 'package:social_media_app/constants/strings.dart'; +import 'package:social_media_app/constants/styles.dart'; +import 'package:social_media_app/global_widgets/custom_app_bar.dart'; +import 'package:social_media_app/global_widgets/primary_filled_btn.dart'; +import 'package:social_media_app/modules/profile/controllers/edit_website_controller.dart'; + +class EditWebsiteView extends StatelessWidget { + const EditWebsiteView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => FocusManager.instance.primaryFocus?.unfocus(), + child: Scaffold( + body: SafeArea( + child: SizedBox( + width: Dimens.screenWidth, + height: Dimens.screenHeight, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + NxAppBar( + title: StringValues.website, + padding: Dimens.edgeInsets8_16, + ), + Dimens.boxHeight24, + _buildBody(), + ], + ), + ), + ), + ), + ); + } + + Widget _buildBody() => GetBuilder<EditWebsiteController>( + builder: (logic) => Expanded( + child: SingleChildScrollView( + child: Padding( + padding: Dimens.edgeInsets0_16, + child: FocusScope( + node: logic.focusNode, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + height: Dimens.fiftySix, + constraints: BoxConstraints(maxWidth: Dimens.screenWidth), + child: TextFormField( + decoration: InputDecoration( + hintText: StringValues.website, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(Dimens.eight), + ), + hintStyle: AppStyles.style14Normal.copyWith( + color: ColorValues.grayColor, + ), + ), + maxLines: 1, + initialValue: logic.website, + keyboardType: TextInputType.url, + style: AppStyles.style14Normal.copyWith( + color: + Theme.of(Get.context!).textTheme.bodyText1!.color, + ), + onChanged: (value) => logic.onChangeWebsite(value), + onEditingComplete: logic.focusNode.unfocus, + ), + ), + Dimens.boxHeight40, + NxFilledButton( + onTap: logic.updateWebsite, + label: StringValues.save.toUpperCase(), + ), + Dimens.boxHeight16, + ], + ), + ), + ), + ), + ), + ); +} diff --git a/lib/modules/profile/views/profile_details_view.dart b/lib/modules/profile/views/profile_details_view.dart index 0a3ce8a..c5d2825 100644 --- a/lib/modules/profile/views/profile_details_view.dart +++ b/lib/modules/profile/views/profile_details_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:social_media_app/constants/colors.dart'; import 'package:social_media_app/constants/data.dart'; import 'package:social_media_app/constants/dimens.dart'; import 'package:social_media_app/constants/strings.dart'; @@ -231,10 +232,6 @@ class ProfileDetailsView extends StatelessWidget { NxListTile( padding: Dimens.edgeInsets12_8, bgColor: Theme.of(Get.context!).dialogBackgroundColor, - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(Dimens.eight), - bottomRight: Radius.circular(Dimens.eight), - ), leading: const Icon(Icons.male_outlined), title: Text( StringValues.gender, @@ -260,6 +257,39 @@ class ProfileDetailsView extends StatelessWidget { ), onTap: RouteManagement.goToEditGenderView, ), + + Dimens.divider, + + /// Website + + NxListTile( + padding: Dimens.edgeInsets12_8, + bgColor: Theme.of(Get.context!).dialogBackgroundColor, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(Dimens.eight), + bottomRight: Radius.circular(Dimens.eight), + ), + leading: const Icon(Icons.link), + title: Text( + StringValues.website, + style: AppStyles.style12Normal.copyWith( + color: + Theme.of(Get.context!).textTheme.subtitle1!.color, + ), + ), + subtitle: Text( + logic.profileDetails.user!.website ?? 'Add website', + style: AppStyles.style16Normal.copyWith( + color: logic.profileDetails.user!.website == null + ? Theme.of(Get.context!) + .textTheme + .subtitle1 + ?.color + : ColorValues.primaryColor, + ), + ), + onTap: RouteManagement.goToEditWebsiteView, + ), Dimens.boxHeight16, ], ), diff --git a/lib/modules/settings/bindings/change_email_binding.dart b/lib/modules/settings/bindings/change_email_binding.dart new file mode 100644 index 0000000..3610ce8 --- /dev/null +++ b/lib/modules/settings/bindings/change_email_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import 'package:social_media_app/modules/settings/controllers/change_email_controller.dart'; + +class ChangeEmailBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(ChangeEmailController.new); + } +} diff --git a/lib/modules/settings/bindings/change_phone_binding.dart b/lib/modules/settings/bindings/change_phone_binding.dart new file mode 100644 index 0000000..1e42840 --- /dev/null +++ b/lib/modules/settings/bindings/change_phone_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import 'package:social_media_app/modules/settings/controllers/change_phone_controller.dart'; + +class ChangePhoneBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(ChangePhoneController.new); + } +} diff --git a/lib/modules/settings/controllers/change_email_controller.dart b/lib/modules/settings/controllers/change_email_controller.dart new file mode 100644 index 0000000..fb453f6 --- /dev/null +++ b/lib/modules/settings/controllers/change_email_controller.dart @@ -0,0 +1,214 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:http/http.dart' as http; +import 'package:social_media_app/apis/providers/api_provider.dart'; +import 'package:social_media_app/apis/services/auth_service.dart'; +import 'package:social_media_app/constants/strings.dart'; +import 'package:social_media_app/helpers/utils.dart'; +import 'package:social_media_app/modules/home/controllers/profile_controller.dart'; +import 'package:social_media_app/routes/route_management.dart'; + +class ChangeEmailController extends GetxController { + static ChangeEmailController get find => Get.find(); + + final _auth = AuthService.find; + final profile = ProfileController.find; + + final _apiProvider = ApiProvider(http.Client()); + + final _isLoading = false.obs; + final _otpSent = false.obs; + final emailTextController = TextEditingController(); + final otpTextController = TextEditingController(); + + final FocusScopeNode focusNode = FocusScopeNode(); + + /// Getters + bool get isLoading => _isLoading.value; + + bool get otpSent => _otpSent.value; + + Future<void> _sendChangeEmailOtp(String email) async { + if (email.isEmpty) { + AppUtils.showSnackBar( + StringValues.enterEmail, + StringValues.warning, + ); + return; + } + + if (profile.profileDetails.user!.email == email) { + AppUtils.showSnackBar( + StringValues.enterDifferentEmail, + StringValues.warning, + ); + return; + } + + final body = {'email': email}; + + AppUtils.printLog("Send Change Email OTP Request"); + AppUtils.showLoadingDialog(); + _isLoading.value = true; + update(); + + try { + final response = await _apiProvider.sendChangeEmailOtp(_auth.token, body); + + final decodedData = jsonDecode(utf8.decode(response.bodyBytes)); + + if (response.statusCode == 200) { + AppUtils.printLog("Send Change Email OTP Success"); + AppUtils.closeDialog(); + _isLoading.value = false; + _otpSent.value = true; + update(); + AppUtils.showSnackBar( + decodedData[StringValues.message], + StringValues.success, + ); + } else { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Send Change Email OTP Error"); + AppUtils.showSnackBar( + decodedData[StringValues.message], + StringValues.error, + ); + } + } on SocketException { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Send Change Email OTP Error"); + AppUtils.printLog(StringValues.internetConnError); + AppUtils.showSnackBar(StringValues.internetConnError, StringValues.error); + } on TimeoutException { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Send Change Email OTP Error"); + AppUtils.printLog(StringValues.connTimedOut); + AppUtils.showSnackBar(StringValues.connTimedOut, StringValues.error); + } on FormatException catch (e) { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Send Change Email OTP Error"); + AppUtils.printLog(StringValues.formatExcError); + AppUtils.printLog(e); + AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); + } catch (exc) { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Send Change Email OTP Error"); + AppUtils.printLog(StringValues.errorOccurred); + AppUtils.printLog(exc); + AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); + } + } + + Future<void> _changeEmail(String otp, String email) async { + if (otp.isEmpty) { + AppUtils.showSnackBar( + StringValues.enterOtp, + StringValues.warning, + ); + return; + } + if (email.isEmpty) { + AppUtils.showSnackBar( + StringValues.enterEmail, + StringValues.warning, + ); + return; + } + + final body = { + 'otp': otp, + 'email': email, + }; + + AppUtils.printLog("Change Email Request"); + AppUtils.showLoadingDialog(); + _isLoading.value = true; + update(); + + try { + final response = await _apiProvider.changeEmail(_auth.token, body); + + final decodedData = jsonDecode(utf8.decode(response.bodyBytes)); + + if (response.statusCode == 200) { + AppUtils.printLog("Change Email Success"); + await profile.fetchProfileDetails(fetchPost: false); + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + RouteManagement.goToBack(); + AppUtils.showSnackBar( + decodedData[StringValues.message], + StringValues.success, + ); + } else { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Change Email Error"); + AppUtils.showSnackBar( + decodedData[StringValues.message], + StringValues.error, + ); + } + } on SocketException { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Change Email Error"); + AppUtils.printLog(StringValues.internetConnError); + AppUtils.showSnackBar(StringValues.internetConnError, StringValues.error); + } on TimeoutException { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Change Email Error"); + AppUtils.printLog(StringValues.connTimedOut); + AppUtils.showSnackBar(StringValues.connTimedOut, StringValues.error); + } on FormatException catch (e) { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Change Email Error"); + AppUtils.printLog(StringValues.formatExcError); + AppUtils.printLog(e); + AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); + } catch (exc) { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Change Email Error"); + AppUtils.printLog(StringValues.errorOccurred); + AppUtils.printLog(exc); + AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); + } + } + + Future<void> changeEmail() async { + AppUtils.closeFocus(); + await _changeEmail( + otpTextController.text.trim(), + emailTextController.text.trim(), + ); + } + + Future<void> sendChangeEmailOtp() async { + AppUtils.closeFocus(); + await _sendChangeEmailOtp(emailTextController.text.trim()); + } +} diff --git a/lib/modules/settings/controllers/change_password_controller.dart b/lib/modules/settings/controllers/change_password_controller.dart index 1e0177b..561cc65 100644 --- a/lib/modules/settings/controllers/change_password_controller.dart +++ b/lib/modules/settings/controllers/change_password_controller.dart @@ -9,6 +9,7 @@ import 'package:social_media_app/apis/providers/api_provider.dart'; import 'package:social_media_app/apis/services/auth_service.dart'; import 'package:social_media_app/constants/strings.dart'; import 'package:social_media_app/helpers/utils.dart'; +import 'package:social_media_app/routes/route_management.dart'; class ChangePasswordController extends GetxController { static ChangePasswordController get find => Get.find(); @@ -76,7 +77,7 @@ class ChangePasswordController extends GetxController { 'confirmPassword': confPassword, }; - AppUtils.printLog("Change Password Request..."); + AppUtils.printLog("Change Password Request"); AppUtils.showLoadingDialog(); _isLoading.value = true; update(); @@ -87,14 +88,16 @@ class ChangePasswordController extends GetxController { final decodedData = jsonDecode(utf8.decode(response.bodyBytes)); if (response.statusCode == 200) { - await _auth.logout(); AppUtils.closeDialog(); _isLoading.value = false; update(); + AppUtils.printLog("Change Password Success"); + RouteManagement.goToBack(); } else { AppUtils.closeDialog(); _isLoading.value = false; update(); + AppUtils.printLog("Change Password Error"); AppUtils.showSnackBar( decodedData[StringValues.message], StringValues.error, @@ -104,19 +107,21 @@ class ChangePasswordController extends GetxController { AppUtils.closeDialog(); _isLoading.value = false; update(); + AppUtils.printLog("Change Password Error"); AppUtils.printLog(StringValues.internetConnError); AppUtils.showSnackBar(StringValues.internetConnError, StringValues.error); } on TimeoutException { AppUtils.closeDialog(); _isLoading.value = false; update(); - AppUtils.printLog(StringValues.connTimedOut); + AppUtils.printLog("Change Password Error"); AppUtils.printLog(StringValues.connTimedOut); AppUtils.showSnackBar(StringValues.connTimedOut, StringValues.error); } on FormatException catch (e) { AppUtils.closeDialog(); _isLoading.value = false; update(); + AppUtils.printLog("Change Password Error"); AppUtils.printLog(StringValues.formatExcError); AppUtils.printLog(e); AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); @@ -124,6 +129,7 @@ class ChangePasswordController extends GetxController { AppUtils.closeDialog(); _isLoading.value = false; update(); + AppUtils.printLog("Change Password Error"); AppUtils.printLog(StringValues.errorOccurred); AppUtils.printLog(exc); AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); diff --git a/lib/modules/settings/controllers/change_phone_controller.dart b/lib/modules/settings/controllers/change_phone_controller.dart new file mode 100644 index 0000000..bcdf955 --- /dev/null +++ b/lib/modules/settings/controllers/change_phone_controller.dart @@ -0,0 +1,261 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:fl_country_code_picker/fl_country_code_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:http/http.dart' as http; +import 'package:social_media_app/apis/providers/api_provider.dart'; +import 'package:social_media_app/apis/services/auth_service.dart'; +import 'package:social_media_app/constants/dimens.dart'; +import 'package:social_media_app/constants/strings.dart'; +import 'package:social_media_app/helpers/utils.dart'; +import 'package:social_media_app/modules/home/controllers/profile_controller.dart'; +import 'package:social_media_app/routes/route_management.dart'; + +class ChangePhoneController extends GetxController { + static ChangePhoneController get find => Get.find(); + + final _auth = AuthService.find; + final profile = ProfileController.find; + + final _apiProvider = ApiProvider(http.Client()); + + final _isLoading = false.obs; + final _otpSent = false.obs; + final _phone = ''.obs; + final _otp = ''.obs; + final FocusScopeNode focusNode = FocusScopeNode(); + CountryCode code = const CountryCode( + name: 'India', + code: 'IN', + dialCode: '+91', + ); + + /// Getters + bool get isLoading => _isLoading.value; + + bool get otpSent => _otpSent.value; + + String get phone => _phone.value; + + String get otp => _otp.value; + + /// Setters + set phone(String val) => _phone.value = val; + + set otp(String val) => _otp.value = val; + + void onChangeCountryCode(CountryCode code) { + code = code; + update(); + } + + void onChangePhone(String value) { + phone = value; + update(); + } + + void onChangeOtp(String value) { + otp = value; + update(); + } + + Future<void> _sendAddChangePhoneOtp() async { + if (_phone.value.isEmpty) { + AppUtils.showSnackBar( + StringValues.enterPhoneNo, + StringValues.warning, + ); + return; + } + + if (profile.profileDetails.user!.phone == phone) { + AppUtils.showSnackBar( + StringValues.enterDifferentPhoneNo, + StringValues.warning, + ); + return; + } + + final body = { + "phone": _phone.value, + "countryCode": code.dialCode, + }; + + AppUtils.printLog("Send Change Phone OTP Request"); + AppUtils.showLoadingDialog(); + _isLoading.value = true; + update(); + + try { + final response = + await _apiProvider.sendAddChangePhoneOtp(_auth.token, body); + + final decodedData = jsonDecode(utf8.decode(response.bodyBytes)); + + if (response.statusCode == 200) { + AppUtils.printLog("Send Change Phone OTP Success"); + AppUtils.closeDialog(); + _isLoading.value = false; + _otpSent.value = true; + update(); + AppUtils.showSnackBar( + decodedData[StringValues.message], + StringValues.success, + ); + } else { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Send Change Phone OTP Error"); + AppUtils.showSnackBar( + decodedData[StringValues.message], + StringValues.error, + ); + } + } on SocketException { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Send Change Phone OTP Error"); + AppUtils.printLog(StringValues.internetConnError); + AppUtils.showSnackBar(StringValues.internetConnError, StringValues.error); + } on TimeoutException { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Send Change Phone OTP Error"); + AppUtils.printLog(StringValues.connTimedOut); + AppUtils.showSnackBar(StringValues.connTimedOut, StringValues.error); + } on FormatException catch (e) { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Send Change Phone OTP Error"); + AppUtils.printLog(StringValues.formatExcError); + AppUtils.printLog(e); + AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); + } catch (exc) { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Send Change Phone OTP Error"); + AppUtils.printLog(StringValues.errorOccurred); + AppUtils.printLog(exc); + AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); + } + } + + Future<void> _addChangePhone() async { + if (_otp.value.isEmpty) { + AppUtils.showSnackBar( + StringValues.enterOtp, + StringValues.warning, + ); + return; + } + + if (_phone.value.isEmpty) { + AppUtils.showSnackBar( + StringValues.enterPhoneNo, + StringValues.warning, + ); + return; + } + + final body = { + 'otp': _otp.value, + "phone": _phone.value, + "countryCode": code.dialCode, + }; + + AppUtils.printLog("Change Phone Request"); + AppUtils.showLoadingDialog(); + _isLoading.value = true; + update(); + + try { + final response = await _apiProvider.addChangePhone(_auth.token, body); + + final decodedData = jsonDecode(utf8.decode(response.bodyBytes)); + + if (response.statusCode == 200) { + AppUtils.printLog("Change Phone Success"); + await profile.fetchProfileDetails(fetchPost: false); + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + RouteManagement.goToBack(); + AppUtils.showSnackBar( + decodedData[StringValues.message], + StringValues.success, + ); + } else { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Change Phone Error"); + AppUtils.showSnackBar( + decodedData[StringValues.message], + StringValues.error, + ); + } + } on SocketException { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Change Phone Error"); + AppUtils.printLog(StringValues.internetConnError); + AppUtils.showSnackBar(StringValues.internetConnError, StringValues.error); + } on TimeoutException { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Change Phone Error"); + AppUtils.printLog(StringValues.connTimedOut); + AppUtils.showSnackBar(StringValues.connTimedOut, StringValues.error); + } on FormatException catch (e) { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Change Phone Error"); + AppUtils.printLog(StringValues.formatExcError); + AppUtils.printLog(e); + AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); + } catch (exc) { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Change Phone Error"); + AppUtils.printLog(StringValues.errorOccurred); + AppUtils.printLog(exc); + AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); + } + } + + void showCountryCodePicker() async { + const countryPicker = FlCountryCodePicker(); + final code = await countryPicker.showPicker( + context: Get.context!, + fullScreen: false, + pickerMinHeight: Dimens.screenHeight * 0.5, + pickerMaxHeight: Dimens.screenHeight * 0.75, + initialSelectedLocale: 'IN', + ); + if (code != null) { + onChangeCountryCode(code); + } + } + + Future<void> addChangePhone() async { + AppUtils.closeFocus(); + await _addChangePhone(); + } + + Future<void> sendAddChangePhoneOtp() async { + AppUtils.closeFocus(); + await _sendAddChangePhoneOtp(); + } +} diff --git a/lib/modules/settings/views/pages/about_settings_view.dart b/lib/modules/settings/views/pages/about_settings_view.dart index 837cfc5..6fa6376 100644 --- a/lib/modules/settings/views/pages/about_settings_view.dart +++ b/lib/modules/settings/views/pages/about_settings_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:social_media_app/constants/assets.dart'; +import 'package:social_media_app/constants/colors.dart'; import 'package:social_media_app/constants/dimens.dart'; import 'package:social_media_app/constants/strings.dart'; import 'package:social_media_app/constants/styles.dart'; @@ -107,16 +108,41 @@ class AboutSettingsView extends StatelessWidget { ], ), const Spacer(), - Center( - child: ClipRRect( - borderRadius: BorderRadius.circular(Dimens.hundred), - child: NxAssetImage( - imgAsset: AssetValues.makeInIndia, - fit: BoxFit.cover, - width: Dimens.hundred, - height: Dimens.hundred, + Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Made with ❤️ by', + style: AppStyles.style14Normal.copyWith( + color: Theme.of(Get.context!).textTheme.subtitle1!.color, + ), ), - ), + Dimens.boxHeight4, + InkWell( + onTap: () => + AppUtils.openUrl(Uri.parse(StringValues.portfolioUrl)), + child: Text( + 'Nikhil Rajput', + style: AppStyles.style16Bold.copyWith( + color: ColorValues.primaryColor, + ), + ), + ), + Dimens.boxHeight16, + Center( + child: ClipRRect( + borderRadius: BorderRadius.circular(Dimens.hundred), + child: NxAssetImage( + imgAsset: AssetValues.makeInIndia, + fit: BoxFit.cover, + width: Dimens.sixtyFour, + height: Dimens.sixtyFour, + ), + ), + ), + ], ), Dimens.boxHeight16, ], diff --git a/lib/modules/settings/views/pages/account/change_email_view.dart b/lib/modules/settings/views/pages/account/change_email_view.dart new file mode 100644 index 0000000..a664d1b --- /dev/null +++ b/lib/modules/settings/views/pages/account/change_email_view.dart @@ -0,0 +1,154 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:social_media_app/constants/colors.dart'; +import 'package:social_media_app/constants/dimens.dart'; +import 'package:social_media_app/constants/strings.dart'; +import 'package:social_media_app/constants/styles.dart'; +import 'package:social_media_app/global_widgets/custom_app_bar.dart'; +import 'package:social_media_app/global_widgets/primary_filled_btn.dart'; +import 'package:social_media_app/modules/settings/controllers/change_email_controller.dart'; + +class ChangeEmailView extends StatelessWidget { + const ChangeEmailView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => FocusManager.instance.primaryFocus?.unfocus(), + child: Scaffold( + body: SafeArea( + child: SizedBox( + width: Dimens.screenWidth, + height: Dimens.screenHeight, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + NxAppBar( + title: StringValues.changeEmail, + padding: Dimens.edgeInsets8_16, + ), + Dimens.boxHeight24, + _buildBody(), + ], + ), + ), + ), + ), + ); + } + + Widget _buildBody() => GetBuilder<ChangeEmailController>( + builder: (logic) { + return Expanded( + child: SingleChildScrollView( + child: Padding( + padding: Dimens.edgeInsets0_16, + child: FocusScope( + node: logic.focusNode, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RichText( + text: TextSpan( + children: [ + const TextSpan( + text: 'Your current email is ', + ), + TextSpan( + text: logic.profile.profileDetails.user!.email, + style: AppStyles.style14Bold, + ), + const TextSpan( + text: + '. Enter the email you want to change with it.'), + ], + style: AppStyles.style14Normal.copyWith( + color: Theme.of(Get.context!) + .textTheme + .bodyText1! + .color, + ), + ), + ), + Dimens.boxHeight32, + Container( + height: Dimens.fiftySix, + constraints: + BoxConstraints(maxWidth: Dimens.screenWidth), + child: TextFormField( + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(Dimens.eight), + ), + hintStyle: AppStyles.style14Normal.copyWith( + color: ColorValues.grayColor, + ), + hintText: StringValues.email, + ), + enabled: logic.otpSent ? false : true, + keyboardType: TextInputType.emailAddress, + maxLines: 1, + style: AppStyles.style14Normal.copyWith( + color: Theme.of(Get.context!) + .textTheme + .bodyText1! + .color, + ), + controller: logic.emailTextController, + ), + ), + if (logic.otpSent) Dimens.boxHeight16, + if (logic.otpSent) + Container( + height: Dimens.fiftySix, + constraints: + BoxConstraints(maxWidth: Dimens.screenWidth), + child: TextFormField( + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: + BorderRadius.circular(Dimens.eight), + ), + hintStyle: AppStyles.style14Normal.copyWith( + color: ColorValues.grayColor, + ), + hintText: StringValues.otp, + ), + keyboardType: TextInputType.number, + maxLines: 1, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(6), + ], + style: AppStyles.style14Normal.copyWith( + color: Theme.of(Get.context!) + .textTheme + .bodyText1! + .color, + ), + controller: logic.otpTextController, + onEditingComplete: logic.focusNode.nextFocus, + ), + ), + Dimens.boxHeight40, + NxFilledButton( + onTap: logic.otpSent + ? logic.changeEmail + : logic.sendChangeEmailOtp, + label: logic.otpSent + ? StringValues.save.toUpperCase() + : StringValues.next.toUpperCase(), + ), + Dimens.boxHeight16, + ], + ), + ), + ), + ), + ); + }, + ); +} diff --git a/lib/modules/settings/views/pages/account/change_phone_view.dart b/lib/modules/settings/views/pages/account/change_phone_view.dart new file mode 100644 index 0000000..610d58f --- /dev/null +++ b/lib/modules/settings/views/pages/account/change_phone_view.dart @@ -0,0 +1,217 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:social_media_app/constants/colors.dart'; +import 'package:social_media_app/constants/dimens.dart'; +import 'package:social_media_app/constants/strings.dart'; +import 'package:social_media_app/constants/styles.dart'; +import 'package:social_media_app/global_widgets/custom_app_bar.dart'; +import 'package:social_media_app/global_widgets/primary_filled_btn.dart'; +import 'package:social_media_app/modules/settings/controllers/change_phone_controller.dart'; + +class ChangePhoneView extends StatelessWidget { + const ChangePhoneView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => FocusManager.instance.primaryFocus?.unfocus(), + child: Scaffold( + body: SafeArea( + child: SizedBox( + width: Dimens.screenWidth, + height: Dimens.screenHeight, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + NxAppBar( + title: StringValues.changePhone, + padding: Dimens.edgeInsets8_16, + ), + Dimens.boxHeight24, + _buildBody(), + ], + ), + ), + ), + ), + ); + } + + Widget _buildBody() => GetBuilder<ChangePhoneController>( + builder: (logic) { + return Expanded( + child: SingleChildScrollView( + child: Padding( + padding: Dimens.edgeInsets0_16, + child: FocusScope( + node: logic.focusNode, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (logic.profile.profileDetails.user!.phone != null) + RichText( + text: TextSpan( + children: [ + const TextSpan( + text: 'Your current phone number is ', + ), + TextSpan( + text: logic.profile.profileDetails.user!.phone, + style: AppStyles.style14Bold, + ), + const TextSpan( + text: + '. Enter the new phone number you want to change with it.'), + ], + style: AppStyles.style14Normal.copyWith( + color: Theme.of(Get.context!) + .textTheme + .bodyText1! + .color, + ), + ), + ) + else + RichText( + text: TextSpan( + children: const [ + TextSpan( + text: + 'Enter a phone number you want to add.'), + ], + style: AppStyles.style14Normal.copyWith( + color: Theme.of(Get.context!) + .textTheme + .bodyText1! + .color, + ), + ), + ), + Dimens.boxHeight32, + Row( + children: [ + InkWell( + onTap: logic.showCountryCodePicker, + child: Container( + height: Dimens.fiftySix, + padding: Dimens.edgeInsets0_8, + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(Get.context!).dividerColor, + ), + borderRadius: + BorderRadius.circular(Dimens.eight), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: Dimens.twentyFour, + height: Dimens.twentyFour, + child: logic.code.flagImage, + ), + Dimens.boxWidth8, + Text( + logic.code.dialCode, + style: AppStyles.style16Bold, + ), + ], + ), + ), + ), + Dimens.boxWidth16, + Expanded( + child: Container( + height: Dimens.fiftySix, + constraints: + BoxConstraints(maxWidth: Dimens.screenWidth), + child: TextFormField( + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: + BorderRadius.circular(Dimens.eight), + ), + hintStyle: AppStyles.style14Normal.copyWith( + color: ColorValues.grayColor, + ), + hintText: StringValues.phoneNo, + ), + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(12), + ], + enabled: logic.otpSent ? false : true, + keyboardType: TextInputType.phone, + maxLines: 1, + style: AppStyles.style14Normal.copyWith( + color: Theme.of(Get.context!) + .textTheme + .bodyText1! + .color, + ), + onChanged: (value) => + logic.onChangePhone(value), + onEditingComplete: () => + logic.focusNode.unfocus(), + ), + ), + ), + ], + ), + Dimens.boxHeight16, + if (logic.otpSent) Dimens.boxHeight16, + if (logic.otpSent) + Container( + height: Dimens.fiftySix, + constraints: + BoxConstraints(maxWidth: Dimens.screenWidth), + child: TextFormField( + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: + BorderRadius.circular(Dimens.eight), + ), + hintStyle: AppStyles.style14Normal.copyWith( + color: ColorValues.grayColor, + ), + hintText: StringValues.otp, + ), + keyboardType: TextInputType.number, + maxLines: 1, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(6), + ], + style: AppStyles.style14Normal.copyWith( + color: Theme.of(Get.context!) + .textTheme + .bodyText1! + .color, + ), + onChanged: (value) => logic.onChangeOtp(value), + onEditingComplete: () => logic.focusNode.unfocus(), + ), + ), + Dimens.boxHeight40, + NxFilledButton( + onTap: logic.otpSent + ? logic.addChangePhone + : logic.sendAddChangePhoneOtp, + label: logic.otpSent + ? StringValues.save.toUpperCase() + : StringValues.next.toUpperCase(), + ), + Dimens.boxHeight16, + ], + ), + ), + ), + ), + ); + }, + ); +} diff --git a/lib/modules/settings/views/pages/account_settings_view.dart b/lib/modules/settings/views/pages/account_settings_view.dart index 9929abf..e824ecd 100644 --- a/lib/modules/settings/views/pages/account_settings_view.dart +++ b/lib/modules/settings/views/pages/account_settings_view.dart @@ -6,6 +6,7 @@ import 'package:social_media_app/constants/styles.dart'; import 'package:social_media_app/extensions/string_extensions.dart'; import 'package:social_media_app/global_widgets/custom_app_bar.dart'; import 'package:social_media_app/global_widgets/custom_list_tile.dart'; +import 'package:social_media_app/routes/route_management.dart'; class AccountSettingsView extends StatelessWidget { const AccountSettingsView({Key? key}) : super(key: key); @@ -58,9 +59,12 @@ class AccountSettingsView extends StatelessWidget { color: Theme.of(Get.context!).textTheme.bodyText1!.color, ), title: Text( - StringValues.changeEmail.toTitleCase(), + StringValues.changeEmailAddress.toTitleCase(), style: AppStyles.style14Bold, ), + onTap: () => RouteManagement.goToVerifyPasswordView( + RouteManagement.goToChangeEmailSettingsView, + ), ), Dimens.divider, @@ -79,6 +83,9 @@ class AccountSettingsView extends StatelessWidget { StringValues.changePhoneNo.toTitleCase(), style: AppStyles.style14Bold, ), + onTap: () => RouteManagement.goToVerifyPasswordView( + RouteManagement.goToChangePhoneSettingsView, + ), ), Dimens.divider, diff --git a/lib/modules/settings/views/pages/theme_settings_view.dart b/lib/modules/settings/views/pages/theme_settings_view.dart index 8493dcf..29998be 100644 --- a/lib/modules/settings/views/pages/theme_settings_view.dart +++ b/lib/modules/settings/views/pages/theme_settings_view.dart @@ -50,12 +50,12 @@ class ThemeSettingsView extends StatelessWidget { topLeft: Radius.circular(Dimens.eight), topRight: Radius.circular(Dimens.eight), ), - onTap: () => logic.setThemeMode(appThemeModes.elementAt(0)), + onTap: () => logic.setThemeMode(AppThemeModes.system), onChanged: (value) { logic.setThemeMode(value); }, - title: appThemeModes.elementAt(0).toString(), - value: appThemeModes.elementAt(0).toString(), + title: StringValues.system.toString(), + value: AppThemeModes.system, groupValue: logic.themeMode, ), @@ -66,12 +66,12 @@ class ThemeSettingsView extends StatelessWidget { NxRadioTile( padding: Dimens.edgeInsets8, bgColor: Theme.of(Get.context!).dialogBackgroundColor, - onTap: () => logic.setThemeMode(appThemeModes.elementAt(1)), + onTap: () => logic.setThemeMode(AppThemeModes.light), onChanged: (value) { logic.setThemeMode(value); }, - title: appThemeModes.elementAt(1).toString(), - value: appThemeModes.elementAt(1).toString(), + title: StringValues.light.toString(), + value: AppThemeModes.light, groupValue: logic.themeMode, ), @@ -86,12 +86,12 @@ class ThemeSettingsView extends StatelessWidget { bottomLeft: Radius.circular(Dimens.eight), bottomRight: Radius.circular(Dimens.eight), ), - onTap: () => logic.setThemeMode(appThemeModes.elementAt(2)), + onTap: () => logic.setThemeMode(AppThemeModes.dark), onChanged: (value) { logic.setThemeMode(value); }, - title: appThemeModes.elementAt(2).toString(), - value: appThemeModes.elementAt(2).toString(), + title: StringValues.dark.toString(), + value: AppThemeModes.dark, groupValue: logic.themeMode, ), ], diff --git a/lib/modules/user/user_profile_view.dart b/lib/modules/user/user_profile_view.dart index b799e0f..2184fc4 100644 --- a/lib/modules/user/user_profile_view.dart +++ b/lib/modules/user/user_profile_view.dart @@ -16,7 +16,6 @@ import 'package:social_media_app/global_widgets/post_thumb_widget.dart'; import 'package:social_media_app/global_widgets/primary_outlined_btn.dart'; import 'package:social_media_app/global_widgets/primary_text_btn.dart'; import 'package:social_media_app/global_widgets/shimmer_loading.dart'; -import 'package:social_media_app/modules/home/controllers/profile_controller.dart'; import 'package:social_media_app/modules/user/user_details_controller.dart'; import 'package:social_media_app/routes/route_management.dart'; @@ -279,7 +278,6 @@ class UserProfileView extends StatelessWidget { Container _buildCountDetails(UserDetailsController logic) { final user = logic.userDetails.user; - final profile = ProfileController.find; return Container( width: Dimens.screenWidth, padding: Dimens.edgeInsets8_0, diff --git a/lib/modules/verify_password/verify_password_bindings.dart b/lib/modules/verify_password/verify_password_bindings.dart new file mode 100644 index 0000000..5234cc3 --- /dev/null +++ b/lib/modules/verify_password/verify_password_bindings.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import 'package:social_media_app/modules/verify_password/verify_password_controller.dart'; + +class VerifyPasswordBinding implements Bindings { + @override + void dependencies() { + Get.lazyPut(VerifyPasswordController.new); + } +} diff --git a/lib/modules/verify_password/verify_password_controller.dart b/lib/modules/verify_password/verify_password_controller.dart new file mode 100644 index 0000000..f67488a --- /dev/null +++ b/lib/modules/verify_password/verify_password_controller.dart @@ -0,0 +1,113 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/widgets.dart'; +import 'package:get/get.dart'; +import 'package:http/http.dart' as http; +import 'package:social_media_app/apis/providers/api_provider.dart'; +import 'package:social_media_app/apis/services/auth_service.dart'; +import 'package:social_media_app/constants/strings.dart'; +import 'package:social_media_app/helpers/utils.dart'; +import 'package:social_media_app/routes/route_management.dart'; + +class VerifyPasswordController extends GetxController { + static VerifyPasswordController get find => Get.find(); + + final _auth = AuthService.find; + + final _apiProvider = ApiProvider(http.Client()); + + final passwordTextController = TextEditingController(); + + final FocusScopeNode focusNode = FocusScopeNode(); + + final _isLoading = false.obs; + final _showPassword = true.obs; + + /// Getters + bool get isLoading => _isLoading.value; + + bool get showPassword => _showPassword.value; + + void toggleViewPassword() { + _showPassword(!_showPassword.value); + update(); + } + + Future<void> _verifyPassword(String password) async { + if (password.isEmpty) { + AppUtils.showSnackBar( + StringValues.enterPassword, + StringValues.warning, + ); + return; + } + + var cb = Get.arguments; + + AppUtils.printLog("Verify Password Request"); + AppUtils.showLoadingDialog(); + _isLoading.value = true; + update(); + + try { + final response = await _apiProvider.verifyPassword(_auth.token, password); + + final decodedData = jsonDecode(utf8.decode(response.bodyBytes)); + + if (response.statusCode == 200) { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Verify Password Success"); + RouteManagement.goToBack(); + cb(); + } else { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Verify Password Error"); + AppUtils.showSnackBar( + decodedData[StringValues.message], + StringValues.error, + ); + } + } on SocketException { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Verify Password Error"); + AppUtils.printLog(StringValues.internetConnError); + AppUtils.showSnackBar(StringValues.internetConnError, StringValues.error); + } on TimeoutException { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Verify Password Error"); + AppUtils.printLog(StringValues.connTimedOut); + AppUtils.showSnackBar(StringValues.connTimedOut, StringValues.error); + } on FormatException catch (e) { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Verify Password Error"); + AppUtils.printLog(StringValues.formatExcError); + AppUtils.printLog(e); + AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); + } catch (exc) { + AppUtils.closeDialog(); + _isLoading.value = false; + update(); + AppUtils.printLog("Verify Password Error"); + AppUtils.printLog(StringValues.errorOccurred); + AppUtils.printLog(exc); + AppUtils.showSnackBar(StringValues.errorOccurred, StringValues.error); + } + } + + Future<void> verifyPassword() async { + AppUtils.closeFocus(); + await _verifyPassword(passwordTextController.text.trim()); + } +} diff --git a/lib/modules/verify_password/verify_password_view.dart b/lib/modules/verify_password/verify_password_view.dart new file mode 100644 index 0000000..0ddac60 --- /dev/null +++ b/lib/modules/verify_password/verify_password_view.dart @@ -0,0 +1,103 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:social_media_app/constants/colors.dart'; +import 'package:social_media_app/constants/dimens.dart'; +import 'package:social_media_app/constants/strings.dart'; +import 'package:social_media_app/constants/styles.dart'; +import 'package:social_media_app/global_widgets/custom_app_bar.dart'; +import 'package:social_media_app/global_widgets/primary_filled_btn.dart'; +import 'package:social_media_app/modules/verify_password/verify_password_controller.dart'; + +class VerifyPasswordView extends StatelessWidget { + const VerifyPasswordView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => FocusManager.instance.primaryFocus?.unfocus(), + child: Scaffold( + body: SafeArea( + child: SizedBox( + width: Dimens.screenWidth, + height: Dimens.screenHeight, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + NxAppBar( + title: StringValues.verifyPassword, + padding: Dimens.edgeInsets8_16, + ), + Dimens.boxHeight24, + _buildBody(), + ], + ), + ), + ), + ), + ); + } + + Widget _buildBody() => GetBuilder<VerifyPasswordController>( + builder: (logic) { + return Expanded( + child: SingleChildScrollView( + child: Padding( + padding: Dimens.edgeInsets0_16, + child: FocusScope( + node: logic.focusNode, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + height: Dimens.fiftySix, + constraints: + BoxConstraints(maxWidth: Dimens.screenWidth), + child: TextFormField( + obscureText: logic.showPassword, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(Dimens.eight), + ), + hintStyle: AppStyles.style14Normal.copyWith( + color: ColorValues.grayColor, + ), + hintText: StringValues.currentPassword, + suffixIcon: InkWell( + onTap: logic.toggleViewPassword, + child: Icon( + logic.showPassword + ? CupertinoIcons.eye + : CupertinoIcons.eye_slash, + ), + ), + ), + keyboardType: TextInputType.visiblePassword, + maxLines: 1, + style: AppStyles.style14Normal.copyWith( + color: Theme.of(Get.context!) + .textTheme + .bodyText1! + .color, + ), + controller: logic.passwordTextController, + onEditingComplete: logic.focusNode.nextFocus, + ), + ), + Dimens.boxHeight40, + NxFilledButton( + onTap: () => logic.verifyPassword(), + label: StringValues.next.toUpperCase(), + ), + Dimens.boxHeight16, + ], + ), + ), + ), + ), + ); + }, + ); +} diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index bd0f8ff..414b65d 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -31,18 +31,24 @@ import 'package:social_media_app/modules/profile/bindings/edit_name_binding.dart import 'package:social_media_app/modules/profile/bindings/edit_profession_binding.dart'; import 'package:social_media_app/modules/profile/bindings/edit_profile_picture_binding.dart'; import 'package:social_media_app/modules/profile/bindings/edit_username_binding.dart'; +import 'package:social_media_app/modules/profile/bindings/edit_website_binding.dart'; import 'package:social_media_app/modules/profile/views/edit_views/edit_about_view.dart'; import 'package:social_media_app/modules/profile/views/edit_views/edit_dob_view.dart'; import 'package:social_media_app/modules/profile/views/edit_views/edit_gender_view.dart'; import 'package:social_media_app/modules/profile/views/edit_views/edit_name_view.dart'; import 'package:social_media_app/modules/profile/views/edit_views/edit_profession_view.dart'; import 'package:social_media_app/modules/profile/views/edit_views/edit_username_view.dart'; +import 'package:social_media_app/modules/profile/views/edit_views/edit_website_view.dart'; import 'package:social_media_app/modules/profile/views/profile_details_view.dart'; +import 'package:social_media_app/modules/settings/bindings/change_email_binding.dart'; import 'package:social_media_app/modules/settings/bindings/change_password_binding.dart'; +import 'package:social_media_app/modules/settings/bindings/change_phone_binding.dart'; import 'package:social_media_app/modules/settings/bindings/login_device_info_binding.dart'; import 'package:social_media_app/modules/settings/bindings/privacy_settings_binding.dart'; import 'package:social_media_app/modules/settings/bindings/setting_bindings.dart'; import 'package:social_media_app/modules/settings/views/pages/about_settings_view.dart'; +import 'package:social_media_app/modules/settings/views/pages/account/change_email_view.dart'; +import 'package:social_media_app/modules/settings/views/pages/account/change_phone_view.dart'; import 'package:social_media_app/modules/settings/views/pages/account_settings_view.dart'; import 'package:social_media_app/modules/settings/views/pages/help_settings_view.dart'; import 'package:social_media_app/modules/settings/views/pages/privacy/account_privacy_view.dart'; @@ -54,6 +60,8 @@ import 'package:social_media_app/modules/settings/views/pages/theme_settings_vie import 'package:social_media_app/modules/settings/views/settings_view.dart'; import 'package:social_media_app/modules/user/user_details_binding.dart'; import 'package:social_media_app/modules/user/user_profile_view.dart'; +import 'package:social_media_app/modules/verify_password/verify_password_bindings.dart'; +import 'package:social_media_app/modules/verify_password/verify_password_view.dart'; import 'package:social_media_app/modules/welcome/welcome_view.dart'; part 'app_routes.dart'; @@ -183,6 +191,14 @@ abstract class AppPages { transition: defaultTransition, ), + GetPage( + name: _Routes.editWebsite, + page: EditWebsiteView.new, + binding: EditWebsiteBinding(), + transitionDuration: transitionDuration, + transition: defaultTransition, + ), + /// ------------------------------------------------------------------------ /// Post Pages ------------------------------------------------------------- @@ -310,6 +326,26 @@ abstract class AppPages { /// ------------------------------------------------------------------------ + /// ACCOUNT SETTINGS ------------------------------------------------------- + + GetPage( + name: _Routes.changeEmailSettings, + page: ChangeEmailView.new, + transitionDuration: transitionDuration, + binding: ChangeEmailBinding(), + transition: defaultTransition, + ), + + GetPage( + name: _Routes.changePhoneSettings, + page: ChangePhoneView.new, + transitionDuration: transitionDuration, + binding: ChangePhoneBinding(), + transition: defaultTransition, + ), + + /// ------------------------------------------------------------------------ + /// SECURITY SETTINGS ----------------------------------------------------- GetPage( @@ -343,6 +379,7 @@ abstract class AppPages { /// ------------------------------------------------------------------------ /// App Update ------------------------------------------------------------- + GetPage( name: _Routes.appUpdate, page: AppUpdateView.new, @@ -351,5 +388,17 @@ abstract class AppPages { ), /// ------------------------------------------------------------------------ + + /// App Update ------------------------------------------------------------- + + GetPage( + name: _Routes.verifyPassword, + page: VerifyPasswordView.new, + binding: VerifyPasswordBinding(), + transitionDuration: transitionDuration, + transition: defaultTransition, + ), + + /// ------------------------------------------------------------------------ ]; } diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 4fcbcea..37373f2 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -37,6 +37,7 @@ abstract class AppRoutes { static const verifyAccount = _Routes.verifyAccount; static const sendVerifyEmailOtp = _Routes.sendVerifyEmailOtp; static const verifyEmail = _Routes.verifyEmail; + static const verifyPassword = _Routes.verifyPassword; static const editProfile = _Routes.editProfile; static const editName = _Routes.editName; @@ -103,6 +104,7 @@ abstract class _Routes { static const verifyAccount = '/verify_account'; static const sendVerifyEmailOtp = '/send_email_verification_otp'; static const verifyEmail = '/verify_email'; + static const verifyPassword = '/verify_password'; static const editProfile = '/edit_profile'; static const editName = '/edit_name'; diff --git a/lib/routes/route_management.dart b/lib/routes/route_management.dart index f344015..c3d35a4 100644 --- a/lib/routes/route_management.dart +++ b/lib/routes/route_management.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; import 'package:social_media_app/apis/models/entities/post.dart'; import 'package:social_media_app/modules/follower/controllers/followers_list_controller.dart'; @@ -75,6 +76,10 @@ abstract class RouteManagement { Get.toNamed(AppRoutes.editProfession); } + static void goToEditWebsiteView() { + Get.toNamed(AppRoutes.editWebsite); + } + /// -------------------------------------------------------------------------- /// Profile & User ----------------------------------------------------------- @@ -148,10 +153,14 @@ abstract class RouteManagement { /// -------------------------------------------------------------------------- - /// Go to App Update View ---------------------------------------------------- + /// Account Settings Pages -------------------------------------------------- - static void goToAppUpdateView() { - Get.offAllNamed(AppRoutes.appUpdate); + static void goToChangeEmailSettingsView() { + Get.toNamed(AppRoutes.changeEmailSettings); + } + + static void goToChangePhoneSettingsView() { + Get.toNamed(AppRoutes.changePhoneSettings); } /// -------------------------------------------------------------------------- @@ -176,6 +185,22 @@ abstract class RouteManagement { /// -------------------------------------------------------------------------- + /// Go to App Update View ---------------------------------------------------- + + static void goToAppUpdateView() { + Get.offAllNamed(AppRoutes.appUpdate); + } + + /// -------------------------------------------------------------------------- + + /// Go to Verify Password View ----------------------------------------------- + + static void goToVerifyPasswordView(VoidCallback cb) { + Get.toNamed(AppRoutes.verifyPassword, arguments: cb); + } + + /// -------------------------------------------------------------------------- + /// Go to back Page / Close Pages -------------------------------------------- static void goToBack() { diff --git a/lib/translations/app_translations.dart b/lib/translations/app_translations.dart new file mode 100644 index 0000000..07e45bc --- /dev/null +++ b/lib/translations/app_translations.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:social_media_app/translations/en_US/en_us_translations.dart'; +import 'package:social_media_app/translations/hi_IN/hi_in_translations.dart'; + +class AppTranslation extends Translations { + @override + Map<String, Map<String, String>> get keys => { + 'en': enUs, + 'hi': hiIn, + }; +} diff --git a/lib/translations/en_US/en_us_translations.dart b/lib/translations/en_US/en_us_translations.dart new file mode 100644 index 0000000..872c6f8 --- /dev/null +++ b/lib/translations/en_US/en_us_translations.dart @@ -0,0 +1,4 @@ +final Map<String, String> enUs = { + 'hi': 'Hi', + 'app_name': 'Rippl', +}; diff --git a/lib/translations/hi_IN/hi_in_translations.dart b/lib/translations/hi_IN/hi_in_translations.dart new file mode 100644 index 0000000..e4517ef --- /dev/null +++ b/lib/translations/hi_IN/hi_in_translations.dart @@ -0,0 +1,4 @@ +final Map<String, String> hiIn = { + 'hi': 'Hi', + 'app_name': 'Rippl', +}; diff --git a/pubspec.lock b/pubspec.lock index f3fc262..4420699 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -225,13 +225,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.2" - country_code_picker: - dependency: "direct main" - description: - name: country_code_picker - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.2" cross_file: dependency: transitive description: @@ -372,6 +365,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" + fl_country_code_picker: + dependency: "direct main" + description: + name: fl_country_code_picker + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.3" flutter: dependency: "direct main" description: flutter @@ -397,7 +397,7 @@ packages: name: flutter_carousel_widget url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" flutter_datetime_picker: dependency: "direct main" description: @@ -460,7 +460,7 @@ packages: name: flutter_native_splash url: "https://pub.dartlang.org" source: hosted - version: "2.2.8" + version: "2.2.9" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -582,7 +582,7 @@ packages: name: get_time_ago url: "https://pub.dartlang.org" source: hosted - version: "1.1.5" + version: "1.1.6" glob: dependency: transitive description: @@ -786,13 +786,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" - modal_bottom_sheet: - dependency: transitive - description: - name: modal_bottom_sheet - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" octo_image: dependency: transitive description: @@ -1024,6 +1017,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.27.5" + scrollable_positioned_list: + dependency: transitive + description: + name: scrollable_positioned_list + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.3" share: dependency: "direct main" description: @@ -1218,13 +1218,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.4" - universal_platform: - dependency: transitive - description: - name: universal_platform - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0+1" url_launcher: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 5a0ed01..a86963a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: A social media app using Flutter, Dart, GetX, and REST API that all publish_to: "none" -version: 1.0.1+05 +version: 1.0.1+06 environment: sdk: ">=2.17.0 <3.0.0" @@ -23,12 +23,12 @@ dependencies: image_cropper: ^3.0.0 cached_network_image: ^3.2.2 photo_view: ^0.14.0 - flutter_carousel_widget: ^1.1.0 + flutter_carousel_widget: ^1.2.0 connectivity: ^3.0.6 http: ^0.13.5 intl: ^0.17.0 - get_time_ago: ^1.1.5 + get_time_ago: ^1.1.6 cloudinary: ^1.0.0 r_upgrade: ^0.3.8+2 @@ -49,7 +49,7 @@ dependencies: permission_handler: ^10.0.0 pattern_formatter: ^2.0.0 flutter_datetime_picker: ^1.5.1 - country_code_picker: ^2.0.2 + fl_country_code_picker: ^0.0.3 get: ^4.6.5 get_storage: ^2.0.3 @@ -63,7 +63,7 @@ dev_dependencies: flutter_lints: ^2.0.1 flutter_launcher_icons: ^0.10.0 - flutter_native_splash: ^2.2.8 + flutter_native_splash: ^2.2.9 build_runner: ^2.2.0 json_serializable: ^6.3.1 diff --git a/web/index.html b/web/index.html index 53a6d0b..b07cc38 100644 --- a/web/index.html +++ b/web/index.html @@ -1,6 +1,4 @@ -<!DOCTYPE html> -<html> -<head> +<!DOCTYPE html><html><head> <!-- If you are serving your web app in a path other than the root, change the href value below to reflect the base path you are serving from. @@ -27,20 +25,21 @@ <link rel="apple-touch-icon" href="icons/Icon-192.png"> <!-- Favicon --> - <link rel="icon" type="image/png" href="favicon.png"/> + <link rel="icon" type="image/png" href="favicon.png"> <title>social_media_app</title> <link rel="manifest" href="manifest.json"> <script src="splash/splash.js"></script> - <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/> + <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <link rel="stylesheet" type="text/css" href="splash/style.css"> </head> -<body> - <picture id="splash"> - <source srcset="splash/img/light-1x.png 1x, splash/img/light-2x.png 2x, splash/img/light-3x.png 3x, splash/img/light-4x.png 4x" media="(prefers-color-scheme: light)"> - <source srcset="splash/img/dark-1x.png 1x, splash/img/dark-2x.png 2x, splash/img/dark-3x.png 3x, splash/img/dark-4x.png 4x" media="(prefers-color-scheme: dark)"> - <img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt=""/> +<body> <picture id="splash"> + <source srcset="splash/img/light-1x.png 1x, splash/img/light-2x.png 2x, splash/img/light-3x.png 3x, splash/img/light-4x.png 4x" media="(prefers-color-scheme: light)"> + <source srcset="splash/img/dark-1x.png 1x, splash/img/dark-2x.png 2x, splash/img/dark-3x.png 3x, splash/img/dark-4x.png 4x" media="(prefers-color-scheme: dark)"> + <img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt=""> </picture> + + <!-- This script installs service_worker.js to provide PWA functionality to application. For more information, see: https://developers.google.com/web/fundamentals/primers/service-workers --> @@ -108,5 +107,6 @@ loadMainDartJs(); } </script> -</body> -</html> + + +</body></html> \ No newline at end of file diff --git a/web/splash/style.css b/web/splash/style.css index 99491bb..6439e37 100644 --- a/web/splash/style.css +++ b/web/splash/style.css @@ -1,7 +1,7 @@ body { margin:0; height:100%; - background: #F0F0F0; + background: #ECECEC; background-size: 100% 100%; } @@ -56,7 +56,7 @@ body { body { margin:0; height:100%; - background: #1C1D31; + background: #12121E; background-size: 100% 100%; }