├── .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 | ![Lints](https://github.com/sunshine-protocol/sunshine-bounty-ui/workflows/Lints/badge.svg) 4 | ![iOS](https://github.com/sunshine-protocol/sunshine-bounty-ui/workflows/iOS/badge.svg) 5 | ![Android](https://github.com/sunshine-protocol/sunshine-bounty-ui/workflows/Android/badge.svg) 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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 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