Compare commits
	
		
			No commits in common. "951ea17b5de545f015a0e38d004c4d0342ae6e9b" and "37c644ab0d8c524ff3f6e7fdf119dac66a9f841e" have entirely different histories.
		
	
	
		
			951ea17b5d
			...
			37c644ab0d
		
	
		
@ -1,108 +0,0 @@
 | 
			
		||||
name: Lint Code
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches: [ "master" ]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  lint-mobile:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    outputs:
 | 
			
		||||
      output1: ${{ steps.filter.outputs.workflows }}
 | 
			
		||||
    name: 'Lint Flutter Mobile'
 | 
			
		||||
    steps:
 | 
			
		||||
    - uses: actions/checkout@v3
 | 
			
		||||
    # - uses: dorny/paths-filter@v2
 | 
			
		||||
    #   id: filter
 | 
			
		||||
    #   with:
 | 
			
		||||
    #     list-files: shell
 | 
			
		||||
    #     filters: |
 | 
			
		||||
    #       mobile:
 | 
			
		||||
    #         - added|modified: 'mobile/**'
 | 
			
		||||
    - name: Install Deps
 | 
			
		||||
      run: |
 | 
			
		||||
          apt-get update
 | 
			
		||||
          apt-get --yes --force-yes install jq unzip
 | 
			
		||||
    - name: Install Flutter SDK and Lint
 | 
			
		||||
      # if: steps.filter.outputs.mobile == 'true'
 | 
			
		||||
      run: |
 | 
			
		||||
        echo 'Downloading sdk...'
 | 
			
		||||
        cd ..
 | 
			
		||||
        git clone -b stable --single-branch https://github.com/flutter/flutter.git
 | 
			
		||||
        echo 'Adding flutter to path:'
 | 
			
		||||
        export PATH="$PATH:`pwd`/flutter/bin"
 | 
			
		||||
        cd rluv-client
 | 
			
		||||
        flutter doctor
 | 
			
		||||
        echo 'Generating model files...'
 | 
			
		||||
        dart run build_runner build
 | 
			
		||||
        echo 'Analyzing...'
 | 
			
		||||
        flutter analyze --no-fatal-infos --no-fatal-warnings > mobile_lint.txt
 | 
			
		||||
        export MOBILE=$(cat mobile_lint.txt | grep -o 'No issues found' | wc -l | xargs)
 | 
			
		||||
        if [[ $MOBILE == 0 ]]; then echo 'Flutter Linting failed with' && cat mobile_lint.txt | tail -n 15 && echo '...' && exit 1; fi
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
  # lint-busypay:
 | 
			
		||||
  #   runs-on: ubuntu-latest
 | 
			
		||||
  #   outputs:
 | 
			
		||||
  #     output1: ${{ steps.filter.outputs.workflows }}
 | 
			
		||||
  #   name: 'Lint Flutter Busypay'
 | 
			
		||||
  #   steps:
 | 
			
		||||
  #   - uses: actions/checkout@v3
 | 
			
		||||
  #   - uses: dorny/paths-filter@v2
 | 
			
		||||
  #     id: filter
 | 
			
		||||
  #     with:
 | 
			
		||||
  #       list-files: shell
 | 
			
		||||
  #       filters: |
 | 
			
		||||
  #         busypay:
 | 
			
		||||
  #           - added|modified: 'busypay/lib/**'
 | 
			
		||||
  #   - name: Install Flutter SDK
 | 
			
		||||
  #     if: steps.filter.outputs.busypay == 'true'
 | 
			
		||||
  #     uses: subosito/flutter-action@v2
 | 
			
		||||
  #     with:
 | 
			
		||||
  #       flutter-version: '3.0.2'
 | 
			
		||||
  #       channel: 'stable'
 | 
			
		||||
  #   - name: Lint BusyPay Flutter Files
 | 
			
		||||
  #     if: steps.filter.outputs.busypay == 'true'
 | 
			
		||||
  #     run: |
 | 
			
		||||
  #         echo 'Analyzing flutter files'
 | 
			
		||||
  #         cd busypay
 | 
			
		||||
  #         flutter pub get
 | 
			
		||||
  #         cd ..
 | 
			
		||||
  #         flutter analyze --no-fatal-infos --no-fatal-warnings ${{ steps.filter.outputs.busypay_files }} > busypay_lint.txt
 | 
			
		||||
  #         export BUSYPAY=$(cat busypay_lint.txt | grep -o 'No issues found' | wc -l | xargs)
 | 
			
		||||
  #         if [[ $BUSYPAY == 0 ]]; then echo 'Flutter Linting failed with' && cat busypay_lint.txt | tail -n 15 && echo '...' && exit 1; fi
 | 
			
		||||
 | 
			
		||||
  # lint-api:
 | 
			
		||||
  #   runs-on: ubuntu-latest
 | 
			
		||||
  #   outputs:
 | 
			
		||||
  #     output1: ${{ steps.filter.outputs.workflows }}
 | 
			
		||||
  #   name: 'Lint API'
 | 
			
		||||
  #   steps:
 | 
			
		||||
  #   - uses: actions/checkout@v3
 | 
			
		||||
  #   - uses: dorny/paths-filter@v2
 | 
			
		||||
  #     id: filter
 | 
			
		||||
  #     with:
 | 
			
		||||
  #       list-files: shell
 | 
			
		||||
  #       filters: |
 | 
			
		||||
  #         api:
 | 
			
		||||
  #           - added|modified: 'api/**/*.rb'
 | 
			
		||||
  #   # Run only if .rb files in api were modified
 | 
			
		||||
  #   - name: Install Ruby
 | 
			
		||||
  #     if: steps.filter.outputs.api == 'true'
 | 
			
		||||
  #     uses: ruby/setup-ruby@v1
 | 
			
		||||
  #     with:
 | 
			
		||||
  #       ruby-version: '2.6'
 | 
			
		||||
  #       #bundler-cache: true # runs 'bundle install' and caches installed gems automatically
 | 
			
		||||
  #       working-directory: 'api/'
 | 
			
		||||
  #   - name: Lint API
 | 
			
		||||
  #     if: steps.filter.outputs.api == 'true'  # sudo apt-get -yqq install libpq-dev
 | 
			
		||||
  #     run: |
 | 
			
		||||
  #         echo 'Installing rubocop gem'
 | 
			
		||||
  #         export PATH=$RUBY_PATH:$PATH
 | 
			
		||||
  #         gem install rubocop
 | 
			
		||||
  #         rubocop -v
 | 
			
		||||
  #         rubocop --fail-level=E ${{ steps.filter.outputs.api_files }} > api_lint.txt
 | 
			
		||||
  #         export API=$(cat api_lint.txt | grep -o 'no offenses' | wc -l | xargs)
 | 
			
		||||
  #         if [[ $API == 0 ]]; then echo 'API Linting failed with' && cat api_lint.txt | tail -n 15 && echo '...' && exit 1; fi
 | 
			
		||||
  #     env: 
 | 
			
		||||
  #       RUBY_PATH: /home/runner/.gem/ruby/2.6.0/bin
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@ -42,6 +42,3 @@ app.*.map.json
 | 
			
		||||
/android/app/debug
 | 
			
		||||
/android/app/profile
 | 
			
		||||
/android/app/release
 | 
			
		||||
 | 
			
		||||
#generated files
 | 
			
		||||
lib/**/*.g.dart
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								.ignore
									
									
									
									
									
								
							
							
						
						@ -1,8 +0,0 @@
 | 
			
		||||
android
 | 
			
		||||
ios
 | 
			
		||||
web
 | 
			
		||||
linux
 | 
			
		||||
.metadata
 | 
			
		||||
analysis_options.yaml
 | 
			
		||||
pubspec.lock
 | 
			
		||||
// lib/**/*.g.dart
 | 
			
		||||
							
								
								
									
										45
									
								
								.metadata
									
									
									
									
									
								
							
							
						
						@ -1,45 +0,0 @@
 | 
			
		||||
# This file tracks properties of this Flutter project.
 | 
			
		||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
 | 
			
		||||
#
 | 
			
		||||
# This file should be version controlled.
 | 
			
		||||
 | 
			
		||||
version:
 | 
			
		||||
  revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
  channel: unknown
 | 
			
		||||
 | 
			
		||||
project_type: app
 | 
			
		||||
 | 
			
		||||
# Tracks metadata for the flutter migrate command
 | 
			
		||||
migration:
 | 
			
		||||
  platforms:
 | 
			
		||||
    - platform: root
 | 
			
		||||
      create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
      base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
    - platform: android
 | 
			
		||||
      create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
      base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
    - platform: ios
 | 
			
		||||
      create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
      base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
    - platform: linux
 | 
			
		||||
      create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
      base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
    - platform: macos
 | 
			
		||||
      create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
      base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
    - platform: web
 | 
			
		||||
      create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
      base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
    - platform: windows
 | 
			
		||||
      create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
      base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
 | 
			
		||||
 | 
			
		||||
  # User provided section
 | 
			
		||||
 | 
			
		||||
  # List of Local paths (relative to this file) that should be
 | 
			
		||||
  # ignored by the migrate tool.
 | 
			
		||||
  #
 | 
			
		||||
  # Files that are not part of the templates will be ignored by default.
 | 
			
		||||
  unmanaged_files:
 | 
			
		||||
    - 'lib/main.dart'
 | 
			
		||||
    - 'ios/Runner.xcodeproj/project.pbxproj'
 | 
			
		||||
							
								
								
									
										16
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,16 @@
 | 
			
		||||
# rluv
 | 
			
		||||
 | 
			
		||||
A new Flutter project.
 | 
			
		||||
 | 
			
		||||
## Getting Started
 | 
			
		||||
 | 
			
		||||
This project is a starting point for a Flutter application.
 | 
			
		||||
 | 
			
		||||
A few resources to get you started if this is your first Flutter project:
 | 
			
		||||
 | 
			
		||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
 | 
			
		||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
 | 
			
		||||
 | 
			
		||||
For help getting started with Flutter development, view the
 | 
			
		||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
 | 
			
		||||
samples, guidance on mobile development, and a full API reference.
 | 
			
		||||
@ -1,7 +1,5 @@
 | 
			
		||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    package="com.example.rluv">
 | 
			
		||||
    <uses-permission android:name="android.permission.INTERNET"/>
 | 
			
		||||
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
 | 
			
		||||
   <application
 | 
			
		||||
        android:label="rluv"
 | 
			
		||||
        android:name="${applicationName}"
 | 
			
		||||
 | 
			
		||||
| 
		 Before  
							(image error) Size: 37 KiB  | 
@ -1,5 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
  <background android:drawable="@color/ic_launcher_background"/>
 | 
			
		||||
  <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
 | 
			
		||||
</adaptive-icon>
 | 
			
		||||
| 
		 Before  
							(image error) Size: 3.7 KiB After  
							(image error) Size: 544 B  
							
								 
							
							
						 | 
| 
		 Before  
							(image error) Size: 7.3 KiB  | 
| 
		 Before  
							(image error) Size: 2.2 KiB After  
							(image error) Size: 442 B  
							
								 
							
							
						 | 
| 
		 Before  
							(image error) Size: 4.4 KiB  | 
| 
		 Before  
							(image error) Size: 5.2 KiB After  
							(image error) Size: 721 B  
							
								 
							
							
						 | 
| 
		 Before  
							(image error) Size: 10 KiB  | 
| 
		 Before  
							(image error) Size: 8.7 KiB After  
							(image error) Size: 1.0 KiB  
							
								 
							
							
						 | 
| 
		 Before  
							(image error) Size: 18 KiB  | 
| 
		 Before  
							(image error) Size: 12 KiB After  
							(image error) Size: 1.4 KiB  
							
								 
							
							
						 | 
| 
		 Before  
							(image error) Size: 26 KiB  | 
@ -1,4 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
  <color name="ic_launcher_background">#FFA188A6</color>
 | 
			
		||||
</resources>
 | 
			
		||||
@ -26,6 +26,6 @@ subprojects {
 | 
			
		||||
    project.evaluationDependsOn(':app')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.register("clean", Delete) {
 | 
			
		||||
task clean(type: Delete) {
 | 
			
		||||
    delete rootProject.buildDir
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
		 Before (image error) Size: 163 KiB  | 
| 
		 Before (image error) Size: 219 KiB  | 
| 
		 Before (image error) Size: 250 KiB  | 
| 
		 Before (image error) Size: 443 KiB  | 
							
								
								
									
										10
									
								
								build.yaml
									
									
									
									
									
								
							
							
						
						@ -1,10 +0,0 @@
 | 
			
		||||
targets:
 | 
			
		||||
  $default:
 | 
			
		||||
    sources:
 | 
			
		||||
      exclude:
 | 
			
		||||
        - 'example/**'
 | 
			
		||||
    builders:
 | 
			
		||||
      json_serializable:
 | 
			
		||||
        options:
 | 
			
		||||
          explicit_to_json: true 
 | 
			
		||||
          field_rename: snake
 | 
			
		||||
@ -1,122 +1,122 @@
 | 
			
		||||
{
 | 
			
		||||
  "images": [
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-20x20@2x.png",
 | 
			
		||||
      "idiom": "iphone",
 | 
			
		||||
      "scale": "2x",
 | 
			
		||||
      "size": "20x20"
 | 
			
		||||
      "size" : "20x20",
 | 
			
		||||
      "idiom" : "iphone",
 | 
			
		||||
      "filename" : "Icon-App-20x20@2x.png",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-20x20@3x.png",
 | 
			
		||||
      "idiom": "iphone",
 | 
			
		||||
      "scale": "3x",
 | 
			
		||||
      "size": "20x20"
 | 
			
		||||
      "size" : "20x20",
 | 
			
		||||
      "idiom" : "iphone",
 | 
			
		||||
      "filename" : "Icon-App-20x20@3x.png",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-29x29@1x.png",
 | 
			
		||||
      "idiom": "iphone",
 | 
			
		||||
      "scale": "1x",
 | 
			
		||||
      "size": "29x29"
 | 
			
		||||
      "size" : "29x29",
 | 
			
		||||
      "idiom" : "iphone",
 | 
			
		||||
      "filename" : "Icon-App-29x29@1x.png",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-29x29@2x.png",
 | 
			
		||||
      "idiom": "iphone",
 | 
			
		||||
      "scale": "2x",
 | 
			
		||||
      "size": "29x29"
 | 
			
		||||
      "size" : "29x29",
 | 
			
		||||
      "idiom" : "iphone",
 | 
			
		||||
      "filename" : "Icon-App-29x29@2x.png",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-29x29@3x.png",
 | 
			
		||||
      "idiom": "iphone",
 | 
			
		||||
      "scale": "3x",
 | 
			
		||||
      "size": "29x29"
 | 
			
		||||
      "size" : "29x29",
 | 
			
		||||
      "idiom" : "iphone",
 | 
			
		||||
      "filename" : "Icon-App-29x29@3x.png",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-40x40@2x.png",
 | 
			
		||||
      "idiom": "iphone",
 | 
			
		||||
      "scale": "2x",
 | 
			
		||||
      "size": "40x40"
 | 
			
		||||
      "size" : "40x40",
 | 
			
		||||
      "idiom" : "iphone",
 | 
			
		||||
      "filename" : "Icon-App-40x40@2x.png",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-40x40@3x.png",
 | 
			
		||||
      "idiom": "iphone",
 | 
			
		||||
      "scale": "3x",
 | 
			
		||||
      "size": "40x40"
 | 
			
		||||
      "size" : "40x40",
 | 
			
		||||
      "idiom" : "iphone",
 | 
			
		||||
      "filename" : "Icon-App-40x40@3x.png",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-60x60@2x.png",
 | 
			
		||||
      "idiom": "iphone",
 | 
			
		||||
      "scale": "2x",
 | 
			
		||||
      "size": "60x60"
 | 
			
		||||
      "size" : "60x60",
 | 
			
		||||
      "idiom" : "iphone",
 | 
			
		||||
      "filename" : "Icon-App-60x60@2x.png",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-60x60@3x.png",
 | 
			
		||||
      "idiom": "iphone",
 | 
			
		||||
      "scale": "3x",
 | 
			
		||||
      "size": "60x60"
 | 
			
		||||
      "size" : "60x60",
 | 
			
		||||
      "idiom" : "iphone",
 | 
			
		||||
      "filename" : "Icon-App-60x60@3x.png",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-20x20@1x.png",
 | 
			
		||||
      "idiom": "ipad",
 | 
			
		||||
      "scale": "1x",
 | 
			
		||||
      "size": "20x20"
 | 
			
		||||
      "size" : "20x20",
 | 
			
		||||
      "idiom" : "ipad",
 | 
			
		||||
      "filename" : "Icon-App-20x20@1x.png",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-20x20@2x.png",
 | 
			
		||||
      "idiom": "ipad",
 | 
			
		||||
      "scale": "2x",
 | 
			
		||||
      "size": "20x20"
 | 
			
		||||
      "size" : "20x20",
 | 
			
		||||
      "idiom" : "ipad",
 | 
			
		||||
      "filename" : "Icon-App-20x20@2x.png",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-29x29@1x.png",
 | 
			
		||||
      "idiom": "ipad",
 | 
			
		||||
      "scale": "1x",
 | 
			
		||||
      "size": "29x29"
 | 
			
		||||
      "size" : "29x29",
 | 
			
		||||
      "idiom" : "ipad",
 | 
			
		||||
      "filename" : "Icon-App-29x29@1x.png",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-29x29@2x.png",
 | 
			
		||||
      "idiom": "ipad",
 | 
			
		||||
      "scale": "2x",
 | 
			
		||||
      "size": "29x29"
 | 
			
		||||
      "size" : "29x29",
 | 
			
		||||
      "idiom" : "ipad",
 | 
			
		||||
      "filename" : "Icon-App-29x29@2x.png",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-40x40@1x.png",
 | 
			
		||||
      "idiom": "ipad",
 | 
			
		||||
      "scale": "1x",
 | 
			
		||||
      "size": "40x40"
 | 
			
		||||
      "size" : "40x40",
 | 
			
		||||
      "idiom" : "ipad",
 | 
			
		||||
      "filename" : "Icon-App-40x40@1x.png",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-40x40@2x.png",
 | 
			
		||||
      "idiom": "ipad",
 | 
			
		||||
      "scale": "2x",
 | 
			
		||||
      "size": "40x40"
 | 
			
		||||
      "size" : "40x40",
 | 
			
		||||
      "idiom" : "ipad",
 | 
			
		||||
      "filename" : "Icon-App-40x40@2x.png",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-76x76@1x.png",
 | 
			
		||||
      "idiom": "ipad",
 | 
			
		||||
      "scale": "1x",
 | 
			
		||||
      "size": "76x76"
 | 
			
		||||
      "size" : "76x76",
 | 
			
		||||
      "idiom" : "ipad",
 | 
			
		||||
      "filename" : "Icon-App-76x76@1x.png",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-76x76@2x.png",
 | 
			
		||||
      "idiom": "ipad",
 | 
			
		||||
      "scale": "2x",
 | 
			
		||||
      "size": "76x76"
 | 
			
		||||
      "size" : "76x76",
 | 
			
		||||
      "idiom" : "ipad",
 | 
			
		||||
      "filename" : "Icon-App-76x76@2x.png",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-83.5x83.5@2x.png",
 | 
			
		||||
      "idiom": "ipad",
 | 
			
		||||
      "scale": "2x",
 | 
			
		||||
      "size": "83.5x83.5"
 | 
			
		||||
      "size" : "83.5x83.5",
 | 
			
		||||
      "idiom" : "ipad",
 | 
			
		||||
      "filename" : "Icon-App-83.5x83.5@2x.png",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename": "Icon-App-1024x1024@1x.png",
 | 
			
		||||
      "idiom": "ios-marketing",
 | 
			
		||||
      "scale": "1x",
 | 
			
		||||
      "size": "1024x1024"
 | 
			
		||||
      "size" : "1024x1024",
 | 
			
		||||
      "idiom" : "ios-marketing",
 | 
			
		||||
      "filename" : "Icon-App-1024x1024@1x.png",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info": {
 | 
			
		||||
    "author": "icons_launcher",
 | 
			
		||||
    "version": 1
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "version" : 1,
 | 
			
		||||
    "author" : "xcode"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
		 Before (image error) Size: 62 KiB After (image error) Size: 11 KiB  | 
| 
		 Before (image error) Size: 629 B After (image error) Size: 295 B  | 
| 
		 Before (image error) Size: 1.5 KiB After (image error) Size: 406 B  | 
| 
		 Before (image error) Size: 2.6 KiB After (image error) Size: 450 B  | 
| 
		 Before (image error) Size: 1.0 KiB After (image error) Size: 282 B  | 
| 
		 Before (image error) Size: 2.5 KiB After (image error) Size: 462 B  | 
| 
		 Before (image error) Size: 4.2 KiB After (image error) Size: 704 B  | 
| 
		 Before (image error) Size: 1.5 KiB After (image error) Size: 406 B  | 
| 
		 Before (image error) Size: 3.8 KiB After (image error) Size: 586 B  | 
| 
		 Before (image error) Size: 6.4 KiB After (image error) Size: 862 B  | 
| 
		 Before (image error) Size: 6.4 KiB After (image error) Size: 862 B  | 
| 
		 Before (image error) Size: 11 KiB After (image error) Size: 1.6 KiB  | 
| 
		 Before (image error) Size: 3.6 KiB After (image error) Size: 762 B  | 
| 
		 Before (image error) Size: 8.6 KiB After (image error) Size: 1.2 KiB  | 
| 
		 Before (image error) Size: 9.7 KiB After (image error) Size: 1.4 KiB  | 
@ -1,115 +0,0 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
import 'package:helpers/helpers/misc_build/build_media.dart';
 | 
			
		||||
import 'package:rluv/features/account/signup.dart';
 | 
			
		||||
import 'package:rluv/global/styles.dart';
 | 
			
		||||
 | 
			
		||||
import '../../global/api.dart';
 | 
			
		||||
import '../../global/widgets/ui_button.dart';
 | 
			
		||||
import '../../main.dart';
 | 
			
		||||
import 'login.dart';
 | 
			
		||||
 | 
			
		||||
class AccountCreateScreen extends ConsumerStatefulWidget {
 | 
			
		||||
  const AccountCreateScreen({super.key});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ConsumerState<AccountCreateScreen> createState() =>
 | 
			
		||||
      _AccountCreateScreenState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum _AccountScreen { options, login, signup }
 | 
			
		||||
 | 
			
		||||
class _AccountCreateScreenState extends ConsumerState<AccountCreateScreen> {
 | 
			
		||||
  _AccountScreen currentScreen = _AccountScreen.options;
 | 
			
		||||
  static final signupFormKey = GlobalKey<FormState>();
 | 
			
		||||
  static final loginFormKey = GlobalKey<FormState>();
 | 
			
		||||
 | 
			
		||||
  bool usingUsername = true;
 | 
			
		||||
  bool hasFamilyCode = false;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (ref.watch(tokenProvider) != null) {
 | 
			
		||||
      WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
        (_) {
 | 
			
		||||
          Navigator.pushAndRemoveUntil(
 | 
			
		||||
            context,
 | 
			
		||||
            MaterialPageRoute(builder: (ctx) => const Home()),
 | 
			
		||||
            (r) => false,
 | 
			
		||||
          );
 | 
			
		||||
        },
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    final screen = BuildMedia(context).size;
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      backgroundColor: Styles.purpleNurple,
 | 
			
		||||
      body: SafeArea(
 | 
			
		||||
        child: AnimatedSwitcher(
 | 
			
		||||
          duration: const Duration(milliseconds: 300),
 | 
			
		||||
          child: currentScreen == _AccountScreen.options
 | 
			
		||||
              ? Center(
 | 
			
		||||
                  child: SizedBox(
 | 
			
		||||
                    width: screen.width * 0.5 > 400 ? 400 : screen.width * 0.5,
 | 
			
		||||
                    child: Column(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        const Spacer(),
 | 
			
		||||
                        const Text(
 | 
			
		||||
                          'Welcome!',
 | 
			
		||||
                          style: TextStyle(fontSize: 28),
 | 
			
		||||
                        ),
 | 
			
		||||
                        Padding(
 | 
			
		||||
                          padding: const EdgeInsets.symmetric(vertical: 40.0),
 | 
			
		||||
                          child: Image.asset("assets/app_icon.png",
 | 
			
		||||
                              height:
 | 
			
		||||
                                  screen.width > 500 ? 250 : screen.width * 0.5,
 | 
			
		||||
                              width: screen.width > 500
 | 
			
		||||
                                  ? 250
 | 
			
		||||
                                  : screen.width * 0.5),
 | 
			
		||||
                        ),
 | 
			
		||||
                        UiButton(
 | 
			
		||||
                          color: Styles.sunflower,
 | 
			
		||||
                          onPressed: () {
 | 
			
		||||
                            setState(
 | 
			
		||||
                              () => currentScreen = _AccountScreen.signup,
 | 
			
		||||
                            );
 | 
			
		||||
                          },
 | 
			
		||||
                          text: 'Signup',
 | 
			
		||||
                        ),
 | 
			
		||||
                        const SizedBox(height: 20),
 | 
			
		||||
                        UiButton(
 | 
			
		||||
                          color: Styles.flounderBlue,
 | 
			
		||||
                          onPressed: () {
 | 
			
		||||
                            setState(
 | 
			
		||||
                              () => currentScreen = _AccountScreen.login,
 | 
			
		||||
                            );
 | 
			
		||||
                          },
 | 
			
		||||
                          text: 'Login',
 | 
			
		||||
                        ),
 | 
			
		||||
                        const Spacer(),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                )
 | 
			
		||||
              : currentScreen == _AccountScreen.login
 | 
			
		||||
                  ? Login(formKey: loginFormKey, exitNav: exitNav())
 | 
			
		||||
                  : Signup(
 | 
			
		||||
                      formKey: signupFormKey,
 | 
			
		||||
                      exitNav: exitNav(),
 | 
			
		||||
                    ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget exitNav() => Align(
 | 
			
		||||
        alignment: Alignment.topLeft,
 | 
			
		||||
        child: IconButton(
 | 
			
		||||
          icon: const Icon(Icons.chevron_left),
 | 
			
		||||
          onPressed: () {
 | 
			
		||||
            setState(
 | 
			
		||||
              () => currentScreen = _AccountScreen.options,
 | 
			
		||||
            );
 | 
			
		||||
          },
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
}
 | 
			
		||||
@ -1,239 +0,0 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
import 'package:helpers/helpers/misc_build/build_media.dart';
 | 
			
		||||
import 'package:helpers/helpers/print.dart';
 | 
			
		||||
 | 
			
		||||
import '../../global/api.dart';
 | 
			
		||||
import '../../global/styles.dart';
 | 
			
		||||
import '../../global/utils.dart';
 | 
			
		||||
import '../../global/widgets/ui_button.dart';
 | 
			
		||||
 | 
			
		||||
class Login extends ConsumerStatefulWidget {
 | 
			
		||||
  const Login({super.key, required this.formKey, required this.exitNav});
 | 
			
		||||
 | 
			
		||||
  final GlobalKey<FormState> formKey;
 | 
			
		||||
  final Widget exitNav;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ConsumerState<Login> createState() => _LoginState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _LoginState extends ConsumerState<Login> {
 | 
			
		||||
  bool usingUsername = false;
 | 
			
		||||
  final emailController = TextEditingController();
 | 
			
		||||
  final usernameController = TextEditingController();
 | 
			
		||||
  final passwordController = TextEditingController();
 | 
			
		||||
 | 
			
		||||
  final focusNodes = [
 | 
			
		||||
    FocusNode(),
 | 
			
		||||
    FocusNode(),
 | 
			
		||||
    FocusNode(),
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final screen = BuildMedia(context).size;
 | 
			
		||||
    return Form(
 | 
			
		||||
      key: widget.formKey,
 | 
			
		||||
      child: Stack(
 | 
			
		||||
        children: [
 | 
			
		||||
          Column(
 | 
			
		||||
            children: [
 | 
			
		||||
              const Spacer(),
 | 
			
		||||
              const Text(
 | 
			
		||||
                'Login',
 | 
			
		||||
                style: TextStyle(fontSize: 24),
 | 
			
		||||
              ),
 | 
			
		||||
              const Spacer(flex: 2),
 | 
			
		||||
              Row(
 | 
			
		||||
                mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
                children: [
 | 
			
		||||
                  Column(
 | 
			
		||||
                    children: [
 | 
			
		||||
                      const Padding(
 | 
			
		||||
                        padding: EdgeInsets.all(2.0),
 | 
			
		||||
                        child: Text(
 | 
			
		||||
                          'Username',
 | 
			
		||||
                          style: TextStyle(
 | 
			
		||||
                            fontSize: 16,
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                      Checkbox(
 | 
			
		||||
                        activeColor: Styles.lavender,
 | 
			
		||||
                        onChanged: (bool? value) {
 | 
			
		||||
                          setState(() => usingUsername = true);
 | 
			
		||||
                        },
 | 
			
		||||
                        value: usingUsername,
 | 
			
		||||
                      ),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                  const SizedBox(
 | 
			
		||||
                    width: 35,
 | 
			
		||||
                  ),
 | 
			
		||||
                  Column(
 | 
			
		||||
                    children: [
 | 
			
		||||
                      const Padding(
 | 
			
		||||
                        padding: EdgeInsets.all(2.0),
 | 
			
		||||
                        child: Text(
 | 
			
		||||
                          'Email',
 | 
			
		||||
                          style: TextStyle(
 | 
			
		||||
                            fontSize: 16,
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                      Checkbox(
 | 
			
		||||
                        activeColor: Styles.lavender,
 | 
			
		||||
                        onChanged: (bool? value) {
 | 
			
		||||
                          setState(() => usingUsername = false);
 | 
			
		||||
                        },
 | 
			
		||||
                        value: !usingUsername,
 | 
			
		||||
                      ),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
              AnimatedSwitcher(
 | 
			
		||||
                duration: const Duration(milliseconds: 200),
 | 
			
		||||
                child: !usingUsername
 | 
			
		||||
                    ? generateTextField(
 | 
			
		||||
                        controller: emailController,
 | 
			
		||||
                        size: screen,
 | 
			
		||||
                        text: 'Email: ',
 | 
			
		||||
                        validatorFunc: (s) {
 | 
			
		||||
                          if (s != null && s.isNotEmpty && !isEmailValid(s)) {
 | 
			
		||||
                            return 'Email entered is invalid';
 | 
			
		||||
                          }
 | 
			
		||||
                          return null;
 | 
			
		||||
                        },
 | 
			
		||||
                        index: 0)
 | 
			
		||||
                    : generateTextField(
 | 
			
		||||
                        controller: usernameController,
 | 
			
		||||
                        size: screen,
 | 
			
		||||
                        text: 'Username: ',
 | 
			
		||||
                        validatorFunc: (s) {
 | 
			
		||||
                          if (s == null || s.isEmpty) {
 | 
			
		||||
                            return 'Invalid Username';
 | 
			
		||||
                          }
 | 
			
		||||
                          if (s.length < 3) {
 | 
			
		||||
                            return 'Username Not 3 Letters Long';
 | 
			
		||||
                          }
 | 
			
		||||
                          if (s.length > 20) {
 | 
			
		||||
                            return 'Username Too Long';
 | 
			
		||||
                          }
 | 
			
		||||
                          if (!isUsernameValid(s)) {
 | 
			
		||||
                            return 'Letters, Numbers, and ., -, _ Allowed';
 | 
			
		||||
                          }
 | 
			
		||||
                          return null;
 | 
			
		||||
                        },
 | 
			
		||||
                        index: 0),
 | 
			
		||||
              ),
 | 
			
		||||
              generateTextField(
 | 
			
		||||
                  controller: passwordController,
 | 
			
		||||
                  size: screen,
 | 
			
		||||
                  text: 'Password: ',
 | 
			
		||||
                  validatorFunc: (s) {
 | 
			
		||||
                    if (s != null && s.length < 6) {
 | 
			
		||||
                      return 'Please do a better password (you have to)';
 | 
			
		||||
                    }
 | 
			
		||||
                    return null;
 | 
			
		||||
                  },
 | 
			
		||||
                  index: 1,
 | 
			
		||||
                  isPassword: true),
 | 
			
		||||
              const Spacer(flex: 2),
 | 
			
		||||
              UiButton(
 | 
			
		||||
                width: screen.width * 0.85 > 400 ? 400 : screen.width * 0.85,
 | 
			
		||||
                showLoading: true,
 | 
			
		||||
                onPressed: () => login(),
 | 
			
		||||
                text: 'Submit',
 | 
			
		||||
                color: Styles.seaweedGreen,
 | 
			
		||||
              ),
 | 
			
		||||
              const Spacer(),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
          widget.exitNav,
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget generateTextField({
 | 
			
		||||
    required String text,
 | 
			
		||||
    String? label,
 | 
			
		||||
    required int index,
 | 
			
		||||
    required TextEditingController controller,
 | 
			
		||||
    isPassword = false,
 | 
			
		||||
    required Size size,
 | 
			
		||||
    required String? Function(String?) validatorFunc,
 | 
			
		||||
  }) {
 | 
			
		||||
    return Padding(
 | 
			
		||||
      padding: const EdgeInsets.all(8.0),
 | 
			
		||||
      child: Row(
 | 
			
		||||
        children: [
 | 
			
		||||
          Padding(
 | 
			
		||||
            padding: const EdgeInsets.all(8.0),
 | 
			
		||||
            child: Text(
 | 
			
		||||
              text,
 | 
			
		||||
              style: TextStyle(fontSize: size.width < 350 ? 16 : 20),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          const Spacer(),
 | 
			
		||||
          Container(
 | 
			
		||||
            decoration: Styles.boxLavenderBubble,
 | 
			
		||||
            width: size.width * 0.5 > 300 ? 300 : size.width * 0.5,
 | 
			
		||||
            child: TextFormField(
 | 
			
		||||
              validator: validatorFunc,
 | 
			
		||||
              controller: controller,
 | 
			
		||||
              decoration: Styles.inputLavenderBubble(labelText: label),
 | 
			
		||||
              obscureText: isPassword,
 | 
			
		||||
              focusNode: focusNodes[index],
 | 
			
		||||
              style: const TextStyle(fontSize: 16),
 | 
			
		||||
              onFieldSubmitted: (_) {
 | 
			
		||||
                if (index != focusNodes.length - 1) {
 | 
			
		||||
                  focusNodes[index + 1].requestFocus();
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          SizedBox(
 | 
			
		||||
            width: size.width * 0.05,
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future login() async {
 | 
			
		||||
    try {
 | 
			
		||||
      if (widget.formKey.currentState != null &&
 | 
			
		||||
          !widget.formKey.currentState!.validate()) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      final data =
 | 
			
		||||
          await ref.read(apiProvider.notifier).post(path: 'auth/login', data: {
 | 
			
		||||
        'username':
 | 
			
		||||
            usernameController.text.isEmpty ? null : usernameController.text,
 | 
			
		||||
        'email': emailController.text.isEmpty ? null : emailController.text,
 | 
			
		||||
        'password': passwordController.text,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      String message = 'Login unsuccessful';
 | 
			
		||||
      bool success = data?['success'] == 'false';
 | 
			
		||||
      if (data != null) {
 | 
			
		||||
        if (data['message'] != null) {
 | 
			
		||||
          message = data['message'];
 | 
			
		||||
        } else if (success) {
 | 
			
		||||
          message = 'Logged in!';
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      // final bool success = data?['success'] ?? false;
 | 
			
		||||
      printAmber(data);
 | 
			
		||||
      showSnack(
 | 
			
		||||
          ref: ref,
 | 
			
		||||
          text: message,
 | 
			
		||||
          type: !success ? SnackType.error : SnackType.success);
 | 
			
		||||
    } catch (err, st) {
 | 
			
		||||
      printRed('Error in login: $err\n$st');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,258 +0,0 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
import 'package:helpers/helpers/misc_build/build_media.dart';
 | 
			
		||||
import 'package:helpers/helpers/print.dart';
 | 
			
		||||
 | 
			
		||||
import '../../global/api.dart';
 | 
			
		||||
import '../../global/styles.dart';
 | 
			
		||||
import '../../global/utils.dart';
 | 
			
		||||
import '../../global/widgets/ui_button.dart';
 | 
			
		||||
 | 
			
		||||
class Signup extends ConsumerStatefulWidget {
 | 
			
		||||
  const Signup({super.key, required this.formKey, required this.exitNav});
 | 
			
		||||
 | 
			
		||||
  final GlobalKey<FormState> formKey;
 | 
			
		||||
  final Widget exitNav;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ConsumerState<Signup> createState() => _SignupState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _SignupState extends ConsumerState<Signup> {
 | 
			
		||||
  bool hasFamilyCode = false;
 | 
			
		||||
  final nameCotroller = TextEditingController();
 | 
			
		||||
  final usernameController = TextEditingController();
 | 
			
		||||
  final emailController = TextEditingController();
 | 
			
		||||
  final passwordController = TextEditingController();
 | 
			
		||||
  final familyCodeController = TextEditingController();
 | 
			
		||||
 | 
			
		||||
  final focusNodes = [
 | 
			
		||||
    FocusNode(),
 | 
			
		||||
    FocusNode(),
 | 
			
		||||
    FocusNode(),
 | 
			
		||||
    FocusNode(),
 | 
			
		||||
    FocusNode(),
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final screen = BuildMedia(context).size;
 | 
			
		||||
    return Form(
 | 
			
		||||
      key: widget.formKey,
 | 
			
		||||
      child: Stack(
 | 
			
		||||
        children: [
 | 
			
		||||
          Column(
 | 
			
		||||
            children: [
 | 
			
		||||
              const Spacer(),
 | 
			
		||||
              const Text(
 | 
			
		||||
                'Signup',
 | 
			
		||||
                style: TextStyle(fontSize: 24),
 | 
			
		||||
              ),
 | 
			
		||||
              const Spacer(),
 | 
			
		||||
              generateTextField(
 | 
			
		||||
                  controller: nameCotroller,
 | 
			
		||||
                  size: screen,
 | 
			
		||||
                  text: 'Name:',
 | 
			
		||||
                  validatorFunc: (s) {
 | 
			
		||||
                    if (s == null || s.isEmpty) {
 | 
			
		||||
                      return 'You matter! Enter a name :)';
 | 
			
		||||
                    }
 | 
			
		||||
                    return null;
 | 
			
		||||
                  },
 | 
			
		||||
                  index: 0),
 | 
			
		||||
              generateTextField(
 | 
			
		||||
                  controller: usernameController,
 | 
			
		||||
                  size: screen,
 | 
			
		||||
                  text: 'Username: ',
 | 
			
		||||
                  validatorFunc: (s) {
 | 
			
		||||
                    if (s == null || s.isEmpty) {
 | 
			
		||||
                      return 'Invalid Username';
 | 
			
		||||
                    }
 | 
			
		||||
                    if (s.length < 3) {
 | 
			
		||||
                      return 'Username Not 3 Letters Long';
 | 
			
		||||
                    }
 | 
			
		||||
                    if (s.length > 20) {
 | 
			
		||||
                      return 'Username Too Long';
 | 
			
		||||
                    }
 | 
			
		||||
                    if (!isUsernameValid(s)) {
 | 
			
		||||
                      return 'Letters, Numbers, and ., -, _ Allowed';
 | 
			
		||||
                    }
 | 
			
		||||
                    return null;
 | 
			
		||||
                  },
 | 
			
		||||
                  index: 1),
 | 
			
		||||
              generateTextField(
 | 
			
		||||
                  controller: emailController,
 | 
			
		||||
                  size: screen,
 | 
			
		||||
                  validatorFunc: (s) {
 | 
			
		||||
                    if (s != null && s.isNotEmpty && !isEmailValid(s)) {
 | 
			
		||||
                      return 'Email entered is invalid';
 | 
			
		||||
                    }
 | 
			
		||||
                    return null;
 | 
			
		||||
                  },
 | 
			
		||||
                  text: 'Email:',
 | 
			
		||||
                  subtext: '(optional)',
 | 
			
		||||
                  index: 2),
 | 
			
		||||
              generateTextField(
 | 
			
		||||
                  controller: passwordController,
 | 
			
		||||
                  size: screen,
 | 
			
		||||
                  validatorFunc: (s) {
 | 
			
		||||
                    if (s != null && s.length < 6) {
 | 
			
		||||
                      return 'Please do a better password (you have to)';
 | 
			
		||||
                    }
 | 
			
		||||
                    return null;
 | 
			
		||||
                  },
 | 
			
		||||
                  text: 'Password:',
 | 
			
		||||
                  index: 3,
 | 
			
		||||
                  isPassword: true),
 | 
			
		||||
              const SizedBox(height: 30),
 | 
			
		||||
              AnimatedSwitcher(
 | 
			
		||||
                  duration: const Duration(milliseconds: 300),
 | 
			
		||||
                  child: hasFamilyCode
 | 
			
		||||
                      ? Column(
 | 
			
		||||
                          children: [
 | 
			
		||||
                            generateTextField(
 | 
			
		||||
                              controller: familyCodeController,
 | 
			
		||||
                              size: screen,
 | 
			
		||||
                              validatorFunc: (s) {
 | 
			
		||||
                                if (hasFamilyCode) {
 | 
			
		||||
                                  if (s == null || s.length != 5) {
 | 
			
		||||
                                    return 'Invalid Code';
 | 
			
		||||
                                  }
 | 
			
		||||
                                }
 | 
			
		||||
                                return null;
 | 
			
		||||
                              },
 | 
			
		||||
                              text: 'Family Code:',
 | 
			
		||||
                              index: 4,
 | 
			
		||||
                            ),
 | 
			
		||||
                            UiButton(
 | 
			
		||||
                              width: screen.width * 0.4,
 | 
			
		||||
                              icon: const Icon(Icons.cancel,
 | 
			
		||||
                                  color: Styles.washedStone, size: 20),
 | 
			
		||||
                              onPressed: () {
 | 
			
		||||
                                setState(
 | 
			
		||||
                                  () => hasFamilyCode = false,
 | 
			
		||||
                                );
 | 
			
		||||
                              },
 | 
			
		||||
                              text: 'Cancel',
 | 
			
		||||
                            ),
 | 
			
		||||
                          ],
 | 
			
		||||
                        )
 | 
			
		||||
                      : Padding(
 | 
			
		||||
                          padding: const EdgeInsets.all(8.0),
 | 
			
		||||
                          child: UiButton(
 | 
			
		||||
                            width: screen.width * 0.5 > 400
 | 
			
		||||
                                ? 400
 | 
			
		||||
                                : screen.width * 0.5,
 | 
			
		||||
                            text: 'JOIN FAMILY',
 | 
			
		||||
                            // color: Styles.flounderBlue,
 | 
			
		||||
                            onPressed: () {
 | 
			
		||||
                              setState(
 | 
			
		||||
                                () => hasFamilyCode = true,
 | 
			
		||||
                              );
 | 
			
		||||
                            },
 | 
			
		||||
                          ),
 | 
			
		||||
                        )),
 | 
			
		||||
              const Spacer(),
 | 
			
		||||
              UiButton(
 | 
			
		||||
                showLoading: true,
 | 
			
		||||
                onPressed: () => signup(),
 | 
			
		||||
                text: 'Submit',
 | 
			
		||||
                color: Styles.seaweedGreen,
 | 
			
		||||
              ),
 | 
			
		||||
              const Spacer(),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
          widget.exitNav,
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget generateTextField({
 | 
			
		||||
    required String text,
 | 
			
		||||
    String? subtext,
 | 
			
		||||
    String? label,
 | 
			
		||||
    required int index,
 | 
			
		||||
    required TextEditingController controller,
 | 
			
		||||
    isPassword = false,
 | 
			
		||||
    required Size size,
 | 
			
		||||
    required String? Function(String?) validatorFunc,
 | 
			
		||||
  }) {
 | 
			
		||||
    return Padding(
 | 
			
		||||
      padding: const EdgeInsets.all(8.0),
 | 
			
		||||
      child: Row(
 | 
			
		||||
        children: [
 | 
			
		||||
          Padding(
 | 
			
		||||
            padding: const EdgeInsets.all(8.0),
 | 
			
		||||
            child: Column(
 | 
			
		||||
              children: [
 | 
			
		||||
                Text(
 | 
			
		||||
                  text,
 | 
			
		||||
                  style: TextStyle(fontSize: size.width < 350 ? 16 : 20),
 | 
			
		||||
                ),
 | 
			
		||||
                if (subtext != null)
 | 
			
		||||
                  Text(subtext, style: const TextStyle(fontSize: 12)),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          const Spacer(),
 | 
			
		||||
          Container(
 | 
			
		||||
            decoration: Styles.boxLavenderBubble,
 | 
			
		||||
            width: size.width * 0.5 > 300 ? 300 : size.width * 0.5,
 | 
			
		||||
            child: TextFormField(
 | 
			
		||||
              validator: validatorFunc,
 | 
			
		||||
              controller: controller,
 | 
			
		||||
              decoration: Styles.inputLavenderBubble(labelText: label),
 | 
			
		||||
              obscureText: isPassword,
 | 
			
		||||
              focusNode: focusNodes[index],
 | 
			
		||||
              style: const TextStyle(fontSize: 16),
 | 
			
		||||
              onFieldSubmitted: (_) {
 | 
			
		||||
                if (index != focusNodes.length - 1) {
 | 
			
		||||
                  focusNodes[index + 1].requestFocus();
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          SizedBox(
 | 
			
		||||
            width: size.width * 0.05,
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future signup() async {
 | 
			
		||||
    try {
 | 
			
		||||
      if (widget.formKey.currentState != null &&
 | 
			
		||||
          !widget.formKey.currentState!.validate()) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      final data =
 | 
			
		||||
          await ref.read(apiProvider.notifier).post(path: 'auth/signup', data: {
 | 
			
		||||
        'name': nameCotroller.text,
 | 
			
		||||
        'username':
 | 
			
		||||
            usernameController.text.isEmpty ? null : usernameController.text,
 | 
			
		||||
        'email': emailController.text.isEmpty ? null : emailController.text,
 | 
			
		||||
        'password': passwordController.text,
 | 
			
		||||
        'family_code': familyCodeController.text.isEmpty
 | 
			
		||||
            ? null
 | 
			
		||||
            : familyCodeController.text.toUpperCase(),
 | 
			
		||||
        'budget_name': 'Budget'
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      final success = data?['success'] ?? false;
 | 
			
		||||
      showSnack(
 | 
			
		||||
          ref: ref,
 | 
			
		||||
          text: data?['message'] ?? success
 | 
			
		||||
              ? 'Login successful'
 | 
			
		||||
              : 'Login unsuccessful',
 | 
			
		||||
          type: !success ? SnackType.error : SnackType.success);
 | 
			
		||||
 | 
			
		||||
      printAmber(data);
 | 
			
		||||
      // final user = User.fromJson(data?['user']);
 | 
			
		||||
      // ref.read(tokenProvider.notifier).state =
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      printRed(err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,320 +0,0 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
import 'package:helpers/helpers.dart';
 | 
			
		||||
import 'package:rluv/features/budget/screens/transactions_listview.dart';
 | 
			
		||||
import 'package:rluv/features/budget/widgets/add_transaction_dialog.dart';
 | 
			
		||||
import 'package:rluv/features/budget/widgets/budget_category_bar.dart';
 | 
			
		||||
import 'package:rluv/features/budget/widgets/budget_net_bar.dart';
 | 
			
		||||
import 'package:rluv/global/styles.dart';
 | 
			
		||||
import 'package:rluv/global/utils.dart';
 | 
			
		||||
 | 
			
		||||
import '../../../global/store.dart';
 | 
			
		||||
import '../../../global/widgets/ui_button.dart';
 | 
			
		||||
import '../../../models/transaction_model.dart';
 | 
			
		||||
import '../widgets/add_budget_category_dialog.dart';
 | 
			
		||||
 | 
			
		||||
class BudgetOverviewScreen extends ConsumerStatefulWidget {
 | 
			
		||||
  const BudgetOverviewScreen({super.key, required this.initialData});
 | 
			
		||||
 | 
			
		||||
  final Map<String, dynamic> initialData;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ConsumerState<BudgetOverviewScreen> createState() =>
 | 
			
		||||
      _BudgetOverviewScreenState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _BudgetOverviewScreenState extends ConsumerState<BudgetOverviewScreen> {
 | 
			
		||||
  final budgetListScrollController = ScrollController();
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final budget = ref.watch(budgetProvider);
 | 
			
		||||
    final budgetCategories = ref.watch(budgetCategoriesProvider);
 | 
			
		||||
    final transactions = ref.watch(transactionsProvider);
 | 
			
		||||
    final screen = BuildMedia(context).size;
 | 
			
		||||
    double netExpense = 0.0;
 | 
			
		||||
    double netIncome = 0.0;
 | 
			
		||||
    double expectedExpenses = 0.0;
 | 
			
		||||
    Map<int, double> budgetCategoryNetMap = {};
 | 
			
		||||
    netExpense = transactions
 | 
			
		||||
        .where((t) => t.type == TransactionType.expense)
 | 
			
		||||
        .fold(netExpense, (net, t) => net + t.amount);
 | 
			
		||||
    netIncome = transactions
 | 
			
		||||
        .where((t) => t.type == TransactionType.income)
 | 
			
		||||
        .fold(netIncome, (net, t) => net + t.amount);
 | 
			
		||||
    for (final bud in budgetCategories) {
 | 
			
		||||
      double net = 0.0;
 | 
			
		||||
      expectedExpenses += bud.amount;
 | 
			
		||||
      net = transactions
 | 
			
		||||
          .where((t) => t.budgetCategoryId == bud.id)
 | 
			
		||||
          .fold(net, (net, t) => net + t.amount);
 | 
			
		||||
      budgetCategoryNetMap[bud.id!] = net;
 | 
			
		||||
    }
 | 
			
		||||
    return Stack(
 | 
			
		||||
      children: [
 | 
			
		||||
        Column(
 | 
			
		||||
          children: [
 | 
			
		||||
            /// TOP HALF, TITLE & OVERVIEW
 | 
			
		||||
            Container(
 | 
			
		||||
              height: screen.height * 0.3,
 | 
			
		||||
              width: screen.width,
 | 
			
		||||
              color: Styles.purpleNurple,
 | 
			
		||||
              child: Column(
 | 
			
		||||
                crossAxisAlignment: CrossAxisAlignment.center,
 | 
			
		||||
                children: [
 | 
			
		||||
                  const Spacer(flex: 2),
 | 
			
		||||
                  Text(
 | 
			
		||||
                    formatDate(DateTime.now()),
 | 
			
		||||
                    style: const TextStyle(
 | 
			
		||||
                        fontSize: 16, color: Styles.electricBlue),
 | 
			
		||||
                  ),
 | 
			
		||||
                  const Spacer(),
 | 
			
		||||
                  const Text('MONTHLY',
 | 
			
		||||
                      style:
 | 
			
		||||
                          TextStyle(fontSize: 42, color: Styles.electricBlue)),
 | 
			
		||||
                  const Spacer(flex: 2),
 | 
			
		||||
                  BudgetNetBar(
 | 
			
		||||
                      isPositive: true,
 | 
			
		||||
                      net: netIncome,
 | 
			
		||||
                      expected: budget?.expectedIncome ?? 0.0),
 | 
			
		||||
                  const Spacer(),
 | 
			
		||||
                  BudgetNetBar(
 | 
			
		||||
                      isPositive: false,
 | 
			
		||||
                      net: netExpense,
 | 
			
		||||
                      expected: expectedExpenses),
 | 
			
		||||
                  const Spacer(),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
 | 
			
		||||
            /// BOTTOM HALF, BUDGET BREAKDOWN
 | 
			
		||||
            Expanded(
 | 
			
		||||
              child: Container(
 | 
			
		||||
                color: Styles.sunflower,
 | 
			
		||||
                width: screen.width,
 | 
			
		||||
                child: Column(
 | 
			
		||||
                  mainAxisSize: MainAxisSize.max,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Padding(
 | 
			
		||||
                      padding: const EdgeInsets.all(14.0),
 | 
			
		||||
                      child: Container(
 | 
			
		||||
                        decoration: BoxDecoration(
 | 
			
		||||
                          color: Styles.blushingPink,
 | 
			
		||||
                          borderRadius: BorderRadius.circular(16.0),
 | 
			
		||||
                        ),
 | 
			
		||||
                        child: Column(
 | 
			
		||||
                          children: [
 | 
			
		||||
                            const Padding(
 | 
			
		||||
                              padding: EdgeInsets.all(8.0),
 | 
			
		||||
                              child: Text(
 | 
			
		||||
                                'BUDGET',
 | 
			
		||||
                                style: TextStyle(
 | 
			
		||||
                                    fontSize: 28, color: Styles.electricBlue),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                            budgetCategories.isEmpty
 | 
			
		||||
                                ? Padding(
 | 
			
		||||
                                    padding: const EdgeInsets.all(8.0),
 | 
			
		||||
                                    child: SizedBox(
 | 
			
		||||
                                      width: screen.width * 0.8,
 | 
			
		||||
                                      child: Column(
 | 
			
		||||
                                        children: [
 | 
			
		||||
                                          const Padding(
 | 
			
		||||
                                            padding: EdgeInsets.all(8.0),
 | 
			
		||||
                                            child: Text(
 | 
			
		||||
                                                'No budget categories created yet, add some!'),
 | 
			
		||||
                                          ),
 | 
			
		||||
                                          UiButton(
 | 
			
		||||
                                            onPressed:
 | 
			
		||||
                                                ref.watch(budgetProvider) ==
 | 
			
		||||
                                                        null
 | 
			
		||||
                                                    ? null
 | 
			
		||||
                                                    : () => showDialog(
 | 
			
		||||
                                                          context: context,
 | 
			
		||||
                                                          builder: (context) => Dialog(
 | 
			
		||||
                                                              shape: Styles
 | 
			
		||||
                                                                  .dialogShape,
 | 
			
		||||
                                                              backgroundColor:
 | 
			
		||||
                                                                  Styles
 | 
			
		||||
                                                                      .dialogColor,
 | 
			
		||||
                                                              child:
 | 
			
		||||
                                                                  const BudgetCategoryDialog()),
 | 
			
		||||
                                                        ),
 | 
			
		||||
                                            text: 'Add Category',
 | 
			
		||||
                                          ),
 | 
			
		||||
                                        ],
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  )
 | 
			
		||||
                                : Padding(
 | 
			
		||||
                                    padding: const EdgeInsets.symmetric(
 | 
			
		||||
                                        horizontal: 2.0, vertical: 4.0),
 | 
			
		||||
                                    child: SizedBox(
 | 
			
		||||
                                      height: screen.height * 0.36,
 | 
			
		||||
                                      child: Scrollbar(
 | 
			
		||||
                                        controller: budgetListScrollController,
 | 
			
		||||
                                        thumbVisibility: true,
 | 
			
		||||
                                        child: ListView(
 | 
			
		||||
                                          physics:
 | 
			
		||||
                                              const BouncingScrollPhysics(),
 | 
			
		||||
                                          controller:
 | 
			
		||||
                                              budgetListScrollController,
 | 
			
		||||
                                          shrinkWrap: true,
 | 
			
		||||
                                          children: [
 | 
			
		||||
                                            ...budgetCategories
 | 
			
		||||
                                                .mapIndexed((i, category) {
 | 
			
		||||
                                              return BudgetCategoryBar(
 | 
			
		||||
                                                budgetCategory: category,
 | 
			
		||||
                                                currentAmount:
 | 
			
		||||
                                                    budgetCategoryNetMap[
 | 
			
		||||
                                                        category.id]!,
 | 
			
		||||
                                                index: i,
 | 
			
		||||
                                              );
 | 
			
		||||
                                            }).toList(),
 | 
			
		||||
                                            const SizedBox(height: 20),
 | 
			
		||||
                                            Row(
 | 
			
		||||
                                              mainAxisAlignment:
 | 
			
		||||
                                                  MainAxisAlignment.center,
 | 
			
		||||
                                              children: [
 | 
			
		||||
                                                SizedBox(
 | 
			
		||||
                                                  width: 140,
 | 
			
		||||
                                                  child: ElevatedButton(
 | 
			
		||||
                                                    onPressed: ref.watch(
 | 
			
		||||
                                                                budgetProvider) ==
 | 
			
		||||
                                                            null
 | 
			
		||||
                                                        ? null
 | 
			
		||||
                                                        : () => showDialog(
 | 
			
		||||
                                                              context: context,
 | 
			
		||||
                                                              builder: (context) => Dialog(
 | 
			
		||||
                                                                  shape: Styles
 | 
			
		||||
                                                                      .dialogShape,
 | 
			
		||||
                                                                  backgroundColor:
 | 
			
		||||
                                                                      Styles
 | 
			
		||||
                                                                          .dialogColor,
 | 
			
		||||
                                                                  child:
 | 
			
		||||
                                                                      const BudgetCategoryDialog()),
 | 
			
		||||
                                                            ),
 | 
			
		||||
                                                    child: const Text(
 | 
			
		||||
                                                        'Add Category'),
 | 
			
		||||
                                                  ),
 | 
			
		||||
                                                ),
 | 
			
		||||
                                              ],
 | 
			
		||||
                                            ),
 | 
			
		||||
                                            const SizedBox(height: 20),
 | 
			
		||||
                                          ],
 | 
			
		||||
                                        ),
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ),
 | 
			
		||||
                          ],
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                    const Spacer(),
 | 
			
		||||
                    Row(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Expanded(
 | 
			
		||||
                          child: Padding(
 | 
			
		||||
                            padding:
 | 
			
		||||
                                const EdgeInsets.only(left: 20.0, right: 15.0),
 | 
			
		||||
                            child: Container(
 | 
			
		||||
                              height: 70,
 | 
			
		||||
                              decoration: BoxDecoration(
 | 
			
		||||
                                color: Styles.seaweedGreen,
 | 
			
		||||
                                borderRadius: BorderRadius.circular(15.0),
 | 
			
		||||
                              ),
 | 
			
		||||
                              child: InkWell(
 | 
			
		||||
                                child: const Center(
 | 
			
		||||
                                  child: Padding(
 | 
			
		||||
                                    padding: EdgeInsets.symmetric(
 | 
			
		||||
                                        horizontal: 20.0, vertical: 14.0),
 | 
			
		||||
                                    child: FittedBox(
 | 
			
		||||
                                      child: Text(
 | 
			
		||||
                                        'Transaction History',
 | 
			
		||||
                                        textAlign: TextAlign.center,
 | 
			
		||||
                                        style: TextStyle(fontSize: 25),
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
                                onTap: () {
 | 
			
		||||
                                  Navigator.push(
 | 
			
		||||
                                      context,
 | 
			
		||||
                                      MaterialPageRoute(
 | 
			
		||||
                                          builder: (context) =>
 | 
			
		||||
                                              const TransactionsListview()));
 | 
			
		||||
                                },
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                        Padding(
 | 
			
		||||
                          padding: const EdgeInsets.only(right: 20.0),
 | 
			
		||||
                          child: Container(
 | 
			
		||||
                            decoration: BoxDecoration(
 | 
			
		||||
                              color: Styles.purpleNurple,
 | 
			
		||||
                              borderRadius: BorderRadius.circular(40.0),
 | 
			
		||||
                            ),
 | 
			
		||||
                            height: 80,
 | 
			
		||||
                            width: 80,
 | 
			
		||||
                            child: IconButton(
 | 
			
		||||
                              icon: const Icon(
 | 
			
		||||
                                Icons.add,
 | 
			
		||||
                                size: 48,
 | 
			
		||||
                                color: Styles.lavender,
 | 
			
		||||
                              ),
 | 
			
		||||
                              onPressed: () {
 | 
			
		||||
                                showDialog(
 | 
			
		||||
                                  context: context,
 | 
			
		||||
                                  builder: (BuildContext context) {
 | 
			
		||||
                                    return Dialog(
 | 
			
		||||
                                        backgroundColor: Styles.dialogColor,
 | 
			
		||||
                                        shape: Styles.dialogShape,
 | 
			
		||||
                                        child: const TransactionDialog());
 | 
			
		||||
                                  },
 | 
			
		||||
                                );
 | 
			
		||||
                              },
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                    const Spacer(),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            )
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
        Align(
 | 
			
		||||
          alignment: Alignment.topRight,
 | 
			
		||||
          child: Padding(
 | 
			
		||||
            padding: const EdgeInsets.only(top: 5.0, right: 5.0),
 | 
			
		||||
            child: IconButton(
 | 
			
		||||
              icon: const Icon(Icons.loop),
 | 
			
		||||
              color: Styles.seaweedGreen,
 | 
			
		||||
              onPressed: () =>
 | 
			
		||||
                  ref.read(dashboardProvider.notifier).fetchDashboard(),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        IgnorePointer(
 | 
			
		||||
          child: AnimatedOpacity(
 | 
			
		||||
            duration: const Duration(milliseconds: 150),
 | 
			
		||||
            opacity: ref.watch(loadingStateProvider) ? 0.75 : 0.0,
 | 
			
		||||
            child: Container(
 | 
			
		||||
              color: Colors.black12,
 | 
			
		||||
              height: screen.height,
 | 
			
		||||
              width: screen.width,
 | 
			
		||||
              child: const Center(
 | 
			
		||||
                child: CircularProgressIndicator(
 | 
			
		||||
                  color: Styles.lavender,
 | 
			
		||||
                  strokeWidth: 2.5,
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        )
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,302 +0,0 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
import 'package:helpers/helpers/misc_build/build_media.dart';
 | 
			
		||||
import 'package:rluv/features/budget/widgets/transaction_list_item.dart';
 | 
			
		||||
import 'package:rluv/global/styles.dart';
 | 
			
		||||
import 'package:rluv/models/transaction_model.dart';
 | 
			
		||||
 | 
			
		||||
import '../../../global/store.dart';
 | 
			
		||||
import '../../../models/budget_category_model.dart';
 | 
			
		||||
 | 
			
		||||
class TransactionsListview extends ConsumerStatefulWidget {
 | 
			
		||||
  const TransactionsListview({super.key});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ConsumerState<TransactionsListview> createState() =>
 | 
			
		||||
      _TransactionsListviewState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _TransactionsListviewState extends ConsumerState<TransactionsListview> {
 | 
			
		||||
  final scaffoldKey = GlobalKey<ScaffoldState>();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    // TODO: implement initState
 | 
			
		||||
    WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
			
		||||
      ref.read(selectedTransactionSortProvider.notifier).state =
 | 
			
		||||
          TransactionSort.all;
 | 
			
		||||
      ref.read(selectedSortDateProvider.notifier).state = SortDate.decending;
 | 
			
		||||
    });
 | 
			
		||||
    super.initState();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final budgetCategories = ref.watch(budgetCategoriesProvider);
 | 
			
		||||
    if (ref.read(selectedCategory) == null && budgetCategories.isNotEmpty) {
 | 
			
		||||
      WidgetsBinding.instance.addPostFrameCallback((_) =>
 | 
			
		||||
          ref.read(selectedCategory.notifier).state = budgetCategories.first);
 | 
			
		||||
    }
 | 
			
		||||
    final sortType = ref.watch(selectedTransactionSortProvider);
 | 
			
		||||
    final sortDate = ref.watch(selectedSortDateProvider);
 | 
			
		||||
    List<Transaction> transactions = [...ref.watch(transactionsProvider)];
 | 
			
		||||
    // Removes transactions by category
 | 
			
		||||
    switch (sortType) {
 | 
			
		||||
      case TransactionSort.income:
 | 
			
		||||
        transactions = transactions
 | 
			
		||||
            .where(
 | 
			
		||||
              (element) => element.type == TransactionType.income,
 | 
			
		||||
            )
 | 
			
		||||
            .toList();
 | 
			
		||||
        break;
 | 
			
		||||
      case TransactionSort.expense:
 | 
			
		||||
        transactions = transactions
 | 
			
		||||
            .where(
 | 
			
		||||
              (element) => element.type == TransactionType.expense,
 | 
			
		||||
            )
 | 
			
		||||
            .toList();
 | 
			
		||||
        break;
 | 
			
		||||
      case TransactionSort.category:
 | 
			
		||||
        if (ref.read(selectedCategory) != null) {
 | 
			
		||||
          transactions = transactions
 | 
			
		||||
              .where(
 | 
			
		||||
                (element) =>
 | 
			
		||||
                    element.budgetCategoryId == ref.read(selectedCategory)!.id,
 | 
			
		||||
              )
 | 
			
		||||
              .toList();
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      case TransactionSort.all:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    // Sorts transactions by date
 | 
			
		||||
    switch (sortDate) {
 | 
			
		||||
      case SortDate.ascending:
 | 
			
		||||
        transactions.sort((a, b) => a.date.compareTo(b.date));
 | 
			
		||||
        break;
 | 
			
		||||
      case SortDate.decending:
 | 
			
		||||
        transactions.sort((a, b) => b.date.compareTo(a.date));
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    WidgetsBinding.instance.addPostFrameCallback((_) =>
 | 
			
		||||
        ref.read(transactionHistoryListProvider.notifier).state = transactions);
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      endDrawer: Drawer(
 | 
			
		||||
        backgroundColor: Styles.purpleNurple,
 | 
			
		||||
        child: SafeArea(
 | 
			
		||||
          child: Column(
 | 
			
		||||
            children: [
 | 
			
		||||
              SizedBox(height: BuildMedia(context).height * 0.15),
 | 
			
		||||
              DropdownButton<TransactionSort>(
 | 
			
		||||
                  dropdownColor: Styles.lavender,
 | 
			
		||||
                  value: ref.watch(selectedTransactionSortProvider),
 | 
			
		||||
                  items: TransactionSort.values
 | 
			
		||||
                      .map(
 | 
			
		||||
                        (e) => DropdownMenuItem(
 | 
			
		||||
                          value: e,
 | 
			
		||||
                          child: Row(
 | 
			
		||||
                            mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                            children: [
 | 
			
		||||
                              sortTypeToIcon(e),
 | 
			
		||||
                              const SizedBox(width: 12),
 | 
			
		||||
                              Text(e.name),
 | 
			
		||||
                            ],
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      )
 | 
			
		||||
                      .toList(),
 | 
			
		||||
                  onChanged: (TransactionSort? value) {
 | 
			
		||||
                    if (value != null) {
 | 
			
		||||
                      ref.read(selectedTransactionSortProvider.notifier).state =
 | 
			
		||||
                          value;
 | 
			
		||||
                    }
 | 
			
		||||
                  }),
 | 
			
		||||
              const SizedBox(height: 20),
 | 
			
		||||
              DropdownButton<SortDate>(
 | 
			
		||||
                  dropdownColor: Styles.lavender,
 | 
			
		||||
                  value: ref.watch(selectedSortDateProvider),
 | 
			
		||||
                  items: SortDate.values
 | 
			
		||||
                      .map(
 | 
			
		||||
                        (e) => DropdownMenuItem(
 | 
			
		||||
                          value: e,
 | 
			
		||||
                          child: Row(
 | 
			
		||||
                            mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                            children: [
 | 
			
		||||
                              sortDateToIcon(e),
 | 
			
		||||
                              const SizedBox(width: 12),
 | 
			
		||||
                              Text(e.name),
 | 
			
		||||
                            ],
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      )
 | 
			
		||||
                      .toList(),
 | 
			
		||||
                  onChanged: (SortDate? value) {
 | 
			
		||||
                    if (value != null) {
 | 
			
		||||
                      ref.read(selectedSortDateProvider.notifier).state = value;
 | 
			
		||||
                    }
 | 
			
		||||
                  }),
 | 
			
		||||
              const SizedBox(height: 20),
 | 
			
		||||
              if (ref.read(selectedTransactionSortProvider) ==
 | 
			
		||||
                  TransactionSort.category)
 | 
			
		||||
                DropdownButton<BudgetCategory>(
 | 
			
		||||
                  dropdownColor: Styles.lavender,
 | 
			
		||||
                  value: ref.watch(selectedCategory),
 | 
			
		||||
                  items: budgetCategories
 | 
			
		||||
                      .map(
 | 
			
		||||
                        (e) => DropdownMenuItem(
 | 
			
		||||
                          value: e,
 | 
			
		||||
                          child: Row(
 | 
			
		||||
                            mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                            children: [
 | 
			
		||||
                              Container(
 | 
			
		||||
                                height: 18,
 | 
			
		||||
                                width: 18,
 | 
			
		||||
                                decoration: BoxDecoration(
 | 
			
		||||
                                  shape: BoxShape.circle,
 | 
			
		||||
                                  border: Border.all(
 | 
			
		||||
                                      color: Colors.black, width: 1.5),
 | 
			
		||||
                                  color: e.color,
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                              const SizedBox(width: 12),
 | 
			
		||||
                              Text(e.name),
 | 
			
		||||
                            ],
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      )
 | 
			
		||||
                      .toList(),
 | 
			
		||||
                  onChanged: (BudgetCategory? value) {
 | 
			
		||||
                    if (value != null) {
 | 
			
		||||
                      ref.read(selectedCategory.notifier).state = value;
 | 
			
		||||
                    }
 | 
			
		||||
                  },
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
      key: scaffoldKey,
 | 
			
		||||
      backgroundColor: Styles.purpleNurple,
 | 
			
		||||
      body: SafeArea(
 | 
			
		||||
        child: Stack(
 | 
			
		||||
          children: [
 | 
			
		||||
            if (transactions.isEmpty) ...[
 | 
			
		||||
              Align(
 | 
			
		||||
                alignment: Alignment.topLeft,
 | 
			
		||||
                child: Padding(
 | 
			
		||||
                  padding: const EdgeInsets.only(left: 8.0, top: 16.0),
 | 
			
		||||
                  child: IconButton(
 | 
			
		||||
                      icon: const Icon(Icons.chevron_left),
 | 
			
		||||
                      onPressed: () => Navigator.pop(context)),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              const Center(
 | 
			
		||||
                child: Text('No transactions'),
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
            if (transactions.isNotEmpty)
 | 
			
		||||
              Column(
 | 
			
		||||
                children: [
 | 
			
		||||
                  Row(mainAxisAlignment: MainAxisAlignment.center, children: [
 | 
			
		||||
                    Padding(
 | 
			
		||||
                      padding: const EdgeInsets.only(left: 8.0, top: 16.0),
 | 
			
		||||
                      child: IconButton(
 | 
			
		||||
                          icon: const Icon(Icons.chevron_left),
 | 
			
		||||
                          onPressed: () => Navigator.pop(context)),
 | 
			
		||||
                    ),
 | 
			
		||||
                    const Spacer(),
 | 
			
		||||
                    const Padding(
 | 
			
		||||
                      padding:
 | 
			
		||||
                          EdgeInsets.symmetric(vertical: 18.0, horizontal: 0.0),
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        'Transaction History',
 | 
			
		||||
                        style: TextStyle(fontSize: 28),
 | 
			
		||||
                        textAlign: TextAlign.center,
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                    const Spacer(),
 | 
			
		||||
                    Padding(
 | 
			
		||||
                      padding: const EdgeInsets.only(left: 8.0, top: 16.0),
 | 
			
		||||
                      child: IconButton(
 | 
			
		||||
                          icon: const Icon(Icons.sort),
 | 
			
		||||
                          onPressed: () {
 | 
			
		||||
                            toggleDrawer();
 | 
			
		||||
                          }),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ]),
 | 
			
		||||
                  Expanded(
 | 
			
		||||
                    child: ListView.builder(
 | 
			
		||||
                      physics: const BouncingScrollPhysics(),
 | 
			
		||||
                      itemCount: transactions.length,
 | 
			
		||||
                      itemBuilder: (BuildContext context, int index) {
 | 
			
		||||
                        return TransactionListItem(
 | 
			
		||||
                          index: index,
 | 
			
		||||
                        );
 | 
			
		||||
                      },
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void toggleDrawer() {
 | 
			
		||||
    if (scaffoldKey.currentState != null &&
 | 
			
		||||
        scaffoldKey.currentState!.isDrawerOpen) {
 | 
			
		||||
      scaffoldKey.currentState!.openDrawer();
 | 
			
		||||
    } else if (scaffoldKey.currentState != null) {
 | 
			
		||||
      scaffoldKey.currentState!.openEndDrawer();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum TransactionSort {
 | 
			
		||||
  all,
 | 
			
		||||
  income,
 | 
			
		||||
  expense,
 | 
			
		||||
  category,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum SortDate {
 | 
			
		||||
  decending,
 | 
			
		||||
  ascending,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Icon sortTypeToIcon(TransactionSort s) {
 | 
			
		||||
  switch (s) {
 | 
			
		||||
    case TransactionSort.all:
 | 
			
		||||
      return const Icon(Icons.donut_large);
 | 
			
		||||
    case TransactionSort.income:
 | 
			
		||||
      return const Icon(Icons.attach_money);
 | 
			
		||||
    case TransactionSort.expense:
 | 
			
		||||
      return const Icon(Icons.shopping_bag);
 | 
			
		||||
    case TransactionSort.category:
 | 
			
		||||
      return const Icon(Icons.sort);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Icon sortDateToIcon(SortDate s) {
 | 
			
		||||
  switch (s) {
 | 
			
		||||
    case SortDate.ascending:
 | 
			
		||||
      return const Icon(Icons.arrow_circle_up);
 | 
			
		||||
    case SortDate.decending:
 | 
			
		||||
      return const Icon(Icons.arrow_circle_down);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
final selectedTransactionSortProvider = StateProvider<TransactionSort>(
 | 
			
		||||
  (ref) => TransactionSort.all,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
final selectedSortDateProvider =
 | 
			
		||||
    StateProvider<SortDate>((ref) => SortDate.decending);
 | 
			
		||||
 | 
			
		||||
final transactionHistoryListProvider = StateProvider<List<Transaction>>(
 | 
			
		||||
  (ref) => [],
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
final selectedCategory = StateProvider<BudgetCategory?>((ref) => null);
 | 
			
		||||
@ -1,354 +0,0 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
import 'package:helpers/helpers/misc_build/build_media.dart';
 | 
			
		||||
import 'package:helpers/helpers/print.dart';
 | 
			
		||||
import 'package:rluv/global/styles.dart';
 | 
			
		||||
import 'package:rluv/models/budget.dart';
 | 
			
		||||
import 'package:rluv/models/budget_category_model.dart';
 | 
			
		||||
 | 
			
		||||
import '../../../global/api.dart';
 | 
			
		||||
import '../../../global/store.dart';
 | 
			
		||||
import '../../../global/utils.dart';
 | 
			
		||||
import '../../../global/widgets/ui_button.dart';
 | 
			
		||||
 | 
			
		||||
class BudgetCategoryDialog extends ConsumerStatefulWidget {
 | 
			
		||||
  const BudgetCategoryDialog({super.key, this.category});
 | 
			
		||||
 | 
			
		||||
  final BudgetCategory? category;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ConsumerState<BudgetCategoryDialog> createState() =>
 | 
			
		||||
      _AddBudgetCategoryDialogState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _AddBudgetCategoryDialogState
 | 
			
		||||
    extends ConsumerState<BudgetCategoryDialog> {
 | 
			
		||||
  late final Budget? budget;
 | 
			
		||||
  final categoryNameController = TextEditingController();
 | 
			
		||||
  final amountController = TextEditingController();
 | 
			
		||||
 | 
			
		||||
  final categoryFocusNode = FocusNode();
 | 
			
		||||
  final amountFocusNode = FocusNode();
 | 
			
		||||
 | 
			
		||||
  final colors = Styles.curatedColors;
 | 
			
		||||
  int selectedColorIndex = -1;
 | 
			
		||||
 | 
			
		||||
  final formKey = GlobalKey<FormState>();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    budget = ref.read(budgetProvider);
 | 
			
		||||
    WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
			
		||||
      categoryFocusNode.requestFocus();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (widget.category != null) {
 | 
			
		||||
      categoryNameController.text = widget.category!.name;
 | 
			
		||||
      amountController.text = widget.category!.amount.toString();
 | 
			
		||||
      selectedColorIndex = colors.indexOf(widget.category!.color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    super.initState();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (budget == null) {
 | 
			
		||||
      WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
			
		||||
        Navigator.pop(context);
 | 
			
		||||
        showSnack(
 | 
			
		||||
          ref: ref,
 | 
			
		||||
          text: 'Could not get your budget',
 | 
			
		||||
          type: SnackType.error,
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
      return Container();
 | 
			
		||||
    }
 | 
			
		||||
    return SizedBox(
 | 
			
		||||
      width: BuildMedia(context).width * Styles.dialogScreenWidthFactor,
 | 
			
		||||
      child: Stack(
 | 
			
		||||
        children: [
 | 
			
		||||
          Padding(
 | 
			
		||||
            padding:
 | 
			
		||||
                const EdgeInsets.symmetric(vertical: 18.0, horizontal: 32.0),
 | 
			
		||||
            child: Form(
 | 
			
		||||
              key: formKey,
 | 
			
		||||
              child: Column(
 | 
			
		||||
                mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                children: [
 | 
			
		||||
                  Padding(
 | 
			
		||||
                    padding: const EdgeInsets.only(bottom: 18.0),
 | 
			
		||||
                    child: Text(widget.category == null
 | 
			
		||||
                        ? 'Add Category:'
 | 
			
		||||
                        : 'Edit Category'),
 | 
			
		||||
                  ),
 | 
			
		||||
                  Row(
 | 
			
		||||
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | 
			
		||||
                    children: [
 | 
			
		||||
                      const Text('Name:'),
 | 
			
		||||
                      const SizedBox(width: 30),
 | 
			
		||||
                      Container(
 | 
			
		||||
                        width: 150,
 | 
			
		||||
                        height: 40,
 | 
			
		||||
                        decoration: Styles.boxLavenderBubble,
 | 
			
		||||
                        child: TextFormField(
 | 
			
		||||
                          validator: (text) {
 | 
			
		||||
                            if (text == null || text.isEmpty) {
 | 
			
		||||
                              return 'Cannot be blank';
 | 
			
		||||
                            }
 | 
			
		||||
                            return null;
 | 
			
		||||
                          },
 | 
			
		||||
                          focusNode: categoryFocusNode,
 | 
			
		||||
                          textAlign: TextAlign.center,
 | 
			
		||||
                          cursorColor: Styles.blushingPink,
 | 
			
		||||
                          controller: categoryNameController,
 | 
			
		||||
                          decoration: Styles.inputLavenderBubble(),
 | 
			
		||||
                          onFieldSubmitted: (_) {
 | 
			
		||||
                            amountFocusNode.requestFocus();
 | 
			
		||||
                          },
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                  const SizedBox(height: 18),
 | 
			
		||||
                  Row(
 | 
			
		||||
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | 
			
		||||
                    children: [
 | 
			
		||||
                      const Text('Amount:'),
 | 
			
		||||
                      const SizedBox(width: 30),
 | 
			
		||||
                      Container(
 | 
			
		||||
                        width: 150,
 | 
			
		||||
                        height: 40,
 | 
			
		||||
                        decoration: Styles.boxLavenderBubble,
 | 
			
		||||
                        child: TextFormField(
 | 
			
		||||
                          validator: (text) {
 | 
			
		||||
                            try {
 | 
			
		||||
                              if (text == null || text.isEmpty) {
 | 
			
		||||
                                return 'Invalid Amount';
 | 
			
		||||
                              }
 | 
			
		||||
                              final amount = double.tryParse(text);
 | 
			
		||||
                              if (amount == null) {
 | 
			
		||||
                                return 'Not A Number';
 | 
			
		||||
                              }
 | 
			
		||||
                              if (amount < 0) {
 | 
			
		||||
                                return 'Not Positive';
 | 
			
		||||
                              }
 | 
			
		||||
                              return null;
 | 
			
		||||
                            } catch (err) {
 | 
			
		||||
                              return 'Invalid input';
 | 
			
		||||
                            }
 | 
			
		||||
                          },
 | 
			
		||||
                          focusNode: amountFocusNode,
 | 
			
		||||
                          keyboardType: TextInputType.number,
 | 
			
		||||
                          textAlign: TextAlign.center,
 | 
			
		||||
                          cursorColor: Styles.blushingPink,
 | 
			
		||||
                          controller: amountController,
 | 
			
		||||
                          decoration: Styles.inputLavenderBubble(),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                  Padding(
 | 
			
		||||
                    padding: const EdgeInsets.symmetric(
 | 
			
		||||
                        vertical: 6.0, horizontal: 16.0),
 | 
			
		||||
                    child: SizedBox(
 | 
			
		||||
                      height: 76,
 | 
			
		||||
                      child: ListView.builder(
 | 
			
		||||
                        itemCount: colors.length,
 | 
			
		||||
                        scrollDirection: Axis.horizontal,
 | 
			
		||||
                        itemBuilder: (BuildContext context, int index) {
 | 
			
		||||
                          return GestureDetector(
 | 
			
		||||
                            onTap: () {
 | 
			
		||||
                              if (selectedColorIndex != index) {
 | 
			
		||||
                                setState(() => selectedColorIndex = index);
 | 
			
		||||
                              } else {
 | 
			
		||||
                                setState(() => selectedColorIndex = -1);
 | 
			
		||||
                              }
 | 
			
		||||
                              printBlue(selectedColorIndex);
 | 
			
		||||
                            },
 | 
			
		||||
                            child: Padding(
 | 
			
		||||
                              padding: const EdgeInsets.all(8.0),
 | 
			
		||||
                              child: AnimatedContainer(
 | 
			
		||||
                                decoration: selectedColorIndex == index
 | 
			
		||||
                                    ? BoxDecoration(
 | 
			
		||||
                                        borderRadius:
 | 
			
		||||
                                            BorderRadius.circular(15.0),
 | 
			
		||||
                                        border: Border.all(
 | 
			
		||||
                                          color: Styles.washedStone,
 | 
			
		||||
                                          width: 2.0,
 | 
			
		||||
                                        ),
 | 
			
		||||
                                      )
 | 
			
		||||
                                    : BoxDecoration(
 | 
			
		||||
                                        borderRadius:
 | 
			
		||||
                                            BorderRadius.circular(5.0),
 | 
			
		||||
                                        border: Border.all(
 | 
			
		||||
                                          color: Colors.transparent,
 | 
			
		||||
                                          width: 2.0,
 | 
			
		||||
                                        ),
 | 
			
		||||
                                      ),
 | 
			
		||||
                                duration: const Duration(milliseconds: 400),
 | 
			
		||||
                                child: Padding(
 | 
			
		||||
                                  padding: const EdgeInsets.all(8.0),
 | 
			
		||||
                                  child: Stack(
 | 
			
		||||
                                    alignment: Alignment.center,
 | 
			
		||||
                                    children: [
 | 
			
		||||
                                      Container(
 | 
			
		||||
                                        height: 40,
 | 
			
		||||
                                        width: 40,
 | 
			
		||||
                                        decoration: const BoxDecoration(
 | 
			
		||||
                                          shape: BoxShape.circle,
 | 
			
		||||
                                        ),
 | 
			
		||||
                                      ),
 | 
			
		||||
                                      Container(
 | 
			
		||||
                                        height: 40,
 | 
			
		||||
                                        width: 40,
 | 
			
		||||
                                        decoration: BoxDecoration(
 | 
			
		||||
                                          color: colors[index],
 | 
			
		||||
                                          shape: BoxShape.circle,
 | 
			
		||||
                                          border: Border.all(
 | 
			
		||||
                                              color: Colors.black, width: 1.5),
 | 
			
		||||
                                        ),
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ],
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                          );
 | 
			
		||||
                        },
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  if (widget.category != null && widget.category?.id != null)
 | 
			
		||||
                    Padding(
 | 
			
		||||
                      padding: const EdgeInsets.only(bottom: 16.0),
 | 
			
		||||
                      child: UiButton(
 | 
			
		||||
                        showLoading: true,
 | 
			
		||||
                        color: Styles.expensesRed,
 | 
			
		||||
                        text: 'DELETE',
 | 
			
		||||
                        onPressed: () {
 | 
			
		||||
                          if (formKey.currentState != null &&
 | 
			
		||||
                              formKey.currentState!.validate()) {
 | 
			
		||||
                            removeCategory().then((success) {
 | 
			
		||||
                              Navigator.pop(context);
 | 
			
		||||
                              if (success) {
 | 
			
		||||
                                showSnack(
 | 
			
		||||
                                  ref: ref,
 | 
			
		||||
                                  text: 'Budget Category Removed',
 | 
			
		||||
                                  type: SnackType.success,
 | 
			
		||||
                                );
 | 
			
		||||
                              } else {
 | 
			
		||||
                                showSnack(
 | 
			
		||||
                                  ref: ref,
 | 
			
		||||
                                  text: 'Unable to Remove Budget Category',
 | 
			
		||||
                                  type: SnackType.error,
 | 
			
		||||
                                );
 | 
			
		||||
                              }
 | 
			
		||||
                            });
 | 
			
		||||
                          }
 | 
			
		||||
                        },
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  UiButton(
 | 
			
		||||
                    showLoading: true,
 | 
			
		||||
                    color: Styles.lavender,
 | 
			
		||||
                    text: 'SAVE',
 | 
			
		||||
                    onPressed: () {
 | 
			
		||||
                      if (formKey.currentState != null &&
 | 
			
		||||
                          formKey.currentState!.validate()) {
 | 
			
		||||
                        submitCategory(colors[selectedColorIndex]).then((_) {
 | 
			
		||||
                          Navigator.pop(context);
 | 
			
		||||
                          showSnack(
 | 
			
		||||
                            ref: ref,
 | 
			
		||||
                            text: 'Budget Category Added!',
 | 
			
		||||
                            type: SnackType.success,
 | 
			
		||||
                          );
 | 
			
		||||
                        });
 | 
			
		||||
                      }
 | 
			
		||||
                    },
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          IconButton(
 | 
			
		||||
            icon: const Icon(Icons.close),
 | 
			
		||||
            onPressed: () => Navigator.pop(context),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future submitCategory(Color categoryColor) async {
 | 
			
		||||
    if (formKey.currentState != null && !formKey.currentState!.validate()) {
 | 
			
		||||
      printPink('Failed validation');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    Map<String, dynamic>? budgetData;
 | 
			
		||||
    bool success = false;
 | 
			
		||||
    if (widget.category == null) {
 | 
			
		||||
      final newBudget = BudgetCategory(
 | 
			
		||||
        id: null,
 | 
			
		||||
        amount: double.parse(amountController.text),
 | 
			
		||||
        budgetId: budget!.id!,
 | 
			
		||||
        color: categoryColor,
 | 
			
		||||
        name: categoryNameController.text,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      budgetData = await ref
 | 
			
		||||
          .read(apiProvider.notifier)
 | 
			
		||||
          .post(path: 'budget_category', data: newBudget.toJson());
 | 
			
		||||
      success = budgetData != null ? budgetData['success'] as bool : false;
 | 
			
		||||
      if (success) {
 | 
			
		||||
        ref
 | 
			
		||||
            .read(dashboardProvider.notifier)
 | 
			
		||||
            .add({'budget_categories': budgetData});
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      final newBudget =
 | 
			
		||||
          BudgetCategory.copyWith(category: widget.category!, data: {
 | 
			
		||||
        'amount': double.parse(amountController.text),
 | 
			
		||||
        'color': categoryColor,
 | 
			
		||||
        'name': categoryNameController.text
 | 
			
		||||
      });
 | 
			
		||||
      budgetData = await ref
 | 
			
		||||
          .read(apiProvider.notifier)
 | 
			
		||||
          .put(path: 'budget_category', data: newBudget.toJson());
 | 
			
		||||
      success = budgetData != null ? budgetData['success'] as bool : false;
 | 
			
		||||
      if (success) {
 | 
			
		||||
        ref
 | 
			
		||||
            .read(dashboardProvider.notifier)
 | 
			
		||||
            .update({'budget_categories': budgetData});
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (success) {
 | 
			
		||||
      showSnack(
 | 
			
		||||
          ref: ref,
 | 
			
		||||
          text: widget.category == null
 | 
			
		||||
              ? 'Added budget category!'
 | 
			
		||||
              : 'Updated category!',
 | 
			
		||||
          type: SnackType.error);
 | 
			
		||||
    } else {
 | 
			
		||||
      showSnack(
 | 
			
		||||
          ref: ref,
 | 
			
		||||
          text: widget.category == null
 | 
			
		||||
              ? 'Could not add budget category'
 | 
			
		||||
              : 'Could not update category',
 | 
			
		||||
          type: SnackType.error);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<bool> removeCategory() async {
 | 
			
		||||
    final res = await ref
 | 
			
		||||
        .read(apiProvider.notifier)
 | 
			
		||||
        .delete(path: 'budget_category', data: {'id': widget.category!.id});
 | 
			
		||||
    final success = res != null ? res['success'] as bool : false;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      ref
 | 
			
		||||
          .read(dashboardProvider.notifier)
 | 
			
		||||
          .removeWithId('budget_categories', widget.category!.id!);
 | 
			
		||||
    }
 | 
			
		||||
    return success;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,329 +0,0 @@
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
import 'package:helpers/helpers/misc_build/build_media.dart';
 | 
			
		||||
import 'package:rluv/global/utils.dart';
 | 
			
		||||
 | 
			
		||||
import '../../../global/api.dart';
 | 
			
		||||
import '../../../global/store.dart';
 | 
			
		||||
import '../../../global/styles.dart';
 | 
			
		||||
import '../../../global/widgets/ui_button.dart';
 | 
			
		||||
import '../../../models/budget_category_model.dart';
 | 
			
		||||
import '../../../models/transaction_model.dart';
 | 
			
		||||
import '../../../models/user.dart';
 | 
			
		||||
 | 
			
		||||
class TransactionDialog extends ConsumerStatefulWidget {
 | 
			
		||||
  const TransactionDialog({super.key, this.transaction});
 | 
			
		||||
 | 
			
		||||
  final Transaction? transaction;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ConsumerState<TransactionDialog> createState() =>
 | 
			
		||||
      _AddTransactionDialogState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _AddTransactionDialogState extends ConsumerState<TransactionDialog> {
 | 
			
		||||
  late DateTime selectedDate;
 | 
			
		||||
  late final TextEditingController amountController;
 | 
			
		||||
  late final TextEditingController memoController;
 | 
			
		||||
  User? user;
 | 
			
		||||
 | 
			
		||||
  final amountFocusNode = FocusNode();
 | 
			
		||||
  final memoFocusNode = FocusNode();
 | 
			
		||||
 | 
			
		||||
  TransactionType transactionType = TransactionType.expense;
 | 
			
		||||
  late BudgetCategory selectedBudgetCategory;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    if (widget.transaction != null) {
 | 
			
		||||
      amountController =
 | 
			
		||||
          TextEditingController(text: widget.transaction!.amount.toString());
 | 
			
		||||
      memoController = TextEditingController(text: widget.transaction!.memo);
 | 
			
		||||
      transactionType = widget.transaction!.type;
 | 
			
		||||
      selectedDate = widget.transaction!.date;
 | 
			
		||||
    } else {
 | 
			
		||||
      amountController = TextEditingController();
 | 
			
		||||
      memoController = TextEditingController();
 | 
			
		||||
      selectedDate = DateTime.now();
 | 
			
		||||
    }
 | 
			
		||||
    final categories = ref.read(budgetCategoriesProvider);
 | 
			
		||||
    if (categories.isNotEmpty) {
 | 
			
		||||
      selectedBudgetCategory = categories.first;
 | 
			
		||||
    }
 | 
			
		||||
    final u = ref.read(userProvider);
 | 
			
		||||
    if (u == null) {
 | 
			
		||||
      WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
          (_) => ref.read(jwtProvider.notifier).revokeToken());
 | 
			
		||||
    } else {
 | 
			
		||||
      user = u;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    super.initState();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (user == null) return Container();
 | 
			
		||||
    final List<BudgetCategory> budgetCategories =
 | 
			
		||||
        ref.read(budgetCategoriesProvider);
 | 
			
		||||
    return SizedBox(
 | 
			
		||||
        width: BuildMedia(context).width * Styles.dialogScreenWidthFactor,
 | 
			
		||||
        child: budgetCategories.isEmpty
 | 
			
		||||
            ? const Padding(
 | 
			
		||||
                padding: EdgeInsets.all(8.0),
 | 
			
		||||
                child: Text(
 | 
			
		||||
                  'Add budget categories to sort your transactions',
 | 
			
		||||
                  textAlign: TextAlign.center,
 | 
			
		||||
                ),
 | 
			
		||||
              )
 | 
			
		||||
            : Padding(
 | 
			
		||||
                padding: const EdgeInsets.all(18.0),
 | 
			
		||||
                child: Column(
 | 
			
		||||
                  mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    if (widget.transaction == null)
 | 
			
		||||
                      Padding(
 | 
			
		||||
                        padding: const EdgeInsets.only(bottom: 8.0, left: 8.0),
 | 
			
		||||
                        child: Row(children: [
 | 
			
		||||
                          if (budgetCategories.isNotEmpty)
 | 
			
		||||
                            InkWell(
 | 
			
		||||
                              onTap: transactionType == TransactionType.expense
 | 
			
		||||
                                  ? null
 | 
			
		||||
                                  : () {
 | 
			
		||||
                                      setState(() => transactionType =
 | 
			
		||||
                                          TransactionType.expense);
 | 
			
		||||
                                    },
 | 
			
		||||
                              child: AnimatedContainer(
 | 
			
		||||
                                height: 38,
 | 
			
		||||
                                width: 80,
 | 
			
		||||
                                decoration: BoxDecoration(
 | 
			
		||||
                                    borderRadius: const BorderRadius.only(
 | 
			
		||||
                                        topLeft: Radius.circular(8.0),
 | 
			
		||||
                                        topRight: Radius.circular(8.0)),
 | 
			
		||||
                                    color: transactionType ==
 | 
			
		||||
                                            TransactionType.expense
 | 
			
		||||
                                        ? Styles.lavender
 | 
			
		||||
                                        : Styles.washedStone),
 | 
			
		||||
                                duration: const Duration(milliseconds: 300),
 | 
			
		||||
                                child: const Padding(
 | 
			
		||||
                                  padding: EdgeInsets.all(8.0),
 | 
			
		||||
                                  child: Text('Expense',
 | 
			
		||||
                                      style: TextStyle(fontSize: 16)),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                          InkWell(
 | 
			
		||||
                            onTap: transactionType == TransactionType.income
 | 
			
		||||
                                ? null
 | 
			
		||||
                                : () {
 | 
			
		||||
                                    setState(() => transactionType =
 | 
			
		||||
                                        TransactionType.income);
 | 
			
		||||
                                  },
 | 
			
		||||
                            child: AnimatedContainer(
 | 
			
		||||
                              height: 38,
 | 
			
		||||
                              width: 80,
 | 
			
		||||
                              decoration: BoxDecoration(
 | 
			
		||||
                                  borderRadius: const BorderRadius.only(
 | 
			
		||||
                                      topLeft: Radius.circular(8.0),
 | 
			
		||||
                                      topRight: Radius.circular(8.0)),
 | 
			
		||||
                                  color:
 | 
			
		||||
                                      transactionType == TransactionType.income
 | 
			
		||||
                                          ? Styles.lavender
 | 
			
		||||
                                          : Styles.washedStone),
 | 
			
		||||
                              duration: const Duration(milliseconds: 300),
 | 
			
		||||
                              child: const Padding(
 | 
			
		||||
                                padding: EdgeInsets.all(8.0),
 | 
			
		||||
                                child: Text('Income',
 | 
			
		||||
                                    style: TextStyle(fontSize: 16)),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ]),
 | 
			
		||||
                      ),
 | 
			
		||||
                    Row(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        const Padding(
 | 
			
		||||
                          padding: EdgeInsets.all(8.0),
 | 
			
		||||
                          child: Text(
 | 
			
		||||
                            'Amount:',
 | 
			
		||||
                            style: TextStyle(fontSize: 18.0),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                        Container(
 | 
			
		||||
                          width: 120,
 | 
			
		||||
                          height: 40,
 | 
			
		||||
                          decoration: Styles.boxLavenderBubble,
 | 
			
		||||
                          child: TextFormField(
 | 
			
		||||
                            keyboardType: TextInputType.number,
 | 
			
		||||
                            textAlign: TextAlign.center,
 | 
			
		||||
                            style: const TextStyle(fontSize: 18.0),
 | 
			
		||||
                            cursorColor: Styles.blushingPink,
 | 
			
		||||
                            decoration: Styles.inputLavenderBubble(),
 | 
			
		||||
                            focusNode: amountFocusNode,
 | 
			
		||||
                            controller: amountController,
 | 
			
		||||
                            onFieldSubmitted: (_) {
 | 
			
		||||
                              memoFocusNode.requestFocus();
 | 
			
		||||
                            },
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                    if (budgetCategories.isEmpty)
 | 
			
		||||
                      const Padding(
 | 
			
		||||
                        padding: EdgeInsets.all(18.0),
 | 
			
		||||
                        child: Text(
 | 
			
		||||
                          'Add budget categories to sort your transactions',
 | 
			
		||||
                          textAlign: TextAlign.center,
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    if (budgetCategories.isNotEmpty)
 | 
			
		||||
                      AnimatedSwitcher(
 | 
			
		||||
                        duration: const Duration(milliseconds: 300),
 | 
			
		||||
                        child: transactionType == TransactionType.income
 | 
			
		||||
                            ? const SizedBox(height: 12.5)
 | 
			
		||||
                            : Padding(
 | 
			
		||||
                                padding: const EdgeInsets.only(left: 8.0),
 | 
			
		||||
                                child: Row(
 | 
			
		||||
                                  children: [
 | 
			
		||||
                                    const Padding(
 | 
			
		||||
                                      padding: EdgeInsets.only(right: 28.0),
 | 
			
		||||
                                      child: Text(
 | 
			
		||||
                                        'Category:',
 | 
			
		||||
                                        style: TextStyle(fontSize: 18.0),
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    DropdownButton<BudgetCategory>(
 | 
			
		||||
                                      dropdownColor: Styles.lavender,
 | 
			
		||||
                                      value: selectedBudgetCategory,
 | 
			
		||||
                                      items: budgetCategories
 | 
			
		||||
                                          .map(
 | 
			
		||||
                                            (e) => DropdownMenuItem(
 | 
			
		||||
                                              value: e,
 | 
			
		||||
                                              child: Row(
 | 
			
		||||
                                                mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                                                children: [
 | 
			
		||||
                                                  Container(
 | 
			
		||||
                                                    height: 18,
 | 
			
		||||
                                                    width: 18,
 | 
			
		||||
                                                    decoration: BoxDecoration(
 | 
			
		||||
                                                      shape: BoxShape.circle,
 | 
			
		||||
                                                      border: Border.all(
 | 
			
		||||
                                                          color: Colors.black,
 | 
			
		||||
                                                          width: 1.5),
 | 
			
		||||
                                                      color: e.color,
 | 
			
		||||
                                                    ),
 | 
			
		||||
                                                  ),
 | 
			
		||||
                                                  const SizedBox(width: 12),
 | 
			
		||||
                                                  Text(e.name),
 | 
			
		||||
                                                ],
 | 
			
		||||
                                              ),
 | 
			
		||||
                                            ),
 | 
			
		||||
                                          )
 | 
			
		||||
                                          .toList(),
 | 
			
		||||
                                      onChanged: (BudgetCategory? value) {
 | 
			
		||||
                                        if (value != null) {
 | 
			
		||||
                                          if (kDebugMode) {
 | 
			
		||||
                                            print('${value.name} selected');
 | 
			
		||||
                                          }
 | 
			
		||||
                                          setState(() =>
 | 
			
		||||
                                              selectedBudgetCategory = value);
 | 
			
		||||
                                        }
 | 
			
		||||
                                      },
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ],
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    const Padding(
 | 
			
		||||
                      padding: EdgeInsets.all(8.0),
 | 
			
		||||
                      child: Row(
 | 
			
		||||
                        children: [
 | 
			
		||||
                          Text(
 | 
			
		||||
                            'Memo',
 | 
			
		||||
                            style: TextStyle(fontSize: 18.0),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ],
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                    Container(
 | 
			
		||||
                      width: BuildMedia(context).width * 0.65,
 | 
			
		||||
                      height: 100,
 | 
			
		||||
                      decoration: Styles.boxLavenderBubble,
 | 
			
		||||
                      child: TextFormField(
 | 
			
		||||
                        style: const TextStyle(fontSize: 18.0),
 | 
			
		||||
                        maxLines: 4,
 | 
			
		||||
                        focusNode: memoFocusNode,
 | 
			
		||||
                        textAlign: TextAlign.left,
 | 
			
		||||
                        cursorColor: Styles.blushingPink,
 | 
			
		||||
                        controller: memoController,
 | 
			
		||||
                        decoration: Styles.inputLavenderBubble(),
 | 
			
		||||
                        onFieldSubmitted: (_) {},
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                    UiButton(
 | 
			
		||||
                      showLoading: true,
 | 
			
		||||
                      text: 'ADD',
 | 
			
		||||
                      color: Styles.lavender,
 | 
			
		||||
                      onPressed: () => submitTransaction().then((_) {
 | 
			
		||||
                        Navigator.pop(context);
 | 
			
		||||
                        showSnack(
 | 
			
		||||
                          ref: ref,
 | 
			
		||||
                          text: widget.transaction != null
 | 
			
		||||
                              ? 'Transaction updated!'
 | 
			
		||||
                              : 'Transaction added!',
 | 
			
		||||
                          type: SnackType.success,
 | 
			
		||||
                        );
 | 
			
		||||
                      }),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future submitTransaction() async {
 | 
			
		||||
    Map<String, dynamic>? data;
 | 
			
		||||
    if (widget.transaction != null) {
 | 
			
		||||
      data = await ref.read(apiProvider.notifier).put(
 | 
			
		||||
          path: 'transaction',
 | 
			
		||||
          data: Transaction.copyWith(widget.transaction!, {
 | 
			
		||||
            'memo': memoController.text.isNotEmpty ? memoController.text : null,
 | 
			
		||||
            'amount': double.parse(amountController.text),
 | 
			
		||||
            'budget_category_id': transactionType == TransactionType.income
 | 
			
		||||
                ? null
 | 
			
		||||
                : selectedBudgetCategory.id,
 | 
			
		||||
          }).toJson());
 | 
			
		||||
    } else {
 | 
			
		||||
      data = await ref.read(apiProvider.notifier).post(
 | 
			
		||||
          path: 'transaction',
 | 
			
		||||
          data: Transaction(
 | 
			
		||||
                  amount: double.parse(amountController.text),
 | 
			
		||||
                  createdByUserId: user!.id!,
 | 
			
		||||
                  budgetCategoryId: transactionType == TransactionType.income
 | 
			
		||||
                      ? null
 | 
			
		||||
                      : selectedBudgetCategory.id,
 | 
			
		||||
                  budgetId: user!.budgetId,
 | 
			
		||||
                  date: DateTime.now(),
 | 
			
		||||
                  type: transactionType,
 | 
			
		||||
                  memo: memoController.text.isNotEmpty
 | 
			
		||||
                      ? memoController.text
 | 
			
		||||
                      : null)
 | 
			
		||||
              .toJson());
 | 
			
		||||
    }
 | 
			
		||||
    final success = data != null ? data['success'] as bool : false;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      if (widget.transaction != null) {
 | 
			
		||||
        ref.read(dashboardProvider.notifier).update({'transactions': data});
 | 
			
		||||
      } else {
 | 
			
		||||
        ref.read(dashboardProvider.notifier).add({'transactions': data});
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      showSnack(
 | 
			
		||||
          ref: ref,
 | 
			
		||||
          text: widget.transaction != null
 | 
			
		||||
              ? 'Failed to edit transaction'
 | 
			
		||||
              : 'Failed to add transaction',
 | 
			
		||||
          type: SnackType.error);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,173 +0,0 @@
 | 
			
		||||
import 'dart:math';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:helpers/helpers/print.dart';
 | 
			
		||||
import 'package:rluv/features/budget/widgets/add_budget_category_dialog.dart';
 | 
			
		||||
import 'package:rluv/global/utils.dart';
 | 
			
		||||
import 'package:rluv/models/budget_category_model.dart';
 | 
			
		||||
 | 
			
		||||
import '../../../global/styles.dart';
 | 
			
		||||
 | 
			
		||||
class BudgetCategoryBar extends StatefulWidget {
 | 
			
		||||
  const BudgetCategoryBar(
 | 
			
		||||
      {super.key,
 | 
			
		||||
      required this.budgetCategory,
 | 
			
		||||
      required this.currentAmount,
 | 
			
		||||
      required this.index,
 | 
			
		||||
      this.height = 32,
 | 
			
		||||
      this.innerPadding = 2.5});
 | 
			
		||||
 | 
			
		||||
  final int index;
 | 
			
		||||
  final double currentAmount;
 | 
			
		||||
  final BudgetCategory budgetCategory;
 | 
			
		||||
  final double height;
 | 
			
		||||
  final double innerPadding;
 | 
			
		||||
  @override
 | 
			
		||||
  State<BudgetCategoryBar> createState() => _BudgetCategoryBarState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _BudgetCategoryBarState extends State<BudgetCategoryBar> {
 | 
			
		||||
  double percentSpent = 0.0;
 | 
			
		||||
  double setTo = 0.0;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    Future.delayed(Duration(milliseconds: min(1600, 200 * widget.index)), () {
 | 
			
		||||
      final valToSetTo = widget.currentAmount / widget.budgetCategory.amount;
 | 
			
		||||
      if (valToSetTo != setTo) {
 | 
			
		||||
        setTo = valToSetTo;
 | 
			
		||||
        setState(() => percentSpent = valToSetTo);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    final innerHeight = widget.height - widget.innerPadding * 2;
 | 
			
		||||
    final isBright =
 | 
			
		||||
        getBrightness(widget.budgetCategory.color) == Brightness.light;
 | 
			
		||||
    final textStyle = TextStyle(
 | 
			
		||||
        fontSize: 14,
 | 
			
		||||
        fontWeight: FontWeight.bold,
 | 
			
		||||
        color: widget.currentAmount > widget.budgetCategory.amount
 | 
			
		||||
            ? Styles.expensesRed
 | 
			
		||||
            : isBright
 | 
			
		||||
                ? Colors.black87
 | 
			
		||||
                : Colors.white);
 | 
			
		||||
    return InkWell(
 | 
			
		||||
      onLongPress: () async {
 | 
			
		||||
        showDialog(
 | 
			
		||||
            context: context,
 | 
			
		||||
            builder: (context) => Dialog(
 | 
			
		||||
                shape: Styles.dialogShape,
 | 
			
		||||
                backgroundColor: Styles.dialogColor,
 | 
			
		||||
                child: BudgetCategoryDialog(category: widget.budgetCategory)));
 | 
			
		||||
      },
 | 
			
		||||
      child: Padding(
 | 
			
		||||
        padding: const EdgeInsets.symmetric(vertical: 4.0),
 | 
			
		||||
        child: Row(
 | 
			
		||||
          children: [
 | 
			
		||||
            Padding(
 | 
			
		||||
              padding: const EdgeInsets.all(8.0),
 | 
			
		||||
              child: SizedBox(
 | 
			
		||||
                width: 90,
 | 
			
		||||
                child: FittedBox(
 | 
			
		||||
                  fit: BoxFit.scaleDown,
 | 
			
		||||
                  alignment: Alignment.centerLeft,
 | 
			
		||||
                  child: Text(
 | 
			
		||||
                    formatBudgetName(widget.budgetCategory.name),
 | 
			
		||||
                    style: const TextStyle(fontSize: 18.0),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            Expanded(
 | 
			
		||||
                child: Padding(
 | 
			
		||||
              padding: const EdgeInsets.only(right: 18.0),
 | 
			
		||||
              child: Stack(
 | 
			
		||||
                children: [
 | 
			
		||||
                  Container(
 | 
			
		||||
                    height: widget.height,
 | 
			
		||||
                    decoration: BoxDecoration(
 | 
			
		||||
                      color: Colors.black,
 | 
			
		||||
                      borderRadius: BorderRadius.circular(13.0),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  Padding(
 | 
			
		||||
                    padding: EdgeInsets.all(widget.innerPadding),
 | 
			
		||||
                    child: Container(
 | 
			
		||||
                      height: innerHeight,
 | 
			
		||||
                      decoration: BoxDecoration(
 | 
			
		||||
                        color: Styles.emptyBarGrey,
 | 
			
		||||
                        borderRadius: BorderRadius.circular(12.0),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  Padding(
 | 
			
		||||
                    padding: EdgeInsets.all(widget.innerPadding),
 | 
			
		||||
                    child: SizedBox(
 | 
			
		||||
                      height: innerHeight,
 | 
			
		||||
                      child: AnimatedFractionallySizedBox(
 | 
			
		||||
                        curve: Curves.easeOutSine,
 | 
			
		||||
                        heightFactor: 1.0,
 | 
			
		||||
                        widthFactor: min(1.0, max(0.08, percentSpent)),
 | 
			
		||||
                        duration: const Duration(milliseconds: 350),
 | 
			
		||||
                        child: Container(
 | 
			
		||||
                          decoration: BoxDecoration(
 | 
			
		||||
                            color: widget.budgetCategory.color,
 | 
			
		||||
                            borderRadius: BorderRadius.circular(12.0),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  SizedBox(
 | 
			
		||||
                    height: widget.height,
 | 
			
		||||
                    child: Row(
 | 
			
		||||
                      mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                      crossAxisAlignment: CrossAxisAlignment.center,
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Center(
 | 
			
		||||
                          child: Padding(
 | 
			
		||||
                            padding: const EdgeInsets.only(left: 8.0),
 | 
			
		||||
                            child: Text(
 | 
			
		||||
                                '${widget.currentAmount.currency()} / ${widget.budgetCategory.amount.currency()}',
 | 
			
		||||
                                style: textStyle),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ))
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String formatBudgetName(String name) {
 | 
			
		||||
    if (name.length < 12) {
 | 
			
		||||
      return name;
 | 
			
		||||
    }
 | 
			
		||||
    if (!name.contains(' ') || name.indexOf(' ') == name.length - 1) {
 | 
			
		||||
      return name;
 | 
			
		||||
    }
 | 
			
		||||
    printLime('here');
 | 
			
		||||
    final words = name.split(' ');
 | 
			
		||||
    int index = 0;
 | 
			
		||||
    String firstLine = words[index];
 | 
			
		||||
    while (true) {
 | 
			
		||||
      index += 1;
 | 
			
		||||
      if (index == words.length) {
 | 
			
		||||
        return '$firstLine\n${words.sublist(index).join(' ')}';
 | 
			
		||||
      }
 | 
			
		||||
      if (firstLine.length + words[index].length > 12) {
 | 
			
		||||
        return '$firstLine\n${words.sublist(index).join(' ')}';
 | 
			
		||||
      }
 | 
			
		||||
      firstLine += ' ${words[index]}';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,46 +0,0 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:helpers/helpers.dart';
 | 
			
		||||
import 'package:rluv/global/styles.dart';
 | 
			
		||||
import 'package:rluv/global/utils.dart';
 | 
			
		||||
 | 
			
		||||
class BudgetNetBar extends StatelessWidget {
 | 
			
		||||
  const BudgetNetBar(
 | 
			
		||||
      {super.key,
 | 
			
		||||
      required this.isPositive,
 | 
			
		||||
      required this.net,
 | 
			
		||||
      required this.expected});
 | 
			
		||||
 | 
			
		||||
  final bool isPositive;
 | 
			
		||||
  final double net;
 | 
			
		||||
  final double expected;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final screenWidth = BuildMedia(context).width;
 | 
			
		||||
    return Container(
 | 
			
		||||
      width: screenWidth * 0.85,
 | 
			
		||||
      decoration: BoxDecoration(
 | 
			
		||||
        borderRadius: BorderRadius.circular(10.0),
 | 
			
		||||
        color: isPositive ? Styles.incomeBlue : Styles.expensesOrange,
 | 
			
		||||
      ),
 | 
			
		||||
      child: Padding(
 | 
			
		||||
        padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 12.0),
 | 
			
		||||
        child:
 | 
			
		||||
            Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
 | 
			
		||||
          Text(
 | 
			
		||||
            isPositive ? 'Income' : 'Expenses',
 | 
			
		||||
            style: const TextStyle(
 | 
			
		||||
              fontSize: 20,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          Text(
 | 
			
		||||
            '${net.currency()} / ${expected.currency()}',
 | 
			
		||||
            style: TextStyle(
 | 
			
		||||
                fontSize: 20,
 | 
			
		||||
                color: isPositive ? Styles.incomeGreen : Styles.expensesRed),
 | 
			
		||||
          ),
 | 
			
		||||
        ]),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,202 +0,0 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
import 'package:helpers/helpers/misc_build/build_media.dart';
 | 
			
		||||
import 'package:intl/intl.dart';
 | 
			
		||||
import 'package:rluv/features/budget/screens/transactions_listview.dart';
 | 
			
		||||
import 'package:rluv/features/budget/widgets/add_transaction_dialog.dart';
 | 
			
		||||
import 'package:rluv/global/api.dart';
 | 
			
		||||
import 'package:rluv/global/utils.dart';
 | 
			
		||||
import 'package:rluv/models/transaction_model.dart';
 | 
			
		||||
 | 
			
		||||
import '../../../global/store.dart';
 | 
			
		||||
import '../../../global/styles.dart';
 | 
			
		||||
import '../../../global/widgets/budget_color_circle.dart';
 | 
			
		||||
import '../../../models/budget_category_model.dart';
 | 
			
		||||
 | 
			
		||||
class TransactionListItem extends ConsumerStatefulWidget {
 | 
			
		||||
  const TransactionListItem({super.key, required this.index});
 | 
			
		||||
 | 
			
		||||
  final int index;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ConsumerState<TransactionListItem> createState() =>
 | 
			
		||||
      _TransactionListItemState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _TransactionListItemState extends ConsumerState<TransactionListItem> {
 | 
			
		||||
  bool showDetails = false;
 | 
			
		||||
  double cardHeight = 70.0;
 | 
			
		||||
 | 
			
		||||
  BudgetCategory? budgetCategory;
 | 
			
		||||
  Transaction? transaction;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    transaction =
 | 
			
		||||
        // ignore: sdk_version_since
 | 
			
		||||
        ref.watch(transactionHistoryListProvider).elementAtOrNull(widget.index);
 | 
			
		||||
    if (transaction == null) return Container();
 | 
			
		||||
    final budgetCategories = ref.read(budgetCategoriesProvider);
 | 
			
		||||
    if (transaction!.type == TransactionType.expense) {
 | 
			
		||||
      budgetCategory = budgetCategories.singleWhere(
 | 
			
		||||
        (category) => category.id == transaction!.budgetCategoryId,
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      budgetCategory = null;
 | 
			
		||||
    }
 | 
			
		||||
    return GestureDetector(
 | 
			
		||||
      onTap: () => toggleDetails(),
 | 
			
		||||
      child: Padding(
 | 
			
		||||
        padding: const EdgeInsets.only(top: 8.0, left: 8.0, bottom: 8.0),
 | 
			
		||||
        child: ClipRRect(
 | 
			
		||||
          borderRadius: const BorderRadius.only(
 | 
			
		||||
              topLeft: Radius.circular(10.0),
 | 
			
		||||
              bottomLeft: Radius.circular(10.0)),
 | 
			
		||||
          child: AnimatedContainer(
 | 
			
		||||
            curve: Curves.easeOut,
 | 
			
		||||
            duration: const Duration(milliseconds: 200),
 | 
			
		||||
            height: cardHeight,
 | 
			
		||||
            decoration: const BoxDecoration(
 | 
			
		||||
              borderRadius: BorderRadius.only(
 | 
			
		||||
                  topLeft: Radius.circular(10.0),
 | 
			
		||||
                  bottomLeft: Radius.circular(10.0)),
 | 
			
		||||
              color: Styles.washedStone,
 | 
			
		||||
            ),
 | 
			
		||||
            child: Row(
 | 
			
		||||
              children: [
 | 
			
		||||
                AnimatedContainer(
 | 
			
		||||
                    curve: Curves.easeOut,
 | 
			
		||||
                    duration: const Duration(milliseconds: 200),
 | 
			
		||||
                    height: cardHeight,
 | 
			
		||||
                    width: 6,
 | 
			
		||||
                    color: transaction!.type == TransactionType.income
 | 
			
		||||
                        ? Styles.incomeBlue
 | 
			
		||||
                        : Styles.expensesOrange),
 | 
			
		||||
                SizedBox(
 | 
			
		||||
                  width: BuildMedia(context).width * 0.65,
 | 
			
		||||
                  child: Padding(
 | 
			
		||||
                    padding: const EdgeInsets.only(left: 8.0),
 | 
			
		||||
                    child: Column(
 | 
			
		||||
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | 
			
		||||
                      crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Text(DateFormat('EEE MMM d, h:mm a')
 | 
			
		||||
                            .format(transaction!.date)),
 | 
			
		||||
                        Row(
 | 
			
		||||
                          children: [
 | 
			
		||||
                            const SizedBox(
 | 
			
		||||
                              width: 6,
 | 
			
		||||
                            ),
 | 
			
		||||
                            if (budgetCategory != null) ...[
 | 
			
		||||
                              BudgetColorCircle(
 | 
			
		||||
                                color: budgetCategory!.color,
 | 
			
		||||
                              ),
 | 
			
		||||
                              const SizedBox(
 | 
			
		||||
                                width: 8,
 | 
			
		||||
                              ),
 | 
			
		||||
                            ],
 | 
			
		||||
                            Text(
 | 
			
		||||
                              transaction!.type == TransactionType.income
 | 
			
		||||
                                  ? 'Income'
 | 
			
		||||
                                  : budgetCategory!.name,
 | 
			
		||||
                              style: const TextStyle(fontSize: 20),
 | 
			
		||||
                            ),
 | 
			
		||||
                          ],
 | 
			
		||||
                        ),
 | 
			
		||||
                        if (showDetails)
 | 
			
		||||
                          Text(
 | 
			
		||||
                            transaction!.memo ?? '',
 | 
			
		||||
                            style: const TextStyle(fontSize: 16),
 | 
			
		||||
                          )
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                const Spacer(),
 | 
			
		||||
                Column(
 | 
			
		||||
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Text(
 | 
			
		||||
                      transaction!.amount.currency(),
 | 
			
		||||
                      style: TextStyle(
 | 
			
		||||
                          fontSize: 24,
 | 
			
		||||
                          color: transaction!.type == TransactionType.income
 | 
			
		||||
                              ? Styles.incomeGreen
 | 
			
		||||
                              : Styles.expensesRed),
 | 
			
		||||
                    ),
 | 
			
		||||
                    if (showDetails) ...[
 | 
			
		||||
                      IconButton(
 | 
			
		||||
                        icon: const Icon(
 | 
			
		||||
                          Icons.edit_rounded,
 | 
			
		||||
                        ),
 | 
			
		||||
                        onPressed: () {
 | 
			
		||||
                          showDialog(
 | 
			
		||||
                            context: context,
 | 
			
		||||
                            builder: (context) => Dialog(
 | 
			
		||||
                              backgroundColor: Styles.dialogColor,
 | 
			
		||||
                              shape: Styles.dialogShape,
 | 
			
		||||
                              child:
 | 
			
		||||
                                  TransactionDialog(transaction: transaction),
 | 
			
		||||
                            ),
 | 
			
		||||
                          );
 | 
			
		||||
                        },
 | 
			
		||||
                      ),
 | 
			
		||||
                      IconButton(
 | 
			
		||||
                        icon: const Icon(
 | 
			
		||||
                          Icons.delete,
 | 
			
		||||
                          color: Styles.expensesRed,
 | 
			
		||||
                        ),
 | 
			
		||||
                        onPressed: () => deleteTransaction(),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
                const SizedBox(
 | 
			
		||||
                  width: 14,
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void toggleDetails() {
 | 
			
		||||
    if (showDetails) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        showDetails = false;
 | 
			
		||||
        cardHeight = 70;
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        showDetails = true;
 | 
			
		||||
        cardHeight = 120;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future deleteTransaction() async {
 | 
			
		||||
    final res = await ref
 | 
			
		||||
        .read(apiProvider.notifier)
 | 
			
		||||
        .delete(path: 'transaction', data: {'id': transaction!.id});
 | 
			
		||||
 | 
			
		||||
    final success = res != null ? res['success'] as bool : false;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      ref
 | 
			
		||||
          .read(dashboardProvider.notifier)
 | 
			
		||||
          .removeWithId('transactions', transaction!.id!);
 | 
			
		||||
      showSnack(ref: ref, text: 'Transaction removed', type: SnackType.success);
 | 
			
		||||
    } else {
 | 
			
		||||
      showSnack(
 | 
			
		||||
          ref: ref,
 | 
			
		||||
          text: 'Could not delete transaction',
 | 
			
		||||
          type: SnackType.error);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,288 +0,0 @@
 | 
			
		||||
import 'package:dio/dio.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
import 'package:helpers/helpers/misc_build/build_media.dart';
 | 
			
		||||
import 'package:helpers/helpers/print.dart';
 | 
			
		||||
import 'package:rluv/global/utils.dart';
 | 
			
		||||
import 'package:rluv/models/shared_note.dart';
 | 
			
		||||
 | 
			
		||||
import '../../../global/api.dart';
 | 
			
		||||
import '../../../global/store.dart';
 | 
			
		||||
import '../../../global/styles.dart';
 | 
			
		||||
import '../../../global/widgets/ui_button.dart';
 | 
			
		||||
 | 
			
		||||
class SharedNotesScreen extends ConsumerStatefulWidget {
 | 
			
		||||
  const SharedNotesScreen({super.key, required this.initialData});
 | 
			
		||||
 | 
			
		||||
  final Map<String, dynamic> initialData;
 | 
			
		||||
  @override
 | 
			
		||||
  ConsumerState<SharedNotesScreen> createState() => _SharedNotesScreenState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _SharedNotesScreenState extends ConsumerState<SharedNotesScreen> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final screen = BuildMedia(context).size;
 | 
			
		||||
    final sharedNotes = ref.watch(sharedNotesProvider);
 | 
			
		||||
    return Column(
 | 
			
		||||
      children: [
 | 
			
		||||
        const Padding(
 | 
			
		||||
          padding: EdgeInsets.all(18.0),
 | 
			
		||||
          child: Text(
 | 
			
		||||
            'Notes:',
 | 
			
		||||
            style: TextStyle(fontSize: 20),
 | 
			
		||||
            textAlign: TextAlign.center,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        if (sharedNotes.isEmpty) ...[
 | 
			
		||||
          const Text('Add notes to get started:'),
 | 
			
		||||
        ],
 | 
			
		||||
        if (sharedNotes.isNotEmpty)
 | 
			
		||||
          SizedBox(
 | 
			
		||||
            height: screen.height * 0.65,
 | 
			
		||||
            child: GridView.builder(
 | 
			
		||||
              itemCount: sharedNotes.length,
 | 
			
		||||
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
 | 
			
		||||
                crossAxisCount: 2,
 | 
			
		||||
              ),
 | 
			
		||||
              itemBuilder: (BuildContext context, int index) {
 | 
			
		||||
                final note = sharedNotes[index];
 | 
			
		||||
                final colorBrightness = note.color == null
 | 
			
		||||
                    ? Brightness.light
 | 
			
		||||
                    : getBrightness(note.color!);
 | 
			
		||||
                final textStyle = TextStyle(
 | 
			
		||||
                  fontSize: 16.0,
 | 
			
		||||
                  color: colorBrightness == Brightness.light
 | 
			
		||||
                      ? Colors.black54
 | 
			
		||||
                      : Colors.white70,
 | 
			
		||||
                );
 | 
			
		||||
                return Padding(
 | 
			
		||||
                  padding: const EdgeInsets.all(8.0),
 | 
			
		||||
                  child: InkWell(
 | 
			
		||||
                    child: Card(
 | 
			
		||||
                      color: note.color ?? Styles.washedStone,
 | 
			
		||||
                      child: SizedBox(
 | 
			
		||||
                        width: screen.width * 0.4,
 | 
			
		||||
                        child: Padding(
 | 
			
		||||
                          padding: const EdgeInsets.all(8.0),
 | 
			
		||||
                          child: Column(
 | 
			
		||||
                            children: [
 | 
			
		||||
                              FittedBox(
 | 
			
		||||
                                  child: Text(note.title,
 | 
			
		||||
                                      style: const TextStyle(fontSize: 20))),
 | 
			
		||||
                              Flexible(
 | 
			
		||||
                                child: Text(
 | 
			
		||||
                                  note.content.length > 80
 | 
			
		||||
                                      ? "${note.content.substring(0, 75)}..."
 | 
			
		||||
                                      : note.content,
 | 
			
		||||
                                  style: textStyle,
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ],
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                    onTap: () {
 | 
			
		||||
                      showModalBottomSheet(
 | 
			
		||||
                          isScrollControlled: true,
 | 
			
		||||
                          shape: RoundedRectangleBorder(
 | 
			
		||||
                            borderRadius: BorderRadius.circular(20.0),
 | 
			
		||||
                          ),
 | 
			
		||||
                          backgroundColor: Colors.transparent,
 | 
			
		||||
                          context: context,
 | 
			
		||||
                          builder: (BuildContext context) {
 | 
			
		||||
                            return Padding(
 | 
			
		||||
                              padding: EdgeInsets.only(
 | 
			
		||||
                                  bottom:
 | 
			
		||||
                                      MediaQuery.of(context).viewInsets.bottom),
 | 
			
		||||
                              child: NoteBottomSheet(
 | 
			
		||||
                                index: index,
 | 
			
		||||
                              ),
 | 
			
		||||
                            );
 | 
			
		||||
                          });
 | 
			
		||||
                    },
 | 
			
		||||
                  ),
 | 
			
		||||
                );
 | 
			
		||||
              },
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        Padding(
 | 
			
		||||
          padding: const EdgeInsets.symmetric(vertical: 18.0, horizontal: 12.0),
 | 
			
		||||
          child: UiButton(
 | 
			
		||||
              text: 'Add Note',
 | 
			
		||||
              onPressed: () {
 | 
			
		||||
                showModalBottomSheet(
 | 
			
		||||
                    isScrollControlled: true,
 | 
			
		||||
                    shape: RoundedRectangleBorder(
 | 
			
		||||
                      borderRadius: BorderRadius.circular(20.0),
 | 
			
		||||
                    ),
 | 
			
		||||
                    backgroundColor: Colors.transparent,
 | 
			
		||||
                    context: context,
 | 
			
		||||
                    builder: (BuildContext context) {
 | 
			
		||||
                      return Padding(
 | 
			
		||||
                        padding: EdgeInsets.only(
 | 
			
		||||
                            bottom: MediaQuery.of(context).viewInsets.bottom),
 | 
			
		||||
                        child: const NoteBottomSheet(
 | 
			
		||||
                          index: null,
 | 
			
		||||
                        ),
 | 
			
		||||
                      );
 | 
			
		||||
                    });
 | 
			
		||||
              }),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class NoteBottomSheet extends ConsumerStatefulWidget {
 | 
			
		||||
  const NoteBottomSheet({super.key, required this.index});
 | 
			
		||||
 | 
			
		||||
  final int? index;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ConsumerState<NoteBottomSheet> createState() => _NoteBottomSheetState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _NoteBottomSheetState extends ConsumerState<NoteBottomSheet> {
 | 
			
		||||
  late final SharedNote? oldNote;
 | 
			
		||||
  late SharedNote newNote;
 | 
			
		||||
 | 
			
		||||
  final titleFocusNode = FocusNode();
 | 
			
		||||
  final contentFocusNode = FocusNode();
 | 
			
		||||
  late final TextEditingController titleController;
 | 
			
		||||
  late final TextEditingController contentController;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    if (widget.index == null) {
 | 
			
		||||
      oldNote = null;
 | 
			
		||||
      final family = ref.read(familyProvider);
 | 
			
		||||
      final user = ref.read(userProvider);
 | 
			
		||||
      newNote = SharedNote(
 | 
			
		||||
        familyId: family!.id,
 | 
			
		||||
        createdByUserId: user!.id!,
 | 
			
		||||
        title: 'Title',
 | 
			
		||||
        content: '',
 | 
			
		||||
        tagIds: [],
 | 
			
		||||
        color: Styles.washedStone,
 | 
			
		||||
        isMarkdown: false,
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      oldNote = ref.read(sharedNotesProvider).elementAt(widget.index!);
 | 
			
		||||
      newNote = SharedNote.copy(oldNote!);
 | 
			
		||||
    }
 | 
			
		||||
    titleController = TextEditingController(text: newNote.title);
 | 
			
		||||
    contentController = TextEditingController(text: newNote.content);
 | 
			
		||||
    WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
      (timeStamp) => titleFocusNode.requestFocus(),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final screen = BuildMedia(context).size;
 | 
			
		||||
    return Container(
 | 
			
		||||
      width: screen.width,
 | 
			
		||||
      decoration: BoxDecoration(
 | 
			
		||||
          color: newNote.color ?? Styles.washedStone,
 | 
			
		||||
          borderRadius: const BorderRadius.only(
 | 
			
		||||
              topLeft: Radius.circular(20.0), topRight: Radius.circular(20.0)),
 | 
			
		||||
          boxShadow: const [
 | 
			
		||||
            BoxShadow(
 | 
			
		||||
                color: Colors.black26,
 | 
			
		||||
                spreadRadius: 5.0,
 | 
			
		||||
                blurRadius: 2.0,
 | 
			
		||||
                offset: Offset(0, -2))
 | 
			
		||||
          ]),
 | 
			
		||||
      child: Padding(
 | 
			
		||||
        padding: const EdgeInsets.all(16.0),
 | 
			
		||||
        child: Column(
 | 
			
		||||
          mainAxisSize: MainAxisSize.min,
 | 
			
		||||
          children: [
 | 
			
		||||
            TextField(
 | 
			
		||||
              style: const TextStyle(fontSize: 18.0),
 | 
			
		||||
              maxLines: 1,
 | 
			
		||||
              focusNode: titleFocusNode,
 | 
			
		||||
              textAlign: TextAlign.left,
 | 
			
		||||
              controller: titleController,
 | 
			
		||||
              // decoration: Styles.inputLavenderBubble(),
 | 
			
		||||
            ),
 | 
			
		||||
            TextField(
 | 
			
		||||
              style: const TextStyle(fontSize: 18.0),
 | 
			
		||||
              maxLines: 6,
 | 
			
		||||
              focusNode: contentFocusNode,
 | 
			
		||||
              textAlign: TextAlign.left,
 | 
			
		||||
              controller: contentController,
 | 
			
		||||
              // decoration: Styles.inputLavenderBubble(),
 | 
			
		||||
            ),
 | 
			
		||||
            Padding(
 | 
			
		||||
              padding:
 | 
			
		||||
                  const EdgeInsets.symmetric(vertical: 28.0, horizontal: 36.0),
 | 
			
		||||
              child: UiButton(
 | 
			
		||||
                onPressed: !noteChanged(oldNote, newNote)
 | 
			
		||||
                    ? null
 | 
			
		||||
                    : () async {
 | 
			
		||||
                        newNote.content = contentController.text;
 | 
			
		||||
                        newNote.title = titleController.text;
 | 
			
		||||
                        try {
 | 
			
		||||
                          Response? res;
 | 
			
		||||
                          if (widget.index == null) {
 | 
			
		||||
                            res = await ref
 | 
			
		||||
                                .read(apiProvider)
 | 
			
		||||
                                .post('shared_note', data: newNote.toJson());
 | 
			
		||||
                          } else {
 | 
			
		||||
                            res = await ref
 | 
			
		||||
                                .read(apiProvider)
 | 
			
		||||
                                .put('shared_note', data: newNote.toJson());
 | 
			
		||||
                          }
 | 
			
		||||
                          if (res.data != null && res.data['success']) {
 | 
			
		||||
                            if (widget.index == null) {
 | 
			
		||||
                              ref
 | 
			
		||||
                                  .read(dashboardProvider.notifier)
 | 
			
		||||
                                  .add({'shared_notes': res.data});
 | 
			
		||||
                            } else {
 | 
			
		||||
                              ref
 | 
			
		||||
                                  .read(dashboardProvider.notifier)
 | 
			
		||||
                                  .update({'shared_notes': res.data});
 | 
			
		||||
                            }
 | 
			
		||||
                            showSnack(
 | 
			
		||||
                                ref: ref,
 | 
			
		||||
                                text: 'Added note',
 | 
			
		||||
                                type: SnackType.success);
 | 
			
		||||
                          } else {
 | 
			
		||||
                            showSnack(
 | 
			
		||||
                                ref: ref,
 | 
			
		||||
                                text: res.data['message'] ??
 | 
			
		||||
                                    'Unexpected error occurred',
 | 
			
		||||
                                type: SnackType.error);
 | 
			
		||||
                          }
 | 
			
		||||
                        } catch (err) {
 | 
			
		||||
                          showSnack(
 | 
			
		||||
                              ref: ref,
 | 
			
		||||
                              text: 'Unexpected error occurred',
 | 
			
		||||
                              type: SnackType.error);
 | 
			
		||||
                          printRed(err);
 | 
			
		||||
                        }
 | 
			
		||||
                        // ignore: use_build_context_synchronously
 | 
			
		||||
                        Navigator.pop(context);
 | 
			
		||||
                      },
 | 
			
		||||
                text: 'Save',
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool noteChanged(SharedNote? n, SharedNote m) {
 | 
			
		||||
    if (n == null) return true;
 | 
			
		||||
    if (n.content != m.content) return true;
 | 
			
		||||
    if (n.title != m.content) return true;
 | 
			
		||||
    if (n.tagIds != m.tagIds) return true;
 | 
			
		||||
    if (n.color != m.color) return true;
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,44 +0,0 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
 | 
			
		||||
import '../../../global/api.dart';
 | 
			
		||||
import '../../../global/store.dart';
 | 
			
		||||
import '../../../global/widgets/ui_button.dart';
 | 
			
		||||
 | 
			
		||||
class SettingsScreen extends ConsumerStatefulWidget {
 | 
			
		||||
  const SettingsScreen({super.key});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ConsumerState<SettingsScreen> createState() => _SettingsScreenState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _SettingsScreenState extends ConsumerState<SettingsScreen> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final user = ref.read(userProvider);
 | 
			
		||||
    final family = ref.read(familyProvider);
 | 
			
		||||
    return Column(
 | 
			
		||||
      children: [
 | 
			
		||||
        const Text('Settings'),
 | 
			
		||||
        const SizedBox(height: 20),
 | 
			
		||||
        Text(user?.name ?? 'N/A'),
 | 
			
		||||
        Text("Username: ${user?.username ?? 'N/A'}"),
 | 
			
		||||
        Text("Email: ${user?.email ?? 'N/A'}"),
 | 
			
		||||
        Text("Family Code: ${family?.code ?? 'N/A'}"),
 | 
			
		||||
        const Spacer(),
 | 
			
		||||
        UiButton(
 | 
			
		||||
          onPressed: () {
 | 
			
		||||
            ref.read(jwtProvider.notifier).revokeToken();
 | 
			
		||||
            // Navigator.pushAndRemoveUntil(
 | 
			
		||||
            //   context,
 | 
			
		||||
            //   MaterialPageRoute(builder: (ctx) => const AccountCreateScreen()),
 | 
			
		||||
            //   (_) => false,
 | 
			
		||||
            // );
 | 
			
		||||
          },
 | 
			
		||||
          text: 'Sign Out',
 | 
			
		||||
        ),
 | 
			
		||||
        const SizedBox(height: 20),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,235 +0,0 @@
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
 | 
			
		||||
import 'package:dio/dio.dart';
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
import 'package:helpers/helpers/print.dart';
 | 
			
		||||
import 'package:jwt_decoder/jwt_decoder.dart';
 | 
			
		||||
import 'package:rluv/global/store.dart';
 | 
			
		||||
 | 
			
		||||
import '../models/token.dart';
 | 
			
		||||
 | 
			
		||||
final tokenProvider = StateProvider<Token?>((ref) {
 | 
			
		||||
  final jwt = ref.watch(jwtProvider);
 | 
			
		||||
  printLime('Current token: $jwt');
 | 
			
		||||
  if (jwt == null) return null;
 | 
			
		||||
  try {
 | 
			
		||||
    return Token.fromJson(JwtDecoder.decode(jwt));
 | 
			
		||||
  } catch (_) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
final jwtProvider = StateNotifierProvider<_JwtNotifier, String?>((ref) {
 | 
			
		||||
  final prefs = ref.watch(prefsProvider);
 | 
			
		||||
  final jwt = prefs?.getString('jwt');
 | 
			
		||||
  return _JwtNotifier(ref, jwt);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
class _JwtNotifier extends StateNotifier<String?> {
 | 
			
		||||
  _JwtNotifier(this.ref, String? jwt) : super(null) {
 | 
			
		||||
    if (jwt != null) {
 | 
			
		||||
      setToken(jwt);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  final StateNotifierProviderRef ref;
 | 
			
		||||
 | 
			
		||||
  void setToken(String jwt) {
 | 
			
		||||
    state = jwt;
 | 
			
		||||
    printCyan('Loaded jwt into client: $jwt');
 | 
			
		||||
    ref.read(prefsProvider)?.setString('jwt', jwt);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void revokeToken() {
 | 
			
		||||
    printCyan('jwt token revoked');
 | 
			
		||||
    state = null;
 | 
			
		||||
    ref.read(prefsProvider)?.remove('jwt');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
final apiProvider = StateNotifierProvider<_ApiNotifier, Dio>((ref) {
 | 
			
		||||
  return _ApiNotifier(ref, Dio());
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
class _ApiNotifier extends StateNotifier<Dio> {
 | 
			
		||||
  _ApiNotifier(this.ref, this.dio) : super(dio) {
 | 
			
		||||
    // dio.options.baseUrl = "https://af70-136-36-2-234.ngrok-free.app/";
 | 
			
		||||
    // dio.options.baseUrl = "http://localhost:8081/";
 | 
			
		||||
    dio.options.baseUrl = "https://rluv.fosscat.com/";
 | 
			
		||||
    dio.interceptors.addAll([
 | 
			
		||||
      InterceptorsWrapper(onRequest:
 | 
			
		||||
          (RequestOptions options, RequestInterceptorHandler handler) {
 | 
			
		||||
        final jwt = ref.read(jwtProvider);
 | 
			
		||||
        if (jwt != null) {
 | 
			
		||||
          options.headers['token'] = jwt;
 | 
			
		||||
        }
 | 
			
		||||
        return handler.next(options);
 | 
			
		||||
      }, onResponse: (Response response, ResponseInterceptorHandler handler) {
 | 
			
		||||
        if (response.statusCode != null) {
 | 
			
		||||
          if (response.statusCode == 200) {
 | 
			
		||||
            try {
 | 
			
		||||
              if ((response.data as Map<String, dynamic>)
 | 
			
		||||
                  .containsKey('success')) {
 | 
			
		||||
                if (!response.data['success']) return handler.next(response);
 | 
			
		||||
              }
 | 
			
		||||
              if ((response.data as Map<String, dynamic>)
 | 
			
		||||
                  .containsKey('token')) {
 | 
			
		||||
                final jwt = response.data['token'];
 | 
			
		||||
                if (jwt != null) {
 | 
			
		||||
                  ref.read(jwtProvider.notifier).setToken(jwt);
 | 
			
		||||
                  // ref.read(tokenProvider.notifier).state =
 | 
			
		||||
                  //     Token.fromJson(jwtData);
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            } catch (err) {
 | 
			
		||||
              printRed('Error in interceptor for token: $err');
 | 
			
		||||
              return handler.next(response);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        return handler.next(response);
 | 
			
		||||
      }, onError: (DioException err, ErrorInterceptorHandler handler) {
 | 
			
		||||
        if (err.response?.statusCode == 403) {
 | 
			
		||||
          ref.read(jwtProvider.notifier).revokeToken();
 | 
			
		||||
        }
 | 
			
		||||
        return handler.next(err);
 | 
			
		||||
      }),
 | 
			
		||||
      _LoggingInterceptor(),
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  final Dio dio;
 | 
			
		||||
  final StateNotifierProviderRef ref;
 | 
			
		||||
 | 
			
		||||
  Future<Map<String, dynamic>?> get(String path) async {
 | 
			
		||||
    try {
 | 
			
		||||
      final res = await dio.get(path);
 | 
			
		||||
 | 
			
		||||
      if (res.data != null) {
 | 
			
		||||
        return res.data as Map<String, dynamic>;
 | 
			
		||||
      }
 | 
			
		||||
      return null;
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<Map<String, dynamic>?> put(
 | 
			
		||||
      {required String path, Object? data}) async {
 | 
			
		||||
    try {
 | 
			
		||||
      final res = await dio.put(path, data: data);
 | 
			
		||||
 | 
			
		||||
      if (res.data != null) {
 | 
			
		||||
        return res.data as Map<String, dynamic>;
 | 
			
		||||
      }
 | 
			
		||||
      return null;
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<Map<String, dynamic>?> post(
 | 
			
		||||
      {required String path, Object? data}) async {
 | 
			
		||||
    try {
 | 
			
		||||
      final res = await dio.post(path, data: data);
 | 
			
		||||
 | 
			
		||||
      if (res.data != null) {
 | 
			
		||||
        return res.data as Map<String, dynamic>;
 | 
			
		||||
      }
 | 
			
		||||
      return null;
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<Map<String, dynamic>?> delete(
 | 
			
		||||
      {required String path, Object? data}) async {
 | 
			
		||||
    try {
 | 
			
		||||
      final res = await dio.delete(path, data: data);
 | 
			
		||||
 | 
			
		||||
      if (res.data != null) {
 | 
			
		||||
        return res.data as Map<String, dynamic>;
 | 
			
		||||
      }
 | 
			
		||||
      return null;
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _LoggingInterceptor extends Interceptor {
 | 
			
		||||
  _LoggingInterceptor();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Future onRequest(
 | 
			
		||||
      RequestOptions options, RequestInterceptorHandler handler) async {
 | 
			
		||||
    logPrint('///*** REQUEST ***\\\\\\');
 | 
			
		||||
    printKV('URI', options.uri);
 | 
			
		||||
    printKV('METHOD', options.method);
 | 
			
		||||
    logPrint('HEADERS:');
 | 
			
		||||
    options.headers.forEach((key, v) => printKV(' - $key', v));
 | 
			
		||||
    logPrint('BODY:');
 | 
			
		||||
    printJson(options.data);
 | 
			
		||||
 | 
			
		||||
    return handler.next(options);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void onError(DioException err, ErrorInterceptorHandler handler) {
 | 
			
		||||
    printRed('///*** ERROR RESPONSE ***\\\\\\');
 | 
			
		||||
    logPrint('URI: ${err.requestOptions.uri}');
 | 
			
		||||
    if (err.response != null) {
 | 
			
		||||
      logPrint('STATUS CODE: ${err.response?.statusCode?.toString()}');
 | 
			
		||||
    }
 | 
			
		||||
    logPrint('$err');
 | 
			
		||||
    if (err.response != null) {
 | 
			
		||||
      printKV('REDIRECT', err.response?.realUri ?? '');
 | 
			
		||||
      logPrint('BODY:');
 | 
			
		||||
      printJson(err.response?.data);
 | 
			
		||||
    }
 | 
			
		||||
    return handler.next(err);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Future onResponse(
 | 
			
		||||
      Response response, ResponseInterceptorHandler handler) async {
 | 
			
		||||
    logPrint('///*** RESPONSE ***\\\\\\');
 | 
			
		||||
    printKV('URI', response.requestOptions.uri);
 | 
			
		||||
    printKV('STATUS CODE', response.statusCode ?? '');
 | 
			
		||||
    // printKV('REDIRECT', response.isRedirect);
 | 
			
		||||
    logPrint('BODY:');
 | 
			
		||||
    printJson(response.data);
 | 
			
		||||
 | 
			
		||||
    return handler.next(response);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void printKV(String key, Object v) {
 | 
			
		||||
    if (kDebugMode) {
 | 
			
		||||
      printOrange('$key: $v');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void printJson(dynamic s) {
 | 
			
		||||
    try {
 | 
			
		||||
      final data = (s as Map<String, dynamic>?);
 | 
			
		||||
      if (kDebugMode) {
 | 
			
		||||
        if (data == null) {
 | 
			
		||||
          printAmber({});
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        JsonEncoder encoder = const JsonEncoder.withIndent('  ');
 | 
			
		||||
        String prettyprint = encoder.convert(s);
 | 
			
		||||
        printAmber(prettyprint);
 | 
			
		||||
      }
 | 
			
		||||
    } catch (_) {
 | 
			
		||||
      printAmber(s);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void logPrint(String s) {
 | 
			
		||||
    if (kDebugMode) {
 | 
			
		||||
      printOrange(s);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,214 +0,0 @@
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
import 'package:helpers/helpers/print.dart';
 | 
			
		||||
import 'package:rluv/global/api.dart';
 | 
			
		||||
import 'package:rluv/models/budget.dart';
 | 
			
		||||
import 'package:rluv/models/budget_category_model.dart';
 | 
			
		||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
			
		||||
 | 
			
		||||
import '../models/family_model.dart';
 | 
			
		||||
import '../models/shared_note.dart';
 | 
			
		||||
import '../models/transaction_model.dart';
 | 
			
		||||
import '../models/user.dart';
 | 
			
		||||
 | 
			
		||||
StateProvider<SharedPreferences?> prefsProvider =
 | 
			
		||||
    StateProvider<SharedPreferences?>((ref) => null);
 | 
			
		||||
 | 
			
		||||
// final StateProvider<Token?> tokenProvider = StateProvider<Token?>((ref) {
 | 
			
		||||
//   // final tokenStr = prefs.getString('jwt');
 | 
			
		||||
//   // if (tokenStr != null) {
 | 
			
		||||
//   // return Token.fromJson(jsonDecode(tokenStr));
 | 
			
		||||
//   // }
 | 
			
		||||
//   return null;
 | 
			
		||||
// });
 | 
			
		||||
 | 
			
		||||
final Provider<User?> userProvider = Provider<User?>(
 | 
			
		||||
  (ref) {
 | 
			
		||||
    final dash = ref.watch(dashboardProvider);
 | 
			
		||||
    if (dash == null) return null;
 | 
			
		||||
    final userData = dash['user'] as Map<String, dynamic>;
 | 
			
		||||
    return User.fromJson(userData);
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
final Provider<FamilyModel?> familyProvider = Provider<FamilyModel?>(
 | 
			
		||||
  (ref) {
 | 
			
		||||
    final dash = ref.watch(dashboardProvider);
 | 
			
		||||
    if (dash == null) return null;
 | 
			
		||||
    final familyData = dash['family'] as Map<String, dynamic>;
 | 
			
		||||
    return FamilyModel.fromJson(familyData);
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
final Provider<List<BudgetCategory>> budgetCategoriesProvider =
 | 
			
		||||
    Provider<List<BudgetCategory>>((ref) {
 | 
			
		||||
  final dash = ref.watch(dashboardProvider);
 | 
			
		||||
  if (dash == null) return [];
 | 
			
		||||
  final categoriesData = dash['budget_categories'] as List<dynamic>;
 | 
			
		||||
  final categories = categoriesData
 | 
			
		||||
      .map(
 | 
			
		||||
        (e) => BudgetCategory.fromJson(e as Map<String, dynamic>),
 | 
			
		||||
      )
 | 
			
		||||
      .toList();
 | 
			
		||||
 | 
			
		||||
  final prefs = ref.read(prefsProvider);
 | 
			
		||||
  final budgetJson = jsonEncode({'budget_categories': categoriesData});
 | 
			
		||||
  printBlue('updated prefs stored categories');
 | 
			
		||||
  prefs?.setString('budget_categories', budgetJson);
 | 
			
		||||
 | 
			
		||||
  return categories;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
final Provider<Budget?> budgetProvider = Provider<Budget?>(
 | 
			
		||||
  (ref) {
 | 
			
		||||
    final dash = ref.watch(dashboardProvider);
 | 
			
		||||
    if (dash == null) return null;
 | 
			
		||||
    final budgetData = dash['budget'] as Map<String, dynamic>?;
 | 
			
		||||
    if (budgetData == null) return null;
 | 
			
		||||
    return Budget.fromJson(budgetData);
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
final Provider<List<Transaction>> transactionsProvider =
 | 
			
		||||
    Provider<List<Transaction>>((ref) {
 | 
			
		||||
  final dash = ref.watch(dashboardProvider);
 | 
			
		||||
  if (dash == null) return [];
 | 
			
		||||
  final transactions = dash['transactions'] as List<dynamic>;
 | 
			
		||||
  return transactions
 | 
			
		||||
      .map(
 | 
			
		||||
        (e) => Transaction.fromJson(e as Map<String, dynamic>),
 | 
			
		||||
      )
 | 
			
		||||
      .toList();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
final Provider<List<SharedNote>> sharedNotesProvider =
 | 
			
		||||
    Provider<List<SharedNote>>(
 | 
			
		||||
  (ref) {
 | 
			
		||||
    final dash = ref.watch(dashboardProvider);
 | 
			
		||||
    if (dash == null) return [];
 | 
			
		||||
    final sharedNotes = dash['shared_notes'] as List<dynamic>;
 | 
			
		||||
    return sharedNotes
 | 
			
		||||
        .map(
 | 
			
		||||
          (e) => SharedNote.fromJson(e as Map<String, dynamic>),
 | 
			
		||||
        )
 | 
			
		||||
        .toList();
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
final dashboardProvider =
 | 
			
		||||
    StateNotifierProvider<DashBoardStateNotifier, Map<String, dynamic>?>(
 | 
			
		||||
  (ref) {
 | 
			
		||||
    return DashBoardStateNotifier(ref);
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
final loadingStateProvider = StateProvider<bool>(
 | 
			
		||||
  (ref) => false,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
class DashBoardStateNotifier extends StateNotifier<Map<String, dynamic>?> {
 | 
			
		||||
  DashBoardStateNotifier(this.ref) : super(null);
 | 
			
		||||
 | 
			
		||||
  StateNotifierProviderRef ref;
 | 
			
		||||
 | 
			
		||||
  Future<void> fetchDashboard() async {
 | 
			
		||||
    WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
      (_) => ref.read(loadingStateProvider.notifier).state = true,
 | 
			
		||||
    );
 | 
			
		||||
    final token = ref.read(tokenProvider);
 | 
			
		||||
    if (token?.familyId == null) {
 | 
			
		||||
      printPink('No token, cannot fetch dashboard');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    printAmber('Fetching dashboard');
 | 
			
		||||
    state = await ref.read(apiProvider.notifier).get("dashboard");
 | 
			
		||||
    WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
      (_) => ref.read(loadingStateProvider.notifier).state = false,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void update(Map<String, dynamic> data) {
 | 
			
		||||
    if (state == null) {
 | 
			
		||||
      printPink('Cant update data, state is null');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (data.keys.length != 1 || data.values.length != 1) {
 | 
			
		||||
      throw Exception('Only one key/val used in update');
 | 
			
		||||
    }
 | 
			
		||||
    final key = data.keys.first;
 | 
			
		||||
    switch (key) {
 | 
			
		||||
      case 'transactions':
 | 
			
		||||
      case 'budget_categories':
 | 
			
		||||
      case 'shared_notes':
 | 
			
		||||
        final subStateList = state![key] as List<dynamic>;
 | 
			
		||||
        final subStateListObj = subStateList
 | 
			
		||||
            .map(
 | 
			
		||||
              (e) => e as Map<String, dynamic>,
 | 
			
		||||
            )
 | 
			
		||||
            .toList();
 | 
			
		||||
        subStateListObj.removeWhere(
 | 
			
		||||
          (element) =>
 | 
			
		||||
              element['id'] ==
 | 
			
		||||
              (data.values.first as Map<String, dynamic>)['id'],
 | 
			
		||||
        );
 | 
			
		||||
        subStateListObj.add(data.values.first);
 | 
			
		||||
 | 
			
		||||
        final newState = state;
 | 
			
		||||
        newState![key] = subStateListObj;
 | 
			
		||||
        state = {...newState};
 | 
			
		||||
        // printBlue(state);
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void add(Map<String, dynamic> data) {
 | 
			
		||||
    if (state == null) {
 | 
			
		||||
      printPink('Cant add data, state is null');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (data.keys.length != 1 || data.values.length != 1) {
 | 
			
		||||
      throw Exception('Only one key/val used in add');
 | 
			
		||||
    }
 | 
			
		||||
    final key = data.keys.first;
 | 
			
		||||
    switch (key) {
 | 
			
		||||
      case 'transactions':
 | 
			
		||||
      case 'budget_categories':
 | 
			
		||||
      case 'shared_noted':
 | 
			
		||||
        final subStateList = state![key] as List<dynamic>;
 | 
			
		||||
        final newState = state;
 | 
			
		||||
        subStateList.add(data.values.first);
 | 
			
		||||
        newState![key] = subStateList;
 | 
			
		||||
        state = {...newState};
 | 
			
		||||
        // printBlue(state);
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void removeWithId(String stateKey, int id) {
 | 
			
		||||
    if (state == null) {
 | 
			
		||||
      printPink('Cant remove data, state is null');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    switch (stateKey) {
 | 
			
		||||
      case 'transactions':
 | 
			
		||||
      case 'budget_categories':
 | 
			
		||||
      case 'shared_noted':
 | 
			
		||||
        final subStateList = state![stateKey] as List<dynamic>;
 | 
			
		||||
        final newState = state;
 | 
			
		||||
        subStateList
 | 
			
		||||
            .removeWhere((e) => (e as Map<String, dynamic>)['id'] == id);
 | 
			
		||||
        newState![stateKey] = subStateList;
 | 
			
		||||
        state = {...newState};
 | 
			
		||||
        // printBlue(state);
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,81 +0,0 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class Styles {
 | 
			
		||||
  // Theme Colors
 | 
			
		||||
  static const Color purpleNurple = Color(0xFFA188A6);
 | 
			
		||||
  static const Color deepPurpleNurple = Color(0xFF977C9C);
 | 
			
		||||
  static const Color sunflower = Color(0xffFFEE88);
 | 
			
		||||
  static const Color electricBlue = Color(0xFF19647E);
 | 
			
		||||
  static const Color blushingPink = Color(0xFFE78F8E);
 | 
			
		||||
  static const Color seaweedGreen = Color(0xFF86BA90);
 | 
			
		||||
  static const Color emptyBarGrey = Color(0xFFC8C8C8);
 | 
			
		||||
  static const Color lavender = Color(0xFFB8B8FF);
 | 
			
		||||
  static const Color washedStone = Color(0xFFD9D9D9);
 | 
			
		||||
  static const Color flounderBlue = Color(0xFFA7E2E3);
 | 
			
		||||
  // Income Colors
 | 
			
		||||
  static const Color incomeBlue = Color(0xFFB8B8FF);
 | 
			
		||||
  static const Color incomeGreen = Color(0xFF0FA102);
 | 
			
		||||
 | 
			
		||||
  // Expenses Colors
 | 
			
		||||
  static const Color expensesOrange = Color(0xFFFA824C);
 | 
			
		||||
  static const Color expensesRed = Color(0xFFC73E1D);
 | 
			
		||||
 | 
			
		||||
  static const List<Color> curatedColors = [
 | 
			
		||||
    Color(0xFF6D326D),
 | 
			
		||||
    Color(0xFFE58F65),
 | 
			
		||||
    Color(0xFFD3E298),
 | 
			
		||||
    Color(0xFFFD96A9),
 | 
			
		||||
    Color(0xFF4F7CAC),
 | 
			
		||||
    Color(0xFFE4B4C2),
 | 
			
		||||
    Color(0xFFFFB8D1),
 | 
			
		||||
    Color(0xFFDDFDFE),
 | 
			
		||||
    Color(0xFFD7BE82),
 | 
			
		||||
    Color(0xFFD4C5C7),
 | 
			
		||||
    Color(0xFF8EB8E5),
 | 
			
		||||
    Color(0xFF9893DA),
 | 
			
		||||
    Color(0xFF99AA38),
 | 
			
		||||
    Color(0xFFA7E2E3),
 | 
			
		||||
    Color(0xFFF7B2BD),
 | 
			
		||||
    Color(0xFFFEDC97),
 | 
			
		||||
    Color(0xFFC28CAE),
 | 
			
		||||
    Color(0xFFF1BF98),
 | 
			
		||||
    Color(0xFFD1BCE3),
 | 
			
		||||
    Color(0xFFBDC667),
 | 
			
		||||
    Color(0xFFFFB563)
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  // Button Styles
 | 
			
		||||
  static const Color disabledButton = Color(0xFFA8A8A8);
 | 
			
		||||
  static const Color disabledButtonText = Color(0xFFD9D9D9);
 | 
			
		||||
 | 
			
		||||
  // Widget Styles
 | 
			
		||||
  static BoxDecoration boxLavenderBubble = BoxDecoration(
 | 
			
		||||
    color: Styles.lavender,
 | 
			
		||||
    borderRadius: BorderRadius.circular(20.0),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  static InputDecoration inputLavenderBubble({String? labelText}) =>
 | 
			
		||||
      InputDecoration(
 | 
			
		||||
        contentPadding:
 | 
			
		||||
            const EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
 | 
			
		||||
        labelText: labelText,
 | 
			
		||||
        errorStyle: const TextStyle(
 | 
			
		||||
          color: Styles.expensesRed,
 | 
			
		||||
        ),
 | 
			
		||||
        focusColor: Styles.blushingPink,
 | 
			
		||||
        hoverColor: Styles.blushingPink,
 | 
			
		||||
        fillColor: Styles.lavender,
 | 
			
		||||
        border: const OutlineInputBorder(
 | 
			
		||||
          borderSide: BorderSide.none,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  static const Color dialogColor = Styles.purpleNurple;
 | 
			
		||||
 | 
			
		||||
  static RoundedRectangleBorder dialogShape = RoundedRectangleBorder(
 | 
			
		||||
    borderRadius: BorderRadius.circular(20.0),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // Sizing Styles
 | 
			
		||||
  static const dialogScreenWidthFactor = 0.75;
 | 
			
		||||
}
 | 
			
		||||
@ -1,131 +0,0 @@
 | 
			
		||||
import 'dart:math';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/services.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
import 'package:helpers/helpers/print.dart';
 | 
			
		||||
import 'package:intl/intl.dart';
 | 
			
		||||
import 'package:rluv/global/styles.dart';
 | 
			
		||||
import 'package:rluv/main.dart';
 | 
			
		||||
 | 
			
		||||
String formatDate(DateTime time) {
 | 
			
		||||
  return DateFormat('EEEE, dd MMMM yyyy').format(time);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DateTime dateFromJson(int value) => DateTime.fromMillisecondsSinceEpoch(value);
 | 
			
		||||
 | 
			
		||||
int? dateToJson(DateTime? value) => value?.millisecondsSinceEpoch;
 | 
			
		||||
 | 
			
		||||
bool boolFromJson(int value) => value == 1;
 | 
			
		||||
 | 
			
		||||
int boolToJson(bool hide) => hide ? 1 : 0;
 | 
			
		||||
 | 
			
		||||
String colorToJson(Color color) =>
 | 
			
		||||
    color.toString().split('(0x')[1].split(')')[0];
 | 
			
		||||
 | 
			
		||||
Color colorFromJson(String hex) => Color(int.parse(hex, radix: 16));
 | 
			
		||||
 | 
			
		||||
String? optionalColorToJson(Color? optionalColor) => optionalColor == null
 | 
			
		||||
    ? null
 | 
			
		||||
    : optionalColor.toString().split('(0x')[1].split(')')[0];
 | 
			
		||||
 | 
			
		||||
Color? optionalColorFromJson(String? hex) =>
 | 
			
		||||
    hex == null ? null : Color(int.parse(hex, radix: 16));
 | 
			
		||||
 | 
			
		||||
Brightness getBrightness(Color color) =>
 | 
			
		||||
    ThemeData.estimateBrightnessForColor(color);
 | 
			
		||||
 | 
			
		||||
List<Color> generateColorList() {
 | 
			
		||||
  List<Color> colors = [];
 | 
			
		||||
  for (int i = 100; i <= 255; i += 30) {
 | 
			
		||||
    // Red value variations
 | 
			
		||||
    for (int j = 100; j <= 255; j += 30) {
 | 
			
		||||
      // Green value variations
 | 
			
		||||
      for (int k = 100; k <= 255; k += 30) {
 | 
			
		||||
        // Blue value variations
 | 
			
		||||
        final alpha = Random().nextInt(256) + 50 % 255;
 | 
			
		||||
        colors.add(Color.fromARGB(alpha, i, j, k));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  colors.shuffle();
 | 
			
		||||
  return colors;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extension MonetaryExtension on double {
 | 
			
		||||
  String currency() {
 | 
			
		||||
    final numStr = toStringAsFixed(2);
 | 
			
		||||
    final pieces = numStr.split(".");
 | 
			
		||||
    if (pieces.length == 1) {
 | 
			
		||||
      return "\$${pieces.first}.00";
 | 
			
		||||
    } else {
 | 
			
		||||
      if (pieces.length > 2) {
 | 
			
		||||
        printBlue(pieces);
 | 
			
		||||
      }
 | 
			
		||||
      return '\$${pieces[0]}.${pieces[1].padRight(2, "0")}';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setDevicePortraitOrientation() {
 | 
			
		||||
  SystemChrome.setPreferredOrientations([
 | 
			
		||||
    DeviceOrientation.portraitUp,
 | 
			
		||||
    DeviceOrientation.portraitDown,
 | 
			
		||||
  ]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum SnackType { info, success, error }
 | 
			
		||||
 | 
			
		||||
void showSnack(
 | 
			
		||||
    {required WidgetRef ref,
 | 
			
		||||
    required String text,
 | 
			
		||||
    SnackType type = SnackType.info,
 | 
			
		||||
    Duration duration = const Duration(seconds: 2)}) {
 | 
			
		||||
  final messengerKey = ref.read(scaffoldMessengerKeyProvider);
 | 
			
		||||
  if (messengerKey.currentState == null) {
 | 
			
		||||
    printPink('Cannot show snackbar, state == null');
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  final color = type == SnackType.info
 | 
			
		||||
      ? Styles.washedStone
 | 
			
		||||
      : type == SnackType.success
 | 
			
		||||
          ? Styles.seaweedGreen
 | 
			
		||||
          : Styles.expensesRed;
 | 
			
		||||
  final textStyle = TextStyle(
 | 
			
		||||
    fontSize: 16,
 | 
			
		||||
    color: color,
 | 
			
		||||
  );
 | 
			
		||||
  messengerKey.currentState!.showSnackBar(SnackBar(
 | 
			
		||||
    elevation: 8,
 | 
			
		||||
    backgroundColor: Styles.deepPurpleNurple,
 | 
			
		||||
    shape: const RoundedRectangleBorder(
 | 
			
		||||
      borderRadius: BorderRadius.only(
 | 
			
		||||
          topLeft: Radius.circular(20.0), topRight: Radius.circular(20.0)),
 | 
			
		||||
    ),
 | 
			
		||||
    content: Padding(
 | 
			
		||||
      padding: const EdgeInsets.all(8.0),
 | 
			
		||||
      child: Row(
 | 
			
		||||
        children: [
 | 
			
		||||
          Padding(
 | 
			
		||||
            padding: const EdgeInsets.only(right: 14.0),
 | 
			
		||||
            child: Icon(
 | 
			
		||||
                type == SnackType.success ? Icons.check_circle : Icons.info,
 | 
			
		||||
                color: color),
 | 
			
		||||
          ),
 | 
			
		||||
          Text(text, style: textStyle),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
  ));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool isEmailValid(String email) {
 | 
			
		||||
  final RegExp regex =
 | 
			
		||||
      RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$");
 | 
			
		||||
  return regex.hasMatch(email);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool isUsernameValid(String username) {
 | 
			
		||||
  final RegExp regex = RegExp(r"[a-zA-Z0-9._-]");
 | 
			
		||||
  return regex.hasMatch(username);
 | 
			
		||||
}
 | 
			
		||||
@ -1,35 +0,0 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class BudgetColorCircle extends StatelessWidget {
 | 
			
		||||
  const BudgetColorCircle(
 | 
			
		||||
      {super.key, required this.color, this.height = 18, this.width = 18});
 | 
			
		||||
 | 
			
		||||
  final double height;
 | 
			
		||||
  final double width;
 | 
			
		||||
  final Color color;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Stack(
 | 
			
		||||
      children: [
 | 
			
		||||
        Container(
 | 
			
		||||
          height: height,
 | 
			
		||||
          width: width,
 | 
			
		||||
          decoration: const BoxDecoration(
 | 
			
		||||
            shape: BoxShape.circle,
 | 
			
		||||
            color: Colors.white,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        Container(
 | 
			
		||||
          height: height,
 | 
			
		||||
          width: width,
 | 
			
		||||
          decoration: BoxDecoration(
 | 
			
		||||
            shape: BoxShape.circle,
 | 
			
		||||
            border: Border.all(color: Colors.black, width: 1.5),
 | 
			
		||||
            color: color,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,107 +0,0 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:rluv/global/styles.dart';
 | 
			
		||||
import 'package:rluv/global/utils.dart';
 | 
			
		||||
 | 
			
		||||
class UiButton extends StatefulWidget {
 | 
			
		||||
  const UiButton({
 | 
			
		||||
    super.key,
 | 
			
		||||
    required this.text,
 | 
			
		||||
    this.color = Styles.deepPurpleNurple,
 | 
			
		||||
    required this.onPressed,
 | 
			
		||||
    this.height,
 | 
			
		||||
    this.width,
 | 
			
		||||
    this.icon,
 | 
			
		||||
    this.showLoading = false,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  final String text;
 | 
			
		||||
  final Function? onPressed;
 | 
			
		||||
  final Color color;
 | 
			
		||||
  final double? width;
 | 
			
		||||
  final double? height;
 | 
			
		||||
  final Icon? icon;
 | 
			
		||||
  final bool showLoading;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<UiButton> createState() => _UiButtonState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _UiButtonState extends State<UiButton> {
 | 
			
		||||
  final double borderRadius = 12.0;
 | 
			
		||||
  bool loading = false;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final computedColor =
 | 
			
		||||
        widget.onPressed == null ? Styles.disabledButton : widget.color;
 | 
			
		||||
    final brightness = getBrightness(computedColor);
 | 
			
		||||
    return SizedBox(
 | 
			
		||||
      width: widget.width,
 | 
			
		||||
      height: widget.height,
 | 
			
		||||
      child: Padding(
 | 
			
		||||
        padding: const EdgeInsets.all(8.0),
 | 
			
		||||
        child: InkWell(
 | 
			
		||||
          borderRadius: BorderRadius.circular(borderRadius),
 | 
			
		||||
          onTap: widget.onPressed == null || loading
 | 
			
		||||
              ? null
 | 
			
		||||
              : widget.showLoading
 | 
			
		||||
                  ? () async {
 | 
			
		||||
                      setState(() => loading = true);
 | 
			
		||||
                      await widget.onPressed!();
 | 
			
		||||
                      setState(() => loading = false);
 | 
			
		||||
                    }
 | 
			
		||||
                  : () => widget.onPressed!(),
 | 
			
		||||
          child: Container(
 | 
			
		||||
            decoration: BoxDecoration(
 | 
			
		||||
              color: computedColor,
 | 
			
		||||
              boxShadow: const [
 | 
			
		||||
                BoxShadow(
 | 
			
		||||
                  color: Colors.black26,
 | 
			
		||||
                  blurRadius: 2.0,
 | 
			
		||||
                  spreadRadius: 2.0,
 | 
			
		||||
                  offset: Offset(2.5, 2.5),
 | 
			
		||||
                )
 | 
			
		||||
              ],
 | 
			
		||||
              borderRadius: BorderRadius.circular(borderRadius),
 | 
			
		||||
            ),
 | 
			
		||||
            child: Row(
 | 
			
		||||
              mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
              children: loading
 | 
			
		||||
                  ? [
 | 
			
		||||
                      Padding(
 | 
			
		||||
                        padding: const EdgeInsets.all(4.0),
 | 
			
		||||
                        child: SizedBox(
 | 
			
		||||
                            height: 26,
 | 
			
		||||
                            width: 26,
 | 
			
		||||
                            child: CircularProgressIndicator(
 | 
			
		||||
                                strokeWidth: 2.0,
 | 
			
		||||
                                color: brightness == Brightness.dark
 | 
			
		||||
                                    ? Styles.lavender
 | 
			
		||||
                                    : Styles.electricBlue)),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ]
 | 
			
		||||
                  : [
 | 
			
		||||
                      if (widget.icon != null) widget.icon!,
 | 
			
		||||
                      Padding(
 | 
			
		||||
                        padding: const EdgeInsets.all(8.0),
 | 
			
		||||
                        child: Text(
 | 
			
		||||
                          widget.text,
 | 
			
		||||
                          style: TextStyle(
 | 
			
		||||
                            fontSize: 16,
 | 
			
		||||
                            fontWeight: FontWeight.w600,
 | 
			
		||||
                            color: widget.onPressed == null
 | 
			
		||||
                                ? Styles.disabledButtonText
 | 
			
		||||
                                : brightness == Brightness.dark
 | 
			
		||||
                                    ? Styles.washedStone
 | 
			
		||||
                                    : Colors.black87,
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										239
									
								
								lib/main.dart
									
									
									
									
									
								
							
							
						
						@ -1,194 +1,115 @@
 | 
			
		||||
import 'package:animated_splash_screen/animated_splash_screen.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
import 'package:helpers/helpers/misc_build/build_media.dart';
 | 
			
		||||
import 'package:rluv/features/account/account_create_screen.dart';
 | 
			
		||||
import 'package:rluv/features/budget/screens/budget_overview.dart';
 | 
			
		||||
import 'package:rluv/features/notes/screens/notes_screen.dart';
 | 
			
		||||
import 'package:rluv/features/settings/screens/settings_screen.dart';
 | 
			
		||||
import 'package:rluv/global/store.dart';
 | 
			
		||||
import 'package:rluv/global/styles.dart';
 | 
			
		||||
import 'package:rluv/global/utils.dart';
 | 
			
		||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
			
		||||
 | 
			
		||||
import 'global/api.dart';
 | 
			
		||||
import 'global/widgets/ui_button.dart';
 | 
			
		||||
 | 
			
		||||
void main() async {
 | 
			
		||||
  runApp(const ProviderScope(child: MyApp()));
 | 
			
		||||
void main() {
 | 
			
		||||
  runApp(const MyApp());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MyApp extends ConsumerStatefulWidget {
 | 
			
		||||
class MyApp extends StatelessWidget {
 | 
			
		||||
  const MyApp({super.key});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ConsumerState<MyApp> createState() => _MyAppState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _MyAppState extends ConsumerState<MyApp> {
 | 
			
		||||
  // This widget is the root of your application.
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return MaterialApp(
 | 
			
		||||
      scaffoldMessengerKey: ref.read(scaffoldMessengerKeyProvider),
 | 
			
		||||
      debugShowCheckedModeBanner: false,
 | 
			
		||||
      title: 'Flutter Demo',
 | 
			
		||||
      theme: ThemeData(
 | 
			
		||||
        // This is the theme of your application.
 | 
			
		||||
        //
 | 
			
		||||
        // Try running your application with "flutter run". You'll see the
 | 
			
		||||
        // application has a blue toolbar. Then, without quitting the app, try
 | 
			
		||||
        // changing the primarySwatch below to Colors.green and then invoke
 | 
			
		||||
        // "hot reload" (press "r" in the console where you ran "flutter run",
 | 
			
		||||
        // or simply save your changes to "hot reload" in a Flutter IDE).
 | 
			
		||||
        // Notice that the counter didn't reset back to zero; the application
 | 
			
		||||
        // is not restarted.
 | 
			
		||||
        primarySwatch: Colors.blue,
 | 
			
		||||
      ),
 | 
			
		||||
      home: AnimatedSplashScreen.withScreenFunction(
 | 
			
		||||
        curve: Curves.easeInOutCubic,
 | 
			
		||||
        splashTransition: SplashTransition.scaleTransition,
 | 
			
		||||
        splashIconSize: 200,
 | 
			
		||||
        splash: Image.asset('assets/splash_icon.png'),
 | 
			
		||||
        backgroundColor: Styles.purpleNurple,
 | 
			
		||||
        duration: 1,
 | 
			
		||||
        screenFunction: () async {
 | 
			
		||||
          final prefs = await SharedPreferences.getInstance();
 | 
			
		||||
          ref.read(prefsProvider.notifier).state = prefs;
 | 
			
		||||
          Widget nextScreen = const AccountCreateScreen();
 | 
			
		||||
          if (ref.read(tokenProvider) != null) {
 | 
			
		||||
            nextScreen = const Home();
 | 
			
		||||
          }
 | 
			
		||||
          return nextScreen;
 | 
			
		||||
        },
 | 
			
		||||
      ),
 | 
			
		||||
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Home extends ConsumerStatefulWidget {
 | 
			
		||||
  const Home({super.key});
 | 
			
		||||
class MyHomePage extends StatefulWidget {
 | 
			
		||||
  const MyHomePage({super.key, required this.title});
 | 
			
		||||
 | 
			
		||||
  // This widget is the home page of your application. It is stateful, meaning
 | 
			
		||||
  // that it has a State object (defined below) that contains fields that affect
 | 
			
		||||
  // how it looks.
 | 
			
		||||
 | 
			
		||||
  // This class is the configuration for the state. It holds the values (in this
 | 
			
		||||
  // case the title) provided by the parent (in this case the App widget) and
 | 
			
		||||
  // used by the build method of the State. Fields in a Widget subclass are
 | 
			
		||||
  // always marked "final".
 | 
			
		||||
 | 
			
		||||
  final String title;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ConsumerState<Home> createState() => _HomeState();
 | 
			
		||||
  State<MyHomePage> createState() => _MyHomePageState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _HomeState extends ConsumerState<Home> {
 | 
			
		||||
  Map<String, dynamic> initData = {};
 | 
			
		||||
  final drawerColors = Styles.curatedColors;
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    setDevicePortraitOrientation();
 | 
			
		||||
    if (!ref.read(loadingStateProvider)) {
 | 
			
		||||
      ref.read(dashboardProvider.notifier).fetchDashboard();
 | 
			
		||||
    }
 | 
			
		||||
    WidgetsBinding.instance.addPostFrameCallback(
 | 
			
		||||
      (_) {
 | 
			
		||||
        ref.read(currentHomePageProvider.notifier).state =
 | 
			
		||||
            const BudgetOverviewScreen(initialData: {});
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
    super.initState();
 | 
			
		||||
class _MyHomePageState extends State<MyHomePage> {
 | 
			
		||||
  int _counter = 0;
 | 
			
		||||
 | 
			
		||||
  void _incrementCounter() {
 | 
			
		||||
    setState(() {
 | 
			
		||||
      // This call to setState tells the Flutter framework that something has
 | 
			
		||||
      // changed in this State, which causes it to rerun the build method below
 | 
			
		||||
      // so that the display can reflect the updated values. If we changed
 | 
			
		||||
      // _counter without calling setState(), then the build method would not be
 | 
			
		||||
      // called again, and so nothing would appear to happen.
 | 
			
		||||
      _counter++;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (ref.watch(tokenProvider) == null) {
 | 
			
		||||
      WidgetsBinding.instance.addPostFrameCallback((_) {
 | 
			
		||||
        Navigator.pushAndRemoveUntil(
 | 
			
		||||
          context,
 | 
			
		||||
          MaterialPageRoute(builder: (ctx) => const AccountCreateScreen()),
 | 
			
		||||
          (r) => false,
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (ref.watch(currentHomePageProvider).toString() ==
 | 
			
		||||
        "BudgetOverviewScreen") {
 | 
			
		||||
      initData = {};
 | 
			
		||||
    }
 | 
			
		||||
    // This method is rerun every time setState is called, for instance as done
 | 
			
		||||
    // by the _incrementCounter method above.
 | 
			
		||||
    //
 | 
			
		||||
    // The Flutter framework has been optimized to make rerunning build methods
 | 
			
		||||
    // fast, so that you can just rebuild anything that needs updating rather
 | 
			
		||||
    // than having to individually change instances of widgets.
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      key: ref.read(_scaffoldKeyProvider),
 | 
			
		||||
      resizeToAvoidBottomInset: false,
 | 
			
		||||
      drawer: Drawer(
 | 
			
		||||
        backgroundColor: Styles.purpleNurple,
 | 
			
		||||
        child: SafeArea(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        // Here we take the value from the MyHomePage object that was created by
 | 
			
		||||
        // the App.build method, and use it to set our appbar title.
 | 
			
		||||
        title: Text(widget.title),
 | 
			
		||||
      ),
 | 
			
		||||
      body: Center(
 | 
			
		||||
        // Center is a layout widget. It takes a single child and positions it
 | 
			
		||||
        // in the middle of the parent.
 | 
			
		||||
        child: Column(
 | 
			
		||||
            children: [
 | 
			
		||||
              SizedBox(height: BuildMedia(context).height * 0.15),
 | 
			
		||||
              UiButton(
 | 
			
		||||
                text: 'Budget',
 | 
			
		||||
                color: drawerColors[4],
 | 
			
		||||
                onPressed: () {
 | 
			
		||||
                  if (ref.read(currentHomePageProvider).toString() !=
 | 
			
		||||
                      "BudgetOverviewScreen") {
 | 
			
		||||
                    ref.read(currentHomePageProvider.notifier).state =
 | 
			
		||||
                        BudgetOverviewScreen(initialData: initData);
 | 
			
		||||
                  }
 | 
			
		||||
                  toggleDrawer();
 | 
			
		||||
                },
 | 
			
		||||
          // Column is also a layout widget. It takes a list of children and
 | 
			
		||||
          // arranges them vertically. By default, it sizes itself to fit its
 | 
			
		||||
          // children horizontally, and tries to be as tall as its parent.
 | 
			
		||||
          //
 | 
			
		||||
          // Invoke "debug painting" (press "p" in the console, choose the
 | 
			
		||||
          // "Toggle Debug Paint" action from the Flutter Inspector in Android
 | 
			
		||||
          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
 | 
			
		||||
          // to see the wireframe for each widget.
 | 
			
		||||
          //
 | 
			
		||||
          // Column has various properties to control how it sizes itself and
 | 
			
		||||
          // how it positions its children. Here we use mainAxisAlignment to
 | 
			
		||||
          // center the children vertically; the main axis here is the vertical
 | 
			
		||||
          // axis because Columns are vertical (the cross axis would be
 | 
			
		||||
          // horizontal).
 | 
			
		||||
          mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
          children: <Widget>[
 | 
			
		||||
            const Text(
 | 
			
		||||
              'You have pushed the button this many times:',
 | 
			
		||||
            ),
 | 
			
		||||
              UiButton(
 | 
			
		||||
                text: 'Notes',
 | 
			
		||||
                color: drawerColors[5],
 | 
			
		||||
                onPressed: () {
 | 
			
		||||
                  if (ref.read(currentHomePageProvider).toString() !=
 | 
			
		||||
                      "SharedNotesScreen") {
 | 
			
		||||
                    ref.read(currentHomePageProvider.notifier).state =
 | 
			
		||||
                        SharedNotesScreen(initialData: initData);
 | 
			
		||||
                  }
 | 
			
		||||
                  toggleDrawer();
 | 
			
		||||
                },
 | 
			
		||||
            Text(
 | 
			
		||||
              '$_counter',
 | 
			
		||||
              style: Theme.of(context).textTheme.headlineMedium,
 | 
			
		||||
            ),
 | 
			
		||||
              const Spacer(),
 | 
			
		||||
              UiButton(
 | 
			
		||||
                text: 'Settings',
 | 
			
		||||
                color: drawerColors[0],
 | 
			
		||||
                onPressed: () {
 | 
			
		||||
                  if (ref.read(currentHomePageProvider).toString() !=
 | 
			
		||||
                      "SettingsScreen") {
 | 
			
		||||
                    ref.read(currentHomePageProvider.notifier).state =
 | 
			
		||||
                        const SettingsScreen();
 | 
			
		||||
                  }
 | 
			
		||||
                  toggleDrawer();
 | 
			
		||||
                },
 | 
			
		||||
              ),
 | 
			
		||||
              const SizedBox(height: 24),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
      ),
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        backgroundColor: Styles.purpleNurple,
 | 
			
		||||
        title: Text(
 | 
			
		||||
          ref.watch(appBarTitleProvider),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
      body: ref.watch(currentHomePageProvider),
 | 
			
		||||
      floatingActionButton: FloatingActionButton(
 | 
			
		||||
        onPressed: _incrementCounter,
 | 
			
		||||
        tooltip: 'Increment',
 | 
			
		||||
        child: const Icon(Icons.add),
 | 
			
		||||
      ), // This trailing comma makes auto-formatting nicer for build methods.
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleDrawer() async {
 | 
			
		||||
    final key = ref.read(_scaffoldKeyProvider);
 | 
			
		||||
    if (key.currentState != null && key.currentState!.isDrawerOpen) {
 | 
			
		||||
      key.currentState!.openEndDrawer();
 | 
			
		||||
    } else if (key.currentState != null) {
 | 
			
		||||
      key.currentState!.openDrawer();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
final _scaffoldKeyProvider = Provider<GlobalKey<ScaffoldState>>(
 | 
			
		||||
  (ref) => GlobalKey<ScaffoldState>(),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
final currentHomePageProvider = StateProvider<Widget>(
 | 
			
		||||
  (ref) => Container(),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
final appBarTitleProvider = Provider<String>(
 | 
			
		||||
  (ref) {
 | 
			
		||||
    final currentPageName = ref.watch(currentHomePageProvider).toString();
 | 
			
		||||
    switch (currentPageName) {
 | 
			
		||||
      case 'BudgetOverviewScreen':
 | 
			
		||||
        return 'Budget';
 | 
			
		||||
      case 'SharedNotesScreen':
 | 
			
		||||
        return 'Notes';
 | 
			
		||||
      default:
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
final scaffoldMessengerKeyProvider =
 | 
			
		||||
    Provider<GlobalKey<ScaffoldMessengerState>>(
 | 
			
		||||
        (ref) => GlobalKey<ScaffoldMessengerState>());
 | 
			
		||||
 | 
			
		||||
@ -1,34 +0,0 @@
 | 
			
		||||
import 'package:json_annotation/json_annotation.dart';
 | 
			
		||||
 | 
			
		||||
import '../global/utils.dart';
 | 
			
		||||
 | 
			
		||||
part 'budget.g.dart';
 | 
			
		||||
 | 
			
		||||
@JsonSerializable()
 | 
			
		||||
class Budget {
 | 
			
		||||
  const Budget({
 | 
			
		||||
    this.id,
 | 
			
		||||
    required this.familyId,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.expectedIncome,
 | 
			
		||||
    required this.createdAt,
 | 
			
		||||
    required this.updatedAt,
 | 
			
		||||
    this.hide = false,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  final int? id;
 | 
			
		||||
  final int familyId;
 | 
			
		||||
  final double? expectedIncome;
 | 
			
		||||
  final String name;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: boolFromJson, toJson: boolToJson)
 | 
			
		||||
  final bool hide;
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime createdAt;
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime updatedAt;
 | 
			
		||||
 | 
			
		||||
  factory Budget.fromJson(Map<String, dynamic> json) => _$BudgetFromJson(json);
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() => _$BudgetToJson(this);
 | 
			
		||||
}
 | 
			
		||||
@ -1,57 +0,0 @@
 | 
			
		||||
import 'dart:ui';
 | 
			
		||||
 | 
			
		||||
import 'package:json_annotation/json_annotation.dart';
 | 
			
		||||
 | 
			
		||||
import '../global/utils.dart';
 | 
			
		||||
 | 
			
		||||
part 'budget_category_model.g.dart';
 | 
			
		||||
 | 
			
		||||
@JsonSerializable()
 | 
			
		||||
class BudgetCategory {
 | 
			
		||||
  const BudgetCategory({
 | 
			
		||||
    this.id,
 | 
			
		||||
    required this.budgetId,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.color,
 | 
			
		||||
    this.createdAt,
 | 
			
		||||
    this.updatedAt,
 | 
			
		||||
    required this.amount,
 | 
			
		||||
    this.hide = false,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  final int budgetId;
 | 
			
		||||
  final int? id;
 | 
			
		||||
  final String name;
 | 
			
		||||
  final double amount;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: boolFromJson, toJson: boolToJson)
 | 
			
		||||
  final bool hide;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: colorFromJson, toJson: colorToJson)
 | 
			
		||||
  final Color color;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime? createdAt;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime? updatedAt;
 | 
			
		||||
 | 
			
		||||
  factory BudgetCategory.fromJson(Map<String, dynamic> json) =>
 | 
			
		||||
      _$BudgetCategoryFromJson(json);
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() => _$BudgetCategoryToJson(this);
 | 
			
		||||
 | 
			
		||||
  factory BudgetCategory.copyWith(
 | 
			
		||||
          {required BudgetCategory category,
 | 
			
		||||
          required Map<String, dynamic> data}) =>
 | 
			
		||||
      BudgetCategory(
 | 
			
		||||
        id: data['id'] ?? category.id,
 | 
			
		||||
        budgetId: data['budget_id'] ?? category.budgetId,
 | 
			
		||||
        name: data['name'] ?? category.name,
 | 
			
		||||
        color: data['color'] ?? category.color,
 | 
			
		||||
        createdAt: data['created_at'] ?? category.createdAt,
 | 
			
		||||
        updatedAt: data['updated_at'] ?? category.updatedAt,
 | 
			
		||||
        amount: data['amount'] ?? category.amount,
 | 
			
		||||
        hide: data['hide'] ?? category.hide,
 | 
			
		||||
      );
 | 
			
		||||
}
 | 
			
		||||
@ -1,31 +0,0 @@
 | 
			
		||||
import 'package:json_annotation/json_annotation.dart';
 | 
			
		||||
 | 
			
		||||
import '../global/utils.dart';
 | 
			
		||||
 | 
			
		||||
part 'family_model.g.dart';
 | 
			
		||||
 | 
			
		||||
@JsonSerializable()
 | 
			
		||||
class FamilyModel {
 | 
			
		||||
  const FamilyModel({
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.code,
 | 
			
		||||
    required this.createdAt,
 | 
			
		||||
    required this.updatedAt,
 | 
			
		||||
    this.hide = false,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  final int id;
 | 
			
		||||
  final String? code;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: boolFromJson, toJson: boolToJson)
 | 
			
		||||
  final bool hide;
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime createdAt;
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime updatedAt;
 | 
			
		||||
 | 
			
		||||
  factory FamilyModel.fromJson(Map<String, dynamic> json) =>
 | 
			
		||||
      _$FamilyModelFromJson(json);
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() => _$FamilyModelToJson(this);
 | 
			
		||||
}
 | 
			
		||||
@ -1,68 +0,0 @@
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
import 'dart:ui';
 | 
			
		||||
 | 
			
		||||
import 'package:json_annotation/json_annotation.dart';
 | 
			
		||||
 | 
			
		||||
import '../global/utils.dart';
 | 
			
		||||
 | 
			
		||||
part 'shared_note.g.dart';
 | 
			
		||||
 | 
			
		||||
@JsonSerializable()
 | 
			
		||||
class SharedNote {
 | 
			
		||||
  SharedNote({
 | 
			
		||||
    this.id,
 | 
			
		||||
    required this.familyId,
 | 
			
		||||
    required this.createdByUserId,
 | 
			
		||||
    required this.content,
 | 
			
		||||
    required this.title,
 | 
			
		||||
    this.color,
 | 
			
		||||
    this.createdAt,
 | 
			
		||||
    this.updatedAt,
 | 
			
		||||
    required this.tagIds,
 | 
			
		||||
    this.isMarkdown = false,
 | 
			
		||||
    this.hide = false,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  final int? id;
 | 
			
		||||
  final int familyId, createdByUserId;
 | 
			
		||||
  String content, title;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: _tagIdsFromJson, toJson: _tagIdsToJson)
 | 
			
		||||
  List<int> tagIds;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: optionalColorFromJson, toJson: optionalColorToJson)
 | 
			
		||||
  Color? color;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: boolFromJson, toJson: boolToJson)
 | 
			
		||||
  bool isMarkdown;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: boolFromJson, toJson: boolToJson)
 | 
			
		||||
  final bool hide;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime? createdAt;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime? updatedAt;
 | 
			
		||||
 | 
			
		||||
  factory SharedNote.fromJson(Map<String, dynamic> json) =>
 | 
			
		||||
      _$SharedNoteFromJson(json);
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() => _$SharedNoteToJson(this);
 | 
			
		||||
 | 
			
		||||
  factory SharedNote.copy(SharedNote note) =>
 | 
			
		||||
      SharedNote.fromJson(note.toJson());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
List<int> _tagIdsFromJson(String tagJson) {
 | 
			
		||||
  final tagsMap = jsonDecode(tagJson) as List<dynamic>;
 | 
			
		||||
  return tagsMap
 | 
			
		||||
      .map(
 | 
			
		||||
        (e) => int.parse(e),
 | 
			
		||||
      )
 | 
			
		||||
      .toList();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
String _tagIdsToJson(List<int> tagIds) {
 | 
			
		||||
  return jsonEncode(tagIds);
 | 
			
		||||
}
 | 
			
		||||
@ -1,41 +0,0 @@
 | 
			
		||||
import 'package:json_annotation/json_annotation.dart';
 | 
			
		||||
 | 
			
		||||
import '../global/utils.dart';
 | 
			
		||||
 | 
			
		||||
part 'tag.g.dart';
 | 
			
		||||
 | 
			
		||||
enum TagType {
 | 
			
		||||
  note,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@JsonSerializable()
 | 
			
		||||
class Tag {
 | 
			
		||||
  const Tag({
 | 
			
		||||
    this.id,
 | 
			
		||||
    required this.familyId,
 | 
			
		||||
    required this.createdByUserId,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.type,
 | 
			
		||||
    this.createdAt,
 | 
			
		||||
    this.updatedAt,
 | 
			
		||||
    this.hide = false,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  final int? id;
 | 
			
		||||
  final int familyId, createdByUserId;
 | 
			
		||||
  final String name;
 | 
			
		||||
  final TagType type;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: boolFromJson, toJson: boolToJson)
 | 
			
		||||
  final bool hide;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime? createdAt;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime? updatedAt;
 | 
			
		||||
 | 
			
		||||
  factory Tag.fromJson(Map<String, dynamic> json) => _$TagFromJson(json);
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() => _$TagToJson(this);
 | 
			
		||||
}
 | 
			
		||||
@ -1,27 +0,0 @@
 | 
			
		||||
import 'package:json_annotation/json_annotation.dart';
 | 
			
		||||
 | 
			
		||||
import '../global/utils.dart';
 | 
			
		||||
 | 
			
		||||
part 'token.g.dart';
 | 
			
		||||
 | 
			
		||||
@JsonSerializable()
 | 
			
		||||
class Token {
 | 
			
		||||
  const Token({
 | 
			
		||||
    required this.userId,
 | 
			
		||||
    required this.familyId,
 | 
			
		||||
    required this.generatedAt,
 | 
			
		||||
    required this.expiresAt,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  final int familyId, userId;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime generatedAt;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime expiresAt;
 | 
			
		||||
 | 
			
		||||
  factory Token.fromJson(Map<String, dynamic> json) => _$TokenFromJson(json);
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() => _$TokenToJson(this);
 | 
			
		||||
}
 | 
			
		||||
@ -1,65 +0,0 @@
 | 
			
		||||
import 'package:json_annotation/json_annotation.dart';
 | 
			
		||||
 | 
			
		||||
import '../global/utils.dart';
 | 
			
		||||
 | 
			
		||||
part 'transaction_model.g.dart';
 | 
			
		||||
 | 
			
		||||
enum TransactionType {
 | 
			
		||||
  income,
 | 
			
		||||
  expense,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@JsonSerializable()
 | 
			
		||||
class Transaction {
 | 
			
		||||
  const Transaction({
 | 
			
		||||
    this.id,
 | 
			
		||||
    required this.amount,
 | 
			
		||||
    required this.type,
 | 
			
		||||
    required this.budgetId,
 | 
			
		||||
    this.budgetCategoryId,
 | 
			
		||||
    required this.createdByUserId,
 | 
			
		||||
    required this.date,
 | 
			
		||||
    this.memo,
 | 
			
		||||
    this.createdAt,
 | 
			
		||||
    this.updatedAt,
 | 
			
		||||
    this.hide = false,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  final int? id, budgetCategoryId;
 | 
			
		||||
  final int budgetId, createdByUserId;
 | 
			
		||||
  final double amount;
 | 
			
		||||
  final String? memo;
 | 
			
		||||
  final TransactionType type;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: boolFromJson, toJson: boolToJson)
 | 
			
		||||
  final bool hide;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime date;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime? createdAt;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime? updatedAt;
 | 
			
		||||
 | 
			
		||||
  factory Transaction.fromJson(Map<String, dynamic> json) =>
 | 
			
		||||
      _$TransactionFromJson(json);
 | 
			
		||||
 | 
			
		||||
  factory Transaction.copyWith(Transaction trans, Map<String, dynamic> data) =>
 | 
			
		||||
      Transaction(
 | 
			
		||||
        id: data['id'] ?? trans.id,
 | 
			
		||||
        amount: data['amount'] ?? trans.amount,
 | 
			
		||||
        type: data['type'] ?? trans.type,
 | 
			
		||||
        budgetId: data['budget_id'] ?? trans.budgetId,
 | 
			
		||||
        budgetCategoryId: data['budget_category_id'] ?? trans.budgetCategoryId,
 | 
			
		||||
        createdByUserId: data['created_by_user_id'] ?? trans.createdByUserId,
 | 
			
		||||
        date: data['date'] ?? trans.date,
 | 
			
		||||
        memo: data['memo'] ?? trans.memo,
 | 
			
		||||
        createdAt: data['created_at'] ?? trans.createdAt,
 | 
			
		||||
        updatedAt: data['updated_at'] ?? trans.updatedAt,
 | 
			
		||||
        hide: data['hide'] ?? trans.hide,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() => _$TransactionToJson(this);
 | 
			
		||||
}
 | 
			
		||||
@ -1,39 +0,0 @@
 | 
			
		||||
import 'package:json_annotation/json_annotation.dart';
 | 
			
		||||
 | 
			
		||||
import '../global/utils.dart';
 | 
			
		||||
 | 
			
		||||
part 'user.g.dart';
 | 
			
		||||
 | 
			
		||||
@JsonSerializable()
 | 
			
		||||
class User {
 | 
			
		||||
  const User({
 | 
			
		||||
    this.id,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.familyId,
 | 
			
		||||
    required this.budgetId,
 | 
			
		||||
    this.email,
 | 
			
		||||
    this.username,
 | 
			
		||||
    this.createdAt,
 | 
			
		||||
    this.updatedAt,
 | 
			
		||||
    this.lastActivityAt,
 | 
			
		||||
    this.hide = false,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  final int? id;
 | 
			
		||||
  final int familyId, budgetId;
 | 
			
		||||
  final String name;
 | 
			
		||||
  final String? username, email;
 | 
			
		||||
 | 
			
		||||
  @JsonKey(fromJson: boolFromJson, toJson: boolToJson)
 | 
			
		||||
  final bool hide;
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime? createdAt;
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime? updatedAt;
 | 
			
		||||
  @JsonKey(fromJson: dateFromJson, toJson: dateToJson)
 | 
			
		||||
  final DateTime? lastActivityAt;
 | 
			
		||||
 | 
			
		||||
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() => _$UserToJson(this);
 | 
			
		||||
}
 | 
			
		||||
@ -1 +0,0 @@
 | 
			
		||||
 | 
			
		||||
@ -6,10 +6,6 @@
 | 
			
		||||
 | 
			
		||||
#include "generated_plugin_registrant.h"
 | 
			
		||||
 | 
			
		||||
#include <url_launcher_linux/url_launcher_plugin.h>
 | 
			
		||||
 | 
			
		||||
void fl_register_plugins(FlPluginRegistry* registry) {
 | 
			
		||||
  g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
 | 
			
		||||
      fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
 | 
			
		||||
  url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
list(APPEND FLUTTER_PLUGIN_LIST
 | 
			
		||||
  url_launcher_linux
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										352
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						@ -5,34 +5,18 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: _fe_analyzer_shared
 | 
			
		||||
      sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
 | 
			
		||||
      sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "64.0.0"
 | 
			
		||||
    version: "61.0.0"
 | 
			
		||||
  analyzer:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: analyzer
 | 
			
		||||
      sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
 | 
			
		||||
      sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "6.2.0"
 | 
			
		||||
  animated_splash_screen:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: animated_splash_screen
 | 
			
		||||
      sha256: f45634db6ec4e8cf034c53e03f3bd83898a16fe3c9286bf5510b6831dfcf2124
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.3.0"
 | 
			
		||||
  archive:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: archive
 | 
			
		||||
      sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.3.7"
 | 
			
		||||
    version: "5.13.0"
 | 
			
		||||
  args:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -45,10 +29,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: async
 | 
			
		||||
      sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
 | 
			
		||||
      sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.11.0"
 | 
			
		||||
    version: "2.10.0"
 | 
			
		||||
  boolean_selector:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -61,10 +45,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: build
 | 
			
		||||
      sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
 | 
			
		||||
      sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.4.1"
 | 
			
		||||
    version: "2.3.1"
 | 
			
		||||
  build_config:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -77,10 +61,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: build_daemon
 | 
			
		||||
      sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65"
 | 
			
		||||
      sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "4.0.0"
 | 
			
		||||
    version: "3.1.1"
 | 
			
		||||
  build_resolvers:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -93,18 +77,18 @@ packages:
 | 
			
		||||
    dependency: "direct dev"
 | 
			
		||||
    description:
 | 
			
		||||
      name: build_runner
 | 
			
		||||
      sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b"
 | 
			
		||||
      sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.4.6"
 | 
			
		||||
    version: "2.3.3"
 | 
			
		||||
  build_runner_core:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: build_runner_core
 | 
			
		||||
      sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41"
 | 
			
		||||
      sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "7.2.10"
 | 
			
		||||
    version: "7.2.7+1"
 | 
			
		||||
  built_collection:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -125,10 +109,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: characters
 | 
			
		||||
      sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
 | 
			
		||||
      sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.3.0"
 | 
			
		||||
    version: "1.2.1"
 | 
			
		||||
  checked_yaml:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -157,10 +141,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: collection
 | 
			
		||||
      sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
 | 
			
		||||
      sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.17.2"
 | 
			
		||||
    version: "1.17.0"
 | 
			
		||||
  convert:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -185,14 +169,6 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.0.5"
 | 
			
		||||
  custom_refresh_indicator:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: custom_refresh_indicator
 | 
			
		||||
      sha256: "65a463f09623f6baf75e45e0c9034e9304810be3f5dfb00a54edde7252f4a524"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.2.1"
 | 
			
		||||
  dart_style:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -205,10 +181,10 @@ packages:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: dio
 | 
			
		||||
      sha256: ce75a1b40947fea0a0e16ce73337122a86762e38b982e1ccb909daa3b9bc4197
 | 
			
		||||
      sha256: a9d76e72985d7087eb7c5e7903224ae52b337131518d127c554b9405936752b8
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "5.3.2"
 | 
			
		||||
    version: "5.2.1+1"
 | 
			
		||||
  fake_async:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -221,10 +197,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: ffi
 | 
			
		||||
      sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
 | 
			
		||||
      sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.0"
 | 
			
		||||
    version: "2.0.2"
 | 
			
		||||
  file:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -246,14 +222,6 @@ packages:
 | 
			
		||||
    description: flutter
 | 
			
		||||
    source: sdk
 | 
			
		||||
    version: "0.0.0"
 | 
			
		||||
  flutter_highlight:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: flutter_highlight
 | 
			
		||||
      sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.7.0"
 | 
			
		||||
  flutter_lints:
 | 
			
		||||
    dependency: "direct dev"
 | 
			
		||||
    description:
 | 
			
		||||
@ -266,10 +234,10 @@ packages:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: flutter_riverpod
 | 
			
		||||
      sha256: b6cb0041c6c11cefb2dcb97ef436eba43c6d41287ac6d8ca93e02a497f53a4f3
 | 
			
		||||
      sha256: b83ac5827baadefd331ea1d85110f34645827ea234ccabf53a655f41901a9bf4
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.3.7"
 | 
			
		||||
    version: "2.3.6"
 | 
			
		||||
  flutter_test:
 | 
			
		||||
    dependency: "direct dev"
 | 
			
		||||
    description: flutter
 | 
			
		||||
@ -312,14 +280,6 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.2.3"
 | 
			
		||||
  highlight:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: highlight
 | 
			
		||||
      sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.7.0"
 | 
			
		||||
  http_multi_server:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -336,30 +296,6 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "4.0.2"
 | 
			
		||||
  icons_launcher:
 | 
			
		||||
    dependency: "direct dev"
 | 
			
		||||
    description:
 | 
			
		||||
      name: icons_launcher
 | 
			
		||||
      sha256: af05397792f6d82b93375a8a0253b8db0d3f816ef1dd1bf5c35cbab55321d327
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.3"
 | 
			
		||||
  image:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: image
 | 
			
		||||
      sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "4.0.17"
 | 
			
		||||
  intl:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: intl
 | 
			
		||||
      sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.18.1"
 | 
			
		||||
  io:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -372,10 +308,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: js
 | 
			
		||||
      sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
 | 
			
		||||
      sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.6.7"
 | 
			
		||||
    version: "0.6.5"
 | 
			
		||||
  json_annotation:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
@ -388,26 +324,18 @@ packages:
 | 
			
		||||
    dependency: "direct dev"
 | 
			
		||||
    description:
 | 
			
		||||
      name: json_serializable
 | 
			
		||||
      sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969
 | 
			
		||||
      sha256: "43793352f90efa5d8b251893a63d767b2f7c833120e3cc02adad55eefec04dc7"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "6.7.1"
 | 
			
		||||
  jwt_decoder:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: jwt_decoder
 | 
			
		||||
      sha256: "54774aebf83f2923b99e6416b4ea915d47af3bde56884eb622de85feabbc559f"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.0.1"
 | 
			
		||||
    version: "6.6.2"
 | 
			
		||||
  lints:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: lints
 | 
			
		||||
      sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
 | 
			
		||||
      sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.1"
 | 
			
		||||
    version: "2.0.1"
 | 
			
		||||
  logging:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -416,46 +344,30 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.2.0"
 | 
			
		||||
  markdown:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: markdown
 | 
			
		||||
      sha256: acf35edccc0463a9d7384e437c015a3535772e09714cf60e07eeef3a15870dcd
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "7.1.1"
 | 
			
		||||
  markdown_widget:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: markdown_widget
 | 
			
		||||
      sha256: "088feae6be2dd527c7dd54e06ad104a3e70505aff2ce14a3b464482551a0e273"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.2.0"
 | 
			
		||||
  matcher:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: matcher
 | 
			
		||||
      sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
 | 
			
		||||
      sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.12.16"
 | 
			
		||||
    version: "0.12.13"
 | 
			
		||||
  material_color_utilities:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: material_color_utilities
 | 
			
		||||
      sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
 | 
			
		||||
      sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.5.0"
 | 
			
		||||
    version: "0.2.0"
 | 
			
		||||
  meta:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: meta
 | 
			
		||||
      sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
 | 
			
		||||
      sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.9.1"
 | 
			
		||||
    version: "1.8.0"
 | 
			
		||||
  mime:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -472,78 +384,54 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.0"
 | 
			
		||||
  page_transition:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: page_transition
 | 
			
		||||
      sha256: a7694bc120b7064a7f57c336914bb8885acf4f70bb3772c30c2fcfe6a85e43ff
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.0.9"
 | 
			
		||||
  path:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: path
 | 
			
		||||
      sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
 | 
			
		||||
      sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.8.3"
 | 
			
		||||
    version: "1.8.2"
 | 
			
		||||
  path_provider_linux:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: path_provider_linux
 | 
			
		||||
      sha256: ba2b77f0c52a33db09fc8caf85b12df691bf28d983e84cf87ff6d693cfa007b3
 | 
			
		||||
      sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.2.0"
 | 
			
		||||
    version: "2.1.11"
 | 
			
		||||
  path_provider_platform_interface:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: path_provider_platform_interface
 | 
			
		||||
      sha256: bced5679c7df11190e1ddc35f3222c858f328fff85c3942e46e7f5589bf9eb84
 | 
			
		||||
      sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.0"
 | 
			
		||||
    version: "2.0.6"
 | 
			
		||||
  path_provider_windows:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: path_provider_windows
 | 
			
		||||
      sha256: ee0e0d164516b90ae1f970bdf29f726f1aa730d7cfc449ecc74c495378b705da
 | 
			
		||||
      sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.2.0"
 | 
			
		||||
  petitparser:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: petitparser
 | 
			
		||||
      sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "5.4.0"
 | 
			
		||||
    version: "2.1.7"
 | 
			
		||||
  platform:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: platform
 | 
			
		||||
      sha256: "57c07bf82207aee366dfaa3867b3164e4f03a238a461a11b0e8a3a510d51203d"
 | 
			
		||||
      sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.1.1"
 | 
			
		||||
    version: "3.1.0"
 | 
			
		||||
  plugin_platform_interface:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: plugin_platform_interface
 | 
			
		||||
      sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd"
 | 
			
		||||
      sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.5"
 | 
			
		||||
  pointycastle:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: pointycastle
 | 
			
		||||
      sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.7.3"
 | 
			
		||||
    version: "2.1.4"
 | 
			
		||||
  pool:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -572,18 +460,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: riverpod
 | 
			
		||||
      sha256: b0657b5b30c81a3184bdaab353045f0a403ebd60bb381591a8b7ad77dcade793
 | 
			
		||||
      sha256: "80e48bebc83010d5e67a11c9514af6b44bbac1ec77b4333c8ea65dbc79e2d8ef"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.3.7"
 | 
			
		||||
  scroll_to_index:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: scroll_to_index
 | 
			
		||||
      sha256: b707546e7500d9f070d63e5acf74fd437ec7eeeb68d3412ef7b0afada0b4f176
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.0.1"
 | 
			
		||||
    version: "2.3.6"
 | 
			
		||||
  shared_preferences:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
@ -604,10 +484,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: shared_preferences_foundation
 | 
			
		||||
      sha256: d29753996d8eb8f7619a1f13df6ce65e34bc107bef6330739ed76f18b22310ef
 | 
			
		||||
      sha256: f39696b83e844923b642ce9dd4bd31736c17e697f6731a5adf445b1274cf3cd4
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.3.3"
 | 
			
		||||
    version: "2.3.2"
 | 
			
		||||
  shared_preferences_linux:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -665,10 +545,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: source_gen
 | 
			
		||||
      sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16
 | 
			
		||||
      sha256: "373f96cf5a8744bc9816c1ff41cf5391bbdbe3d7a96fe98c622b6738a8a7bd33"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.4.0"
 | 
			
		||||
    version: "1.3.2"
 | 
			
		||||
  source_helper:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -681,10 +561,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: source_span
 | 
			
		||||
      sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
 | 
			
		||||
      sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.10.0"
 | 
			
		||||
    version: "1.9.1"
 | 
			
		||||
  stack_trace:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -697,10 +577,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: state_notifier
 | 
			
		||||
      sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb
 | 
			
		||||
      sha256: "8fe42610f179b843b12371e40db58c9444f8757f8b69d181c97e50787caed289"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.0.0"
 | 
			
		||||
    version: "0.7.2+1"
 | 
			
		||||
  stream_channel:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -737,10 +617,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: test_api
 | 
			
		||||
      sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
 | 
			
		||||
      sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.6.0"
 | 
			
		||||
    version: "0.4.16"
 | 
			
		||||
  timing:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -757,78 +637,6 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.3.2"
 | 
			
		||||
  universal_io:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: universal_io
 | 
			
		||||
      sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.2.2"
 | 
			
		||||
  url_launcher:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: url_launcher
 | 
			
		||||
      sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "6.1.12"
 | 
			
		||||
  url_launcher_android:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: url_launcher_android
 | 
			
		||||
      sha256: "3dd2388cc0c42912eee04434531a26a82512b9cb1827e0214430c9bcbddfe025"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "6.0.38"
 | 
			
		||||
  url_launcher_ios:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: url_launcher_ios
 | 
			
		||||
      sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "6.1.4"
 | 
			
		||||
  url_launcher_linux:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: url_launcher_linux
 | 
			
		||||
      sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.0.5"
 | 
			
		||||
  url_launcher_macos:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: url_launcher_macos
 | 
			
		||||
      sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.0.6"
 | 
			
		||||
  url_launcher_platform_interface:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: url_launcher_platform_interface
 | 
			
		||||
      sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.3"
 | 
			
		||||
  url_launcher_web:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: url_launcher_web
 | 
			
		||||
      sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.0.18"
 | 
			
		||||
  url_launcher_windows:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: url_launcher_windows
 | 
			
		||||
      sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.0.7"
 | 
			
		||||
  uuid:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
@ -845,30 +653,14 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.4"
 | 
			
		||||
  visibility_detector:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: visibility_detector
 | 
			
		||||
      sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.4.0+2"
 | 
			
		||||
  watcher:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: watcher
 | 
			
		||||
      sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
 | 
			
		||||
      sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.1.0"
 | 
			
		||||
  web:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: web
 | 
			
		||||
      sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.1.4-beta"
 | 
			
		||||
    version: "1.0.2"
 | 
			
		||||
  web_socket_channel:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -881,26 +673,18 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: win32
 | 
			
		||||
      sha256: f2add6fa510d3ae152903412227bda57d0d5a8da61d2c39c1fb022c9429a41c0
 | 
			
		||||
      sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "5.0.6"
 | 
			
		||||
    version: "4.1.4"
 | 
			
		||||
  xdg_directories:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: xdg_directories
 | 
			
		||||
      sha256: f0c26453a2d47aa4c2570c6a033246a3fc62da2fe23c7ffdd0a7495086dc0247
 | 
			
		||||
      sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.0.2"
 | 
			
		||||
  xml:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: xml
 | 
			
		||||
      sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "6.3.0"
 | 
			
		||||
    version: "1.0.1"
 | 
			
		||||
  yaml:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -910,5 +694,5 @@ packages:
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.1.2"
 | 
			
		||||
sdks:
 | 
			
		||||
  dart: ">=3.1.0-185.0.dev <4.0.0"
 | 
			
		||||
  flutter: ">=3.10.0"
 | 
			
		||||
  dart: ">=2.19.6 <3.0.0"
 | 
			
		||||
  flutter: ">=3.3.0"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								pubspec.yaml
									
									
									
									
									
								
							
							
						
						@ -41,11 +41,6 @@ dependencies:
 | 
			
		||||
  uuid: ^3.0.7
 | 
			
		||||
  json_annotation: ^4.8.0
 | 
			
		||||
  shared_preferences: ^2.1.2
 | 
			
		||||
  intl: ^0.18.1
 | 
			
		||||
  markdown_widget: ^2.2.0
 | 
			
		||||
  custom_refresh_indicator: ^2.2.1
 | 
			
		||||
  jwt_decoder: ^2.0.1
 | 
			
		||||
  animated_splash_screen: ^1.3.0
 | 
			
		||||
 | 
			
		||||
dev_dependencies:
 | 
			
		||||
  flutter_test:
 | 
			
		||||
@ -54,7 +49,6 @@ dev_dependencies:
 | 
			
		||||
  build_runner: ^2.3.3
 | 
			
		||||
  json_serializable: ^6.6.0
 | 
			
		||||
  flutter_lints: ^2.0.0
 | 
			
		||||
  icons_launcher: ^2.1.3
 | 
			
		||||
 | 
			
		||||
# For information on the generic Dart part of this file, see the
 | 
			
		||||
# following page: https://dart.dev/tools/pub/pubspec
 | 
			
		||||
@ -68,8 +62,9 @@ flutter:
 | 
			
		||||
  uses-material-design: true
 | 
			
		||||
 | 
			
		||||
  # To add assets to your application, add an assets section, like this:
 | 
			
		||||
  assets:
 | 
			
		||||
    - assets/
 | 
			
		||||
  # assets:
 | 
			
		||||
  #   - images/a_dot_burr.jpeg
 | 
			
		||||
  #   - 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
 | 
			
		||||
@ -96,13 +91,3 @@ flutter:
 | 
			
		||||
  #
 | 
			
		||||
  # For details regarding fonts from package dependencies,
 | 
			
		||||
  # see https://flutter.dev/custom-fonts/#from-packages
 | 
			
		||||
icons_launcher:
 | 
			
		||||
  image_path: "assets/app_icon.png"
 | 
			
		||||
  platforms:
 | 
			
		||||
    android:
 | 
			
		||||
      enable: true
 | 
			
		||||
      adaptive_background_color: "#A188A6"
 | 
			
		||||
      adaptive_foreground_image: "assets/app_icon1.png"
 | 
			
		||||
      adaptize_round_image: "assets/app_icon_round.png"
 | 
			
		||||
    ios:
 | 
			
		||||
      enable: true
 | 
			
		||||















