├── .editorconfig
├── .github
└── workflows
│ ├── android.yml
│ ├── ios.yml
│ └── lints.yml
├── .gitignore
├── .metadata
├── .vscode
├── launch.json
└── settings.json
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── Makefile.toml
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── res
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
├── chainspec.json
├── logo.png
└── svg
│ └── sun.svg
├── cbindgen.toml
├── ios
├── .gitignore
├── Flutter
│ ├── .last_build_id
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-1024x1024@1x.png
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-50x50@1x.png
│ │ ├── Icon-App-50x50@2x.png
│ │ ├── Icon-App-57x57@1x.png
│ │ ├── Icon-App-57x57@2x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-72x72@1x.png
│ │ ├── Icon-App-72x72@2x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ └── Icon-App-83.5x83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
├── lib
├── app.dart
├── constants.dart
├── core
│ ├── core.dart
│ ├── extensions
│ │ ├── extensions.dart
│ │ └── router.dart
│ └── register_module.dart
├── main.dart
├── models
│ ├── account_model.dart
│ ├── bounty_model.dart
│ ├── bounty_submission_model.dart
│ ├── device_model.dart
│ ├── github_issue_model.dart
│ ├── identity
│ │ ├── github.dart
│ │ └── service.dart
│ └── models.dart
├── router
│ ├── router.dart
│ └── router.gr.dart
├── screens
│ ├── account_screen.dart
│ ├── blank_screen.dart
│ ├── bounty_screen.dart
│ ├── create_bounty_screen.dart
│ ├── devices_screen.dart
│ ├── generate_account_screen.dart
│ ├── identities_screen.dart
│ ├── intro_screen.dart
│ ├── logs_screen.dart
│ ├── main_screen.dart
│ ├── paper_key_screen.dart
│ ├── recover_account_screen.dart
│ ├── screens.dart
│ ├── splash_screen.dart
│ ├── submit_for_bounty_screen.dart
│ └── wallet_transfer_screen.dart
├── services
│ ├── account_service.dart
│ ├── bounty_service.dart
│ ├── client
│ │ ├── client_service.dart
│ │ ├── dev_client_service.dart
│ │ ├── prod_client_service.dart
│ │ └── sunshine_client_service.dart
│ ├── device_service.dart
│ ├── github_service.dart
│ ├── identity_service.dart
│ ├── key_service.dart
│ ├── logger_service.dart
│ ├── path_provider_service.dart
│ ├── services.dart
│ └── wallet_service.dart
├── setup.dart
├── setup.iconfig.dart
├── sunshine.dart
├── ui
│ ├── bounty_item.dart
│ ├── button.dart
│ ├── header_text.dart
│ ├── hint_text.dart
│ ├── input.dart
│ ├── list_cell.dart
│ ├── my_app_bar.dart
│ ├── submission_item.dart
│ ├── sunshine_loading.dart
│ ├── sunshine_logo.dart
│ └── ui.dart
└── utils
│ ├── github_issue_regex.dart
│ ├── random_issue.dart
│ └── utils.dart
├── native
└── sunshine
│ ├── Cargo.toml
│ ├── binding.h
│ └── src
│ └── lib.rs
├── packages
├── sunshine
│ ├── .gitignore
│ ├── .metadata
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── android
│ │ ├── .gitignore
│ │ ├── app
│ │ │ └── src
│ │ │ │ └── main
│ │ │ │ └── java
│ │ │ │ └── io
│ │ │ │ └── flutter
│ │ │ │ └── plugins
│ │ │ │ └── GeneratedPluginRegistrant.java
│ │ ├── build.gradle
│ │ ├── gradle.properties
│ │ ├── gradle
│ │ │ └── wrapper
│ │ │ │ └── gradle-wrapper.properties
│ │ ├── settings.gradle
│ │ └── src
│ │ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── foundation
│ │ │ └── sunshine
│ │ │ └── sunshine
│ │ │ └── SunshinePlugin.java
│ ├── ios
│ │ ├── .gitignore
│ │ ├── Assets
│ │ │ └── .gitkeep
│ │ ├── Classes
│ │ │ ├── SunshinePlugin.h
│ │ │ ├── SunshinePlugin.m
│ │ │ ├── SwiftSunshinePlugin.swift
│ │ │ └── binding.h
│ │ └── sunshine_ffi.podspec
│ ├── lib
│ │ ├── constants.dart
│ │ ├── dto.dart
│ │ ├── ffi.dart
│ │ └── sunshine_client.dart
│ ├── pubspec.lock
│ └── pubspec.yaml
└── timeago
│ ├── .gitignore
│ ├── .metadata
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── lib
│ └── timeago.dart
│ ├── pubspec.lock
│ └── pubspec.yaml
├── pubspec.lock
├── pubspec.yaml
├── rustfmt.toml
└── test_driver
├── app.dart
└── app_test.dart
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 | indent_style = space
13 | indent_size = 4
14 |
15 | [*.md]
16 | trim_trailing_whitespace = false
17 |
18 | [*.yml]
19 | indent_size = 2
20 |
21 | [*.yaml]
22 | indent_size = 2
23 |
24 | [*.dart]
25 | indent_brace_style = K&R
26 | indent_size = 2
27 |
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 |
3 | name: Android
4 |
5 | jobs:
6 | ci:
7 | name: Compile, Build and Release
8 | runs-on: macos-latest
9 | steps:
10 | - name: Cache LLVM and Clang
11 | id: cache-llvm
12 | uses: actions/cache@v2
13 | with:
14 | path: ${{ runner.temp }}/llvm/10.0
15 | key: ${{ runner.os }}-cached-llvm-10.0
16 |
17 | - name: Install LLVM and Clang
18 | uses: KyleMayes/install-llvm-action@v1
19 | with:
20 | version: "10.0"
21 | directory: ${{ runner.temp }}/llvm/10.0
22 | cached: ${{ steps.cache-llvm.outputs.cache-hit }}
23 |
24 | - name: Checkout sources
25 | uses: actions/checkout@v2
26 |
27 | - name: Install Rust Toolchain
28 | uses: actions-rs/toolchain@v1
29 | with:
30 | profile: minimal
31 | toolchain: nightly
32 | override: true
33 | components: rustfmt
34 |
35 | - name: Install WASM Target
36 | run: rustup target add wasm32-unknown-unknown
37 |
38 | - name: Cache Cargo
39 | uses: actions/cache@v2
40 | with:
41 | path: |
42 | ~/.cargo/registry
43 | ~/.cargo/git
44 | ~/.cargo/bin
45 | target/aarch64-linux-android
46 | target/armv7-linux-androideabi
47 | target/x86_64-linux-android
48 | target/i686-linux-android
49 | target/aarch64-apple-ios
50 | target/x86_64-apple-ios
51 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
52 |
53 | - name: Install Cargo Tools (cargo-make)
54 | uses: actions-rs/cargo@v1
55 | continue-on-error: true
56 | with:
57 | command: install
58 | args: cargo-make
59 |
60 | - name: Install Cargo Tools (cbindgen, v0.14.4)
61 | uses: actions-rs/cargo@v1
62 | continue-on-error: true
63 | with:
64 | command: install
65 | args: cbindgen --version 0.14.4
66 |
67 | - name: Install Cargo Tools (dart-bindgen)
68 | uses: actions-rs/cargo@v1
69 | continue-on-error: true
70 | with:
71 | command: install
72 | args: dart-bindgen --features cli
73 |
74 | - name: Setup Flutter
75 | uses: subosito/flutter-action@v1
76 | with:
77 | channel: "stable"
78 |
79 | - name: Run Flutter pub get
80 | run: flutter pub get
81 |
82 | - name: Set up JDK 1.8
83 | uses: actions/setup-java@v1
84 | with:
85 | java-version: 1.8
86 |
87 | - name: Setup Android SDK
88 | uses: android-actions/setup-android@v1
89 |
90 | - name: Cache NDK
91 | id: cache-ndk
92 | uses: actions/cache@v2
93 | with:
94 | path: /Users/runner/android/ndk-bundle
95 | key: ${{ runner.os }}-cached-ndk
96 |
97 | - name: Install Android NDK
98 | if: steps.cache-ndk.outputs.cache-hit != 'true'
99 | run: $ANDROID_SDK_ROOT/tools/bin/sdkmanager ndk-bundle
100 |
101 | - name: Set Android NDK Env
102 | run: echo '::set-env name=ANDROID_NDK_HOME::/Users/runner/android/ndk-bundle'
103 |
104 | - name: Install Android Targets
105 | run: rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android
106 |
107 | - name: Create Release (Android)
108 | id: create_release
109 | uses: ChanTsune/release-with-commit@v1.0.0
110 | with:
111 | regexp: "Release (\\d+([.]\\d+)*)\n*((\\s|\\S)+)"
112 | regexp_options: "us"
113 | release_name: "version $1"
114 | tag_name: "v$1"
115 | body: "$3"
116 | draft: false
117 | prerelease: false
118 | env:
119 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
120 |
121 | - name: Run cargo make android (Debug)
122 | if: steps.create_release.outputs.upload_url == null
123 | uses: actions-rs/cargo@v1
124 | continue-on-error: false
125 | with:
126 | command: make
127 | args: android-dev
128 |
129 | - name: Build APK (x86_64, Debug)
130 | if: steps.create_release.outputs.upload_url == null
131 | run: flutter build apk --debug
132 |
133 | - name: Run Tests (Android Emulator)
134 | if: steps.create_release.outputs.upload_url == null
135 | uses: reactivecircus/android-emulator-runner@v2
136 | with:
137 | api-level: 28
138 | arch: x86_64
139 | script: flutter drive --target=test_driver/app.dart
140 |
141 | - name: Run cargo make android (Release)
142 | if: steps.create_release.outputs.upload_url != null
143 | uses: actions-rs/cargo@v1
144 | continue-on-error: false
145 | with:
146 | command: make
147 | args: android-arm --profile release
148 |
149 | - name: Build APK (arm64, Release)
150 | if: steps.create_release.outputs.upload_url == null
151 | run: flutter build apk --release --target-platform android-arm64
152 |
153 | - name: Upload Release (Android)
154 | id: upload-release-asset
155 | if: steps.create_release.outputs.upload_url != null
156 | uses: actions/upload-release-asset@v1
157 | env:
158 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
159 | with:
160 | upload_url: ${{ steps.create_release.outputs.upload_url }}
161 | asset_path: ./build/app/outputs/flutter-apk/app-release.apk
162 | asset_name: sunshine-release-arm64.apk
163 | asset_content_type: application/octet-stream
164 |
--------------------------------------------------------------------------------
/.github/workflows/ios.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 |
3 | name: iOS
4 |
5 | jobs:
6 | ci:
7 | name: Compile, Build
8 | runs-on: macos-latest
9 | steps:
10 | - name: Cache LLVM and Clang
11 | id: cache-llvm
12 | uses: actions/cache@v2
13 | with:
14 | path: ${{ runner.temp }}/llvm/10.0
15 | key: ${{ runner.os }}-cached-llvm-10.0
16 |
17 | - name: Install LLVM and Clang
18 | uses: KyleMayes/install-llvm-action@v1
19 | with:
20 | version: "10.0"
21 | directory: ${{ runner.temp }}/llvm/10.0
22 | cached: ${{ steps.cache-llvm.outputs.cache-hit }}
23 |
24 | - name: Checkout sources
25 | uses: actions/checkout@v2
26 |
27 | - name: Install Rust Toolchain
28 | uses: actions-rs/toolchain@v1
29 | with:
30 | profile: minimal
31 | toolchain: nightly
32 | override: true
33 | components: rustfmt
34 |
35 | - name: Install WASM Target
36 | run: rustup target add wasm32-unknown-unknown
37 |
38 | - name: Cache Cargo
39 | uses: actions/cache@v2
40 | with:
41 | path: |
42 | ~/.cargo/registry
43 | ~/.cargo/git
44 | ~/.cargo/bin
45 | target/aarch64-linux-android
46 | target/armv7-linux-androideabi
47 | target/x86_64-linux-android
48 | target/i686-linux-android
49 | target/aarch64-apple-ios
50 | target/x86_64-apple-ios
51 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
52 |
53 | - name: Install Cargo Tools (cargo-make)
54 | uses: actions-rs/cargo@v1
55 | continue-on-error: true
56 | with:
57 | command: install
58 | args: cargo-make
59 |
60 | - name: Install Cargo Tools (cbindgen, v0.14.4)
61 | uses: actions-rs/cargo@v1
62 | continue-on-error: true
63 | with:
64 | command: install
65 | args: cbindgen --version 0.14.4
66 |
67 | - name: Install Cargo Tools (dart-bindgen)
68 | uses: actions-rs/cargo@v1
69 | continue-on-error: true
70 | with:
71 | command: install
72 | args: dart-bindgen --features cli
73 |
74 | - name: Setup Flutter
75 | uses: subosito/flutter-action@v1
76 | with:
77 | channel: "stable"
78 |
79 | - name: Run Flutter pub get
80 | run: flutter pub get
81 |
82 | - name: Install iOS Targets
83 | run: rustup target add aarch64-apple-ios x86_64-apple-ios
84 |
85 | - name: Run cargo make ios (Release)
86 | uses: actions-rs/cargo@v1
87 | continue-on-error: false
88 | with:
89 | command: make
90 | args: ios --profile release
91 |
92 | - name: Build iOS App (Simulator, Debug)
93 | run: flutter build ios --debug --no-codesign --simulator
94 |
--------------------------------------------------------------------------------
/.github/workflows/lints.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 |
3 | name: Lints
4 |
5 | jobs:
6 | combo:
7 | name: Clippy + rustfmt
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Cache LLVM and Clang
11 | id: cache-llvm
12 | uses: actions/cache@v2
13 | with:
14 | path: ${{ runner.temp }}/llvm/10.0
15 | key: ${{ runner.os }}-cached-llvm-10.0
16 |
17 | - name: Install LLVM and Clang
18 | uses: KyleMayes/install-llvm-action@v1
19 | with:
20 | version: "10.0"
21 | directory: ${{ runner.temp }}/llvm/10.0
22 | cached: ${{ steps.cache-llvm.outputs.cache-hit }}
23 |
24 | - name: Checkout sources
25 | uses: actions/checkout@v2
26 |
27 | - name: Install nightly toolchain
28 | uses: actions-rs/toolchain@v1
29 | with:
30 | profile: minimal
31 | toolchain: nightly
32 | override: true
33 | components: rustfmt, clippy
34 |
35 | - name: Install WASM Target
36 | run: rustup target add wasm32-unknown-unknown
37 |
38 | - name: Run cargo fmt
39 | uses: actions-rs/cargo@v1
40 | continue-on-error: false
41 | with:
42 | command: fmt
43 | args: --all -- --check
44 |
45 | - name: Cache Cargo
46 | uses: actions/cache@v2
47 | with:
48 | path: |
49 | ~/.cargo/registry
50 | ~/.cargo/git
51 | ~/.cargo/bin
52 | target
53 | key: ${{ runner.os }}-lint-cargo-${{ hashFiles('**/Cargo.lock') }}
54 |
55 | - name: Run cargo clippy
56 | uses: actions-rs/cargo@v1
57 | continue-on-error: false
58 | with:
59 | command: clippy
60 | args: -- -D warnings
61 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | build/
32 |
33 | # Web related
34 | lib/generated_plugin_registrant.dart
35 |
36 | # Symbolication related
37 | app.*.symbols
38 |
39 | # Obfuscation related
40 | app.*.map.json
41 |
42 | # Exceptions to above rules.
43 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
44 |
45 | /target
46 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: de1e5729165b61829a8fa7c41b449c6c7ad74c84
8 | channel: dev
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Debug Mode",
9 | "request": "launch",
10 | "flutterMode": "debug",
11 | "type": "dart"
12 | },
13 | {
14 | "name": "Profile Mode",
15 | "request": "launch",
16 | "flutterMode": "profile",
17 | "type": "dart"
18 | },
19 | {
20 | "name": "Release Mode",
21 | "request": "launch",
22 | "flutterMode": "release",
23 | "type": "dart"
24 | }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dart.analysisExcludedFolders": [
3 | "packages/sunshine/lib/ffi.dart",
4 | "lib/setup.iconfig.dart",
5 | "lib/router/router.gr.dart"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = ["native/*"]
3 |
4 | [patch.crates-io]
5 | substrate-subxt = { git = "https://github.com/dvc94ch/substrate-subxt", branch = "different-assert" }
6 |
7 | # [profile.dev]
8 | # panic = 'abort'
9 |
10 | # [profile.release]
11 | # lto = true
12 | # panic = 'abort'
13 | # codegen-units = 1
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sunshine Flutter Frontend
2 |
3 | 
4 | 
5 | 
6 |
7 | This project uses [cargo make](https://github.com/sagiegurari/cargo-make) to combine the build commands for compiling the Rust `sunshine` module and linking it to the Flutter interface. This structure will eventually enable our substrate light client to talk directly to the Flutter interface (with no dependency on polkadot-js).
8 |
9 | [`shekohex/flutterust`](https://github.com/shekohex/flutterust) is a generic template for Rust to Dart FFI
10 |
11 | ## Build Instructions
12 |
13 | 1. **[Install Flutter](https://flutter.dev/docs/get-started/install)**.
14 |
15 | It is common for developers to get stuck setting the path to the Dart and Flutter SDKs after installation so take careful note of where these are installed. See the Dart docs: [Configure PATH and environment variables](https://dartcode.org/docs/configuring-path-and-environment-variables/) for more info.
16 |
17 | 2. Run `flutter doctor` to verify that you have everything needed to build and run a flutter application.
18 |
19 | You probably won't have a connected device and that's OK, you can pick your emulator in step (5) below.
20 |
21 | 3. Install Cargo Make, Dart Bindgen and CBindgen (skip if you already have them installed)
22 |
23 | ```
24 | $ cargo install cargo-make cbindgen
25 | $ cargo install dart-bindgen --features cli
26 | ```
27 |
28 | 4. Clone this repository.
29 |
30 | ### Android
31 |
32 | _These instructions assume that you have installed Android Studio or the flutter plugins for Android Studio._
33 |
34 | 5. [Open the Android emulator](https://developer.android.com/studio/run/emulator).
35 |
36 | 6. Run the following commands in the root of the cloned repository.
37 |
38 | ```
39 | $ cargo make android-dev # for android only using x86_64 emulator (run cargo make android to build for all android targets)
40 | $ flutter run
41 | ```
42 |
43 | ### iOS
44 |
45 | _These instructions assume that you may NOT have installed Android Studio nor the flutter plugins for Android Studio._
46 |
47 | 5. [Open the iOS emulator](https://stackoverflow.com/questions/10379622/how-to-run-iphone-emulator-without-starting-xcode). This could be as simple as invoking the following command if you've set this alias before.
48 |
49 | ```
50 | $ open -a simulator
51 | ```
52 |
53 | You may have to specify File -> Open Device -> `$MODEL` once the simulator opens.
54 |
55 | 6. In the root of the cloned repo, run the following commands.
56 |
57 | ```
58 | $ cargo make ios --profile release # Release Build is Required since debug build is very big.
59 | $ flutter run
60 | ```
61 |
62 | After you run the application, all changed state is saved in the directory. To restart from the `Generate Your Account` screen, you must reset the cloned repo to head (`git reset --hard HEAD`), delete the app from the simulator and do the last three commands listed above again.
63 |
64 | # License
65 |
66 | Copyright 2020 Sunshine Protocol
67 |
68 | Permission is hereby granted, free of charge, to any person obtaining a copy of
69 | this software and associated documentation files (the "Software"), to deal in
70 | the Software without restriction, including without limitation the rights to
71 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
72 | of the Software, and to permit persons to whom the Software is furnished to do
73 | so, subject to the following conditions:
74 |
75 | The above copyright notice and this permission notice shall be included in all
76 | copies or substantial portions of the Software.
77 |
78 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
79 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
80 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
81 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
82 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
83 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
84 | SOFTWARE.
85 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | linter:
2 | rules:
3 | - always_declare_return_types
4 | - always_put_control_body_on_new_line
5 | - always_put_required_named_parameters_first
6 | - always_require_non_null_named_parameters
7 | - annotate_overrides
8 | - avoid_as
9 | - avoid_bool_literals_in_conditional_expressions
10 | # - avoid_catches_without_on_clauses
11 | - avoid_catching_errors
12 | - avoid_classes_with_only_static_members
13 | - avoid_double_and_int_checks
14 | - avoid_empty_else
15 | - avoid_field_initializers_in_const_classes
16 | - avoid_function_literals_in_foreach_calls
17 | - avoid_implementing_value_types
18 | - avoid_init_to_null
19 | - avoid_js_rounded_ints
20 | - avoid_null_checks_in_equality_operators
21 | - avoid_positional_boolean_parameters
22 | - avoid_private_typedef_functions
23 | - avoid_relative_lib_imports
24 | - avoid_renaming_method_parameters
25 | - avoid_return_types_on_setters
26 | - avoid_returning_null
27 | - avoid_returning_null_for_void
28 | - avoid_returning_this
29 | - avoid_setters_without_getters
30 | - avoid_single_cascade_in_expression_statements
31 | - avoid_slow_async_io
32 | - avoid_types_as_parameter_names
33 | - avoid_types_on_closure_parameters
34 | - avoid_unused_constructor_parameters
35 | - avoid_void_async
36 | - await_only_futures
37 | - camel_case_types
38 | - cancel_subscriptions
39 | - cascade_invocations
40 | - close_sinks
41 | - comment_references
42 | - constant_identifier_names
43 | - control_flow_in_finally
44 | - curly_braces_in_flow_control_structures
45 | - directives_ordering
46 | - empty_catches
47 | - empty_constructor_bodies
48 | - empty_statements
49 | - file_names
50 | - flutter_style_todos
51 | - hash_and_equals
52 | - implementation_imports
53 | - invariant_booleans
54 | - iterable_contains_unrelated_type
55 | - join_return_with_assignment
56 | - library_names
57 | - library_prefixes
58 | - lines_longer_than_80_chars
59 | - list_remove_unrelated_type
60 | - literal_only_boolean_expressions
61 | - no_adjacent_strings_in_list
62 | - no_duplicate_case_values
63 | - non_constant_identifier_names
64 | - null_closures
65 | - omit_local_variable_types
66 | - one_member_abstracts
67 | - only_throw_errors
68 | - overridden_fields
69 | - package_api_docs
70 | - package_names
71 | - package_prefixed_library_names
72 | - parameter_assignments
73 | - prefer_adjacent_string_concatenation
74 | - prefer_asserts_in_initializer_lists
75 | - prefer_collection_literals
76 | - prefer_conditional_assignment
77 | - prefer_const_constructors
78 | - prefer_const_constructors_in_immutables
79 | - prefer_const_declarations
80 | - prefer_const_literals_to_create_immutables
81 | - prefer_constructors_over_static_methods
82 | - prefer_contains
83 | - prefer_equal_for_default_values
84 | # - prefer_expression_function_bodies
85 | - prefer_final_fields
86 | - prefer_final_locals
87 | - prefer_foreach
88 | - prefer_function_declarations_over_variables
89 | - prefer_generic_function_type_aliases
90 | - prefer_initializing_formals
91 | - prefer_int_literals
92 | - prefer_interpolation_to_compose_strings
93 | - prefer_is_empty
94 | - prefer_is_not_empty
95 | - prefer_iterable_whereType
96 | - prefer_mixin
97 | - prefer_single_quotes
98 | - prefer_typing_uninitialized_variables
99 | - prefer_void_to_null
100 | #- public_member_api_docs
101 | - recursive_getters
102 | - slash_for_doc_comments
103 | - sort_constructors_first
104 | - sort_pub_dependencies
105 | - sort_unnamed_constructors_first
106 | - test_types_in_equals
107 | - throw_in_finally
108 | - type_annotate_public_apis
109 | - type_init_formals
110 | - unawaited_futures
111 | - unnecessary_brace_in_string_interps
112 | - unnecessary_const
113 | - unnecessary_getters_setters
114 | - unnecessary_lambdas
115 | - unnecessary_new
116 | - unnecessary_null_aware_assignments
117 | - unnecessary_null_in_if_null_operators
118 | - unnecessary_overrides
119 | - unnecessary_parenthesis
120 | - unnecessary_statements
121 | - unnecessary_this
122 | - unrelated_type_equality_checks
123 | - use_rethrow_when_possible
124 | - use_setters_to_change_properties
125 | - use_string_buffers
126 | - use_to_and_as_if_applicable
127 | - valid_regexps
128 | - void_checks
129 | - use_full_hex_values_for_flutter_colors
130 | analyzer:
131 | strong-mode:
132 | implicit-casts: false
133 | implicit-dynamic: true
134 | exclude:
135 | - lib/**/*.g.dart
136 | - lib/setup.iconfig.dart
137 | - lib/router/router.gr.dart
138 | - packages/**/lib/ffi.dart
139 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion 28
30 |
31 | sourceSets {
32 | main.java.srcDirs += 'src/main/kotlin'
33 | }
34 |
35 | lintOptions {
36 | disable 'InvalidPackage'
37 | }
38 |
39 | defaultConfig {
40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
41 | applicationId "com.example.sunshine"
42 | minSdkVersion 16
43 | targetSdkVersion 28
44 | versionCode flutterVersionCode.toInteger()
45 | versionName flutterVersionName
46 | }
47 |
48 | buildTypes {
49 | release {
50 | // TODO: Add your own signing config for the release build.
51 | // Signing with the debug keys for now, so `flutter run --release` works.
52 | signingConfig signingConfigs.debug
53 | }
54 | }
55 | }
56 |
57 | flutter {
58 | source '../..'
59 | }
60 |
61 | dependencies {
62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
63 | }
64 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.3.50'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.5.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | jcenter()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
7 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/assets/logo.png
--------------------------------------------------------------------------------
/assets/svg/sun.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/cbindgen.toml:
--------------------------------------------------------------------------------
1 | # See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml
2 | # for detailed documentation of every option here.
3 |
4 | language = "C"
5 | no_includes = false
6 | after_includes = ""
7 | braces = "SameLine"
8 | line_length = 100
9 | tab_width = 2
10 | documentation_style = "auto"
11 | style = "both"
12 |
13 | [export]
14 | item_types = ["structs", "functions", "constants"]
15 | renaming_overrides_prefixing = false
16 |
17 | [const]
18 | allow_static_const = false
19 | allow_constexpr = false
20 |
21 | [parse.expand]
22 | crates = ["sunshine"]
23 | all_features = false
24 | default_features = true
25 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/ios/Flutter/.last_build_id:
--------------------------------------------------------------------------------
1 | bd19cfcd85366890884fa28f4239ebc1
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - frusty_logger (0.0.1):
4 | - Flutter
5 | - path_provider (0.0.1):
6 | - Flutter
7 | - sensors (0.0.1):
8 | - Flutter
9 | - shared_preferences (0.0.1):
10 | - Flutter
11 | - sunshine_ffi (0.0.1):
12 | - Flutter
13 | - url_launcher (0.0.1):
14 | - Flutter
15 |
16 | DEPENDENCIES:
17 | - Flutter (from `Flutter`)
18 | - frusty_logger (from `.symlinks/plugins/frusty_logger/ios`)
19 | - path_provider (from `.symlinks/plugins/path_provider/ios`)
20 | - sensors (from `.symlinks/plugins/sensors/ios`)
21 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
22 | - sunshine_ffi (from `.symlinks/plugins/sunshine_ffi/ios`)
23 | - url_launcher (from `.symlinks/plugins/url_launcher/ios`)
24 |
25 | EXTERNAL SOURCES:
26 | Flutter:
27 | :path: Flutter
28 | frusty_logger:
29 | :path: ".symlinks/plugins/frusty_logger/ios"
30 | path_provider:
31 | :path: ".symlinks/plugins/path_provider/ios"
32 | sensors:
33 | :path: ".symlinks/plugins/sensors/ios"
34 | shared_preferences:
35 | :path: ".symlinks/plugins/shared_preferences/ios"
36 | sunshine_ffi:
37 | :path: ".symlinks/plugins/sunshine_ffi/ios"
38 | url_launcher:
39 | :path: ".symlinks/plugins/url_launcher/ios"
40 |
41 | SPEC CHECKSUMS:
42 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
43 | frusty_logger: cc5ccbb4d9f92b44cd0bbc7f5d17846b3edd3f52
44 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
45 | sensors: 84eb7a30e47a649e4172b71d6e81be614c280336
46 | shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
47 | sunshine_ffi: 5c5a8847b6fcb7dbeef8873250c9b4703c8c5a6c
48 | url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
49 |
50 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
51 |
52 | COCOAPODS: 1.8.4
53 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshine-protocol/sunscreen/d9931d1c56a78ef5c3885e6091d354b541f49774/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | Sunshine Bounty
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/lib/app.dart:
--------------------------------------------------------------------------------
1 | import 'package:auto_route/auto_route.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:sunshine/constants.dart';
4 | import 'package:sunshine/router/router.dart';
5 |
6 | class MyApp extends StatelessWidget {
7 | @override
8 | Widget build(BuildContext context) {
9 | return MaterialApp(
10 | title: 'Sunshine Bounty',
11 | debugShowCheckedModeBanner: false,
12 | theme: ThemeData(
13 | accentColor: AppColors.primary,
14 | scaffoldBackgroundColor: AppColors.mainBackground,
15 | ),
16 | builder: ExtendedNavigator(
17 | router: Router(),
18 | onUnknownRoute: _onUnknownRoute,
19 | ),
20 | );
21 | }
22 |
23 | Route _onUnknownRoute(RouteSettings settings) {
24 | return MaterialPageRoute(
25 | builder: (_) => Scaffold(
26 | body: Center(
27 | child: Text('No route defined for ${settings.name}'),
28 | ),
29 | ),
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/constants.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | class AppColors {
6 | static const mainBackground = Color(0xFF1E1E1E);
7 | static const lightBackgroud = Color(0xFF2D2D2D);
8 | static const primary = Color(0xFFA761AF);
9 | static const secondry = Color(0xFF7F8F82);
10 | static const success = Color(0xFF616161);
11 | static const danger = Color(0xFFEE3A3A);
12 | static const disabled = Color(0xFFB7B7B7);
13 | }
14 |
15 | class SharedPrefKeys {
16 | static const firstName = 'account:firstName';
17 | static const lastName = 'account:lastName';
18 | }
19 |
--------------------------------------------------------------------------------
/lib/core/core.dart:
--------------------------------------------------------------------------------
1 | export 'extensions/extensions.dart';
2 | export 'register_module.dart';
3 |
--------------------------------------------------------------------------------
/lib/core/extensions/extensions.dart:
--------------------------------------------------------------------------------
1 | export 'router.dart';
2 |
--------------------------------------------------------------------------------
/lib/core/extensions/router.dart:
--------------------------------------------------------------------------------
1 | import 'package:auto_route/auto_route.dart';
2 |
3 | extension PopPages on ExtendedNavigatorState {
4 | Future popPages(int pages) async {
5 | var count = 0;
6 | while (canPop() && count <= pages) {
7 | count += 1;
8 | pop();
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/lib/core/register_module.dart:
--------------------------------------------------------------------------------
1 | import 'package:injectable/injectable.dart';
2 | import 'package:shared_preferences/shared_preferences.dart';
3 | import 'package:sunshine/services/services.dart';
4 |
5 | @module
6 | abstract class RegisterModule {
7 | @lazySingleton
8 | @preResolve
9 | Future get pathProvider => PathProviderService.create();
10 | @lazySingleton
11 | @preResolve
12 | Future get prefs => SharedPreferences.getInstance();
13 | }
14 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/services.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:sunshine/app.dart';
4 | import 'package:sunshine/constants.dart';
5 | import 'package:sunshine/setup.dart';
6 | import 'package:injectable/injectable.dart';
7 | import 'package:intl/intl.dart';
8 |
9 | Future main() async {
10 | WidgetsFlutterBinding.ensureInitialized();
11 | await _setup();
12 | runApp(MyApp());
13 | await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
14 | SystemChrome.setSystemUIOverlayStyle(
15 | const SystemUiOverlayStyle(
16 | statusBarColor: AppColors.mainBackground,
17 | statusBarIconBrightness: Brightness.light,
18 | statusBarBrightness: Brightness.light,
19 | systemNavigationBarColor: AppColors.mainBackground,
20 | systemNavigationBarDividerColor: Colors.white,
21 | systemNavigationBarIconBrightness: Brightness.light,
22 | ),
23 | );
24 | }
25 |
26 | Future _setup() async {
27 | await configureDependencies(environment: prod);
28 | Intl.defaultLocale = 'en_US';
29 |
30 | // other pre-start setup goes here
31 | }
32 |
--------------------------------------------------------------------------------
/lib/models/account_model.dart:
--------------------------------------------------------------------------------
1 | enum AccountState {
2 | unknown,
3 | noAccount,
4 | locked,
5 | unlocked,
6 | }
7 |
8 | class Account {
9 | const Account({
10 | this.uid,
11 | this.state,
12 | });
13 | final String uid;
14 | final AccountState state;
15 | }
16 |
--------------------------------------------------------------------------------
/lib/models/bounty_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:sunshine/models/github_issue_model.dart';
2 | import 'package:sunshine/sunshine.dart';
3 | import 'package:sunshine_ffi/dto.dart';
4 |
5 | class Bounty {
6 | Bounty({
7 | @required this.info,
8 | @required this.issue,
9 | });
10 | final BountyInformation info;
11 | final GithubIssue issue;
12 | }
13 |
--------------------------------------------------------------------------------
/lib/models/bounty_submission_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:sunshine/models/github_issue_model.dart';
2 | import 'package:sunshine/sunshine.dart';
3 | import 'package:sunshine_ffi/dto.dart';
4 |
5 | class BountySubmission {
6 | BountySubmission({
7 | @required this.info,
8 | @required this.issue,
9 | });
10 | final BountySubmissionInformation info;
11 | final GithubIssue issue;
12 | }
13 |
--------------------------------------------------------------------------------
/lib/models/device_model.dart:
--------------------------------------------------------------------------------
1 | class Device {
2 | const Device({
3 | this.id,
4 | this.currentDevice = false,
5 | });
6 | final String id;
7 | final bool currentDevice;
8 |
9 | bool get isCurrentDevice => currentDevice;
10 | @override
11 | // ignore: hash_and_equals
12 | int get hashCode => id.hashCode;
13 | }
14 |
--------------------------------------------------------------------------------
/lib/models/github_issue_model.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: avoid_as
2 |
3 | import 'dart:convert';
4 |
5 | class GithubIssue {
6 | GithubIssue({
7 | this.htmlUrl,
8 | this.number,
9 | this.title,
10 | this.user,
11 | this.state,
12 | this.comments,
13 | this.createdAt,
14 | this.updatedAt,
15 | this.body,
16 | });
17 |
18 | factory GithubIssue.fromRawJson(String str) => GithubIssue.fromJson(
19 | json.decode(str) as Map,
20 | );
21 |
22 | factory GithubIssue.fromJson(Map json) {
23 | return GithubIssue(
24 | htmlUrl: json['html_url'] as String,
25 | number: json['number'] as int,
26 | title: json['title'] as String,
27 | user: User.fromJson(json['user'] as Map),
28 | state: json['state'] as String,
29 | comments: json['comments'] as int,
30 | createdAt: DateTime.parse(json['created_at'] as String),
31 | updatedAt: DateTime.parse(json['updated_at'] as String),
32 | body: json['body'] as String,
33 | );
34 | }
35 | final String htmlUrl;
36 | final int number;
37 | final String title;
38 | final User user;
39 | final String state;
40 | final int comments;
41 | final DateTime createdAt;
42 | final DateTime updatedAt;
43 | final String body;
44 | Map toJson() => {
45 | 'html_url': htmlUrl,
46 | 'number': number,
47 | 'title': title,
48 | 'user': user.toJson(),
49 | 'state': state,
50 | 'comments': comments,
51 | 'created_at': createdAt.toIso8601String(),
52 | 'updated_at': updatedAt.toIso8601String(),
53 | 'body': body,
54 | };
55 | }
56 |
57 | class User {
58 | User({
59 | this.login,
60 | this.id,
61 | this.avatarUrl,
62 | this.gravatarId,
63 | });
64 |
65 | factory User.fromRawJson(String str) => User.fromJson(
66 | json.decode(str) as Map,
67 | );
68 |
69 | factory User.fromJson(Map json) {
70 | return User(
71 | login: json['login'] as String,
72 | id: json['id'] as int,
73 | avatarUrl: json['avatar_url'] as String,
74 | gravatarId: json['gravatar_id'] as String,
75 | );
76 | }
77 |
78 | String toRawJson() => json.encode(toJson());
79 |
80 | final String login;
81 | final int id;
82 | final String avatarUrl;
83 | final String gravatarId;
84 |
85 | Map toJson() => {
86 | 'login': login,
87 | 'id': id,
88 | 'avatar_url': avatarUrl,
89 | 'gravatar_id': gravatarId,
90 | };
91 | }
92 |
--------------------------------------------------------------------------------
/lib/models/identity/github.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | import 'service.dart';
4 |
5 | class GithubIdentity extends SocialIdentityService {
6 | GithubIdentity({
7 | @required String username,
8 | @required String proofUrl,
9 | }) : _username = username,
10 | _proofUrl = proofUrl;
11 | final String _username;
12 | final String _proofUrl;
13 |
14 | @override
15 | String get serviceName => 'github';
16 |
17 | @override
18 | String get username => _username;
19 |
20 | @override
21 | String get proofUrl => _proofUrl;
22 | }
23 |
--------------------------------------------------------------------------------
/lib/models/identity/service.dart:
--------------------------------------------------------------------------------
1 | abstract class SocialIdentityService {
2 | String get username;
3 | String get serviceName;
4 | String get proofUrl;
5 | String get display => '$username@$serviceName';
6 | bool get isProved => proofUrl != null;
7 | }
8 |
--------------------------------------------------------------------------------
/lib/models/models.dart:
--------------------------------------------------------------------------------
1 | export 'account_model.dart';
2 | export 'bounty_model.dart';
3 | export 'bounty_submission_model.dart';
4 | export 'device_model.dart';
5 | export 'github_issue_model.dart';
6 | export 'identity/github.dart';
7 | export 'identity/service.dart';
8 |
--------------------------------------------------------------------------------
/lib/router/router.dart:
--------------------------------------------------------------------------------
1 | import 'package:auto_route/auto_route_annotations.dart';
2 | import 'package:sunshine/sunshine.dart';
3 |
4 | export 'router.gr.dart';
5 |
6 | @AdaptiveAutoRouter(
7 | routes: [
8 | AdaptiveRoute(page: BlankScreen),
9 | AdaptiveRoute(page: SplashScreen, initial: true),
10 | AdaptiveRoute(
11 | page: IntroScreen,
12 | maintainState: false,
13 | ),
14 | AdaptiveRoute(page: GenerateAccountStepOneScreen),
15 | AdaptiveRoute(page: GenerateAccountStepTwoScreen),
16 | AdaptiveRoute(page: GenerateAccountDoneScreen),
17 | AdaptiveRoute(page: RecoverAccountStepOneScreen),
18 | AdaptiveRoute(page: RecoverAccountStepTwoScreen),
19 | AdaptiveRoute(page: RecoverAccountDoneScreen),
20 | AdaptiveRoute(
21 | page: MainScreen,
22 | maintainState: true,
23 | ),
24 | AdaptiveRoute(page: WalletTransferScreen),
25 | AdaptiveRoute(page: WalletTransferConfirmationScreen),
26 | AdaptiveRoute(page: WalletTransferDoneScreen),
27 | AdaptiveRoute(
28 | page: AccountScreen,
29 | maintainState: false,
30 | ),
31 | AdaptiveRoute(page: BountyScreen),
32 | AdaptiveRoute(page: CreateBountyScreen),
33 | AdaptiveRoute(page: SubmitForBountyScreen),
34 | AdaptiveRoute(page: DevicesScreen),
35 | AdaptiveRoute(page: PaperKeyScreen),
36 | AdaptiveRoute(page: RevokeDeviceScreen),
37 | AdaptiveRoute(page: RevokeDeviceDoneScreen),
38 | AdaptiveRoute(page: IdentitiesScreen),
39 | AdaptiveRoute(page: ProveIdentityScreen),
40 | AdaptiveRoute(page: ProveIdentityInstractionsScreen),
41 | AdaptiveRoute(page: ProveIdentityDone),
42 | AdaptiveRoute(page: RevokeIdentityScreen),
43 | AdaptiveRoute(page: RevokeIdentityDoneScreen),
44 | AdaptiveRoute(
45 | page: LoggerScreen,
46 | maintainState: true,
47 | ),
48 | ],
49 | generateNavigationHelperExtension: false,
50 | )
51 | class $Router {}
52 |
--------------------------------------------------------------------------------
/lib/screens/account_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:sunshine/models/models.dart';
4 | import 'package:sunshine/sunshine.dart';
5 | import 'package:url_launcher/url_launcher.dart';
6 |
7 | class AccountScreen extends StatefulWidget {
8 | @override
9 | _AccountScreenState createState() => _AccountScreenState();
10 | }
11 |
12 | class _AccountScreenState extends State {
13 | final _accountService = GetIt.I.get();
14 |
15 | final _walletService = GetIt.I.get();
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Scaffold(
20 | appBar: const MyAppBar(title: 'Account', elevation: 1),
21 | body: RefreshIndicator(
22 | onRefresh: () async {
23 | setState(() {});
24 | return true;
25 | },
26 | child: ListView(
27 | children: [
28 | const HeaderText('Your Information'),
29 | SizedBox(height: 15.w.toDouble()),
30 | const HintText('Tip: long press to copy to clipboard'),
31 | SizedBox(height: 10.w.toDouble()),
32 | ListCell(
33 | title: 'UID',
34 | trailing: SizedBox(
35 | width: 60.w.toDouble(),
36 | child: FutureBuilder(
37 | initialData: '...',
38 | future: _accountService.uid(),
39 | builder: (context, snapshot) {
40 | return HintText(
41 | snapshot.data ?? 'N/A',
42 | );
43 | },
44 | ),
45 | ),
46 | onLongPress: () async {
47 | await Clipboard.setData(
48 | ClipboardData(
49 | text: await _accountService.uid(),
50 | ),
51 | );
52 | },
53 | ),
54 | const Divider(
55 | thickness: 2,
56 | indent: 15,
57 | endIndent: 15,
58 | color: AppColors.disabled,
59 | ),
60 | ListCell(
61 | title: 'Device ID',
62 | trailing: SizedBox(
63 | width: 120.w.toDouble(),
64 | child: FutureBuilder(
65 | initialData: const Device(
66 | id: '...',
67 | currentDevice: true,
68 | ),
69 | future: _accountService.currentDevice(),
70 | builder: (context, snapshot) => HintText(
71 | snapshot.data?.id ?? 'N/A',
72 | ),
73 | ),
74 | ),
75 | onLongPress: () async {
76 | await Clipboard.setData(
77 | ClipboardData(
78 | text: (await _accountService.currentDevice()).id,
79 | ),
80 | );
81 | },
82 | ),
83 | const Divider(
84 | thickness: 2,
85 | indent: 15,
86 | endIndent: 15,
87 | color: AppColors.disabled,
88 | ),
89 | ListCell(
90 | title: 'Balance',
91 | trailing: SizedBox(
92 | width: 120.w.toDouble(),
93 | child: FutureBuilder(
94 | initialData: '...',
95 | future: _walletService.balance(),
96 | builder: (context, snapshot) {
97 | return HintText(
98 | '☼${snapshot.data}' ?? 'N/A',
99 | );
100 | },
101 | ),
102 | ),
103 | onTap: () async {
104 | try {
105 | await _walletService.mint();
106 | } catch (_) {
107 | // don't do anything, it is only for testing ..
108 | }
109 | },
110 | onLongPress: () async {
111 | await Clipboard.setData(
112 | ClipboardData(
113 | text: await _walletService.balance(),
114 | ),
115 | );
116 | },
117 | ),
118 | const Divider(
119 | thickness: 2,
120 | indent: 15,
121 | endIndent: 15,
122 | color: AppColors.disabled,
123 | ),
124 | SizedBox(height: 20.w.toDouble()),
125 | const HeaderText('Profile'),
126 | SizedBox(height: 10.w.toDouble()),
127 | ListCell(
128 | title: 'Devices',
129 | trailing: const Icon(
130 | Icons.chevron_right,
131 | size: 32,
132 | color: Colors.white,
133 | ),
134 | onTap: () {
135 | ExtendedNavigator.root.push(Routes.devicesScreen);
136 | },
137 | ),
138 | const Divider(
139 | thickness: 2,
140 | indent: 15,
141 | endIndent: 15,
142 | color: AppColors.disabled,
143 | ),
144 | ListCell(
145 | title: 'Identities',
146 | trailing: const Icon(
147 | Icons.chevron_right,
148 | size: 32,
149 | color: Colors.white,
150 | ),
151 | onTap: () {
152 | ExtendedNavigator.root.push(Routes.identitiesScreen);
153 | },
154 | ),
155 | const Divider(
156 | thickness: 2,
157 | indent: 15,
158 | endIndent: 15,
159 | color: AppColors.disabled,
160 | ),
161 | SizedBox(height: 20.w.toDouble()),
162 | const HeaderText('Legal'),
163 | SizedBox(height: 10.w.toDouble()),
164 | ListCell(
165 | title: 'About',
166 | trailing: const Icon(
167 | Icons.share,
168 | size: 22,
169 | color: Colors.white,
170 | ),
171 | onTap: () {
172 | showAboutDialog(
173 | context: context,
174 | applicationName: 'Sunshine',
175 | applicationVersion: 'v0.1.0',
176 | applicationIcon: const SunshineLogo(),
177 | useRootNavigator: true,
178 | applicationLegalese: 'Powered by Substrate',
179 | children: [
180 | const SizedBox(height: 10),
181 | FlatButton(
182 | child: const Text('Website'),
183 | onPressed: () {
184 | launch('https://sunshine.foundation/');
185 | },
186 | ),
187 | ],
188 | );
189 | },
190 | ),
191 | const Divider(
192 | thickness: 2,
193 | indent: 15,
194 | endIndent: 15,
195 | color: AppColors.disabled,
196 | ),
197 | ],
198 | ),
199 | ),
200 | );
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/lib/screens/blank_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:sunshine/sunshine.dart';
3 |
4 | class BlankScreen extends StatefulWidget {
5 | @override
6 | _BlankScreenState createState() => _BlankScreenState();
7 | }
8 |
9 | class _BlankScreenState extends State {
10 | @override
11 | Widget build(BuildContext context) {
12 | ScreenUtil.init(
13 | context,
14 | width: 375,
15 | height: 812,
16 | allowFontScaling: true,
17 | );
18 | return const Scaffold(
19 | body: Center(child: SizedBox()),
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/screens/create_bounty_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:sunshine/sunshine.dart';
3 |
4 | class CreateBountyScreen extends StatefulWidget {
5 | @override
6 | _CreateBountyScreenState createState() => _CreateBountyScreenState();
7 | }
8 |
9 | class _CreateBountyScreenState extends State {
10 | BountyService _bountyService;
11 | TextEditingController _urlController;
12 | TextEditingController _amountController;
13 | String _errText;
14 |
15 | @override
16 | void initState() {
17 | super.initState();
18 | _bountyService = GetIt.I.get();
19 | _urlController = TextEditingController();
20 | _amountController = TextEditingController();
21 | }
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | return Scaffold(
26 | appBar: const MyAppBar(title: 'Create Bounty'),
27 | body: Column(
28 | mainAxisSize: MainAxisSize.min,
29 | crossAxisAlignment: CrossAxisAlignment.start,
30 | children: [
31 | const FittedBox(
32 | fit: BoxFit.fitWidth,
33 | child: HeaderText('Post a new Bounty'),
34 | ),
35 | SizedBox(height: 30.h.toDouble()),
36 | Input(
37 | hintText: 'Github Issue URL',
38 | errorText: _errText,
39 | controller: _urlController,
40 | enableSuggestions: true,
41 | textInputType: TextInputType.url,
42 | textInputAction: TextInputAction.next,
43 | onChanged: (v) {
44 | if (_errText != null) {
45 | setState(() {
46 | _errText = null;
47 | });
48 | }
49 | },
50 | ),
51 | SizedBox(height: 14.h.toDouble()),
52 | Input(
53 | hintText: 'Bounty amount',
54 | controller: _amountController,
55 | textInputType: const TextInputType.numberWithOptions(
56 | signed: false,
57 | decimal: false,
58 | ),
59 | ),
60 | const Expanded(
61 | flex: 1,
62 | child: SizedBox(),
63 | ),
64 | Builder(
65 | builder: (context) => Button(
66 | text: 'Create',
67 | variant: ButtonVariant.success,
68 | onPressed: () => _createBounty(context),
69 | ),
70 | ),
71 | SizedBox(height: 15.h.toDouble())
72 | ],
73 | ),
74 | );
75 | }
76 |
77 | Future _createBounty(BuildContext context) async {
78 | if (_urlController.text.isEmpty) {
79 | setState(() {
80 | _errText = 'The Url is required';
81 | });
82 | return;
83 | }
84 | final data = githubParseIssueUrl(_urlController.text);
85 | if (data == null) {
86 | setState(() {
87 | _errText = 'this looks like not a valid github issue url!';
88 | });
89 | return;
90 | }
91 | FocusScope.of(context).requestFocus(FocusNode());
92 | try {
93 | final id = await _bountyService.postBounty(
94 | data.owner,
95 | data.repo,
96 | data.issue,
97 | BigInt.parse(_amountController.text),
98 | );
99 | print('Created Bounty with Id: $id');
100 | await Future.delayed(
101 | const Duration(milliseconds: 50),
102 | () {
103 | ExtendedNavigator.root.pop(true);
104 | },
105 | );
106 | } catch (_) {
107 | const snackbar = SnackBar(
108 | content: Text("Couldn't Create a bounty, check your balance"),
109 | backgroundColor: AppColors.danger,
110 | duration: Duration(seconds: 5),
111 | );
112 | final result = Scaffold.of(context).showSnackBar(snackbar);
113 | await result.closed;
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/lib/screens/generate_account_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:sunshine/sunshine.dart';
3 | import 'package:sunshine/models/models.dart';
4 |
5 | class GenerateAccountStepOneScreen extends StatefulWidget {
6 | @override
7 | _GenerateAccountStepOneScreenState createState() =>
8 | _GenerateAccountStepOneScreenState();
9 | }
10 |
11 | class _GenerateAccountStepOneScreenState
12 | extends State {
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | appBar: const MyAppBar(title: 'Generate Account'),
17 | body: Column(
18 | mainAxisSize: MainAxisSize.min,
19 | crossAxisAlignment: CrossAxisAlignment.start,
20 | children: [
21 | const HeaderText('Choose your Device Name'),
22 | SizedBox(height: 30.h.toDouble()),
23 | const Input(
24 | hintText: 'ex. Android Phone',
25 | ),
26 | SizedBox(height: 30.h.toDouble()),
27 | const Center(
28 | child: HintText(
29 | 'Your name will only be stored on your device '
30 | 'we don\'t share it anywhere else.',
31 | ),
32 | ),
33 | const Expanded(
34 | child: SizedBox(),
35 | ),
36 | Button(
37 | text: 'Next',
38 | variant: ButtonVariant.success,
39 | onPressed: () {
40 | ExtendedNavigator.root
41 | .popAndPush(Routes.generateAccountStepTwoScreen);
42 | },
43 | ),
44 | SizedBox(height: 15.h.toDouble())
45 | ],
46 | ),
47 | );
48 | }
49 | }
50 |
51 | class GenerateAccountStepTwoScreen extends StatefulWidget {
52 | @override
53 | _GenerateAccountStepTwoScreenState createState() =>
54 | _GenerateAccountStepTwoScreenState();
55 | }
56 |
57 | class _GenerateAccountStepTwoScreenState
58 | extends State {
59 | KeyService _keyService;
60 | TextEditingController _passwordController;
61 | TextEditingController _passwordAgainController;
62 | String _errText;
63 | @override
64 | void initState() {
65 | super.initState();
66 | _keyService = GetIt.I.get();
67 | _passwordController = TextEditingController();
68 | _passwordAgainController = TextEditingController();
69 | }
70 |
71 | @override
72 | Widget build(BuildContext context) {
73 | return Scaffold(
74 | appBar: const MyAppBar(title: 'Generate Account'),
75 | body: Column(
76 | mainAxisSize: MainAxisSize.min,
77 | crossAxisAlignment: CrossAxisAlignment.start,
78 | children: [
79 | const FittedBox(
80 | fit: BoxFit.fitWidth,
81 | child: HeaderText('Add a password to secure your account'),
82 | ),
83 | SizedBox(height: 30.h.toDouble()),
84 | Input(
85 | hintText: 'Password',
86 | obscureText: true,
87 | errorText: _errText,
88 | controller: _passwordController,
89 | onChanged: (v) {
90 | if (_errText != null) {
91 | setState(() {
92 | _errText = null;
93 | });
94 | }
95 | },
96 | ),
97 | SizedBox(height: 14.h.toDouble()),
98 | Input(
99 | hintText: 'Password Again',
100 | obscureText: true,
101 | errorText: _errText,
102 | controller: _passwordAgainController,
103 | onChanged: (v) {
104 | if (_errText != null) {
105 | setState(() {
106 | _errText = null;
107 | });
108 | }
109 | },
110 | ),
111 | SizedBox(height: 30.h.toDouble()),
112 | const Center(
113 | child: HintText('Password must be at least 8 characters'),
114 | ),
115 | const Expanded(
116 | flex: 1,
117 | child: SizedBox(),
118 | ),
119 | Builder(
120 | builder: (context) => Button(
121 | text: 'Generate',
122 | variant: ButtonVariant.success,
123 | onPressed: () => _generateAccount(context),
124 | ),
125 | ),
126 | SizedBox(height: 15.h.toDouble())
127 | ],
128 | ),
129 | );
130 | }
131 |
132 | Future _generateAccount(BuildContext context) async {
133 | final isLessThan8 = _passwordController.text.length < 8 ||
134 | _passwordAgainController.text.length < 8;
135 | if (isLessThan8) {
136 | setState(() {
137 | _errText = 'Please choose a password that at least 8 characters';
138 | });
139 | return;
140 | }
141 | if (_passwordController.text != _passwordAgainController.text) {
142 | setState(() {
143 | _errText = 'Passwords dose not match';
144 | });
145 | return;
146 | }
147 | // hide keyboard
148 | FocusScope.of(context).requestFocus(FocusNode());
149 | await Future.delayed(const Duration(milliseconds: 100));
150 | try {
151 | await _keyService.generate(_passwordController.text);
152 | await Future.delayed(
153 | const Duration(milliseconds: 100),
154 | () {
155 | ExtendedNavigator.root.popAndPush(Routes.generateAccountDoneScreen);
156 | },
157 | );
158 | } catch (_) {
159 | const snackbar = SnackBar(
160 | content: Text("Couldn't generate the account"),
161 | backgroundColor: AppColors.danger,
162 | duration: Duration(seconds: 5),
163 | );
164 | final result = Scaffold.of(context).showSnackBar(snackbar);
165 | await result.closed;
166 | ExtendedNavigator.root.pop();
167 | }
168 | }
169 | }
170 |
171 | class GenerateAccountDoneScreen extends StatelessWidget {
172 | final _accountService = GetIt.I.get();
173 | @override
174 | Widget build(BuildContext context) {
175 | final f = _accountService.currentAccount();
176 | return Scaffold(
177 | body: Column(
178 | mainAxisAlignment: MainAxisAlignment.start,
179 | children: [
180 | SizedBox(height: 100.h.toDouble()),
181 | const HeaderText(
182 | 'Your Account has been created\n'
183 | 'Here is your account id',
184 | ),
185 | SizedBox(height: 30.h.toDouble()),
186 | FutureBuilder(
187 | initialData: const Account(uid: '...'),
188 | future: f,
189 | builder: (context, snapshot) => Input(
190 | hintText: snapshot.data.uid,
191 | readOnly: true,
192 | ),
193 | ),
194 | SizedBox(height: 30.h.toDouble()),
195 | const Center(
196 | child: Padding(
197 | padding: EdgeInsets.symmetric(horizontal: 20),
198 | child: HintText(
199 | 'you could access these information '
200 | 'in your profile page.',
201 | maxLines: 2,
202 | ),
203 | ),
204 | ),
205 | const Expanded(
206 | flex: 1,
207 | child: SizedBox(),
208 | ),
209 | Button(
210 | text: 'Finish',
211 | variant: ButtonVariant.primary,
212 | onPressed: () {
213 | ExtendedNavigator.root.popAndPush(Routes.mainScreen);
214 | },
215 | ),
216 | SizedBox(height: 15.h.toDouble())
217 | ],
218 | ),
219 | );
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/lib/screens/intro_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:sunshine/sunshine.dart';
3 |
4 | class IntroScreen extends StatefulWidget {
5 | @override
6 | _IntroScreenState createState() => _IntroScreenState();
7 | }
8 |
9 | class _IntroScreenState extends State {
10 | @override
11 | void initState() {
12 | super.initState();
13 | }
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | ScreenUtil.init(
18 | context,
19 | width: 375,
20 | height: 812,
21 | allowFontScaling: true,
22 | );
23 | return Scaffold(
24 | body: Column(
25 | mainAxisAlignment: MainAxisAlignment.start,
26 | children: [
27 | SizedBox(height: 280.h.toDouble()),
28 | Center(
29 | child: SunshineLogo(
30 | height: 150.h.toDouble(),
31 | width: 150.w.toDouble(),
32 | ),
33 | ),
34 | SizedBox(height: 15.h.toDouble()),
35 | Text(
36 | 'Join Sunshine',
37 | style: TextStyle(
38 | fontWeight: FontWeight.w500,
39 | fontSize: 26.ssp.toDouble(),
40 | color: Colors.white,
41 | ),
42 | ),
43 | SizedBox(height: 145.h.toDouble()),
44 | Button(
45 | variant: ButtonVariant.success,
46 | text: 'Generate Account',
47 | onPressed: () {
48 | // step one: Device name, we skip that for now.
49 | ExtendedNavigator.root.push(Routes.generateAccountStepTwoScreen);
50 | },
51 | ),
52 | // SizedBox(height: 20.h.toDouble()),
53 | // Button(
54 | // variant: ButtonVariant.primary,
55 | // text: 'Restore my account',
56 | // onPressed: () {
57 | // ignore: lines_longer_than_80_chars
58 | // ExtendedNavigator.root.push(Routes.recoverAccountStepTwoScreen);
59 | // },
60 | // ),
61 | ],
62 | ),
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/lib/screens/logs_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:sunshine/sunshine.dart';
3 | import 'package:xterm/frontend/terminal_view.dart';
4 | import 'package:xterm/xterm.dart';
5 |
6 | class LoggerScreen extends StatefulWidget {
7 | @override
8 | _LoggerScreenState createState() => _LoggerScreenState();
9 | }
10 |
11 | class _LoggerScreenState extends State {
12 | LoggerService _loggerService;
13 | @override
14 | void initState() {
15 | super.initState();
16 | _loggerService = GetIt.I.get();
17 | }
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | return Scaffold(
22 | appBar: const MyAppBar(title: 'Logger'),
23 | body: StreamBuilder(
24 | stream: _loggerService.stream,
25 | builder: (context, snapshot) {
26 | final _terminal = Terminal(
27 | onInput: (_) {},
28 | );
29 | _loggerService.items.forEach(_terminal.write);
30 | return TerminalView(
31 | terminal: _terminal,
32 | );
33 | },
34 | ),
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/screens/main_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:shimmer/shimmer.dart';
3 | import 'package:sunshine/models/models.dart';
4 | import 'package:sunshine/sunshine.dart';
5 | import 'package:sunshine_ffi/dto.dart';
6 | import 'package:url_launcher/url_launcher.dart';
7 |
8 | class MainScreen extends StatefulWidget {
9 | @override
10 | _MainScreenState createState() => _MainScreenState();
11 | }
12 |
13 | class _MainScreenState extends State {
14 | BountyService bountyService;
15 | @override
16 | void initState() {
17 | super.initState();
18 | bountyService = GetIt.I.get();
19 | }
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return Scaffold(
24 | appBar: MyAppBar(
25 | title: 'Bounties',
26 | leading: IconButton(
27 | icon: const Icon(
28 | Icons.person,
29 | color: Colors.white,
30 | ),
31 | onPressed: () {
32 | ExtendedNavigator.root.push(Routes.accountScreen);
33 | },
34 | ),
35 | actions: [
36 | IconButton(
37 | icon: const Icon(
38 | Icons.add,
39 | color: Colors.white,
40 | ),
41 | onPressed: () async {
42 | final res = await ExtendedNavigator.root.push(
43 | Routes.createBountyScreen,
44 | );
45 | if (res != null) {
46 | // refresh
47 | setState(() {});
48 | }
49 | },
50 | ),
51 | ],
52 | ),
53 | body: RefreshIndicator(
54 | onRefresh: () async {
55 | setState(() {});
56 | },
57 | child: FutureBuilder>(
58 | initialData: const [],
59 | builder: _buildList,
60 | future: bountyService.listOpenBounties(BigInt.one),
61 | ),
62 | ),
63 | );
64 | }
65 |
66 | Widget _buildList(
67 | BuildContext context,
68 | AsyncSnapshot> snapshot,
69 | ) {
70 | if (snapshot.connectionState == ConnectionState.waiting) {
71 | return ListView.builder(
72 | itemBuilder: (_, i) => SizedBox(
73 | width: ScreenUtil.screenWidthPx,
74 | height: 80,
75 | child: Shimmer.fromColors(
76 | baseColor: AppColors.mainBackground,
77 | highlightColor: AppColors.success,
78 | child: BountyItem(
79 | bounty: Bounty(
80 | info: BountyInformation(
81 | issueNumber: BigInt.from(i) + BigInt.one,
82 | repoName: 'sunshine',
83 | repoOwner: 'sunshine',
84 | total: BigInt.from(1000),
85 | ),
86 | issue: GithubIssue(
87 | createdAt: DateTime.now(),
88 | number: 0,
89 | title: getRandomIssueTitle(),
90 | user: User(
91 | avatarUrl:
92 | 'https://avatars0.githubusercontent.com/u/55122894',
93 | ),
94 | ),
95 | ),
96 | ),
97 | ),
98 | ),
99 | itemCount: rnd.nextInt(8) + 1,
100 | );
101 | }
102 | if (snapshot.hasData) {
103 | return ListView.separated(
104 | itemBuilder: (_, i) {
105 | final bounty = snapshot.data[i];
106 | return GestureDetector(
107 | child: BountyItem(
108 | bounty: bounty,
109 | ),
110 | onLongPress: () => launch(bounty.issue.htmlUrl),
111 | onTap: () async {
112 | debugPrint('Open Bounty ${bounty.info.id}');
113 | final res = await ExtendedNavigator.root.push(
114 | Routes.bountyScreen,
115 | arguments: BountyScreenArguments(
116 | bounty: bounty,
117 | ),
118 | );
119 | if (res != null) {
120 | // refresh
121 | setState(() {});
122 | }
123 | },
124 | );
125 | },
126 | separatorBuilder: (context, _) => const Divider(
127 | color: AppColors.disabled,
128 | thickness: 0.2,
129 | ),
130 | itemCount: snapshot.data.length,
131 | );
132 | } else {
133 | return const Center(child: SunshineLoading());
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/lib/screens/paper_key_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:sunshine/sunshine.dart';
3 |
4 | class PaperKeyScreen extends StatefulWidget {
5 | @override
6 | _PaperKeyScreenState createState() => _PaperKeyScreenState();
7 | }
8 |
9 | class _PaperKeyScreenState extends State {
10 | DeviceService _deviceService;
11 |
12 | @override
13 | void initState() {
14 | super.initState();
15 | _deviceService = GetIt.I.get();
16 | }
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return Scaffold(
21 | appBar: const MyAppBar(title: 'Devices'),
22 | body: Column(
23 | crossAxisAlignment: CrossAxisAlignment.start,
24 | children: [
25 | const HeaderText('Device paper key'),
26 | SizedBox(height: 28.h.toDouble()),
27 | FutureBuilder(
28 | future: _deviceService.addPaperKey(),
29 | initialData: '...',
30 | builder: (context, snapshot) => Input(
31 | hintText: snapshot.data,
32 | maxLines: 5,
33 | readOnly: true,
34 | ),
35 | ),
36 | const Padding(
37 | padding: EdgeInsets.all(22),
38 | child: HintText(
39 | 'A paper key can be used to access your account in case you'
40 | ' lose all your devices. Keep one in a safe place '
41 | '(like a wallet) to keep your data safe.',
42 | maxLines: 4,
43 | softWrap: true,
44 | ),
45 | ),
46 | const Expanded(child: SizedBox()),
47 | Button(
48 | text: 'Yes, I wrote it down',
49 | onPressed: ExtendedNavigator.root.pop,
50 | ),
51 | SizedBox(height: 15.h.toDouble()),
52 | ],
53 | ),
54 | );
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/screens/screens.dart:
--------------------------------------------------------------------------------
1 | export 'account_screen.dart';
2 | export 'blank_screen.dart';
3 | export 'bounty_screen.dart';
4 | export 'create_bounty_screen.dart';
5 | export 'devices_screen.dart';
6 | export 'generate_account_screen.dart';
7 | export 'identities_screen.dart';
8 | export 'intro_screen.dart';
9 | export 'logs_screen.dart';
10 | export 'main_screen.dart';
11 | export 'paper_key_screen.dart';
12 | export 'recover_account_screen.dart';
13 | export 'splash_screen.dart';
14 | export 'submit_for_bounty_screen.dart';
15 | export 'wallet_transfer_screen.dart';
16 |
--------------------------------------------------------------------------------
/lib/screens/splash_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:shake/shake.dart';
3 | import 'package:sunshine/sunshine.dart';
4 |
5 | class SplashScreen extends StatefulWidget {
6 | @override
7 | _SplashScreenState createState() => _SplashScreenState();
8 | }
9 |
10 | class _SplashScreenState extends State {
11 | KeyService _keyService;
12 | ClientService _clientService;
13 |
14 | @override
15 | void initState() {
16 | super.initState();
17 | _keyService = GetIt.I.get();
18 | _clientService = GetIt.I.get();
19 | GetIt.I.get().listenToLogs();
20 | // Shake the Device to view logs ;)
21 | final shakeDetector = ShakeDetector.autoStart(onPhoneShake: () {
22 | ExtendedNavigator.root.push(Routes.loggerScreen);
23 | });
24 | // ignore: cascade_invocations
25 | shakeDetector.startListening();
26 | debugPrint('Shake the device to see the logs');
27 | }
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | ScreenUtil.init(
32 | context,
33 | width: 375,
34 | height: 812,
35 | allowFontScaling: true,
36 | );
37 | return Scaffold(
38 | body: Column(
39 | mainAxisAlignment: MainAxisAlignment.start,
40 | children: [
41 | SizedBox(height: 280.h.toDouble()),
42 | Center(
43 | child: SunshineLogo(
44 | height: 150.h.toDouble(),
45 | width: 150.w.toDouble(),
46 | ),
47 | ),
48 | const Expanded(child: SizedBox()),
49 | FutureBuilder(
50 | future: _clientService.ready,
51 | builder: _clientIsReady,
52 | ),
53 | SizedBox(height: 50.h.toDouble()),
54 | ],
55 | ),
56 | );
57 | }
58 |
59 | Widget _clientIsReady(BuildContext context, AsyncSnapshot snapshot) {
60 | if (snapshot.hasData) {
61 | return FutureBuilder(
62 | future: _keyService.hasKey(),
63 | builder: _hasDeviceKey,
64 | );
65 | } else if (snapshot.hasError) {
66 | return Center(
67 | child: HintText(
68 | 'error while starting up client: ${snapshot.error}',
69 | maxLines: 4,
70 | textAlign: TextAlign.center,
71 | ),
72 | );
73 | } else {
74 | return Column(
75 | children: const [
76 | CircularProgressIndicator(),
77 | SizedBox(height: 20),
78 | HintText('Starting up client ...'),
79 | ],
80 | );
81 | }
82 | }
83 |
84 | Widget _hasDeviceKey(BuildContext context, AsyncSnapshot snapshot) {
85 | if (snapshot.hasData && snapshot.data) {
86 | Future.microtask(
87 | () => ExtendedNavigator.root.popAndPush(Routes.mainScreen),
88 | );
89 | } else {
90 | Future.microtask(
91 | () => ExtendedNavigator.root.popAndPush(Routes.introScreen),
92 | );
93 | }
94 | return Container();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/lib/screens/submit_for_bounty_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:sunshine/models/models.dart';
3 | import 'package:sunshine/sunshine.dart';
4 |
5 | class SubmitForBountyScreen extends StatefulWidget {
6 | const SubmitForBountyScreen(this.bounty);
7 | final Bounty bounty;
8 | @override
9 | _SubmitForBountyScreenState createState() => _SubmitForBountyScreenState();
10 | }
11 |
12 | class _SubmitForBountyScreenState extends State {
13 | BountyService _bountyService;
14 | TextEditingController _urlController;
15 | TextEditingController _amountController;
16 | String _errText;
17 |
18 | @override
19 | void initState() {
20 | super.initState();
21 | _bountyService = GetIt.I.get();
22 | _urlController = TextEditingController();
23 | _amountController = TextEditingController();
24 | }
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | final bounty = widget.bounty;
29 | return Scaffold(
30 | appBar: const MyAppBar(title: 'Bounty Submission'),
31 | body: Column(
32 | mainAxisSize: MainAxisSize.min,
33 | crossAxisAlignment: CrossAxisAlignment.start,
34 | children: [
35 | FittedBox(
36 | fit: BoxFit.fitWidth,
37 | child: HeaderText('Submit for ${bounty.issue.title} Bounty'),
38 | ),
39 | SizedBox(height: 30.h.toDouble()),
40 | Input(
41 | hintText: 'Github Issue URL',
42 | errorText: _errText,
43 | controller: _urlController,
44 | enableSuggestions: true,
45 | textInputType: TextInputType.url,
46 | textInputAction: TextInputAction.next,
47 | onChanged: (v) {
48 | if (_errText != null) {
49 | setState(() {
50 | _errText = null;
51 | });
52 | }
53 | },
54 | ),
55 | SizedBox(height: 14.h.toDouble()),
56 | Input(
57 | hintText: 'tokens amount',
58 | controller: _amountController,
59 | textInputType: const TextInputType.numberWithOptions(
60 | signed: false,
61 | decimal: false,
62 | ),
63 | ),
64 | const Expanded(
65 | flex: 1,
66 | child: SizedBox(),
67 | ),
68 | Builder(
69 | builder: (context) => Button(
70 | text: 'Submit',
71 | variant: ButtonVariant.success,
72 | onPressed: () => _submitForBounty(context),
73 | ),
74 | ),
75 | SizedBox(height: 15.h.toDouble())
76 | ],
77 | ),
78 | );
79 | }
80 |
81 | Future _submitForBounty(BuildContext context) async {
82 | if (_urlController.text.isEmpty) {
83 | setState(() {
84 | _errText = 'The Url is required';
85 | });
86 | return;
87 | }
88 | final data = githubParseIssueUrl(_urlController.text);
89 | if (data == null) {
90 | setState(() {
91 | _errText = 'this looks like not a valid github issue url!';
92 | });
93 | return;
94 | }
95 | FocusScope.of(context).requestFocus(FocusNode());
96 | try {
97 | final id = await _bountyService.submitForBounty(
98 | widget.bounty.info.id,
99 | data.owner,
100 | data.repo,
101 | data.issue,
102 | BigInt.parse(_amountController.text),
103 | );
104 | print('Submitted for bounty with: $id');
105 | await Future.delayed(
106 | const Duration(milliseconds: 50),
107 | () {
108 | ExtendedNavigator.root.pop(true);
109 | },
110 | );
111 | } catch (_) {
112 | const snackbar = SnackBar(
113 | content: Text("Couldn't Submit for that bounty, try again"),
114 | backgroundColor: AppColors.danger,
115 | duration: Duration(seconds: 5),
116 | );
117 | final result = Scaffold.of(context).showSnackBar(snackbar);
118 | await result.closed;
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/lib/screens/wallet_transfer_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:sunshine/sunshine.dart';
3 |
4 | class WalletTransferScreen extends StatefulWidget {
5 | const WalletTransferScreen(this.amount);
6 |
7 | final String amount;
8 |
9 | @override
10 | _WalletTransferScreenState createState() => _WalletTransferScreenState();
11 | }
12 |
13 | class _WalletTransferScreenState extends State {
14 | TextEditingController _idController;
15 | String _errText;
16 | @override
17 | void initState() {
18 | super.initState();
19 | _idController = TextEditingController();
20 | }
21 |
22 | @override
23 | void dispose() {
24 | _idController.dispose();
25 | super.dispose();
26 | }
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | return Scaffold(
31 | appBar: const MyAppBar(title: 'Transfer'),
32 | body: Column(
33 | mainAxisSize: MainAxisSize.min,
34 | crossAxisAlignment: CrossAxisAlignment.start,
35 | children: [
36 | const HeaderText('Transfer Tokens from your wallet'),
37 | SizedBox(height: 20.h.toDouble()),
38 | Padding(
39 | padding: EdgeInsets.only(left: 25.w.toDouble()),
40 | child: const HintText('Who you want to send these tokens?'),
41 | ),
42 | SizedBox(height: 20.h.toDouble()),
43 | Input(
44 | hintText: 'UID or username@service',
45 | controller: _idController,
46 | errorText: _errText,
47 | onChanged: (_) {
48 | if (_errText != null) {
49 | _errText = null;
50 | }
51 | },
52 | ),
53 | SizedBox(height: 10.h.toDouble()),
54 | const Expanded(
55 | child: SizedBox(),
56 | ),
57 | Button(
58 | text: 'Next',
59 | variant: ButtonVariant.success,
60 | onPressed: () {
61 | if (_idController.text.isEmpty) {
62 | setState(() {
63 | _errText = 'Please enter the UID or username@service';
64 | });
65 | return;
66 | }
67 | // hide keyboard
68 | FocusScope.of(context).requestFocus(FocusNode());
69 | ExtendedNavigator.root.popAndPush(
70 | Routes.walletTransferConfirmationScreen,
71 | arguments: WalletTransferConfirmationScreenArguments(
72 | amount: widget.amount,
73 | id: _idController.text,
74 | ),
75 | );
76 | },
77 | ),
78 | SizedBox(height: 15.h.toDouble())
79 | ],
80 | ),
81 | );
82 | }
83 | }
84 |
85 | class WalletTransferConfirmationScreen extends StatefulWidget {
86 | const WalletTransferConfirmationScreen(this.id, this.amount);
87 |
88 | final String amount;
89 | final String id;
90 |
91 | @override
92 | _WalletTransferConfirmationScreenState createState() =>
93 | _WalletTransferConfirmationScreenState();
94 | }
95 |
96 | class _WalletTransferConfirmationScreenState
97 | extends State {
98 | WalletService _walletService;
99 | @override
100 | void initState() {
101 | super.initState();
102 | _walletService = GetIt.I.get();
103 | }
104 |
105 | @override
106 | Widget build(BuildContext context) {
107 | return Scaffold(
108 | appBar: const MyAppBar(title: 'Transfer'),
109 | body: Column(
110 | mainAxisSize: MainAxisSize.min,
111 | crossAxisAlignment: CrossAxisAlignment.start,
112 | children: [
113 | SizedBox(height: 30.h.toDouble()),
114 | const Center(child: HeaderText('You are about to transfer')),
115 | SizedBox(height: 30.h.toDouble()),
116 | _TransferTokensValue(tokens: widget.amount),
117 | SizedBox(height: 12.h.toDouble()),
118 | const Center(child: HintText('Tokens')),
119 | SizedBox(height: 30.h.toDouble()),
120 | Center(child: HeaderText('To ${widget.id}')),
121 | SizedBox(height: 30.h.toDouble()),
122 | const Center(
123 | child: HintText('There is no way to reverse this'),
124 | ),
125 | const Expanded(
126 | flex: 1,
127 | child: SizedBox(),
128 | ),
129 | Builder(
130 | builder: (context) => Button(
131 | text: 'Transfer',
132 | variant: ButtonVariant.success,
133 | onPressed: () => _walletTransfer(context),
134 | ),
135 | ),
136 | SizedBox(height: 15.h.toDouble())
137 | ],
138 | ),
139 | );
140 | }
141 |
142 | Future _walletTransfer(BuildContext context) async {
143 | // hide keyboard
144 | FocusScope.of(context).requestFocus(FocusNode());
145 | try {
146 | await _walletService.transfer(
147 | widget.id,
148 | BigInt.parse(widget.amount),
149 | );
150 | await Future.delayed(
151 | const Duration(milliseconds: 100),
152 | () {
153 | ExtendedNavigator.root.popAndPush(
154 | Routes.walletTransferDoneScreen,
155 | arguments: WalletTransferDoneScreenArguments(
156 | id: widget.id,
157 | amount: widget.amount,
158 | ),
159 | );
160 | },
161 | );
162 | } catch (_) {
163 | ExtendedNavigator.root.pop();
164 | const snackbar = SnackBar(
165 | content: Text(
166 | "Couldn't complete the transaction, check the UID or username again.",
167 | ),
168 | backgroundColor: AppColors.danger,
169 | duration: Duration(seconds: 5),
170 | );
171 | Scaffold.of(context).showSnackBar(snackbar);
172 | }
173 | }
174 | }
175 |
176 | class _TransferTokensValue extends StatelessWidget {
177 | const _TransferTokensValue({
178 | @required String tokens,
179 | Key key,
180 | }) : _tokens = tokens,
181 | super(key: key);
182 |
183 | final String _tokens;
184 |
185 | @override
186 | Widget build(BuildContext context) {
187 | return Center(
188 | child: Padding(
189 | padding: EdgeInsets.symmetric(horizontal: 12.w.toDouble()),
190 | child: FittedBox(
191 | child: Text(
192 | '☼$_tokens',
193 | style: TextStyle(
194 | fontSize: 42.ssp.toDouble(),
195 | fontWeight: FontWeight.w900,
196 | color: AppColors.success,
197 | ),
198 | ),
199 | fit: BoxFit.fitWidth,
200 | ),
201 | ),
202 | );
203 | }
204 | }
205 |
206 | class WalletTransferDoneScreen extends StatelessWidget {
207 | const WalletTransferDoneScreen(this.id, this.amount);
208 | final String amount;
209 | final String id;
210 | @override
211 | Widget build(BuildContext context) {
212 | final _walletService = GetIt.I.get();
213 | return Scaffold(
214 | body: Column(
215 | mainAxisAlignment: MainAxisAlignment.start,
216 | children: [
217 | SizedBox(height: 100.h.toDouble()),
218 | const Center(child: HeaderText('You successfully sent')),
219 | SizedBox(height: 30.h.toDouble()),
220 | _TransferTokensValue(tokens: amount),
221 | SizedBox(height: 30.h.toDouble()),
222 | Center(child: HeaderText('tokens to $id')),
223 | SizedBox(height: 30.h.toDouble()),
224 | const SunshineLogo(),
225 | SizedBox(height: 30.h.toDouble()),
226 | FutureBuilder(
227 | initialData: '...',
228 | future: _walletService.balance(),
229 | builder: (context, snapshot) => Center(
230 | child: FittedBox(
231 | fit: BoxFit.fitWidth,
232 | child: HintText('Your current balance: ☼${snapshot.data}'),
233 | ),
234 | ),
235 | ),
236 | SizedBox(height: 30.h.toDouble()),
237 | const Expanded(
238 | flex: 1,
239 | child: SizedBox(),
240 | ),
241 | Button(
242 | text: 'Finish',
243 | variant: ButtonVariant.primary,
244 | onPressed: () {
245 | ExtendedNavigator.root.popPages(3);
246 | },
247 | ),
248 | SizedBox(height: 15.h.toDouble())
249 | ],
250 | ),
251 | );
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/lib/services/account_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:sunshine/sunshine.dart';
2 | import 'package:sunshine/models/models.dart';
3 | import 'package:injectable/injectable.dart';
4 |
5 | import 'client/client_service.dart';
6 |
7 | @lazySingleton
8 | class AccountService {
9 | AccountService(
10 | {ClientService clientService,
11 | IdentityService identityService,
12 | DeviceService deviceService})
13 | : _clientService = clientService,
14 | _identityService = identityService,
15 | _deviceService = deviceService;
16 |
17 | final ClientService _clientService;
18 | final IdentityService _identityService;
19 | final DeviceService _deviceService;
20 |
21 | Future currentAccount() async {
22 | final uid = await _clientService.uid();
23 | return Account(
24 | uid: uid,
25 | );
26 | }
27 |
28 | Future> identities() async {
29 | final uid = await _clientService.uid();
30 | return _identityService.identities(uid);
31 | }
32 |
33 | Future> devices() async {
34 | final deviceId = await _clientService.deviceId();
35 | final device = Device(id: deviceId, currentDevice: true);
36 | final devices = await _deviceService.devices();
37 | devices.removeWhere((element) => element.id == deviceId);
38 | return [device, ...devices];
39 | }
40 |
41 | Future currentDevice() async {
42 | final deviceId = await _clientService.deviceId();
43 | return Device(id: deviceId, currentDevice: true);
44 | }
45 |
46 | Future uid() {
47 | return _clientService.uid();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/services/bounty_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:injectable/injectable.dart';
2 | import 'package:sunshine/models/models.dart';
3 | import 'package:sunshine/services/services.dart';
4 |
5 | @injectable
6 | @lazySingleton
7 | class BountyService {
8 | BountyService({
9 | ClientService clientService,
10 | GithubService githubService,
11 | }) : _clientService = clientService,
12 | _githubService = githubService;
13 |
14 | final ClientService _clientService;
15 | final GithubService _githubService;
16 |
17 | Future getBounty(BigInt bountyId) async {
18 | final info = await _clientService.getBounty(bountyId);
19 | final issue = await _githubService.getIssue(
20 | info.repoOwner,
21 | info.repoName,
22 | info.issueNumber.toInt(),
23 | );
24 | if (issue == null) {
25 | return null;
26 | }
27 | return Bounty(info: info, issue: issue);
28 | }
29 |
30 | Future getSubmission(BigInt submissionId) async {
31 | final info = await _clientService.getSubmission(submissionId);
32 | final issue = await _githubService.getIssue(
33 | info.repoOwner,
34 | info.repoName,
35 | info.issueNumber.toInt(),
36 | );
37 | if (issue == null) {
38 | return null;
39 | }
40 | return BountySubmission(info: info, issue: issue);
41 | }
42 |
43 | Future> listOpenBounties(BigInt min) async {
44 | final list = await _clientService.listOpenBounties(min);
45 | final result = [];
46 | for (final info in list) {
47 | final issue = await _githubService.getIssue(
48 | info.repoOwner,
49 | info.repoName,
50 | info.issueNumber.toInt(),
51 | );
52 | if (issue == null) {
53 | continue;
54 | }
55 | result.add(Bounty(info: info, issue: issue));
56 | }
57 | return result;
58 | }
59 |
60 | Future> listBountySubmissions(
61 | BigInt bountyId,
62 | ) async {
63 | final list = await _clientService.listBountySubmissions(bountyId);
64 | final result = [];
65 | for (final info in list) {
66 | final issue = await _githubService.getIssue(
67 | info.repoOwner,
68 | info.repoName,
69 | info.issueNumber.toInt(),
70 | );
71 | if (issue == null) {
72 | continue;
73 | }
74 | result.add(BountySubmission(info: info, issue: issue));
75 | }
76 | return result;
77 | }
78 |
79 | Future postBounty(
80 | String repoOwner,
81 | String repoName,
82 | BigInt issueNumber,
83 | BigInt amount,
84 | ) {
85 | return _clientService.postBounty(
86 | repoOwner,
87 | repoName,
88 | issueNumber,
89 | amount,
90 | );
91 | }
92 |
93 | Future submitForBounty(
94 | BigInt bountyId,
95 | String repoOwner,
96 | String repoName,
97 | BigInt issueNumber,
98 | BigInt amount,
99 | ) {
100 | return _clientService.submitForBounty(
101 | bountyId,
102 | repoOwner,
103 | repoName,
104 | issueNumber,
105 | amount,
106 | );
107 | }
108 |
109 | Future approveBounty(BigInt submissionId) {
110 | return _clientService.approveBounty(submissionId);
111 | }
112 |
113 | Future contibuteToBounty(BigInt bountyId, BigInt amount) {
114 | return _clientService.contibuteToBounty(bountyId, amount);
115 | }
116 |
117 | Future canSubmitFor(Bounty bounty) async {
118 | final uid = await _clientService.uid();
119 | return uid != bounty.info.depositer;
120 | }
121 |
122 | Future canApprove(Bounty bounty) async {
123 | final uid = await _clientService.uid();
124 | return uid == bounty.info.depositer;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/lib/services/client/client_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:sunshine/models/models.dart';
2 | import 'package:sunshine_ffi/dto.dart';
3 |
4 | abstract class ClientService {
5 | Future get ready;
6 | Future lock();
7 | Future unlock(String password);
8 | Future setKey(String password, {String suri, String paperKey});
9 | Future uid();
10 | Future hasKey();
11 | Future balance();
12 | Future transfer(String id, BigInt amount);
13 | Future mint();
14 | Future getBounty(BigInt bountyId);
15 | Future getSubmission(BigInt submissionId);
16 | Future> listOpenBounties(BigInt min);
17 | Future> listBountySubmissions(
18 | BigInt bountyId,
19 | );
20 | Future postBounty(
21 | String repoOwner,
22 | String repoName,
23 | BigInt issueNumber,
24 | BigInt amount,
25 | );
26 | Future submitForBounty(
27 | BigInt bountyId,
28 | String repoOwner,
29 | String repoName,
30 | BigInt issueNumber,
31 | BigInt amount,
32 | );
33 | Future approveBounty(BigInt submissionId);
34 | Future contibuteToBounty(BigInt bountyId, BigInt amount);
35 | Future deviceId();
36 | Future revokeDevice(String id);
37 | Future addDevice(String id);
38 | Future> devices();
39 | Future addPaperKey();
40 | Future> identities(String uid);
41 | Future uidOf(String id);
42 | Future proveIdentity(SocialIdentityService service);
43 | Future revokeIdentity(SocialIdentityService service);
44 | }
45 |
46 | class ProveIdentityResult {
47 | const ProveIdentityResult(this.instructions, this.proof);
48 | final String instructions;
49 | final String proof;
50 | }
51 |
--------------------------------------------------------------------------------
/lib/services/client/prod_client_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:sunshine/models/models.dart';
2 | import 'package:sunshine/sunshine.dart';
3 | import 'package:injectable/injectable.dart';
4 | import 'package:sunshine_ffi/dto.dart';
5 |
6 | import 'client_service.dart';
7 | import 'sunshine_client_service.dart';
8 |
9 | @prod
10 | @LazySingleton(as: ClientService)
11 | class ProdClientService implements ClientService {
12 | ProdClientService({SunshineClientService sunshineClientService})
13 | : _sunshineClientService = sunshineClientService;
14 | final SunshineClientService _sunshineClientService;
15 |
16 | @override
17 | Future get ready => _sunshineClientService.ready;
18 |
19 | @override
20 | Future deviceId() async {
21 | return _sunshineClientService.currentDevice();
22 | }
23 |
24 | @override
25 | Future addPaperKey() {
26 | return _sunshineClientService.addPaperKey();
27 | }
28 |
29 | @override
30 | Future addDevice(String id) {
31 | return _sunshineClientService.addDevice(id);
32 | }
33 |
34 | @override
35 | Future> identities(String uid) async {
36 | return _sunshineClientService.listIdentities(uid);
37 | }
38 |
39 | @override
40 | Future proveIdentity(
41 | SocialIdentityService service) async {
42 | final result = await _sunshineClientService.proveIdentity(service.display);
43 | return ProveIdentityResult(result[0], result[1]);
44 | }
45 |
46 | @override
47 | Future> devices() async {
48 | return _sunshineClientService.listDevices(await uid());
49 | }
50 |
51 | @override
52 | Future revokeDevice(String id) {
53 | return _sunshineClientService.removeDevice(id);
54 | }
55 |
56 | @override
57 | Future revokeIdentity(SocialIdentityService service) {
58 | return _sunshineClientService.revokeIdentity(service.display);
59 | }
60 |
61 | @override
62 | Future uidOf(String id) {
63 | return _sunshineClientService.resolveUid(id);
64 | }
65 |
66 | @override
67 | Future lock() {
68 | return _sunshineClientService.lockKey();
69 | }
70 |
71 | @override
72 | Future setKey(String password, {String suri, String paperKey}) async {
73 | return _sunshineClientService.setKey(
74 | password,
75 | suri: suri,
76 | paperKey: paperKey,
77 | );
78 | }
79 |
80 | @override
81 | Future unlock(String password) {
82 | return _sunshineClientService.unlockKey(password);
83 | }
84 |
85 | @override
86 | Future balance() async {
87 | return _sunshineClientService
88 | .balance(null)
89 | .then((value) => value.toString());
90 | }
91 |
92 | @override
93 | Future transfer(String id, BigInt amount) {
94 | return _sunshineClientService.transfer(id, amount);
95 | }
96 |
97 | @override
98 | Future uid() async {
99 | return _sunshineClientService.resolveUid(await deviceId());
100 | }
101 |
102 | @override
103 | Future mint() {
104 | return _sunshineClientService.mint();
105 | }
106 |
107 | @override
108 | Future hasKey() {
109 | return _sunshineClientService.hasKey();
110 | }
111 |
112 | @override
113 | Future approveBounty(BigInt submissionId) {
114 | return _sunshineClientService.approveBounty(submissionId);
115 | }
116 |
117 | @override
118 | Future contibuteToBounty(BigInt bountyId, BigInt amount) {
119 | return _sunshineClientService.contibuteToBounty(bountyId, amount);
120 | }
121 |
122 | @override
123 | Future getBounty(BigInt bountyId) {
124 | return _sunshineClientService.getBounty(bountyId);
125 | }
126 |
127 | @override
128 | Future getSubmission(BigInt submissionId) {
129 | return _sunshineClientService.getSubmission(submissionId);
130 | }
131 |
132 | @override
133 | Future> listBountySubmissions(
134 | BigInt bountyId,
135 | ) async {
136 | final list = await _sunshineClientService.listBountySubmissions(bountyId);
137 | print('Bounty $bountyId Submissions: $list');
138 | return list;
139 | }
140 |
141 | @override
142 | Future> listOpenBounties(BigInt min) async {
143 | final list = await _sunshineClientService.listOpenBounties(min);
144 | print('Open Bounties: $list');
145 | return list;
146 | }
147 |
148 | @override
149 | Future postBounty(
150 | String repoOwner,
151 | String repoName,
152 | BigInt issueNumber,
153 | BigInt amount,
154 | ) async {
155 | final id = await _sunshineClientService.postBounty(
156 | repoOwner,
157 | repoName,
158 | issueNumber,
159 | amount,
160 | );
161 | return id;
162 | }
163 |
164 | @override
165 | Future submitForBounty(
166 | BigInt bountyId,
167 | String repoOwner,
168 | String repoName,
169 | BigInt issueNumber,
170 | BigInt amount,
171 | ) {
172 | return _sunshineClientService.submitForBounty(
173 | bountyId,
174 | repoOwner,
175 | repoName,
176 | issueNumber,
177 | amount,
178 | );
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/lib/services/client/sunshine_client_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:sunshine/services/services.dart';
2 | import 'package:injectable/injectable.dart';
3 | import 'package:sunshine_ffi/sunshine_client.dart';
4 |
5 | @lazySingleton
6 | class SunshineClientService extends SunshineClient {
7 | SunshineClientService({PathProviderService pathProviderService})
8 | : super(
9 | root: pathProviderService.applicationDocumentsDirectory,
10 | url: 'ws://10.0.2.2:9944',
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/lib/services/device_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:sunshine/models/models.dart';
2 | import 'package:injectable/injectable.dart';
3 |
4 | import 'client/client_service.dart';
5 |
6 | @lazySingleton
7 | class DeviceService {
8 | DeviceService({ClientService clientService}) : _clientService = clientService;
9 | final ClientService _clientService;
10 |
11 | Future> devices() {
12 | return _clientService.devices().then((v) => v
13 | .map(
14 | (e) => Device(
15 | id: e,
16 | currentDevice: false,
17 | ),
18 | )
19 | .toList());
20 | }
21 |
22 | Future addDevice(String deviceId) {
23 | return _clientService.addDevice(deviceId);
24 | }
25 |
26 | Future revokeDevice(String deviceId) {
27 | return _clientService.revokeDevice(deviceId);
28 | }
29 |
30 | Future addPaperKey() {
31 | return _clientService.addPaperKey();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/services/github_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:http_extensions/http_extensions.dart';
2 | import 'package:http_extensions_cache/http_extensions_cache.dart';
3 | import 'package:injectable/injectable.dart';
4 | import 'package:http/http.dart' as http;
5 | import 'package:sunshine/models/models.dart';
6 |
7 | @lazySingleton
8 | @injectable
9 | class GithubService {
10 | GithubService()
11 | : _httpClient = ExtendedClient(
12 | // ignore: avoid_as
13 | inner: http.Client() as http.BaseClient,
14 | extensions: [
15 | CacheExtension(
16 | defaultOptions: CacheOptions(
17 | expiry: const Duration(hours: 1),
18 | forceUpdate: false,
19 | forceCache: false,
20 | ignoreCache: false,
21 | returnCacheOnError: false,
22 | store: MemoryCacheStore(),
23 | ),
24 | ),
25 | ],
26 | );
27 |
28 | final ExtendedClient _httpClient;
29 |
30 | Future getIssue(
31 | String owner,
32 | String repo,
33 | int number,
34 | ) async {
35 | final url = 'https://api.github.com/repos/$owner/$repo/issues/$number';
36 | final result = await _httpClient.get(url);
37 | if (result.statusCode != 200) {
38 | return null;
39 | }
40 | return GithubIssue.fromRawJson(result.body);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/services/identity_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:sunshine/models/models.dart';
2 | import 'package:injectable/injectable.dart';
3 |
4 | import 'client/client_service.dart';
5 |
6 | @lazySingleton
7 | class IdentityService {
8 | IdentityService({ClientService clientService})
9 | : _clientService = clientService;
10 |
11 | final ClientService _clientService;
12 |
13 | Future uidOf(String id) {
14 | return _clientService.uidOf(id);
15 | }
16 |
17 | Future> identities(String uid) {
18 | return _clientService.identities(uid).then(
19 | (ids) => ids.map(_parse).toList(),
20 | );
21 | }
22 |
23 | Future proveIdentity(SocialIdentityService service) {
24 | return _clientService.proveIdentity(service);
25 | }
26 |
27 | Future revokeIdentity(SocialIdentityService service) {
28 | return _clientService.revokeIdentity(service);
29 | }
30 |
31 | SocialIdentityService _parse(String identity) {
32 | print(identity);
33 | if (identity.contains('gist.github.com')) {
34 | final parts = identity.split(' ');
35 | print(parts);
36 | final parts2 = parts[0].split('@');
37 | return GithubIdentity(username: parts2[0], proofUrl: parts[1]);
38 | } else {
39 | return GithubIdentity(username: null, proofUrl: null);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/services/key_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:sunshine/models/models.dart';
2 | import 'package:injectable/injectable.dart';
3 |
4 | import 'client/client_service.dart';
5 |
6 | @lazySingleton
7 | class KeyService {
8 | KeyService({ClientService clientService}) : _clientService = clientService;
9 |
10 | final ClientService _clientService;
11 |
12 | Future lock() {
13 | return _clientService.lock();
14 | }
15 |
16 | Future unlock(String password) {
17 | return _clientService.unlock(password);
18 | }
19 |
20 | Future hasKey() {
21 | return _clientService.hasKey();
22 | }
23 |
24 | Future generate(String password) async {
25 | final deviceId = await _clientService.setKey(password);
26 | print('device ID: $deviceId');
27 | final amount = await _clientService.mint();
28 | print('Account minted with: $amount');
29 | final uid = await _clientService.uid();
30 | return Account(uid: uid);
31 | }
32 |
33 | Future restore(String password, String paperKey) async {
34 | final uid = await _clientService.setKey(password, paperKey: paperKey);
35 | return Account(
36 | uid: uid,
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/services/logger_service.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:injectable/injectable.dart';
4 | import 'package:frusty_logger/frusty_logger.dart';
5 |
6 | @lazySingleton
7 | class LoggerService {
8 | List items = [];
9 | StreamController _streamController;
10 | void _onLogEvent(String event) {
11 | items.add('$event\r\n');
12 | _streamController.add(null);
13 | }
14 |
15 | void listenToLogs() {
16 | FrustyLogger.addListener(_onLogEvent);
17 | _streamController = StreamController.broadcast();
18 | }
19 |
20 | Stream get stream => _streamController.stream;
21 | }
22 |
--------------------------------------------------------------------------------
/lib/services/path_provider_service.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:path_provider/path_provider.dart' as path_provider;
3 |
4 | class PathProviderService {
5 | PathProviderService({
6 | Directory applicationDocumentsDirectory,
7 | }) : _applicationDocumentsDirectory = applicationDocumentsDirectory;
8 |
9 | static Future create() async {
10 | return PathProviderService(
11 | applicationDocumentsDirectory:
12 | await path_provider.getApplicationDocumentsDirectory(),
13 | );
14 | }
15 |
16 | final Directory _applicationDocumentsDirectory;
17 |
18 | Directory get applicationDocumentsDirectory => _applicationDocumentsDirectory;
19 | }
20 |
--------------------------------------------------------------------------------
/lib/services/services.dart:
--------------------------------------------------------------------------------
1 | export 'account_service.dart';
2 | export 'bounty_service.dart';
3 | export 'client/client_service.dart';
4 | export 'device_service.dart';
5 | export 'github_service.dart';
6 | export 'identity_service.dart';
7 | export 'key_service.dart';
8 | export 'logger_service.dart';
9 | export 'path_provider_service.dart';
10 | export 'wallet_service.dart';
11 |
--------------------------------------------------------------------------------
/lib/services/wallet_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:injectable/injectable.dart';
3 |
4 | import 'client/client_service.dart';
5 |
6 | @lazySingleton
7 | class WalletService {
8 | WalletService({ClientService clientService}) : _clientService = clientService;
9 |
10 | final ClientService _clientService;
11 |
12 | Future balance() {
13 | return _clientService.balance();
14 | }
15 |
16 | Future transfer(String to, BigInt amount) {
17 | return _clientService.transfer(to, amount);
18 | }
19 |
20 | Future mint() async {
21 | // Enable mint account when he tap in his balance
22 | // this just for testing ...
23 | if (kDebugMode) {
24 | return _clientService.mint();
25 | } else {
26 | return BigInt.zero;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/setup.dart:
--------------------------------------------------------------------------------
1 | import 'package:get_it/get_it.dart';
2 | import 'package:injectable/injectable.dart';
3 | import 'package:sunshine/setup.iconfig.dart';
4 |
5 | @injectableInit
6 | Future configureDependencies({Environment environment}) =>
7 | $initGetIt(GetIt.I, environment: environment.name);
8 |
--------------------------------------------------------------------------------
/lib/setup.iconfig.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | // **************************************************************************
4 | // InjectableConfigGenerator
5 | // **************************************************************************
6 |
7 | import 'package:sunshine/services/client/dev_client_service.dart';
8 | import 'package:sunshine/services/client/client_service.dart';
9 | import 'package:sunshine/services/device_service.dart';
10 | import 'package:sunshine/services/github_service.dart';
11 | import 'package:sunshine/services/identity_service.dart';
12 | import 'package:sunshine/services/key_service.dart';
13 | import 'package:sunshine/services/logger_service.dart';
14 | import 'package:sunshine/services/path_provider_service.dart';
15 | import 'package:sunshine/core/register_module.dart';
16 | import 'package:shared_preferences/shared_preferences.dart';
17 | import 'package:sunshine/services/client/sunshine_client_service.dart';
18 | import 'package:sunshine/services/wallet_service.dart';
19 | import 'package:sunshine/services/account_service.dart';
20 | import 'package:sunshine/services/bounty_service.dart';
21 | import 'package:sunshine/services/client/prod_client_service.dart';
22 | import 'package:get_it/get_it.dart';
23 |
24 | Future $initGetIt(GetIt g, {String environment}) async {
25 | final registerModule = _$RegisterModule();
26 | g.registerLazySingleton(
27 | () => DeviceService(clientService: g()));
28 | g.registerLazySingleton(() => GithubService());
29 | g.registerLazySingleton(
30 | () => IdentityService(clientService: g()));
31 | g.registerLazySingleton(
32 | () => KeyService(clientService: g()));
33 | g.registerLazySingleton(() => LoggerService());
34 | final pathProviderService = await registerModule.pathProvider;
35 | g.registerLazySingleton(() => pathProviderService);
36 | final sharedPreferences = await registerModule.prefs;
37 | g.registerLazySingleton(() => sharedPreferences);
38 | g.registerLazySingleton(() =>
39 | SunshineClientService(pathProviderService: g()));
40 | g.registerLazySingleton(
41 | () => WalletService(clientService: g()));
42 | g.registerLazySingleton(() => AccountService(
43 | clientService: g(),
44 | identityService: g(),
45 | deviceService: g(),
46 | ));
47 | g.registerFactory(() => BountyService(
48 | clientService: g(), githubService: g()));
49 |
50 | //Register dev Dependencies --------
51 | if (environment == 'dev') {
52 | g.registerLazySingleton(() => DevClientService());
53 | }
54 |
55 | //Register prod Dependencies --------
56 | if (environment == 'prod') {
57 | g.registerLazySingleton(() =>
58 | ProdClientService(sunshineClientService: g()));
59 | }
60 | }
61 |
62 | class _$RegisterModule extends RegisterModule {}
63 |
--------------------------------------------------------------------------------
/lib/sunshine.dart:
--------------------------------------------------------------------------------
1 | export 'package:auto_route/auto_route.dart';
2 | export 'package:flutter_screenutil/flutter_screenutil.dart';
3 | export 'package:get_it/get_it.dart';
4 |
5 | export 'constants.dart';
6 | export 'core/core.dart';
7 | export 'router/router.gr.dart';
8 | export 'screens/screens.dart';
9 | export 'services/services.dart';
10 | export 'ui/ui.dart';
11 | export 'utils/utils.dart';
12 |
--------------------------------------------------------------------------------
/lib/ui/bounty_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:intl/intl.dart';
3 | import 'package:sunshine/models/models.dart';
4 | import 'package:sunshine/sunshine.dart';
5 | import 'package:timeago/timeago.dart' as timeago;
6 |
7 | class BountyItem extends StatefulWidget {
8 | const BountyItem({
9 | this.bounty,
10 | });
11 | final Bounty bounty;
12 |
13 | @override
14 | _BountyItemState createState() => _BountyItemState();
15 | }
16 |
17 | class _BountyItemState extends State {
18 | @override
19 | Widget build(BuildContext context) {
20 | return SizedBox(
21 | height: 81.h.toDouble(),
22 | child: Column(
23 | children: [
24 | _BountyHeader(parent: widget),
25 | _BountyBody(parent: widget),
26 | ],
27 | ),
28 | );
29 | }
30 | }
31 |
32 | class _BountyHeader extends StatelessWidget {
33 | const _BountyHeader({
34 | @required this.parent,
35 | Key key,
36 | }) : super(key: key);
37 |
38 | final BountyItem parent;
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 | return Padding(
43 | padding: EdgeInsets.only(
44 | top: 12.h.toDouble(),
45 | left: 12.w.toDouble(),
46 | right: 9.w.toDouble(),
47 | ),
48 | child: Row(
49 | children: [
50 | Container(
51 | width: 28.w.toDouble(),
52 | height: 28.h.toDouble(),
53 | decoration: BoxDecoration(
54 | borderRadius: BorderRadius.circular(5),
55 | border: const Border.fromBorderSide(
56 | BorderSide(
57 | color: Colors.white,
58 | width: 0.2,
59 | ),
60 | ),
61 | ),
62 | child: Image(
63 | image: NetworkImage(
64 | parent.bounty.issue.user.avatarUrl,
65 | ),
66 | fit: BoxFit.contain,
67 | ),
68 | ),
69 | SizedBox(width: 10.w.toDouble()),
70 | FittedBox(
71 | fit: BoxFit.fitWidth,
72 | child: Text(
73 | '${parent.bounty.info.repoOwner}/${parent.bounty.info.repoName} #${parent.bounty.info.issueNumber}',
74 | style: TextStyle(
75 | color: const Color(0xFF989898),
76 | fontSize: 14.ssp.toDouble(),
77 | ),
78 | ),
79 | ),
80 | const Expanded(child: SizedBox()),
81 | Text(
82 | timeago.format(parent.bounty.issue.createdAt),
83 | style: TextStyle(
84 | color: const Color(0xFF989898),
85 | fontSize: 12.ssp.toDouble(),
86 | ),
87 | ),
88 | SizedBox(width: 8.w.toDouble()),
89 | ],
90 | ),
91 | );
92 | }
93 | }
94 |
95 | class _BountyBody extends StatelessWidget {
96 | _BountyBody({
97 | @required this.parent,
98 | Key key,
99 | }) : super(key: key);
100 |
101 | final BountyItem parent;
102 | final numberFormat = NumberFormat.compactCurrency(
103 | decimalDigits: 0,
104 | symbol: '',
105 | );
106 |
107 | @override
108 | Widget build(BuildContext context) {
109 | return Padding(
110 | padding: EdgeInsets.only(
111 | top: 5.h.toDouble(),
112 | left: 12.w.toDouble(),
113 | right: 9.w.toDouble(),
114 | ),
115 | child: Row(
116 | children: [
117 | SizedBox(width: 40.w.toDouble()),
118 | Container(
119 | width: 250.w.toDouble(),
120 | height: 20.h.toDouble(),
121 | child: Text(
122 | parent.bounty.issue.title,
123 | style: TextStyle(
124 | color: Colors.white,
125 | fontSize: 16.ssp.toDouble(),
126 | fontWeight: FontWeight.w500,
127 | ),
128 | overflow: TextOverflow.ellipsis,
129 | textAlign: TextAlign.start,
130 | ),
131 | ),
132 | const Expanded(child: SizedBox()),
133 | Container(
134 | width: 36.w.toDouble(),
135 | height: 18.h.toDouble(),
136 | padding: const EdgeInsets.all(2),
137 | decoration: BoxDecoration(
138 | color: AppColors.success,
139 | borderRadius: BorderRadius.circular(3),
140 | ),
141 | child: Center(
142 | child: FittedBox(
143 | fit: BoxFit.fitWidth,
144 | child: Text(
145 | numberFormat.format(parent.bounty.info.total.toInt()),
146 | style: TextStyle(
147 | color: const Color(0xFF25C100),
148 | fontSize: 12.ssp.toDouble(),
149 | fontWeight: FontWeight.bold,
150 | ),
151 | ),
152 | ),
153 | ),
154 | ),
155 | SizedBox(width: 8.w.toDouble()),
156 | ],
157 | ),
158 | );
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/lib/ui/button.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:sunshine/sunshine.dart';
5 |
6 | enum ButtonVariant { primary, secondry, success, danger }
7 |
8 | class Button extends StatefulWidget {
9 | const Button({
10 | this.onPressed,
11 | this.text = 'Button',
12 | this.enabled = true,
13 | this.textColor = Colors.white,
14 | this.variant = ButtonVariant.primary,
15 | });
16 | @override
17 | _ButtonState createState() => _ButtonState();
18 | final String text;
19 | final FutureOr Function() onPressed;
20 | final Color textColor;
21 | final bool enabled;
22 | final ButtonVariant variant;
23 | }
24 |
25 | class _ButtonState extends State