diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..50defe410 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [palisadoes] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: PalisadoesFoundation/talawa +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..ccbb9c4d8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,29 @@ +--- +name: Bug report +about: Create a report to help us improve. +title: Bug report +labels: Bug +assignees: "" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. +2. +3. +4. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Actual behavior** +A clear and concise description of how the code performed w.r.t expectations. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional details** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..1c93611c4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: Feature request +labels: Feature +assignees: "" +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Approach to be followed (optional)** +A clear and concise description of approach to be followed. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PR_TEMPLATE/pr-template.md b/.github/PR_TEMPLATE/pr-template.md new file mode 100644 index 000000000..0401cd75c --- /dev/null +++ b/.github/PR_TEMPLATE/pr-template.md @@ -0,0 +1,8 @@ +- **What kind of change does this PR introduce:** +- **Issue Number:** +- **Did you add tests for your code?** +- **Snapshots/Videos:** +- **Does this PR introduce a breaking change?** +- **Other information:** +- **Have you read the [contributing guide](https://github.com/PalisadoesFoundation/talawa/blob/master/CONTRIBUTING.md)?** + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..a944fcc75 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,51 @@ + + + + +**What kind of change does this PR introduce?** + + + +**Issue Number:** + +Fixes # + +**Did you add tests for your changes?** + + + +**Snapshots/Videos:** + + + +**If relevant, did you update the documentation?** + + + +**Summary** + + + + +**Does this PR introduce a breaking change?** + + + +**Other information** + + + +**Have you read the [contributing guide](https://github.com/PalisadoesFoundation/talawa/blob/master/CONTRIBUTING.md)?** + + diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 000000000..5a6126b23 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,22 @@ +# Talawa GitHub Workflows Guidelines + +Here are some contributing guidelines for this directory. + +## YAML Files + +Follow these guidelines when creating new YAML defined GitHub actions. + +1. Place all actions related to issues in the `issues.yml` file. +1. Place all actions related to pull requests in the `pull-requests.yml` file. + +## Scripts + +Follow these guidelines when creating or modifying scripts in this directory. + +1. All scripts in this directory must be written in python3 for consistency. +1. The python3 scripts must follow the following coding standards. Run these commands against your scripts before submitting PRs that modify or create python3 scripts in this directory. + 1. Pycodestyle + 1. Pydocstyle + 1. Pylint + 1. Flake8 +1. All scripts must run a main() function. diff --git a/.github/workflows/countline.py b/.github/workflows/countline.py new file mode 100755 index 000000000..396bb5fba --- /dev/null +++ b/.github/workflows/countline.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +"""Script to encourage more efficient coding practices. + +Methodology: + + Analyses the `lib` and `test` directories to find files that exceed a + pre-defined number of lines of code. + + This script was created to help improve code quality by encouraging + contributors to create reusable code. + +NOTE: + + This script complies with our python3 coding and documentation standards + and should be used as a reference guide. It complies with: + + 1) Pylint + 2) Pydocstyle + 3) Pycodestyle + 4) Flake8 + + Run these commands from the CLI to ensure the code is compliant for all + your pull requests. + +""" + +# Standard imports +import os +import sys +import argparse + + +def _excluded_filepaths(excludes): + """Create a list of full file paths to exclude from the analysis. + + Args: + excludes: List of files to exclude + + Returns: + result: A list of full file paths + + """ + # Initialize key variables + result = [] + + if bool(excludes) is True: + for filename in excludes: + # Ignore files that appear to be full paths because they start + # with a '/' or whatever the OS uses to distinguish directories + if filename.startswith(os.sep): + result.append(filename) + continue + + # Create a file path + filepath = '{}{}{}'.format(os.getcwd(), os.sep, filename) + if os.path.isfile(filepath) is True: + result.append(filepath) + # Return + return result + + +def _arg_parser_resolver(): + """Resolve the CLI arguments provided by the user. + + Args: + None + + Returns: + result: Parsed argument object + + """ + # Initialize parser and add the CLI options we should expect + parser = argparse.ArgumentParser() + parser.add_argument('--lines', type=int, required=False, default=300, + help='The maximum number of lines of code to accept.') + parser.add_argument('--directory', type=str, required=False, + default=os.getcwd(), + help='The parent directory of files to analyze.') + parser.add_argument('--exclude', type=str, required=False, + nargs='*', + default=None, + const=None, + help='''\ +An optional list of files to exclude from the analysis separated by spaces.''') + + # Return parser + result = parser.parse_args() + # print(result.exclude) + # sys.exit(0) + return result + + +def main(): + """Analyze dart files. + + This function finds, and prints the files that exceed the CLI + defined defaults. + + Args: + None + + Returns: + None + + """ + # Initialize key variables + lookup = {} + errors_found = False + file_count = 0 + excluded_filepaths = [] + + # Get the CLI arguments + args = _arg_parser_resolver() + + # Define the directories of interest + directories = [ + os.path.expanduser(os.path.join(args.directory, 'lib')), + os.path.expanduser(os.path.join(args.directory, 'test')) + ] + + # Get a corrected list of filenames to exclude + excluded_filepaths = _excluded_filepaths(args.exclude) + + # Iterate and analyze each directory + for directory in directories: + for root, _, files in os.walk(directory, topdown=False): + for name in files: + # Read each file and count the lines found + filepath = os.path.join(root, name) + + # Skip excluded files + if bool(args.exclude) is True: + if filepath in excluded_filepaths: + continue + + # Process the rest + with open(filepath) as code: + line_count = sum( + 1 for line in code + if line.strip() and not line.startswith('#') + ) + lookup[filepath] = line_count + + # If the line rule is voilated then the value is changed to 1 + for filepath, line_count in lookup.items(): + if line_count > args.lines: + errors_found = True + file_count += 1 + if file_count == 1: + print(''' +LINE COUNT ERROR: Files with excessive lines of code have been found\n''') + + print(' Line count: {:>5} File: {}'.format(line_count, filepath)) + + # Evaluate and exit + if bool(errors_found) is True: + print(''' +The {} files listed above have more than {} lines of code. + +Please fix this. It is a pre-requisite for pull request approval. +'''.format(file_count, args.lines)) + sys.exit(1) + else: + sys.exit(0) + + +if __name__ == '__main__': + main() diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml new file mode 100644 index 000000000..9b5a2594b --- /dev/null +++ b/.github/workflows/issues.yml @@ -0,0 +1,23 @@ +name: Issues Workflow +on: + issues: + types: ['opened'] +jobs: + Opened-issue-label: + name: Adding Issue Label + runs-on: ubuntu-latest + steps: + - uses: Renato66/auto-label@v2.2.0 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + ignore-comments: true + default-labels: '["unapproved"]' + + Issue-Greeting: + name: Greeting Message to User + runs-on: ubuntu-latest + steps: + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + issue-message: "Congratulations on making your first Issue! :confetti_ball: If you haven't already, check out our [Contributing Guidelines](https://github.com/PalisadoesFoundation/talawa/blob/master/CONTRIBUTING.md) and [Issue Reporting Guidelines](https://github.com/PalisadoesFoundation/talawa/blob/master/issue-guidelines.md) to ensure that you are following our guidelines for contributing and making issues." \ No newline at end of file diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 000000000..409236a59 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,81 @@ +name: PR Workflow +on: [pull_request] + +jobs: + PR-Greeting: + name: Greeting Message to user + runs-on: ubuntu-latest + steps: + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + pr-message: "Congratulations on making your first PR! :confetti_ball: If you haven't already, check out our [Contributing Guidelines](https://github.com/PalisadoesFoundation/talawa/blob/master/CONTRIBUTING.md) and [PR Reporting Guidelines](https://github.com/PalisadoesFoundation/talawa/blob/master/PR-guidelines.md) to ensure that you are following our guidelines for contributing and creating PR." + + Linter: + name: Checking if code formatting is done correctly + runs-on: ubuntu-latest + needs: PR-Greeting + steps: + - uses: actions/checkout@v2 + - uses: axel-op/dart-package-analyzer@v3 + with: + githubToken: ${{ secrets.GITHUB_TOKEN }} + - uses: subosito/flutter-action@v1 + with: + channel: 'beta' + - run: flutter format --set-exit-if-changed . + + Flutter-analyze: + name: Analyzing flutter code + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: axel-op/dart-package-analyzer@v3 + with: + githubToken: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/setup-java@v1 + with: + java-version: '12.x' + - uses: subosito/flutter-action@v1 + with: + channel: 'stable' + flutter-version: '2.2.1' + - run: flutter pub get + - run: flutter analyze --no-pub + + Count-lines-of-code: + name: Total number of lines in every file should not be more than 300 + needs: Linter + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: chmod +x ./.github/workflows/countline.py + - run: ./.github/workflows/countline.py --exclude lib/custom_painters/talawa_logo.dart lib/utils/queries.dart test/widget_tests/set_url_page_test.dart lib/view_model/pre_auth_view_models/select_organization_view_model.dart + + Trailing-Comma-Pattern: + name: Trailing comma added at the end of every required code + needs: Linter + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: chmod +x ./.github/workflows/trailing_commas.py + - run: ./.github/workflows/trailing_commas.py + + Coverage-Report: + name: Generate coverage report + needs: Linter + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Sets up a Flutter environment + uses: subosito/flutter-action@v1 + with: + channel: 'beta' + - name: Generates LCOV file + run: flutter test --coverage + - name: Report code coverage + uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 + with: + path: "./coverage/lcov.info" + min_coverage: 3 diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml new file mode 100644 index 000000000..749a2e148 --- /dev/null +++ b/.github/workflows/push.yaml @@ -0,0 +1,81 @@ +name: PUSH Workflow +on: [push] + +jobs: + PR-Greeting: + name: Greeting Message to user + runs-on: ubuntu-latest + steps: + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + pr-message: "Congratulations on making your first PR! :confetti_ball: If you haven't already, check out our [Contributing Guidelines](https://github.com/PalisadoesFoundation/talawa/blob/master/CONTRIBUTING.md) and [PR Reporting Guidelines](https://github.com/PalisadoesFoundation/talawa/blob/master/PR-guidelines.md) to ensure that you are following our guidelines for contributing and creating PR." + + Linter: + name: Checking if code formatting is done correctly + runs-on: ubuntu-latest + needs: PR-Greeting + steps: + - uses: actions/checkout@v2 + - uses: axel-op/dart-package-analyzer@v3 + with: + githubToken: ${{ secrets.GITHUB_TOKEN }} + - uses: subosito/flutter-action@v1 + with: + channel: 'beta' + - run: flutter format --set-exit-if-changed . + + Flutter-analyze: + name: Analyzing flutter code + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: axel-op/dart-package-analyzer@v3 + with: + githubToken: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/setup-java@v1 + with: + java-version: '12.x' + - uses: subosito/flutter-action@v1 + with: + channel: 'stable' + flutter-version: '2.2.1' + - run: flutter pub get + - run: flutter analyze --no-pub + + Count-lines-of-code: + name: Total number of lines in every file should not be more than 300 + needs: Linter + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: chmod +x ./.github/workflows/countline.py + - run: ./.github/workflows/countline.py --exclude lib/custom_painters/talawa_logo.dart lib/utils/queries.dart test/widget_tests/set_url_page_test.dart lib/view_model/pre_auth_view_models/select_organization_view_model.dart + + Trailing-Comma-Pattern: + name: Trailing comma added at the end of every required code + needs: Linter + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: chmod +x ./.github/workflows/trailing_commas.py + - run: ./.github/workflows/trailing_commas.py + + Coverage-Report: + name: Generate coverage report + needs: Linter + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Sets up a Flutter environment + uses: subosito/flutter-action@v1 + with: + channel: 'beta' + - name: Generates LCOV file + run: flutter test --coverage + - name: Report code coverage + uses: VeryGoodOpenSource/very_good_coverage@v1.1.1 + with: + path: "./coverage/lcov.info" + min_coverage: 3 \ No newline at end of file diff --git a/.github/workflows/trailing_commas.py b/.github/workflows/trailing_commas.py new file mode 100644 index 000000000..51e1b35f2 --- /dev/null +++ b/.github/workflows/trailing_commas.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# -*- coding=UTF-8 -*- +"""This is a prettify script so that flutter formatting can work efficiently. + +Methodology: + Analyses the `lib` and `test` directories to go through the code line by + line and add commas if the syntax matches the conditions. + This script was created to help improve code quality by making it prettier + and more readable. + +NOTE: + This script complies with our python3 coding and documentation standards + and should be used as a reference guide. It complies with: + 1) Pylint + 2) Pydocstyle + 3) Pycodestyle + Run these commands from the CLI to ensure the code is compliant for all + your pull requests if you want to contribute to this file. + +""" + +# Standard imports +import os +import sys +import argparse + + +def arg_parser_resolver(): + """Resolve the CLI arguments provided by the user. + + Args: + None + + Returns: + result: Parsed argument object + + """ + parser = argparse.ArgumentParser( + description='for parsing across the directory') + parser.add_argument('--dir', type=str, required=False, default=os.getcwd(), + help='directory-location where files are present') + return parser.parse_args() + + +def syntax_matcher(root: str, files: list): + """Pattern matching and adding trailing commas at the required places. + + Args: + root : Name of the root directory + files : List of all the files'(including sub-folders) paths + + Returns: + None + """ + for name in files: + + file_location = os.path.join(root, name) + data = [] + with open(file_location, 'r') as read_loc: + data = read_loc.readlines() + with open(file_location, 'w') as write_loc: + + for index in range(0, len(data)): + # genral case of addition of commas + if '))' and not ')) {' in data[index]: + data[index] = data[index].replace('))', '),)') + + # for cases of function declaration without any parameters + if '(,)' in data[index]: + data[index] = data[index].replace('(,)', '()') + + # for commas already exist and formatting is already done + if ',));' in data[index]: + data[index] = data[index].replace(',));', '));') + + # for the unique exception occuring in grops_controller.dart + # since a conditional operator means it is an IF line + # and adding an trailing comma will cause an error + if '==' or '!=' in data[index]: + data[index] = data[index].replace('(),)', '())') + + # for removal of comma at the start of function call + if '),).' in data[index]: + data[index] = data[index].replace('),).', ')).') + write_loc.write(data[index]) + + +def main(): + """Find, and update, for files having comma in trailing sequence. + + Args: + None + + Returns: + None + + """ + args = arg_parser_resolver() + # libPath and testPath dir location + lib_path = os.path.expanduser(os.path.join(args.dir, 'lib')) + test_path = os.path.expanduser(os.path.join(args.dir, 'test')) + # counting lines in lib and test + for root, _, files in os.walk(lib_path, topdown=False): + syntax_matcher(root, files) + for root, _, files in os.walk(test_path, topdown=False): + syntax_matcher(root, files) + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..992bc99f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,191 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.packages +.pub-cache/ +.pub/ +/build/ +.flutter-plugins-dependencies + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +# Sphinx documentation +docs/_build/ + + +############################################################################### +# Generic Python Exclusions (Provided by GitHub) +# +# Created by https://www.gitignore.io/api/python +# Edit at https://www.gitignore.io/?templates=python +############################################################################### + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# End of https://www.gitignore.io/api/python +.flutter-plugins-dependencies +pubspec.lock diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000..36ddd6bf7 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,22 @@ +# File: .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF and ePub +formats: all + +# Optionally set the version of Python and requirements required to build docs +# We have to set this or else ReadTheDocs will attempt to install pip packages +# from pip_requirements that have system library dependencies that ReadTheDocs +# does not have +python: + version: 3.6 + install: + - requirements: docs/requirements.txt diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..cc0cc31f1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,39 @@ +{ + "language": "dart", + "os": [ + "linux" + ], + "dist": "xenial", + "addons": { + "apt": { + "packages": [ + "lib32stdc++6" + ] + } + }, + "install": [ + "git clone https://github.com/flutter/flutter.git -b stable", + "./flutter/bin/flutter doctor" + ], + "script": [ + "./flutter/bin/flutter test" + ], + "deploy": [ + { + "provider": "pages", + "strategy": "git", + "on": { + "branch": [ + "drewdev" + ] + }, + "cleanup": true, + "token": "$GITHUB_TOKEN" + } + ], + "cache": { + "directories": [ + "$HOME/.pub-cache" + ] + } +} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..5b0d1618a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ + +# 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, caste, color, 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 [The Palisadoes Foundation](http://www.palisadoes.org/) + +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][v2.0]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..e26eb2386 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,189 @@ +# Contributing to Talawa + +Thank you for your interest in contributing to Talawa. Regardless of the size of the contribution you make, all contributions are welcome and are appreciated. + +If you are new to contributing to open source, please read the Open Source Guides on [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/). + +## Code of Conduct + +A safe environment is required for everyone to contribute. Read our [Code of Conduct Guide](https://github.com/PalisadoesFoundation/talawa/blob/master/CODE_OF_CONDUCT.md) to understand what this means. Let us know immediately if you have unacceptable experiences in this area. + +No one should fear voicing their opinion. Respones must be respectful. + +## Ways to Contribute + +If you are ready to start contributing code right away, we have a list of [good first issues](https://github.com/PalisadoesFoundation/talawa/labels/good%20first%20issue) that contain issues with a limited scope. + +### Our Development Process + +We utilize GitHub issues and pull requests to keep track of issues and contributions from the community. + +#### Issues + +Make sure you are following [issue report guidelines](https://github.com/PalisadoesFoundation/talawa/blob/master/issue-guidelines.md) available here before creating any new issues on Talawa project. + +#### Pull Requests + +[Pull Request guidelines](https://github.com/PalisadoesFoundation/talawa/blob/master/PR-guidelines.md) is best resource to follow to start working on open issues. + +#### Branching Strategy + +For Talawa, we had employed the following branching strategy to simplify the development process and to ensure that only stable code is pushed to the `master` branch: + +- `develop`: For unstable code and bug fixing +- `alpha-x.x.x`: For stability testing +- `master`: Where the stable production ready code lies + +### Contributing Code + +Code contributions to Talawa come in the form of pull requests. These are done by forking the repo and making changes locally. + +The process of proposing a change to Talawa can be summarized as: + +1. Fork the Talawa repository and branch off `master`. +1. The repository can be cloned locally using `git clone `. +1. Make the desired changes to the Talawa source. +1. Run the app and test your changes. +1. If you've added code that should be tested, write tests. +1. Ensure that your code is appropriately formatted before making your submission. Submissions which are not properly formatted will be rejected if they are not fixed by the contributor. + 1. **In your IDE:** + 1. _Visual Studio Code:_ There is a setting which allows your code to be formatted [automatically when you save](https://stackoverflow.com/a/66538607/15290492), or you may manually trigger it using `Ctrl + Shift + P` or `Cmd + Shift + P` and typing `Format Document`. + 1. _IntelliJ_, _Android Studio_, and other _Jetbrains_-based IDEs. Use the `Ctrl + Alt + L` or `Cmd + Opt + L` to trigger code formatting. + 1. **On the command line before committing**: Run this command from the root of your repository directory tree. + ``` + flutter format --set-exit-if-changed . + ``` +1. Ensure that **your code should not more than 300 lines**. It is there to make the code more modular and readable. Submissions that are not properly maintained will be rejected if the contributor does not fix them. Otherwise, the contributor will have to explain the need for it. +1. After making changes you can add them to git locally using `git add `(to add changes only in a particular file) or `git add .` (to add all changes). +1. After adding the changes you need to commit them using `git commit -m ''`(look at the commit guidelines below for commit messages). + 1. You can link and automatically close the issue tied to your pull request by [using a supported keyword in either the pull request's description or in a commit message.](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) This is a very useful feature that helps to prevent zombie issues that never die. +1. Once you have successfully commited your changes, you need to push the changes to the forked repo on github using: `git push origin `.(Here branch name must be name of the branch you want to push the changes to.) +1. Now create a pull request to the Talawa repository from your forked repo. Open an issue regarding the same and link your PR to it. +1. Ensure the test suite passes, either locally or on CI once a PR has been created. +1. Review and address comments on your pull request if requested. + +### General Guidelines + +#### Folder Description + +- `controllers`: The folder contains all the files responsible for managing the state. + ``` + 1. Files contain codes for all the business logic related to any screen. + 2. Files also contain the client-side query & mutation calls and server-side side response. + 3. Before adding any controller make sure whether it already exists or not. + ``` +- `enum`: The folder contains all the enumerator used in the entire project. + ``` + 1. File contains an enum that is either used with controllers or widgets. + 2. Before creating any new enum files check if the existing enum can be modified to fulfil your requirement. + ``` +- `model`: The folder contains all the data models file. + ``` + 1. Files contains data model that is used in controller file that contains the server-side response in an organised form. + 2. These data models are used to effectively organise projects and render the data on widgets. + 3. In the controller file, convert every response to a particular data model type. + ``` +- `utils`: The folder contains all the external utility files. + ``` + 1. Codes related to an external utility like validator, UI-scaling, constant strings etc + 2. Any utility-related files should be created here if not already present. + ``` +- `views`: The folder contains all the files related to the UI display. + ``` + 1. Pages: Folder that contains all the pages related to sub-folder and code. + 2. Widgets: Folder that contains widget file for pages to avoid code duplication + ``` + +#### File Code Rules + +- Filename should be created with lowercase and underscore letters +- The business logic & UI based file should be separated from each other. + - `controllers`: Folder that contains all business logic files + - `views`: Folder that contains UI specific files +- If it is UI based file, try to use as much `stateless widget` as possible. +- Don't use the `print` statement in your code, instead use `debugPrint`. +- Constructor should be present just after the class declaration. +- Make sure to add proper `keyword` (final or const) and data types for any variable. +- In your files, structure code this way inside your widget class: + ``` + -- constructor + -- explicitly defined variables using its type (private if possible) + -- build method (Inside build(), use sub methods like _buildAppBar() + -- sub-build methods + -- other methods + -- utility methods + ``` + +**_Note: Don't use constant numerical value anywhere in your UI related code. Use SizeConfig class to assign the constant value. SizeConfig class does the job of scaling the UI based on the device size._** + +Example: + +``` +Incorrect Way: +SizedBox(height: 8, width: 4) + +Correct Way: +SizedBox(height: SizeConfig.safeBlockVertical, width: SizeConfig.safeBlockHorizontal) +``` + +The value of `safeBlockVertical` and `safeBlockHorizontal` will be displayed in your `console` and varies based on the device being used. + +#### Before making PR, ensure these: + + - All your file should contain at max `300` lines of code. + - Follow proper code formatting and run `flutter format .` before your PR. + - Run `flutter analyze` before your PR and make sure to resolve all the found issues. + +#### Project structure + +``` +app +- presentation: interactions and data presented to the user + - screens: app and feature screens + - + - .screen.dart + - widget: internal widgets related to a screen/feature + - viewmodels: shared business logic, so we can extract it easily soon as we modularize our app +- resources: local and remote data sources, other services + +components +- example: runnable app to view all components added in the app +- + - src + - component.configs.dart + - component.name.dart + +core +- enums: common enums shared +- models: business data models, entities +- theme: application theme, colors, dimens +- utils: utility classes +``` + +#### Commit guidelines + +``` +feat: (addition of a new feature) +rfac: (refactoring the code: optimization/ different logic of existing code - output doesn't change, just the way of execution changes) +docs: (documenting the code, be it readme, or extra comments) +bfix: (bug fixing) +chor: (chore - beautifying code, indents, spaces, camelcasing, changing variable names to have an appropriate meaning) +ptch: (patches - small changes in code, mainly UI, for example color of a button, increasing size of text, etc) +conf: (configurational settings - changing directory structure, updating gitignore, add libraries, changing manifest etc) +``` + +### Internships + +We have internship partnerships with a number of organizations. See below for more details. + +#### GSoC + +If you are participating in the 2021 Summer of Code, please read more about us and our processes [here](https://palisadoesfoundation.github.io/talawa-docs/docs/internships/gsoc/gsoc-introduction) + +#### GitHub Externship + +If you are participating in the 2021 GitHub Externship, please read more about us and our processes [here](https://palisadoesfoundation.github.io/talawa-docs/docs/internships/github/github-introduction) + +### Community + +The Palisadoes Foundation has a Slack channel where members can assist with support and clarification. Click [here](https://join.slack.com/t/thepalisadoes-dyb6419/shared_invite/zt-nk79xxlg-OxTdlrD7RLaswu8EO_Q5rg) to join our slack channel. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/PR-guidelines.md b/PR-guidelines.md new file mode 100644 index 000000000..80047d4ad --- /dev/null +++ b/PR-guidelines.md @@ -0,0 +1,21 @@ +# Pull Request Guidelines + +:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: + +In order to give everyone a chance to submit a pull request and contribute to the Talawa project, we have put restrictions in place. This section outlines the guidelines that should be imposed upon pull requests in the Talawa project. + +1. Do not start working on any open issue and raise a PR unless it is assigned to you. +3. Pull requests must be based on [open issues](https://github.com/PalisadoesFoundation/talawa/issues) available. +4. [Use this method to automatically close the issue when the PR is completed.](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) +5. Each contributor may only create one pull request at a time. We have this rule in place due to our limited resources - if everyone was allowed to post multiple pull requests we will not be able to review them properly. It is also better for contributors because you can focus on creating one quality PR - so spend time making sure it is as good as it can be. +6. If the pull request's code quality is not up to par, or it would break the app, it will more likely be closed. So please be careful when creating a PR. +7. Please follow the [PR template](https://github.com/PalisadoesFoundation/talawa/blob/master/.github/PR_TEMPLATE/pr-template.md). Ensure the PR title clearly describes the problem it is solving. In the description, include the relevant issue number, snapshots and videos after changes added. +8. If you are borrowing code, please disclose it. It is fine and sometimes even recommended to borrow code, but we need to know about it to assess your work. If we find out that your pull request contains a lot of code copied from elsewhere, we will close the pull request. +9. All pull request must have test units. If for some reason it is not possible to add tests, please let us know and explain why. In that case, you'll need to tell us what steps you followed to manually test your changes. +10. No Work In Progress. ONLY completed and working pull requests, and with test units, will be accepted. A WIP would fall under rule 4 and be closed immediately. +11. Please do not @mention contributors and mentors. Sometimes it takes time before we can review your pull request or answer your questions but we'll get to it sooner or later. @mentioning someone just adds to the pile of notifications we get and it won't make us look at your issue faster. +12. Do not force push. If you make changes to your pull request, please simply add a new commit as that makes it easy for us to review your new changes. If you force push, we'll have to review everything from the beginning. +13. PR should be small, easy to review and should follow standard coding styles. +14. If PR has conflicts because of recently added changes to the same file, resolve issues, test new changes and submit PR again for review. +15. PRs should be atomic. That is, they should address one item (issue or feature) +16. After submitting PR, if you are not replying within 48 hours then in that case we may need to assign issue to other contributors based on priority of the issue. diff --git a/README.md b/README.md new file mode 100644 index 000000000..057715c74 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# Talawa +[![Build Status](https://travis-ci.org/PalisadoesFoundation/talawa.svg?branch=master)](http://www.palisadoes.org/) +[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) +[![GitHub stars](https://img.shields.io/github/stars/PalisadoesFoundation/talawa.svg?style=social&label=Star&maxAge=2592000)](https://github.com/PalisadoesFoundation/talawa) +[![GitHub forks](https://img.shields.io/github/forks/PalisadoesFoundation/talawa.svg?style=social&label=Fork&maxAge=2592000)](https://github.com/PalisadoesFoundation/talawa) + +[![N|Solid](images/talawa-logo-lite-200x200.png)](https://github.com/PalisadoesFoundation/talawa) + +Talawa is a modular open source project to manage group activities of both non-profit organizations and businesses. + +Core features include: + + 1. Membership management + 2. Groups management + 3. Event registrations + 4. Recurring meetings + 5. Facilities registrations + + ``talawa`` is based on the original ``quito`` code created by the [Palisadoes Foundation](http://www.palisadoes.org) as part of its annual Calico Challenge program. Calico provides paid summer internships for Jamaican university students to work on selected open source projects. They are mentored by software professionals and receive stipends based on the completion of predefined milestones. Calico was started in 2015. Visit [The Palisadoes Foundation's website](http://www.palisadoes.org/) for more details on its origin and activities. + +# Talawa Components + +`talawa` has these major software components: + +1. **talawa**: [A mobile application with social media features](https://github.com/PalisadoesFoundation/talawa) +1. **talawa-api**: [An API providing access to user data and features](https://github.com/PalisadoesFoundation/talawa-api) +1. **talawa-admin**: [A web based administrative portal](https://github.com/PalisadoesFoundation/talawa-admin) +1. **talawa-docs**: [The online documentation website](https://github.com/PalisadoesFoundation/talawa-docs) + +# Documentation + - The talawa documentation can be found [here](https://palisadoesfoundation.github.io/talawa-docs/). + - Want to contribute? Look at [CONTRIBUTING.md](https://github.com/PalisadoesFoundation/talawa/blob/master/CONTRIBUTING.md) to get started. + - Visit the [Talawa GitHub](https://github.com/PalisadoesFoundation/talawa) to see the code. + +# Installation for Developers + +You can test talawa in your own development environment. This section shows you how. + +## Prerequisites + +You'll need to setup the IDE and mobile device emulator on your local system, and have access to a system running the Talawa API which the mobile needs to access to operate properly. + +1. **Development Environment**: You'll need to have the following installed: + 1. [Flutter SDK](https://flutter.dev/docs/get-started/install) + 1. [Android Studio](https://developer.android.com/studio) +1. **API Environment**: You will need to enter the URL of the API server in the Talawa app when it first starts up. The URL could be active on a system you control or in our test environment. + 1. *Your API Server:* Check the [Talawa API repository](https://github.com/PalisadoesFoundation/talawa-api) for information on how to setup the API on your local machine or a machine under your legitimate control. There is a section in the README.md file that discusses installation. + 2. *Our API Server for Talawa Contributors*: We also have a test environment that our open source contributors use. Try it if you are not inclined to setup or customize your own API server.This is a development environment. The data stored on this server may be modified or deleted without warning. Information about this server can be found in the [Talawa documentation site](https://palisadoesfoundation.github.io/talawa-docs/) + +## Command Line Steps + +We have tried to make the process simple. Here's what you need to do. + +1. Clone and change into the project. + ```sh + $ git clone https://github.com/PalisadoesFoundation/talawa.git + $ cd talawa + ``` +1. Install packages + ```sh + $ flutter pub get + ``` +1. Start developing! diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 000000000..d80865eef --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,46 @@ +include: package:lint/analysis_options.yaml + +analyzer: + exclude: [lib/generated_plugin_registrant.dart] + +linter: + rules: + # Make constructors the first thing in every class + sort_constructors_first: true + + # Use parameter order as in json response + always_put_required_named_parameters_first: false + + # Util classes + avoid_classes_with_only_static_members: false + + avoid_redundant_argument_values: false + + # Unnecessary use of this in constructors should not be done + unnecessary_this: false + + # source files name using lowercase_with_underscores + lowercase_with_underscores: true + + leading_newlines_in_multiline_strings: false + + always_declare_return_types: false + + type_annotate_public_apis: false + #always_use_package_imports to make a quick navigation to particular files + always_use_package_imports: true + + directives_ordering: true + + # In case of production should be set to true + avoid_print: false + + avoid_unnecessary_containers: false + + prefer_if_elements_to_conditional_expressions: false + + avoid_function_literals_in_foreach_calls: false + + join_return_with_assignment: false + + prefer_const_literals_to_create_immutables: false \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 000000000..0a741cb43 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,11 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 000000000..0cbd33251 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,59 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 30 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.talawa" + minSdkVersion 18 + targetSdkVersion 30 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..d1b4ad2aa --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..127f9df3a --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/talawa/MainActivity.kt b/android/app/src/main/kotlin/com/example/talawa/MainActivity.kt new file mode 100644 index 000000000..7366da855 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/talawa/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.talawa + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/android/app/src/main/res/drawable-night/launch_background.xml b/android/app/src/main/res/drawable-night/launch_background.xml new file mode 100644 index 000000000..81618253b --- /dev/null +++ b/android/app/src/main/res/drawable-night/launch_background.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable-night/launch_image.png b/android/app/src/main/res/drawable-night/launch_image.png new file mode 100644 index 000000000..9ebc349e6 Binary files /dev/null and b/android/app/src/main/res/drawable-night/launch_image.png differ diff --git a/android/app/src/main/res/drawable-night/launcher_image.xml b/android/app/src/main/res/drawable-night/launcher_image.xml new file mode 100644 index 000000000..72ffd0710 --- /dev/null +++ b/android/app/src/main/res/drawable-night/launcher_image.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..8b8748cca --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/launch_image.png b/android/app/src/main/res/drawable/launch_image.png new file mode 100644 index 000000000..9ebc349e6 Binary files /dev/null and b/android/app/src/main/res/drawable/launch_image.png differ diff --git a/android/app/src/main/res/drawable/launcher_image.xml b/android/app/src/main/res/drawable/launcher_image.xml new file mode 100644 index 000000000..72ffd0710 --- /dev/null +++ b/android/app/src/main/res/drawable/launcher_image.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..afb72e056 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..12fd512e0 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..e4c088414 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..3c689750f Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..133cc242f Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..c88cf94be --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + #ffffff + #18191A + \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..2ba34d5e4 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 000000000..bc151583f --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.2.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 000000000..a5965ab8d --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..939efa295 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 000000000..44e62bcf0 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/assets/fonts/OpenSans-Bold.ttf b/assets/fonts/OpenSans-Bold.ttf new file mode 100644 index 000000000..efdd5e84a Binary files /dev/null and b/assets/fonts/OpenSans-Bold.ttf differ diff --git a/assets/fonts/OpenSans-Regular.ttf b/assets/fonts/OpenSans-Regular.ttf new file mode 100644 index 000000000..29bfd35a2 Binary files /dev/null and b/assets/fonts/OpenSans-Regular.ttf differ diff --git a/assets/fonts/OpenSans-SemiBold.ttf b/assets/fonts/OpenSans-SemiBold.ttf new file mode 100644 index 000000000..54e7059cf Binary files /dev/null and b/assets/fonts/OpenSans-SemiBold.ttf differ diff --git a/assets/fonts/ProductSans-Bold.ttf b/assets/fonts/ProductSans-Bold.ttf new file mode 100644 index 000000000..96619df92 Binary files /dev/null and b/assets/fonts/ProductSans-Bold.ttf differ diff --git a/assets/fonts/ProductSans-Medium.ttf b/assets/fonts/ProductSans-Medium.ttf new file mode 100644 index 000000000..fd818d6f5 Binary files /dev/null and b/assets/fonts/ProductSans-Medium.ttf differ diff --git a/assets/fonts/ProductSans-Regular.ttf b/assets/fonts/ProductSans-Regular.ttf new file mode 100644 index 000000000..e2c69c3fb Binary files /dev/null and b/assets/fonts/ProductSans-Regular.ttf differ diff --git a/assets/images/launcher_icon.png b/assets/images/launcher_icon.png new file mode 100644 index 000000000..9ebc349e6 Binary files /dev/null and b/assets/images/launcher_icon.png differ diff --git a/assets/images/no_internet.png b/assets/images/no_internet.png new file mode 100644 index 000000000..c267a80f5 Binary files /dev/null and b/assets/images/no_internet.png differ diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..9d474a326 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Talawa documentation build configuration file, created by +# sphinx-quickstart on Tue Oct 22 22:23:00 2019. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinxcontrib.fulltoc'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'Talawa' +copyright = '2019, Peter Harrison' +author = 'Peter Harrison' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '' +# The full version, including alpha/beta/rc tags. +release = '' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] +html_favicon = '_static/talawa-rtd.png' +html_logo = '_static/talawa-rtd.png' + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'globaltoc.html', + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + ] +} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Talawadoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'Talawa.tex', 'Talawa Documentation', + 'Peter Harrison', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'talawa', 'Talawa Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'Talawa', 'Talawa Documentation', + author, 'Talawa', 'One line description of project.', + 'Miscellaneous'), +] diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 000000000..151026b91 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,33 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..9367d483e --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..2fa683c03 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,471 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.example.talawa; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.example.talawa; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.example.talawa; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 000000000..11d3da689 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 000000000..4ec6dfae1 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 000000000..f40d5e03f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 000000000..37acc361b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 000000000..9b1922180 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 000000000..463b58b71 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 000000000..d93204039 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 000000000..a6393d471 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 000000000..cac6dfba0 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 000000000..cda9658a6 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 000000000..fbde3620c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..73d3b7f6d --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1 @@ +{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"}]} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..28c6bf030 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..f091b6b0b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cde12118 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..d0ef06e7e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..dcdc2306c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..c8f9ed8f5 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..75b2d164a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..c4df70d39 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..6a84f41e1 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..d0e1f5853 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json new file mode 100644 index 000000000..9f447e1b3 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "background.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png new file mode 100644 index 000000000..e29b3b59f Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..00cabce83 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "LaunchImage.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "LaunchImage@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "LaunchImage@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..67774cf3d Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..67774cf3d Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..67774cf3d Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..c9811f020 --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 000000000..fc9053c34 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + talawa + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + UIStatusBarHidden + + + \ No newline at end of file diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/issue-guidelines.md b/issue-guidelines.md new file mode 100644 index 000000000..06be31f47 --- /dev/null +++ b/issue-guidelines.md @@ -0,0 +1,15 @@ +# Issue Report Guidelines + +:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: + +In order to give everyone a chance to submit a issues reports and contribute to the Talawa project, we have put restrictions in place. This section outlines the guidelines that should be imposed upon issue reports in the Talawa project. + +1. Use the [GitHub open issue search](https://github.com/PalisadoesFoundation/talawa/issues) — check if the issue has already been reported. +2. If the issue is already reported and not assigned to anyone, if you are interested to work on the issue then ask mentors to assign it to you in #talawa slack channel. +3. Check if the issue has been fixed — try to reproduce it using the latest master or development branch in the repository. +4. For newly found unfixed issues or features: + 1. Start discussing it in #gsoc-newissues channel with mentors + 2. Please do not derail or troll issues. + 3. Keep the discussion on topic and respect the opinions of others. +6. After mentor approval you can create a new issue by following [issue template](https://github.com/PalisadoesFoundation/talawa/blob/master/.github/ISSUE_TEMPLATE/issue-template.md) available here. +7. Feature requests are welcome. But take a moment to find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the mentors of the merits of this feature. Please provide as much detail and context as possible. diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 000000000..1ba2c7382 --- /dev/null +++ b/lang/en.json @@ -0,0 +1,156 @@ +{ + "Hello": "Hello", + "User Name": "User Name", + "we've": "we've", + "got you covered": "got you covered", + "password": "password", + "Enter new password": "Enter new password", + "Re-Enter your password": "Re-Enter your password", + "Change Password": "Change Password", + "Email Hint": "test@test.org", + "Enter your registered Email": "Enter your registered Email", + "Enter your password": "Enter your password", + "Forgot password": "Forgot password", + "Login": "Login", + "Sit back relax, we'll": "Sit back relax, we'll", + "Recover": "Recover", + "your password": "your password", + "Recover Password": "Recover Password", + "Select Language": "Select Language", + "Default": "Default", + "Select": "Select", + "Selected Organization": "Selected Organization", + "Continue": "Continue", + "Enter Organization URL": "Enter Organization URL", + "Verify": "Verify", + "Sign Up": "Sign Up", + "Change language": "Change language", + "First Name Hint": "John", + "Enter your first name": "Enter your first name", + "Last Name Hint": "Carlos", + "Enter your last name": "Enter your last name", + "Confirm your password": "Confirm your password", + "Next": "Next", + "Request Sent to": "Request Sent to", + "Log out": "Log out", + + "Join": "Join", + "and": "and", + "Collaborate": "Collaborate", + "with your": "with your", + "Organizations": "Organizations", + + "Title from the viewMode GSoC branch": "Title from the viewMode GSoC branch", + "Please verify URL first": "Please verify URL first", + "Enter a valid URL": "Enter a valid URL", + "Firstname must not be left blank.": "Firstname must not be left blank.", + "Invalid Firstname": "Invalid Firstname", + "Lastname must not be left blank.": "Lastname must not be left blank.", + "Invalid Lastname": "Invalid Lastname", + "Email must not be left blank": "Email must not be left blank", + "Please enter a valid Email Address": "Please enter a valid Email Address", + "Password must not be left blank": "Password must not be left blank", + "Invalid Password": "Invalid Password", + "Password must not contain spaces": "Password must not contain spaces", + "Password does not match original": "Password does not match original", + "Join Organisation": "Join Organisation", + + "We're": "We're", + "Glad": "Glad", + "you're": "you're", + "Back": "Back", + + "Let's": "Let's", + "get": "get", + "you": "you", + "SignUp": "SignUp", + + "Add Event Title": "Add Event Title", + "Where is the event": "Where is the event", + "Add Location": "Add Location", + "Describe the event": "Describe the event", + "Add Description": "Add Description", + "Add Event": "Add Event", + "Add": "Add", + "Add Image": "Add Image", + "Select Start Date and Time": "Select Start Date and Time", + "Select End Date and Time": "Select End Date and Time", + "Does not repeat": "Does not repeat", + "Keep Public": "Keep Public", + "Keep Registerable": "Keep Registerable", + "Event Details": "Event Details", + "Register": "Register", + "Created by": "Created by", + "public": "public", + "private": "private", + "Description": "Description", + "Admins": "Admins", + "Attendees": "Attendees", + "See all": "See all", + "Start Date": "Start Date", + "End Date": "End Date", + "Cancel": "Cancel", + "Done": "Done", + "Explore Events": "Explore Events", + "Add Date": "Add Date", + "Event": "Event", + "My Events": "My Events", + "Public Events": "Public Events", + "Private Events": "Private Events", + "Liked by": "Liked by", + "Comments": "Comments", + "FirstName LastName": "FirstName LastName", + "Pinned Posts": "Pinned Posts", + "Profile": "Profile", + "Email": "Email", + "Update": "Update", + "App Settings": "App Settings", + "Language": "Language", + "dark mode": "dark mode", + "font size": "font size", + "Help": "Help", + "Reach out to us for help": "Reach out to us for help", + "Donate Us": "Donate Us", + "Help us to develop for you": "Help us to develop for you", + "Log out from Talawa": "Log out from Talawa", + "Share News": "Share News", + "Post": "Post", + "Organization Name": "Organization Name", + "Add hasthtag": "Add hasthtag", + "Write here what do you want to share": "Write here what do you want to share", + "Join selected organisation": "Join selected organisation", + "Home": "Home", + "Events": "Events", + "Chat": "Chat", + "Chat Screen": "Chat Screen", + "Confirm": "Confirm", + "Confirmation": "Confirmation", + "Close": "Close", + "Switch Organization": "Switch Organization", + "Join new Organization": "Join new Organization", + "Leave Current Organization": "Leave Current Organization", + "Creator": "Creator", + "Public": "Public", + "Private": "Private", + "No Internet": "No Internet", + "Subscribed": "Subscribed", + "from": "from", + "See all Pinned news": "See all Pinned news", + "show more": "show more", + "show less": "show less", + "Likes": "Likes", + "comments": "comments", + "Final": "Final", + "Enter Details": "Enter Details", + "Select\nOrganization": "Select\nOrganization", + "already selected": "already selected", + "Switched to": "Switched to", + "Organisation already joined": "Organisation already joined", + "Membership request already sent": "Membership request already sent", + "Select one organization to continue": "Select one organization to continue", + "SomeThing went wrong": "SomeThing went wrong", + "Join in request sent to": "Join in request sent to", + "successfully": "successfully", + "Are you sure you want to logout?": "Are you sure you want to logout?", + "Logout": "Logout" +} \ No newline at end of file diff --git a/lang/es.json b/lang/es.json new file mode 100644 index 000000000..c6ff442be --- /dev/null +++ b/lang/es.json @@ -0,0 +1,151 @@ +{ + "Hello": "hola", + "User Name": "Nombre de usuario", + "we've": "tenemos", + "got you covered": "te cubrió", + "password": "contraseña", + "Enter new password": "Introduzca nueva contraseña", + "Re-Enter your password": "Vuelva a ingresar su contraseña", + "Change Password": "Cambiar contraseña", + "Email Hint": "test@test.org", + "Enter your registered Email": "Ingrese su correo electrónico registrado", + "Enter your password": "Ingresa tu contraseña", + "Forgot password": "¿Olvidaste la contraseña?", + "Login": "Acceso", + "Sit back relax, we'll": "Siéntese, ", + "Recover": "", + "your password": "relájese, recuperaremos su contraseña", + "Recover Password": "Recupera tu contraseña", + "Select Language": "Seleccione el idioma", + "Default": "Valor por defecto", + "Select": "Seleccione", + "Selected Organization": "Organización seleccionada", + "Continue": "Continuar", + "Enter Organization URL": "Ingrese la URL de la organización", + "Verify": "Verificarlo", + "Sign Up": "Inscribirse", + "Change language": "Cambiar idioma", + "First Name Hint": "Isabela", + "Enter your first name": "Ponga su primer nombre", + "Last Name Hint": "Rossellini", + "Enter your last name": "Ingrese su apellido", + "Confirm your password": "Confirmar la contraseña", + "Next": "Próximo", + "Request Sent to": "Solicitud enviada a", + "Log out": "Cerrar sesión", + "Join": "Únase", + "and": "y", + "Collaborate": "colabore", + "with your": "con sus", + "Organizations": "organizaciones", + "Title from the viewMode GSoC branch": "Título de la rama viewMode GSoC", + "Please verify URL first": "Primero verifique la URL", + "Enter a valid URL": "Ingrese una URL válida", + "Firstname must not be left blank.": "El nombre no debe dejarse en blanco.", + "Invalid Firstname": "Nombre inválido", + "Lastname must not be left blank.": "El apellido no debe dejarse en blanco.", + "Invalid Lastname": "Apellido inválido", + "Email must not be left blank": "El correo electrónico no debe dejarse en blanco", + "Please enter a valid Email Address": "Por favor, introduce una dirección de correo electrónico válida", + "Password must not be left blank": "La contraseña no debe dejarse en blanco", + "Invalid Password": "Contraseña invalida", + "Password must not contain spaces": "La contraseña no debe contener espacios", + "Password does not match original": "La contraseña no coincide con la original", + "Join Organisation": "Unirse a la organización", + "We're": "Nos", + "Glad": "alegra que", + "you're": "hayas", + "Back": "vuelto", + "Let's": "Consigamos", + "get": "que", + "you": "te", + "SignUp": "registres", + "Add Event Title": "Agregar título de evento", + "Where is the event": "Donde es el evento", + "Add Location": "Añadir lugar", + "Describe the event": "Describe el evento", + "Add Description": "Agregar descripción", + "Add Event": "Añadir evento", + "Add": "Agregarlo", + "Add Image": "Añadir imagen", + "Select Start Date and Time": "Seleccione la fecha y hora de inicio", + "Select End Date and Time": "Seleccione la fecha y hora de finalización", + "Does not repeat": "No se repite", + "Keep Public": "Mantener pública", + "Keep Registerable": "Mantener registrable", + "Event Details": "Detalles del evento", + "Register": "Registrarse", + "Created by": "Creado por", + "public": "pública", + "private": "privada", + "Description": "Descripción", + "Admins": "Administradoras", + "Attendees": "Asistentes", + "See all": "Ver todo", + "Start Date": "Fecha de inicio", + "End Date": "Fecha final", + "Cancel": "Cancelar", + "Done": "Hecho", + "Explore Events": "Explorar eventos", + "Add Date": "Agregar fecha", + "Event": "Evento", + "My Events": "Mis Eventos", + "Public Events": "Eventos publicos", + "Private Events": "Eventos privados", + "Liked by": "Apreciado por", + "Comments": "Comentarios", + "FirstName LastName": "Nombre Apellido", + "Pinned Posts": "Puestos fijadas", + "Profile": "Perfil", + "Email": "Correo electrónico", + "Update": "Actualizar", + "App Settings": "Ajustes de Aplicacion", + "Language": "Idioma", + "dark mode": "modo oscuro", + "font size": "tamaño de fuente", + "Help": "Ayudar", + "Reach out to us for help": "Comuníquese con nosotros para obtener ayuda", + "Donate Us": "Donarnos", + "Help us to develop for you": "Ayúdanos a desarrollar para ti", + "Log out from Talawa": "Cerrar sesión en Talawa", + "Share News": "Compartir noticias", + "Post": "Correo", + "Organization Name": "Nombre de la Organización", + "Add hasthtag": "Agregar hashtag", + "Write here what do you want to share": "Escribe aquí lo que quieres compartir", + "Join selected organisation": "Unirse a la organización seleccionada", + "Home": "Casa", + "Events": "Eventos", + "Chat": "Chat", + "Chat Screen": "Pantalla de chat", + "Confirm": "Confirmar", + "Confirmation": "Confirmación", + "Close": "Cerca", + "Switch Organization": "Cambiar de organización", + "Join new Organization": "Unirse a una nueva organización", + "Leave Current Organization": "Dejar la organización actual", + "Creator": "Creadora", + "Public": "Pública", + "Private": "Privada", + "No Internet": "No Internet", + "Subscribed": "Suscrita", + "from": "de", + "See all Pinned news": "Ver todas las noticias fijadas", + "show more": "mostrar más", + "show less": "Muestra menos", + "Likes": "Gustos", + "comments": "comentarios", + "Final": "Final", + "Enter Details": "Ingrese detalles", + "Select\nOrganization": "Seleccione\nOrganización", + "already selected": "ya seleccionado", + "Switched to": "Cambiado a", + "Organisation already joined": "Organización ya unida", + "Membership request already sent": "Solicitud de membresía ya enviada", + "Select one organization to continue": "Seleccione una organización para continuar", + "SomeThing went wrong": "Algo salió mal", + "Join in request sent to": "Únase a la solicitud enviada a", + "successfully": "exitosamente", + "Are you sure you want to logout?": "¿Estás segura de que quieres cerrar sesión?", + "Logout": "Cerrar sesión" +} \ No newline at end of file diff --git a/lang/fr.json b/lang/fr.json new file mode 100644 index 000000000..457ade942 --- /dev/null +++ b/lang/fr.json @@ -0,0 +1,155 @@ +{ + "Hello": "Bonjour", + "User Name": "Nom d'utilisateur", + "we've": "on a", + "got you covered": "vous a couvert", + "password": "le mot de passe", + "Enter new password": "Entrez un nouveau mot de passe", + "Re-Enter your password": "Entrez à nouveau votre mot de passe", + "Change Password": "Changer le mot de passe", + + "Email Hint": "test@test.org", + "Enter your registered Email": "Entrez votre email enregistré", + "Enter your password": "Tapez votre mot de passe", + "Forgot password": "Mot de passe oublié", + "Login": "Connexion", + "Sit back relax, we'll": "Asseyez-vous, détendez-vous, nous récupérerons", + "Recover": "votre mot", + "your password": "de passe", + "Recover Password": "Récupérer mot de passe", + "Select Language": "Choisir la langue", + "Default": "Défaut", + "Select": "Sélectionner", + "Selected Organization": "Organisation sélectionnée", + "Continue": "Continuer", + "Enter Organization URL": "Saisissez l'URL de l'organisation", + "Verify": "Vérifier", + "Sign Up": "S'inscrire", + "Change language": "Changer de langue", + "First Name Hint": "John", + "Enter your first name": "Entrez votre prénom", + "Last Name Hint": "Carlos", + "Enter your last name": "Entrez votre nom de famille", + "Confirm your password": "Confirmer votre mot de passe", + "Next": "Suivante", + "Request Sent to": "Demande envoyée à", + "Log out": "Se déconnecter", + + "Join": "Rejoignez", + "and": "et", + "Collaborate": "collaborez", + "with your": "avec vos", + "Organizations": "organisations", + + "Title from the viewMode GSoC branch": "Titre de la branche viewMode GSoC", + "Please verify URL first": "Veuillez d'abord vérifier l'URL", + "Enter a valid URL": "Saisissez une URL valide", + "Firstname must not be left blank.": "Le prénom ne doit pas être laissé en blanc.", + "Invalid Firstname": "Prénom invalide", + "Lastname must not be left blank.": "Le nom de famille ne doit pas être laissé vide.", + "Invalid Lastname": "Nom de famille invalide", + "Email must not be left blank": "L'e-mail ne doit pas être laissé vide", + "Please enter a valid Email Address": "S'il vous plaît, mettez une adresse email valide", + "Password must not be left blank": "Le mot de passe ne doit pas être laissé vide", + "Invalid Password": "Mot de passe incorrect", + "Password must not contain spaces": "Le mot de passe ne doit pas contenir d'espaces", + "Password does not match original": "Le mot de passe ne correspond pas à l'original", + "Join Organisation": "Rejoindre l'organisation", + + "We're": "Nous sommes", + "Glad": "heureux que vous", + "you're": "soyez de", + "Back": "retour", + "Let's": "Laissez-nous vous inscrire", + "get": "", + "you": "vous", + "SignUp": "inscrire", + "Add Event Title": "Ajouter le titre de l'événement", + "Where is the event": "Où est l'événement", + "Add Location": "Ajouter un emplacement", + "Describe the event": "Décrivez l'événement", + "Add Description": "Ajouter une description", + "Add Event": "Ajouter un évènement", + "Add": "Ajoutez-le", + "Add Image": "Ajouter une image", + "Select Start Date and Time": "Sélectionnez la date et l'heure de début", + "Select End Date and Time": "Sélectionnez la date et l'heure de fin", + "Does not repeat": "ça ne se répète pas", + "Keep Public": "Garder public", + "Keep Registerable": "Garder enregistrable", + "Event Details": "Détails de l'évènement", + "Register": "S'inscrire", + "Created by": "Créé par", + "public": "publique", + "private": "privée", + "Description": "La description", + "Admins": "Administratrices", + "Attendees": "Participantes", + "See all": "Voir tout", + "Start Date": "Date de début", + "End Date": "Date de fin", + "Cancel": "Annuler", + "Done": "Fait", + "Explore Events": "Explorer les événements", + "Add Date": "Ajouter une date", + "Event": "Événement", + "My Events": "Mes événements", + "Public Events": "Événements publics", + "Private Events": "Événements privés", + "Liked by": "Aimé par", + "Comments": "commentaires", + "FirstName LastName": "Prénom nom de famille", + "Pinned Posts": "Messages épinglés", + "Profile": "Profil", + "Email": "E-mail", + "Update": "Mettre à jour", + "App Settings": "Paramètres de l'application", + "Language": "Langue", + "dark mode": "mode sombre", + "font size": "taille de police", + "Help": "Aider", + "Reach out to us for help": "Contactez-nous pour obtenir de l'aide", + "Donate Us": "Faites-nous un don", + "Help us to develop for you": "Aidez-nous à développer pour vous", + "Log out from Talawa": "Se déconnecter de Talawa", + "Share News": "Partager Nouvelles", + "Post": "Poster", + "Organization Name": "nom de l'organisation", + "Add hasthtag": "Ajouter un hashtag", + "Write here what do you want to share": "Écrivez ici ce que vous voulez partager", + "Join selected organisation": "Rejoindre l'organisation sélectionnée", + "Home": "Domicile", + "Events": "Événements", + "Chat": "Discuter", + "Chat Screen": "Écran de discussion", + "Confirm": "Confirmer", + "Confirmation": "Confirmation", + "Close": "Fermer", + "Switch Organization": "Changer d'organisation", + "Join new Organization": "Rejoindre une nouvelle organisation", + "Leave Current Organization": "Quitter l'organisation actuelle", + "Creator": "Créatrice", + "Public": "Publique", + "Private": "Privée", + "No Internet": "Sans Internet", + "Subscribed": "Abonnée", + "from": "de", + "See all Pinned news": "Voir toutes les actualités épinglées", + "show more": "montre plus", + "show less": "Montrer moins", + "Likes": "Aime", + "comments": "commentaires", + "Final": "Finale", + "Enter Details": "Entrez les détails", + "Select\nOrganization": "Sélectionner\nOrganisation", + "already selected": "déjà sélectionné", + "Switched to": "Basculé vers", + "Organisation already joined": "Organisation déjà adhérée", + "Membership request already sent": "Demande d'adhésion déjà envoyée", + "Select one organization to continue": "Sélectionnez une organisation pour continuer", + "SomeThing went wrong": "Quelque chose s'est mal passé", + "Join in request sent to": "Demande d'adhésion envoyée à", + "successfully": "avec succès", + "Are you sure you want to logout?": "Êtes-vous sûr de vouloir vous déconnecter?", + "Logout": "Se déconnecter" +} \ No newline at end of file diff --git a/lang/hi.json b/lang/hi.json new file mode 100644 index 000000000..efa38c813 --- /dev/null +++ b/lang/hi.json @@ -0,0 +1,151 @@ +{ + "Hello": "नमस्ते", + "User Name": "उपयोगकर्ता नाम", + "we've": "हमने", + "got you covered": "आपको कवर कर लिया है", + "password": "पास वर्ड दर्ज करें", + "Enter new password": "नया पासवर्ड दर्ज करें", + "Re-Enter your password": "दुबारापासवडृ िलखो", + "Change Password": "पासवर्ड बदलें", + "Email Hint": "test@test.org", + "Enter your registered Email": "अपना पंजीकृत ईमेल दर्ज करें", + "Enter your password": "अपना पासवर्ड डालें", + "Forgot password": "क्या आप पासवर्ड भूल गए", + "Login": "लॉग इन करें", + "Sit back relax, we'll": "आराम से बैठें, हम", + "Recover": "रिकवर कर लेंगे", + "your password": "आपका पासवर्ड", + "Recover Password": "पासवर्ड पुनः प्राप्त करना", + "Select Language": "भाषा का चयन करें", + "Default": "डिफ़ॉल्ट मान", + "Select": "चुनते हैं", + "Selected Organization": "चयनित संगठन", + "Continue": "जारी रखें", + "Enter Organization URL": "संगठन URL दर्ज करें", + "Verify": "सत्यापित करें", + "Sign Up": "साइन अप करें", + "Change language": "भाषा बदलें", + "First Name Hint": "राहुल", + "Enter your first name": "अपना पहला नाम दर्ज करें", + "Last Name Hint": "कुमार", + "Enter your last name": "अपना अंतिम नाम दर्ज करें", + "Confirm your password": "अपने पासवर्ड की पुष्टि करें", + "Next": "अगला", + "Request Sent to": "अनुरोध भेजा गया", + "Log out": "लॉग आउट", + "Join": "अपने संगठनों", + "and": "से", + "Collaborate": "जुड़ें", + "with your": "और सहयोग करें", + "Organizations": "", + "Title from the viewMode GSoC branch": "व्यूमोड जीएसओसी शाखा से शीर्षक", + "Please verify URL first": "कृपया पहले URL सत्यापित करें", + "Enter a valid URL": "एक मान्य यूआरएल दर्ज करें", + "Firstname must not be left blank.": "प्रथम नाम खाली नहीं छोड़ा जाना चाहिए।", + "Invalid Firstname": "अमान्य प्रथम नाम", + "Lastname must not be left blank.": "उपनाम खाली नहीं छोड़ा जाना चाहिए।", + "Invalid Lastname": "अमान्य अंतिम नाम", + "Email must not be left blank": "ईमेल खाली नहीं छोड़ना चाहिए", + "Please enter a valid Email Address": "कृपया एक वैध ई - मेल एड्रेस डालें", + "Password must not be left blank": "पासवर्ड खाली नहीं छोड़ना चाहिए", + "Invalid Password": "अवैध पासवर्ड", + "Password must not contain spaces": "पासवर्ड में रिक्त स्थान नहीं होना चाहिए", + "Password does not match original": "पासवर्ड मूल से मेल नहीं खाता", + "Join Organisation": "संगठन में शामिल हों", + "We're": "हमें", + "Glad": "खुशी है कि", + "you're": "आप", + "Back": "वापस आ गए हैं", + "Let's": "आइए", + "get": "आपको", + "you": "साइनअप", + "SignUp": "कराते हैं", + "Add Event Title": "घटना का शीर्षक जोड़ें", + "Where is the event": "घटना कहाँ है", + "Add Location": "स्थान जोड़ना", + "Describe the event": "घटना का वर्णन करें", + "Add Description": "विवरण जोड़ें", + "Add Event": "कार्यक्रम जोड़ें", + "Add": "इसे जोड़ें", + "Add Image": "छवि जोड़ें", + "Select Start Date and Time": "प्रारंभ दिनांक और समय चुनें", + "Select End Date and Time": "समाप्ति तिथि और समय चुनें", + "Does not repeat": "यह दोहराता नहीं है", + "Keep Public": "सार्वजनिक रखें", + "Keep Registerable": "पंजीकरण योग्य रखें", + "Event Details": "घटना की जानकारी", + "Register": "रजिस्टर करें", + "Created by": "के द्वारा बनाई गई", + "public": "सह लोक", + "private": "निजी", + "Description": "विवरण", + "Admins": "व्यवस्थापक", + "Attendees": "सहभागी", + "See all": "सभी देखें", + "Start Date": "आरंभ करने की तिथि", + "End Date": "समाप्ति तिथि", + "Cancel": "रद्द करना", + "Done": "किया हुआ", + "Explore Events": "घटनाओं का अन्वेषण करें", + "Add Date": "तिथि जोड़ें", + "Event": "प्रतिस्पर्धा", + "My Events": "मेरे कार्यक्रम", + "Public Events": "सार्वजनिक कार्यक्रम", + "Private Events": "निजी कार्यक्रम", + "Liked by": "द्वारा पसंद किया गया", + "Comments": "टिप्पणियाँ", + "FirstName LastName": "प्रथम नाम अंतिम नाम", + "Pinned Posts": "चिपके पत्र", + "Profile": "प्रोफ़ाइल", + "Email": "ईमेल", + "Update": "अपडेट करें", + "App Settings": "एप्लिकेशन सेटिंग", + "Language": "भाषा: हिन्दी", + "dark mode": "डार्क मोड", + "font size": "फ़ॉन्ट आकार", + "Help": "मदद", + "Reach out to us for help": "मदद के लिए हमसे संपर्क करें", + "Donate Us": "हमें दान करें", + "Help us to develop for you": "आपके लिए विकास करने में हमारी सहायता करें", + "Log out from Talawa": "तलवा से लॉग आउट करें", + "Share News": "समाचार साझा करें", + "Post": "पद", + "Organization Name": "संगठन का नाम", + "Add hasthtag": "हैशटैग जोड़ें", + "Write here what do you want to share": "यहां लिखें कि आप क्या साझा करना चाहते हैं", + "Join selected organisation": "चयनित संगठन से जुड़ें", + "Home": "घर", + "Events": "आयोजन", + "Chat": "चैट", + "Chat Screen": "चैट स्क्रीन", + "Confirm": "पुष्टि करें", + "Confirmation": "पुष्टीकरण", + "Close": "बंद करे", + "Switch Organization": "संगठन स्विच करें", + "Join new Organization": "नए संगठन में शामिल हों", + "Leave Current Organization": "वर्तमान संगठन छोड़ें", + "Creator": "निर्माता", + "Public": "सार्वजनिक", + "Private": "निजी", + "No Internet": "कोई इंटरनेट नहीं", + "Subscribed": "सदस्यता ली गई", + "from": "से", + "See all Pinned news": "सभी पिन किए गए समाचार देखें", + "show more": "अधिक दिखाएं", + "show less": "कम दिखाएं", + "Likes": "पसंद", + "comments": "टिप्पणियां", + "Final": "फाइनल", + "Enter Details": "विवरण दर्ज करें", + "Select\nOrganization": "चुनते हैं\nसंगठन", + "already selected": "पहले से चयनित", + "Switched to": "इस पर स्विच किया गया", + "Organisation already joined": "संगठन पहले ही शामिल हो चुका है", + "Membership request already sent": "सदस्यता अनुरोध पहले ही भेजा जा चुका है", + "Select one organization to continue": "जारी रखने के लिए एक संगठन चुनें", + "SomeThing went wrong": "कुछ गलत हो गया", + "Join in request sent to": "ज्वाइन इन रिक्वेस्ट इन को भेजा गया", + "successfully": "सफलतापूर्वक", + "Are you sure you want to logout?": "क्या आप वाकई लॉगआउट करना चाहते हैं?", + "Logout": "लॉगआउट" +} \ No newline at end of file diff --git a/lang/zh.json b/lang/zh.json new file mode 100644 index 000000000..28b569f75 --- /dev/null +++ b/lang/zh.json @@ -0,0 +1,151 @@ +{ + "Hello": "你好", + "User Name": "用户名", + "we've": "我们已经", + "got you covered": "为您服务", + "password": "输入您的密码", + "Enter new password": "输入新密码", + "Re-Enter your password": "重新输入您的密码", + "Change Password": "更改密码", + "Email Hint": "test@test.org", + "Enter your registered Email": "输入您的注册邮箱", + "Enter your password": "输入您的密码", + "Forgot password": "你忘记密码了吗", + "Login": "登录", + "Sit back relax, we'll": "高枕无忧, ", + "Recover": "", + "your password": "我们会找回您的密码", + "Recover Password": "恢复你的密码", + "Select Language": "选择语言", + "Default": "默认值", + "Select": "选择", + "Selected Organization": "选定组织", + "Continue": "继续", + "Enter Organization URL": "输入组织 URL", + "Verify": "验证一下", + "Sign Up": "报名", + "Change language": "改变语言", + "First Name Hint": "杰基", + "Enter your first name": "输入您的名字", + "Last Name Hint": "陈", + "Enter your last name": "输入您的姓氏", + "Confirm your password": "确认你的密码", + "Next": "下一个", + "Request Sent to": "请求发送至", + "Log out": "登出", + "Join": "加入您的组织并与之协作", + "and": "", + "Collaborate": "", + "with your": "", + "Organizations": "", + "Title from the viewMode GSoC branch": "来自 viewMode GSoC 分支的标题", + "Please verify URL first": "请先验证网址", + "Enter a valid URL": "输入有效的网址", + "Firstname must not be left blank.": "名字不能留空。", + "Invalid Firstname": "名字无效", + "Lastname must not be left blank.": "姓氏不能留空。", + "Invalid Lastname": "无效的姓氏", + "Email must not be left blank": "电子邮件不得留空", + "Please enter a valid Email Address": "请输入有效的电子邮件地址", + "Password must not be left blank": "密码不能为空", + "Invalid Password": "无效的密码", + "Password must not contain spaces": "密码不能包含空格", + "Password does not match original": "密码与原密码不符", + "Join Organisation": "加入组织", + "We're": "我们很高兴", + "Glad": "", + "you're": "你回来了", + "Back": "", + "Let's": "让我们", + "get": "帮", + "you": "", + "SignUp": "你注册", + "Add Event Title": "添加活动标题", + "Where is the event": "活动地点", + "Add Location": "添加位置", + "Describe the event": "描述事件", + "Add Description": "添加说明", + "Add Event": "添加事件", + "Add": "添加", + "Add Image": "添加图片", + "Select Start Date and Time": "选择开始日期和时间", + "Select End Date and Time": "选择结束日期和时间", + "Does not repeat": "不重复", + "Keep Public": "保持公开", + "Keep Registerable": "保持注册", + "Event Details": "活动详情", + "Register": "注册", + "Created by": "创建者", + "public": "公开", + "private": "私人", + "Description": "说明", + "Admins": "管理员", + "Attendees": "与会者", + "See all": "查看全部", + "Start Date": "开始日期", + "End Date": "结束日期", + "Cancel": "取消", + "Done": "完成", + "Explore Events": "探索事件", + "Add Date": "添加日期", + "Event": "事件", + "My Events": "我的活动", + "Public Events": "公共事件", + "Private Events": "私人活动", + "Liked by": "喜欢的人", + "Comments": "评论", + "FirstName LastName": "名字姓氏", + "Pinned Posts": "已标记的帖子", + "Profile": "简介", + "Email": "电子邮件", + "Update": "更新", + "App Settings": "应用设置", + "Language": "语言", + "dark mode": "黑暗模式", + "font size": "字体大小", + "Help": "帮助", + "Reach out to us for help": "联系我们寻求帮助", + "Donate Us": "捐赠我们", + "Help us to develop for you": "帮助我们为您开发", + "Log out from Talawa": "从 Talawa 注销", + "Share News": "分享新闻", + "Post": "邮政", + "Organization Name": "机构名称", + "Add hasthtag": "添加主题标签", + "Write here what do you want to share": "在这里写下您想分享的内容", + "Join selected organisation": "加入选定的组织", + "Home": "家", + "Events": "事件", + "Chat": "聊天", + "Chat Screen": "聊天画面", + "Confirm": "确认", + "Confirmation": "确认", + "Close": "关闭", + "Switch Organization": "切换组织", + "Join new Organization": "加入新组织", + "Leave Current Organization": "离开当前组织", + "Creator": "创造者", + "Public": "公共", + "Private": "私人", + "No Internet": "没有互联网", + "Subscribed": "订阅", + "from": "来自", + "See all Pinned news": "查看所有置顶新闻", + "show more": "显示更多", + "show less": "显示较少", + "Likes": "喜欢", + "comments": "评论", + "Final": "最终", + "Enter Details": "输入详细信息", + "Select\nOrganization": "Select\nOrganization", + "already selected": "已选", + "Switched to": "切换到", + "Organisation already joined": "组织已加入", + "Membership request already sent": "会员申请已发送", + "Select one organization to continue": "选择一个组织继续", + "SomeThing went wrong": "出问题了", + "Join in request sent to": "加入请求发送至", + "successfully": "成功", + "Are you sure you want to logout?": "您确定要退出吗?", + "Logout": "登出" +} \ No newline at end of file diff --git a/lib/constants/constants.dart b/lib/constants/constants.dart new file mode 100644 index 000000000..079d52fe4 --- /dev/null +++ b/lib/constants/constants.dart @@ -0,0 +1,34 @@ +import 'package:talawa/models/language/language_model.dart'; + +List languages = [ + Language( + countryCode: 'US', + langCode: 'en', + langName: 'English', + langSample: 'Welcome User', + ), + Language( + countryCode: 'IN', + langCode: 'hi', + langName: 'हिंदी', + langSample: 'स्वागत उपयोगकर्ता', + ), + Language( + countryCode: 'CN', + langCode: 'zh', + langName: '中国人', + langSample: '欢迎用户', + ), + Language( + countryCode: 'FR', + langCode: 'fr', + langName: 'français', + langSample: 'Bienvenue utilisateur', + ), + Language( + countryCode: 'ES', + langCode: 'es', + langName: 'Española', + langSample: 'Bienvenida usuario', + ), +]; diff --git a/lib/constants/custom_theme.dart b/lib/constants/custom_theme.dart new file mode 100644 index 000000000..b5e8c7287 --- /dev/null +++ b/lib/constants/custom_theme.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; + +class TalawaTheme { + static const Color _lightCursorColor = Color(0xff34AD64); + static const Color _lightAccentColor = Color(0xff34AD64); + static const Color _lightScaffoldColor = Colors.white; + static const Color _lightPrimaryColor = Colors.white; + static const Color _lightPrimaryVariantColor = Color(0xFFe5e5e5); + static const Color _lightIconColor = Color(0xff8C8E8D); + static const Color _lightInBlack = Color(0xff000000); + static const Color _lightColorSchemePrimary = Color(0xfffabc57); + + static const Color _darkCursorColor = Color(0xff34AD64); + static const Color _darkAccentColor = Color(0xff34AD64); + static const Color _darkScaffoldColor = Color(0xff18191A); + static const Color _darkPrimaryColor = Colors.black; + static const Color _darkPrimaryVariantColor = Colors.black; + static const Color _darkIconColor = Colors.white70; + static const Color _darkInWhite = Color(0xffffffff); + static const Color _darkColorSchemePrimary = Color(0xfffabc57); + + static final lightTheme = ThemeData( + scaffoldBackgroundColor: _lightScaffoldColor, + textSelectionTheme: const TextSelectionThemeData( + cursorColor: _lightCursorColor, + ), + primaryColor: _lightPrimaryColor, + colorScheme: const ColorScheme.light( + primaryVariant: _lightPrimaryVariantColor, + primary: _lightColorSchemePrimary, + secondary: Color(0xffF5F5F5), + secondaryVariant: _darkScaffoldColor), + accentColor: _lightAccentColor, + iconTheme: const IconThemeData( + color: _lightIconColor, + ), + fontFamily: 'product-sans', + textTheme: _lightTextTheme, + inputDecorationTheme: _lightInputDecor); + + static final darkTheme = ThemeData( + textSelectionTheme: const TextSelectionThemeData( + cursorColor: _darkCursorColor, + ), + scaffoldBackgroundColor: _darkScaffoldColor, + primaryColor: _darkPrimaryColor, + colorScheme: const ColorScheme.dark( + primaryVariant: _darkPrimaryVariantColor, + primary: _darkColorSchemePrimary, + secondary: Colors.black, + secondaryVariant: _lightScaffoldColor), + iconTheme: const IconThemeData( + color: _darkIconColor, + ), + accentColor: _darkAccentColor, + fontFamily: 'product-sans', + textTheme: _darkTextTheme, + inputDecorationTheme: _darkInputDecor); + + static const TextTheme _lightTextTheme = TextTheme( + headline4: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 34, + ), + headline5: TextStyle( + color: _lightInBlack, + fontSize: 20, + ), + headline6: TextStyle( + fontSize: 16, + color: Color(0xFF737373), + ), + bodyText1: TextStyle( + fontSize: 14, + ), + bodyText2: TextStyle( + fontSize: 14, + ), + caption: TextStyle( + fontWeight: FontWeight.w400, color: Color(0xFF737373), fontSize: 12.0), + ); + + static const TextTheme _darkTextTheme = TextTheme( + headline4: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 34, + ), + headline5: TextStyle(color: _darkInWhite, fontSize: 20), + headline6: TextStyle( + fontSize: 16, + color: Color(0xFF737373), + ), + bodyText1: TextStyle( + fontSize: 14, + color: Colors.white, + ), + bodyText2: TextStyle( + fontSize: 14, + color: Colors.white, + ), + caption: TextStyle( + fontWeight: FontWeight.w400, color: Color(0xFF737373), fontSize: 12.0), + ); + + static const InputDecorationTheme _lightInputDecor = InputDecorationTheme( + border: InputBorder.none, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color(0xFF008A37)), + ), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.grey), + ), + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorMaxLines: 3, + ); + static const InputDecorationTheme _darkInputDecor = InputDecorationTheme( + border: InputBorder.none, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color(0xFF008A37)), + ), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.grey), + ), + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorMaxLines: 3, + ); +} diff --git a/lib/constants/routing_constants.dart b/lib/constants/routing_constants.dart new file mode 100644 index 000000000..c36f87f25 --- /dev/null +++ b/lib/constants/routing_constants.dart @@ -0,0 +1,25 @@ +//routes + +class Routes { + static const String demoPageViewRoute = "/demoPageViewRoute"; + static const String splashScreen = "/"; + static const String languageSelectionRoute = "/selectLang"; + static const String setUrlScreen = "/setUrl"; + static const String loginScreen = "/login"; + static const String selectOrgScreen = "/selectOrg"; + static const String signupDetailScreen = "/signupDetails"; + static const String waitingScreen = "/waiting"; + static const String recoverScreen = "/recover"; + static const String updateScreen = "/update"; + static const String homeScreen = "/homeScreen"; + static const String mainScreen = "/mainScreen"; + static const String progressDialog = "/progress"; + static const String individualPost = "/individualPost"; + static const String pinnedPostPage = "/pinnedPostPage"; + static const String exploreEventsScreen = "/exploreEvents"; + static const String eventInfoPage = "/eventInfo"; + static const String createEventPage = "/createEventPage"; + static const String profilePage = "/profilePage"; + static const String editProfilePage = "/editProfilePage"; + static const String joinOrg = '/joinOrg'; +} diff --git a/lib/custom_painters/language_icon.dart b/lib/custom_painters/language_icon.dart new file mode 100644 index 000000000..488f1df82 --- /dev/null +++ b/lib/custom_painters/language_icon.dart @@ -0,0 +1,271 @@ +import 'package:flutter/material.dart'; + +class LanguageIcon extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final Path path_0 = Path(); + path_0.moveTo(size.width * 0.2376568, size.height * 0.4381250); + path_0.cubicTo( + size.width * 0.2321886, + size.height * 0.4003127, + size.width * 0.2254689, + size.height * 0.3343750, + size.width * 0.2254689, + size.height * 0.3343750); + path_0.lineTo(size.width * 0.2246875, size.height * 0.3343750); + path_0.cubicTo( + size.width * 0.2246875, + size.height * 0.3343750, + size.width * 0.2179686, + size.height * 0.4003127, + size.width * 0.2125000, + size.height * 0.4381250); + path_0.lineTo(size.width * 0.1951561, size.height * 0.5553136); + path_0.lineTo(size.width * 0.2546886, size.height * 0.5553136); + path_0.lineTo(size.width * 0.2376568, size.height * 0.4381250); + path_0.close(); + path_0.moveTo(size.width * 0.9625000, 0); + path_0.lineTo(size.width * 0.5250000, 0); + path_0.lineTo(size.width * 0.5250000, size.height); + path_0.lineTo(size.width * 0.9625000, size.height); + path_0.cubicTo(size.width * 0.9832818, size.height, size.width, + size.height * 0.9665636, size.width, size.height * 0.9250000); + path_0.lineTo(size.width, size.height * 0.07500000); + path_0.cubicTo(size.width, size.height * 0.03343750, size.width * 0.9832818, + 0, size.width * 0.9625000, 0); + path_0.close(); + path_0.moveTo(size.width * 0.9250000, size.height * 0.3750000); + path_0.cubicTo( + size.width * 0.9250000, + size.height * 0.3956250, + size.width * 0.9165636, + size.height * 0.4125000, + size.width * 0.9062500, + size.height * 0.4125000); + path_0.lineTo(size.width * 0.8884364, size.height * 0.4125000); + path_0.cubicTo( + size.width * 0.8776568, + size.height * 0.4862500, + size.width * 0.8545318, + size.height * 0.5606227, + size.width * 0.8217182, + size.height * 0.6309364); + path_0.cubicTo( + size.width * 0.8348432, + size.height * 0.6509364, + size.width * 0.8484364, + size.height * 0.6700000, + size.width * 0.8625000, + size.height * 0.6871864); + path_0.cubicTo( + size.width * 0.8710932, + size.height * 0.6978136, + size.width * 0.8739068, + size.height * 0.7200000, + size.width * 0.8689068, + size.height * 0.7378136); + path_0.lineTo(size.width * 0.8565636, size.height * 0.7812500); + path_0.cubicTo( + size.width * 0.8512500, + size.height * 0.7996864, + size.width * 0.8395318, + size.height * 0.8056227, + size.width * 0.8304682, + size.height * 0.7946864); + path_0.cubicTo( + size.width * 0.8107818, + size.height * 0.7703136, + size.width * 0.7921886, + size.height * 0.7443727, + size.width * 0.7751568, + size.height * 0.7168727); + path_0.cubicTo( + size.width * 0.7581250, + size.height * 0.7440636, + size.width * 0.7396886, + size.height * 0.7703136, + size.width * 0.7198432, + size.height * 0.7946864); + path_0.cubicTo( + size.width * 0.7107818, + size.height * 0.8056227, + size.width * 0.6990636, + size.height * 0.7996864, + size.width * 0.6937500, + size.height * 0.7812500); + path_0.lineTo(size.width * 0.6814068, size.height * 0.7378136); + path_0.cubicTo( + size.width * 0.6764068, + size.height * 0.7203136, + size.width * 0.6792182, + size.height * 0.6978136, + size.width * 0.6879682, + size.height * 0.6871864); + path_0.cubicTo( + size.width * 0.7025000, + size.height * 0.6693727, + size.width * 0.7160932, + size.height * 0.6506273, + size.width * 0.7287500, + size.height * 0.6309364); + path_0.cubicTo( + size.width * 0.7164068, + size.height * 0.6046864, + size.width * 0.7054682, + size.height * 0.5778136, + size.width * 0.6959386, + size.height * 0.5506273); + path_0.cubicTo( + size.width * 0.6896886, + size.height * 0.5328136, + size.width * 0.6925000, + size.height * 0.5081273, + size.width * 0.7017182, + size.height * 0.4971864); + path_0.lineTo(size.width * 0.7118750, size.height * 0.4850000); + path_0.lineTo(size.width * 0.7232818, size.height * 0.4715636); + path_0.cubicTo( + size.width * 0.7317182, + size.height * 0.4615636, + size.width * 0.7426568, + size.height * 0.4662500, + size.width * 0.7482818, + size.height * 0.4821864); + path_0.cubicTo( + size.width * 0.7560932, + size.height * 0.5040636, + size.width * 0.7651568, + size.height * 0.5259364, + size.width * 0.7754682, + size.height * 0.5475000); + path_0.cubicTo( + size.width * 0.7965614, + size.height * 0.5031273, + size.width * 0.8126568, + size.height * 0.4571864, + size.width * 0.8223432, + size.height * 0.4125000); + path_0.lineTo(size.width * 0.6437500, size.height * 0.4125000); + path_0.cubicTo( + size.width * 0.6334386, + size.height * 0.4125000, + size.width * 0.6250000, + size.height * 0.3956250, + size.width * 0.6250000, + size.height * 0.3750000); + path_0.lineTo(size.width * 0.6250000, size.height * 0.3250000); + path_0.cubicTo( + size.width * 0.6250000, + size.height * 0.3043750, + size.width * 0.6334386, + size.height * 0.2875000, + size.width * 0.6437500, + size.height * 0.2875000); + path_0.lineTo(size.width * 0.7437500, size.height * 0.2875000); + path_0.lineTo(size.width * 0.7437500, size.height * 0.2375000); + path_0.cubicTo( + size.width * 0.7437500, + size.height * 0.2168750, + size.width * 0.7521886, + size.height * 0.2000000, + size.width * 0.7625000, + size.height * 0.2000000); + path_0.lineTo(size.width * 0.7875000, size.height * 0.2000000); + path_0.cubicTo( + size.width * 0.7978136, + size.height * 0.2000000, + size.width * 0.8062500, + size.height * 0.2168750, + size.width * 0.8062500, + size.height * 0.2375000); + path_0.lineTo(size.width * 0.8062500, size.height * 0.2875000); + path_0.lineTo(size.width * 0.9062500, size.height * 0.2875000); + path_0.cubicTo( + size.width * 0.9165636, + size.height * 0.2875000, + size.width * 0.9250000, + size.height * 0.3043750, + size.width * 0.9250000, + size.height * 0.3250000); + path_0.lineTo(size.width * 0.9250000, size.height * 0.3750000); + path_0.close(); + path_0.moveTo(0, size.height * 0.07500000); + path_0.lineTo(0, size.height * 0.9250000); + path_0.cubicTo(0, size.height * 0.9665636, size.width * 0.01671875, + size.height, size.width * 0.03750000, size.height); + path_0.lineTo(size.width * 0.4750000, size.height); + path_0.lineTo(size.width * 0.4750000, 0); + path_0.lineTo(size.width * 0.03750000, 0); + path_0.cubicTo(size.width * 0.01671875, 0, 0, size.height * 0.03343750, 0, + size.height * 0.07500000); + path_0.close(); + path_0.moveTo(size.width * 0.09203136, size.height * 0.7503136); + path_0.lineTo(size.width * 0.1818750, size.height * 0.2218750); + path_0.cubicTo( + size.width * 0.1845314, + size.height * 0.2065623, + size.width * 0.1915625, + size.height * 0.1965623, + size.width * 0.1996875, + size.height * 0.1965623); + path_0.lineTo(size.width * 0.2504682, size.height * 0.1965623); + path_0.cubicTo( + size.width * 0.2584386, + size.height * 0.1965623, + size.width * 0.2656250, + size.height * 0.2068750, + size.width * 0.2682818, + size.height * 0.2218750); + path_0.lineTo(size.width * 0.3581250, size.height * 0.7503136); + path_0.cubicTo( + size.width * 0.3621864, + size.height * 0.7746864, + size.width * 0.3532818, + size.height * 0.8000000, + size.width * 0.3403136, + size.height * 0.8000000); + path_0.lineTo(size.width * 0.3045318, size.height * 0.8000000); + path_0.cubicTo( + size.width * 0.3004886, + size.height * 0.7999955, + size.width * 0.2965523, + size.height * 0.7973727, + size.width * 0.2933159, + size.height * 0.7925318); + path_0.cubicTo( + size.width * 0.2900773, + size.height * 0.7876864, + size.width * 0.2877091, + size.height * 0.7808818, + size.width * 0.2865614, + size.height * 0.7731227); + path_0.lineTo(size.width * 0.2718750, size.height * 0.6734364); + path_0.lineTo(size.width * 0.1778125, size.height * 0.6734364); + path_0.lineTo(size.width * 0.1635936, size.height * 0.7728136); + path_0.cubicTo( + size.width * 0.1612500, + size.height * 0.7887500, + size.width * 0.1539061, + size.height * 0.8000000, + size.width * 0.1456250, + size.height * 0.8000000); + path_0.lineTo(size.width * 0.1098439, size.height * 0.8000000); + path_0.cubicTo( + size.width * 0.09703136, + size.height * 0.8000000, + size.width * 0.08796886, + size.height * 0.7746864, + size.width * 0.09203136, + size.height * 0.7503136); + path_0.close(); + + final Paint paint0fill = Paint()..style = PaintingStyle.fill; + paint0fill.color = const Color(0xff737373).withOpacity(1.0); + canvas.drawPath(path_0, paint0fill); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} diff --git a/lib/custom_painters/talawa_logo.dart b/lib/custom_painters/talawa_logo.dart new file mode 100644 index 000000000..d648c8c5d --- /dev/null +++ b/lib/custom_painters/talawa_logo.dart @@ -0,0 +1,379 @@ +import 'package:flutter/material.dart'; + +class AppLogo extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final Path path_0 = Path(); + path_0.moveTo(size.width * 0.6581984, size.height * 0.6200040); + path_0.cubicTo( + size.width * 0.6951984, + size.height * 0.6792024, + size.width * 0.7321984, + size.height * 0.7386032, + size.width * 0.7695992, + size.height * 0.7978016); + path_0.cubicTo( + size.width * 0.7737976, + size.height * 0.8044008, + size.width * 0.7730000, + size.height * 0.8070040, + size.width * 0.7664008, + size.height * 0.8112024); + path_0.cubicTo( + size.width * 0.7314008, + size.height * 0.8330040, + size.width * 0.6964008, + size.height * 0.8552024, + size.width * 0.6617976, + size.height * 0.8776032); + path_0.cubicTo( + size.width * 0.6560000, + size.height * 0.8814008, + size.width * 0.6534008, + size.height * 0.8820040, + size.width * 0.6491984, + size.height * 0.8750040); + path_0.cubicTo( + size.width * 0.5995992, + size.height * 0.7930040, + size.width * 0.5495992, + size.height * 0.7112024, + size.width * 0.4997976, + size.height * 0.6292024); + path_0.cubicTo( + size.width * 0.4941984, + size.height * 0.6198016, + size.width * 0.4940000, + size.height * 0.6200040, + size.width * 0.4884008, + size.height * 0.6292024); + path_0.cubicTo( + size.width * 0.4380000, + size.height * 0.7114008, + size.width * 0.3873992, + size.height * 0.7938016, + size.width * 0.3371992, + size.height * 0.8762024); + path_0.cubicTo( + size.width * 0.3337992, + size.height * 0.8818016, + size.width * 0.3317992, + size.height * 0.8816032, + size.width * 0.3267992, + size.height * 0.8784008); + path_0.cubicTo( + size.width * 0.2913992, + size.height * 0.8558016, + size.width * 0.2559992, + size.height * 0.8334008, + size.width * 0.2203992, + size.height * 0.8112024); + path_0.cubicTo( + size.width * 0.2165992, + size.height * 0.8088016, + size.width * 0.2131992, + size.height * 0.8076032, + size.width * 0.2167992, + size.height * 0.8018016); + path_0.cubicTo( + size.width * 0.2547992, + size.height * 0.7420040, + size.width * 0.2923992, + size.height * 0.6818016, + size.width * 0.3301992, + size.height * 0.6218016); + path_0.cubicTo( + size.width * 0.3575992, + size.height * 0.5766032, + size.width * 0.3853992, + size.height * 0.5320040, + size.width * 0.4130000, + size.height * 0.4870040); + path_0.cubicTo( + size.width * 0.4191984, + size.height * 0.4768016, + size.width * 0.4255992, + size.height * 0.4666032, + size.width * 0.4317976, + size.height * 0.4564008); + path_0.cubicTo( + size.width * 0.4347976, + size.height * 0.4516032, + size.width * 0.4377976, + size.height * 0.4468016, + size.width * 0.4441984, + size.height * 0.4458016); + path_0.cubicTo( + size.width * 0.4541984, + size.height * 0.4452024, + size.width * 0.4640000, + size.height * 0.4472024, + size.width * 0.4740000, + size.height * 0.4474008); + path_0.cubicTo( + size.width * 0.4960000, + size.height * 0.4482024, + size.width * 0.5167976, + size.height * 0.4438016, + size.width * 0.5377976, + size.height * 0.4382024); + path_0.cubicTo( + size.width * 0.5435992, + size.height * 0.4374008, + size.width * 0.5470000, + size.height * 0.4408016, + size.width * 0.5497976, + size.height * 0.4454008); + path_0.cubicTo( + size.width * 0.5830000, + size.height * 0.5002024, + size.width * 0.6170000, + size.height * 0.5546032, + size.width * 0.6497976, + size.height * 0.6096032); + path_0.cubicTo( + size.width * 0.6517976, + size.height * 0.6136032, + size.width * 0.6544008, + size.height * 0.6172024, + size.width * 0.6581984, + size.height * 0.6200040); + path_0.close(); + + final Paint paint0fill = Paint()..style = PaintingStyle.fill; + paint0fill.color = const Color(0xff31BB6B).withOpacity(1.0); + canvas.drawPath(path_0, paint0fill); + + final Path path_1 = Path(); + path_1.moveTo(size.width * 0.5405992, size.height * 0.4396032); + path_1.cubicTo( + size.width * 0.5084008, + size.height * 0.4512024, + size.width * 0.4754008, + size.height * 0.4532024, + size.width * 0.4418016, + size.height * 0.4472024); + path_1.cubicTo( + size.width * 0.4292024, + size.height * 0.4490000, + size.width * 0.4184008, + size.height * 0.4428016, + size.width * 0.4078016, + size.height * 0.4382024); + path_1.cubicTo( + size.width * 0.3772004, + size.height * 0.4248016, + size.width * 0.3516004, + size.height * 0.4048016, + size.width * 0.3326004, + size.height * 0.3768020); + path_1.cubicTo( + size.width * 0.3302004, + size.height * 0.3732020, + size.width * 0.3276004, + size.height * 0.3696020, + size.width * 0.3278004, + size.height * 0.3650020); + path_1.cubicTo( + size.width * 0.3124004, + size.height * 0.3368020, + size.width * 0.3032004, + size.height * 0.3070020, + size.width * 0.3040004, + size.height * 0.2746020); + path_1.cubicTo( + size.width * 0.3056004, + size.height * 0.2126020, + size.width * 0.3322004, + size.height * 0.1636020, + size.width * 0.3842004, + size.height * 0.1300020); + path_1.cubicTo( + size.width * 0.4872024, + size.height * 0.06320198, + size.width * 0.6244008, + size.height * 0.1212020, + size.width * 0.6494008, + size.height * 0.2418020); + path_1.cubicTo( + size.width * 0.6554008, + size.height * 0.2704020, + size.width * 0.6535992, + size.height * 0.2994020, + size.width * 0.6452024, + size.height * 0.3276020); + path_1.cubicTo( + size.width * 0.6444008, + size.height * 0.3302020, + size.width * 0.6440000, + size.height * 0.3328020, + size.width * 0.6434008, + size.height * 0.3354020); + path_1.cubicTo( + size.width * 0.6380000, + size.height * 0.3574020, + size.width * 0.6260000, + size.height * 0.3756020, + size.width * 0.6118016, + size.height * 0.3924020); + path_1.cubicTo( + size.width * 0.5960000, + size.height * 0.4108016, + size.width * 0.5770000, + size.height * 0.4252024, + size.width * 0.5548016, + size.height * 0.4354008); + path_1.cubicTo( + size.width * 0.5500000, + size.height * 0.4376032, + size.width * 0.5455992, + size.height * 0.4394008, + size.width * 0.5405992, + size.height * 0.4396032); + path_1.close(); + + final Paint paint1fill = Paint()..style = PaintingStyle.fill; + paint1fill.color = const Color(0xffFEBC59).withOpacity(1.0); + canvas.drawPath(path_1, paint1fill); + + final Path path_2 = Path(); + path_2.moveTo(size.width * 0.5405992, size.height * 0.4395992); + path_2.cubicTo( + size.width * 0.5880000, + size.height * 0.4200000, + size.width * 0.6224008, + size.height * 0.3870008, + size.width * 0.6410000, + size.height * 0.3386008); + path_2.cubicTo( + size.width * 0.6414008, + size.height * 0.3374008, + size.width * 0.6425992, + size.height * 0.3364008, + size.width * 0.6434008, + size.height * 0.3354008); + path_2.cubicTo( + size.width * 0.6850000, + size.height * 0.4020000, + size.width * 0.7265992, + size.height * 0.4688016, + size.width * 0.7685992, + size.height * 0.5352024); + path_2.cubicTo( + size.width * 0.7742024, + size.height * 0.5440000, + size.width * 0.7724008, + size.height * 0.5470000, + size.width * 0.7642024, + size.height * 0.5520000); + path_2.cubicTo( + size.width * 0.7285992, + size.height * 0.5740000, + size.width * 0.6924008, + size.height * 0.5954008, + size.width * 0.6584008, + size.height * 0.6200000); + path_2.cubicTo( + size.width * 0.6535992, + size.height * 0.6225992, + size.width * 0.6520000, + size.height * 0.6190000, + size.width * 0.6502024, + size.height * 0.6158016); + path_2.cubicTo( + size.width * 0.6158016, + size.height * 0.5590000, + size.width * 0.5814008, + size.height * 0.5022024, + size.width * 0.5470000, + size.height * 0.4455992); + path_2.cubicTo( + size.width * 0.5452024, + size.height * 0.4430000, + size.width * 0.5440000, + size.height * 0.4402024, + size.width * 0.5405992, + size.height * 0.4395992); + path_2.close(); + + final Paint paint2fill = Paint()..style = PaintingStyle.fill; + paint2fill.color = const Color(0xff737373).withOpacity(1.0); + canvas.drawPath(path_2, paint2fill); + + final Path path_3 = Path(); + path_3.moveTo(size.width * 0.3277992, size.height * 0.3651976); + path_3.cubicTo( + size.width * 0.3541992, + size.height * 0.4085992, + size.width * 0.3929992, + size.height * 0.4351984, + size.width * 0.4417976, + size.height * 0.4471984); + path_3.cubicTo( + size.width * 0.4287976, + size.height * 0.4623968, + size.width * 0.4197976, + size.height * 0.4803968, + size.width * 0.4094008, + size.height * 0.4973968); + path_3.cubicTo( + size.width * 0.3847992, + size.height * 0.5373968, + size.width * 0.3591992, + size.height * 0.5765992, + size.width * 0.3361992, + size.height * 0.6173968); + path_3.cubicTo( + size.width * 0.3347992, + size.height * 0.6197976, + size.width * 0.3333992, + size.height * 0.6219960, + size.width * 0.3301992, + size.height * 0.6215992); + path_3.cubicTo( + size.width * 0.3235992, + size.height * 0.6143968, + size.width * 0.3149992, + size.height * 0.6097976, + size.width * 0.3069992, + size.height * 0.6045992); + path_3.cubicTo( + size.width * 0.2783992, + size.height * 0.5863968, + size.width * 0.2493992, + size.height * 0.5685992, + size.width * 0.2205992, + size.height * 0.5507976); + path_3.cubicTo( + size.width * 0.2163992, + size.height * 0.5481984, + size.width * 0.2131992, + size.height * 0.5465992, + size.width * 0.2169992, + size.height * 0.5403968); + path_3.cubicTo( + size.width * 0.2535992, + size.height * 0.4825992, + size.width * 0.2899992, + size.height * 0.4243968, + size.width * 0.3263992, + size.height * 0.3663976); + path_3.cubicTo( + size.width * 0.3265992, + size.height * 0.3659976, + size.width * 0.3271992, + size.height * 0.3655976, + size.width * 0.3277992, + size.height * 0.3651976); + path_3.close(); + + final Paint paint3fill = Paint()..style = PaintingStyle.fill; + paint3fill.color = const Color(0xff737373).withOpacity(1.0); + canvas.drawPath(path_3, paint3fill); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} diff --git a/lib/demo_server_data/events_demo_data.dart b/lib/demo_server_data/events_demo_data.dart new file mode 100644 index 000000000..f2e4ac0a6 --- /dev/null +++ b/lib/demo_server_data/events_demo_data.dart @@ -0,0 +1,193 @@ +const eventsDemoData = [ + { + "title": "Calculus", + "description": + "This course introduces calculus using analytic geometry functions. Topics include limits and continuity, derivatives, optimization, related rates, graphing and other applications of derivatives, definite and indefinite integrals, and numerical integration.", + "attendees": "50", + "location": "Lostilos", + "recurring": "false", + "allDay": "true", + "startDate": "1 Aug", + "endDate": "15 Aug", + "startTime": "11am", + "endTime": "3pm", + "recurrence": "MONTHLY", + "isPublic": "true", + "isSubscribed": "false", + "isRegisterable": "true", + "creator": { + "firstName": "Utkarsh", + "lastName": "Shendge", + "_id": "asdasdasd" + }, + "registrants": [ + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + {"firstName": "Warren", "lastName": "Buff", "_id": "asdasdasd"}, + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + {"firstName": "Warren", "lastName": "Buff", "_id": "asdasdasd"}, + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + {"firstName": "Warren", "lastName": "Buff", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + ], + "admins": [ + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + ], + "organization": { + "_id": '1', + "name": 'Mathematicians', + "image": ' ', + "creator": { + "firstName": "Utkarsh", + "lastName": "Shendge", + "_id": "asdasdasd" + } + }, + }, + { + "title": "UI/UX", + "description": + "UX design refers to the term “user experience design”, while UI stands for “user interface design”. Both elements are crucial to a product and work closely together.", + "attendees": "80", + "location": "Tokyo, Japan", + "recurring": "false", + "allDay": "true", + "startDate": "5 May", + "endDate": "13 May", + "startTime": "2am", + "endTime": "3pm", + "recurrence": "MONTHLY", + "isPublic": "true", + "isSubscribed": "true", + "isRegisterable": "true", + "creator": { + "firstName": "Rutvik", + "lastName": "Chandla", + "_id": "asdasdasd" + }, + "registrants": [ + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + {"firstName": "Warren", "lastName": "Buff", "_id": "asdasdasd"}, + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + {"firstName": "Warren", "lastName": "Buff", "_id": "asdasdasd"}, + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + {"firstName": "Warren", "lastName": "Buff", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + ], + "admins": [ + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + ], + "organization": { + "_id": '1', + "name": 'Courses', + "image": ' ', + "creator": { + "firstName": "Rutvik", + "lastName": "Chandla", + "_id": "asdasdasd" + } + }, + }, + { + "title": "System Design", + "description": + "Systems design is the process of defining the architecture, product design, modules, interfaces, and data for a system to satisfy specified requirements. Systems design could be seen as the application of systems theory to product development.", + "attendees": "29", + "location": "Shimla, India", + "recurring": "false", + "allDay": "true", + "startDate": "15 Dec", + "endDate": "18 Aug", + "startTime": "8am", + "endTime": "3pm", + "recurrence": "MONTHLY", + "isPublic": "false", + "isSubscribed": "false", + "isRegisterable": "true", + "creator": { + "firstName": "Ritik", + "lastName": "Srivastav", + "_id": "asdasdasd" + }, + "registrants": [ + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + {"firstName": "Warren", "lastName": "Buff", "_id": "asdasdasd"}, + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + {"firstName": "Warren", "lastName": "Buff", "_id": "asdasdasd"}, + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + {"firstName": "Warren", "lastName": "Buff", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + ], + "admins": [ + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + ], + "organization": { + "_id": '1', + "image": ' ', + "name": 'Computer Science', + "creator": { + "firstName": "Ritik", + "lastName": "Srivastav", + "_id": "asdasdasd" + } + }, + }, + { + "title": "Gaming", + // ignore: missing_whitespace_between_adjacent_strings + "description": + "Cyberpunk 2077 is a 2020 action role-playing v_ideo game developed and published by CD Projekt. The story takes place in Night City, an open world set in the Cyberpunk universe.", + + "attendees": "5k+", + "location": "Nagpur, India", + "recurring": "false", + "allDay": "true", + "startDate": "1 Aug", + "endDate": "15 Aug", + "startTime": "11am", + "endTime": "3pm", + "recurrence": "MONTHLY", + "isPublic": "true", + "isSubscribed": "true", + "isRegisterable": "true", + + "creator": { + "firstName": "Utkarsh", + "lastName": "Shendge", + "_id": "asdasdasd" + }, + "registrants": [ + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + {"firstName": "Warren", "lastName": "Buff", "_id": "asdasdasd"}, + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + {"firstName": "Warren", "lastName": "Buff", "_id": "asdasdasd"}, + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + {"firstName": "Warren", "lastName": "Buff", "_id": "asdasdasd"}, + {"firstName": "Bustin", "lastName": "Jiber", "_id": "asdasdasd"}, + ], + "admins": [ + {"firstName": "Utkarsh", "lastName": "Shendge", "_id": "asdasdasd"}, + ], + "organization": { + "_id": '1', + "image": ' ', + "name": 'Cyclone', + "creator": { + "firstName": "Utkarsh", + "lastName": "Shendge", + "_id": "asdasdasd" + } + }, + } +]; diff --git a/lib/demo_server_data/pinned_post_demo_data.dart b/lib/demo_server_data/pinned_post_demo_data.dart new file mode 100644 index 000000000..7ccd64aad --- /dev/null +++ b/lib/demo_server_data/pinned_post_demo_data.dart @@ -0,0 +1,154 @@ +const pinnedPostsDemoData = [ + { + "description": + "Flutter is Google’s mobile UI framework for crafting high-quality native interfaces on iOS and Android in record time. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source.", + "createdAt": "created at string", + "imageUrl": "image url string", + "creator": { + "firstName": "Rutvik", + "lastName": "Chandla", + "id": "asdasdasd" + }, + "likedBy": [ + {"firstName": "User", "lastName": "1", "id": "asdasdasdas"}, + {"firstName": "User", "lastName": "2", "id": "asdasdasdas"}, + {"firstName": "User", "lastName": "3", "id": "asdasdasdas"} + ], + "comments": [ + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + ] + }, + { + "description": + "Flutter is Google’s mobile UI framework for crafting high-quality native interfaces on iOS and Android in record time. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source.", + "createdAt": "created at string", + "imageUrl": "image url string", + "creator": { + "firstName": "Ranchhod Das", + "lastName": "Chanchad", + "id": "asdasdasd" + }, + "likedBy": [ + {"firstName": "User", "lastName": "1", "id": "asdasdasdas"}, + {"firstName": "User", "lastName": "2", "id": "asdasdasdas"}, + {"firstName": "User", "lastName": "3", "id": "asdasdasdas"} + ], + "comments": [ + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + ] + }, + { + "description": + "Flutter is Google’s mobile UI framework for crafting high-quality native interfaces on iOS and Android in record time. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source.", + "createdAt": "created at string", + "imageUrl": "image url string", + "creator": { + "firstName": "Ritik", + "lastName": "Srivastav", + "id": "asdasdasd" + }, + "likedBy": [ + {"firstName": "User", "lastName": "1", "id": "asdasdasdas"}, + {"firstName": "User", "lastName": "2", "id": "asdasdasdas"}, + {"firstName": "User", "lastName": "3", "id": "asdasdasdas"} + ], + "comments": [ + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + ] + }, + { + "description": + "Flutter is Google’s mobile UI framework for crafting high-quality native interfaces on iOS and Android in record time. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source.", + "createdAt": "created at string", + "imageUrl": "image url string", + "creator": { + "firstName": "Utkarsh", + "lastName": "Shendge", + "id": "asdasdasd" + }, + "likedBy": [ + {"firstName": "User", "lastName": "1", "id": "asdasdasdas"}, + {"firstName": "User", "lastName": "2", "id": "asdasdasdas"}, + {"firstName": "User", "lastName": "3", "id": "asdasdasdas"} + ], + "comments": [ + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + { + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "id": "asdasdasdas"} + }, + ] + }, +]; diff --git a/lib/demo_server_data/post_demo_data.dart b/lib/demo_server_data/post_demo_data.dart new file mode 100644 index 000000000..dab6e96f4 --- /dev/null +++ b/lib/demo_server_data/post_demo_data.dart @@ -0,0 +1,178 @@ +const postsDemoData = [ + { + "_id": "asdasdasd", + "text": + "Flutter is Google’s mobile UI framework for crafting high-quality native interfaces on iOS and Android in record time. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source.", + "createdAt": "created at string", + "imageUrl": "image url string", + "creator": { + "firstName": "Rutvik", + "lastName": "Chandla", + "_id": "asdasdasd" + }, + "likedBy": [ + {"firstName": "User", "lastName": "1", "_id": "asdasdasdas"}, + {"firstName": "User", "lastName": "2", "_id": "asdasdasdas"}, + {"firstName": "User", "lastName": "3", "_id": "asdasdasdas"} + ], + "comments": [ + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "1", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "2", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "3", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "5", "_id": "asdasdasdas"} + }, + ] + }, + { + "_id": "asdasdasd", + "text": + "Flutter is Google’s mobile UI framework for crafting high-quality native interfaces on iOS and Andro_id in record time. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source.", + "createdAt": "created at string", + "imageUrl": "image url string", + "creator": { + "firstName": "Ranchhod Das Chhanchad", + "lastName": "Chandla", + "_id": "asdasdasd" + }, + "likedBy": [ + {"firstName": "User", "lastName": "1", "_id": "asdasdasdas"}, + {"firstName": "User", "lastName": "2", "_id": "asdasdasdas"}, + {"firstName": "User", "lastName": "3", "_id": "asdasdasdas"} + ], + "comments": [ + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + ] + }, + { + "_id": "asdasdasd", + "text": + "Flutter is Google’s mobile UI framework for crafting high-quality native interfaces on iOS and Andro_id in record time. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source.", + "createdAt": "created at string", + "imageUrl": "image url string", + "creator": { + "firstName": "Ritik", + "lastName": "Srivastav", + "_id": "asdasdasd" + }, + "likedBy": [ + {"firstName": "User", "lastName": "1", "_id": "asdasdasdas"}, + {"firstName": "User", "lastName": "2", "_id": "asdasdasdas"}, + {"firstName": "User", "lastName": "3", "_id": "asdasdasdas"} + ], + "comments": [ + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + ] + }, + { + "_id": "asdasdasd", + "text": + "Flutter is Google’s mobile UI framework for crafting high-quality native interfaces on iOS and Andro_id in record time. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source.", + "createdAt": "created at string", + "imageUrl": "image url string", + "creator": { + "firstName": "Utkarsh", + "lastName": "Shengde", + "_id": "asdasdasd" + }, + "likedBy": [ + {"firstName": "User", "lastName": "1", "_id": "asdasdasdas"}, + {"firstName": "User", "lastName": "2", "_id": "asdasdasdas"}, + {"firstName": "User", "lastName": "3", "_id": "asdasdasdas"} + ], + "comments": [ + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + { + "_id": "commentID", + "text": "This is the posted comment", + "creator": {"firstName": "User", "lastName": "4", "_id": "asdasdasdas"} + }, + ] + }, +]; diff --git a/lib/enums/enums.dart b/lib/enums/enums.dart new file mode 100644 index 000000000..a07ebc181 --- /dev/null +++ b/lib/enums/enums.dart @@ -0,0 +1,9 @@ +/// Represents the state of the view +enum ViewState { + idle, + busy, +} + +enum TileType { user, org, option } + +enum CallFor { login, signup, joinPublicOrg } diff --git a/lib/locator.dart b/lib/locator.dart new file mode 100644 index 000000000..3e686ff59 --- /dev/null +++ b/lib/locator.dart @@ -0,0 +1,83 @@ +import 'package:get_it/get_it.dart'; +import 'package:talawa/main.dart'; +import 'package:talawa/services/comment_service.dart'; +import 'package:talawa/services/database_mutation_functions.dart'; +import 'package:talawa/services/event_service.dart'; +import 'package:talawa/services/graphql_config.dart'; +import 'package:talawa/services/navigation_service.dart'; +import 'package:talawa/services/post_service.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/services/user_config.dart'; +import 'package:talawa/view_model/lang_view_model.dart'; +import 'package:talawa/utils/queries.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/create_event_view_model.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart'; +import 'package:talawa/view_model/after_auth_view_models/feed_view_models/organization_feed_view_model.dart'; +import 'package:talawa/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart'; +import 'package:talawa/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart'; +import 'package:talawa/view_model/main_screen_view_model.dart'; +import 'package:talawa/view_model/pre_auth_view_models/login_view_model.dart'; +import 'package:talawa/view_model/pre_auth_view_models/select_organization_view_model.dart'; +import 'package:talawa/view_model/pre_auth_view_models/set_url_view_model.dart'; +import 'package:talawa/view_model/pre_auth_view_models/signup_details_view_model.dart'; +import 'package:talawa/view_model/pre_auth_view_models/waiting_view_model.dart'; +import 'package:talawa/view_model/widgets_view_models/comments_view_model.dart'; +import 'package:talawa/view_model/widgets_view_models/custom_drawer_view_model.dart'; +import 'package:talawa/view_model/widgets_view_models/like_button_view_model.dart'; +import 'package:talawa/view_model/widgets_view_models/progress_dialog_view_model.dart'; + +GetIt locator = GetIt.instance; +final userConfig = locator(); +final navigationService = locator(); +final databaseFunctions = locator(); +final graphqlConfig = locator(); +final sizeConfig = locator(); +final queries = locator(); + +void setupLocator() { + //services + locator.registerSingleton(NavigationService()); + + //sizeConfig + locator.registerSingleton(SizeConfig()); + + //userConfig + locator.registerSingleton(UserConfig()); + + //Services + locator.registerLazySingleton(() => PostService()); + locator.registerLazySingleton(() => EventService()); + locator.registerLazySingleton(() => CommentService()); + + //graphql + locator.registerSingleton(GraphqlConfig()); + + //databaseMutationFunction + locator.registerSingleton(DataBaseMutationFunctions()); + + //queries + locator.registerSingleton(Queries()); + + //Page viewModels + locator.registerFactory(() => DemoViewModel()); + // locator.registerFactory(() => OrganizationFeedViewModel()); + locator.registerFactory(() => OrganizationFeedViewModel()); + locator.registerFactory(() => SetUrlViewModel()); + locator.registerFactory(() => LoginViewModel()); + + locator.registerFactory(() => SelectOrganizationViewModel()); + locator.registerFactory(() => SignupDetailsViewModel()); + locator.registerFactory(() => WaitingViewModel()); + locator.registerFactory(() => ExploreEventsViewModel()); + locator.registerFactory(() => MainScreenViewModel()); + locator.registerFactory(() => ProfilePageViewModel()); + locator.registerFactory(() => EditProfilePageViewModel()); + locator.registerFactory(() => CreateEventViewModel()); + + //Widgets viewModels + locator.registerFactory(() => ProgressDialogViewModel()); + locator.registerFactory(() => CustomDrawerViewModel()); + locator.registerFactory(() => LikeButtonViewModel()); + locator.registerFactory(() => AppLanguage()); + locator.registerFactory(() => CommentsViewModel()); +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 000000000..98f148fd6 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,108 @@ +import 'dart:io'; +import 'package:flutter_localizations/flutter_localizations.dart'; + +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:path_provider/path_provider.dart' as path; +import 'package:provider/provider.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/lang_view_model.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/constants/custom_theme.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/router.dart' as router; +import 'package:talawa/view_model/base_view_model.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + final Directory dir = await path.getApplicationDocumentsDirectory(); + Hive + ..init(dir.path) + ..registerAdapter(UserAdapter()) + ..registerAdapter(OrgInfoAdapter()); + await Hive.openBox('currentUser'); + await Hive.openBox('currentOrg'); + await Hive.openBox('url'); + setupLocator(); + runApp(MyApp()); +} + +class MyApp extends StatefulWidget { + // This widget is the root of your application. + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) => model.initialize(), + builder: (context, model, child) { + return MaterialApp( + locale: Provider.of(context).appLocal, + supportedLocales: [ + const Locale('en', 'US'), + const Locale('es', 'ES'), + const Locale('fr', 'FR'), + const Locale('hi', 'IN'), + const Locale('zh', 'CN'), + ], + localizationsDelegates: [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + title: 'Talawa', + themeMode: ThemeMode.system, + theme: TalawaTheme.lightTheme, + darkTheme: TalawaTheme.darkTheme, + debugShowCheckedModeBanner: false, + navigatorKey: navigationService.navigatorKey, + onGenerateRoute: router.generateRoute, + localeResolutionCallback: + (Locale? locale, Iterable supportedLocales) { + if (locale == null) { + debugPrint("*language locale is null!!!"); + return supportedLocales.first; + } + for (final Locale supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale.languageCode || + supportedLocale.countryCode == locale.countryCode) { + return supportedLocale; + } + } + return supportedLocales.first; + }, + initialRoute: '/', + ); + }, + ); + } +} + +class DemoPageView extends StatelessWidget { + const DemoPageView({required Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BaseView( + builder: (context, model, child) => Scaffold( + appBar: AppBar( + title: + Text(AppLocalizations.of(context)!.strictTranslate('Demo Page')), + ), + body: Container( + child: Text(model.title), + ), + ), + ); + } +} + +class DemoViewModel extends BaseModel { + final String _title = "Title from the viewMode GSoC branch"; + String get title => _title; +} diff --git a/lib/models/comment/comment_model.dart b/lib/models/comment/comment_model.dart new file mode 100644 index 000000000..46c06a8f3 --- /dev/null +++ b/lib/models/comment/comment_model.dart @@ -0,0 +1,24 @@ +import 'package:talawa/models/user/user_info.dart'; + +class Comment { + Comment({this.text, this.createdAt, this.creator, this.post, this.likeCount}); + + factory Comment.fromJson(Map json) { + return Comment( + text: json['text'] as String?, + createdAt: json['createdAt'] as String?, + creator: json['creator'] == null + ? null + : User.fromJson(json['creator'] as Map, + fromOrg: true), + post: json['post'] as String?, + likeCount: json['likeCount'] as String?, + ); + } + + String? text; + String? createdAt; + User? creator; + String? post; + String? likeCount; +} diff --git a/lib/models/events/event_model.dart b/lib/models/events/event_model.dart new file mode 100644 index 000000000..219a05f35 --- /dev/null +++ b/lib/models/events/event_model.dart @@ -0,0 +1,82 @@ +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/models/user/user_info.dart'; + +class Event { + Event( + {this.id, + this.title, + this.description, + this.attendees, + this.location, + this.recurring, + this.allDay, + this.startDate, + this.endDate, + this.startTime, + this.endTime, + this.recurrence, + this.isPublic, + this.isRegistered, + this.isRegisterable, + this.creator, + this.organization, + this.admins, + this.registrants}); + + factory Event.fromJson( + Map json, + ) { + return Event( + id: json['_id'] as String, + title: json['title'] as String?, + description: json['description'] as String?, + attendees: json['attendees'] as String?, + location: json['location'] as String?, + recurring: json['recurring'] as bool?, + allDay: json['allDay'] as bool?, + startDate: json['startDate'] as String?, + endDate: json['endDate'] as String?, + startTime: json['startTime'] as String?, + endTime: json['endTime'] as String?, + recurrence: json['recurrence'] as String?, + isPublic: json['isPublic'] as bool?, + isRegistered: json['isSubscribed'] as bool?, + isRegisterable: json['isRegisterable'] as bool?, + creator: json['creator'] == null + ? null + : User.fromJson(json['creator'] as Map, + fromOrg: true), + organization: json['organization'] == null + ? null + : OrgInfo.fromJson(json['organization'] as Map), + admins: json['admins'] == null + ? null + : (json['admins'] as List?) + ?.map((e) => + User.fromJson(e as Map, fromOrg: true)) + .toList(), + registrants: (json['registrants'] as List?) + ?.map((e) => User.fromJson(e as Map, fromOrg: true)) + .toList(), + ); + } + String? id; + String? title; + String? description; + String? attendees; + String? location; + bool? recurring; + bool? allDay; + String? startDate; + String? endDate; + String? startTime; + String? endTime; + String? recurrence; + bool? isPublic; + bool? isRegistered; + bool? isRegisterable; + User? creator; + OrgInfo? organization; + List? admins; + List? registrants; +} diff --git a/lib/models/language/language_model.dart b/lib/models/language/language_model.dart new file mode 100644 index 000000000..ad48609ab --- /dev/null +++ b/lib/models/language/language_model.dart @@ -0,0 +1,13 @@ +class Language { + Language({ + required this.countryCode, + required this.langCode, + required this.langName, + required this.langSample, + }); + + final String countryCode; + final String langCode; + final String langName; + final String langSample; +} diff --git a/lib/models/options/options.dart b/lib/models/options/options.dart new file mode 100644 index 000000000..c35d44a1a --- /dev/null +++ b/lib/models/options/options.dart @@ -0,0 +1,15 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class Options { + Options( + {required this.icon, + required this.title, + required this.subtitle, + this.trailingIconButton}); + + Widget icon; + String title; + String subtitle; + IconButton? trailingIconButton; +} diff --git a/lib/models/organization/org_info.dart b/lib/models/organization/org_info.dart new file mode 100644 index 000000000..7bd85bc9a --- /dev/null +++ b/lib/models/organization/org_info.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:talawa/models/user/user_info.dart'; + +part 'org_info.g.dart'; + +@HiveType(typeId: 2) +class OrgInfo { + OrgInfo( + {this.admins, + this.members, + this.creatorInfo, + this.description, + this.id, + this.image, + this.isPublic, + this.name}); + + factory OrgInfo.fromJson(Map json1, + {bool memberRequest = false}) { + Map json; + if (memberRequest) { + json = json1['organization'] as Map; + } else { + json = json1; + } + return OrgInfo( + id: json['_id'] != null ? json['_id'] as String : null, + image: json['image'] != null ? json['image'] as String? : null, + name: json['name'] != null ? json['name'] as String? : null, + description: + json['description'] != null ? json['description'] as String? : null, + isPublic: json['isPublic'] != null ? json['isPublic'] as bool? : null, + creatorInfo: json['creator'] != null + ? User.fromJson(json['creator'] as Map, + fromOrg: true) + : null, + members: json['members'] != null + ? (json['members'] as List?) + ?.map((e) => + User.fromJson(e as Map, fromOrg: true)) + .toList() + : null, + admins: json['admins'] != null + ? (json['admins'] as List?) + ?.map((e) => + User.fromJson(e as Map, fromOrg: true)) + .toList() + : null, + ); + } + + List fromJsonToList(List json) { + final List _orgList = []; + json.forEach((element) { + final OrgInfo org = OrgInfo.fromJson(element as Map); + _orgList.add(org); + }); + return _orgList; + } + + @HiveField(0) + String? image; + @HiveField(1) + String? id; + @HiveField(2) + String? name; + @HiveField(3) + List? admins; + @HiveField(4) + List? members; + @HiveField(5) + String? description; + @HiveField(6) + bool? isPublic; + @HiveField(7) + User? creatorInfo; + + printOrgInfo() { + debugPrint('_id: ${this.id}'); + debugPrint('name: ${this.name}'); + debugPrint('image: ${this.image}'); + debugPrint('description: ${this.description}'); + debugPrint('isPublic: ${this.isPublic}'); + debugPrint('creatorInfo: ${this.creatorInfo}'); + debugPrint('admins: ${this.admins}'); + debugPrint('members: ${this.members}'); + } +} diff --git a/lib/models/organization/org_info.g.dart b/lib/models/organization/org_info.g.dart new file mode 100644 index 000000000..054dae8a6 --- /dev/null +++ b/lib/models/organization/org_info.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'org_info.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class OrgInfoAdapter extends TypeAdapter { + @override + final int typeId = 2; + + @override + OrgInfo read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return OrgInfo( + admins: (fields[3] as List?)?.cast(), + members: (fields[4] as List?)?.cast(), + creatorInfo: fields[7] as User?, + description: fields[5] as String?, + id: fields[1] as String?, + image: fields[0] as String?, + isPublic: fields[6] as bool?, + name: fields[2] as String?, + ); + } + + @override + void write(BinaryWriter writer, OrgInfo obj) { + writer + ..writeByte(8) + ..writeByte(0) + ..write(obj.image) + ..writeByte(1) + ..write(obj.id) + ..writeByte(2) + ..write(obj.name) + ..writeByte(3) + ..write(obj.admins) + ..writeByte(4) + ..write(obj.members) + ..writeByte(5) + ..write(obj.description) + ..writeByte(6) + ..write(obj.isPublic) + ..writeByte(7) + ..write(obj.creatorInfo); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is OrgInfoAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/models/post/post_model.dart b/lib/models/post/post_model.dart new file mode 100644 index 000000000..995e92040 --- /dev/null +++ b/lib/models/post/post_model.dart @@ -0,0 +1,96 @@ +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/models/user/user_info.dart'; + +class Post { + Post( + {required this.sId, + this.description, + this.createdAt, + this.imageUrl, + this.videoUrl, + required this.creator, + this.organization, + this.likedBy, + this.comments}); + + Post.fromJson(Map json) { + sId = json['_id'] as String; + description = json['text'] as String?; + createdAt = DateTime.fromMillisecondsSinceEpoch( + int.parse(json['createdAt'] as String)); + imageUrl = json['imageUrl'] as String?; + videoUrl = json['videoUrl'] as String?; + creator = json['creator'] != null + ? User.fromJson(json['creator'] as Map, fromOrg: true) + : null; + organization = json['organization'] != null + ? OrgInfo.fromJson(json['organization'] as Map) + : null; + if (json['likedBy'] != null) { + likedBy = []; + json['likedBy'].forEach((v) { + likedBy?.add(LikedBy.fromJson(v as Map)); + }); + } + if (json['comments'] != null) { + comments = []; + json['comments'].forEach((v) { + comments?.add(Comments.fromJson(v as Map)); + }); + } + } + + late String sId; + String? description; + DateTime? createdAt; + String? imageUrl; + String? videoUrl; + User? creator; + OrgInfo? organization; + List? likedBy; + List? comments; + + String getPostCreatedDuration() { + if (DateTime.now().difference(this.createdAt!).inSeconds < 60) { + return '${DateTime.now().difference(this.createdAt!).inSeconds} Seconds Ago'; + } else if (DateTime.now().difference(this.createdAt!).inMinutes < 60) { + return '${DateTime.now().difference(this.createdAt!).inMinutes} Minutes Ago'; + } else if (DateTime.now().difference(this.createdAt!).inHours < 24) { + return '${DateTime.now().difference(this.createdAt!).inHours} Hours Ago'; + } else if (DateTime.now().difference(this.createdAt!).inDays < 30) { + return '${DateTime.now().difference(this.createdAt!).inDays} Days Ago'; + } else if (DateTime.now().difference(this.createdAt!).inDays < 365) { + return '${DateTime.now().difference(this.createdAt!).inDays ~/ 30} Months Ago'; + } else { + return '${DateTime.now().difference(this.createdAt!).inDays ~/ 365} Years Ago'; + } + } +} + +class LikedBy { + LikedBy({this.sId}); + LikedBy.fromJson(Map json) { + sId = json['_id'] as String?; + } + + String? sId; + Map toJson() { + final Map data = {}; + data['_id'] = this.sId; + return data; + } +} + +class Comments { + Comments({this.sId}); + Comments.fromJson(Map json) { + sId = json['_id'] as String?; + } + + String? sId; + Map toJson() { + final Map data = {}; + data['_id'] = this.sId; + return data; + } +} diff --git a/lib/models/user/user_info.dart b/lib/models/user/user_info.dart new file mode 100644 index 000000000..6996b9cc8 --- /dev/null +++ b/lib/models/user/user_info.dart @@ -0,0 +1,126 @@ +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:talawa/models/organization/org_info.dart'; + +part 'user_info.g.dart'; + +@HiveType(typeId: 1) +class User extends HiveObject { + User( + {this.adminFor, + this.createdOrganizations, + this.email, + this.firstName, + this.id, + this.image, + this.joinedOrganizations, + this.lastName, + this.authToken, + this.refreshToken, + this.membershipRequests}); + + factory User.fromJson(Map json1, {bool fromOrg = false}) { + Map json; + if (fromOrg) { + json = json1; + } else { + json = json1['user'] as Map; + } + return User( + authToken: fromOrg ? ' ' : json1['accessToken'] as String?, + refreshToken: fromOrg ? ' ' : json1['refreshToken'] as String?, + id: json['_id'] as String?, + firstName: + json['firstName'] != null ? json['firstName'] as String? : null, + lastName: json['lastName'] != null ? json['lastName'] as String? : null, + email: json['email'] != null ? json['email'] as String? : null, + image: json['image'] != null ? json['image'] as String? : null, + adminFor: json['adminFor'] != null + ? (json['adminFor'] as List?) + ?.map((e) => OrgInfo.fromJson(e as Map)) + .toList() + : null, + createdOrganizations: json['createdOrganizations'] != null + ? (json['createdOrganizations'] as List?) + ?.map((e) => OrgInfo.fromJson(e as Map)) + .toList() + : null, + joinedOrganizations: json['joinedOrganizations'] != null + ? (json['joinedOrganizations'] as List?) + ?.map((e) => OrgInfo.fromJson(e as Map)) + .toList() + : null, + membershipRequests: json['membershipRequests'] != null + ? (json['membershipRequests'] as List?) + ?.map((e) => OrgInfo.fromJson(e as Map, + memberRequest: true)) + .toList() + : null); + } + + print() { + debugPrint('authToken: ${this.authToken}'); + debugPrint('refreshToken: ${this.refreshToken}'); + debugPrint('_id: ${this.id}'); + debugPrint('firstName: ${this.firstName}'); + debugPrint('lastName: ${this.lastName}'); + debugPrint('image: ${this.image}'); + debugPrint('email: ${this.email}'); + debugPrint('joinedOrganizations: ${this.joinedOrganizations}'); + debugPrint('adminFor: ${this.adminFor}'); + debugPrint('createdOrganizations: ${this.createdOrganizations}'); + debugPrint('membershipRequests: ${this.membershipRequests}'); + } + + @HiveField(0) + String? authToken; + @HiveField(1) + String? refreshToken; + @HiveField(2) + String? id; + @HiveField(3) + String? firstName; + @HiveField(4) + String? lastName; + @HiveField(5) + String? email; + @HiveField(6) + String? image; + @HiveField(7) + List? joinedOrganizations = []; + @HiveField(8) + List? createdOrganizations = []; + @HiveField(9) + List? adminFor = []; + @HiveField(10) + List? membershipRequests = []; + + updateJoinedOrg(List orgList) { + this.joinedOrganizations = orgList; + } + + updateCreatedOrg(List orgList) { + this.createdOrganizations = orgList; + } + + updateMemberRequestOrg(List orgList) { + this.membershipRequests = [...membershipRequests!, ...orgList]; + } + + updateAdminFor(List orgList) { + this.adminFor = orgList; + } + + update(User details) { + this.firstName = details.firstName; + this.lastName = details.firstName; + this.email = details.firstName; + this.image = details.firstName; + this.authToken = details.authToken; + this.refreshToken = details.refreshToken; + this.joinedOrganizations = details.joinedOrganizations; + this.createdOrganizations = details.createdOrganizations; + this.membershipRequests = details.membershipRequests; + this.adminFor = details.adminFor; + } +} diff --git a/lib/models/user/user_info.g.dart b/lib/models/user/user_info.g.dart new file mode 100644 index 000000000..3b89a5ff1 --- /dev/null +++ b/lib/models/user/user_info.g.dart @@ -0,0 +1,71 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_info.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class UserAdapter extends TypeAdapter { + @override + final int typeId = 1; + + @override + User read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return User( + adminFor: (fields[9] as List?)?.cast(), + createdOrganizations: (fields[8] as List?)?.cast(), + email: fields[5] as String?, + firstName: fields[3] as String?, + id: fields[2] as String?, + image: fields[6] as String?, + joinedOrganizations: (fields[7] as List?)?.cast(), + lastName: fields[4] as String?, + authToken: fields[0] as String?, + refreshToken: fields[1] as String?, + membershipRequests: (fields[10] as List?)?.cast(), + ); + } + + @override + void write(BinaryWriter writer, User obj) { + writer + ..writeByte(11) + ..writeByte(0) + ..write(obj.authToken) + ..writeByte(1) + ..write(obj.refreshToken) + ..writeByte(2) + ..write(obj.id) + ..writeByte(3) + ..write(obj.firstName) + ..writeByte(4) + ..write(obj.lastName) + ..writeByte(5) + ..write(obj.email) + ..writeByte(6) + ..write(obj.image) + ..writeByte(7) + ..write(obj.joinedOrganizations) + ..writeByte(8) + ..write(obj.createdOrganizations) + ..writeByte(9) + ..write(obj.adminFor) + ..writeByte(10) + ..write(obj.membershipRequests); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is UserAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/router.dart b/lib/router.dart new file mode 100644 index 000000000..6f9881f65 --- /dev/null +++ b/lib/router.dart @@ -0,0 +1,118 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:talawa/constants/routing_constants.dart'; +import 'package:talawa/main.dart'; +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/models/post/post_model.dart'; +import 'package:talawa/splash_screen.dart'; +import 'package:talawa/views/after_auth_screens/events/create_event_page.dart'; +import 'package:talawa/views/after_auth_screens/events/event_info_page.dart'; +import 'package:talawa/views/after_auth_screens/events/explore_events.dart'; +import 'package:talawa/views/after_auth_screens/feed/individual_post.dart'; +import 'package:talawa/views/after_auth_screens/feed/organization_feed.dart'; +import 'package:talawa/views/after_auth_screens/feed/pinned_post_page.dart'; +import 'package:talawa/views/after_auth_screens/join_organisation_after_auth.dart'; +import 'package:talawa/views/after_auth_screens/profile/edit_profile_page.dart'; +import 'package:talawa/views/after_auth_screens/profile/profile_page.dart'; +import 'package:talawa/views/main_screen.dart'; +import 'package:talawa/views/pre_auth_screens/change_password.dart'; +import 'package:talawa/views/pre_auth_screens/login.dart'; +import 'package:talawa/views/pre_auth_screens/recover.dart'; +import 'package:talawa/views/pre_auth_screens/select_language.dart'; +import 'package:talawa/views/pre_auth_screens/select_organization.dart'; +import 'package:talawa/views/pre_auth_screens/set_url.dart'; +import 'package:talawa/views/pre_auth_screens/signup_details.dart'; +import 'package:talawa/views/pre_auth_screens/waiting_to_join_private_org.dart'; + +Route generateRoute(RouteSettings settings) { + switch (settings.name) { + case Routes.splashScreen: + return MaterialPageRoute( + builder: (context) => const SplashScreen(key: Key('SplashScreen'))); + case Routes.languageSelectionRoute: + return MaterialPageRoute( + builder: (context) => + const SelectLanguage(key: Key('SelectLanguage'))); + case Routes.setUrlScreen: + final String uri = settings.arguments!.toString(); + return MaterialPageRoute( + builder: (context) => SetUrl( + key: const Key('SetUrl'), + uri: uri, + ), + ); + case Routes.loginScreen: + return MaterialPageRoute( + builder: (context) => const Login(key: Key('Login'))); + case Routes.selectOrgScreen: + final String id = settings.arguments!.toString(); + return CupertinoPageRoute( + builder: (context) => SelectOrganization( + key: const Key('Signup'), + selectedOrgId: id, + ), + ); + case Routes.signupDetailScreen: + final OrgInfo org = settings.arguments! as OrgInfo; + return CupertinoPageRoute( + builder: (context) => SignUpDetails( + key: const Key('Signup'), + selectedOrg: org, + ), + ); + case Routes.waitingScreen: + return CupertinoPageRoute( + builder: (context) => const WaitingPage(key: Key('Waiting'))); + case Routes.recoverScreen: + return MaterialPageRoute( + builder: (context) => const Recover(key: Key('Recover'))); + case Routes.updateScreen: + return MaterialPageRoute( + builder: (context) => const ChangePass(key: Key('Update'))); + case Routes.homeScreen: + return MaterialPageRoute( + builder: (context) => const OrganizationFeed(key: Key('HomeScreen'))); + case Routes.mainScreen: + return MaterialPageRoute( + builder: (context) => const MainScreen(key: Key('MainScreen'))); + case Routes.individualPost: + final Post post = settings.arguments! as Post; + return MaterialPageRoute( + builder: (context) => IndividualPostView( + key: const Key('IndividualPost'), + post: post, + )); + case Routes.pinnedPostPage: + final List pinnedPosts = settings.arguments! as List; + return MaterialPageRoute( + builder: (context) => PinnedPostPage(pinnedPosts: pinnedPosts)); + case Routes.exploreEventsScreen: + return MaterialPageRoute( + builder: (context) => const ExploreEvents(key: Key('ExploreEvents'))); + case Routes.eventInfoPage: + final Event event = settings.arguments! as Event; + return MaterialPageRoute( + builder: (context) => + EventInfoPage(key: const Key('EventInfo'), event: event), + ); + case Routes.createEventPage: + return MaterialPageRoute( + builder: (context) => const CreateEventPage(key: Key('CreateEvent'))); + case Routes.profilePage: + return MaterialPageRoute( + builder: (context) => const ProfilePage(key: Key('Profile'))); + case Routes.editProfilePage: + return MaterialPageRoute( + builder: (context) => const EditProfilePage(key: Key('EditProfile'))); + case Routes.joinOrg: + return MaterialPageRoute( + builder: (context) => const JoinOrganisationAfterAuth( + key: Key('JoinOrganisationAfterAuth'))); + default: + return MaterialPageRoute( + builder: (context) => const DemoPageView( + key: Key("DemoPage"), + )); + } +} diff --git a/lib/services/comment_service.dart b/lib/services/comment_service.dart new file mode 100644 index 000000000..a868b262c --- /dev/null +++ b/lib/services/comment_service.dart @@ -0,0 +1,32 @@ +import 'package:talawa/locator.dart'; +import 'package:talawa/services/database_mutation_functions.dart'; +import 'package:talawa/utils/comment_queries.dart'; + +class CommentService { + CommentService() { + _dbFunctions = locator(); + } + late DataBaseMutationFunctions _dbFunctions; + + Future createComments(String postId, String text) async { + print("comment service called"); + final String _createCommentQuery = CommentQueries().createComment(); + final result = + await _dbFunctions.gqlAuthMutation(_createCommentQuery, variables: { + 'postId': postId, //Add your variables here + 'text': text + }); + print("comment added"); + print(result); + return result; + } + + Future getCommentsForPost(String postId) async { + final String _getCommmentQuery = CommentQueries().getPostsComments(postId); + final result = await _dbFunctions.gqlAuthMutation(_getCommmentQuery); + if (result.data != null) { + return result.data["commentsByPost"] as List; + } + return []; + } +} diff --git a/lib/services/database_mutation_functions.dart b/lib/services/database_mutation_functions.dart new file mode 100644 index 000000000..bad26dba9 --- /dev/null +++ b/lib/services/database_mutation_functions.dart @@ -0,0 +1,183 @@ +import 'package:flutter/material.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/utils/queries.dart'; + +class DataBaseMutationFunctions { + late GraphQLClient clientNonAuth; + late GraphQLClient clientAuth; + late Queries _query; + init() { + clientNonAuth = graphqlConfig.clientToQuery(); + clientAuth = graphqlConfig.authClient(); + _query = Queries(); + } + + GraphQLError userNotFound = const GraphQLError(message: 'User not found'); + GraphQLError userNotAuthenticated = + const GraphQLError(message: 'User is not authenticated'); + GraphQLError emailAccountPresent = + const GraphQLError(message: 'Email address already exists'); + GraphQLError wrongCredentials = + const GraphQLError(message: 'Invalid credentials'); + GraphQLError organizationNotFound = + const GraphQLError(message: 'Organization not found'); + GraphQLError refreshAccessTokenExpiredException = const GraphQLError( + message: + 'Access Token has expired. Please refresh session.: Undefined location'); + GraphQLError memberRequestExist = + const GraphQLError(message: 'Membership Request already exists'); + + bool? encounteredExceptionOrError(OperationException exception, + {bool showSnackBar = true}) { + if (exception.linkException != null) { + debugPrint(exception.linkException.toString()); + if (showSnackBar) { + navigationService.showSnackBar("Server not running/wrong url"); + } + return false; + } else { + debugPrint(exception.graphqlErrors.toString()); + for (int i = 0; i < exception.graphqlErrors.length; i++) { + if (exception.graphqlErrors[i].message == + refreshAccessTokenExpiredException.message) { + print('token refreshed'); + refreshAccessToken(userConfig.currentUser.refreshToken!).then( + (value) => graphqlConfig + .getToken() + .then((value) => databaseFunctions.init())); + print('client refreshed'); + return true; + } else if (exception.graphqlErrors[i].message == + userNotAuthenticated.message) { + print('client refreshed'); + graphqlConfig.getToken().then((value) => databaseFunctions.init()); + return true; + } else if (exception.graphqlErrors[i].message == userNotFound.message) { + if (showSnackBar) { + navigationService + .showSnackBar("No account registered with this email"); + } + return false; + } else if (exception.graphqlErrors[i].message == + memberRequestExist.message) { + if (showSnackBar) { + navigationService.showSnackBar("Membership request already exist"); + } + return false; + } else if (exception.graphqlErrors[i].message == + wrongCredentials.message) { + if (showSnackBar) { + navigationService.showSnackBar("Enter a valid password"); + } + return false; + } else if (exception.graphqlErrors[i].message == + organizationNotFound.message) { + if (showSnackBar) { + navigationService.showSnackBar("Organization Not Found"); + } + return false; + } else if (exception.graphqlErrors[i].message == + emailAccountPresent.message) { + if (showSnackBar) { + navigationService + .showSnackBar("Account with this email already registered"); + } + return false; + } + } + navigationService.showSnackBar("Something went wrong"); + return false; + } + } + + Future gqlAuthQuery(String query) async { + final QueryOptions options = QueryOptions( + document: gql(query), + variables: {}, + ); + final QueryResult result = await clientAuth.query(options); + if (result.hasException) { + final bool? exception = encounteredExceptionOrError(result.exception!); + if (exception!) { + gqlAuthQuery(query); + } + } else if (result.data != null && result.isConcrete) { + return result; + } + return null; + } + + Future gqlAuthMutation(String mutation, + {Map? variables}) async { + final QueryResult result = await clientAuth.mutate(MutationOptions( + document: gql(mutation), + variables: variables ?? {}, + )); + if (result.hasException) { + final bool? exception = encounteredExceptionOrError(result.exception!); + if (exception!) { + gqlAuthMutation(mutation, variables: variables); + } + } else if (result.data != null && result.isConcrete) { + return result; + } + return null; + } + + Future gqlNonAuthMutation(String mutation, + {Map? variables, bool reCall = true}) async { + final QueryResult result = await clientNonAuth.mutate(MutationOptions( + document: gql(mutation), variables: variables ?? {})); + + if (result.hasException) { + final bool? exception = encounteredExceptionOrError(result.exception!); + if (exception! && reCall) { + gqlNonAuthMutation(mutation, variables: variables); + } + } else if (result.data != null && result.isConcrete) { + return result; + } + return null; + } + + Future refreshAccessToken(String refreshToken) async { + final QueryResult result = await clientNonAuth.mutate(MutationOptions( + document: gql( + _query.refreshToken(refreshToken), + ), + )); + + if (result.hasException) { + final bool? exception = encounteredExceptionOrError(result.exception!); + if (exception!) { + refreshAccessToken(refreshToken); + } else { + navigationService.pop(); + } + } else if (result.data != null && result.isConcrete) { + userConfig.updateAccessToken( + refreshToken: result.data!['refreshToken']['refreshToken'].toString(), + accessToken: result.data!['refreshToken']['accessToken'].toString()); + databaseFunctions.init(); + return true; + } + return false; + } + + Future fetchOrgById(String id) async { + final QueryResult result = await clientNonAuth + .mutate(MutationOptions(document: gql(_query.fetchOrgById(id)))); + if (result.hasException) { + final bool? exception = encounteredExceptionOrError(result.exception!); + if (exception!) { + fetchOrgById(id); + } + } else if (result.data != null && result.isConcrete) { + return OrgInfo.fromJson( + result.data!['organizations'][0] as Map); + } + return false; + } +} diff --git a/lib/services/event_service.dart b/lib/services/event_service.dart new file mode 100644 index 000000000..0afec2b43 --- /dev/null +++ b/lib/services/event_service.dart @@ -0,0 +1,106 @@ +import 'dart:async'; + +import 'package:intl/intl.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/services/database_mutation_functions.dart'; +import 'package:talawa/services/user_config.dart'; +import 'package:talawa/utils/event_queries.dart'; + +class EventService { + EventService() { + _eventStream = _eventStreamController.stream.asBroadcastStream(); + _currentOrg = _userConfig.currentOrg; + setOrgStreamSubscription(); + } + + final _userConfig = locator(); + final _dbFunctions = locator(); + + late OrgInfo _currentOrg; + late StreamSubscription _currentOrganizationStreamSubscription; + late Stream _eventStream; + + final List _events = []; + final StreamController _eventStreamController = + StreamController(); + Stream get eventStream => _eventStream; + // ignore: prefer_final_fields + Set _ids = {}; + + void setOrgStreamSubscription() { + _currentOrganizationStreamSubscription = + _userConfig.currentOrfInfoStream.listen((updatedOrganization) { + _currentOrg = updatedOrganization; + _events.clear(); + _ids.clear(); + getEvents(); + }); + } + + Future getEvents() async { + final String currentOrgID = _currentOrg.id!; + _dbFunctions.init(); + final String mutation = EventQueries().fetchOrgEvents(currentOrgID); + + final result = await _dbFunctions.gqlAuthMutation(mutation); + if (result.data!["events"] == null) return; + final List eventsJson = result.data!["events"] as List; + eventsJson.forEach((eventJsonData) { + final Event event = Event.fromJson(eventJsonData as Map); + if (!_ids.contains(event.id)) { + _ids.add(event.id!); + _events.add(event); + } + }); + + _events.removeWhere( + (element) => + int.tryParse(element.startTime!) == null || + int.tryParse(element.endTime!) == null || + element.organization!.id != _currentOrg.id, + ); + _events.sort((a, b) { + return DateTime.fromMicrosecondsSinceEpoch(int.parse(a.startTime!)) + .compareTo( + DateTime.fromMicrosecondsSinceEpoch(int.parse(b.startTime!))); + }); + _parseEventDateTime(); + + _events.forEach((element) { + _eventStreamController.add(element); + }); + } + + Future registerForAnEvent(String eventId) async { + final tokenResult = await _dbFunctions + .refreshAccessToken(userConfig.currentUser.refreshToken!); + print(tokenResult); + final Map variables = {'eventId': eventId}; + final result = await _dbFunctions.gqlAuthMutation( + EventQueries().registerForEvent(), + variables: variables, + ); + print(result); + } + + void _parseEventDateTime() { + _events.forEach((element) { + final DateTime _startDate = + DateTime.fromMicrosecondsSinceEpoch(int.parse(element.startTime!)) + .toLocal(); + final DateTime _endDate = + DateTime.fromMicrosecondsSinceEpoch(int.parse(element.endTime!)) + .toLocal(); + element.startDate = DateFormat('yMd').format(_startDate); + element.endDate = DateFormat('yMd').format(_endDate); + element.startTime = DateFormat.jm().format(_startDate); + element.endTime = DateFormat.jm().format(_endDate); + }); + } + + void dispose() { + _currentOrganizationStreamSubscription.cancel(); + } +} diff --git a/lib/services/graphql_config.dart b/lib/services/graphql_config.dart new file mode 100644 index 000000000..6755b69cb --- /dev/null +++ b/lib/services/graphql_config.dart @@ -0,0 +1,70 @@ +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; +import 'package:mockito/mockito.dart'; +import 'package:talawa/locator.dart'; + +class GraphqlConfig { + static const imageUrlKey = "imageUrl"; + static const urlKey = "url"; + static String? orgURI = ' '; + static String? token; + late HttpLink httpLink; + +//prefix route for showing images + String? displayImgRoute; + + Future getToken() async { + final _token = userConfig.currentUser.authToken; + token = _token; + getOrgUrl(); + return true; + } + + getOrgUrl() { + final box = Hive.box('url'); + final String? url = box.get(urlKey) as String?; + final String? imgUrl = box.get(imageUrlKey) as String?; + orgURI = url ?? ' '; + displayImgRoute = imgUrl ?? ' '; + httpLink = HttpLink(orgURI!); + clientToQuery(); + authClient(); + } + + GraphQLClient clientToQuery() { + return GraphQLClient( + cache: GraphQLCache(partialDataPolicy: PartialDataCachePolicy.accept), + link: httpLink, + ); + } + + GraphQLClient authClient() { + final AuthLink authLink = AuthLink(getToken: () async => 'Bearer $token'); + final Link finalAuthLink = authLink.concat(httpLink); + return GraphQLClient( + cache: GraphQLCache(partialDataPolicy: PartialDataCachePolicy.accept), + link: finalAuthLink, + ); + } + + test() { + httpLink = HttpLink('https://talawa-graphql-api.herokuapp.com/graphql', + httpClient: MockHttpClient()); + } +} + +class MockHttpClient extends Mock implements http.Client { + @override + Future send(http.BaseRequest? request) => + // ignore: invalid_use_of_visible_for_testing_member + super.noSuchMethod( + Invocation.method(#send, [request]), + returnValue: Future.value( + http.StreamedResponse( + Stream.fromIterable(const [[]]), + 500, + ), + ), + ) as Future; +} diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart new file mode 100644 index 000000000..90b795382 --- /dev/null +++ b/lib/services/navigation_service.dart @@ -0,0 +1,56 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class NavigationService { + GlobalKey navigatorKey = GlobalKey(); + + Future pushScreen(String routeName, {dynamic arguments}) { + return navigatorKey.currentState! + .pushNamed(routeName, arguments: arguments); + } + + Future popAndPushScreen(String routeName, {dynamic arguments}) { + navigatorKey.currentState!.pop(); + return pushScreen(routeName, arguments: arguments); + } + + Future pushReplacementScreen(String routeName, {dynamic arguments}) { + return navigatorKey.currentState! + .pushReplacementNamed(routeName, arguments: arguments); + } + + void fromInviteLink(List routeNames, List arguments) { + int i = 0; + removeAllAndPush('/${routeNames[i]}', '/', arguments: arguments[i]); + for (i = 1; i < routeNames.length; i++) { + pushScreen('/${routeNames[i]}', arguments: arguments[i]); + } + } + + Future removeAllAndPush(String routeName, String tillRoute, + {dynamic arguments}) { + return navigatorKey.currentState!.pushNamedAndRemoveUntil( + routeName, ModalRoute.withName(tillRoute), + arguments: arguments); + } + + void pushDialog(Widget dialog) { + showDialog( + context: navigatorKey.currentContext!, + barrierColor: Colors.transparent, + barrierDismissible: false, + builder: (BuildContext context) { + return dialog; + }); + } + + void showSnackBar(String message, + {Duration duration = const Duration(seconds: 2)}) { + ScaffoldMessenger.of(navigatorKey.currentContext!) + .showSnackBar(SnackBar(duration: duration, content: Text(message))); + } + + void pop() { + return navigatorKey.currentState!.pop(); + } +} diff --git a/lib/services/post_service.dart b/lib/services/post_service.dart new file mode 100644 index 000000000..63d2e761a --- /dev/null +++ b/lib/services/post_service.dart @@ -0,0 +1,119 @@ +import 'dart:async'; + +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/models/post/post_model.dart'; +import 'package:talawa/services/database_mutation_functions.dart'; +import 'package:talawa/services/user_config.dart'; +import 'package:talawa/utils/post_queries.dart'; + +class PostService { + PostService() { + _postStream = _postStreamController.stream.asBroadcastStream(); + _updatedPostStream = + _updatedPostStreamController.stream.asBroadcastStream(); + _currentOrg = _userConfig.currentOrg; + setOrgStreamSubscription(); + } + // Stream for entire posts + final StreamController> _postStreamController = + StreamController>(); + late Stream> _postStream; + + //Stream for individul post update + final StreamController _updatedPostStreamController = + StreamController(); + late Stream _updatedPostStream; + + final _userConfig = locator(); + final _dbFunctions = locator(); + late OrgInfo _currentOrg; + final Set _renderedPostID = {}; + // ignore: prefer_final_fields + List _posts = []; + + //Getters + Stream> get postStream => _postStream; + Stream get updatedPostStream => _updatedPostStream; + + //Setters + void setOrgStreamSubscription() { + _userConfig.currentOrfInfoStream.listen((updatedOrganization) { + _renderedPostID.clear(); + _currentOrg = updatedOrganization; + }); + } + + //Function to get all posts + Future getPosts() async { + final String currentOrgID = _currentOrg.id!; + final String query = PostQueries().getPostsById(currentOrgID); + final QueryResult result = + await _dbFunctions.gqlAuthQuery(query) as QueryResult; + + //Checking if the dbFunctions return the postJSON, if not return. + if (result.data!['postsByOrganization'] == null) return; + + final List postsJson = result.data!['postsByOrganization'] as List; + + final List _newPosts = []; + postsJson.forEach((postJson) { + final Post post = Post.fromJson(postJson as Map); + if (!_renderedPostID.contains(post.sId)) { + _newPosts.add(post); + _renderedPostID.add(post.sId); + } + }); + _postStreamController.add(_newPosts); + _posts = _newPosts; + } + + // --- Functions related to Likes --- // + Future addLike(String postID) async { + _localAddLike(postID); + final String mutation = PostQueries().addLike(); + final result = await _dbFunctions + .gqlAuthMutation(mutation, variables: {"postID": postID}); + print(result); + return result; + } + + void _localAddLike(String postID) { + _posts.forEach((post) { + if (post.sId == postID) { + post.likedBy!.add(LikedBy(sId: _userConfig.currentUser.id)); + _updatedPostStreamController.add(post); + } + }); + } + + Future removeLike(String postID) async { + _removeLocal(postID); + final String mutation = PostQueries().removeLike(); + final result = await _dbFunctions + .gqlAuthMutation(mutation, variables: {"postID": postID}); + print(result); + return result; + } + + void _removeLocal(String postID) { + _posts.forEach((post) { + if (post.sId == postID) { + post.likedBy!.removeWhere( + (likeUser) => likeUser.sId == _userConfig.currentUser.id); + _updatedPostStreamController.add(post); + } + }); + } + + // --- Functions related to comments --- // + void addCommentLocally(String postID) { + for (int i = 0; i < _posts.length; i++) { + if (_posts[i].sId == postID) { + _posts[i].comments!.add(Comments(sId: postID)); + _updatedPostStreamController.add(_posts[i]); + } + } + } +} diff --git a/lib/services/size_config.dart b/lib/services/size_config.dart new file mode 100644 index 000000000..baf236309 --- /dev/null +++ b/lib/services/size_config.dart @@ -0,0 +1,48 @@ +import 'package:flutter/widgets.dart'; + +class SizeConfig { + static late MediaQueryData _mediaQueryData; + static double? screenWidth; + static double? screenHeight; + static double? blockSizeHorizontal; + static double? blockSizeVertical; + static double? paddingTop; + + static late double _safeAreaHorizontal; + static late double _safeAreaVertical; + static double? safeBlockHorizontal; + static double? safeBlockVertical; + + void init(BuildContext context) { + _mediaQueryData = MediaQuery.of(context); + screenWidth = _mediaQueryData.size.width; + screenHeight = _mediaQueryData.size.height; + blockSizeHorizontal = screenWidth! / 100; + blockSizeVertical = screenHeight! / 100; + + _safeAreaHorizontal = + _mediaQueryData.padding.left + _mediaQueryData.padding.right; + _safeAreaVertical = + _mediaQueryData.padding.top + _mediaQueryData.padding.bottom; + safeBlockHorizontal = (screenWidth! - _safeAreaHorizontal) / 100; + safeBlockVertical = (screenHeight! - _safeAreaVertical) / 100; + debugPrint("safeBlockHorizontal: $safeBlockHorizontal"); + debugPrint("safeBlockVertical: $safeBlockVertical"); + } + + test() { + _mediaQueryData = + const MediaQueryData(size: Size(360, 684), padding: EdgeInsets.zero); + screenWidth = _mediaQueryData.size.width; + screenHeight = _mediaQueryData.size.height; + blockSizeHorizontal = screenWidth! / 100; + blockSizeVertical = screenHeight! / 100; + + _safeAreaHorizontal = + _mediaQueryData.padding.left + _mediaQueryData.padding.right; + _safeAreaVertical = + _mediaQueryData.padding.top + _mediaQueryData.padding.bottom; + safeBlockHorizontal = (screenWidth! - _safeAreaHorizontal) / 100; + safeBlockVertical = (screenHeight! - _safeAreaVertical) / 100; + } +} diff --git a/lib/services/user_config.dart b/lib/services/user_config.dart new file mode 100644 index 000000000..6e98bf0d6 --- /dev/null +++ b/lib/services/user_config.dart @@ -0,0 +1,126 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:hive/hive.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/models/user/user_info.dart'; + +class UserConfig { + late User? _currentUser = User(id: 'null', authToken: 'null'); + late OrgInfo? _currentOrg = OrgInfo(name: 'Organization Name', id: 'null'); + late Stream _currentOrgInfoStream; + final _currentOrgInfoController = StreamController(); + + Stream get currentOrfInfoStream => _currentOrgInfoStream; + StreamController get currentOrgInfoController => + _currentOrgInfoController; + + OrgInfo get currentOrg => _currentOrg!; + String get currentOrgName => _currentOrg!.name!; + set currentOrg(OrgInfo org) => _currentOrg = org; + User get currentUser => _currentUser!; + + void initialiseStream() { + _currentOrgInfoStream = + _currentOrgInfoController.stream.asBroadcastStream(); + } + + Future userLoggedIn() async { + initialiseStream(); + final boxUser = Hive.box('currentUser'); + final boxOrg = Hive.box('currentOrg'); + _currentOrg = + boxOrg.get('org') ?? OrgInfo(name: 'Organization Name', id: 'null'); + _currentOrgInfoController.add(_currentOrg!); + + _currentUser = boxUser.get('user'); + if (_currentUser == null) { + _currentUser = User(id: 'null', authToken: 'null'); + return false; + } + graphqlConfig.getToken().then((value) async { + databaseFunctions.init(); + try { + final QueryResult result = await databaseFunctions.gqlNonAuthMutation( + queries.fetchUserInfo, + variables: {'id': currentUser.id!}) as QueryResult; + final User userInfo = User.fromJson( + result.data!['users'][0] as Map, + fromOrg: true); + userInfo.authToken = userConfig.currentUser.authToken; + userInfo.refreshToken = userConfig.currentUser.refreshToken; + userConfig.updateUser(userInfo); + _currentOrg ??= _currentUser!.joinedOrganizations![0]; + _currentOrgInfoController.add(_currentOrg!); + + saveUserInHive(); + return true; + } on Exception catch (e) { + print(e); + navigationService.showSnackBar("Couldn't update User details"); + } + }); + return true; + } + + Future updateUserJoinedOrg(List orgDetails) async { + _currentUser!.updateJoinedOrg(orgDetails); + saveUserInHive(); + } + + Future updateUserCreatedOrg(List orgDetails) async { + _currentUser!.updateCreatedOrg(orgDetails); + saveUserInHive(); + } + + Future updateUserMemberRequestOrg(List orgDetails) async { + _currentUser!.updateMemberRequestOrg(orgDetails); + saveUserInHive(); + } + + Future updateUserAdminOrg(List orgDetails) async { + _currentUser!.updateAdminFor(orgDetails); + saveUserInHive(); + } + + Future updateAccessToken( + {required String accessToken, required String refreshToken}) async { + _currentUser!.refreshToken = refreshToken; + _currentUser!.authToken = accessToken; + saveUserInHive(); + } + + Future updateUser(User updatedUserDetails) async { + try { + _currentUser = updatedUserDetails; + saveUserInHive(); + databaseFunctions.init(); + return true; + } on Exception catch (e) { + debugPrint(e.toString()); + return false; + } + } + + saveUserInHive() { + final box = Hive.box('currentUser'); + if (box.get('user') == null) { + box.put('user', _currentUser!); + } else { + box.put('user', _currentUser!); + } + } + + saveCurrentOrgInHive(OrgInfo saveOrgAsCurrent) { + _currentOrg = saveOrgAsCurrent; + _currentOrgInfoController.add(_currentOrg!); + final box = Hive.box('currentOrg'); + if (box.get('org') == null) { + box.put('org', _currentOrg!); + } else { + box.put('org', _currentOrg!); + } + } +} diff --git a/lib/splash_screen.dart b/lib/splash_screen.dart new file mode 100644 index 000000000..8479c91bb --- /dev/null +++ b/lib/splash_screen.dart @@ -0,0 +1,156 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:talawa/constants/routing_constants.dart'; +import 'package:talawa/custom_painters/talawa_logo.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:uni_links/uni_links.dart'; + +class SplashScreen extends StatefulWidget { + const SplashScreen({required Key key}) : super(key: key); + + @override + _SplashScreenState createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State { + Uri? _initialUri; + Uri? _latestUri; + late StreamSubscription _sub; + + Future _handleInitialUri() async { + _sub = uriLinkStream.listen((Uri? uri) { + if (!mounted) return; + setState(() { + _latestUri = uri; + }); + }, onError: (Object err) { + if (!mounted) return; + setState(() { + _latestUri = null; + }); + }); + try { + final uri = await getInitialUri(); + if (!mounted) return; + setState(() => _initialUri = uri); + } on PlatformException { + if (!mounted) return; + setState(() => _initialUri = null); + } on FormatException catch (err) { + debugPrint(err.toString()); + if (!mounted) return; + setState(() => _initialUri = null); + } + if (_latestUri == null && _initialUri == null) { + final bool userLoggedIn = await userConfig.userLoggedIn(); + Future.delayed(const Duration(milliseconds: 750)).then((value) async { + if (userLoggedIn) { + if (userConfig.currentUser.joinedOrganizations!.isEmpty) { + if (userConfig.currentUser.membershipRequests!.isEmpty) { + navigationService.pushReplacementScreen(Routes.joinOrg, + arguments: '0'); + } else { + navigationService.pushReplacementScreen(Routes.waitingScreen, + arguments: '0'); + } + } else { + navigationService.pushReplacementScreen(Routes.mainScreen, + arguments: '0'); + } + } else { + navigationService.pushReplacementScreen(Routes.languageSelectionRoute, + arguments: '0'); + } + }); + } else { + if (_initialUri != null) { + if (_initialUri!.pathSegments[1] == 'invite') { + navigationService.fromInviteLink( + _initialUri!.queryParameters.keys.toList(growable: false), + _initialUri!.queryParameters.values.toList(growable: false)); + } + } + } + } + + @override + void initState() { + _handleInitialUri(); + super.initState(); + } + + @override + void dispose() { + _sub.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + sizeConfig.init(context); + return Scaffold( + key: const Key('SplashScreenScaffold'), + body: Stack( + children: [ + Center( + child: CustomPaint( + key: const Key('LogoPainter'), + size: Size(SizeConfig.screenWidth! * 0.6, + (SizeConfig.screenWidth! * 0.6).toDouble()), + painter: AppLogo(), + ), + ), + Positioned( + top: SizeConfig.screenHeight! / 1.5, + left: 0.0, + right: 0.0, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'TALAWA', + style: Theme.of(context).textTheme.headline4, + ), + ], + )), + Positioned( + top: SizeConfig.screenHeight! / 1.08, + left: 0.0, + right: 0.0, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + AppLocalizations.of(context)!.strictTranslate('from'), + style: Theme.of(context).textTheme.caption, + ), + ], + )), + Positioned( + top: SizeConfig.screenHeight! / 1.05, + left: 0.0, + right: 0.0, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'PALISADOES', + style: Theme.of(context) + .textTheme + .subtitle2! + .copyWith(fontWeight: FontWeight.w700), + ), + ], + )) + ], + ), + ); + } +} diff --git a/lib/utils/app_localization.dart b/lib/utils/app_localization.dart new file mode 100644 index 000000000..f257b98ea --- /dev/null +++ b/lib/utils/app_localization.dart @@ -0,0 +1,95 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class AppLocalizations { + AppLocalizations( + this.locale, { + this.isTest = false, + }); + + late Map _localizedStrings; + final Locale locale; + bool isTest; + + // Helper method to keep the code in the widgets concise + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + // Static member to have a simple access to the delegate from the MaterialApp + static const LocalizationsDelegate delegate = + AppLocalizationsDelegate(); + + Future loadTest(Locale locale) async { + return AppLocalizations(locale); + } + + Future load() async { + // Load the language JSON file from the "lang" folder + final String jsonString = + await rootBundle.loadString('lang/${locale.languageCode}.json'); + final Map jsonMap = + json.decode(jsonString) as Map; + + _localizedStrings = jsonMap.map((key, value) { + return MapEntry(key, value.toString()); + }); + + return true; + } + + // This method will be called from every widget which needs a localized text + String? translate(String? key) { + if (isTest) return key; + + // ignore: unnecessary_null_comparison + if (key == null) { + return '...'; + } + return _localizedStrings[key]; + } + + // This method will be called from every widget which needs a localized text + String strictTranslate(String key) { + if (isTest) return key; + + // ignore: unnecessary_null_comparison + if (key == null) { + return '...'; + } + + final String translate = _localizedStrings[key] ?? key; + return translate; + } +} + +class AppLocalizationsDelegate extends LocalizationsDelegate { + const AppLocalizationsDelegate({ + this.isTest = false, + }); + final bool isTest; + + @override + bool isSupported(Locale locale) { + // Include all of your supported language codes here + return ['en', 'es', 'fr', 'hi', 'zh'].contains(locale.languageCode); + } + + @override + Future load(Locale locale) async { + // AppLocalizations class is where the JSON loading actually runs + final AppLocalizations localizations = + AppLocalizations(locale, isTest: isTest); + if (isTest) { + await localizations.loadTest(locale); + } else { + await localizations.load(); + } + + return localizations; + } + + @override + bool shouldReload(AppLocalizationsDelegate old) => false; +} diff --git a/lib/utils/comment_queries.dart b/lib/utils/comment_queries.dart new file mode 100644 index 000000000..569629d39 --- /dev/null +++ b/lib/utils/comment_queries.dart @@ -0,0 +1,31 @@ +class CommentQueries { + String createComment() { + return """ + mutation createComment(\$postId: ID!, \$text: String!) { + createComment(postId: \$postId, + data:{ + text: \$text, + } + ){ + _id + } + } + """; + } + + String getPostsComments(String postId) { + return """ + query{ + commentsByPost(id: "$postId"){ + _id + text + createdAt + creator{ + firstName + lastName + } + } + } + """; + } +} diff --git a/lib/utils/event_queries.dart b/lib/utils/event_queries.dart new file mode 100644 index 000000000..50f99c24e --- /dev/null +++ b/lib/utils/event_queries.dart @@ -0,0 +1,83 @@ +class EventQueries { + String fetchOrgEvents(String orgId) { + return """ + query { + events(id: "$orgId"){ + _id + organization { + _id + image + } + title + description + isPublic + isRegisterable + recurring + recurrance + startTime + endTime + allDay + startTime + endTime + location + isRegistered + creator{ + _id + } + } + } + """; + } + + String addEvent() { + return """ + mutation createEvent( \$organizationId: ID!, + \$title:String!, + \$description: String!, + \$startTime: String, + \$endTime: String, + \$allDay: Boolean!, + \$recurring: Boolean!, + \$isPublic: Boolean!, + \$isRegisterable: Boolean!, + \$location: String, + \$startDate : String!, + \$endDate : String!, + ) { + createEvent( + data:{ + organizationId: \$organizationId, + title: \$title, + description: \$description, + isPublic: \$isPublic, + isRegisterable: \$isRegisterable, + recurring: \$recurring, + allDay: \$allDay, + startTime: \$startTime, + endTime: \$endTime, + startDate: \$startDate, + endDate: \$endDate, + location: \$location + } + ){ + _id + title + description + } + } + """; + } + + String registerForEvent() { + return """ + mutation registerForEvent(\$eventId: ID!) { + registerForEvent(id: \$eventId) + { + _id + title + description + } + } + """; + } +} diff --git a/lib/utils/post_queries.dart b/lib/utils/post_queries.dart new file mode 100644 index 000000000..199a5c1f3 --- /dev/null +++ b/lib/utils/post_queries.dart @@ -0,0 +1,59 @@ +class PostQueries { + String getPostsById(String orgId) { + return """ + query { + postsByOrganization(id: "$orgId",orderBy: createdAt_DESC ) + { + _id + text + createdAt + imageUrl + videoUrl + title + commentCount + likeCount + creator{ + _id + firstName + lastName + image + } + organization{ + _id + } + likedBy{ + _id + } + comments{ + _id + } + } + } +"""; + } + + String addLike() { + return """ + mutation likePost(\$postID: ID!) { + likePost( id: \$postID,) + { + _id + } + } + """; + } + + String removeLike() { + return """ + mutation unlikePost(\$postID: ID!) { + unlikePost( id: \$postID,) + { + _id + likedBy{ + _id + } + } + } + """; + } +} diff --git a/lib/utils/queries.dart b/lib/utils/queries.dart new file mode 100644 index 000000000..3f1eb9589 --- /dev/null +++ b/lib/utils/queries.dart @@ -0,0 +1,337 @@ +class Queries { + String registerUser( + String firstName, String lastName, String email, String password) { + return """ + mutation{ + signUp(data: {firstName: "$firstName", lastName: "$lastName", email: "$email", password: "$password"}) + { + accessToken + user{ + _id + firstName + lastName + email + image + joinedOrganizations{ + _id + name + image + description + isPublic + creator{ + _id + firstName + lastName + image + } + } + createdOrganizations{ + _id + name + image + description + isPublic + creator{ + _id + firstName + lastName + image + } + } + membershipRequests{ + organization{ + _id + name + image + description + isPublic + creator{ + _id + firstName + lastName + image + } + } + } + adminFor{ + _id + } + } + refreshToken + } + } + """; + } + + //login the user + String loginUser(String email, String password) { + return """ + mutation { + login(data: {email: "$email", password: "$password"}){ + accessToken + user{ + _id + firstName + lastName + email + image + joinedOrganizations{ + _id + name + image + description + isPublic + creator{ + _id + firstName + lastName + image + } + } + createdOrganizations{ + _id + name + image + description + isPublic + creator{ + _id + firstName + lastName + image + } + } + membershipRequests{ + organization{ + _id + name + image + description + isPublic + creator{ + _id + firstName + lastName + image + } + } + } + adminFor{ + _id + } + } + refreshToken + } + } + """; + } + + String get fetchJoinInOrg { + return """ + query organizationsConnection(\$first: Int, \$skip: Int){ + organizationsConnection( + first: \$first, + skip: \$skip, + orderBy: name_ASC + ){ + image + _id + name + image + isPublic + creator{ + firstName + lastName + } + } + } +"""; + } + + String get fetchJoinInOrgByName { + return """ + query organizationsConnection( + \$first: Int, + \$skip: Int, + \$nameStartsWith: String + ){ + organizationsConnection( + where:{ + name_starts_with: \$nameStartsWith, + visibleInSearch: true, + isPublic: true, + } + first: \$first, + skip: \$skip, + orderBy: name_ASC + ){ + image + _id + name + image + isPublic + creator{ + firstName + lastName + } + } + } +"""; + } + + String joinOrgById(String orgId) { + return ''' + mutation { + joinPublicOrganization(organizationId: "$orgId") { + joinedOrganizations{ + _id + name + image + description + isPublic + creator{ + _id + firstName + lastName + image + } + } + } + } + '''; + } + + String sendMembershipRequest(String orgId) { + return ''' + mutation { + sendMembershipRequest(organizationId: "$orgId"){ + organization{ + _id + name + image + description + isPublic + creator{ + _id + firstName + lastName + image + } + } + } + } + '''; + } + + String fetchUserInfo = ''' + query Users(\$id: ID!){ + users(id:\$id){ + _id + firstName + lastName + email + image + joinedOrganizations{ + _id + name + image + description + isPublic + creator{ + _id + firstName + lastName + image + } + } + createdOrganizations{ + _id + name + image + description + isPublic + creator{ + _id + firstName + lastName + image + } + } + membershipRequests{ + organization{ + _id + name + image + isPublic + creator{ + _id + firstName + lastName + image + } + } + } + adminFor{ + _id + } + } + } + '''; + + String refreshToken(String refreshToken) { + return ''' + mutation{ + refreshToken(refreshToken: "$refreshToken"){ + accessToken + refreshToken + } + } + '''; + } + + String fetchOrgById(String orgId) { + return ''' + query{ + organizations(id: "$orgId"){ + image + _id + name + image + isPublic + creator{ + firstName + lastName + } + } + } + '''; + } + + String fetchOrgDetailsById(String orgId) { + return ''' + query{ + organizations(id: "$orgId"){ + image + _id + name + admins{ + _id + } + description + isPublic + creator{ + _id + firstName + lastName + } + members{ + _id + firstName + lastName + image + } + } + } + '''; + } +} diff --git a/lib/utils/validators.dart b/lib/utils/validators.dart new file mode 100644 index 000000000..e4f2dd1df --- /dev/null +++ b/lib/utils/validators.dart @@ -0,0 +1,117 @@ +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart' as http; + +class Validator { + static String? validateURL( + String value, + ) { + if (value.isEmpty) { + return 'Please verify URL first'; + } + final bool validURL = Uri.parse(value).isAbsolute; + if (!validURL) { + return 'Enter a valid URL'; + } + return null; + } + + static String? validateFirstName(String value) { + if (value.isEmpty) { + return 'Firstname must not be left blank.'; + } + // ignore: unnecessary_raw_strings + const String pattern = r'(?=.*?[A-Za-z]).+'; + final RegExp regex = RegExp(pattern); + if (!regex.hasMatch(value)) { + return "Invalid Firstname"; + } + return null; + } + + static String? validateLastName(String value) { + if (value.isEmpty) { + return 'Lastname must not be left blank.'; + } + // ignore: unnecessary_raw_strings + const String pattern = r"(?=.*?[A-Za-z]).+"; + final RegExp regex = RegExp(pattern); + if (!regex.hasMatch(value)) { + return "Invalid Lastname"; + } + return null; + } + + static String? validateEmail( + String email, + ) { + // If email is empty return. + if (email.isEmpty) { + return "Email must not be left blank"; + } + const String pattern = + r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$"; + final RegExp regex = RegExp(pattern); + if (!regex.hasMatch(email)) { + return 'Please enter a valid Email Address'; + } + return null; + } + + static String? validatePassword( + String password, + ) { + // If password is empty return. + if (password.isEmpty) { + return "Password must not be left blank"; + } + const String pattern = + r'^(?=.*?[A-Z])(?=.*?[0-9])(?=.*?[!@#\$&*%^~.]).{8,}$'; + final RegExp regExp = RegExp(pattern); + + //Regex for no spaces allowed + const String noSpaces = r'^\S+$'; + final RegExp noSpaceRegex = RegExp(noSpaces); + + if (!regExp.hasMatch(password)) { + return "Invalid Password"; + } + if (!noSpaceRegex.hasMatch(password)) { + return "Password must not contain spaces"; + } + + return null; + } + + static String? validatePasswordConfirm( + String value, + String comparator, + ) { + if (value != comparator) { + return 'Password does not match original'; + } + return null; + } + + static Future validateUrlExistence(String url) async { + try { + await http.get(Uri.parse(url)); + return true; + } on Exception catch (e) { + debugPrint(e.toString()); + return false; + } + } + + static String? validateEventForm(String value, String? label) { + if (value.isEmpty) { + return '$label must not be left blank.'; + } + // ignore: unnecessary_raw_strings + const String pattern = r'(?=.*?[A-Za-z]).+'; + final RegExp regex = RegExp(pattern); + if (!regex.hasMatch(value)) { + return "Invalid $label"; + } + return null; + } +} diff --git a/lib/view_model/after_auth_view_models/event_view_models/create_event_view_model.dart b/lib/view_model/after_auth_view_models/event_view_models/create_event_view_model.dart new file mode 100644 index 000000000..2e5cec45b --- /dev/null +++ b/lib/view_model/after_auth_view_models/event_view_models/create_event_view_model.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/services/database_mutation_functions.dart'; +import 'package:talawa/services/event_service.dart'; +import 'package:talawa/services/user_config.dart'; +import 'package:talawa/utils/event_queries.dart'; +import 'package:talawa/view_model/base_view_model.dart'; +import 'package:talawa/widgets/custom_progress_dialog.dart'; + +class CreateEventViewModel extends BaseModel { + TextEditingController eventTitleTextController = TextEditingController(); + TextEditingController eventLocationTextController = TextEditingController(); + TextEditingController eventDescriptionTextController = + TextEditingController(); + TimeOfDay eventStartTime = TimeOfDay.now(); + TimeOfDay eventEndTime = TimeOfDay.now(); + DateTime eventStartDate = DateTime.now(); + DateTime eventEndDate = DateTime.now(); + bool isPublicSwitch = true; + bool isRegisterableSwitch = false; + FocusNode titleFocus = FocusNode(); + FocusNode locationFocus = FocusNode(); + FocusNode descriptionFocus = FocusNode(); + + final formKey = GlobalKey(); + + final _eventService = locator(); + final _dbFunctions = locator(); + AutovalidateMode validate = AutovalidateMode.disabled; + + late OrgInfo _currentOrg; + final _userConfig = locator(); + + initialize() { + _currentOrg = _userConfig.currentOrg; + } + + Future createEvent() async { + titleFocus.unfocus(); + locationFocus.unfocus(); + descriptionFocus.unfocus(); + validate = AutovalidateMode.always; + if (formKey.currentState!.validate()) { + validate = AutovalidateMode.disabled; + + final DateTime startDate = eventStartDate; + final DateTime endDate = eventStartDate; + final DateTime startTime = DateTime(startDate.year, startDate.month, + startDate.day, eventStartTime.hour, eventStartTime.minute); + final DateTime endTime = DateTime(endDate.year, endDate.month, + endDate.day, eventEndTime.hour, eventEndTime.minute); + final Map variables = { + 'startDate': startDate.toString(), + 'endDate': endDate.toString(), + 'organizationId': _currentOrg.id!, + 'title': eventTitleTextController.text, + 'description': eventDescriptionTextController.text, + 'location': eventLocationTextController.text, + 'isPublic': isPublicSwitch, + 'isRegisterable': isRegisterableSwitch, + 'recurring': false, + 'allDay': false, + 'startTime': startTime.microsecondsSinceEpoch.toString(), + 'endTime': endTime.microsecondsSinceEpoch.toString(), + }; + + navigationService.pushDialog( + const CustomProgressDialog(key: Key('EventCreationProgress'))); + final tokenResult = await _dbFunctions + .refreshAccessToken(userConfig.currentUser.refreshToken!); + print(tokenResult); + final result = await _dbFunctions.gqlAuthMutation( + EventQueries().addEvent(), + variables: variables, + ); + navigationService.pop(); + print('Result is : $result'); + if (result != null) { + navigationService.pop(); + + _eventService.getEvents(); + } + } + } +} diff --git a/lib/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart b/lib/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart new file mode 100644 index 000000000..feff64891 --- /dev/null +++ b/lib/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart @@ -0,0 +1,76 @@ +import 'dart:async'; + +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/services/event_service.dart'; +import 'package:talawa/view_model/base_view_model.dart'; + +class ExploreEventsViewModel extends BaseModel { + final _eventService = locator(); + late StreamSubscription _eventStreamSubscription; + + String _chosenValue = 'My Events'; + List _events = []; + + late StreamSubscription _currentOrganizationStreamSubscription; + late final List _bufferEvents; + + String get chosenValue => _chosenValue; + choseValue(String value) { + _chosenValue = value; + notifyListeners(); + + if (chosenValue == 'Private Events') { + print(_events.length); + + _events = + _bufferEvents.where((element) => element.isPublic == false).toList(); + print(_events.length); + } else if (chosenValue == 'Public Events') { + print(_events.length); + + _events = + _bufferEvents.where((element) => element.isPublic == true).toList(); + print(_events.length); + } else if (chosenValue == 'My Events') { + _events = _bufferEvents; + } + } + + List get events => _events; + + void fetchNewEvents() { + _eventService.getEvents(); + } + + void refreshEvents() { + _events.clear(); + notifyListeners(); + } + + void initialise() { + setState(ViewState.busy); + + _currentOrganizationStreamSubscription = userConfig.currentOrfInfoStream + .listen((updatedOrganization) => refreshEvents()); + _eventStreamSubscription = + _eventService.eventStream.listen((newEvent) => addNewEvent(newEvent)); + _eventService.getEvents(); + _bufferEvents = _events; + setState(ViewState.idle); + } + + @override + void dispose() { + // Canceling the subscription so that there will be no rebuild after the widget is disposed. + _eventStreamSubscription.cancel(); + _currentOrganizationStreamSubscription.cancel(); + super.dispose(); + } + + addNewEvent(Event newEvent) { + _events.insert(0, newEvent); + notifyListeners(); + } +} diff --git a/lib/view_model/after_auth_view_models/feed_view_models/organization_feed_view_model.dart b/lib/view_model/after_auth_view_models/feed_view_models/organization_feed_view_model.dart new file mode 100644 index 000000000..5422be695 --- /dev/null +++ b/lib/view_model/after_auth_view_models/feed_view_models/organization_feed_view_model.dart @@ -0,0 +1,114 @@ +import 'dart:async'; + +import 'package:talawa/constants/routing_constants.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/post/post_model.dart'; +import 'package:talawa/services/navigation_service.dart'; +import 'package:talawa/services/post_service.dart'; +import 'package:talawa/services/user_config.dart'; +import 'package:talawa/view_model/base_view_model.dart'; + +class OrganizationFeedViewModel extends BaseModel { + // Local caching variables for a session. + // ignore: prefer_final_fields + List _posts = [], _pinnedPosts = []; + final Set _renderedPostID = {}; + late String _currentOrgname = ""; + + // Importing services. + final NavigationService _navigationService = locator(); + final UserConfig _userConfig = locator(); + final PostService _postService = locator(); + + // Stream variables + late StreamSubscription _currentOrganizationStreamSubscription; + late StreamSubscription _postsSubscription; + late StreamSubscription _updatePostSubscription; + + // Getters + List get posts => _posts; + List get pinnedPosts => _pinnedPosts; + String get currentOrgName => _currentOrgname; + + // Setters + void setCurrentOrganizationName(String updatedOrganization) { + _posts.clear(); + _renderedPostID.clear(); + _currentOrgname = updatedOrganization; + notifyListeners(); + _postService.getPosts(); + } + + void fetchNewPosts() { + _postService.getPosts(); + } + + void initialise() { + // For caching/initalizing the current organization after the stream subsciption has canceled and the stream is updated + _currentOrgname = _userConfig.currentOrg.name!; + _postService.getPosts(); + // ------ + // Attasching the stream subscription to rebuild the widgets automatically + _currentOrganizationStreamSubscription = _userConfig.currentOrfInfoStream + .listen((updatedOrganization) => + setCurrentOrganizationName(updatedOrganization.name!)); + + _postsSubscription = + _postService.postStream.listen((newPosts) => buildNewPosts(newPosts)); + + _updatePostSubscription = + _postService.updatedPostStream.listen((post) => updatedPost(post)); + } + + void initializeWithDemoData() { + // final postJsonResult = postsDemoData; + + // ------ + // Calling function to ge the post for the only 1st time. + // _postService.getPosts(); + + // //fetching pinnedPosts + // final pinnedPostJsonResult = pinnedPostsDemoData; + // pinnedPostJsonResult.forEach((pinnedPostJsonData) { + // _pinnedPosts.add(Post.fromJson(pinnedPostJsonData)); + // }); + } + + void buildNewPosts(List newPosts) { + _posts = newPosts; + notifyListeners(); + } + + void navigateToIndividualPage(Post post) { + _navigationService.pushScreen(Routes.individualPost, arguments: post); + } + + void navigateToPinnedPostPage() { + _navigationService.pushScreen(Routes.pinnedPostPage, + arguments: _pinnedPosts); + } + + @override + void dispose() { + // Canceling the subscription so that there will be no rebuild after the widget is disposed. + _currentOrganizationStreamSubscription.cancel(); + _postsSubscription.cancel(); + _updatePostSubscription.cancel(); + super.dispose(); + } + + addNewPost(Post newPost) { + _posts.insert(0, newPost); + notifyListeners(); + } + + updatedPost(Post post) { + for (int i = 0; i < _posts.length; i++) { + if (_posts[i].sId == post.sId) { + _posts[i] = post; + notifyListeners(); + break; + } + } + } +} diff --git a/lib/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart b/lib/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart new file mode 100644 index 000000000..3ec4714e5 --- /dev/null +++ b/lib/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/view_model/base_view_model.dart'; + +class EditProfilePageViewModel extends BaseModel { + final user = Hive.box('currentUser'); + final org = Hive.box('currentOrg'); + TextEditingController firstNameTextController = TextEditingController(); + TextEditingController lastNameTextController = TextEditingController(); + FocusNode firstNameFocus = FocusNode(); + FocusNode lastNameFocus = FocusNode(); + final databaseService = databaseFunctions; + + initialize() {} +} diff --git a/lib/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart b/lib/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart new file mode 100644 index 000000000..1f0d30190 --- /dev/null +++ b/lib/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/services/user_config.dart'; +import 'package:talawa/view_model/base_view_model.dart'; +import 'package:talawa/widgets/custom_alert_dialog.dart'; +import 'package:talawa/widgets/custom_progress_dialog.dart'; + +class ProfilePageViewModel extends BaseModel { + // Services + final _userConfig = locator(); + + late final Box user; + late final Box url; + late final Box organisation; + late OrgInfo currentOrg; + late User currentUser; + + initialize() { + setState(ViewState.busy); + currentOrg = _userConfig.currentOrg; + currentUser = _userConfig.currentUser; + setState(ViewState.idle); + } + + Future logout(BuildContext context) async { + navigationService.pushDialog(CustomAlertDialog( + reverse: true, + dialogSubTitle: 'Are you sure you want to logout?', + successText: 'Logout', + success: () { + navigationService.pop(); + navigationService.pushDialog(const CustomProgressDialog( + key: Key('LogoutProgress'), + )); + Future.delayed(const Duration(seconds: 1)).then((value) { + user = Hive.box('currentUser'); + url = Hive.box('url'); + organisation = Hive.box('currentOrg'); + user.clear(); + url.clear(); + organisation.clear(); + navigationService.removeAllAndPush('/selectLang', '/', + arguments: '0'); + }); + }, + )); + } +} diff --git a/lib/view_model/base_view_model.dart b/lib/view_model/base_view_model.dart new file mode 100644 index 000000000..335462fce --- /dev/null +++ b/lib/view_model/base_view_model.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/enums/enums.dart'; + +class BaseModel extends ChangeNotifier { + ViewState _state = ViewState.idle; + + ViewState get state => _state; + bool get isBusy => _state == ViewState.busy; + + void setState(ViewState viewState) { + _state = viewState; + notifyListeners(); + } +} diff --git a/lib/view_model/lang_view_model.dart b/lib/view_model/lang_view_model.dart new file mode 100644 index 000000000..d51fc8035 --- /dev/null +++ b/lib/view_model/lang_view_model.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:talawa/view_model/base_view_model.dart'; + +class AppLanguage extends BaseModel { + AppLanguage({this.isTest = false}); + final bool isTest; + + Locale _appLocale = const Locale('en'); + Locale get appLocal => _appLocale; + + initialize() { + fetchLocale(); + } + + fetchLocale() async { + final prefs = await SharedPreferences.getInstance(); + final String langCode = prefs.getString('language_code') ?? 'en'; + _appLocale = Locale(langCode); + notifyListeners(); + return Null; + } + + Future changeLanguage(Locale type) async { + if (_appLocale == type) { + return; + } + + if (isTest) { + _appLocale = type; + } else { + final prefs = await SharedPreferences.getInstance(); + if (type == const Locale("es")) { + //If selected language is spanish + _appLocale = const Locale("es"); + await prefs.setString('language_code', 'es'); + await prefs.setString('countryCode', 'ES'); + } else if (type == const Locale("fr")) { + //If selected language is french + _appLocale = const Locale("fr"); + await prefs.setString('language_code', 'fr'); + await prefs.setString('countryCode', 'FR'); + } else if (type == const Locale("hi")) { + //If selected language is hindi + _appLocale = const Locale("hi"); + await prefs.setString('language_code', 'hi'); + await prefs.setString('countryCode', 'IN'); + } else if (type == const Locale("zh")) { + //If selected language is Chinese + _appLocale = const Locale("zh"); + await prefs.setString('language_code', 'zh'); + await prefs.setString('countryCode', 'CN'); + } else { + //If selected language is english + _appLocale = const Locale("en"); + await prefs.setString('language_code', 'en'); + await prefs.setString('countryCode', 'US'); + } + } + + notifyListeners(); + } +} diff --git a/lib/view_model/main_screen_view_model.dart b/lib/view_model/main_screen_view_model.dart new file mode 100644 index 000000000..7af6e9abc --- /dev/null +++ b/lib/view_model/main_screen_view_model.dart @@ -0,0 +1,10 @@ +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/view_model/base_view_model.dart'; + +class MainScreenViewModel extends BaseModel { + int currentIndex = 0; + onTabTapped(int index) { + currentIndex = index; + setState(ViewState.idle); + } +} diff --git a/lib/view_model/pre_auth_view_models/login_view_model.dart b/lib/view_model/pre_auth_view_models/login_view_model.dart new file mode 100644 index 000000000..af0b584b2 --- /dev/null +++ b/lib/view_model/pre_auth_view_models/login_view_model.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/view_model/base_view_model.dart'; +import 'package:talawa/widgets/custom_progress_dialog.dart'; + +class LoginViewModel extends BaseModel { + final formKey = GlobalKey(); + late List> greeting; + TextEditingController password = TextEditingController(); + TextEditingController email = TextEditingController(); + FocusNode passwordFocus = FocusNode(); + FocusNode emailFocus = FocusNode(); + AutovalidateMode validate = AutovalidateMode.disabled; + bool hidePassword = true; + + initialize() { + greeting = [ + { + 'text': "We're ", + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5 + }, + { + 'text': 'Glad ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline6! + .copyWith(fontSize: 24) + }, + { + 'text': "you're ", + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5 + }, + { + 'text': 'Back ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline6! + .copyWith(fontSize: 24) + }, + ]; + } + + login() async { + emailFocus.unfocus(); + passwordFocus.unfocus(); + validate = AutovalidateMode.always; + if (formKey.currentState!.validate()) { + validate = AutovalidateMode.disabled; + navigationService + .pushDialog(const CustomProgressDialog(key: Key('LoginProgress'))); + databaseFunctions.init(); + try { + final QueryResult result = await databaseFunctions.gqlNonAuthMutation( + queries.loginUser(email.text, password.text)) as QueryResult; + navigationService.pop(); + final User loggedInUser = + User.fromJson(result.data!['login'] as Map); + userConfig.updateUser(loggedInUser); + if (userConfig.currentUser.joinedOrganizations!.isEmpty) { + navigationService.removeAllAndPush('/waiting', '/'); + } else { + userConfig.saveCurrentOrgInHive( + userConfig.currentUser.joinedOrganizations![0]); + navigationService.removeAllAndPush('/mainScreen', '/'); + } + } on Exception catch (e) { + print(e); + } + } + } +} diff --git a/lib/view_model/pre_auth_view_models/select_organization_view_model.dart b/lib/view_model/pre_auth_view_models/select_organization_view_model.dart new file mode 100644 index 000000000..ed19f478e --- /dev/null +++ b/lib/view_model/pre_auth_view_models/select_organization_view_model.dart @@ -0,0 +1,333 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:talawa/constants/routing_constants.dart'; +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/queries.dart'; +import 'package:talawa/view_model/base_view_model.dart'; +import 'package:talawa/widgets/custom_list_tile.dart'; +import 'package:visibility_detector/visibility_detector.dart'; + +class SelectOrganizationViewModel extends BaseModel { + final ScrollController allOrgController = ScrollController(); + final ScrollController controller = ScrollController(); + final FocusNode searchFocus = FocusNode(); + final TextEditingController searchController = TextEditingController(); + late OrgInfo selectedOrganization = OrgInfo(id: '-1'); + late List organizations = []; + bool searching = false; + late Widget showSearchOrgList = Container(); + + searchActive() { + if (searchFocus.hasFocus) { + organizations = []; + searching = true; + setState(ViewState.idle); + } + } + + initialise(String initialData) async { + searchFocus.addListener(searchActive); + if (!initialData.contains('-1')) { + final fetch = await databaseFunctions.fetchOrgById(initialData); + if (fetch.runtimeType == OrgInfo) { + selectedOrganization = fetch as OrgInfo; + setState(ViewState.idle); + navigationService.pushScreen('/signupDetails', + arguments: selectedOrganization); + } + } + } + + selectOrg(OrgInfo item) async { + bool orgAlreadyJoined = false; + bool orgRequestAlreadyPresent = false; + final bool userLoggedIn = await userConfig.userLoggedIn(); + if (userLoggedIn) { + userConfig.currentUser.joinedOrganizations!.forEach((element) { + if (element.id! == item.id) { + orgAlreadyJoined = true; + } + }); + userConfig.currentUser.membershipRequests!.forEach((element) { + if (element.id! == item.id) { + orgRequestAlreadyPresent = true; + } + }); + if (!orgAlreadyJoined && !orgRequestAlreadyPresent) { + selectedOrganization = item; + setState(ViewState.idle); + } else if (orgAlreadyJoined) { + navigationService.showSnackBar('Organisation already joined'); + } else { + navigationService.showSnackBar('Membership request already sent'); + } + } else { + selectedOrganization = item; + setState(ViewState.idle); + } + } + + onTapContinue() { + if (selectedOrganization.id != '-1') { + navigationService.pushScreen('/signupDetails', + arguments: selectedOrganization); + } else { + navigationService.showSnackBar('Select one organization to continue', + duration: const Duration(milliseconds: 750)); + } + } + + onTapJoin() async { + if (selectedOrganization.isPublic == true) { + try { + final QueryResult result = await databaseFunctions.gqlAuthMutation( + queries.joinOrgById(selectedOrganization.id!)) as QueryResult; + + final List? joinedOrg = (result.data!['joinPublicOrganization'] + ['joinedOrganizations'] as List?) + ?.map((e) => OrgInfo.fromJson(e as Map)) + .toList(); + userConfig.updateUserJoinedOrg(joinedOrg!); + if (userConfig.currentUser.joinedOrganizations!.length == 1) { + userConfig.saveCurrentOrgInHive( + userConfig.currentUser.joinedOrganizations![0]); + navigationService.removeAllAndPush( + Routes.mainScreen, Routes.splashScreen); + } else { + navigationService.pop(); + navigationService + .showSnackBar('Joined ${selectedOrganization.name} successfully'); + } + } on Exception catch (e) { + print(e); + navigationService.showSnackBar('SomeThing went wrong'); + } + } else { + try { + final QueryResult result = await databaseFunctions.gqlAuthMutation( + queries.sendMembershipRequest(selectedOrganization.id!)) + as QueryResult; + final OrgInfo membershipRequest = OrgInfo.fromJson( + result.data!['sendMembershipRequest']['organization'] + as Map); + userConfig.updateUserMemberRequestOrg([membershipRequest]); + if (userConfig.currentUser.joinedOrganizations!.isEmpty) { + navigationService.removeAllAndPush( + Routes.waitingScreen, Routes.splashScreen); + } else { + navigationService.pop(); + navigationService.showSnackBar( + 'Join in request sent to ${selectedOrganization.name} successfully'); + } + } on Exception catch (e) { + print(e); + navigationService.showSnackBar('SomeThing went wrong'); + } + } + } + + Widget showOrganizationList() { + organizations = []; + return GraphQLProvider( + client: ValueNotifier(graphqlConfig.clientToQuery()), + child: Query( + options: + QueryOptions(document: gql(Queries().fetchJoinInOrg), variables: { + 'first': 15, + 'skip': 0, + }), + builder: (QueryResult result, + {Future Function(FetchMoreOptions)? fetchMore, + Future Function()? refetch}) { + if (result.hasException) { + final bool? isException = + databaseFunctions.encounteredExceptionOrError(result.exception!, + showSnackBar: false); + if (isException!) { + refetch!(); + } else { + refetch!(); + } + } else { + if (!result.isLoading) { + organizations = OrgInfo().fromJsonToList( + result.data!['organizationsConnection'] as List); + } + return Scrollbar( + isAlwaysShown: true, + interactive: true, + controller: allOrgController, + child: ListView.separated( + controller: allOrgController, + padding: EdgeInsets.zero, + shrinkWrap: true, + itemCount: result.isLoading + ? organizations.length + 1 + : organizations.length, + itemBuilder: (BuildContext context, int index) { + if (index == organizations.length) { + return ListTile( + title: Center( + child: CupertinoActivityIndicator( + radius: SizeConfig.screenWidth! * 0.065, + ))); + } + if (index == organizations.length - 3) { + return VisibilityDetector( + key: const Key('OrgSelItem'), + onVisibilityChanged: (VisibilityInfo info) { + if (info.visibleFraction > 0) { + fetchMoreHelper(fetchMore!, organizations); + } + }, + child: CustomListTile( + index: index, + type: TileType.org, + orgInfo: organizations[index], + onTapOrgInfo: (item) => selectOrg(item), + key: Key('OrgSelItem$index'), + )); + } + return CustomListTile( + index: index, + type: TileType.org, + orgInfo: organizations[index], + onTapOrgInfo: (item) => selectOrg(item), + key: Key('OrgSelItem$index'), + ); + }, + separatorBuilder: (BuildContext context, int index) { + return Padding( + padding: EdgeInsets.only( + left: SizeConfig.screenWidth! * 0.2, right: 12), + child: const Divider( + color: Color(0xFFE5E5E5), + thickness: 0.5, + ), + ); + }, + ), + ); + } + return Container(); + }, + ), + ); + } + + Widget showSearchList(String searchOrgWithName) { + print(searchOrgWithName); + return GraphQLProvider( + client: ValueNotifier(graphqlConfig.authClient()), + child: Query( + options: QueryOptions( + document: gql(Queries().fetchJoinInOrgByName), + variables: { + 'nameStartsWith': searchOrgWithName, + 'first': 30, + 'skip': 0, + }), + builder: (QueryResult result, + {Future Function(FetchMoreOptions)? fetchMore, + Future Function()? refetch}) { + if (result.hasException) { + final bool? isException = + databaseFunctions.encounteredExceptionOrError(result.exception!, + showSnackBar: false); + if (isException!) { + refetch!(); + } else { + refetch!(); + } + } else { + if (!result.isLoading) { + organizations = OrgInfo().fromJsonToList( + result.data!['organizationsConnection'] as List); + } + return Scrollbar( + isAlwaysShown: true, + interactive: true, + controller: controller, + child: ListView.separated( + controller: controller, + padding: EdgeInsets.zero, + shrinkWrap: true, + itemCount: result.isLoading + ? organizations.length + 1 + : organizations.length, + itemBuilder: (BuildContext context, int index) { + if (index == organizations.length) { + return ListTile( + title: Center( + child: CupertinoActivityIndicator( + radius: SizeConfig.screenWidth! * 0.065, + ))); + } + if (index == organizations.length - 3) { + return VisibilityDetector( + key: const Key('OrgSelItem'), + onVisibilityChanged: (VisibilityInfo info) { + if (info.visibleFraction > 0) { + print(organizations.length); + fetchMoreHelper(fetchMore!, organizations); + print(organizations.length); + } + }, + child: CustomListTile( + index: index, + type: TileType.org, + orgInfo: organizations[index], + onTapOrgInfo: (item) => selectOrg(item), + key: Key('OrgSelItem$index'), + )); + } + return CustomListTile( + index: index, + type: TileType.org, + orgInfo: organizations[index], + onTapOrgInfo: (item) => selectOrg(item), + key: Key('OrgSelItem$index'), + ); + }, + separatorBuilder: (BuildContext context, int index) { + return Padding( + padding: EdgeInsets.only( + left: SizeConfig.screenWidth! * 0.2, right: 12), + child: const Divider( + color: Color(0xFFE5E5E5), + thickness: 0.5, + ), + ); + }, + ), + ); + } + return Container(); + }, + ), + ); + } + + void fetchMoreHelper(FetchMore fetchMore, List organizations) { + fetchMore( + FetchMoreOptions( + variables: { + "first": organizations.length + 15, + "skip": organizations.length, + }, + updateQuery: (existingOrganizations, newOrganizations) { + return { + 'organizationsConnection': [ + ...existingOrganizations!["organizationsConnection"], + ...newOrganizations!['organizationsConnection'], + ], + }; + }, + ), + ); + } +} diff --git a/lib/view_model/pre_auth_view_models/set_url_view_model.dart b/lib/view_model/pre_auth_view_models/set_url_view_model.dart new file mode 100644 index 000000000..2518c3d43 --- /dev/null +++ b/lib/view_model/pre_auth_view_models/set_url_view_model.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/utils/validators.dart'; +import 'package:talawa/view_model/base_view_model.dart'; +import 'package:talawa/widgets/custom_progress_dialog.dart'; + +class SetUrlViewModel extends BaseModel { + final formKey = GlobalKey(); + static const imageUrlKey = "imageUrl"; + static const urlKey = "url"; + TextEditingController url = TextEditingController(); + FocusNode urlFocus = FocusNode(); + late List> greeting; + AutovalidateMode validate = AutovalidateMode.disabled; + + initialise({String inviteUrl = ''}) { + final uri = inviteUrl; + if (uri.isNotEmpty) { + final box = Hive.box('url'); + box.put(urlKey, uri); + box.put(imageUrlKey, "$uri/talawa/"); + graphqlConfig.getOrgUrl(); + } + greeting = [ + { + 'text': 'Join ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline6! + .copyWith(fontSize: 24, fontWeight: FontWeight.w700) + }, + { + 'text': 'and ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5 + }, + { + 'text': 'Collaborate ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline6! + .copyWith(fontSize: 24, fontWeight: FontWeight.w700) + }, + { + 'text': 'with your ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5 + }, + { + 'text': 'Organizations', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5! + .copyWith(fontSize: 24, color: const Color(0xFF4285F4)) + }, + ]; + } + + checkURLandNavigate(String navigateTo, String argument) async { + urlFocus.unfocus(); + validate = AutovalidateMode.always; + if (formKey.currentState!.validate()) { + navigationService + .pushDialog(const CustomProgressDialog(key: Key('UrlCheckProgress'))); + validate = AutovalidateMode.disabled; + final String uri = url.text.trim(); + final bool? urlPresent = await Validator.validateUrlExistence(uri); + if (urlPresent! == true) { + final box = Hive.box('url'); + await box.put(urlKey, uri); + await box.put(imageUrlKey, "$uri/talawa/"); + + navigationService.pop(); + graphqlConfig.getOrgUrl(); + navigationService.pushScreen(navigateTo, arguments: argument); + } else { + navigationService + .showSnackBar("URL doesn't exist/no connection please check"); + } + } + } +} diff --git a/lib/view_model/pre_auth_view_models/signup_details_view_model.dart b/lib/view_model/pre_auth_view_models/signup_details_view_model.dart new file mode 100644 index 000000000..868a0d62a --- /dev/null +++ b/lib/view_model/pre_auth_view_models/signup_details_view_model.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/view_model/base_view_model.dart'; +import 'package:talawa/widgets/custom_progress_dialog.dart'; + +class SignupDetailsViewModel extends BaseModel { + final formKey = GlobalKey(); + late List> greeting; + late OrgInfo selectedOrganization; + TextEditingController confirmPassword = TextEditingController(); + TextEditingController firstName = TextEditingController(); + TextEditingController lastName = TextEditingController(); + TextEditingController password = TextEditingController(); + TextEditingController email = TextEditingController(); + AutovalidateMode validate = AutovalidateMode.disabled; + FocusNode confirmFocus = FocusNode(); + bool hidePassword = true; + + initialise(OrgInfo org) { + selectedOrganization = org; + greeting = [ + { + 'text': "Let's ", + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5 + }, + { + 'text': 'get ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5 + }, + { + 'text': "you ", + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5 + }, + { + 'text': 'SignUp ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline6! + .copyWith(fontSize: 24) + }, + ]; + } + + signUp() async { + FocusScope.of(navigationService.navigatorKey.currentContext!).unfocus(); + setState(ViewState.busy); + validate = AutovalidateMode.always; + setState(ViewState.idle); + if (formKey.currentState!.validate()) { + validate = AutovalidateMode.disabled; + navigationService + .pushDialog(const CustomProgressDialog(key: Key('SignUpProgress'))); + databaseFunctions.init(); + try { + final QueryResult result = await databaseFunctions.gqlNonAuthMutation( + queries.registerUser( + firstName.text, lastName.text, email.text, password.text)) + as QueryResult; + final User signedInUser = + User.fromJson(result.data!['signUp'] as Map); + final bool userSaved = await userConfig.updateUser(signedInUser); + final bool tokenRefreshed = await graphqlConfig.getToken() as bool; + if (userSaved && tokenRefreshed) { + if (selectedOrganization.isPublic!) { + try { + final QueryResult result = + await databaseFunctions.gqlAuthMutation( + queries.joinOrgById(selectedOrganization.id!)) + as QueryResult; + + final List? joinedOrg = + (result.data!['joinPublicOrganization']['joinedOrganizations'] + as List?) + ?.map((e) => OrgInfo.fromJson(e as Map)) + .toList(); + userConfig.updateUserJoinedOrg(joinedOrg!); + userConfig.saveCurrentOrgInHive( + userConfig.currentUser.joinedOrganizations![0]); + navigationService.removeAllAndPush('/mainScreen', '/'); + } on Exception catch (e) { + print(e); + navigationService.showSnackBar('SomeThing went wrong'); + } + } else { + try { + final QueryResult result = + await databaseFunctions.gqlAuthMutation(queries + .sendMembershipRequest(selectedOrganization.id!)) + as QueryResult; + + final OrgInfo membershipRequest = OrgInfo.fromJson( + result.data!['sendMembershipRequest']['organization'] + as Map); + userConfig.updateUserMemberRequestOrg([membershipRequest]); + navigationService.pop(); + navigationService.removeAllAndPush('/waiting', '/'); + } on Exception catch (e) { + print(e); + navigationService.showSnackBar('SomeThing went wrong'); + } + } + } + } on Exception catch (e) { + print(e); + navigationService.showSnackBar('SomeThing went wrong'); + } + } + } +} diff --git a/lib/view_model/pre_auth_view_models/waiting_view_model.dart b/lib/view_model/pre_auth_view_models/waiting_view_model.dart new file mode 100644 index 000000000..d6d18da25 --- /dev/null +++ b/lib/view_model/pre_auth_view_models/waiting_view_model.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:talawa/constants/routing_constants.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/view_model/base_view_model.dart'; + +class WaitingViewModel extends BaseModel { + late List> greeting; + late List pendingRequestOrg; + late User currentUser; + + initialise() { + currentUser = userConfig.currentUser; + pendingRequestOrg = currentUser.membershipRequests!; + print(pendingRequestOrg.length); + greeting = [ + { + 'text': "Please wait ", + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5 + }, + { + 'text': currentUser.firstName, + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline6! + .copyWith(fontSize: 24) + }, + { + 'text': " for organisation(s) to accept your invitation. ", + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5 + }, + ]; + } + + logout() { + final user = Hive.box('currentUser'); + final url = Hive.box('url'); + user.clear(); + url.clear(); + navigationService.removeAllAndPush('/selectLang', '/', arguments: '0'); + } + + joinOrg() { + navigationService.pushScreen(Routes.joinOrg); + } +} diff --git a/lib/view_model/widgets_view_models/comments_view_model.dart b/lib/view_model/widgets_view_models/comments_view_model.dart new file mode 100644 index 000000000..974c46487 --- /dev/null +++ b/lib/view_model/widgets_view_models/comments_view_model.dart @@ -0,0 +1,54 @@ +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/comment/comment_model.dart'; +import 'package:talawa/services/comment_service.dart'; +import 'package:talawa/services/post_service.dart'; +import 'package:talawa/services/user_config.dart'; +import 'package:talawa/view_model/base_view_model.dart'; + +class CommentsViewModel extends BaseModel { + late CommentService _commentService; + late PostService _postService; + late String _postID; + late List _commentlist; + late UserConfig _userConfig; + + // Getters + List get commentList => _commentlist; + + void initialise(String postID) { + _commentlist = []; + _postID = postID; + _commentService = locator(); + _userConfig = locator(); + _postService = locator(); + notifyListeners(); + getComments(); + } + + Future getComments() async { + setState(ViewState.busy); + final List _commentsJSON = + await _commentService.getCommentsForPost(_postID) as List; + print(_commentsJSON); + _commentsJSON.forEach((commentJson) { + _commentlist.add(Comment.fromJson(commentJson as Map)); + }); + setState(ViewState.idle); + } + + Future createComment(String msg) async { + print("comment viewModel called"); + await _commentService.createComments(_postID, msg); + addCommentLocally(msg); + } + + void addCommentLocally(String msg) { + _postService.addCommentLocally(_postID); + final _creator = _userConfig.currentUser; + final Comment _localComment = Comment( + text: msg, createdAt: DateTime.now().toString(), creator: _creator); + _commentlist.insert(0, _localComment); + notifyListeners(); + } +} diff --git a/lib/view_model/widgets_view_models/custom_drawer_view_model.dart b/lib/view_model/widgets_view_models/custom_drawer_view_model.dart new file mode 100644 index 000000000..ccc1325c8 --- /dev/null +++ b/lib/view_model/widgets_view_models/custom_drawer_view_model.dart @@ -0,0 +1,48 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/view_model/base_view_model.dart'; + +class CustomDrawerViewModel extends BaseModel { + final ScrollController controller = ScrollController(); + late User _currentUser; + late List _switchAbleOrg; + late OrgInfo _selectedOrg; + late StreamSubscription _currentOrganizationStreamSubscription; + OrgInfo get selectedOrg => _selectedOrg; + List get switchAbleOrg => _switchAbleOrg; + + initialize() { + _currentOrganizationStreamSubscription = userConfig.currentOrfInfoStream + .listen((updatedOrganization) => + setSelectedOrganizationName(updatedOrganization)); + _currentUser = userConfig.currentUser; + _selectedOrg = userConfig.currentOrg; + _switchAbleOrg = _currentUser.joinedOrganizations!; + } + + switchOrg(OrgInfo switchToOrg) { + if (selectedOrg == switchToOrg) { + // _navigationService.pop(); + navigationService.showSnackBar('${switchToOrg.name} already selected'); + } else { + userConfig.saveCurrentOrgInHive(switchToOrg); + navigationService.showSnackBar('Switched to ${switchToOrg.name}'); + } + navigationService.pop(); + } + + setSelectedOrganizationName(OrgInfo updatedOrganization) { + _selectedOrg = updatedOrganization; + notifyListeners(); + } + + @override + void dispose() { + _currentOrganizationStreamSubscription.cancel(); + super.dispose(); + } +} diff --git a/lib/view_model/widgets_view_models/like_button_view_model.dart b/lib/view_model/widgets_view_models/like_button_view_model.dart new file mode 100644 index 000000000..07490c2a8 --- /dev/null +++ b/lib/view_model/widgets_view_models/like_button_view_model.dart @@ -0,0 +1,76 @@ +import 'dart:async'; + +import 'package:talawa/locator.dart'; +import 'package:talawa/models/post/post_model.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/services/post_service.dart'; +import 'package:talawa/services/user_config.dart'; +import 'package:talawa/view_model/base_view_model.dart'; + +class LikeButtonViewModel extends BaseModel { + // Services + final _userConfig = locator(); + final _postService = locator(); + + // Local Variables for session caching + bool _isLiked = false; + late User _user; + List _likedBy = []; + late String _postID; + + // ignore: unused_field + late StreamSubscription _updatePostSubscription; + + // Getters + bool get isLiked => _isLiked; + List get likedBy => _likedBy; + int get likesCount => _likedBy.length; + // initialize + void initialize(List likedBy, String postID) { + _postID = postID; + _user = _userConfig.currentUser; + _likedBy = likedBy; + notifyListeners(); + checkAndSetTheIsLiked(); + _updatePostSubscription = + _postService.updatedPostStream.listen((post) => updatePost(post)); + } + + void toggleIsLiked() { + if (!_isLiked) { + _postService.addLike(_postID); + } else { + _postService.removeLike(_postID); + } + } + + void setIsLiked({bool val = true}) { + _isLiked = val; + notifyListeners(); + } + + /*TODO: This function must be removed bec the checking, that the user + has liked the post or not must be send from the backend and not + the processing should be done in front end.*/ + void checkAndSetTheIsLiked() { + setIsLiked(val: false); + for (var i = 0; i < _likedBy.length; i++) { + if (_likedBy[i].sId == _user.id) { + setIsLiked(); + } + } + } + + updatePost(Post post) { + if (_postID == post.sId) { + _likedBy = post.likedBy!; + checkAndSetTheIsLiked(); + } + } + + @override + // ignore: must_call_super + void dispose() { + _updatePostSubscription.cancel(); + } +} diff --git a/lib/view_model/widgets_view_models/progress_dialog_view_model.dart b/lib/view_model/widgets_view_models/progress_dialog_view_model.dart new file mode 100644 index 000000000..b60b347e6 --- /dev/null +++ b/lib/view_model/widgets_view_models/progress_dialog_view_model.dart @@ -0,0 +1,21 @@ +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/view_model/base_view_model.dart'; + +class ProgressDialogViewModel extends BaseModel { + late ConnectivityResult connectivityResult; + bool connectivityPresent = false; + initialise() async { + setState(ViewState.busy); + connectivityResult = await Connectivity().checkConnectivity(); + if (connectivityResult == ConnectivityResult.none) { + connectivityPresent = false; + Future.delayed(const Duration(seconds: 2)) + .then((value) => navigationService.pop()); + } else { + connectivityPresent = true; + } + setState(ViewState.idle); + } +} diff --git a/lib/views/after_auth_screens/add_post_page.dart b/lib/views/after_auth_screens/add_post_page.dart new file mode 100644 index 000000000..99ced46fd --- /dev/null +++ b/lib/views/after_auth_screens/add_post_page.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/utils/app_localization.dart'; + +class AddPost extends StatelessWidget { + const AddPost({Key? key, this.drawerKey}) : super(key: key); + final GlobalKey? drawerKey; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + elevation: 0.9, + centerTitle: true, + title: Text( + AppLocalizations.of(context)!.strictTranslate('Share News'), + style: Theme.of(context).textTheme.headline6!.copyWith( + fontWeight: FontWeight.w600, + fontSize: 20, + ), + ), + leading: IconButton( + icon: const Icon(Icons.menu), + onPressed: () => drawerKey!.currentState!.openDrawer(), + ), + actions: [ + TextButton( + onPressed: () {}, + child: Text( + AppLocalizations.of(context)!.strictTranslate("Post"), + style: Theme.of(context).textTheme.headline5!.copyWith( + fontWeight: FontWeight.w600, + color: Theme.of(context).accentColor), + ), + ), + ], + ), + body: Column( + children: [ + ListTile( + leading: const CircleAvatar(radius: 25), + title: const Text('Rutvik Chandla'), + subtitle: Text(AppLocalizations.of(context)! + .strictTranslate('Organization Name')), + ), + Row( + children: [ + IconButton(onPressed: () {}, icon: const Icon(Icons.photo)), + IconButton( + onPressed: () {}, icon: const Icon(Icons.camera_alt)), + IconButton( + onPressed: () {}, icon: const Icon(Icons.file_upload)), + TextButton( + onPressed: () {}, + child: Text( + '# ${AppLocalizations.of(context)!.strictTranslate("Add hasthtag")}', + style: Theme.of(context).textTheme.headline6, + )), + ], + ), + const Divider(), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: TextField( + maxLines: null, + decoration: InputDecoration( + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + hintText: AppLocalizations.of(context)!.strictTranslate( + "Write here what do you want to share")), + ), + )) + ], + )); + } +} diff --git a/lib/views/after_auth_screens/events/create_event_form.dart b/lib/views/after_auth_screens/events/create_event_form.dart new file mode 100644 index 000000000..474fb6879 --- /dev/null +++ b/lib/views/after_auth_screens/events/create_event_form.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/utils/validators.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/create_event_view_model.dart'; + +class CreateEventForm extends StatelessWidget { + const CreateEventForm({Key? key, required this.model}) : super(key: key); + final CreateEventViewModel model; + @override + Widget build(BuildContext context) { + return Form( + key: model.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + textInputAction: TextInputAction.next, + controller: model.eventTitleTextController, + keyboardType: TextInputType.name, + maxLength: 20, + focusNode: model.titleFocus, + validator: (value) { + final String? val = Validator.validateEventForm(value!, 'Title'); + if (val != null) { + return AppLocalizations.of(context)!.translate(val); + } + + return null; + }, + decoration: InputDecoration( + labelText: + AppLocalizations.of(context)!.translate('Add Event Title'), + isDense: true, + labelStyle: Theme.of(context).textTheme.subtitle1, + focusedBorder: InputBorder.none, + counterText: "", + enabledBorder: InputBorder.none, + prefixIcon: Container( + transform: Matrix4.translationValues( + -SizeConfig.screenWidth! * 0.027, 0.0, 0.0), + child: const Icon(Icons.title, size: 25), + ), + ), + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.013, + ), + TextFormField( + textInputAction: TextInputAction.next, + keyboardType: TextInputType.streetAddress, + controller: model.eventLocationTextController, + focusNode: model.locationFocus, + validator: (value) { + final String? val = + Validator.validateEventForm(value!, 'Location'); + if (val != null) { + return AppLocalizations.of(context)!.translate(val); + } + + return null; + }, + decoration: InputDecoration( + hintText: + '${AppLocalizations.of(context)!.translate("Where is the event")}?', + labelText: + AppLocalizations.of(context)!.translate('Add Location'), + labelStyle: Theme.of(context).textTheme.subtitle1, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + prefixIcon: Container( + transform: Matrix4.translationValues( + -SizeConfig.screenWidth! * 0.027, 0.0, 0.0), + child: const Icon(Icons.place, size: 25), + ), + ), + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.013, + ), + TextFormField( + keyboardType: TextInputType.multiline, + controller: model.eventDescriptionTextController, + focusNode: model.descriptionFocus, + validator: (value) { + final String? val = + Validator.validateEventForm(value!, 'Description'); + if (val != null) { + return AppLocalizations.of(context)!.translate(val); + } + + return null; + }, + maxLines: 10, + minLines: 1, + decoration: InputDecoration( + hintText: + AppLocalizations.of(context)!.translate('Describe the event'), + labelText: + AppLocalizations.of(context)!.translate('Add Description'), + labelStyle: Theme.of(context).textTheme.subtitle1, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + prefixIcon: Container( + transform: Matrix4.translationValues( + -SizeConfig.screenWidth! * 0.027, 0.0, 0.0), + child: const Icon(Icons.view_headline, size: 25), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/views/after_auth_screens/events/create_event_page.dart b/lib/views/after_auth_screens/events/create_event_page.dart new file mode 100644 index 000000000..39b51f9e3 --- /dev/null +++ b/lib/views/after_auth_screens/events/create_event_page.dart @@ -0,0 +1,270 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/create_event_view_model.dart'; +import 'package:talawa/views/after_auth_screens/events/create_event_form.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/date_time_picker.dart'; +import 'package:talawa/widgets/event_date_time_tile.dart'; + +class CreateEventPage extends StatefulWidget { + const CreateEventPage({Key? key}) : super(key: key); + + @override + _CreateEventPageState createState() => _CreateEventPageState(); +} + +class _CreateEventPageState extends State { + @override + Widget build(BuildContext context) { + final TextStyle _subtitleTextStyle = + Theme.of(context).textTheme.headline5!.copyWith(fontSize: 16); + return BaseView( + onModelReady: (model) => model.initialize(), + builder: (context, model, child) { + return Scaffold( + appBar: AppBar( + elevation: 1, + centerTitle: true, + leading: GestureDetector( + onTap: () { + navigationService.pop(); + }, + child: const Icon(Icons.close)), + title: Text( + AppLocalizations.of(context)!.strictTranslate('Add Event'), + style: Theme.of(context).textTheme.headline6!.copyWith( + fontWeight: FontWeight.w600, + fontSize: 20, + ), + ), + actions: [ + TextButton( + onPressed: () { + model.createEvent(); + }, + child: Text( + AppLocalizations.of(context)!.strictTranslate('Add'), + style: Theme.of(context).textTheme.bodyText1!.copyWith( + fontSize: 16, color: Theme.of(context).accentColor), + ), + ), + ], + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon( + Icons.image, + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.036, + ), + TextButton( + onPressed: () {}, + child: Text( + AppLocalizations.of(context)! + .strictTranslate("Add Image"), + style: _subtitleTextStyle, + ), + ) + ], + ), + const Divider(), + CreateEventForm( + model: model, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.013, + ), + const Divider(), + Text( + AppLocalizations.of(context)! + .strictTranslate('Select Start Date and Time'), + style: _subtitleTextStyle, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.013, + ), + DateTimeTile( + child: Row( + children: [ + const Icon( + Icons.calendar_today, + color: Color(0xff524F4F), + size: 19, + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.045, + ), + GestureDetector( + onTap: () async { + final _date = await customDatePicker( + initialDate: model.eventStartDate); + setState(() { + model.eventStartDate = _date; + }); + }, + child: Text( + "${model.eventStartDate.toLocal()}".split(' ')[0], + style: const TextStyle(fontSize: 16), + ), + ), + const Spacer(), + const Icon( + Icons.schedule, + color: Color(0xff524F4F), + size: 19, + ), + SizedBox(width: SizeConfig.screenWidth! * 0.045), + GestureDetector( + onTap: () async { + final _time = await customTimePicker( + initialTime: model.eventStartTime); + + setState(() { + model.eventStartTime = _time; + }); + }, + child: Text( + model.eventStartTime.format(context), + style: const TextStyle(fontSize: 16), + ), + ), + ], + )), + SizedBox( + height: SizeConfig.screenHeight! * 0.026, + ), + Text( + AppLocalizations.of(context)! + .strictTranslate('Select End Date and Time'), + style: Theme.of(context) + .textTheme + .headline5! + .copyWith(fontSize: 16), + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.013, + ), + DateTimeTile( + child: Row( + children: [ + const Icon( + Icons.calendar_today, + color: Color(0xff524F4F), + size: 19, + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.045, + ), + GestureDetector( + onTap: () async { + final _date = await customDatePicker( + initialDate: model.eventEndDate); + setState(() { + model.eventEndDate = _date; + }); + }, + child: Text( + "${model.eventEndDate.toLocal()}".split(' ')[0], + style: const TextStyle(fontSize: 16), + ), + ), + const Spacer(), + const Icon( + Icons.schedule, + color: Color(0xff524F4F), + size: 19, + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.045, + ), + GestureDetector( + onTap: () async { + final _time = await customTimePicker( + initialTime: model.eventEndTime); + + setState(() { + model.eventEndTime = _time; + }); + }, + child: Text( + model.eventEndTime.format(context), + style: const TextStyle(fontSize: 16), + ), + ), + ], + ), + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.026, + ), + Row( + children: [ + const Icon(Icons.restore), + SizedBox( + width: SizeConfig.screenWidth! * 0.045, + ), + Text( + AppLocalizations.of(context)! + .strictTranslate('Does not repeat'), + style: _subtitleTextStyle, + ) + ], + ), + SizedBox(height: SizeConfig.screenHeight! * 0.026), + const Divider(), + Row( + children: [ + Text( + AppLocalizations.of(context)! + .strictTranslate('Keep Public'), + style: _subtitleTextStyle), + SizedBox( + width: SizeConfig.screenWidth! * 0.005, + ), + Switch( + value: model.isPublicSwitch, + onChanged: (value) { + setState(() { + model.isPublicSwitch = value; + print(model.isPublicSwitch); + }); + }, + activeColor: Theme.of(context).colorScheme.primary, + ), + const Spacer(), + Text( + AppLocalizations.of(context)! + .strictTranslate('Keep Registerable'), + style: _subtitleTextStyle), + SizedBox( + width: SizeConfig.screenWidth! * 0.005, + ), + Switch( + value: model.isRegisterableSwitch, + onChanged: (value) { + setState(() { + model.isRegisterableSwitch = value; + print(model.isRegisterableSwitch); + }); + }, + activeColor: Theme.of(context).colorScheme.primary, + ), + ], + ), + ], + ), + ), + ), + ); + }); + } +} diff --git a/lib/views/after_auth_screens/events/event_info_page.dart b/lib/views/after_auth_screens/events/event_info_page.dart new file mode 100644 index 000000000..7d05b80e7 --- /dev/null +++ b/lib/views/after_auth_screens/events/event_info_page.dart @@ -0,0 +1,257 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/widgets/custom_list_tile.dart'; + +class EventInfoPage extends StatefulWidget { + const EventInfoPage({Key? key, required this.event}) : super(key: key); + final Event event; + + @override + _EventInfoPageState createState() => _EventInfoPageState(); +} + +class _EventInfoPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + title: Text( + AppLocalizations.of(context)!.strictTranslate('Event Details')), + pinned: true, + expandedHeight: SizeConfig.screenWidth, + flexibleSpace: FlexibleSpaceBar( + background: Image.network( + 'https://picsum.photos/id/26/200/300', + fit: BoxFit.fill, + ), + ), + ), + _eventInfoBody() + ], + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () { + //EventService().registerForAnEvent(widget.event.id!); + }, + label: Text( + AppLocalizations.of(context)!.strictTranslate("Register"), + style: Theme.of(context) + .textTheme + .bodyText2! + .copyWith(color: Theme.of(context).accentColor), + )), + ); + } + + Widget _eventInfoBody() { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.event.title!, + style: Theme.of(context) + .textTheme + .headline4! + .copyWith(fontSize: 26), + ), + const Icon(Icons.chat_bubble_outline) + ], + ), + Row( + children: [ + widget.event.creator!.firstName != null + ? Text( + "${AppLocalizations.of(context)!.strictTranslate("Created by")}: ${widget.event.creator!.firstName} ${widget.event.creator!.lastName}", + style: Theme.of(context) + .textTheme + .bodyText2! + .copyWith(fontWeight: FontWeight.w600), + ) + : Text( + "${AppLocalizations.of(context)!.strictTranslate("Created by")}: Luke Skywalker", + style: Theme.of(context) + .textTheme + .bodyText2! + .copyWith(fontWeight: FontWeight.w600), + ), + ], + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.011, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon( + Icons.calendar_today, + size: 13, + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.027, + ), + Text( + "${widget.event.startDate!} - ${widget.event.endDate!}", + style: Theme.of(context).textTheme.caption, + ), + const Spacer(), + widget.event.isPublic! + ? Icon( + Icons.lock_open, + size: 13, + color: Theme.of(context).accentColor, + ) + : Icon( + Icons.lock, + size: 13, + color: Theme.of(context).colorScheme.primary, + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.027, + ), + widget.event.isPublic! + ? Text( + AppLocalizations.of(context)!.strictTranslate('public'), + style: Theme.of(context).textTheme.caption, + ) + : Text( + AppLocalizations.of(context)! + .strictTranslate('private'), + style: Theme.of(context).textTheme.caption, + ), + ], + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.011, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon( + Icons.schedule, + size: 12, + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.025, + ), + Text( + "${widget.event.startTime!} - ${widget.event.endTime!}", + style: Theme.of(context).textTheme.caption, + ), + ], + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.011, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon( + Icons.place, + size: 12, + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.027, + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.88, + child: Text( + widget.event.location!, + style: Theme.of(context).textTheme.caption, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.left, + maxLines: 3, + ), + ), + ], + ), + + const Divider(), + SizedBox( + height: SizeConfig.screenHeight! * 0.013, + ), + Text( + AppLocalizations.of(context)!.strictTranslate("Description"), + style: + Theme.of(context).textTheme.headline5!.copyWith(fontSize: 16), + ), + SizedBox(width: SizeConfig.screenWidth! * 0.013), + Text( + widget.event.description!, + style: Theme.of(context).textTheme.caption, + ), + SizedBox(height: SizeConfig.screenHeight! * 0.013), + Text( + AppLocalizations.of(context)!.strictTranslate("Admins"), + style: + Theme.of(context).textTheme.headline5!.copyWith(fontSize: 16), + ), + Divider( + color: Theme.of(context).colorScheme.onBackground, + thickness: 2, + ), + CustomListTile( + index: 0, + key: const Key('Admins'), + type: TileType.user, + userInfo: userConfig.currentUser, + onTapUserInfo: () {}), + SizedBox( + height: SizeConfig.screenHeight! * 0.013, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.strictTranslate("Attendees"), + style: Theme.of(context) + .textTheme + .headline5! + .copyWith(fontSize: 16), + ), + Text( + AppLocalizations.of(context)!.strictTranslate('See all'), + style: Theme.of(context) + .textTheme + .caption! + .copyWith(color: const Color(0xff4285F4)), + ), + ], + ), + Divider( + color: Theme.of(context).colorScheme.onBackground, + thickness: 2, + ), + + // Needs to be replaced with actual event attendees + ListView.builder( + padding: const EdgeInsets.all(0.0), + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: 10, + itemBuilder: (BuildContext context, int index) { + return CustomListTile( + key: Key( + '${AppLocalizations.of(context)!.strictTranslate("Attendee")}$index'), + index: index, + type: TileType.user, + userInfo: userConfig.currentUser, + onTapUserInfo: () {}); + }) + ], + ), + ), + ); + } +} diff --git a/lib/views/after_auth_screens/events/explore_event_dialogue.dart b/lib/views/after_auth_screens/events/explore_event_dialogue.dart new file mode 100644 index 000000000..cf7384968 --- /dev/null +++ b/lib/views/after_auth_screens/events/explore_event_dialogue.dart @@ -0,0 +1,142 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/widgets/date_time_picker.dart'; + +class ExploreEventDialog extends StatefulWidget { + const ExploreEventDialog({required Key key}) : super(key: key); + @override + _ExploreEventDialogState createState() => _ExploreEventDialogState(); +} + +class _ExploreEventDialogState extends State { + DateTime _startDate = DateTime.now(); + DateTime _endDate = DateTime.now().add(const Duration(days: 1)); + + @override + Widget build(BuildContext context) { + return AlertDialog( + insetPadding: EdgeInsets.all( + SizeConfig.screenWidth! * 0.027, + ), + actions: [ + Padding( + padding: const EdgeInsets.all(26), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(AppLocalizations.of(context)! + .strictTranslate('Start Date')), + const SizedBox( + height: 5, + ), + GestureDetector( + onTap: () async { + final _date = + await customDatePicker(initialDate: _startDate); + setState(() { + _startDate = _date; + }); + }, + child: SizedBox( + height: SizeConfig.screenHeight! * 0.07, + width: SizeConfig.screenWidth! * 0.36, + child: Card( + color: Theme.of(context).colorScheme.primaryVariant, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const Icon( + Icons.calendar_today, + size: 19, + ), + Text( + "${_startDate.toLocal()}".split(' ')[0], + ), + ], + ), + ), + ), + ) + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(AppLocalizations.of(context)! + .strictTranslate('End Date')), + const SizedBox( + height: 5, + ), + GestureDetector( + onTap: () async { + final _date = + await customDatePicker(initialDate: _endDate); + setState(() { + _endDate = _date; + }); + }, + child: SizedBox( + height: SizeConfig.screenHeight! * 0.07, + width: SizeConfig.screenWidth! * 0.36, + child: Card( + color: Theme.of(context).colorScheme.primaryVariant, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const Icon( + Icons.calendar_today, + size: 20, + ), + Text( + "${_endDate.toLocal()}".split(' ')[0], + ), + ], + ), + ), + ), + ), + ], + ) + ], + ), + ), + Padding( + padding: EdgeInsets.only( + right: SizeConfig.screenWidth! * 0.072, + ), + child: SizedBox( + width: SizeConfig.screenWidth! * 0.36, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () { + navigationService.pop(); + }, + child: Text( + AppLocalizations.of(context)!.strictTranslate('Cancel'), + style: Theme.of(context).textTheme.bodyText2, + )), + TextButton( + onPressed: () { + navigationService.pop(); + }, + child: Text( + AppLocalizations.of(context)!.strictTranslate('Done'), + style: + const TextStyle(fontSize: 14, color: Color(0xff4285F4)), + ), + ) + ], + ), + ), + ) + ], + ); + } +} diff --git a/lib/views/after_auth_screens/events/explore_events.dart b/lib/views/after_auth_screens/events/explore_events.dart new file mode 100644 index 000000000..f63feefc5 --- /dev/null +++ b/lib/views/after_auth_screens/events/explore_events.dart @@ -0,0 +1,195 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart'; +import 'package:talawa/views/after_auth_screens/events/explore_event_dialogue.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/event_card.dart'; + +class ExploreEvents extends StatelessWidget { + const ExploreEvents({required Key key, this.drawerKey}) : super(key: key); + final GlobalKey? drawerKey; + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) => model.initialise(), + builder: (context, model, child) { + return Scaffold( + appBar: AppBar( + key: const Key("ExploreEventsAppBar"), + elevation: 0.0, + automaticallyImplyLeading: false, + centerTitle: true, + title: Text( + AppLocalizations.of(context)!.strictTranslate('Explore Events'), + style: Theme.of(context).textTheme.headline6!.copyWith( + fontWeight: FontWeight.w600, + fontSize: 20, + ), + ), + leading: IconButton( + icon: const Icon(Icons.menu), + onPressed: () => drawerKey!.currentState!.openDrawer(), + ), + actions: [ + Padding( + padding: EdgeInsets.only( + right: SizeConfig.screenWidth! * 0.027, + ), + child: const Icon(Icons.search, size: 20), + ) + ], + ), + body: model.isBusy + ? const CircularProgressIndicator() + : RefreshIndicator( + onRefresh: () async => model.fetchNewEvents(), + child: Stack( + children: [ + ListView(), + SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: SizeConfig.screenWidth! * 0.027), + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Card( + color: Theme.of(context) + .colorScheme + .secondary, + elevation: 2, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 20), + width: SizeConfig.screenWidth! * 0.55, + child: DropdownButtonHideUnderline( + child: + dropDownList(model, context)), + ), + ), + GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (_) { + return const ExploreEventDialog( + key: Key('ExploreEvents')); + }); + }, + child: Card( + color: Theme.of(context) + .colorScheme + .secondary, + child: SizedBox( + height: + SizeConfig.screenHeight! * 0.068, + width: SizeConfig.screenWidth! * 0.27, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + children: [ + const Icon( + Icons.calendar_today, + color: Color(0xff524F4F), + size: 19, + ), + SizedBox( + width: SizeConfig.screenWidth! * + 0.17, + child: Text( + AppLocalizations.of(context)! + .strictTranslate( + "Add Date"), + style: const TextStyle( + fontSize: 16), + ), + ), + ], + ), + ), + ), + ) + ], + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.027), + ListView.builder( + physics: + const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: model.events.length, + itemBuilder: + (BuildContext context, int index) { + return GestureDetector( + onTap: () { + navigationService.pushScreen( + "/eventInfo", + arguments: model.events[index]); + }, + child: EventCard( + event: model.events[index], + ), + ); + }), + ], + ), + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton.extended( + backgroundColor: Theme.of(context).primaryColor, + onPressed: () { + navigationService.pushScreen( + "/createEventPage", + ); + }, + icon: Icon( + Icons.add, + color: Theme.of(context).accentColor, + ), + label: Text( + AppLocalizations.of(context)!.strictTranslate("Event"), + style: Theme.of(context) + .textTheme + .headline5! + .copyWith(color: Theme.of(context).accentColor), + ), + ), + ); + }); + } + + Widget dropDownList(ExploreEventsViewModel model, BuildContext context) { + return DropdownButton( + value: model.chosenValue, + isExpanded: true, + items: [ + 'My Events', + 'Public Events', + 'Private Events', + ].map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + AppLocalizations.of(context)!.strictTranslate(value), + style: Theme.of(context) + .textTheme + .headline6! + .copyWith(color: Theme.of(context).accentColor), + ), + ); + }).toList(), + onChanged: (value) { + model.choseValue(value!); + }, + ); + } +} diff --git a/lib/views/after_auth_screens/feed/individual_post.dart b/lib/views/after_auth_screens/feed/individual_post.dart new file mode 100644 index 000000000..76f2e7fa8 --- /dev/null +++ b/lib/views/after_auth_screens/feed/individual_post.dart @@ -0,0 +1,218 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/models/comment/comment_model.dart'; +import 'package:talawa/models/post/post_model.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/widgets_view_models/comments_view_model.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/post_widget.dart'; + +//Global Stete, should be removed in next few iterations +late CommentsViewModel _commentViewModel; + +class IndividualPostView extends StatefulWidget { + const IndividualPostView({Key? key, required this.post}) : super(key: key); + final Post post; + + @override + _IndividualPostViewState createState() => _IndividualPostViewState(); +} + +class _IndividualPostViewState extends State { + final TextEditingController _controller = TextEditingController(); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + elevation: 0.0, + ), + bottomSheet: Container( + height: 60, + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _controller, + textInputAction: TextInputAction.send, + onSubmitted: (msg) { + _commentViewModel.createComment(msg); + _controller.text = ""; + }, + textAlign: TextAlign.start, + decoration: const InputDecoration( + hintText: "Write your comment here..", + contentPadding: EdgeInsets.all(8.0), + focusColor: Colors.black, + border: InputBorder.none, + ), + keyboardType: TextInputType.text, + ), + ), + TextButton( + onPressed: () { + _commentViewModel.createComment(_controller.text); + _controller.text = ""; + }, + child: const Text("Send")) + ], + ), + ), + body: ListView( + children: [ + NewsPost( + post: widget.post, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IndividualPageLikeSection( + usersLiked: widget.post.likedBy!, + ), + IndividualPostCommentSection( + comments: widget.post.comments!, + postID: widget.post.sId, + ) + ], + ), + ) + ], + ), + ); + } +} + +Padding buildPadding(BuildContext context, String text) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + AppLocalizations.of(context)!.strictTranslate(text), + style: Theme.of(context).textTheme.headline6, + ), + ); +} + +class IndividualPageLikeSection extends StatelessWidget { + const IndividualPageLikeSection({ + Key? key, + required this.usersLiked, + }) : super(key: key); + + final List usersLiked; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildPadding(context, "Liked by"), + Row( + children: [ + for (int i = 0; i < usersLiked.length; i++) + likedUserCircleAvatar(usersLiked[i]) + ], + ), + ], + ); + } +} + +class IndividualPostCommentSection extends StatelessWidget { + const IndividualPostCommentSection( + {Key? key, required this.comments, required this.postID}) + : super(key: key); + final List comments; + final String postID; + + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) { + model.initialise(postID); + _commentViewModel = model; + }, + builder: (context, model, child) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildPadding(context, "Comments"), + for (int i = 0; i < model.commentList.length; i++) + CommentTemplate(comment: model.commentList[i]) + ], + ), + ); + } +} + +class CommentTemplate extends StatelessWidget { + const CommentTemplate({ + Key? key, + required this.comment, + }) : super(key: key); + + final Comment comment; + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const CircleAvatar(), + Expanded( + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).iconTheme.color!.withOpacity(0.2), + borderRadius: const BorderRadius.all(Radius.circular(8))), + padding: const EdgeInsets.all(16.0), + margin: const EdgeInsets.only(left: 8.0, bottom: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Text( + "${comment.creator!.firstName!} ${comment.creator!.lastName!}", + style: Theme.of(context).textTheme.bodyText2, + ), + ), + Text( + comment.text!, + style: Theme.of(context) + .textTheme + .bodyText1! + .copyWith(fontSize: 16.0), + ), + ], + ), + )) + ], + ); + } +} + +Widget likedUserCircleAvatar(LikedBy user) { + return Padding( + padding: const EdgeInsets.only(right: 10.0, bottom: 16.0), + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + const CircleAvatar( + backgroundColor: Color(0xfff2f2f2), + radius: 20, + ), + const Positioned( + top: 30, + right: 0, + bottom: 20, + left: 20, + child: Icon( + Icons.thumb_up, + color: Colors.blue, + size: 20, + ), + ), + ], + ), + ); +} diff --git a/lib/views/after_auth_screens/feed/organization_feed.dart b/lib/views/after_auth_screens/feed/organization_feed.dart new file mode 100644 index 000000000..7fb1874c5 --- /dev/null +++ b/lib/views/after_auth_screens/feed/organization_feed.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/view_model/after_auth_view_models/feed_view_models/organization_feed_view_model.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/pinned_carousel_widget.dart'; +import 'package:talawa/widgets/post_list_widget.dart'; + +class OrganizationFeed extends StatelessWidget { + const OrganizationFeed({required Key key, this.drawerKey}) : super(key: key); + final GlobalKey? drawerKey; + + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) => model.initialise(), + builder: (context, model, child) { + return Scaffold( + appBar: AppBar( + elevation: 0.0, + centerTitle: true, + title: Text( + model.currentOrgName, + // "hii", + style: Theme.of(context).textTheme.headline6!.copyWith( + fontWeight: FontWeight.w600, + fontSize: 20, + ), + ), + leading: IconButton( + icon: const Icon(Icons.menu), + onPressed: () => drawerKey!.currentState!.openDrawer(), + ), + ), + body: model.isBusy + ? const CircularProgressIndicator() + : RefreshIndicator( + onRefresh: () async => model.fetchNewPosts(), + child: ListView( + children: [ + model.pinnedPosts.isNotEmpty + ? PinnedPostCarousel( + pinnedPosts: model.pinnedPosts, + navigateToPinnedPostPage: + model.navigateToPinnedPostPage, + navigateToIndividualPostPage: + model.navigateToIndividualPage, + ) + : Container(), + model.posts.isNotEmpty + ? PostListWidget( + posts: model.posts, + function: model.navigateToIndividualPage, + ) + : Container(), + ], + ), + )); + }, + ); + } +} diff --git a/lib/views/after_auth_screens/feed/pinned_post_page.dart b/lib/views/after_auth_screens/feed/pinned_post_page.dart new file mode 100644 index 000000000..6048ae89f --- /dev/null +++ b/lib/views/after_auth_screens/feed/pinned_post_page.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/models/post/post_model.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/widgets/post_list_widget.dart'; + +class PinnedPostPage extends StatelessWidget { + const PinnedPostPage({Key? key, required this.pinnedPosts}) : super(key: key); + final List pinnedPosts; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + elevation: 0.0, + title: + Text(AppLocalizations.of(context)!.strictTranslate('Pinned Posts')), + ), + body: ListView( + children: [PostListWidget(posts: pinnedPosts)], + ), + ); + } +} diff --git a/lib/views/after_auth_screens/join_organisation_after_auth.dart b/lib/views/after_auth_screens/join_organisation_after_auth.dart new file mode 100644 index 000000000..5f3e1e2d7 --- /dev/null +++ b/lib/views/after_auth_screens/join_organisation_after_auth.dart @@ -0,0 +1,107 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/pre_auth_view_models/select_organization_view_model.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/custom_list_tile.dart'; +import 'package:talawa/widgets/raised_round_edge_button.dart'; + +class JoinOrganisationAfterAuth extends StatelessWidget { + const JoinOrganisationAfterAuth({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) => model.initialise('-1'), + builder: (context, model, child) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + AppLocalizations.of(context)! + .strictTranslate('Join Organisation'), + style: Theme.of(context).textTheme.headline6!.copyWith( + fontWeight: FontWeight.w600, + fontSize: 20, + ), + ), + ), + body: Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(12, 12, 12, 8), + child: CupertinoSearchTextField( + focusNode: model.searchFocus, + controller: model.searchController, + onChanged: (value) => model.setState(ViewState.idle), + suffixMode: OverlayVisibilityMode.always, + style: Theme.of(context).textTheme.headline5, + suffixIcon: const Icon( + CupertinoIcons.xmark_circle_fill, + size: 25, + ), + onSuffixTap: () { + model.searchController.clear(); + model.organizations = []; + model.searchFocus.unfocus(); + model.searching = false; + model.setState(ViewState.idle); + }, + ), + ), + model.selectedOrganization.id != '-1' + ? Container( + color: Theme.of(context) + .colorScheme + .secondaryVariant + .withOpacity(0.2), + child: CustomListTile( + index: model.organizations + .indexOf(model.selectedOrganization), + type: TileType.org, + orgInfo: model.selectedOrganization, + onTapOrgInfo: (item) => model.selectOrg(item), + key: const Key('OrgSelItem'), + showIcon: true, + ), + ) + : const SizedBox(), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Divider( + color: Colors.grey, + thickness: 2.0, + ), + ), + Expanded( + child: model.searching + ? model.showSearchList(model.searchController.text) + : model.showOrganizationList()), + SizedBox( + height: SizeConfig.screenHeight! * 0.0215, + ), + model.selectedOrganization.id != '-1' + ? Column( + children: [ + RaisedRoundedButton( + buttonLabel: AppLocalizations.of(context)! + .strictTranslate('Join selected organisation'), + onTap: model.onTapJoin, + textColor: const Color(0xFF008A37), + key: const Key('JoinSelectedOrgButton'), + backgroundColor: Colors.white, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.0215, + ), + ], + ) + : const SizedBox(), + ], + ), + ); + }); + } +} diff --git a/lib/views/after_auth_screens/profile/edit_profile_page.dart b/lib/views/after_auth_screens/profile/edit_profile_page.dart new file mode 100644 index 000000000..ce9415a03 --- /dev/null +++ b/lib/views/after_auth_screens/profile/edit_profile_page.dart @@ -0,0 +1,187 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart'; +import 'package:talawa/views/base_view.dart'; + +class EditProfilePage extends StatefulWidget { + const EditProfilePage({Key? key}) : super(key: key); + + @override + _EditProfilePageState createState() => _EditProfilePageState(); +} + +class _EditProfilePageState extends State { + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) => model.initialize(), + builder: (context, model, child) { + return Scaffold( + appBar: AppBar( + elevation: 0.0, + title: Text( + AppLocalizations.of(context)!.strictTranslate('Profile'), + style: Theme.of(context).textTheme.headline6!.copyWith( + fontWeight: FontWeight.w600, + fontSize: 20, + ), + )), + body: SingleChildScrollView( + child: Column( + children: [ + SizedBox( + height: SizeConfig.screenHeight! * 0.068, + ), + Center( + child: Stack( + children: [ + model.user.values.first.image != null + ? CircleAvatar( + radius: SizeConfig.screenHeight! * 0.082, + backgroundImage: NetworkImage( + model.user.values.first.image!), + ) + : CircleAvatar( + radius: SizeConfig.screenHeight! * 0.082, + backgroundColor: Colors.grey.withOpacity(0.2), + child: Text( + model.user.values.first.firstName! + .toString() + .substring(0, 1) + .toUpperCase() + + model.user.values.first.lastName! + .toString() + .substring(0, 1) + .toUpperCase(), + style: Theme.of(context).textTheme.headline4, + ), + ), + Positioned( + bottom: 0, + right: 0, + child: CircleAvatar( + radius: SizeConfig.screenHeight! * 0.034, + backgroundColor: Theme.of(context).accentColor, + child: const Icon( + Icons.photo_camera, + color: Colors.white, + ), + ), + ), + ], + ), + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.068, + ), + const Divider(), + Padding( + padding: const EdgeInsets.all(10), + child: Column( + children: [ + Row( + children: [ + Flexible( + child: TextFormField( + controller: model.firstNameTextController + ..text = model.user.values.first.firstName!, + focusNode: model.firstNameFocus, + keyboardType: TextInputType.name, + decoration: InputDecoration( + labelText: AppLocalizations.of(context)! + .strictTranslate('First Name'), + labelStyle: + Theme.of(context).textTheme.subtitle1, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + prefixIcon: const Icon(Icons.person), + suffixIcon: IconButton( + onPressed: () { + FocusScope.of(context).requestFocus( + model.firstNameFocus); + }, + icon: const Icon(Icons.edit)), + )), + ), + ], + ), + Row( + children: [ + Flexible( + child: TextFormField( + controller: model.lastNameTextController + ..text = model.user.values.first.lastName!, + focusNode: model.lastNameFocus, + keyboardType: TextInputType.name, + decoration: InputDecoration( + labelText: AppLocalizations.of(context)! + .strictTranslate('Last Name'), + labelStyle: + Theme.of(context).textTheme.subtitle1, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + prefixIcon: const Icon(Icons.person), + suffixIcon: IconButton( + onPressed: () { + FocusScope.of(context).requestFocus( + model.lastNameFocus); + }, + icon: const Icon(Icons.edit)), + )), + ), + ], + ), + ], + ), + ), + const Divider(), + Padding( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + SizedBox( + height: SizeConfig.screenHeight! * 0.027, + width: SizeConfig.screenWidth! * 0.055, + child: const Icon(Icons.email), + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.045, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)! + .strictTranslate('Email'), + style: Theme.of(context) + .textTheme + .caption! + .copyWith( + color: Theme.of(context) + .colorScheme + .onBackground), + ), + Text( + model.user.values.first.email!, + style: Theme.of(context) + .textTheme + .bodyText2! + .copyWith(fontSize: 18), + ) + ], + ), + ], + )), + const Divider(), + TextButton( + onPressed: () async {}, + child: Text(AppLocalizations.of(context)! + .strictTranslate('Update'))) + ], + ), + ), + ); + }); + } +} diff --git a/lib/views/after_auth_screens/profile/profile_page.dart b/lib/views/after_auth_screens/profile/profile_page.dart new file mode 100644 index 000000000..6ae03feb0 --- /dev/null +++ b/lib/views/after_auth_screens/profile/profile_page.dart @@ -0,0 +1,161 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/options/options.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/custom_avatar.dart'; +import 'package:talawa/widgets/custom_list_tile.dart'; +import 'package:talawa/widgets/from_palisadoes.dart'; + +class ProfilePage extends StatefulWidget { + const ProfilePage({Key? key, this.drawerKey}) : super(key: key); + final GlobalKey? drawerKey; + @override + _ProfilePageState createState() => _ProfilePageState(); +} + +class _ProfilePageState extends State { + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) => model.initialize(), + builder: (context, model, child) { + return Scaffold( + appBar: AppBar( + elevation: 0.0, + centerTitle: true, + leading: IconButton( + icon: const Icon(Icons.menu), + onPressed: () => widget.drawerKey!.currentState!.openDrawer(), + ), + key: const Key("ProfilePageAppBar"), + title: Text( + AppLocalizations.of(context)!.strictTranslate('Profile'), + style: Theme.of(context).textTheme.headline6!.copyWith( + fontWeight: FontWeight.w600, + fontSize: 20, + ), + ), + ), + body: model.isBusy + ? const CircularProgressIndicator() + : SingleChildScrollView( + child: Column( + children: [ + SizedBox( + height: SizeConfig.screenHeight! * 0.01, + ), + CustomListTile( + key: const Key('OptionEditProfile'), + index: 0, + type: TileType.option, + option: Options( + icon: CustomAvatar( + isImageNull: model.currentUser.image == null, + firstAlphabet: model.currentUser.firstName! + .substring(0, 1), + imageUrl: model.currentUser.image, + fontSize: Theme.of(context) + .textTheme + .headline6! + .fontSize, + ), + title: + '${model.currentUser.firstName!} ${model.currentUser.lastName!}', + subtitle: model.currentUser.email!, + trailingIconButton: IconButton( + icon: Icon( + Icons.drive_file_rename_outline, + color: Theme.of(context).accentColor, + ), + onPressed: () { + navigationService + .pushScreen("/editProfilePage"); + }, + )), + onTapOption: () {}), + const Divider(), + SizedBox( + height: SizeConfig.screenHeight! * 0.63, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + CustomListTile( + key: const Key('Option0'), + index: 0, + type: TileType.option, + option: Options( + icon: const Icon( + Icons.phonelink_setup, + size: 30, + ), + title: AppLocalizations.of(context)! + .strictTranslate('App Settings'), + subtitle: + '${AppLocalizations.of(context)!.strictTranslate("Language")}, ${AppLocalizations.of(context)!.strictTranslate("dark mode")}, ${AppLocalizations.of(context)!.strictTranslate("font size")}', + ), + onTapOption: () {}), + CustomListTile( + key: const Key('Option1'), + index: 1, + type: TileType.option, + option: Options( + icon: const Icon( + Icons.help_outline, + size: 30, + ), + title: AppLocalizations.of(context)! + .strictTranslate('Help'), + subtitle: AppLocalizations.of(context)! + .strictTranslate( + 'Reach out to us for help'), + ), + onTapOption: () {}), + CustomListTile( + key: const Key('Option2'), + index: 2, + type: TileType.option, + option: Options( + icon: Icon( + Icons.monetization_on, + color: + Theme.of(context).colorScheme.primary, + size: 30, + ), + title: AppLocalizations.of(context)! + .strictTranslate('Donate Us'), + subtitle: AppLocalizations.of(context)! + .strictTranslate( + 'Help us to develop for you'), + ), + onTapOption: () {}), + CustomListTile( + key: const Key('Option3'), + index: 3, + type: TileType.option, + option: Options( + icon: Icon( + Icons.logout, + color: Theme.of(context).accentColor, + size: 30, + ), + title: AppLocalizations.of(context)! + .strictTranslate('Log out'), + subtitle: AppLocalizations.of(context)! + .strictTranslate('Log out from Talawa'), + ), + onTapOption: () => model.logout(context)), + const FromPalisadoes(), + ], + ), + ) + ], + ), + ), + ); + }); + } +} diff --git a/lib/views/base_view.dart b/lib/views/base_view.dart new file mode 100644 index 000000000..2c483b6fc --- /dev/null +++ b/lib/views/base_view.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:talawa/locator.dart'; + +class BaseView extends StatefulWidget { + const BaseView({ + required this.builder, + this.onModelReady, + }); + final Function(T)? onModelReady; + final Widget Function(BuildContext, T, Widget?) builder; + + @override + _BaseViewState createState() => _BaseViewState(); +} + +class _BaseViewState extends State> { + T model = locator(); + + @override + void initState() { + if (widget.onModelReady != null) { + widget.onModelReady!( + model, + ); + } + super.initState(); + } + + @override + Widget build( + BuildContext context, + ) { + return ChangeNotifierProvider( + create: (context) => model, + child: Consumer( + builder: widget.builder, + ), + ); + } +} diff --git a/lib/views/main_screen.dart b/lib/views/main_screen.dart new file mode 100644 index 000000000..668a0c749 --- /dev/null +++ b/lib/views/main_screen.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/main_screen_view_model.dart'; +import 'package:talawa/views/after_auth_screens/add_post_page.dart'; +import 'package:talawa/views/after_auth_screens/events/explore_events.dart'; +import 'package:talawa/views/after_auth_screens/feed/organization_feed.dart'; +import 'package:talawa/views/after_auth_screens/profile/profile_page.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/custom_drawer.dart'; + +class MainScreen extends StatelessWidget { + const MainScreen({Key? key}) : super(key: key); + @override + Widget build(BuildContext context) { + final GlobalKey scaffoldKey = GlobalKey(); + final List childrenPage = [ + OrganizationFeed(key: const Key("HomeView"), drawerKey: scaffoldKey), + ExploreEvents(key: const Key('ExploreEvents'), drawerKey: scaffoldKey), + AddPost(key: const Key('AddPost'), drawerKey: scaffoldKey), + Container( + child: Center( + child: Text( + AppLocalizations.of(context)!.strictTranslate('Chat Screen')), + ), + ), + ProfilePage(key: const Key('Profile'), drawerKey: scaffoldKey), + ]; + + return BaseView(builder: (context, model, child) { + return Scaffold( + key: scaffoldKey, + drawer: const CustomDrawer(), + body: IndexedStack( + index: model.currentIndex, + children: childrenPage, + ), + bottomNavigationBar: BottomNavigationBar( + type: BottomNavigationBarType.fixed, + currentIndex: model.currentIndex, + onTap: model.onTabTapped, + selectedItemColor: const Color(0xff34AD64), + items: [ + BottomNavigationBarItem( + icon: const Icon(Icons.home), + label: AppLocalizations.of(context)!.strictTranslate('Home'), + ), + BottomNavigationBarItem( + icon: const Icon(Icons.event_note), + label: AppLocalizations.of(context)!.strictTranslate('Events'), + ), + BottomNavigationBarItem( + icon: const Icon(Icons.add_box), + label: AppLocalizations.of(context)!.strictTranslate('Post'), + ), + BottomNavigationBarItem( + icon: const Icon(Icons.chat_bubble_outline), + label: AppLocalizations.of(context)!.strictTranslate('Chat'), + ), + BottomNavigationBarItem( + icon: const Icon(Icons.account_circle), + label: AppLocalizations.of(context)!.strictTranslate('Profile'), + ) + ], + ), + ); + }); + } +} diff --git a/lib/views/pre_auth_screens/change_password.dart b/lib/views/pre_auth_screens/change_password.dart new file mode 100644 index 000000000..ad7286dbd --- /dev/null +++ b/lib/views/pre_auth_screens/change_password.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/widgets/raised_round_edge_button.dart'; +import 'package:talawa/widgets/rich_text.dart'; + +class ChangePass extends StatefulWidget { + const ChangePass({required Key key}) : super(key: key); + + @override + _ChangePassState createState() => _ChangePassState(); +} + +class _ChangePassState extends State { + TextEditingController newPassword = TextEditingController(); + TextEditingController reNewPassword = TextEditingController(); + FocusNode newPasswordFocus = FocusNode(); + FocusNode reNewPasswordFocus = FocusNode(); + + @override + Widget build(BuildContext context) { + final text = [ + { + 'text': "${AppLocalizations.of(context)!.translate("Hello")}, ", + 'textStyle': Theme.of(context).textTheme.headline5 + }, + { + 'text': '${AppLocalizations.of(context)!.translate("User Name")} ', + 'textStyle': + Theme.of(context).textTheme.headline6!.copyWith(fontSize: 24) + }, + { + 'text': "${AppLocalizations.of(context)!.translate("we've")} ", + 'textStyle': Theme.of(context).textTheme.headline5 + }, + { + 'text': + '${AppLocalizations.of(context)!.translate("got you covered")} ', + 'textStyle': Theme.of(context).textTheme.headline5 + }, + ]; + return Scaffold( + appBar: AppBar( + elevation: 0.0, + backgroundColor: Colors.transparent, + leading: IconButton( + icon: const Icon( + Icons.arrow_back, + color: Colors.black, + ), + onPressed: () { + navigationService.pop(); + }, + ), + ), + body: SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + child: Container( + margin: EdgeInsets.fromLTRB( + SizeConfig.screenWidth! * 0.06, + SizeConfig.screenHeight! * 0.2, + SizeConfig.screenWidth! * 0.06, + 0.0), + width: SizeConfig.screenWidth, + height: SizeConfig.screenHeight, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomRichText( + key: const Key('UrlPageText'), + words: text, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.05, + ), + TextFormField( + controller: newPassword, + focusNode: newPasswordFocus, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + enableSuggestions: true, + autofillHints: const [AutofillHints.password], + obscureText: true, + decoration: InputDecoration( + hintText: + AppLocalizations.of(context)!.translate('password'), + labelText: + '${AppLocalizations.of(context)!.translate("Enter new password")} *', + )), + SizedBox( + height: SizeConfig.screenHeight! * 0.025, + ), + TextFormField( + controller: reNewPassword, + focusNode: reNewPasswordFocus, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + enableSuggestions: true, + autofillHints: const [AutofillHints.password], + obscureText: true, + decoration: InputDecoration( + hintText: + AppLocalizations.of(context)!.translate('password'), + labelText: + '${AppLocalizations.of(context)!.translate("Re-Enter your password")} *', + )), + SizedBox( + height: SizeConfig.screenHeight! * 0.086, + ), + RaisedRoundedButton( + buttonLabel: + '${AppLocalizations.of(context)!.translate("Change Password")} ', + onTap: () { + newPasswordFocus.unfocus(); + reNewPasswordFocus.unfocus(); + }, + textColor: const Color(0xFF008A37), + key: const Key('Change Password Button'), + backgroundColor: Colors.white, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.0215, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/pre_auth_screens/login.dart b/lib/views/pre_auth_screens/login.dart new file mode 100644 index 000000000..dbff05cf9 --- /dev/null +++ b/lib/views/pre_auth_screens/login.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/validators.dart'; +import 'package:talawa/view_model/pre_auth_view_models/login_view_model.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/raised_round_edge_button.dart'; +import 'package:talawa/widgets/rich_text.dart'; + +class Login extends StatefulWidget { + const Login({required Key key}) : super(key: key); + + @override + _LoginState createState() => _LoginState(); +} + +class _LoginState extends State { + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) => model.initialize(), + builder: (context, model, child) { + return Scaffold( + appBar: AppBar( + elevation: 0.0, + backgroundColor: Colors.transparent, + leading: IconButton( + icon: const Icon( + Icons.arrow_back, + ), + onPressed: () { + navigationService.pop(); + }, + ), + ), + body: SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + child: Container( + margin: EdgeInsets.fromLTRB( + SizeConfig.screenWidth! * 0.06, + SizeConfig.screenHeight! * 0.2, + SizeConfig.screenWidth! * 0.06, + 0.0), + width: SizeConfig.screenWidth, + height: SizeConfig.screenHeight, + child: Form( + key: model.formKey, + autovalidateMode: model.validate, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomRichText( + key: const Key('UrlPageText'), + words: model.greeting, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.05, + ), + TextFormField( + controller: model.email, + focusNode: model.emailFocus, + textInputAction: TextInputAction.next, + keyboardType: TextInputType.emailAddress, + autofillHints: const [AutofillHints.email], + enableSuggestions: true, + validator: (value) { + final String? err = Validator.validateEmail(value!); + if (err != null) { + return AppLocalizations.of(context)! + .translate(err); + } + + return null; + }, + decoration: InputDecoration( + hintText: AppLocalizations.of(context)! + .translate("Email Hint"), + labelText: + '${AppLocalizations.of(context)!.translate("Enter your registered Email")} *', + labelStyle: Theme.of(context).textTheme.subtitle1, + )), + SizedBox( + height: SizeConfig.screenHeight! * 0.025, + ), + TextFormField( + controller: model.password, + focusNode: model.passwordFocus, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + enableSuggestions: true, + autofillHints: const [AutofillHints.password], + obscureText: model.hidePassword, + validator: (value) { + final String? err = + Validator.validatePassword(value!); + if (err != null) { + return AppLocalizations.of(context)!.translate(err); + } + + return null; + }, + decoration: InputDecoration( + suffixIcon: IconButton( + onPressed: () { + setState(() { + model.hidePassword = !model.hidePassword; + }); + }, + icon: Icon(model.hidePassword + ? Icons.visibility_off + : Icons.visibility)), + hintText: AppLocalizations.of(context)! + .translate('password'), + labelText: + '${AppLocalizations.of(context)!.translate("Enter your password")} *', + labelStyle: Theme.of(context).textTheme.subtitle1, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () { + model.emailFocus.unfocus(); + model.passwordFocus.unfocus(); + navigationService.pushScreen('/recover'); + }, + child: Text( + '${AppLocalizations.of(context)!.translate("Forgot password")}?', + style: Theme.of(context) + .textTheme + .bodyText2! + .copyWith( + fontSize: 14, + color: const Color(0xFF4285F4)), + ), + ), + ], + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.086, + ), + RaisedRoundedButton( + buttonLabel: AppLocalizations.of(context)! + .strictTranslate('Login'), + onTap: () => model.login(), + textColor: const Color(0xFF008A37), + key: const Key('LoginButton'), + backgroundColor: Colors.white, + ), + SizedBox(height: SizeConfig.screenHeight! * 0.0215), + ], + ), + ), + ), + ), + ); + }); + } +} diff --git a/lib/views/pre_auth_screens/recover.dart b/lib/views/pre_auth_screens/recover.dart new file mode 100644 index 000000000..df63f0176 --- /dev/null +++ b/lib/views/pre_auth_screens/recover.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/widgets/raised_round_edge_button.dart'; +import 'package:talawa/widgets/rich_text.dart'; + +class Recover extends StatefulWidget { + const Recover({required Key key}) : super(key: key); + + @override + _RecoverState createState() => _RecoverState(); +} + +class _RecoverState extends State { + TextEditingController email = TextEditingController(); + + @override + Widget build(BuildContext context) { + final text = [ + { + 'text': + "${AppLocalizations.of(context)!.translate("Sit back relax, we'll")} ", + 'textStyle': Theme.of(context).textTheme.headline5 + }, + { + 'text': '${AppLocalizations.of(context)!.translate("Recover")} ', + 'textStyle': + Theme.of(context).textTheme.headline6!.copyWith(fontSize: 24) + }, + { + 'text': AppLocalizations.of(context)!.translate("your password"), + 'textStyle': Theme.of(context).textTheme.headline5 + }, + ]; + return Scaffold( + appBar: AppBar( + elevation: 0.0, + backgroundColor: Colors.transparent, + leading: IconButton( + icon: const Icon( + Icons.arrow_back, + ), + onPressed: () { + navigationService.pop(); + }, + ), + ), + body: SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + child: Container( + margin: EdgeInsets.fromLTRB( + SizeConfig.screenWidth! * 0.06, + SizeConfig.screenHeight! * 0.2, + SizeConfig.screenWidth! * 0.06, + 0.0), + width: SizeConfig.screenWidth, + height: SizeConfig.screenHeight, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomRichText( + key: const Key('UrlPageText'), + words: text, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.05, + ), + TextFormField( + controller: email, + textInputAction: TextInputAction.next, + keyboardType: TextInputType.emailAddress, + autofillHints: const [AutofillHints.email], + enableSuggestions: true, + decoration: InputDecoration( + hintText: + AppLocalizations.of(context)!.translate("Email Hint"), + labelText: + '${AppLocalizations.of(context)!.translate("Enter your registered Email")} *', + labelStyle: Theme.of(context).textTheme.subtitle1, + )), + SizedBox( + height: SizeConfig.screenHeight! * 0.086, + ), + RaisedRoundedButton( + buttonLabel: AppLocalizations.of(context)! + .strictTranslate('Recover Password'), + onTap: () {}, + textColor: const Color(0xFF008A37), + key: const Key('RecoverButton'), + backgroundColor: Colors.white, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.0215, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/pre_auth_screens/select_language.dart b/lib/views/pre_auth_screens/select_language.dart new file mode 100644 index 000000000..43ba546ff --- /dev/null +++ b/lib/views/pre_auth_screens/select_language.dart @@ -0,0 +1,146 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:talawa/constants/constants.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/lang_view_model.dart'; + +class SelectLanguage extends StatefulWidget { + const SelectLanguage({required Key key}) : super(key: key); + + @override + _SelectLanguageState createState() => _SelectLanguageState(); +} + +class _SelectLanguageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + key: const Key('SelectLanguageScreenScaffold'), + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + body: Padding( + padding: EdgeInsets.fromLTRB( + SizeConfig.screenWidth! * 0.06, + SizeConfig.safeBlockVertical! * 4, + SizeConfig.screenWidth! * 0.06, + 0.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: SizeConfig.screenWidth! * 0.06), + child: Text( + AppLocalizations.of(context)! + .strictTranslate('Select Language'), + style: Theme.of(context).textTheme.headline5, + key: const Key('Select Language'), + ), + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.018, + ), + // const CupertinoSearchTextField( + // key: Key('SearchField'), + // ), + SizedBox( + height: SizeConfig.screenHeight! * 0.016, + ), + Expanded( + child: ListView.builder( + key: const Key('LanguagesList'), + itemCount: languages.length, + itemBuilder: (BuildContext context, int index) { + return InkWell( + key: Key(Provider.of(context) + .appLocal + .languageCode == + languages[index].langCode + ? 'Selected' + : 'NotSelected'), + onTap: () async { + await Provider.of( + context, + listen: false, + ).changeLanguage( + Locale(languages[index].langCode), + ); + }, + child: Consumer( + builder: (context, appLang, _) { + return Container( + key: Key('LanguageItem$index'), + alignment: Alignment.centerLeft, + height: SizeConfig.screenHeight! * 0.063, + padding: EdgeInsets.symmetric( + horizontal: SizeConfig.screenWidth! * 0.06, + ), + decoration: BoxDecoration( + color: languages[index].langCode == + appLang.appLocal.languageCode + ? const Color(0xFFC4C4C4) + .withOpacity(0.15) + : Colors.transparent), + child: index == 0 + ? Row( + key: const Key('LanguageItem'), + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + languages[index].langName, + style: Theme.of(context) + .textTheme + .headline6, + ), + Text( + AppLocalizations.of(context)! + .strictTranslate('Default'), + style: Theme.of(context) + .textTheme + .bodyText2! + .copyWith( + color: + const Color(0xFF4285F4)), + ), + ], + ) + : Text( + languages[index].langName, + style: + Theme.of(context).textTheme.headline6, + key: const Key('LanguageItem'), + ), + ); + }, + ), + ); + })), + const Divider( + color: Color(0xffe5e5e5), + ), + Container( + height: SizeConfig.screenHeight! * 0.08, + alignment: Alignment.centerRight, + child: TextButton( + key: const Key('NavigateToUrlPage'), + onPressed: () async { + navigationService.pushScreen('/setUrl', arguments: ''); + }, + child: Text( + AppLocalizations.of(context)!.strictTranslate('Select'), + style: Theme.of(context).textTheme.headline5!.copyWith( + fontSize: 18, + color: const Color(0xFF008A37), + ), + key: const Key('SelectLangTextButton'), + ), + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/views/pre_auth_screens/select_organization.dart b/lib/views/pre_auth_screens/select_organization.dart new file mode 100644 index 000000000..b090e2d3b --- /dev/null +++ b/lib/views/pre_auth_screens/select_organization.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/view_model/pre_auth_view_models/select_organization_view_model.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/custom_list_tile.dart'; +import 'package:talawa/widgets/raised_round_edge_button.dart'; +import 'package:talawa/widgets/signup_progress_indicator.dart'; + +class SelectOrganization extends StatefulWidget { + const SelectOrganization({required Key key, required this.selectedOrgId}) + : super(key: key); + final String selectedOrgId; + + @override + _SelectOrganizationState createState() => _SelectOrganizationState(); +} + +class _SelectOrganizationState extends State { + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) => model.initialise(widget.selectedOrgId), + builder: (context, model, child) { + return Scaffold( + extendBodyBehindAppBar: true, + appBar: AppBar( + elevation: 0.0, + backgroundColor: Colors.transparent, + leading: IconButton( + icon: const Icon( + Icons.arrow_back, + ), + onPressed: () { + navigationService.pop(); + }, + ), + ), + body: Padding( + padding: + EdgeInsets.only(top: SizeConfig.safeBlockVertical! * 6), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SignupProgressIndicator( + key: const Key('SelectOrg'), currentPageIndex: 0), + model.selectedOrganization.id != '-1' + ? Padding( + padding: const EdgeInsets.all(15), + child: Text( + AppLocalizations.of(context)! + .strictTranslate('Selected Organization'), + style: Theme.of(context).textTheme.headline5, + ), + ) + : const SizedBox(), + model.selectedOrganization.id != '-1' + ? CustomListTile( + index: model.organizations + .indexOf(model.selectedOrganization), + type: TileType.org, + orgInfo: model.selectedOrganization, + onTapOrgInfo: (item) => model.selectOrg(item), + key: const Key('OrgSelItem'), + showIcon: false, + ) + : const SizedBox(), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Divider( + color: Colors.grey, + thickness: 2.0, + ), + ), + Expanded(child: model.showOrganizationList()), + SizedBox( + height: SizeConfig.screenHeight! * 0.0215, + ), + RaisedRoundedButton( + buttonLabel: AppLocalizations.of(context)! + .strictTranslate('Continue'), + onTap: model.onTapContinue, + textColor: const Color(0xFF008A37), + key: const Key('SignUpLoginDetailsButton'), + backgroundColor: Colors.white, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.0215, + ), + ]))); + }); + } +} diff --git a/lib/views/pre_auth_screens/set_url.dart b/lib/views/pre_auth_screens/set_url.dart new file mode 100644 index 000000000..ebd7751ee --- /dev/null +++ b/lib/views/pre_auth_screens/set_url.dart @@ -0,0 +1,188 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/custom_painters/language_icon.dart'; +import 'package:talawa/custom_painters/talawa_logo.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/utils/validators.dart'; +import 'package:talawa/widgets/raised_round_edge_button.dart'; +import 'package:talawa/widgets/rich_text.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/view_model/pre_auth_view_models/set_url_view_model.dart'; +import 'package:talawa/views/base_view.dart'; + +class SetUrl extends StatefulWidget { + const SetUrl({required Key key, required this.uri}) : super(key: key); + final String uri; + + @override + _SetUrlState createState() => _SetUrlState(); +} + +class _SetUrlState extends State { + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) => model.initialise(inviteUrl: widget.uri), + builder: (context, model, child) { + return Scaffold( + key: const Key('SetUrlScreenScaffold'), + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + body: Container( + margin: EdgeInsets.fromLTRB( + SizeConfig.screenWidth! * 0.06, + SizeConfig.safeBlockVertical! * 4, + SizeConfig.screenWidth! * 0.06, + 0.0), + width: SizeConfig.screenWidth, + height: SizeConfig.screenHeight, + alignment: Alignment.center, + child: SingleChildScrollView( + child: Form( + key: model.formKey, + autovalidateMode: model.validate, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.only( + top: SizeConfig.screenHeight! * 0.08), + child: CustomPaint( + key: const Key('LogoPainter'), + size: Size(SizeConfig.screenWidth! * 0.6, + (SizeConfig.screenWidth! * 0.6).toDouble()), + painter: AppLogo(), + ), + ), + CustomRichText( + key: const Key('UrlPageText'), + words: model.greeting, + ), + TextFormField( + key: const Key('UrlInputField'), + controller: model.url, + focusNode: model.urlFocus, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + enableSuggestions: true, + validator: (value) { + final String? msg = Validator.validateURL(value!); + if (msg == null) { + return null; + } + + return AppLocalizations.of(context)!.translate(msg); + }, + onFieldSubmitted: (value) => + AppLocalizations.of(context)! + .translate(Validator.validateURL(value)), + decoration: InputDecoration( + hintText: + 'https://talawa-api-graphql.herokuapp.com/graphql', + labelText: + '${AppLocalizations.of(context)!.translate("Enter Organization URL")} *', + labelStyle: Theme.of(context).textTheme.subtitle1, + suffixIcon: InkWell( + key: const Key('VerifyButton'), + onTap: () { + model.urlFocus.unfocus(); + model.validate = AutovalidateMode.always; + model.formKey.currentState!.validate(); + }, + child: Container( + height: 48, + width: 48, + alignment: Alignment.center, + child: Text( + AppLocalizations.of(context)! + .strictTranslate("Verify"), + style: Theme.of(context).textTheme.bodyText1, + textAlign: TextAlign.center, + ), + ), + )), + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.086, + ), + RaisedRoundedButton( + key: const Key('LoginButton'), + buttonLabel: AppLocalizations.of(context)! + .strictTranslate('Login'), + onTap: () async { + print("login"); + await model.checkURLandNavigate('/login', ''); + }, + showArrow: true, + textColor: Theme.of(context) + .inputDecorationTheme + .focusedBorder! + .borderSide + .color, + backgroundColor: + Theme.of(context).colorScheme.secondaryVariant, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.0215, + ), + RaisedRoundedButton( + key: const Key('SignUpButton'), + buttonLabel: AppLocalizations.of(context)! + .strictTranslate('Sign Up'), + onTap: () => + model.checkURLandNavigate('/selectOrg', '-1'), + showArrow: true, + textColor: + Theme.of(context).colorScheme.secondaryVariant, + backgroundColor: Theme.of(context) + .inputDecorationTheme + .focusedBorder! + .borderSide + .color, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.08, + ), + GestureDetector( + key: const Key('ChangeLanguage'), + onTap: () { + navigationService.pushReplacementScreen('/selectLang', + arguments: '0'); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CustomPaint( + size: Size( + SizeConfig.screenWidth! * 0.125, + (SizeConfig.screenWidth! * 0.125 * 0.5) + .toDouble()), //You can Replace [WIDTH] with your desired width for Custom Paint and height will be calculated automatically + painter: LanguageIcon(), + ), + const SizedBox( + width: 10, + ), + Text( + AppLocalizations.of(context)! + .strictTranslate('Change language'), + style: Theme.of(context) + .textTheme + .headline6! + .copyWith( + color: Theme.of(context) + .colorScheme + .onBackground + .withOpacity(0.8)), + ) + ], + ), + ) + ], + ), + ), + ), + ), + ); + }); + } +} diff --git a/lib/views/pre_auth_screens/signup_details.dart b/lib/views/pre_auth_screens/signup_details.dart new file mode 100644 index 000000000..4472c60d2 --- /dev/null +++ b/lib/views/pre_auth_screens/signup_details.dart @@ -0,0 +1,278 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/validators.dart'; +import 'package:talawa/view_model/pre_auth_view_models/signup_details_view_model.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/raised_round_edge_button.dart'; +import 'package:talawa/widgets/rich_text.dart'; +import 'package:talawa/widgets/signup_progress_indicator.dart'; + +class SignUpDetails extends StatefulWidget { + const SignUpDetails({required Key key, required this.selectedOrg}) + : super(key: key); + final OrgInfo selectedOrg; + @override + _SignUpDetailsState createState() => _SignUpDetailsState(); +} + +class _SignUpDetailsState extends State { + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) => model.initialise(widget.selectedOrg), + builder: (context, model, child) { + return Scaffold( + extendBodyBehindAppBar: true, + appBar: AppBar( + elevation: 0.0, + backgroundColor: Colors.transparent, + automaticallyImplyLeading: false, + ), + body: Padding( + padding: + EdgeInsets.only(top: SizeConfig.safeBlockVertical! * 6), + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SignupProgressIndicator( + key: const Key('SelectOrg'), currentPageIndex: 1), + Form( + key: model.formKey, + autovalidateMode: model.validate, + child: Column( + children: [ + Container( + margin: EdgeInsets.fromLTRB( + SizeConfig.screenWidth! * 0.06, + SizeConfig.screenWidth! * 0.05, + SizeConfig.screenWidth! * 0.06, + 0.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + CustomRichText( + key: const Key('UrlPageText'), + words: model.greeting, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.05, + ), + TextFormField( + controller: model.firstName, + textInputAction: TextInputAction.next, + keyboardType: TextInputType.text, + autofillHints: const [ + AutofillHints.givenName + ], + enableSuggestions: true, + validator: (value) { + final String? msg = + Validator.validateFirstName( + value!); + if (msg == null) { + return null; + } + + return AppLocalizations.of(context)! + .translate( + Validator.validateFirstName( + value, + ), + ); + }, + decoration: InputDecoration( + hintText: AppLocalizations.of( + context)! + .translate('First Name Hint'), + labelText: + '${AppLocalizations.of(context)!.translate("Enter your first name")}*', + labelStyle: Theme.of(context) + .textTheme + .subtitle1, + )), + SizedBox( + height: + SizeConfig.screenHeight! * 0.015, + ), + TextFormField( + controller: model.lastName, + textInputAction: TextInputAction.next, + keyboardType: TextInputType.text, + autofillHints: const [ + AutofillHints.familyName + ], + enableSuggestions: true, + validator: (value) { + final String? msg = + Validator.validateLastName( + value!); + if (msg == null) { + return null; + } + + return AppLocalizations.of(context)! + .translate( + Validator.validateLastName(value), + ); + }, + decoration: InputDecoration( + hintText: AppLocalizations.of( + context)! + .translate('Last Name Hint'), + labelText: + '${AppLocalizations.of(context)!.translate("Enter your last name")}*', + labelStyle: Theme.of(context) + .textTheme + .subtitle1, + )), + SizedBox( + height: + SizeConfig.screenHeight! * 0.015, + ), + TextFormField( + controller: model.email, + textInputAction: TextInputAction.next, + keyboardType: + TextInputType.emailAddress, + autofillHints: const [ + AutofillHints.email + ], + enableSuggestions: true, + validator: (value) { + final String? msg = + Validator.validateEmail(value!); + if (msg == null) { + return null; + } + + return AppLocalizations.of(context)! + .translate( + Validator.validateEmail(value), + ); + }, + decoration: InputDecoration( + hintText: 'test@test.org', + labelText: + '${AppLocalizations.of(context)!.translate("Enter your registered Email")}*', + labelStyle: Theme.of(context) + .textTheme + .subtitle1, + )), + SizedBox( + height: + SizeConfig.screenHeight! * 0.015, + ), + TextFormField( + controller: model.password, + textInputAction: TextInputAction.next, + keyboardType: + TextInputType.visiblePassword, + obscureText: model.hidePassword, + autofillHints: const [ + AutofillHints.password + ], + enableSuggestions: true, + validator: (value) { + final String? msg = + Validator.validatePassword( + value!); + if (msg == null) { + return null; + } + + return AppLocalizations.of(context)! + .translate( + Validator.validatePassword(value), + ); + }, + onFieldSubmitted: (done) { + FocusScope.of(context).requestFocus( + model.confirmFocus); + }, + decoration: InputDecoration( + suffixIcon: IconButton( + onPressed: () { + setState(() { + model.hidePassword = + !model.hidePassword; + }); + }, + icon: Icon(model.hidePassword + ? Icons.visibility_off + : Icons.visibility)), + hintText: + AppLocalizations.of(context)! + .translate('password'), + labelText: + '${AppLocalizations.of(context)!.translate("Enter your password")}*', + labelStyle: Theme.of(context) + .textTheme + .subtitle1, + )), + SizedBox( + height: + SizeConfig.screenHeight! * 0.015, + ), + TextFormField( + controller: model.confirmPassword, + focusNode: model.confirmFocus, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + enableSuggestions: true, + autofillHints: const [ + AutofillHints.password + ], + obscureText: model.hidePassword, + validator: (value) { + final String? msg = Validator + .validatePasswordConfirm( + model.password.text, + value!); + if (msg == null) { + return null; + } + + return AppLocalizations.of(context)! + .translate(Validator + .validatePasswordConfirm( + model.password.text, + value)); + }, + decoration: InputDecoration( + hintText: + AppLocalizations.of(context)! + .translate('password'), + labelText: + '${AppLocalizations.of(context)!.translate("Confirm your password")}*', + labelStyle: Theme.of(context) + .textTheme + .subtitle1, + )), + SizedBox( + height: + SizeConfig.screenHeight! * 0.086, + ), + ], + ), + ), + RaisedRoundedButton( + buttonLabel: AppLocalizations.of(context)! + .strictTranslate('Next'), + onTap: model.signUp, + textColor: const Color(0xFF008A37), + key: const Key('SignUpLoginDetailsButton'), + backgroundColor: Colors.white, + ), + ], + ), + ) + ]), + ))); + }); + } +} diff --git a/lib/views/pre_auth_screens/waiting_to_join_private_org.dart b/lib/views/pre_auth_screens/waiting_to_join_private_org.dart new file mode 100644 index 000000000..6fb99fcaf --- /dev/null +++ b/lib/views/pre_auth_screens/waiting_to_join_private_org.dart @@ -0,0 +1,125 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/pre_auth_view_models/waiting_view_model.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/custom_list_tile.dart'; +import 'package:talawa/widgets/raised_round_edge_button.dart'; +import 'package:talawa/widgets/rich_text.dart'; +import 'package:talawa/widgets/signup_progress_indicator.dart'; + +class WaitingPage extends StatelessWidget { + const WaitingPage({required Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) => model.initialise(), + builder: (context, model, child) { + return Scaffold( + extendBodyBehindAppBar: true, + appBar: AppBar( + elevation: 0.0, + backgroundColor: Colors.transparent, + automaticallyImplyLeading: false, + ), + body: Container( + padding: EdgeInsets.only(top: SizeConfig.safeBlockVertical! * 6), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SignupProgressIndicator( + key: const Key('Waiting'), currentPageIndex: 2), + Container( + margin: EdgeInsets.fromLTRB( + SizeConfig.screenWidth! * 0.06, + SizeConfig.screenHeight! * 0.01, + SizeConfig.screenWidth! * 0.06, + SizeConfig.screenHeight! * 0.01), + width: SizeConfig.screenWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomRichText( + key: const Key('UrlPageText'), + words: model.greeting, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.03, + ), + Text( + AppLocalizations.of(context)! + .strictTranslate('Request Sent to'), + style: Theme.of(context).textTheme.headline6, + ), + ], + ), + ), + Expanded( + child: ListView.builder( + padding: EdgeInsets.zero, + itemCount: model.pendingRequestOrg.length, + itemBuilder: (BuildContext context, int index) { + return CustomListTile( + key: const Key('WaitingJoin'), + index: index, + type: TileType.org, + orgInfo: model.pendingRequestOrg[index], + onTapOrgInfo: (item) {}); + }, + )), + Expanded( + child: Container( + padding: EdgeInsets.symmetric( + horizontal: SizeConfig.screenWidth! * 0.06, + ), + child: Column( + children: [ + const Spacer(), + RaisedRoundedButton( + key: const Key('JoinOrg'), + buttonLabel: AppLocalizations.of(context)! + .strictTranslate('Join Organisation'), + onTap: model.joinOrg, + showArrow: true, + textColor: + Theme.of(context).colorScheme.secondaryVariant, + backgroundColor: Theme.of(context) + .inputDecorationTheme + .focusedBorder! + .borderSide + .color, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.0215, + ), + RaisedRoundedButton( + key: const Key('Logout'), + buttonLabel: AppLocalizations.of(context)! + .strictTranslate('Log out'), + onTap: model.logout, + textColor: Theme.of(context) + .inputDecorationTheme + .focusedBorder! + .borderSide + .color, + backgroundColor: + Theme.of(context).colorScheme.secondaryVariant, + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.0215, + ), + ], + ), + ), + ), + ], + ), + ), + ); + }); + } +} diff --git a/lib/widgets/custom_alert_dialog.dart b/lib/widgets/custom_alert_dialog.dart new file mode 100644 index 000000000..3362d3d61 --- /dev/null +++ b/lib/widgets/custom_alert_dialog.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/widgets/raised_round_edge_button.dart'; + +class CustomAlertDialog extends StatelessWidget { + const CustomAlertDialog( + {Key? key, + this.successText, + this.dialogTitle, + this.reverse = false, + required this.success, + required this.dialogSubTitle}) + : super(key: key); + final bool reverse; + final Function success; + final String? successText; + final String? dialogTitle; + final String dialogSubTitle; + + @override + Widget build(BuildContext context) { + final List actions = [ + RaisedRoundedButton( + key: const Key('Close'), + onTap: () => navigationService.pop(), + buttonLabel: AppLocalizations.of(context)!.strictTranslate('Close'), + textColor: Colors.white, + backgroundColor: const Color(0xFF008A37), + width: SizeConfig.screenWidth! * 0.2, + height: SizeConfig.screenHeight! * 0.06, + ), + RaisedRoundedButton( + key: Key(successText ?? 'Confirm'), + onTap: () => success(), + buttonLabel: successText ?? + AppLocalizations.of(context)!.strictTranslate('Confirm'), + textColor: const Color(0xFF008A37), + backgroundColor: Colors.white, + width: SizeConfig.screenWidth! * 0.2, + height: SizeConfig.screenHeight! * 0.06, + ) + ]; + return AlertDialog( + title: Text( + dialogTitle ?? + AppLocalizations.of(context)!.strictTranslate('Confirmation'), + style: Theme.of(context) + .textTheme + .headline5! + .copyWith(fontWeight: FontWeight.w800), + ), + content: Text(dialogSubTitle), + buttonPadding: EdgeInsets.symmetric( + horizontal: SizeConfig.screenWidth! * 0.05, + vertical: SizeConfig.screenHeight! * 0.05), + actions: reverse ? actions.reversed.toList() : actions, + ); + } +} diff --git a/lib/widgets/custom_avatar.dart b/lib/widgets/custom_avatar.dart new file mode 100644 index 000000000..5603543a0 --- /dev/null +++ b/lib/widgets/custom_avatar.dart @@ -0,0 +1,54 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +class CustomAvatar extends StatelessWidget { + const CustomAvatar( + {Key? key, + required this.isImageNull, + this.firstAlphabet, + this.imageUrl, + this.fontSize = 40}) + : super(key: key); + final bool isImageNull; + final String? firstAlphabet; + final String? imageUrl; + final double? fontSize; + + @override + Widget build(BuildContext context) { + return isImageNull + ? CircleAvatar( + backgroundColor: + Theme.of(context).iconTheme.color!.withOpacity(0.2), + child: Center( + child: Text( + firstAlphabet!, + style: Theme.of(context) + .textTheme + .bodyText2! + .copyWith(fontSize: fontSize), + ), + ), + ) + : CachedNetworkImage( + imageBuilder: (context, imageProvider) { + return CircleAvatar( + backgroundColor: + Theme.of(context).iconTheme.color!.withOpacity(0.2), + backgroundImage: imageProvider, + ); + }, + imageUrl: imageUrl!, + placeholder: (context, url) => CircleAvatar( + child: Shimmer.fromColors( + baseColor: Colors.transparent, + highlightColor: Colors.white30, + child: const CircleAvatar(), + ), + ), + errorWidget: (context, url, error) => + const CircleAvatar(child: Icon(Icons.error)), + ); + } +} diff --git a/lib/widgets/custom_drawer.dart b/lib/widgets/custom_drawer.dart new file mode 100644 index 000000000..a1bb398b3 --- /dev/null +++ b/lib/widgets/custom_drawer.dart @@ -0,0 +1,108 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:talawa/constants/routing_constants.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/widgets_view_models/custom_drawer_view_model.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/custom_avatar.dart'; +import 'package:talawa/widgets/from_palisadoes.dart'; + +class CustomDrawer extends StatelessWidget { + const CustomDrawer({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) => model.initialize(), + builder: (context, model, child) { + return Container( + width: SizeConfig.screenWidth! * 0.6, + alignment: Alignment.centerLeft, + child: Drawer( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + UserAccountsDrawerHeader( + currentAccountPicture: CustomAvatar( + isImageNull: model.selectedOrg.image == null, + imageUrl: model.selectedOrg.image, + firstAlphabet: + model.selectedOrg.name!.substring(0, 1), + ), + accountName: Text(model.selectedOrg.name!), + accountEmail: Text(AppLocalizations.of(context)! + .strictTranslate("Selected Organization")), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 5, horizontal: 8.0), + child: Text( + AppLocalizations.of(context)! + .strictTranslate("Switch Organization"), + style: Theme.of(context).textTheme.headline6, + ), + ), + SizedBox( + height: 270, + child: Scrollbar( + controller: model.controller, + isAlwaysShown: true, + child: ListView.builder( + controller: model.controller, + padding: EdgeInsets.zero, + itemCount: model.switchAbleOrg.length, + // itemCount: 3, + itemBuilder: (BuildContext context, int index) { + return ListTile( + onTap: () => + model.switchOrg(model.switchAbleOrg[index]), + leading: CustomAvatar( + isImageNull: + model.switchAbleOrg[index].image == null, + imageUrl: model.switchAbleOrg[index].image, + firstAlphabet: model + .switchAbleOrg[index].name! + .substring(0, 1), + fontSize: 18, + ), + title: Text( + model.switchAbleOrg[index].name!, + ), + ); + }, + ), + ), + ), + const Divider(), + ListTile( + onTap: () => + navigationService.popAndPushScreen(Routes.joinOrg), + leading: const Icon( + Icons.add, + size: 30, + ), + title: Text(AppLocalizations.of(context)! + .strictTranslate("Join new Organization")), + ), + ListTile( + leading: const Icon(Icons.logout, size: 30), + title: Text(AppLocalizations.of(context)! + .strictTranslate("Leave Current Organization")), + ), + ], + ), + const FromPalisadoes(), + ], + ), + ), + ); + }); + } +} diff --git a/lib/widgets/custom_list_tile.dart b/lib/widgets/custom_list_tile.dart new file mode 100644 index 000000000..9d21d1b3b --- /dev/null +++ b/lib/widgets/custom_list_tile.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/enums/enums.dart'; +import 'package:talawa/models/options/options.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/widgets/custom_avatar.dart'; + +class CustomListTile extends StatelessWidget { + const CustomListTile( + {required Key key, + required this.index, + required this.type, + this.showIcon = false, + this.orgInfo, + this.onTapOrgInfo, + this.userInfo, + this.onTapUserInfo, + this.onTapOption, + this.option}) + : super(key: key); + final int index; + final TileType type; + final OrgInfo? orgInfo; + final User? userInfo; + final Options? option; + final Function? onTapOption; + final Function()? onTapUserInfo; + final Function(OrgInfo)? onTapOrgInfo; + final bool showIcon; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () => type == TileType.org + ? onTapOrgInfo!(orgInfo!) + : type == TileType.user + ? onTapUserInfo!() + : onTapOption!(), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: Row( + children: [ + Expanded( + flex: 1, + child: type == TileType.option + ? option!.icon + : CustomAvatar( + isImageNull: type == TileType.org + ? orgInfo!.image == null + : userInfo!.image == null, + imageUrl: type == TileType.org + ? orgInfo!.image + : userInfo!.image, + firstAlphabet: type == TileType.org + ? orgInfo!.name!.substring(0, 1) + : userInfo!.firstName!.substring(0, 1), + fontSize: 18, + )), + Expanded( + flex: 3, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + type == TileType.org + ? orgInfo!.name! + : type == TileType.user + ? '${userInfo!.firstName!} ${userInfo!.lastName!}' + : option!.title, + style: type == TileType.org + ? Theme.of(context).textTheme.headline5 + : type == TileType.user + ? Theme.of(context).textTheme.headline6 + : option!.trailingIconButton == null + ? Theme.of(context).textTheme.bodyText2 + : Theme.of(context) + .textTheme + .headline5! + .copyWith(fontSize: 18), + ), + type != TileType.user + ? Text( + type == TileType.org + ? '${AppLocalizations.of(context)!.strictTranslate("Creator")}: ${orgInfo!.creatorInfo!.firstName!} ${orgInfo!.creatorInfo!.lastName!}' + : option!.subtitle, + style: type == TileType.org + ? Theme.of(context).textTheme.headline6 + : option!.trailingIconButton == null + ? Theme.of(context).textTheme.caption + : Theme.of(context).textTheme.headline6, + ) + : const SizedBox(), + ], + )), + Expanded( + flex: 1, + child: type != TileType.user + ? type == TileType.org + ? Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Row( + children: [ + Icon( + orgInfo!.isPublic! + ? Icons.lock_open + : Icons.lock, + color: orgInfo!.isPublic! + ? const Color(0xFF34AD64) + : const Color(0xffFABC57), + ), + Text(orgInfo!.isPublic! + ? AppLocalizations.of(context)! + .strictTranslate('Public') + : AppLocalizations.of(context)! + .strictTranslate('Private')), + ], + ), + showIcon + ? const Icon( + Icons.arrow_drop_down, + size: 25, + ) + : const SizedBox(), + ], + ) + : option!.trailingIconButton ?? const SizedBox() + : const SizedBox(), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/custom_progress_dialog.dart b/lib/widgets/custom_progress_dialog.dart new file mode 100644 index 000000000..bdb652d63 --- /dev/null +++ b/lib/widgets/custom_progress_dialog.dart @@ -0,0 +1,52 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/widgets_view_models/progress_dialog_view_model.dart'; +import 'package:talawa/views/base_view.dart'; + +class CustomProgressDialog extends StatelessWidget { + const CustomProgressDialog({required Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BaseView( + onModelReady: (model) => model.initialise(), + builder: (context, model, child) { + return Container( + margin: EdgeInsets.symmetric( + horizontal: SizeConfig.screenWidth! * 0.25, + vertical: SizeConfig.screenHeight! * 0.35), + decoration: BoxDecoration( + color: Theme.of(context).textTheme.headline6!.color, + borderRadius: BorderRadius.circular(15), + ), + child: model.connectivityPresent + ? Center( + child: CupertinoActivityIndicator( + radius: SizeConfig.screenWidth! * 0.065, + )) + : Column( + children: [ + Container( + alignment: Alignment.bottomCenter, + height: SizeConfig.screenWidth! * 0.4, + width: SizeConfig.screenWidth! * 0.4, + padding: const EdgeInsets.only(bottom: 10), + decoration: const BoxDecoration( + image: DecorationImage( + image: + AssetImage('assets/images/no_internet.png'), + fit: BoxFit.scaleDown)), + ), + Text( + '${AppLocalizations.of(context)!.strictTranslate("No Internet")}!', + style: Theme.of(context).textTheme.headline5, + ), + ], + ), + ); + }); + } +} diff --git a/lib/widgets/date_time_picker.dart b/lib/widgets/date_time_picker.dart new file mode 100644 index 000000000..20987ec55 --- /dev/null +++ b/lib/widgets/date_time_picker.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/locator.dart'; + +Future customDatePicker({required DateTime initialDate}) async { + final DateTime? _picked = await showDatePicker( + context: navigationService.navigatorKey.currentContext!, + initialDate: initialDate, + firstDate: DateTime(2015, 8), + lastDate: DateTime(2101)); + if (_picked != null && _picked != initialDate) { + return _picked; + } + return initialDate; +} + +Future customTimePicker({required TimeOfDay initialTime}) async { + final TimeOfDay? _pickedTime = await showTimePicker( + context: navigationService.navigatorKey.currentContext!, + initialTime: initialTime); + + if (_pickedTime != null && _pickedTime != initialTime) { + return _pickedTime; + } + return initialTime; +} diff --git a/lib/widgets/event_card.dart b/lib/widgets/event_card.dart new file mode 100644 index 000000000..26061d98c --- /dev/null +++ b/lib/widgets/event_card.dart @@ -0,0 +1,203 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; + +class EventCard extends StatelessWidget { + const EventCard({Key? key, required this.event}) : super(key: key); + final Event event; + @override + Widget build(BuildContext context) { + final bool isSubscribed = event.isRegistered ?? false; + + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Stack( + children: [ + Card( + shape: RoundedRectangleBorder( + side: isSubscribed + ? BorderSide( + color: Theme.of(context).accentColor, + width: SizeConfig.screenWidth! * 0.008, + ) + : BorderSide.none, + ), + elevation: 3, + color: Theme.of(context).primaryColor, + child: Column( + children: [ + Container( + height: SizeConfig.screenHeight! * 0.11, + width: double.infinity, + color: Colors.grey.withOpacity(0.3), + ), + Padding( + padding: const EdgeInsets.all(10), + child: Column( + children: [ + Row( + children: [ + SizedBox( + width: SizeConfig.screenWidth! * 0.50, + child: Text( + event.title!, + style: Theme.of(context).textTheme.headline5, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.left, + ), + ), + const Spacer(), + const Icon( + Icons.calendar_today, + size: 13, + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.027, + ), + Text( + "${event.startDate!} - ${event.endDate!}", + style: Theme.of(context).textTheme.caption, + ) + ], + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.006, + ), + Row( + children: [ + const Icon( + Icons.schedule, + size: 12, + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.015, + ), + Text( + "${event.startTime!} - ${event.endTime!}", + style: Theme.of(context).textTheme.caption, + ), + const Spacer(), + const Icon( + Icons.place, + size: 12, + ), + SizedBox( + child: Text( + event.location!.substring( + 0, min(event.location!.length, 20)), + style: Theme.of(context).textTheme.caption, + ), + ) + ], + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.013, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: SizeConfig.screenWidth! * 0.55, + child: Text( + event.description!, + style: Theme.of(context).textTheme.caption, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.left, + maxLines: 3, + ), + ), + const Icon( + Icons.chevron_right, + ), + ], + ), + SizedBox( + height: SizeConfig.screenHeight! * 0.007, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + event.isPublic! + ? Icon( + Icons.lock_open, + size: 13, + color: Theme.of(context).colorScheme.primary, + ) + : Icon( + Icons.lock, + size: 13, + color: Theme.of(context).colorScheme.primary, + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.013, + ), + event.isPublic! + ? Text( + AppLocalizations.of(context)! + .strictTranslate('public'), + style: Theme.of(context).textTheme.caption, + ) + : Text( + AppLocalizations.of(context)! + .strictTranslate('private'), + style: Theme.of(context).textTheme.caption, + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.027, + ), + const Icon( + Icons.people_outline, + size: 13, + color: Color(0xff4285F4), + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.013, + ), + event.attendees != null + ? Text( + event.attendees!, + style: Theme.of(context).textTheme.caption, + ) + : Text( + '10', + style: Theme.of(context).textTheme.caption, + ) + ], + ), + ], + ), + ) + ], + ), + ), + Positioned( + top: SizeConfig.screenHeight! * 0.007, + right: SizeConfig.screenWidth! * 0.013, + child: isSubscribed + ? Container( + height: SizeConfig.screenHeight! * 0.041, + width: SizeConfig.screenWidth! * 0.277, + decoration: BoxDecoration( + color: Theme.of(context).accentColor, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(15))), + child: Center( + child: Text( + AppLocalizations.of(context)! + .strictTranslate('Subscribed'), + style: Theme.of(context).textTheme.headline6!.copyWith( + color: Colors.white, fontWeight: FontWeight.w700), + ), + ), + ) + : const SizedBox(), + ) + ], + ), + ); + } +} diff --git a/lib/widgets/event_date_time_tile.dart b/lib/widgets/event_date_time_tile.dart new file mode 100644 index 000000000..80771f31b --- /dev/null +++ b/lib/widgets/event_date_time_tile.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/services/size_config.dart'; + +class DateTimeTile extends StatelessWidget { + const DateTimeTile({Key? key, required this.child}) : super(key: key); + final Widget child; + + @override + Widget build(BuildContext context) { + return Container( + color: Theme.of(context).colorScheme.secondary, + height: SizeConfig.screenHeight! * 0.068, + width: double.infinity, + child: Padding( + padding: + EdgeInsets.symmetric(horizontal: SizeConfig.screenWidth! * 0.083), + child: child), + ); + } +} diff --git a/lib/widgets/from_palisadoes.dart b/lib/widgets/from_palisadoes.dart new file mode 100644 index 000000000..707ee07a3 --- /dev/null +++ b/lib/widgets/from_palisadoes.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; + +class FromPalisadoes extends StatelessWidget { + const FromPalisadoes({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + AppLocalizations.of(context)!.strictTranslate('from'), + style: Theme.of(context).textTheme.caption, + ), + ], + ), + SizedBox( + height: SizeConfig.blockSizeHorizontal, + ), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'PALISADOES', + style: Theme.of(context) + .textTheme + .subtitle2! + .copyWith(fontWeight: FontWeight.w700), + ), + ], + ), + SizedBox( + height: SizeConfig.blockSizeHorizontal! * 5, + ), + ], + ); + } +} diff --git a/lib/widgets/pinned_carousel_widget.dart b/lib/widgets/pinned_carousel_widget.dart new file mode 100644 index 000000000..47102fdb0 --- /dev/null +++ b/lib/widgets/pinned_carousel_widget.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/models/post/post_model.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; + +class PinnedPostCarousel extends StatelessWidget { + const PinnedPostCarousel({ + Key? key, + required this.pinnedPosts, + required this.navigateToPinnedPostPage, + required this.navigateToIndividualPostPage, + }) : super(key: key); + + final List pinnedPosts; + final Function navigateToPinnedPostPage; + final Function navigateToIndividualPostPage; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + height: 220, + color: Theme.of(context).colorScheme.primaryVariant.withOpacity(0.5), + child: CustomCarouselScroller( + pinnedPosts: pinnedPosts, + key: const Key('Carousel'), + navigateToIndividualPostPage: navigateToIndividualPostPage, + ), + ), + GestureDetector( + onTap: () => navigateToPinnedPostPage(), + child: Container( + height: 50, + width: SizeConfig.screenWidth, + padding: const EdgeInsets.symmetric(horizontal: 16.0), + color: Theme.of(context).colorScheme.primaryVariant, + child: Row( + children: [ + Expanded( + flex: 1, + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Icon( + Icons.article, + color: Theme.of(context).accentColor, + ), + ), + ), + Expanded( + flex: 8, + child: Text( + AppLocalizations.of(context)! + .strictTranslate("See all Pinned news"), + style: Theme.of(context).textTheme.headline6, + ), + ), + const Expanded(flex: 1, child: Icon(Icons.arrow_forward_ios)) + ], + ), + ), + ) + ], + ); + } +} + +class CustomCarouselScroller extends StatefulWidget { + const CustomCarouselScroller( + {Key? key, + required this.pinnedPosts, + required this.navigateToIndividualPostPage}) + : super(key: key); + final List pinnedPosts; + final Function navigateToIndividualPostPage; + + @override + _CustomCarouselScrollerState createState() => _CustomCarouselScrollerState(); +} + +class _CustomCarouselScrollerState extends State { + final PageController controller = PageController(initialPage: 0); + int pindex = 0; + + @override + Widget build(BuildContext context) { + return Stack(children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ListTile( + leading: const CircleAvatar( + radius: 15.0, + backgroundColor: Color(0xff737373), + ), + title: Text( + "${widget.pinnedPosts[pindex].creator!.firstName} ${widget.pinnedPosts[pindex].creator!.lastName}"), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Text( + widget.pinnedPosts[pindex].description!.length > 90 + ? "${widget.pinnedPosts[pindex].description!.substring(0, 90)}..." + : widget.pinnedPosts[pindex].description!, + style: Theme.of(context) + .textTheme + .bodyText1! + .copyWith(color: const Color(0xFF737373)), + ), + ), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0), + child: Row( + children: [ + for (int i = 0; i < 4; i++) + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: Divider( + thickness: 3.0, + color: pindex == i + ? Theme.of(context).colorScheme.primary + : Colors.grey, + ), + ), + ) + ], + ), + ) + ], + ), + ), + PageView( + scrollDirection: Axis.horizontal, + controller: controller, + onPageChanged: (index) { + setState(() { + pindex = index; + }); + }, + children: List.generate( + widget.pinnedPosts.length, + (index) => Container(), + ), + ), + ]); + } +} diff --git a/lib/widgets/post_detailed_page.dart b/lib/widgets/post_detailed_page.dart new file mode 100644 index 000000000..9f17ca530 --- /dev/null +++ b/lib/widgets/post_detailed_page.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/utils/app_localization.dart'; + +class DescriptionTextWidget extends StatefulWidget { + const DescriptionTextWidget({required this.text}); + final String text; + + @override + _DescriptionTextWidgetState createState() => _DescriptionTextWidgetState(); +} + +class _DescriptionTextWidgetState extends State { + late String firstHalf; + late String secondHalf; + + bool flag = true; + + @override + void initState() { + super.initState(); + + if (widget.text.length > 150) { + firstHalf = widget.text.substring(0, 150); + secondHalf = widget.text.substring(150, widget.text.length); + } else { + firstHalf = widget.text; + secondHalf = ""; + } + } + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0), + child: secondHalf.isEmpty + ? Text( + firstHalf, + style: Theme.of(context) + .textTheme + .bodyText2! + .copyWith(fontFamily: 'open-sans'), + ) + : Column( + children: [ + Text( + flag ? ("$firstHalf...") : (firstHalf + secondHalf), + style: Theme.of(context) + .textTheme + .bodyText2! + .copyWith(fontFamily: 'open-sans'), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + InkWell( + onTap: () { + setState(() { + flag = !flag; + }); + }, + child: Text( + flag + ? AppLocalizations.of(context)! + .strictTranslate("show more") + : AppLocalizations.of(context)! + .strictTranslate("show less"), + style: const TextStyle(color: Colors.blue), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/widgets/post_list_widget.dart b/lib/widgets/post_list_widget.dart new file mode 100644 index 000000000..597f70f1d --- /dev/null +++ b/lib/widgets/post_list_widget.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/models/post/post_model.dart'; +import 'package:talawa/widgets/post_widget.dart'; + +class PostListWidget extends StatelessWidget { + const PostListWidget({ + Key? key, + required this.posts, + this.function, + }) : super(key: key); + final List posts; + final Function? function; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + for (int i = 0; i < posts.length; i++) + Column( + children: [ + NewsPost( + post: posts[i], + function: function, + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 10.0), + child: Divider( + height: 8, + thickness: 8, + ), + ) + ], + ) + ], + ); + } +} diff --git a/lib/widgets/post_widget.dart b/lib/widgets/post_widget.dart new file mode 100644 index 000000000..fd7a8750a --- /dev/null +++ b/lib/widgets/post_widget.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/models/post/post_model.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/widgets_view_models/like_button_view_model.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/custom_avatar.dart'; +import 'package:talawa/widgets/post_detailed_page.dart'; + +class NewsPost extends StatelessWidget { + const NewsPost({ + Key? key, + required this.post, + this.function, + }) : super(key: key); + + final Post post; + final Function? function; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // const PinnedPostCarousel(), + ListTile( + leading: CustomAvatar( + isImageNull: post.creator!.image == null, + firstAlphabet: + post.creator!.firstName!.substring(0, 1).toUpperCase(), + imageUrl: post.creator!.image, + fontSize: 24, + ), + title: Text( + "${post.creator!.firstName} ${post.creator!.lastName}", + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w400), + ), + subtitle: Text(post.getPostCreatedDuration()), + ), + DescriptionTextWidget(text: post.description!), + Container( + height: 400, + color: Theme.of(context).colorScheme.primaryVariant.withOpacity(0.5), + ), + BaseView( + onModelReady: (model) => + model.initialize(post.likedBy ?? [], post.sId), + builder: (context, model, child) => Column( + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () => function != null ? function!(post) : {}, + child: Text( + "${model.likedBy.length} ${AppLocalizations.of(context)!.strictTranslate("Likes")}", + style: const TextStyle( + fontFamily: 'open-sans', + fontWeight: FontWeight.w800), + ), + ), + GestureDetector( + onTap: () => function != null ? function!(post) : {}, + child: Text( + "${post.comments!.length} ${AppLocalizations.of(context)!.strictTranslate("comments")}")) + ], + ), + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: Divider(), + ), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 5), + child: Row( + children: [ + GestureDetector( + onTap: () { + model.toggleIsLiked(); + }, + child: Icon( + Icons.thumb_up, + color: model.isLiked + ? Theme.of(context).accentColor + : const Color(0xff737373), + ), + ), + GestureDetector( + onTap: () => function != null ? function!(post) : {}, + child: const Padding( + padding: EdgeInsets.only(left: 18.0), + child: Icon( + Icons.comment, + color: Color(0xff737373), + ), + )), + ], + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/widgets/raised_round_edge_button.dart b/lib/widgets/raised_round_edge_button.dart new file mode 100644 index 000000000..cd50f278e --- /dev/null +++ b/lib/widgets/raised_round_edge_button.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/services/size_config.dart'; + +class RaisedRoundedButton extends StatefulWidget { + const RaisedRoundedButton( + {required Key key, + required this.buttonLabel, + required this.backgroundColor, + required this.textColor, + required this.onTap, + this.height, + this.width, + this.showArrow = false}) + : super(key: key); + final Color backgroundColor; + final Color textColor; + final bool showArrow; + final String buttonLabel; + final Function onTap; + final double? height; + final double? width; + + @override + _RaisedRoundedButtonState createState() => _RaisedRoundedButtonState(); +} + +class _RaisedRoundedButtonState extends State { + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + widget.onTap(); + }, + child: Container( + width: widget.width ?? SizeConfig.screenWidth! * 0.94, + height: widget.height ?? SizeConfig.screenHeight! * 0.07, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: widget.backgroundColor, + boxShadow: [ + BoxShadow( + color: Theme.of(context).dividerColor, + offset: const Offset(0, 1), + blurRadius: 4.0, + spreadRadius: 0, + ) + ]), + child: widget.showArrow + ? Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + width: SizeConfig.screenWidth! * 0.06, + ), + //Unused + const Icon( + Icons.arrow_forward, + color: Colors.transparent, + ), + const Spacer(), + Text( + widget.buttonLabel, + style: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 18, + color: widget.textColor), + ), + const Spacer(), + Icon( + Icons.arrow_forward, + color: widget.textColor, + ), + SizedBox( + width: SizeConfig.screenWidth! * 0.06, + ) + ], + ) + : Text( + widget.buttonLabel, + style: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 18, + color: widget.textColor), + ), + )); + } +} diff --git a/lib/widgets/rich_text.dart b/lib/widgets/rich_text.dart new file mode 100644 index 000000000..0623492bd --- /dev/null +++ b/lib/widgets/rich_text.dart @@ -0,0 +1,27 @@ +import 'package:flutter/cupertino.dart'; +import 'package:talawa/utils/app_localization.dart'; + +class CustomRichText extends StatelessWidget { + const CustomRichText({required Key key, required this.words}) + : super(key: key); + final List> words; + @override + Widget build(BuildContext context) { + return RichText( + textAlign: TextAlign.start, + text: TextSpan( + text: + "${AppLocalizations.of(context)!.translate(words[0]['text'].toString().trim())} ", + style: words[0]['textStyle'] as TextStyle, + children: List.generate( + words.length - 1, + (index) => TextSpan( + text: + "${AppLocalizations.of(context)!.translate(words[index + 1]['text'].toString().trim())} ", + style: words[index + 1]['textStyle'] as TextStyle, + ), + ), + ), + ); + } +} diff --git a/lib/widgets/signup_progress_indicator.dart b/lib/widgets/signup_progress_indicator.dart new file mode 100644 index 000000000..005a35311 --- /dev/null +++ b/lib/widgets/signup_progress_indicator.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:timelines/timelines.dart'; + +class SignupProgressIndicator extends StatelessWidget { + SignupProgressIndicator({required Key key, required this.currentPageIndex}) + : super(key: key); + + final int currentPageIndex; + final List progressLabel = [ + 'Select\nOrganization', + 'Enter Details', + 'Final', + ]; + + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + height: SizeConfig.screenHeight! * 0.15, + child: Timeline.tileBuilder( + scrollDirection: Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + builder: TimelineTileBuilder.connected( + contentsBuilder: (_, index) => Text( + AppLocalizations.of(context)!.strictTranslate(progressLabel[index]), + style: Theme.of(context).textTheme.bodyText2!.copyWith( + color: index <= currentPageIndex + ? const Color(0xFF008A37) + : const Color(0xFF737373)), + textAlign: TextAlign.center, + ), + connectorBuilder: (_, index, __) { + return SolidLineConnector( + space: 30, + color: index < currentPageIndex + ? const Color(0xFF008A37) + : const Color(0xFF737373)); + }, + indicatorBuilder: (_, index) { + return DotIndicator( + size: 25, + color: index <= currentPageIndex + ? const Color(0xFF008A37) + : const Color(0xFF737373), + child: index < currentPageIndex + ? const Icon( + Icons.done, + color: Colors.white, + size: 20, + ) + : const SizedBox(), + ); + }, + itemExtentBuilder: (_, __) => MediaQuery.of(context).size.width / 3, + itemCount: 3, + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 000000000..1440ca164 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,93 @@ +name: talawa +description: Welcome to the Talawa Project for the Palisadoes Foundation + Community Organization Management Software. + +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +version: 1.0.0+1 + +homepage: https://github.com/PalisadoesFoundation/talawa + +repository: https://github.com/PalisadoesFoundation/talawa + +environment: + sdk: ">=2.12.0 <3.0.0" + +dependencies: + cached_network_image: ^3.0.0 + connectivity_plus: ^1.0.3 + cupertino_icons: ^1.0.3 + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + get_it: ^7.1.3 + graphql_flutter: ^5.0.0 + hive: ^2.0.4 + http: ^0.13.3 + intl: ^0.17.0 + json_annotation: ^4.0.1 + mockito: ^5.0.10 + path_provider: ^2.0.2 + provider: ^5.0.0 + shared_preferences: ^2.0.6 + shimmer: ^2.0.0 + timelines: ^0.1.0 + uni_links: ^0.5.1 + visibility_detector: ^0.2.0 + +dev_dependencies: + build_runner: ^2.0.4 + flutter_test: + sdk: flutter + hive_generator: ^1.1.0 + json_serializable: ^4.1.3 + lint: ^1.5.3 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec +# The following section is specific to Flutter. +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + # To add assets to your application, add an assets section, like this: + assets: + - assets/images/ + - lang/ + # - images/a_dot_ham.jpeg + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + fonts: + - family: product-sans + fonts: + - asset: assets/fonts/ProductSans-Regular.ttf + - asset: assets/fonts/ProductSans-Medium.ttf + weight: 600 + - asset: assets/fonts/ProductSans-Bold.ttf + weight: 800 + - family: open-sans + fonts: + - asset: assets/fonts/OpenSans-Regular.ttf + - asset: assets/fonts/OpenSans-SemiBold.ttf + weight: 600 + - asset: assets/fonts/OpenSans-Bold.ttf + weight: 800 + # - asset: + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: assets\fonts\OpenSans-Bold.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/test/helpers/test_helpers.dart b/test/helpers/test_helpers.dart new file mode 100644 index 000000000..398876fc4 --- /dev/null +++ b/test/helpers/test_helpers.dart @@ -0,0 +1,143 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/events/event_model.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/models/post/post_model.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/services/event_service.dart'; +import 'package:talawa/services/graphql_config.dart'; +import 'package:talawa/services/navigation_service.dart'; +import 'package:talawa/services/post_service.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/services/user_config.dart'; +import 'package:talawa/view_model/after_auth_view_models/event_view_models/explore_events_view_model.dart'; +import 'package:talawa/view_model/after_auth_view_models/feed_view_models/organization_feed_view_model.dart'; +import 'package:talawa/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart'; +import 'package:talawa/view_model/main_screen_view_model.dart'; +import 'package:talawa/view_model/widgets_view_models/like_button_view_model.dart'; + +import 'test_helpers.mocks.dart'; + +@GenerateMocks([], customMocks: [ + MockSpec(returnNullOnMissingStub: true), + MockSpec(returnNullOnMissingStub: true), + MockSpec(returnNullOnMissingStub: true), + MockSpec(returnNullOnMissingStub: true), + MockSpec(returnNullOnMissingStub: true), +]) +void _removeRegistrationIfExists() { + if (locator.isRegistered()) { + locator.unregister(); + } +} + +NavigationService getAndRegisterNavigationService() { + _removeRegistrationIfExists(); + final service = MockNavigationService(); + when(service.navigatorKey).thenReturn(GlobalKey()); + locator.registerSingleton(service); + return service; +} + +GraphqlConfig getAndRegisterGraphqlConfig() { + _removeRegistrationIfExists(); + final service = MockGraphqlConfig(); + locator.registerSingleton(service); + return service; +} + +UserConfig getAndRegisterUserConfig() { + _removeRegistrationIfExists(); + final service = MockUserConfig(); + + //Mock Data for current organizaiton. + when(service.currentOrg).thenReturn(OrgInfo( + id: "XYZ", + name: "Organization Name", + )); + + //Mock Stream for currentOrgStream + final StreamController _streamController = StreamController(); + final Stream _stream = _streamController.stream.asBroadcastStream(); + when(service.currentOrfInfoStream).thenAnswer((invocation) => _stream); + + //Mkock current user + when(service.currentUser).thenReturn(User( + id: "xzy1", + firstName: "Test", + lastName: "User", + email: "testuser@gmail.com")); + + locator.registerSingleton(service); + return service; +} + +PostService getAndRegisterPostService() { + _removeRegistrationIfExists(); + final service = MockPostService(); + + //Mock Stream for currentOrgStream + final StreamController> _streamController = StreamController(); + final Stream> _stream = + _streamController.stream.asBroadcastStream(); + when(service.postStream).thenAnswer((invocation) => _stream); + + final StreamController _updateStreamController = StreamController(); + final Stream _updateStream = + _updateStreamController.stream.asBroadcastStream(); + when(service.updatedPostStream).thenAnswer((invocation) => _updateStream); + + locator.registerSingleton(service); + return service; +} + +EventService getAndRegisterEventService() { + _removeRegistrationIfExists(); + final service = MockEventService(); + + //Mock Stream for currentOrgStream + final StreamController _streamController = StreamController(); + final Stream _stream = _streamController.stream.asBroadcastStream(); + when(service.eventStream).thenAnswer((invocation) => _stream); + + locator.registerSingleton(service); + return service; +} + +void registerServices() { + getAndRegisterNavigationService(); + getAndRegisterGraphqlConfig(); + getAndRegisterUserConfig(); + getAndRegisterPostService(); + getAndRegisterEventService(); +} + +void unregisterServices() { + locator.unregister(); + locator.unregister(); + locator.unregister(); + locator.unregister(); + locator.unregister(); +} + +void registerViewModels() { + locator.registerFactory(() => MainScreenViewModel()); + locator.registerFactory(() => OrganizationFeedViewModel()); + locator.registerFactory(() => ExploreEventsViewModel()); + locator.registerFactory(() => ProfilePageViewModel()); + locator.registerFactory(() => LikeButtonViewModel()); + locator.registerFactory(() => SizeConfig()); +} + +void unregisterViewModels() { + locator.unregister(); + locator.unregister(); + locator.unregister(); + locator.unregister(); + locator.unregister(); + locator.unregister(); +} diff --git a/test/helpers/test_helpers.mocks.dart b/test/helpers/test_helpers.mocks.dart new file mode 100644 index 000000000..19bed8cc1 --- /dev/null +++ b/test/helpers/test_helpers.mocks.dart @@ -0,0 +1,267 @@ +// Mocks generated by Mockito 5.0.10 from annotations +// in talawa/test/helpers/test_helpers.dart. +// Do not manually edit this file. + +import 'dart:async' as _i5; + +import 'package:flutter/src/widgets/framework.dart' as _i1; +import 'package:flutter/src/widgets/navigator.dart' as _i9; +import 'package:gql_http_link/src/link.dart' as _i3; +import 'package:graphql/src/graphql_client.dart' as _i4; +import 'package:mockito/mockito.dart' as _i2; +import 'package:talawa/models/events/event_model.dart' as _i14; +import 'package:talawa/models/organization/org_info.dart' as _i6; +import 'package:talawa/models/post/post_model.dart' as _i12; +import 'package:talawa/models/user/user_info.dart' as _i7; +import 'package:talawa/services/event_service.dart' as _i13; +import 'package:talawa/services/graphql_config.dart' as _i10; +import 'package:talawa/services/navigation_service.dart' as _i8; +import 'package:talawa/services/post_service.dart' as _i11; +import 'package:talawa/services/user_config.dart' as _i15; + +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: comment_references +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis + +class _FakeGlobalKey> extends _i2.Fake + implements _i1.GlobalKey {} + +class _FakeHttpLink extends _i2.Fake implements _i3.HttpLink {} + +class _FakeGraphQLClient extends _i2.Fake implements _i4.GraphQLClient {} + +class _FakeStreamController extends _i2.Fake + implements _i5.StreamController {} + +class _FakeOrgInfo extends _i2.Fake implements _i6.OrgInfo {} + +class _FakeUser extends _i2.Fake implements _i7.User {} + +/// A class which mocks [NavigationService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockNavigationService extends _i2.Mock implements _i8.NavigationService { + @override + _i1.GlobalKey<_i9.NavigatorState> get navigatorKey => + (super.noSuchMethod(Invocation.getter(#navigatorKey), + returnValue: _FakeGlobalKey<_i9.NavigatorState>()) + as _i1.GlobalKey<_i9.NavigatorState>); + @override + set navigatorKey(_i1.GlobalKey<_i9.NavigatorState>? _navigatorKey) => + super.noSuchMethod(Invocation.setter(#navigatorKey, _navigatorKey), + returnValueForMissingStub: null); + @override + _i5.Future pushScreen(String? routeName, {dynamic arguments}) => + (super.noSuchMethod( + Invocation.method(#pushScreen, [routeName], {#arguments: arguments}), + returnValue: Future.value()) as _i5.Future); + @override + _i5.Future popAndPushScreen(String? routeName, + {dynamic arguments}) => + (super.noSuchMethod( + Invocation.method( + #popAndPushScreen, [routeName], {#arguments: arguments}), + returnValue: Future.value()) as _i5.Future); + @override + _i5.Future pushReplacementScreen(String? routeName, + {dynamic arguments}) => + (super.noSuchMethod( + Invocation.method( + #pushReplacementScreen, [routeName], {#arguments: arguments}), + returnValue: Future.value()) as _i5.Future); + @override + void fromInviteLink(List? routeNames, List? arguments) => + super.noSuchMethod( + Invocation.method(#fromInviteLink, [routeNames, arguments]), + returnValueForMissingStub: null); + @override + _i5.Future removeAllAndPush(String? routeName, String? tillRoute, + {dynamic arguments}) => + (super.noSuchMethod( + Invocation.method(#removeAllAndPush, [routeName, tillRoute], + {#arguments: arguments}), + returnValue: Future.value()) as _i5.Future); + @override + void pushDialog(_i1.Widget? dialog) => + super.noSuchMethod(Invocation.method(#pushDialog, [dialog]), + returnValueForMissingStub: null); + @override + void showSnackBar(String? message, + {Duration? duration = const Duration(seconds: 2)}) => + super.noSuchMethod( + Invocation.method(#showSnackBar, [message], {#duration: duration}), + returnValueForMissingStub: null); + @override + void pop() => super.noSuchMethod(Invocation.method(#pop, []), + returnValueForMissingStub: null); +} + +/// A class which mocks [GraphqlConfig]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockGraphqlConfig extends _i2.Mock implements _i10.GraphqlConfig { + @override + _i3.HttpLink get httpLink => (super.noSuchMethod(Invocation.getter(#httpLink), + returnValue: _FakeHttpLink()) as _i3.HttpLink); + @override + set httpLink(_i3.HttpLink? _httpLink) => + super.noSuchMethod(Invocation.setter(#httpLink, _httpLink), + returnValueForMissingStub: null); + @override + // ignore: avoid_setters_without_getters + set displayImgRoute(String? _displayImgRoute) => + super.noSuchMethod(Invocation.setter(#displayImgRoute, _displayImgRoute), + returnValueForMissingStub: null); + @override + _i5.Future getToken() => + (super.noSuchMethod(Invocation.method(#getToken, []), + returnValue: Future.value()) as _i5.Future); + @override + _i4.GraphQLClient clientToQuery() => + (super.noSuchMethod(Invocation.method(#clientToQuery, []), + returnValue: _FakeGraphQLClient()) as _i4.GraphQLClient); + @override + _i4.GraphQLClient authClient() => + (super.noSuchMethod(Invocation.method(#authClient, []), + returnValue: _FakeGraphQLClient()) as _i4.GraphQLClient); +} + +/// A class which mocks [PostService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPostService extends _i2.Mock implements _i11.PostService { + @override + _i5.Stream> get postStream => + (super.noSuchMethod(Invocation.getter(#postStream), + returnValue: Stream>.empty()) + as _i5.Stream>); + @override + _i5.Stream<_i12.Post> get updatedPostStream => + (super.noSuchMethod(Invocation.getter(#updatedPostStream), + returnValue: Stream<_i12.Post>.empty()) as _i5.Stream<_i12.Post>); + @override + void setOrgStreamSubscription() => + super.noSuchMethod(Invocation.method(#setOrgStreamSubscription, []), + returnValueForMissingStub: null); + @override + _i5.Future getPosts() => + (super.noSuchMethod(Invocation.method(#getPosts, []), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future addLike(String? postID) => + (super.noSuchMethod(Invocation.method(#addLike, [postID]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future removeLike(String? postID) => + (super.noSuchMethod(Invocation.method(#removeLike, [postID]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + void addCommentLocally(String? postID) => + super.noSuchMethod(Invocation.method(#addCommentLocally, [postID]), + returnValueForMissingStub: null); +} + +/// A class which mocks [EventService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockEventService extends _i2.Mock implements _i13.EventService { + @override + _i5.Stream<_i14.Event> get eventStream => + (super.noSuchMethod(Invocation.getter(#eventStream), + returnValue: Stream<_i14.Event>.empty()) as _i5.Stream<_i14.Event>); + @override + void setOrgStreamSubscription() => + super.noSuchMethod(Invocation.method(#setOrgStreamSubscription, []), + returnValueForMissingStub: null); + @override + _i5.Future getEvents() => + (super.noSuchMethod(Invocation.method(#getEvents, []), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future registerForAnEvent(String? eventId) => + (super.noSuchMethod(Invocation.method(#registerForAnEvent, [eventId]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + void dispose() => super.noSuchMethod(Invocation.method(#dispose, []), + returnValueForMissingStub: null); +} + +/// A class which mocks [UserConfig]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockUserConfig extends _i2.Mock implements _i15.UserConfig { + @override + _i5.Stream<_i6.OrgInfo> get currentOrfInfoStream => + (super.noSuchMethod(Invocation.getter(#currentOrfInfoStream), + returnValue: Stream<_i6.OrgInfo>.empty()) as _i5.Stream<_i6.OrgInfo>); + @override + _i5.StreamController<_i6.OrgInfo> get currentOrgInfoController => + (super.noSuchMethod(Invocation.getter(#currentOrgInfoController), + returnValue: _FakeStreamController<_i6.OrgInfo>()) + as _i5.StreamController<_i6.OrgInfo>); + @override + _i6.OrgInfo get currentOrg => + (super.noSuchMethod(Invocation.getter(#currentOrg), + returnValue: _FakeOrgInfo()) as _i6.OrgInfo); + @override + String get currentOrgName => + (super.noSuchMethod(Invocation.getter(#currentOrgName), returnValue: '') + as String); + @override + set currentOrg(_i6.OrgInfo? org) => + super.noSuchMethod(Invocation.setter(#currentOrg, org), + returnValueForMissingStub: null); + @override + _i7.User get currentUser => + (super.noSuchMethod(Invocation.getter(#currentUser), + returnValue: _FakeUser()) as _i7.User); + @override + void initialiseStream() => + super.noSuchMethod(Invocation.method(#initialiseStream, []), + returnValueForMissingStub: null); + @override + _i5.Future userLoggedIn() => + (super.noSuchMethod(Invocation.method(#userLoggedIn, []), + returnValue: Future.value(false)) as _i5.Future); + @override + _i5.Future updateUserJoinedOrg(List<_i6.OrgInfo>? orgDetails) => + (super.noSuchMethod(Invocation.method(#updateUserJoinedOrg, [orgDetails]), + returnValue: Future.value()) as _i5.Future); + @override + _i5.Future updateUserCreatedOrg(List<_i6.OrgInfo>? orgDetails) => + (super.noSuchMethod( + Invocation.method(#updateUserCreatedOrg, [orgDetails]), + returnValue: Future.value()) as _i5.Future); + @override + _i5.Future updateUserMemberRequestOrg( + List<_i6.OrgInfo>? orgDetails) => + (super.noSuchMethod( + Invocation.method(#updateUserMemberRequestOrg, [orgDetails]), + returnValue: Future.value()) as _i5.Future); + @override + _i5.Future updateUserAdminOrg(List<_i6.OrgInfo>? orgDetails) => + (super.noSuchMethod(Invocation.method(#updateUserAdminOrg, [orgDetails]), + returnValue: Future.value()) as _i5.Future); + @override + _i5.Future updateAccessToken( + {String? accessToken, String? refreshToken}) => + (super.noSuchMethod( + Invocation.method(#updateAccessToken, [], + {#accessToken: accessToken, #refreshToken: refreshToken}), + returnValue: Future.value()) as _i5.Future); + @override + _i5.Future updateUser(_i7.User? updatedUserDetails) => + (super.noSuchMethod(Invocation.method(#updateUser, [updatedUserDetails]), + returnValue: Future.value(false)) as _i5.Future); + @override + dynamic saveCurrentOrgInHive(_i6.OrgInfo? saveOrgAsCurrent) => + super.noSuchMethod( + Invocation.method(#saveCurrentOrgInHive, [saveOrgAsCurrent])); +} diff --git a/test/splash_screen_test.dart b/test/splash_screen_test.dart new file mode 100644 index 000000000..739ad09d9 --- /dev/null +++ b/test/splash_screen_test.dart @@ -0,0 +1,193 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:talawa/constants/custom_theme.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/router.dart' as router; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/splash_screen.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/lang_view_model.dart'; +import 'package:talawa/views/base_view.dart'; + +Widget createSplashScreenLight({ThemeMode themeMode = ThemeMode.light}) => + BaseView( + onModelReady: (model) => model.initialize(), + builder: (context, model, child) { + return MaterialApp( + locale: const Locale('en'), + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + key: const Key('Root'), + themeMode: themeMode, + theme: TalawaTheme.lightTheme, + home: const SplashScreen( + key: Key('SplashScreen'), + ), + navigatorKey: navigationService.navigatorKey, + onGenerateRoute: router.generateRoute, + ); + }, + ); + +Widget createSplashScreenDark({ThemeMode themeMode = ThemeMode.dark}) => + BaseView( + onModelReady: (model) => model.initialize(), + builder: (context, model, child) { + return MaterialApp( + locale: const Locale('en'), + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + key: const Key('Root'), + themeMode: themeMode, + darkTheme: TalawaTheme.darkTheme, + home: const SplashScreen( + key: Key('SplashScreen'), + ), + navigatorKey: navigationService.navigatorKey, + onGenerateRoute: router.generateRoute, + ); + }); + +void main() { + setupLocator(); + graphqlConfig.test(); + group('Splash Screen Widget Test in light mode', () { + testWidgets("Testing if Splash Screen shows up", (tester) async { + await tester.pumpWidget(createSplashScreenLight()); + await tester.pumpAndSettle(); + final screenScaffoldWidget = + find.byKey(const Key('SplashScreenScaffold')); + expect(screenScaffoldWidget, findsOneWidget); + expect( + (tester.firstWidget(find.byKey(const Key('Root'))) as MaterialApp) + .theme! + .scaffoldBackgroundColor, + TalawaTheme.lightTheme.scaffoldBackgroundColor, + ); + }); + testWidgets("Testing if app logo shows up", (tester) async { + await tester.pumpWidget(createSplashScreenLight()); + await tester.pumpAndSettle(); + final logoWidget = find.byKey(const Key('LogoPainter')); + expect(logoWidget, findsOneWidget); + expect( + (tester.firstWidget(logoWidget) as CustomPaint).size, + Size(SizeConfig.screenWidth! * 0.6, + (SizeConfig.screenWidth! * 0.6).toDouble())); + }); + testWidgets("Testing if app name shows up", (tester) async { + await tester.pumpWidget(createSplashScreenLight()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.text('TALAWA'); + expect(findAppNameWidget, findsOneWidget); + expect((tester.firstWidget(findAppNameWidget) as Text).style!.color, + TalawaTheme.lightTheme.textTheme.headline4!.color); + expect((tester.firstWidget(findAppNameWidget) as Text).style!.fontFamily, + TalawaTheme.lightTheme.textTheme.headline4!.fontFamily); + expect((tester.firstWidget(findAppNameWidget) as Text).style!.fontSize, + TalawaTheme.lightTheme.textTheme.headline4!.fontSize); + }); + testWidgets("Testing if provider text shows up", (tester) async { + await tester.pumpWidget(createSplashScreenLight()); + await tester.pumpAndSettle(); + final findProviderTextWidget = find.text('from'); + expect(findProviderTextWidget, findsOneWidget); + expect((tester.firstWidget(findProviderTextWidget) as Text).style!.color, + TalawaTheme.lightTheme.textTheme.caption!.color); + expect( + (tester.firstWidget(findProviderTextWidget) as Text) + .style! + .fontFamily, + TalawaTheme.lightTheme.textTheme.caption!.fontFamily); + expect( + (tester.firstWidget(findProviderTextWidget) as Text).style!.fontSize, + TalawaTheme.lightTheme.textTheme.caption!.fontSize); + }); + testWidgets("Testing if provider name shows up", (tester) async { + await tester.pumpWidget(createSplashScreenLight()); + await tester.pumpAndSettle(); + final findProviderNameWidget = find.text('PALISADOES'); + expect(findProviderNameWidget, findsOneWidget); + expect((tester.firstWidget(findProviderNameWidget) as Text).style!.color, + TalawaTheme.lightTheme.textTheme.subtitle2!.color); + expect( + (tester.firstWidget(findProviderNameWidget) as Text) + .style! + .fontFamily, + TalawaTheme.lightTheme.textTheme.subtitle2!.fontFamily); + }); + }); + group('Splash Screen Widget Test in dark mode', () { + testWidgets("Testing if Splash Screen shows up", (tester) async { + await tester.pumpWidget(createSplashScreenDark()); + await tester.pumpAndSettle(); + final screenScaffoldWidget = + find.byKey(const Key('SplashScreenScaffold')); + expect(screenScaffoldWidget, findsOneWidget); + expect( + (tester.firstWidget(find.byKey(const Key('Root'))) as MaterialApp) + .darkTheme! + .scaffoldBackgroundColor, + TalawaTheme.darkTheme.scaffoldBackgroundColor, + ); + }); + testWidgets("Testing if app logo shows up", (tester) async { + await tester.pumpWidget(createSplashScreenDark()); + await tester.pumpAndSettle(); + final logoWidget = find.byKey(const Key('LogoPainter')); + expect(logoWidget, findsOneWidget); + expect( + (tester.firstWidget(logoWidget) as CustomPaint).size, + Size(SizeConfig.screenWidth! * 0.6, + (SizeConfig.screenWidth! * 0.6).toDouble())); + }); + testWidgets("Testing if app name shows up", (tester) async { + await tester.pumpWidget(createSplashScreenDark()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.text('TALAWA'); + expect(findAppNameWidget, findsOneWidget); + expect((tester.firstWidget(findAppNameWidget) as Text).style!.color, + TalawaTheme.darkTheme.textTheme.headline4!.color); + expect((tester.firstWidget(findAppNameWidget) as Text).style!.fontFamily, + TalawaTheme.darkTheme.textTheme.headline4!.fontFamily); + expect((tester.firstWidget(findAppNameWidget) as Text).style!.fontSize, + TalawaTheme.darkTheme.textTheme.headline4!.fontSize); + }); + testWidgets("Testing if provider text shows up", (tester) async { + await tester.pumpWidget(createSplashScreenDark()); + await tester.pumpAndSettle(); + final findProviderTextWidget = find.text('from'); + expect(findProviderTextWidget, findsOneWidget); + expect((tester.firstWidget(findProviderTextWidget) as Text).style!.color, + TalawaTheme.darkTheme.textTheme.caption!.color); + expect( + (tester.firstWidget(findProviderTextWidget) as Text) + .style! + .fontFamily, + TalawaTheme.darkTheme.textTheme.caption!.fontFamily); + expect( + (tester.firstWidget(findProviderTextWidget) as Text).style!.fontSize, + TalawaTheme.darkTheme.textTheme.caption!.fontSize); + }); + testWidgets("Testing if provider name shows up", (tester) async { + await tester.pumpWidget(createSplashScreenDark()); + await tester.pumpAndSettle(); + final findProviderNameWidget = find.text('PALISADOES'); + expect(findProviderNameWidget, findsOneWidget); + expect((tester.firstWidget(findProviderNameWidget) as Text).style!.color, + TalawaTheme.darkTheme.textTheme.subtitle2!.color); + expect( + (tester.firstWidget(findProviderNameWidget) as Text) + .style! + .fontFamily, + TalawaTheme.darkTheme.textTheme.subtitle2!.fontFamily); + }); + }); +} diff --git a/test/widget_tests/home_page_test.dart b/test/widget_tests/home_page_test.dart new file mode 100644 index 000000000..796bcf9a0 --- /dev/null +++ b/test/widget_tests/home_page_test.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:talawa/constants/custom_theme.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/router.dart' as router; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/lang_view_model.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/views/main_screen.dart'; + +import '../helpers/test_helpers.dart'; + +Widget createHomePageScreen() => BaseView( + onModelReady: (model) => model.initialize(), + builder: (context, model, child) { + return MaterialApp( + locale: const Locale('en'), + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + themeMode: ThemeMode.light, + theme: TalawaTheme.lightTheme, + home: const MainScreen( + key: Key('MainScreen'), + ), + navigatorKey: navigationService.navigatorKey, + onGenerateRoute: router.generateRoute, + ); + }, + ); + +void main() { + locator.registerFactory(() => AppLanguage(isTest: true)); + setUp(() { + registerServices(); + registerViewModels(); + locator().test(); + }); + tearDown(() { + unregisterServices(); + unregisterViewModels(); + }); + group('HomePage Widget Test', () { + testWidgets("Testing if HomePage shows up", (tester) async { + await tester.pumpWidget(createHomePageScreen()); + await tester.pumpAndSettle(); + + expect(find.byType(BottomNavigationBar), findsOneWidget); + }); + + testWidgets('Verifying presence of icons in HomePage', (tester) async { + await tester.pumpWidget(createHomePageScreen()); + await tester.pumpAndSettle(); + //detecting icons by find.byIcon(Icons.home) + expect(find.byIcon(Icons.home), findsOneWidget); + expect(find.byIcon(Icons.event_note), findsOneWidget); + expect(find.byIcon(Icons.add_box), findsOneWidget); + expect(find.byIcon(Icons.chat_bubble_outline), findsOneWidget); + expect(find.byIcon(Icons.account_circle), findsOneWidget); + }); + + testWidgets('Verifying if the first index points to newsfeed', + (tester) async { + await tester.pumpWidget(createHomePageScreen()); + await tester.pumpAndSettle(); + expect(find.text('Organization Name'), findsWidgets); + }); + + testWidgets('Testing if Events Screen Shows up', (tester) async { + await tester.runAsync(() async { + await tester.pumpWidget(createHomePageScreen()); + await tester.pumpAndSettle(); + + final eventIcon = find.byIcon(Icons.event_note); + //tapping the eventIcon + await tester.tap(eventIcon); + await tester.pump(); + await tester.pumpAndSettle(); + // Event Screen should be present + expect(find.byKey(const Key("ExploreEventsAppBar")), findsOneWidget); + }); + }); + testWidgets('Testing if Post Screen Shows up', (tester) async { + await tester.pumpWidget(createHomePageScreen()); + await tester.pumpAndSettle(); + + expect(find.text('Post Screen'), findsNothing); + final postIcon = find.byIcon(Icons.add_box); + //tapping on postIcon + await tester.tap(postIcon); + await tester.pump(); + await tester.pumpAndSettle(); + //Post Screen should show up + expect(find.text('Share News'), findsOneWidget); + }); + + testWidgets('Testing if Chat Screen up', (tester) async { + await tester.pumpWidget(createHomePageScreen()); + await tester.pumpAndSettle(); + + final Finder chatIcon = find.byIcon(Icons.chat_bubble_outline); + await tester.tap(chatIcon); + await tester.pump(); + await tester.pumpAndSettle(); + expect(find.text('Chat Screen'), findsWidgets); + }); + + testWidgets('Testing if Profile Screen Shows up', (tester) async { + await tester.runAsync(() async { + await tester.pumpWidget(createHomePageScreen()); + await tester.pumpAndSettle(); + + final profileIcon = find.byIcon(Icons.account_circle); + await tester.tap(profileIcon); + await tester.pump(); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('ProfilePageAppBar')), findsOneWidget); + }); + }); + }); +} diff --git a/test/widget_tests/select_language_page_test.dart b/test/widget_tests/select_language_page_test.dart new file mode 100644 index 000000000..4dc1b2ccd --- /dev/null +++ b/test/widget_tests/select_language_page_test.dart @@ -0,0 +1,270 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:talawa/constants/constants.dart'; +import 'package:talawa/constants/custom_theme.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/router.dart' as router; +import 'package:talawa/services/graphql_config.dart'; +import 'package:talawa/services/navigation_service.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/lang_view_model.dart'; +import 'package:talawa/view_model/pre_auth_view_models/set_url_view_model.dart'; +import 'package:talawa/views/base_view.dart'; +import 'package:talawa/views/pre_auth_screens/select_language.dart'; + +Widget createSelectLanguageScreenLight( + {ThemeMode themeMode = ThemeMode.light}) => + BaseView( + onModelReady: (model) => model.initialize(), + builder: (context, model, child) { + return MaterialApp( + locale: const Locale('en'), + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + key: const Key('Root'), + themeMode: themeMode, + theme: TalawaTheme.lightTheme, + home: const SelectLanguage(key: Key('SelectLanguage')), + navigatorKey: locator().navigatorKey, + onGenerateRoute: router.generateRoute, + ); + }, + ); + +Widget createSelectLanguageScreenDark({ThemeMode themeMode = ThemeMode.dark}) => + BaseView( + onModelReady: (model) => model.initialize(), + builder: (context, model, child) { + return MaterialApp( + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + key: const Key('Root'), + themeMode: themeMode, + darkTheme: TalawaTheme.darkTheme, + home: const SelectLanguage(key: Key('SelectLanguage')), + navigatorKey: locator().navigatorKey, + onGenerateRoute: router.generateRoute, + ); + }, + ); + +void main() { + locator.registerSingleton(NavigationService()); + locator.registerFactory(() => SetUrlViewModel()); + locator.registerSingleton(GraphqlConfig()); + locator.registerSingleton(SizeConfig()); + locator.registerFactory(() => AppLanguage(isTest: true)); + locator().test(); + locator().test(); + group('Select Language Screen Widget Test in light mode', () { + testWidgets("Testing if Select Language Screen shows up", (tester) async { + await tester.pumpWidget(createSelectLanguageScreenLight()); + await tester.pumpAndSettle(); + final screenScaffoldWidget = find.byKey( + const Key('SelectLanguageScreenScaffold'), + ); + expect(screenScaffoldWidget, findsOneWidget); + expect( + (tester.firstWidget(find.byKey(const Key('Root'))) as MaterialApp) + .theme! + .scaffoldBackgroundColor, + TalawaTheme.lightTheme.scaffoldBackgroundColor, + ); + }); + testWidgets("Testing if screen title shows up", (tester) async { + await tester.pumpWidget(createSelectLanguageScreenLight()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.byKey(const Key('Select Language')); + expect(findAppNameWidget, findsOneWidget); + + expect( + (tester.firstWidget(findAppNameWidget) as Text).style!.color, + TalawaTheme.lightTheme.textTheme.headline5!.color, + ); + + expect( + (tester.firstWidget(findAppNameWidget) as Text).style!.fontFamily, + TalawaTheme.lightTheme.textTheme.headline5!.fontFamily, + ); + + expect( + (tester.firstWidget(findAppNameWidget) as Text).style!.fontSize, + TalawaTheme.lightTheme.textTheme.headline5!.fontSize, + ); + }); + //This will be added once we implement the search box + // testWidgets("Testing if search box shows up", (tester) async { + // await tester.pumpWidget(createSelectLanguageScreenLight()); + // await tester.pumpAndSettle(); + // final findAppNameWidget = find.byKey(const Key('SearchField')); + // expect(findAppNameWidget, findsOneWidget); + // }); + testWidgets("Testing if languages list shows up", (tester) async { + await tester.pumpWidget(createSelectLanguageScreenLight()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.byKey(const Key('LanguagesList')); + expect(findAppNameWidget, findsOneWidget); + }); + testWidgets("Testing if all languages are shown", (tester) async { + await tester.pumpWidget(createSelectLanguageScreenLight()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.byKey(const Key('LanguageItem')); + expect(findAppNameWidget, findsNWidgets(languages.length)); + }); + testWidgets("Testing if only one language is selected", (tester) async { + await tester.pumpWidget(createSelectLanguageScreenLight()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.byKey(const Key('Selected')); + expect(findAppNameWidget, findsOneWidget); + }); + testWidgets("Testing unselected language items", (tester) async { + await tester.pumpWidget(createSelectLanguageScreenLight()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.byKey(const Key('NotSelected')); + expect(findAppNameWidget, findsNWidgets(languages.length - 1)); + }); + testWidgets("Testing to change language items", (tester) async { + final int randomNumber = Random().nextInt(languages.length); + print(randomNumber); + await tester.pumpWidget(createSelectLanguageScreenLight()); + await tester.pumpAndSettle(); + + final findAppNameWidget = find.byKey(Key('LanguageItem$randomNumber')); + print("Before Tap"); + await tester.tap(findAppNameWidget); + print("After Tap"); + await tester.pumpAndSettle(); + + expect( + (tester.firstWidget(findAppNameWidget) as Container).decoration, + BoxDecoration(color: const Color(0xFFC4C4C4).withOpacity(0.15)), + ); + }); + testWidgets("Testing to navigate to url page", (tester) async { + await tester.pumpWidget(createSelectLanguageScreenLight()); + await tester.pumpAndSettle(); + + final findAppNameWidget = find.byKey(const Key('NavigateToUrlPage')); + + await tester.tap(findAppNameWidget); + await tester.pumpAndSettle(const Duration(seconds: 3)); + + expect(findAppNameWidget, findsNothing); + }); + testWidgets("Testing to select and navigate button appears", + (tester) async { + await tester.pumpWidget(createSelectLanguageScreenLight()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.byKey(const Key('SelectLangTextButton')); + expect(findAppNameWidget, findsOneWidget); + expect( + (tester.firstWidget(findAppNameWidget) as Text).style!.fontSize, 18); + expect( + (tester.firstWidget(findAppNameWidget) as Text).style!.color, + const Color(0xFF008A37), + ); + }); + }); + group('Select Language Screen Widget Test in dark mode', () { + testWidgets("Testing if Select Language Screen shows up", (tester) async { + await tester.pumpWidget(createSelectLanguageScreenDark()); + await tester.pumpAndSettle(); + final screenScaffoldWidget = + find.byKey(const Key('SelectLanguageScreenScaffold')); + expect(screenScaffoldWidget, findsOneWidget); + expect( + (tester.firstWidget(find.byKey(const Key('Root'))) as MaterialApp) + .darkTheme! + .scaffoldBackgroundColor, + TalawaTheme.darkTheme.scaffoldBackgroundColor, + ); + }); + testWidgets("Testing if screen title shows up", (tester) async { + await tester.pumpWidget(createSelectLanguageScreenDark()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.text('Select Language'); + expect(findAppNameWidget, findsOneWidget); + expect((tester.firstWidget(findAppNameWidget) as Text).style!.color, + TalawaTheme.darkTheme.textTheme.headline5!.color); + expect((tester.firstWidget(findAppNameWidget) as Text).style!.fontFamily, + TalawaTheme.darkTheme.textTheme.headline5!.fontFamily); + expect((tester.firstWidget(findAppNameWidget) as Text).style!.fontSize, + TalawaTheme.darkTheme.textTheme.headline5!.fontSize); + }); + // This is not needed now will be added when required + // testWidgets("Testing if search box shows up", (tester) async { + // await tester.pumpWidget(createSelectLanguageScreenDark()); + // await tester.pumpAndSettle(); + // final findAppNameWidget = find.byKey(const Key('SearchField')); + // expect(findAppNameWidget, findsOneWidget); + // }); + testWidgets("Testing if languages list shows up", (tester) async { + await tester.pumpWidget(createSelectLanguageScreenDark()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.byKey(const Key('LanguagesList')); + expect(findAppNameWidget, findsOneWidget); + }); + testWidgets("Testing if all languages are shown", (tester) async { + await tester.pumpWidget(createSelectLanguageScreenDark()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.byKey(const Key('LanguageItem')); + expect(findAppNameWidget, findsNWidgets(languages.length)); + }); + testWidgets("Testing if only one language is selected", (tester) async { + await tester.pumpWidget(createSelectLanguageScreenDark()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.byKey(const Key('Selected')); + expect(findAppNameWidget, findsOneWidget); + }); + testWidgets("Testing unselected language items", (tester) async { + await tester.pumpWidget(createSelectLanguageScreenDark()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.byKey(const Key('NotSelected')); + expect(findAppNameWidget, findsNWidgets(languages.length - 1)); + }); + testWidgets("Testing to change language items", (tester) async { + final int randomNumber = Random().nextInt(languages.length); + await tester.pumpWidget(createSelectLanguageScreenDark()); + await tester.pumpAndSettle(); + + final findAppNameWidget = find.byKey(Key('LanguageItem$randomNumber')); + await tester.tap(findAppNameWidget); + await tester.pumpAndSettle(); + + expect( + (tester.firstWidget(findAppNameWidget) as Container).decoration, + BoxDecoration(color: const Color(0xFFC4C4C4).withOpacity(0.15)), + ); + }); + testWidgets("Testing to select and navigate button appears", + (tester) async { + await tester.pumpWidget(createSelectLanguageScreenDark()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.byKey(const Key('SelectLangTextButton')); + expect(findAppNameWidget, findsOneWidget); + expect( + (tester.firstWidget(findAppNameWidget) as Text).style!.fontSize, 18); + expect( + (tester.firstWidget(findAppNameWidget) as Text).style!.color, + const Color(0xFF008A37), + ); + }); + testWidgets("Testing to navigate to url page", (tester) async { + await tester.pumpWidget(createSelectLanguageScreenDark()); + await tester.pumpAndSettle(); + final findAppNameWidget = find.byKey(const Key('NavigateToUrlPage')); + await tester.tap(findAppNameWidget); + await tester.pumpAndSettle(); + expect(findAppNameWidget, findsNothing); + }); + }); +} diff --git a/test/widget_tests/set_url_page_test.dart b/test/widget_tests/set_url_page_test.dart new file mode 100644 index 000000000..33626da0a --- /dev/null +++ b/test/widget_tests/set_url_page_test.dart @@ -0,0 +1,509 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:hive/hive.dart'; +import 'package:path_provider/path_provider.dart' as path; +import 'package:provider/provider.dart'; +import 'package:talawa/constants/custom_theme.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/models/organization/org_info.dart'; +import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/router.dart' as router; +import 'package:talawa/services/graphql_config.dart'; +import 'package:talawa/services/navigation_service.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/view_model/lang_view_model.dart'; +import 'package:talawa/views/pre_auth_screens/set_url.dart'; +import 'package:talawa/widgets/raised_round_edge_button.dart'; +import 'package:talawa/widgets/rich_text.dart'; + +Widget createSetUrlScreenLight({ThemeMode themeMode = ThemeMode.light}) => + MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (_) => AppLanguage(isTest: true)), + ], + child: MaterialApp( + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + key: const Key('Root'), + themeMode: themeMode, + theme: TalawaTheme.lightTheme, + home: const SetUrl( + key: Key('SetUrl'), + uri: 'null', + ), + navigatorKey: locator().navigatorKey, + onGenerateRoute: router.generateRoute, + ), + ); + +Widget createSetUrlScreenDark({ThemeMode themeMode = ThemeMode.dark}) => + MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (_) => AppLanguage(isTest: true)), + ], + child: MaterialApp( + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + key: const Key('Root'), + themeMode: themeMode, + darkTheme: TalawaTheme.darkTheme, + home: const SetUrl( + key: Key('SetUrl'), + uri: 'null', + ), + navigatorKey: locator().navigatorKey, + onGenerateRoute: router.generateRoute, + ), + ); + +Future main() async { + TestWidgetsFlutterBinding.ensureInitialized(); //initializing Hive + final Directory dir = await path.getApplicationDocumentsDirectory(); + Hive + ..init(dir.path) + ..registerAdapter(UserAdapter()) + ..registerAdapter(OrgInfoAdapter()); + //opening Hive Boxes + await Hive.openBox('currentUser'); + await Hive.openBox('currentOrg'); + await Hive.openBox('url'); + //setting up MVVM + setupLocator(); + //initializing test functions + locator().test(); + locator().test(); + + //Testing in light mode/normal mode + group('Select Language Screen Widget Test in light mode', () { + testWidgets("Testing if Select Language Screen shows up", (tester) async { + //pushing setUrlScreen + await tester.pumpWidget(createSetUrlScreenLight()); + await tester.pumpAndSettle(); + + //initializing the screenScaffold Finder + final screenScaffoldWidget = + find.byKey(const Key('SetUrlScreenScaffold')); + + //finding the scaffold + expect(screenScaffoldWidget, findsOneWidget); + //testing scaffold color + expect( + (tester.firstWidget(find.byKey(const Key('Root'))) as MaterialApp) + .theme! + .scaffoldBackgroundColor, + TalawaTheme.lightTheme.scaffoldBackgroundColor, + ); + }); + testWidgets("Testing if app logo shows up", (tester) async { + //pushing setUrlScreen + await tester.pumpWidget(createSetUrlScreenLight()); + await tester.pumpAndSettle(); + + //initializing the logo Finder + final logoWidget = find.byKey(const Key('LogoPainter')); + + //finding the logo + expect(logoWidget, findsOneWidget); + //testing logo size + expect( + (tester.firstWidget(logoWidget) as CustomPaint).size, + Size(SizeConfig.screenWidth! * 0.6, + (SizeConfig.screenWidth! * 0.6).toDouble())); + }); + testWidgets("Testing if custom rich text shows up", (tester) async { + //pushing setUrlScreen + await tester.pumpWidget(createSetUrlScreenLight()); + await tester.pumpAndSettle(); + + //initializing the custom rich text widget Finder + final customRichTextWidget = find.byKey(const Key('UrlPageText')); + //initializing the greeting text + final greeting = [ + { + 'text': 'Join ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline6! + .copyWith(fontSize: 24, fontWeight: FontWeight.w700) + }, + { + 'text': 'and ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5 + }, + { + 'text': 'Collaborate ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline6! + .copyWith(fontSize: 24, fontWeight: FontWeight.w700) + }, + { + 'text': 'with your ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5 + }, + { + 'text': 'Organizations', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5! + .copyWith(fontSize: 24, color: const Color(0xFF4285F4)) + }, + ]; + + //finding the custom rich text widget + expect(customRichTextWidget, findsOneWidget); + + //testing greeting text + expect((tester.firstWidget(customRichTextWidget) as CustomRichText).words, + greeting); + }); + testWidgets("Testing the Url Input text form field", (tester) async { + //pushing setUrlScreen + await tester.pumpWidget(createSetUrlScreenLight()); + await tester.pumpAndSettle(); + + //initializing the url input field widget Finder + final urlInputFieldWidget = find.byKey(const Key('UrlInputField')); + //initializing the text field suffix button widget Finder + final findVerifyButton = find.byKey(const Key('VerifyButton')); + //initializing the nullUrlSubmission widget Finder + final nullErrorUrlSubmission = find.text('Please verify URL first'); + //initializing the invalidUrlSubmission widget Finder + final invalidUrlSubmission = find.text('Enter a valid URL'); + + //finding the url input text field + expect(urlInputFieldWidget, findsOneWidget); + //finding the verify suffix button in text form field + expect(findVerifyButton, findsOneWidget); + + //submitting the field with null url + await tester.tap(findVerifyButton); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); + //testing the nullErrorUrlSubmission widget appears + expect(nullErrorUrlSubmission, findsOneWidget); + + //inputting a non url text in the field + await tester.enterText(urlInputFieldWidget, 'non-url text'); + //submitting the field with non url input + await tester.tap(findVerifyButton); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); + //testing the invalidUrlSubmission widget appears + expect(invalidUrlSubmission, findsOneWidget); + + //inputting an existing url text in the field + await tester.enterText(urlInputFieldWidget, + 'https://talawa-graphql-api.herokuapp.com/graphql'); + //submitting the field with a existing url + await tester.tap(findVerifyButton); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); + //testing nullErrorUrlSubmission is not found + expect(nullErrorUrlSubmission, findsNothing); + //testing invalidUrlSubmission is not found + expect(invalidUrlSubmission, findsNothing); + }); + testWidgets("Testing change language button", (tester) async { + //pushing setUrlScreen + await tester.pumpWidget(createSetUrlScreenLight()); + await tester.pumpAndSettle(); + + //initializing the change language widget Finder + final changeLanguageWidget = find.byKey(const Key('ChangeLanguage')); + //initializing the screen scaffold + final screenScaffoldWidget = + find.byKey(const Key('SelectLanguageScreenScaffold')); + //initializing the center offset of changeLanguageWidget + final offset = tester.getCenter(changeLanguageWidget); + //finding the change language widget appears + expect(changeLanguageWidget, findsOneWidget); + + //taping the change language button + await tester.tapAt(offset); + await tester.pumpAndSettle(); + //testing that the scaffold is no more visible + expect(screenScaffoldWidget, findsNothing); + }); + testWidgets("Testing if login button works", (tester) async { + //pushing setUrlScreen + await tester.pumpWidget(createSetUrlScreenLight()); + await tester.pumpAndSettle(); + + //initializing the login button Finder + final loginButtonWidget = find.byKey(const Key('LoginButton')); + + //finding the login button + expect(loginButtonWidget, findsOneWidget); + //testing the login button widget + expect( + (tester.firstWidget(loginButtonWidget) as RaisedRoundedButton) + .backgroundColor, + TalawaTheme.lightTheme.colorScheme.secondaryVariant, + ); + expect( + (tester.firstWidget(loginButtonWidget) as RaisedRoundedButton) + .textColor, + TalawaTheme + .lightTheme.inputDecorationTheme.focusedBorder!.borderSide.color, + ); + expect( + (tester.firstWidget(loginButtonWidget) as RaisedRoundedButton) + .buttonLabel, + 'Login', + ); + }); + testWidgets("Testing if login button works", (tester) async { + //pushing setUrlScreen + await tester.pumpWidget(createSetUrlScreenLight()); + await tester.pumpAndSettle(); + + //initializing the signup button Finder + final signupButtonWidget = find.byKey(const Key('SignUpButton')); + + //finding the signup button + expect(signupButtonWidget, findsOneWidget); + //testing the signup button widget + expect( + (tester.firstWidget(signupButtonWidget) as RaisedRoundedButton) + .backgroundColor, + TalawaTheme + .lightTheme.inputDecorationTheme.focusedBorder!.borderSide.color, + ); + expect( + (tester.firstWidget(signupButtonWidget) as RaisedRoundedButton) + .textColor, + TalawaTheme.lightTheme.colorScheme.secondaryVariant, + ); + expect( + (tester.firstWidget(signupButtonWidget) as RaisedRoundedButton) + .buttonLabel, + 'Sign Up', + ); + }); + }); + + //Testing in dark mode + group('Select Language Screen Widget Test in dark mode', () { + testWidgets("Testing if Select Language Screen shows up", (tester) async { + //pushing setUrlScreen + await tester.pumpWidget(createSetUrlScreenDark()); + await tester.pumpAndSettle(); + + //initializing the screenScaffold Finder + final screenScaffoldWidget = + find.byKey(const Key('SetUrlScreenScaffold')); + + //finding the scaffold + expect(screenScaffoldWidget, findsOneWidget); + //testing scaffold color + expect( + (tester.firstWidget(find.byKey(const Key('Root'))) as MaterialApp) + .darkTheme! + .scaffoldBackgroundColor, + TalawaTheme.darkTheme.scaffoldBackgroundColor, + ); + }); + testWidgets("Testing if app logo shows up", (tester) async { + //pushing setUrlScreen + await tester.pumpWidget(createSetUrlScreenDark()); + await tester.pumpAndSettle(); + + //initializing the logo Finder + final logoWidget = find.byKey(const Key('LogoPainter')); + + //finding the logo + expect(logoWidget, findsOneWidget); + //testing logo size + expect( + (tester.firstWidget(logoWidget) as CustomPaint).size, + Size(SizeConfig.screenWidth! * 0.6, + (SizeConfig.screenWidth! * 0.6).toDouble())); + }); + testWidgets("Testing if custom rich text shows up", (tester) async { + //pushing setUrlScreen + await tester.pumpWidget(createSetUrlScreenDark()); + await tester.pumpAndSettle(); + + //initializing the custom rich text widget Finder + final customRichTextWidget = find.byKey(const Key('UrlPageText')); + //initializing the greeting text + final greeting = [ + { + 'text': 'Join ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline6! + .copyWith(fontSize: 24, fontWeight: FontWeight.w700) + }, + { + 'text': 'and ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5 + }, + { + 'text': 'Collaborate ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline6! + .copyWith(fontSize: 24, fontWeight: FontWeight.w700) + }, + { + 'text': 'with your ', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5 + }, + { + 'text': 'Organizations', + 'textStyle': Theme.of(navigationService.navigatorKey.currentContext!) + .textTheme + .headline5! + .copyWith(fontSize: 24, color: const Color(0xFF4285F4)) + }, + ]; + + //finding the custom rich text widget + expect(customRichTextWidget, findsOneWidget); + + //testing greeting text + expect((tester.firstWidget(customRichTextWidget) as CustomRichText).words, + greeting); + }); + testWidgets("Testing the Url Input text form field", (tester) async { + //pushing setUrlScreen + await tester.pumpWidget(createSetUrlScreenDark()); + await tester.pumpAndSettle(); + + //initializing the url input field widget Finder + final urlInputFieldWidget = find.byKey(const Key('UrlInputField')); + //initializing the text field suffix button widget Finder + final findVerifyButton = find.byKey(const Key('VerifyButton')); + //initializing the nullUrlSubmission widget Finder + final nullErrorUrlSubmission = find.text('Please verify URL first'); + //initializing the invalidUrlSubmission widget Finder + final invalidUrlSubmission = find.text('Enter a valid URL'); + + //finding the url input text field + expect(urlInputFieldWidget, findsOneWidget); + //finding the verify suffix button in text form field + expect(findVerifyButton, findsOneWidget); + + //submitting the field with null url + await tester.tap(findVerifyButton); + await tester.pumpAndSettle(); + //testing the nullErrorUrlSubmission widget appears + expect(nullErrorUrlSubmission, findsOneWidget); + + //inputting a non url text in the field + await tester.enterText(urlInputFieldWidget, 'non-url text'); + //submitting the field with non url input + await tester.tap(findVerifyButton); + await tester.pumpAndSettle(); + //testing the invalidUrlSubmission widget appears + expect(invalidUrlSubmission, findsOneWidget); + + //inputting an existing url text in the field + await tester.enterText(urlInputFieldWidget, + 'https://talawa-graphql-api.herokuapp.com/graphql'); + //submitting the field with a existing url + await tester.tap(findVerifyButton); + await tester.pumpAndSettle(); + //testing nullErrorUrlSubmission is not found + expect(nullErrorUrlSubmission, findsNothing); + //testing invalidUrlSubmission is not found + expect(invalidUrlSubmission, findsNothing); + }); + testWidgets("Testing change language button", (tester) async { + //pushing setUrlScreen + await tester.pumpWidget(createSetUrlScreenDark()); + await tester.pumpAndSettle(); + + //initializing the change language widget Finder + final changeLanguageWidget = find.byKey(const Key('ChangeLanguage')); + //initializing the screen scaffold + final screenScaffoldWidget = + find.byKey(const Key('SelectLanguageScreenScaffold')); + //initializing the center offset of changeLanguageWidget + final offset = tester.getCenter(changeLanguageWidget); + //finding the change language widget appears + expect(changeLanguageWidget, findsOneWidget); + + //taping the change language button + await tester.tapAt(offset); + await tester.pumpAndSettle(); + //testing that the scaffold is no more visible + expect(screenScaffoldWidget, findsNothing); + }); + testWidgets("Testing if login button works", (tester) async { + //pushing setUrlScreen + await tester.pumpWidget(createSetUrlScreenDark()); + await tester.pumpAndSettle(); + + //initializing the login button Finder + final loginButtonWidget = find.byKey(const Key('LoginButton')); + + //finding the login button + expect(loginButtonWidget, findsOneWidget); + //testing the login button widget + expect( + (tester.firstWidget(loginButtonWidget) as RaisedRoundedButton) + .backgroundColor, + TalawaTheme.darkTheme.colorScheme.secondaryVariant, + ); + expect( + (tester.firstWidget(loginButtonWidget) as RaisedRoundedButton) + .textColor, + TalawaTheme + .darkTheme.inputDecorationTheme.focusedBorder!.borderSide.color, + ); + expect( + (tester.firstWidget(loginButtonWidget) as RaisedRoundedButton) + .buttonLabel, + 'Login', + ); + }); + testWidgets("Testing if login button works", (tester) async { + //pushing setUrlScreen + await tester.pumpWidget(createSetUrlScreenDark()); + await tester.pumpAndSettle(); + + //initializing the signup button Finder + final signupButtonWidget = find.byKey(const Key('SignUpButton')); + + //finding the signup button + expect(signupButtonWidget, findsOneWidget); + //testing the signup button widget + expect( + (tester.firstWidget(signupButtonWidget) as RaisedRoundedButton) + .backgroundColor, + TalawaTheme + .darkTheme.inputDecorationTheme.focusedBorder!.borderSide.color, + ); + expect( + (tester.firstWidget(signupButtonWidget) as RaisedRoundedButton) + .textColor, + TalawaTheme.darkTheme.colorScheme.secondaryVariant, + ); + expect( + (tester.firstWidget(signupButtonWidget) as RaisedRoundedButton) + .buttonLabel, + 'Sign Up', + ); + }); + }); +}