├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── check.yml
│ └── release.yml
├── .gitignore
├── .metadata
├── .vscode
└── launch.json
├── LICENSE
├── README.md
├── android
├── .project
├── .settings
│ └── org.eclipse.buildship.core.prefs
├── app
│ ├── .classpath
│ ├── .project
│ ├── .settings
│ │ └── org.eclipse.buildship.core.prefs
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── io
│ │ │ └── github
│ │ │ └── stefanji
│ │ │ └── fluttergitlab
│ │ │ └── MainActivity.java
│ │ └── res
│ │ ├── drawable
│ │ ├── launch_background.xml
│ │ └── screen.png
│ │ ├── 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
│ │ ├── string.xml
│ │ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── key.jks
├── key.properties
└── settings.gradle
├── art
├── 1.png
├── 2.png
├── 3.png
├── 4.png
├── 5.png
├── 6.png
├── 7.png
├── 8.png
├── F4Lab_arch.png
├── f4lab_home.png
└── logo.png
├── ios
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ ├── Flutter.podspec
│ └── Release.xcconfig
├── Podfile
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── Runner
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-20x20@1x.png
│ │ ├── Icon-20x20@2x.png
│ │ ├── Icon-20x20@3x.png
│ │ ├── Icon-29x29@1x.png
│ │ ├── Icon-29x29@2x.png
│ │ ├── Icon-29x29@3x.png
│ │ ├── Icon-40x40@2x.png
│ │ ├── Icon-40x40@3x.png
│ │ ├── Icon-60x60@2x.png
│ │ ├── Icon-60x60@3x.png
│ │ ├── Icon-76x76@1x.png
│ │ ├── Icon-76x76@2x.png
│ │ ├── Icon-83.5@2x.png
│ │ └── Icon-marketing-1024x1024.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── main.m
├── lib
├── api.dart
├── const.dart
├── gitlab_client.dart
├── main.dart
├── main_dev.dart
├── model
│ ├── approvals.dart
│ ├── commit.dart
│ ├── diff.dart
│ ├── discussion.dart
│ ├── group.dart
│ ├── jobs.dart
│ ├── merge_request.dart
│ ├── pipeline.dart
│ ├── project.dart
│ ├── runner.dart
│ ├── todo.dart
│ └── user.dart
├── providers
│ ├── package_info.dart
│ ├── theme.dart
│ └── user.dart
├── ui
│ ├── activity
│ │ └── activity_tab.dart
│ ├── config
│ │ └── config_page.dart
│ ├── group
│ │ └── groups_tab.dart
│ ├── home_nav.dart
│ ├── home_page.dart
│ ├── project
│ │ ├── jobs
│ │ │ └── jobs_tab.dart
│ │ ├── mr
│ │ │ ├── approve.dart
│ │ │ ├── commit_diff.dart
│ │ │ ├── diff.dart
│ │ │ ├── merge_request_action.dart
│ │ │ ├── mr_detail_tabs.dart
│ │ │ ├── mr_home.dart
│ │ │ ├── mr_list.dart
│ │ │ ├── mr_list_item.dart
│ │ │ └── mr_tab_jobs.dart
│ │ ├── project_detail.dart
│ │ └── project_tabs.dart
│ └── todo
│ │ └── todo_tab.dart
├── user_helper.dart
├── util
│ ├── date_util.dart
│ ├── exception_capture.dart
│ └── widget_util.dart
└── widget
│ └── comm_ListView.dart
├── pubspec.yaml
├── script
├── run_dev.sh
└── upload-apk.sh
└── test
└── util
└── date_util_test.dart
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: stefanJi
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Smartphone (please complete the following information):**
27 | - Device: [e.g. Pixel3]
28 | - OS: [e.g. Android 7.0]
29 | - Version [e.g. 1.6.1]
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: stefanJi
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/check.yml:
--------------------------------------------------------------------------------
1 | name: F4LabCI
2 | on: pull_request
3 |
4 | jobs:
5 | test:
6 | name: Test on ${{ matrix.os }}
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | os: [ubuntu-latest, windows-latest, macos-latest]
11 | steps:
12 | - uses: actions/checkout@v1
13 | - uses: actions/setup-java@v1
14 | with:
15 | java-version: "12.x"
16 | - uses: subosito/flutter-action@v1
17 | with:
18 | # same with pubspec.yaml
19 | flutter-version: "1.12.13+hotfix.9"
20 | channel: "stable"
21 | - run: flutter pub get
22 | - run: flutter analyze --no-pub --no-current-package lib/ test/
23 | - run: flutter test --no-pub test/
24 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: F4LabCIRelease
2 | on:
3 | push:
4 | tags:
5 | - "release-v*"
6 |
7 | jobs:
8 | release-to-gitHub:
9 | name: release
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v1
13 | - uses: actions/setup-java@v1
14 | with:
15 | java-version: "12.x"
16 | - uses: subosito/flutter-action@v1
17 | with:
18 | # same with pubspec.yaml
19 | flutter-version: "1.12.13+hotfix.9"
20 | channel: "stable"
21 | - run: flutter pub get
22 | - run: flutter pub deps
23 | - run: flutter analyze --no-pub --no-current-package lib/ test/
24 | - run: flutter test --no-pub test/
25 | - run: flutter build apk --target-platform android-arm,android-arm64,android-x64 --split-per-abi
26 | - uses: softprops/action-gh-release@v1
27 | with:
28 | files: build/app/outputs/apk/release/*.apk
29 | env:
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.lock
4 | *.log
5 | *.pyc
6 | *.swp
7 | .DS_Store
8 | .atom/
9 | .buildlog/
10 | .history
11 | .svn/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # Visual Studio Code related
20 | .vscode/settings.json
21 |
22 | # Flutter repo-specific
23 | /bin/cache/
24 | /bin/mingit/
25 | /dev/benchmarks/mega_gallery/
26 | /dev/bots/.recipe_deps
27 | /dev/bots/android_tools/
28 | /dev/docs/doc/
29 | /dev/docs/flutter.docs.zip
30 | /dev/docs/lib/
31 | /dev/docs/pubspec.yaml
32 | /dev/integration_tests/**/xcuserdata
33 | /dev/integration_tests/**/Pods
34 | /packages/flutter/coverage/
35 | version
36 |
37 | # packages file containing multi-root paths
38 | .packages.generated
39 |
40 | # Flutter/Dart/Pub related
41 | **/doc/api/
42 | .dart_tool/
43 | .flutter-plugins
44 | .packages
45 | .pub-cache/
46 | .pub/
47 | build/
48 | flutter_*.png
49 | linked_*.ds
50 | unlinked.ds
51 | unlinked_spec.ds
52 |
53 | # Android related
54 | **/android/**/gradle-wrapper.jar
55 | **/android/.gradle
56 | **/android/captures/
57 | **/android/gradlew
58 | **/android/gradlew.bat
59 | **/android/local.properties
60 | **/android/**/GeneratedPluginRegistrant.java
61 | **/android/key.properties
62 | *.jks
63 |
64 | # iOS/XCode related
65 | **/ios/**/*.mode1v3
66 | **/ios/**/*.mode2v3
67 | **/ios/**/*.moved-aside
68 | **/ios/**/*.pbxuser
69 | **/ios/**/*.perspectivev3
70 | **/ios/**/*sync/
71 | **/ios/**/.sconsign.dblite
72 | **/ios/**/.tags*
73 | **/ios/**/.vagrant/
74 | **/ios/**/DerivedData/
75 | **/ios/**/Icon?
76 | **/ios/**/Pods/
77 | **/ios/**/.symlinks/
78 | **/ios/**/profile
79 | **/ios/**/xcuserdata
80 | **/ios/.generated/
81 | **/ios/Flutter/App.framework
82 | **/ios/Flutter/Flutter.framework
83 | **/ios/Flutter/Generated.xcconfig
84 | **/ios/Flutter/app.flx
85 | **/ios/Flutter/app.zip
86 | **/ios/Flutter/flutter_assets/
87 | **/ios/Flutter/flutter_export_environment.sh
88 | **/ios/ServiceDefinitions.json
89 | **/ios/Runner/GeneratedPluginRegistrant.*
90 |
91 | # Coverage
92 | coverage/
93 |
94 | # Exceptions to above rules.
95 | !**/ios/**/default.mode1v3
96 | !**/ios/**/default.mode2v3
97 | !**/ios/**/default.pbxuser
98 | !**/ios/**/default.perspectivev3
99 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
100 |
101 | .flutter-plugins
102 | .flutter-plugins-dependencies
--------------------------------------------------------------------------------
/.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: 6a3ff018b199a7febbe2b5adbb564081d8f49e2f
8 | channel: dev
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Flutter4GitlabDev",
9 | "type": "dart",
10 | "request": "launch",
11 | "program": "lib/main_dev.dart"
12 | },
13 | {
14 | "name": "Flutter4Gitlab",
15 | "request": "launch",
16 | "type": "dart"
17 | }
18 | ]
19 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 StefanJi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # F4Lab
4 |
5 | *A glitlab client made by flutter. Support Android & IOS.*
6 |
7 |
8 | [](https://github.com/stefanJi/Flutter4GitLab/actions)
9 |
10 |
11 |
12 | |home|config|nav|project|merge requests| merge request|commit|diff|
13 | |:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
14 | || |  |  |  |  |  |  |
15 |
16 | ## Usage
17 |
18 | ### Running in Android device
19 |
20 | 1. [Download release apk](https://github.com/stefanJi/Flutter4GitLab/releases)
21 | 2. Install apk and then run
22 |
23 | ### Running in IOS device
24 |
25 | 1. Follow [Dev](#Dev) Section
26 |
27 | ## Dev
28 |
29 | > First, you shuold setup your Flutter development env. [Set up an editor](https://flutter.io/docs/get-started/editor).
30 |
31 | ### Run Project
32 |
33 | 1. `fork` or `clone` this project
34 | 2. In project root dir, run:
35 | - `flutter packages pub get`
36 | - `flutter run`
37 |
38 | ---
39 |
40 |
41 | Features: welcome to contribute for the following features
42 |
43 | - **App**
44 | - [x] Login by Personal Access Token
45 | - [x] Projects
46 | - [x] Themes mode
47 | - [ ] Markdown and code highlighting support
48 | - [ ] Search Users/Orgs, Repos, Issues/MRs & Code.
49 | - **Repositories**
50 | - [ ] Search Repos
51 | - [ ] Browse and search Repos
52 | - [x] See your public, private and forked Repos
53 | - [ ] Filter Branches and Commits
54 | - **Issues and Merge Requests**
55 | - [x] Commit code diff
56 | - [x] Run pipeline jobs
57 | - [x] Rebase when merge request
58 | - [x] Merge MRs
59 | - [x] MRs statuses
60 | - [x] Approve or UnApprove MR
61 | - [x] CI Status
62 | - [x] Play|Cancel|Retry CI Job
63 | - [x] Filter Merge Requests State. (opened, closed, locked, merged)
64 | - [x] Filter Merge Requests Assign. (all, assigned_to_me)
65 | - [x] Discussion of merge request
66 | - **Organisations**
67 | - [x] Feeds
68 | - [x] Repos
69 | - **PipeLines**
70 | - [x] List project's pipepine
71 | - [x] Play, Retry, Cancel Pipeline Job
72 |
73 |
74 |
75 |
76 | Api: GitLab Server || Local Server
77 |
78 | - [**GitLab Api Doc**](https://gitlab.com/help/api/README.md)
79 | - Or Your personal GitLab.(Eg: https://gitlab.exsample.com/help/api/README.md)
80 |
81 |
82 |
83 |
84 | Dependencies: Lib && Plugin
85 |
86 | - Android Minimum **SDK 16**, IOS Minimun **9.0**
87 | - [**Flutter**](https://github.com/flutter/flutter)
88 | - [**shared_preferences**](https://pub.dartlang.org/packages/shared_preferences)
89 | - [**pull_to_refresh**](https://pub.dartlang.org/packages/pull_to_refresh)
90 | - [**xml**](https://pub.dartlang.org/packages/xml)
91 | - [**url_launcher**](https://pub.dartlang.org/packages/url_launcher)
92 | - [**sentry**](https://pub.dartlang.org/packages/sentry)
93 | - [**flutter_stetho**](https://pub.dartlang.org/packages/flutter_stetho)
94 | - [**Dio**](https://github.com/flutterchina/dio)
95 | - [**Provider**](https://github.com/rrousselGit/provider)
96 |
97 |
98 |
99 | ## Contribution
100 |
101 | Please **contribute** to the project either by **_creating a PR_** or **_submitting an issue_** on GitHub.
102 |
103 | ## License
104 |
105 | > Copyright (C) 2018 StefanJi.
106 | > (See the [LICENSE](./LICENSE) file for the whole license text.)
107 |
--------------------------------------------------------------------------------
/android/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | android
4 | Project android created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/android/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | connection.project.dir=
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/android/app/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/android/app/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | app
4 | Project app created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.buildship.core.gradleprojectbuilder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.buildship.core.gradleprojectnature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/android/app/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | connection.project.dir=..
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/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 | def versionSplit = flutterVersionName.split("\\.")
25 | flutterVersionCode = 0
26 | versionSplit.each({ s ->
27 | flutterVersionCode += Integer.valueOf(s)
28 | })
29 | println "version name: $flutterVersionName"
30 | println "base version code: $flutterVersionCode"
31 | println "armeabi-v7a version code: ${1000 + flutterVersionCode}"
32 | println "arm64_v8a version code: ${2000 + flutterVersionCode}"
33 | println "x86_64 version code: ${4000 + flutterVersionCode}"
34 |
35 |
36 | apply plugin: 'com.android.application'
37 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
38 |
39 | def keystorePropertiesFile = rootProject.file("key.properties")
40 | def keystoreProperties = new Properties()
41 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
42 |
43 | android {
44 | compileSdkVersion 29
45 | buildToolsVersion "28.0.3"
46 |
47 | lintOptions {
48 | disable 'InvalidPackage'
49 | }
50 |
51 | defaultConfig {
52 | applicationId "io.github.stefanji.fluttergitlab"
53 | minSdkVersion 16
54 | targetSdkVersion 29
55 | versionCode flutterVersionCode.toInteger()
56 | versionName flutterVersionName
57 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
58 | }
59 |
60 | signingConfigs {
61 | release {
62 | keyAlias keystoreProperties['keyAlias']
63 | keyPassword keystoreProperties['keyPassword']
64 | storeFile file(keystoreProperties['storeFile'])
65 | storePassword keystoreProperties['storePassword']
66 | }
67 | }
68 | buildTypes {
69 | release {
70 | shrinkResources true
71 | signingConfig signingConfigs.release
72 | minifyEnabled true
73 |
74 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
75 | }
76 | }
77 | }
78 |
79 | flutter {
80 | source '../..'
81 | }
82 |
83 | dependencies {
84 | testImplementation 'junit:junit:4.12'
85 | androidTestImplementation 'androidx.test:runner:1.2.0'
86 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
87 | }
88 |
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | #Flutter Wrapper
2 | -keep class io.flutter.app.** { *; }
3 | -keep class io.flutter.plugin.** { *; }
4 | -keep class io.flutter.util.** { *; }
5 | -keep class io.flutter.view.** { *; }
6 | -keep class io.flutter.** { *; }
7 | -keep class io.flutter.plugins.** { *; }
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
15 |
19 |
26 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/android/app/src/main/java/io/github/stefanji/fluttergitlab/MainActivity.java:
--------------------------------------------------------------------------------
1 | package io.github.stefanji.fluttergitlab;
2 |
3 | import android.os.Bundle;
4 | import io.flutter.app.FlutterActivity;
5 | import io.flutter.plugins.GeneratedPluginRegistrant;
6 |
7 | public class MainActivity extends FlutterActivity {
8 | @Override
9 | protected void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 | GeneratedPluginRegistrant.registerWith(this);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | -
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/android/app/src/main/res/drawable/screen.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/string.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | F4Lab
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.6.2'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | google()
15 | jcenter()
16 | }
17 | }
18 |
19 | rootProject.buildDir = '../build'
20 | subprojects {
21 | project.buildDir = "${rootProject.buildDir}/${project.name}"
22 | }
23 | subprojects {
24 | project.evaluationDependsOn(':app')
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | android.enableJetifier=true
2 | android.useAndroidX=true
3 | org.gradle.jvmargs=-Xmx1536M
4 | android.enableR8=true
5 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Apr 18 18:24:56 CST 2020
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.4-all.zip
7 |
--------------------------------------------------------------------------------
/android/key.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/android/key.jks
--------------------------------------------------------------------------------
/android/key.properties:
--------------------------------------------------------------------------------
1 | storePassword=123456
2 | keyPassword=123456
3 | keyAlias=key
4 | storeFile=../key.jks
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/art/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/art/1.png
--------------------------------------------------------------------------------
/art/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/art/2.png
--------------------------------------------------------------------------------
/art/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/art/3.png
--------------------------------------------------------------------------------
/art/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/art/4.png
--------------------------------------------------------------------------------
/art/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/art/5.png
--------------------------------------------------------------------------------
/art/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/art/6.png
--------------------------------------------------------------------------------
/art/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/art/7.png
--------------------------------------------------------------------------------
/art/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/art/8.png
--------------------------------------------------------------------------------
/art/F4Lab_arch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/art/F4Lab_arch.png
--------------------------------------------------------------------------------
/art/f4lab_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/art/f4lab_home.png
--------------------------------------------------------------------------------
/art/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/art/logo.png
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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/Flutter.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # NOTE: This podspec is NOT to be published. It is only used as a local source!
3 | # This is a generated file; do not edit or check into version control.
4 | #
5 |
6 | Pod::Spec.new do |s|
7 | s.name = 'Flutter'
8 | s.version = '1.0.0'
9 | s.summary = 'High-performance, high-fidelity mobile apps.'
10 | s.homepage = 'https://flutter.io'
11 | s.license = { :type => 'MIT' }
12 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
13 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
14 | s.ios.deployment_target = '8.0'
15 | # Framework linking is handled by Flutter tooling, not CocoaPods.
16 | # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs.
17 | s.vendored_frameworks = 'path/to/nothing'
18 | end
19 |
--------------------------------------------------------------------------------
/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 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
32 | end
33 |
34 | post_install do |installer|
35 | installer.pods_project.targets.each do |target|
36 | flutter_additional_ios_build_settings(target)
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 2E41D7365257AE2408546E22 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 01511DF71B2CE60B6E96581F /* libPods-Runner.a */; };
12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
13 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
14 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
15 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
16 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
17 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
18 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
19 | /* End PBXBuildFile section */
20 |
21 | /* Begin PBXCopyFilesBuildPhase section */
22 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
23 | isa = PBXCopyFilesBuildPhase;
24 | buildActionMask = 2147483647;
25 | dstPath = "";
26 | dstSubfolderSpec = 10;
27 | files = (
28 | );
29 | name = "Embed Frameworks";
30 | runOnlyForDeploymentPostprocessing = 0;
31 | };
32 | /* End PBXCopyFilesBuildPhase section */
33 |
34 | /* Begin PBXFileReference section */
35 | 01511DF71B2CE60B6E96581F /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
36 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
37 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
38 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
39 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
40 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
41 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
42 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
43 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
44 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
45 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
46 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
47 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
48 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
49 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
50 | 9FE6D3E0C5CC1B70AD424743 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
51 | D80B2CEF7C78B73B94072AC5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
52 | /* End PBXFileReference section */
53 |
54 | /* Begin PBXFrameworksBuildPhase section */
55 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
56 | isa = PBXFrameworksBuildPhase;
57 | buildActionMask = 2147483647;
58 | files = (
59 | 2E41D7365257AE2408546E22 /* libPods-Runner.a in Frameworks */,
60 | );
61 | runOnlyForDeploymentPostprocessing = 0;
62 | };
63 | /* End PBXFrameworksBuildPhase section */
64 |
65 | /* Begin PBXGroup section */
66 | 2FFC179183DE97F5770EDA82 /* Pods */ = {
67 | isa = PBXGroup;
68 | children = (
69 | 9FE6D3E0C5CC1B70AD424743 /* Pods-Runner.debug.xcconfig */,
70 | D80B2CEF7C78B73B94072AC5 /* Pods-Runner.release.xcconfig */,
71 | );
72 | name = Pods;
73 | sourceTree = "";
74 | };
75 | 9740EEB11CF90186004384FC /* Flutter */ = {
76 | isa = PBXGroup;
77 | children = (
78 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
79 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
80 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
81 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
82 | );
83 | name = Flutter;
84 | sourceTree = "";
85 | };
86 | 97C146E51CF9000F007C117D = {
87 | isa = PBXGroup;
88 | children = (
89 | 9740EEB11CF90186004384FC /* Flutter */,
90 | 97C146F01CF9000F007C117D /* Runner */,
91 | 97C146EF1CF9000F007C117D /* Products */,
92 | 2FFC179183DE97F5770EDA82 /* Pods */,
93 | ECD79A50260CE789C9BAC617 /* Frameworks */,
94 | );
95 | sourceTree = "";
96 | };
97 | 97C146EF1CF9000F007C117D /* Products */ = {
98 | isa = PBXGroup;
99 | children = (
100 | 97C146EE1CF9000F007C117D /* Runner.app */,
101 | );
102 | name = Products;
103 | sourceTree = "";
104 | };
105 | 97C146F01CF9000F007C117D /* Runner */ = {
106 | isa = PBXGroup;
107 | children = (
108 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
109 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
110 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
111 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
112 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
113 | 97C147021CF9000F007C117D /* Info.plist */,
114 | 97C146F11CF9000F007C117D /* Supporting Files */,
115 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
116 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
117 | );
118 | path = Runner;
119 | sourceTree = "";
120 | };
121 | 97C146F11CF9000F007C117D /* Supporting Files */ = {
122 | isa = PBXGroup;
123 | children = (
124 | 97C146F21CF9000F007C117D /* main.m */,
125 | );
126 | name = "Supporting Files";
127 | sourceTree = "";
128 | };
129 | ECD79A50260CE789C9BAC617 /* Frameworks */ = {
130 | isa = PBXGroup;
131 | children = (
132 | 01511DF71B2CE60B6E96581F /* libPods-Runner.a */,
133 | );
134 | name = Frameworks;
135 | sourceTree = "";
136 | };
137 | /* End PBXGroup section */
138 |
139 | /* Begin PBXNativeTarget section */
140 | 97C146ED1CF9000F007C117D /* Runner */ = {
141 | isa = PBXNativeTarget;
142 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
143 | buildPhases = (
144 | 646E3F5D8122DEB262BD8D57 /* [CP] Check Pods Manifest.lock */,
145 | 9740EEB61CF901F6004384FC /* Run Script */,
146 | 97C146EA1CF9000F007C117D /* Sources */,
147 | 97C146EB1CF9000F007C117D /* Frameworks */,
148 | 97C146EC1CF9000F007C117D /* Resources */,
149 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
150 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
151 | );
152 | buildRules = (
153 | );
154 | dependencies = (
155 | );
156 | name = Runner;
157 | productName = Runner;
158 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
159 | productType = "com.apple.product-type.application";
160 | };
161 | /* End PBXNativeTarget section */
162 |
163 | /* Begin PBXProject section */
164 | 97C146E61CF9000F007C117D /* Project object */ = {
165 | isa = PBXProject;
166 | attributes = {
167 | LastUpgradeCheck = 0910;
168 | ORGANIZATIONNAME = "The Chromium Authors";
169 | TargetAttributes = {
170 | 97C146ED1CF9000F007C117D = {
171 | CreatedOnToolsVersion = 7.3.1;
172 | };
173 | };
174 | };
175 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
176 | compatibilityVersion = "Xcode 3.2";
177 | developmentRegion = English;
178 | hasScannedForEncodings = 0;
179 | knownRegions = (
180 | en,
181 | Base,
182 | );
183 | mainGroup = 97C146E51CF9000F007C117D;
184 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
185 | projectDirPath = "";
186 | projectRoot = "";
187 | targets = (
188 | 97C146ED1CF9000F007C117D /* Runner */,
189 | );
190 | };
191 | /* End PBXProject section */
192 |
193 | /* Begin PBXResourcesBuildPhase section */
194 | 97C146EC1CF9000F007C117D /* Resources */ = {
195 | isa = PBXResourcesBuildPhase;
196 | buildActionMask = 2147483647;
197 | files = (
198 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
199 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
200 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
201 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
202 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
203 | );
204 | runOnlyForDeploymentPostprocessing = 0;
205 | };
206 | /* End PBXResourcesBuildPhase section */
207 |
208 | /* Begin PBXShellScriptBuildPhase section */
209 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
210 | isa = PBXShellScriptBuildPhase;
211 | buildActionMask = 2147483647;
212 | files = (
213 | );
214 | inputPaths = (
215 | );
216 | name = "Thin Binary";
217 | outputPaths = (
218 | );
219 | runOnlyForDeploymentPostprocessing = 0;
220 | shellPath = /bin/sh;
221 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
222 | };
223 | 646E3F5D8122DEB262BD8D57 /* [CP] Check Pods Manifest.lock */ = {
224 | isa = PBXShellScriptBuildPhase;
225 | buildActionMask = 2147483647;
226 | files = (
227 | );
228 | inputPaths = (
229 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
230 | "${PODS_ROOT}/Manifest.lock",
231 | );
232 | name = "[CP] Check Pods Manifest.lock";
233 | outputPaths = (
234 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
235 | );
236 | runOnlyForDeploymentPostprocessing = 0;
237 | shellPath = /bin/sh;
238 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
239 | showEnvVarsInLog = 0;
240 | };
241 | 9740EEB61CF901F6004384FC /* Run Script */ = {
242 | isa = PBXShellScriptBuildPhase;
243 | buildActionMask = 2147483647;
244 | files = (
245 | );
246 | inputPaths = (
247 | );
248 | name = "Run Script";
249 | outputPaths = (
250 | );
251 | runOnlyForDeploymentPostprocessing = 0;
252 | shellPath = /bin/sh;
253 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
254 | };
255 | /* End PBXShellScriptBuildPhase section */
256 |
257 | /* Begin PBXSourcesBuildPhase section */
258 | 97C146EA1CF9000F007C117D /* Sources */ = {
259 | isa = PBXSourcesBuildPhase;
260 | buildActionMask = 2147483647;
261 | files = (
262 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
263 | 97C146F31CF9000F007C117D /* main.m in Sources */,
264 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
265 | );
266 | runOnlyForDeploymentPostprocessing = 0;
267 | };
268 | /* End PBXSourcesBuildPhase section */
269 |
270 | /* Begin PBXVariantGroup section */
271 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
272 | isa = PBXVariantGroup;
273 | children = (
274 | 97C146FB1CF9000F007C117D /* Base */,
275 | );
276 | name = Main.storyboard;
277 | sourceTree = "";
278 | };
279 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
280 | isa = PBXVariantGroup;
281 | children = (
282 | 97C147001CF9000F007C117D /* Base */,
283 | );
284 | name = LaunchScreen.storyboard;
285 | sourceTree = "";
286 | };
287 | /* End PBXVariantGroup section */
288 |
289 | /* Begin XCBuildConfiguration section */
290 | 97C147031CF9000F007C117D /* Debug */ = {
291 | isa = XCBuildConfiguration;
292 | buildSettings = {
293 | ALWAYS_SEARCH_USER_PATHS = NO;
294 | CLANG_ANALYZER_NONNULL = YES;
295 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
296 | CLANG_CXX_LIBRARY = "libc++";
297 | CLANG_ENABLE_MODULES = YES;
298 | CLANG_ENABLE_OBJC_ARC = YES;
299 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
300 | CLANG_WARN_BOOL_CONVERSION = YES;
301 | CLANG_WARN_COMMA = YES;
302 | CLANG_WARN_CONSTANT_CONVERSION = YES;
303 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
304 | CLANG_WARN_EMPTY_BODY = YES;
305 | CLANG_WARN_ENUM_CONVERSION = YES;
306 | CLANG_WARN_INFINITE_RECURSION = YES;
307 | CLANG_WARN_INT_CONVERSION = YES;
308 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
309 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
310 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
311 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
312 | CLANG_WARN_STRICT_PROTOTYPES = YES;
313 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
314 | CLANG_WARN_UNREACHABLE_CODE = YES;
315 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
316 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
317 | COPY_PHASE_STRIP = NO;
318 | DEBUG_INFORMATION_FORMAT = dwarf;
319 | ENABLE_STRICT_OBJC_MSGSEND = YES;
320 | ENABLE_TESTABILITY = YES;
321 | GCC_C_LANGUAGE_STANDARD = gnu99;
322 | GCC_DYNAMIC_NO_PIC = NO;
323 | GCC_NO_COMMON_BLOCKS = YES;
324 | GCC_OPTIMIZATION_LEVEL = 0;
325 | GCC_PREPROCESSOR_DEFINITIONS = (
326 | "DEBUG=1",
327 | "$(inherited)",
328 | );
329 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
330 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
331 | GCC_WARN_UNDECLARED_SELECTOR = YES;
332 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
333 | GCC_WARN_UNUSED_FUNCTION = YES;
334 | GCC_WARN_UNUSED_VARIABLE = YES;
335 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
336 | MTL_ENABLE_DEBUG_INFO = YES;
337 | ONLY_ACTIVE_ARCH = YES;
338 | SDKROOT = iphoneos;
339 | TARGETED_DEVICE_FAMILY = "1,2";
340 | };
341 | name = Debug;
342 | };
343 | 97C147041CF9000F007C117D /* Release */ = {
344 | isa = XCBuildConfiguration;
345 | buildSettings = {
346 | ALWAYS_SEARCH_USER_PATHS = NO;
347 | CLANG_ANALYZER_NONNULL = YES;
348 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
349 | CLANG_CXX_LIBRARY = "libc++";
350 | CLANG_ENABLE_MODULES = YES;
351 | CLANG_ENABLE_OBJC_ARC = YES;
352 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
353 | CLANG_WARN_BOOL_CONVERSION = YES;
354 | CLANG_WARN_COMMA = YES;
355 | CLANG_WARN_CONSTANT_CONVERSION = YES;
356 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
357 | CLANG_WARN_EMPTY_BODY = YES;
358 | CLANG_WARN_ENUM_CONVERSION = YES;
359 | CLANG_WARN_INFINITE_RECURSION = YES;
360 | CLANG_WARN_INT_CONVERSION = YES;
361 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
362 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
363 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
364 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
365 | CLANG_WARN_STRICT_PROTOTYPES = YES;
366 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
367 | CLANG_WARN_UNREACHABLE_CODE = YES;
368 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
369 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
370 | COPY_PHASE_STRIP = NO;
371 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
372 | ENABLE_NS_ASSERTIONS = NO;
373 | ENABLE_STRICT_OBJC_MSGSEND = YES;
374 | GCC_C_LANGUAGE_STANDARD = gnu99;
375 | GCC_NO_COMMON_BLOCKS = YES;
376 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
377 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
378 | GCC_WARN_UNDECLARED_SELECTOR = YES;
379 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
380 | GCC_WARN_UNUSED_FUNCTION = YES;
381 | GCC_WARN_UNUSED_VARIABLE = YES;
382 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
383 | MTL_ENABLE_DEBUG_INFO = NO;
384 | SDKROOT = iphoneos;
385 | TARGETED_DEVICE_FAMILY = "1,2";
386 | VALIDATE_PRODUCT = YES;
387 | };
388 | name = Release;
389 | };
390 | 97C147061CF9000F007C117D /* Debug */ = {
391 | isa = XCBuildConfiguration;
392 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
393 | buildSettings = {
394 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
395 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
396 | ENABLE_BITCODE = NO;
397 | FRAMEWORK_SEARCH_PATHS = (
398 | "$(inherited)",
399 | "$(PROJECT_DIR)/Flutter",
400 | );
401 | INFOPLIST_FILE = Runner/Info.plist;
402 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
403 | LIBRARY_SEARCH_PATHS = (
404 | "$(inherited)",
405 | "$(PROJECT_DIR)/Flutter",
406 | );
407 | PRODUCT_BUNDLE_IDENTIFIER = io.github.stefanji.flutterGitlab;
408 | PRODUCT_NAME = "$(TARGET_NAME)";
409 | VERSIONING_SYSTEM = "apple-generic";
410 | };
411 | name = Debug;
412 | };
413 | 97C147071CF9000F007C117D /* Release */ = {
414 | isa = XCBuildConfiguration;
415 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
416 | buildSettings = {
417 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
418 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
419 | ENABLE_BITCODE = NO;
420 | FRAMEWORK_SEARCH_PATHS = (
421 | "$(inherited)",
422 | "$(PROJECT_DIR)/Flutter",
423 | );
424 | INFOPLIST_FILE = Runner/Info.plist;
425 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
426 | LIBRARY_SEARCH_PATHS = (
427 | "$(inherited)",
428 | "$(PROJECT_DIR)/Flutter",
429 | );
430 | PRODUCT_BUNDLE_IDENTIFIER = io.github.stefanji.flutterGitlab;
431 | PRODUCT_NAME = "$(TARGET_NAME)";
432 | VERSIONING_SYSTEM = "apple-generic";
433 | };
434 | name = Release;
435 | };
436 | /* End XCBuildConfiguration section */
437 |
438 | /* Begin XCConfigurationList section */
439 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
440 | isa = XCConfigurationList;
441 | buildConfigurations = (
442 | 97C147031CF9000F007C117D /* Debug */,
443 | 97C147041CF9000F007C117D /* Release */,
444 | );
445 | defaultConfigurationIsVisible = 0;
446 | defaultConfigurationName = Release;
447 | };
448 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
449 | isa = XCConfigurationList;
450 | buildConfigurations = (
451 | 97C147061CF9000F007C117D /* Debug */,
452 | 97C147071CF9000F007C117D /* Release */,
453 | );
454 | defaultConfigurationIsVisible = 0;
455 | defaultConfigurationName = Release;
456 | };
457 | /* End XCConfigurationList section */
458 | };
459 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
460 | }
461 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
77 |
83 |
84 |
85 |
86 |
88 |
89 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/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/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : FlutterAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #include "AppDelegate.h"
2 | #include "GeneratedPluginRegistrant.h"
3 |
4 | @implementation AppDelegate
5 |
6 | - (BOOL)application:(UIApplication *)application
7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
8 | [GeneratedPluginRegistrant registerWithRegistry:self];
9 | // Override point for customization after application launch.
10 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
11 | }
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "idiom": "ipad",
5 | "size": "20x20",
6 | "scale": "1x",
7 | "filename": "Icon-20x20@1x.png"
8 | },
9 | {
10 | "idiom": "iphone",
11 | "size": "20x20",
12 | "scale": "2x",
13 | "filename": "Icon-20x20@2x.png"
14 | },
15 | {
16 | "idiom": "iphone",
17 | "size": "20x20",
18 | "scale": "3x",
19 | "filename": "Icon-20x20@3x.png"
20 | },
21 | {
22 | "idiom": "iphone",
23 | "size": "29x29",
24 | "scale": "1x",
25 | "filename": "Icon-29x29@1x.png"
26 | },
27 | {
28 | "idiom": "iphone",
29 | "size": "29x29",
30 | "scale": "2x",
31 | "filename": "Icon-29x29@2x.png"
32 | },
33 | {
34 | "idiom": "iphone",
35 | "size": "29x29",
36 | "scale": "3x",
37 | "filename": "Icon-29x29@3x.png"
38 | },
39 | {
40 | "idiom": "iphone",
41 | "size": "40x40",
42 | "scale": "2x",
43 | "filename": "Icon-40x40@2x.png"
44 | },
45 | {
46 | "idiom": "iphone",
47 | "size": "40x40",
48 | "scale": "3x",
49 | "filename": "Icon-40x40@3x.png"
50 | },
51 | {
52 | "idiom": "iphone",
53 | "size": "60x60",
54 | "scale": "2x",
55 | "filename": "Icon-60x60@2x.png"
56 | },
57 | {
58 | "idiom": "iphone",
59 | "size": "60x60",
60 | "scale": "3x",
61 | "filename": "Icon-60x60@3x.png"
62 | },
63 | {
64 | "idiom": "ipad",
65 | "size": "76x76",
66 | "scale": "1x",
67 | "filename": "Icon-76x76@1x.png"
68 | },
69 | {
70 | "idiom": "ipad",
71 | "size": "76x76",
72 | "scale": "2x",
73 | "filename": "Icon-76x76@2x.png"
74 | },
75 | {
76 | "idiom": "ipad",
77 | "size": "167x167",
78 | "scale": "2x",
79 | "filename": "Icon-83.5@2x.png"
80 | },
81 | {
82 | "idiom": "ios-marketing",
83 | "size": "1024x1024",
84 | "scale": "1x",
85 | "filename": "Icon-marketing-1024x1024.png"
86 | }
87 | ],
88 | "info": {
89 | "version": 1,
90 | "author": "apetools.webprofusion.com"
91 | }
92 | }
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-marketing-1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-marketing-1024x1024.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/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanJi/Flutter4GitLab/d0a245c933742b1d4ce413d46a69e44685027911/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 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | F4Lab
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/main.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char* argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/lib/api.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 |
4 | import 'package:F4Lab/gitlab_client.dart';
5 | import 'package:F4Lab/model/approvals.dart' hide User;
6 | import 'package:F4Lab/model/diff.dart';
7 | import 'package:F4Lab/model/jobs.dart';
8 | import 'package:F4Lab/model/merge_request.dart';
9 | import 'package:F4Lab/model/user.dart';
10 | import 'package:flutter/foundation.dart';
11 | import 'package:http/http.dart';
12 |
13 | class ApiResp {
14 | bool success;
15 | T? data;
16 | String? err;
17 |
18 | ApiResp(this.success, [this.data, this.err]);
19 | }
20 |
21 | class ApiEndPoint {
22 | static const merge_request_states = ["opened", "closed", "locked", "merged"];
23 | static const merge_request_scopes = ["all", "assigned_to_me"];
24 |
25 | /// Get a project's all merge requests with state and scope filter
26 | /// [state] one of [merge_request_states]
27 | /// [scope] one of [merge_request_scopes]
28 | ///
29 | static String mergeRequests(int projectId,
30 | {required String state, required String scope}) =>
31 | "projects/$projectId/merge_requests?state=${state ?? "opened"}&scope=${scope ?? "all"}";
32 |
33 | static String singleMergeRequest(int projectId, int mrIId) =>
34 | "projects/$projectId/merge_requests/$mrIId?include_rebase_in_progress=true&include_diverged_commits_count=true";
35 |
36 | static String mergeRequestCommit(int projectId, int mrIID) =>
37 | "projects/$projectId/merge_requests/$mrIID/commits";
38 |
39 | static String mrApproveData(int projectId, int mrIId) =>
40 | 'projects/$projectId/merge_requests/$mrIId/approvals';
41 |
42 | static String approveMergeRequest(int projectId, int mrIId, bool approve) =>
43 | "projects/$projectId/merge_requests/$mrIId/${approve ? "approve" : "unapprove"}";
44 |
45 | static String rebaseMR(int projectId, int mrIId) =>
46 | "projects/$projectId/merge_requests/$mrIId/rebase";
47 |
48 | static String mergeRequestPipelines(int projectId, int mrIId) =>
49 | "projects/$projectId/merge_requests/$mrIId/pipelines";
50 |
51 | static String pipelineJobs(int projectId, int pipelineId) =>
52 | "projects/$projectId/pipelines/$pipelineId/jobs";
53 |
54 | static String projectJobs(int projectId) => "projects/$projectId/jobs";
55 |
56 | static String mergeMR(
57 | int projectId,
58 | int mrIId, {
59 | bool shouldRemoveSourceBranch = false,
60 | bool mergeMrWhenPipelineSuccess = false,
61 | bool squash = false,
62 | String mergeCommitMessage = "",
63 | }) =>
64 | 'projects/$projectId/merge_requests/$mrIId/merge?' +
65 | 'should_remove_source_branch=$shouldRemoveSourceBranch' +
66 | '&merge_when_pipeline_succeeds=$mergeMrWhenPipelineSuccess' +
67 | '&squash=$squash' +
68 | '$mergeCommitMessage';
69 |
70 | static String cancelMergeMrWhenPipelineSuccess(int projectId, int mrIId) =>
71 | "projects/$projectId/merge_requests/$mrIId/cancel_merge_when_pipeline_succeeds";
72 |
73 | static String triggerPipelineJob(int projectId, int jobId, String action) =>
74 | "projects/$projectId/jobs/$jobId/$action";
75 |
76 | static String commitDiff(int projectId, String sha) =>
77 | "projects/$projectId/repository/commits/$sha/diff";
78 |
79 | static String mergeRequestDiscussion(int projectId, int mrIId) =>
80 | "projects/$projectId/merge_requests/$mrIId/discussions";
81 | }
82 |
83 | class ApiService {
84 | static bool respStatusIsOk(int statusCode) => (statusCode ~/ 100) == 2;
85 |
86 | static dynamic respConvertToUtf8(Response resp) =>
87 | utf8.decode(resp.bodyBytes);
88 |
89 | static Map respConvertToMap(Response resp) {
90 | if (!respStatusIsOk(resp.statusCode)) {
91 | throw Exception("Response error: ${resp.statusCode} ${resp.body}");
92 | }
93 | return jsonDecode(respConvertToUtf8(resp));
94 | }
95 |
96 | static List respConvertToList(Response resp) {
97 | if (!respStatusIsOk(resp.statusCode)) {
98 | throw Exception("Response error: ${resp.statusCode} ${resp.body}");
99 | }
100 | return jsonDecode(respConvertToUtf8(resp));
101 | }
102 |
103 | static Future> getAuthUser() async {
104 | return GitlabClient.buildDio().get('user').then((resp) {
105 | debugPrint(resp.toString());
106 | final ok = respStatusIsOk(resp.statusCode!);
107 | return ApiResp(ok, User.fromJson(resp.data));
108 | }).catchError((err) => ApiResp(false, null, err.toString()));
109 | }
110 |
111 | static Future> getSingleMR(int projectId, int mrIId) {
112 | final endPoint = ApiEndPoint.singleMergeRequest(projectId, mrIId);
113 | final client = GitlabClient.newInstance();
114 | return client.get(Uri.parse(endPoint)).then((resp) {
115 | return ApiResp(respStatusIsOk(resp.statusCode),
116 | MergeRequest.fromJson(respConvertToMap(resp)));
117 | }).catchError((err) {
118 | return ApiResp(false, null, err?.toString());
119 | }).whenComplete(client.close);
120 | }
121 |
122 | static Future approve(
123 | int projectId, int mrIId, bool isApprove) async {
124 | final endPoint =
125 | ApiEndPoint.approveMergeRequest(projectId, mrIId, isApprove);
126 | final client = GitlabClient.newInstance();
127 | return client.post(Uri.parse(endPoint)).then((resp) {
128 | return ApiResp(respStatusIsOk(resp.statusCode), resp.body,
129 | respStatusIsOk(resp.statusCode) ? "" : resp.body);
130 | }).catchError((err) {
131 | return ApiResp(false, null, err?.toString());
132 | }).whenComplete(client.close);
133 | }
134 |
135 | static Future> rebaseMr(int projectId, int mrIId) {
136 | final endPoint = ApiEndPoint.rebaseMR(projectId, mrIId);
137 | final client = GitlabClient.newInstance();
138 | return client
139 | .put(Uri.parse(endPoint))
140 | .then((resp) => ApiResp(respStatusIsOk(resp.statusCode)))
141 | .whenComplete(client.close);
142 | }
143 |
144 | static Future> mrApproveData(int projectId, int mrIId) {
145 | final endPoint = ApiEndPoint.mrApproveData(projectId, mrIId);
146 | final client = GitlabClient.newInstance();
147 | return client
148 | .get(Uri.parse(endPoint))
149 | .then((resp) {
150 | return ApiResp(respStatusIsOk(resp.statusCode),
151 | Approvals.fromJson(respConvertToMap(resp)));
152 | })
153 | .catchError((err) => ApiResp(false, null, err?.toString()))
154 | .whenComplete(client.close);
155 | }
156 |
157 | static Future acceptMR(int projectId, int mrIId,
158 | {bool shouldRemoveSourceBranch = false,
159 | bool mergeMrWhenPipelineSuccess = false,
160 | bool squash = false,
161 | String? mergeCommitMessage}) {
162 | final endPoint = ApiEndPoint.mergeMR(projectId, mrIId,
163 | shouldRemoveSourceBranch: shouldRemoveSourceBranch,
164 | mergeMrWhenPipelineSuccess: mergeMrWhenPipelineSuccess,
165 | mergeCommitMessage: mergeCommitMessage ?? "",
166 | squash: squash);
167 | final client = GitlabClient.newInstance();
168 | return client.put(Uri.parse(endPoint)).then((resp) {
169 | return ApiResp(respStatusIsOk(resp.statusCode));
170 | }).catchError((err) {
171 | return ApiResp(false, null, err?.toString());
172 | }).whenComplete(client.close);
173 | }
174 |
175 | static Future>> pipelineJobs(
176 | int projectId, int pipelineId) {
177 | final endPoint = ApiEndPoint.pipelineJobs(projectId, pipelineId);
178 | return GitlabClient.buildDio()
179 | .get(endPoint)
180 | .then((resp) =>
181 | ApiResp(true, resp.data!.map((it) => Jobs.fromJson(it)).toList()))
182 | .catchError((err) => ApiResp>(false, null, err));
183 | }
184 |
185 | static Future triggerPipelineJob(
186 | int projectId, int jobId, String action) {
187 | final endPoint = ApiEndPoint.triggerPipelineJob(projectId, jobId, action);
188 | final client = GitlabClient.newInstance();
189 | return client
190 | .post(Uri.parse(endPoint))
191 | .then((resp) => ApiResp(respStatusIsOk(resp.statusCode)))
192 | .catchError((err) => ApiResp(false, null, err?.toString()))
193 | .whenComplete(client.close);
194 | }
195 |
196 | static Future>> commitDiff(int projectId, String sha) {
197 | final endPoint = ApiEndPoint.commitDiff(projectId, sha);
198 | return GitlabClient.buildDio()
199 | .get(endPoint)
200 | .then((resp) => ApiResp(
201 | true, resp.data!.map((item) => Diff.fromJson(item)).toList()))
202 | .catchError((err) => ApiResp>(false, null, err));
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/lib/const.dart:
--------------------------------------------------------------------------------
1 | const APP_LEGEND = 'A GitLab Client by Flutter.';
2 | const KEY_ACCESS_TOKEN = 'key.access.token';
3 | const KEY_HOST = 'key.host';
4 | const KEY_API_VERSION = 'key.api.version';
5 | const KEY_TAB_INDEX = 'key.tab.index';
6 | const APP_ICON_URL = "http://image.youcute.cn/18-11-2/26473177.jpg";
7 | const APP_REPO_URL = "https://github.com/stefanJi/Flutter4GitLab";
8 | const APP_FEED_BACK_URL = "https://github.com/stefanJi/Flutter4GitLab/issues";
9 | const KEY_THEME_IS_DARK = "is_dark";
10 |
--------------------------------------------------------------------------------
/lib/gitlab_client.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:dio/dio.dart';
4 | import 'package:http/http.dart' as http;
5 |
6 | const DEFAULT_API_VERSION = 'v4';
7 | const USER_AGENT =
8 | "F4Lab Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36";
9 | const KEY_TOKEN = "private-token";
10 | const KEY_USER_AGENT = "user-agent";
11 |
12 | class GitlabClient extends http.BaseClient {
13 | static String globalHOST = "";
14 | static String globalTOKEN = "";
15 | static String apiVersion = "";
16 |
17 | final http.Client _inner = http.Client();
18 |
19 | static GitlabClient newInstance() => GitlabClient();
20 |
21 | static setUpTokenAndHost(String token, String host, String version) {
22 | globalTOKEN = token;
23 | globalHOST = host;
24 | apiVersion = version;
25 | authHeaders[KEY_TOKEN] = globalTOKEN;
26 | }
27 |
28 | Future send(http.BaseRequest request) {
29 | authHeaders.forEach((k, v) => request.headers[k] = v);
30 | return _inner.send(request);
31 | }
32 |
33 | @override
34 | Future get(endPoint, {Map? headers}) {
35 | return super.get(getRequestUrl(endPoint), headers: headers);
36 | }
37 |
38 | Future getRss(endPoint, {Map? headers}) {
39 | return super.get(Uri.parse("$globalHOST/$endPoint"));
40 | }
41 |
42 | @override
43 | Future post(endPoint,
44 | {Map? headers, body, Encoding? encoding}) {
45 | return super.post(getRequestUrl(endPoint),
46 | headers: headers, body: body, encoding: encoding);
47 | }
48 |
49 | @override
50 | Future put(url,
51 | {Map? headers, body, Encoding? encoding}) {
52 | return super.put(getRequestUrl(url),
53 | headers: headers, body: body, encoding: encoding);
54 | }
55 |
56 | static Uri getRequestUrl(Uri endPoint) =>
57 | Uri.parse("$globalHOST/api/$apiVersion/${endPoint.path}");
58 |
59 | static String baseUrl() => "$globalHOST/api/$apiVersion/";
60 |
61 | static Dio buildDio() {
62 | Dio dio = Dio();
63 | dio.options.baseUrl = baseUrl();
64 | dio.options.connectTimeout = 5000; //5s
65 | dio.options.receiveTimeout = 5000;
66 | authHeaders.forEach((k, v) => dio.options.headers[k] = v);
67 | dio.options.responseType = ResponseType.json;
68 | return dio;
69 | }
70 |
71 | static Map authHeaders = {
72 | KEY_TOKEN: globalTOKEN,
73 | KEY_USER_AGENT: USER_AGENT
74 | };
75 | }
76 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/providers/package_info.dart';
2 | import 'package:F4Lab/providers/theme.dart';
3 | import 'package:F4Lab/providers/user.dart';
4 | import 'package:F4Lab/ui/config/config_page.dart';
5 | import 'package:F4Lab/ui/home_page.dart';
6 | import 'package:F4Lab/util/exception_capture.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:provider/provider.dart';
9 | import 'package:provider/single_child_widget.dart';
10 |
11 | void main() {
12 | runApp(MyApp());
13 | }
14 |
15 | class MyApp extends StatelessWidget {
16 | MyApp() {
17 | FlutterError.onError = MyApp.errorHandler;
18 | }
19 |
20 | static void errorHandler(FlutterErrorDetails details,
21 | {bool forceReport = false}) {
22 | sentry.captureException(
23 | details.exception,
24 | stackTrace: details.stack,
25 | );
26 | }
27 |
28 | List _buildProviders(BuildContext context) {
29 | return [
30 | ChangeNotifierProvider(create: (_) => ThemeProvider()),
31 | ChangeNotifierProvider(create: (_) => UserProvider()),
32 | ChangeNotifierProvider(create: (_) => PackageInfoProvider()),
33 | ];
34 | }
35 |
36 | Map _buildRoutes() => {
37 | '/': (_) => HomePage(),
38 | '/config': (_) => ConfigPage(),
39 | };
40 |
41 | @override
42 | Widget build(BuildContext context) {
43 | return MultiProvider(
44 | providers: _buildProviders(context),
45 | child: Consumer(builder: (context, theme, _) {
46 | return MaterialApp(
47 | title: 'GitLab',
48 | initialRoute: '/',
49 | theme: theme.currentTheme,
50 | routes: _buildRoutes());
51 | }));
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/main_dev.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/main.dart';
2 | import 'package:flutter/material.dart';
3 | // import 'package:flutter_stetho/flutter_stetho.dart';
4 |
5 | void main() {
6 | // Stetho.initialize();
7 | runApp(MyApp());
8 | }
9 |
--------------------------------------------------------------------------------
/lib/model/approvals.dart:
--------------------------------------------------------------------------------
1 | class Approvals {
2 | late int id;
3 | late int iid;
4 | late int projectId;
5 | late String title;
6 | late String description;
7 | late String state;
8 | late String createdAt;
9 | late String updatedAt;
10 | late String mergeStatus;
11 | late int approvalsRequired;
12 | late int approvalsLeft;
13 | late List approvedBy = [];
14 |
15 | Approvals(
16 | {required this.id,
17 | required this.iid,
18 | required this.projectId,
19 | required this.title,
20 | required this.description,
21 | required this.state,
22 | required this.createdAt,
23 | required this.updatedAt,
24 | required this.mergeStatus,
25 | required this.approvalsRequired,
26 | required this.approvalsLeft,
27 | required this.approvedBy});
28 |
29 | Approvals.fromJson(Map json) {
30 | id = json['id'];
31 | iid = json['iid'];
32 | projectId = json['project_id'];
33 | title = json['title'];
34 | description = json['description'];
35 | state = json['state'];
36 | createdAt = json['created_at'];
37 | updatedAt = json['updated_at'];
38 | mergeStatus = json['merge_status'];
39 | approvalsRequired = json['approvals_required'];
40 | approvalsLeft = json['approvals_left'];
41 | if (json['approved_by'] != null) {
42 | approvedBy = [];
43 | json['approved_by'].forEach((v) {
44 | approvedBy.add(new ApprovedBy.fromJson(v));
45 | });
46 | }
47 | }
48 |
49 | Map toJson() {
50 | final Map data = new Map();
51 | data['id'] = this.id;
52 | data['iid'] = this.iid;
53 | data['project_id'] = this.projectId;
54 | data['title'] = this.title;
55 | data['description'] = this.description;
56 | data['state'] = this.state;
57 | data['created_at'] = this.createdAt;
58 | data['updated_at'] = this.updatedAt;
59 | data['merge_status'] = this.mergeStatus;
60 | data['approvals_required'] = this.approvalsRequired;
61 | data['approvals_left'] = this.approvalsLeft;
62 | if (this.approvedBy != null) {
63 | data['approved_by'] = this.approvedBy.map((v) => v.toJson()).toList();
64 | }
65 | return data;
66 | }
67 | }
68 |
69 | class ApprovedBy {
70 | User? user;
71 |
72 | ApprovedBy({this.user});
73 |
74 | ApprovedBy.fromJson(Map json) {
75 | user = json['user'] != null ? new User.fromJson(json['user']) : null;
76 | }
77 |
78 | Map toJson() {
79 | final Map data = new Map();
80 | if (this.user != null) {
81 | data['user'] = this.user!.toJson();
82 | }
83 | return data;
84 | }
85 | }
86 |
87 | class User {
88 | late String name;
89 | late String username;
90 | late int id;
91 | late String state;
92 | late String avatarUrl;
93 | late String webUrl;
94 |
95 | User(
96 | {required this.name,
97 | required this.username,
98 | required this.id,
99 | required this.state,
100 | required this.avatarUrl,
101 | required this.webUrl});
102 |
103 | User.fromJson(Map json) {
104 | name = json['name'];
105 | username = json['username'];
106 | id = json['id'];
107 | state = json['state'];
108 | avatarUrl = json['avatar_url'];
109 | webUrl = json['web_url'];
110 | }
111 |
112 | Map toJson() {
113 | final Map data = new Map();
114 | data['name'] = this.name;
115 | data['username'] = this.username;
116 | data['id'] = this.id;
117 | data['state'] = this.state;
118 | data['avatar_url'] = this.avatarUrl;
119 | data['web_url'] = this.webUrl;
120 | return data;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/lib/model/commit.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/util/date_util.dart';
2 |
3 | class Commit {
4 | late String id;
5 | late String shortId;
6 | late String title;
7 | late String authorName;
8 | late String authorEmail;
9 | late DateTime createdAt;
10 | late String message;
11 |
12 | Commit(
13 | {required this.id,
14 | required this.shortId,
15 | required this.title,
16 | required this.authorName,
17 | required this.authorEmail,
18 | required this.createdAt,
19 | required this.message});
20 |
21 | Commit.fromJson(Map json) {
22 | id = json['id'];
23 | shortId = json['short_id'];
24 | title = json['title'];
25 | authorName = json['author_name'];
26 | authorEmail = json['author_email'];
27 | createdAt = string2Datetime(json['created_at']);
28 | message = json['message'];
29 | }
30 |
31 | Map toJson() {
32 | final Map data = new Map();
33 | data['id'] = this.id;
34 | data['short_id'] = this.shortId;
35 | data['title'] = this.title;
36 | data['author_name'] = this.authorName;
37 | data['author_email'] = this.authorEmail;
38 | data['created_at'] = this.createdAt;
39 | data['message'] = this.message;
40 | return data;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/model/diff.dart:
--------------------------------------------------------------------------------
1 | class Diff {
2 | late String oldPath;
3 | late String newPath;
4 | late String aMode;
5 | late String bMode;
6 | late bool newFile;
7 | late bool renamedFile;
8 | late bool deletedFile;
9 | late String diff;
10 |
11 | Diff(
12 | {required this.oldPath,
13 | required this.newPath,
14 | required this.aMode,
15 | required this.bMode,
16 | required this.newFile,
17 | required this.renamedFile,
18 | required this.deletedFile,
19 | required this.diff});
20 |
21 | Diff.fromJson(Map json) {
22 | oldPath = json['old_path'];
23 | newPath = json['new_path'];
24 | aMode = json['a_mode'];
25 | bMode = json['b_mode'];
26 | newFile = json['new_file'];
27 | renamedFile = json['renamed_file'];
28 | deletedFile = json['deleted_file'];
29 | diff = json['diff'];
30 | }
31 |
32 | Map toJson() {
33 | final Map data = new Map();
34 | data['old_path'] = this.oldPath;
35 | data['new_path'] = this.newPath;
36 | data['a_mode'] = this.aMode;
37 | data['b_mode'] = this.bMode;
38 | data['new_file'] = this.newFile;
39 | data['renamed_file'] = this.renamedFile;
40 | data['deleted_file'] = this.deletedFile;
41 | data['diff'] = this.diff;
42 | return data;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/model/discussion.dart:
--------------------------------------------------------------------------------
1 | class Discussion {
2 | late String id;
3 | late bool individualNote;
4 | late List notes = [];
5 |
6 | Discussion(
7 | {required this.id, required this.individualNote, required this.notes});
8 |
9 | Discussion.fromJson(Map json) {
10 | id = json['id'];
11 | individualNote = json['individual_note'];
12 | if (json['notes'] != null) {
13 | notes = [];
14 | json['notes'].forEach((v) {
15 | notes.add(new Notes.fromJson(v));
16 | });
17 | }
18 | }
19 |
20 | Map toJson() {
21 | final Map data = new Map();
22 | data['id'] = this.id;
23 | data['individual_note'] = this.individualNote;
24 | data['notes'] = this.notes.map((v) => v.toJson()).toList();
25 | return data;
26 | }
27 | }
28 |
29 | class Notes {
30 | late int id;
31 | late String type;
32 | late String body;
33 | late String attachment;
34 | Author? author;
35 | late String createdAt;
36 | late String updatedAt;
37 | late bool system;
38 | late int noteableId;
39 | late String noteableType;
40 | late int noteableIid;
41 | late bool resolved;
42 | late bool resolvable;
43 | Author? resolvedBy;
44 |
45 | Notes(
46 | {required this.id,
47 | required this.type,
48 | required this.body,
49 | required this.attachment,
50 | this.author,
51 | required this.createdAt,
52 | required this.updatedAt,
53 | required this.system,
54 | required this.noteableId,
55 | required this.noteableType,
56 | required this.noteableIid,
57 | required this.resolved,
58 | required this.resolvable,
59 | this.resolvedBy});
60 |
61 | Notes.fromJson(Map json) {
62 | id = json['id'];
63 | type = json['type'];
64 | body = json['body'];
65 | attachment = json['attachment'];
66 | author =
67 | json['author'] != null ? new Author.fromJson(json['author']) : null;
68 | createdAt = json['created_at'];
69 | updatedAt = json['updated_at'];
70 | system = json['system'];
71 | noteableId = json['noteable_id'];
72 | noteableType = json['noteable_type'];
73 | noteableIid = json['noteable_iid'];
74 | resolved = json['resolved'] != null ? json['resolved'] : false;
75 | resolvable = json['resolvable'];
76 | resolvedBy = json['resolved_by'] != null
77 | ? new Author.fromJson(json['author'])
78 | : null;
79 | }
80 |
81 | Map toJson() {
82 | final Map data = new Map();
83 | data['id'] = this.id;
84 | data['type'] = this.type;
85 | data['body'] = this.body;
86 | data['attachment'] = this.attachment;
87 | if (this.author != null) {
88 | data['author'] = this.author?.toJson();
89 | }
90 | data['created_at'] = this.createdAt;
91 | data['updated_at'] = this.updatedAt;
92 | data['system'] = this.system;
93 | data['noteable_id'] = this.noteableId;
94 | data['noteable_type'] = this.noteableType;
95 | data['noteable_iid'] = this.noteableIid;
96 | data['resolved'] = this.resolved;
97 | data['resolvable'] = this.resolvable;
98 | data['resolved_by'] = this.resolvedBy;
99 | return data;
100 | }
101 | }
102 |
103 | class Author {
104 | late int id;
105 | late String name;
106 | late String username;
107 | late String state;
108 | late String avatarUrl;
109 | late String webUrl;
110 |
111 | Author(
112 | {required this.id,
113 | required this.name,
114 | required this.username,
115 | required this.state,
116 | required this.avatarUrl,
117 | required this.webUrl});
118 |
119 | Author.fromJson(Map json) {
120 | id = json['id'];
121 | name = json['name'];
122 | username = json['username'];
123 | state = json['state'];
124 | avatarUrl = json['avatar_url'];
125 | webUrl = json['web_url'];
126 | }
127 |
128 | Map toJson() {
129 | final Map data = new Map();
130 | data['id'] = this.id;
131 | data['name'] = this.name;
132 | data['username'] = this.username;
133 | data['state'] = this.state;
134 | data['avatar_url'] = this.avatarUrl;
135 | data['web_url'] = this.webUrl;
136 | return data;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/lib/model/group.dart:
--------------------------------------------------------------------------------
1 | class Group {
2 | late int id;
3 | late String name;
4 | late String path;
5 | late String description;
6 | late String visibility;
7 | late bool lfsEnabled;
8 | String? avatarUrl;
9 | late String webUrl;
10 | late bool requestAccessEnabled;
11 | late String fullName;
12 | late String fullPath;
13 | int? parentId;
14 |
15 | Group(
16 | {required this.id,
17 | required this.name,
18 | required this.path,
19 | required this.description,
20 | required this.visibility,
21 | required this.lfsEnabled,
22 | this.avatarUrl,
23 | required this.webUrl,
24 | required this.requestAccessEnabled,
25 | required this.fullName,
26 | required this.fullPath,
27 | this.parentId});
28 |
29 | Group.fromJson(Map json) {
30 | id = json['id'];
31 | name = json['name'];
32 | path = json['path'];
33 | description = json['description'];
34 | visibility = json['visibility'];
35 | lfsEnabled = json['lfs_enabled'];
36 | avatarUrl = json['avatar_url'];
37 | webUrl = json['web_url'];
38 | requestAccessEnabled = json['request_access_enabled'];
39 | fullName = json['full_name'];
40 | fullPath = json['full_path'];
41 | parentId = json['parent_id'];
42 | }
43 |
44 | Map toJson() {
45 | final Map data = new Map();
46 | data['id'] = this.id;
47 | data['name'] = this.name;
48 | data['path'] = this.path;
49 | data['description'] = this.description;
50 | data['visibility'] = this.visibility;
51 | data['lfs_enabled'] = this.lfsEnabled;
52 | data['avatar_url'] = this.avatarUrl;
53 | data['web_url'] = this.webUrl;
54 | data['request_access_enabled'] = this.requestAccessEnabled;
55 | data['full_name'] = this.fullName;
56 | data['full_path'] = this.fullPath;
57 | data['parent_id'] = this.parentId;
58 | return data;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/lib/model/jobs.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/model/pipeline.dart';
2 | import 'package:F4Lab/model/runner.dart';
3 | import 'package:F4Lab/model/user.dart';
4 | import 'package:F4Lab/util/date_util.dart';
5 |
6 | class Jobs {
7 | Commit? commit;
8 | late String coverage;
9 | late DateTime createdAt;
10 | late String startedAt;
11 | late String finishedAt;
12 | late double duration;
13 | late String artifactsExpireAt;
14 | late int id;
15 | late String name;
16 | Pipeline? pipeline;
17 | late String ref;
18 | late List artifacts;
19 | late Runner runner;
20 | late String stage;
21 | late String status;
22 | late bool tag;
23 | late String webUrl;
24 | User? user;
25 |
26 | Jobs(
27 | {required this.commit,
28 | required this.coverage,
29 | required this.createdAt,
30 | required this.startedAt,
31 | required this.finishedAt,
32 | required this.duration,
33 | required this.artifactsExpireAt,
34 | required this.id,
35 | required this.name,
36 | this.pipeline,
37 | required this.ref,
38 | required this.artifacts,
39 | required this.runner,
40 | required this.stage,
41 | required this.status,
42 | required this.tag,
43 | required this.webUrl,
44 | this.user});
45 |
46 | Jobs.fromJson(Map json) {
47 | commit =
48 | json['commit'] != null ? new Commit.fromJson(json['commit']) : null;
49 | coverage = json['coverage'];
50 | createdAt = string2Datetime(json['created_at']);
51 | startedAt = json['started_at'];
52 | finishedAt = json['finished_at'];
53 | duration = json['duration'];
54 | artifactsExpireAt = json['artifacts_expire_at'];
55 | id = json['id'];
56 | name = json['name'];
57 | pipeline = json['pipeline'] != null
58 | ? new Pipeline.fromJson(json['pipeline'])
59 | : null;
60 | ref = json['ref'];
61 | artifacts = json['artifacts'].cast();
62 | runner = Runner.fromJson(json['runner']);
63 | stage = json['stage'];
64 | status = json['status'];
65 | tag = json['tag'];
66 | webUrl = json['web_url'];
67 | user = json['user'] != null ? new User.fromJsonInJobs(json['user']) : null;
68 | }
69 |
70 | Map toJson() {
71 | final Map data = new Map();
72 | if (this.commit != null) {
73 | data['commit'] = this.commit?.toJson();
74 | }
75 | data['coverage'] = this.coverage;
76 | data['created_at'] = this.createdAt;
77 | data['started_at'] = this.startedAt;
78 | data['finished_at'] = this.finishedAt;
79 | data['duration'] = this.duration;
80 | data['artifacts_expire_at'] = this.artifactsExpireAt;
81 | data['id'] = this.id;
82 | data['name'] = this.name;
83 | if (this.pipeline != null) {
84 | data['pipeline'] = this.pipeline?.toJson();
85 | }
86 | data['ref'] = this.ref;
87 | data['artifacts'] = this.artifacts;
88 | data['runner'] = this.runner.toJson();
89 | data['stage'] = this.stage;
90 | data['status'] = this.status;
91 | data['tag'] = this.tag;
92 | data['web_url'] = this.webUrl;
93 | if (this.user != null) {
94 | data['user'] = this.user?.toJsonInJobs();
95 | }
96 | return data;
97 | }
98 | }
99 |
100 | class Commit {
101 | late String authorEmail;
102 | late String authorName;
103 | late String createdAt;
104 | late String id;
105 | late String message;
106 | late String shortId;
107 | late String title;
108 |
109 | Commit(
110 | {required this.authorEmail,
111 | required this.authorName,
112 | required this.createdAt,
113 | required this.id,
114 | required this.message,
115 | required this.shortId,
116 | required this.title});
117 |
118 | Commit.fromJson(Map json) {
119 | authorEmail = json['author_email'];
120 | authorName = json['author_name'];
121 | createdAt = json['created_at'];
122 | id = json['id'];
123 | message = json['message'];
124 | shortId = json['short_id'];
125 | title = json['title'];
126 | }
127 |
128 | Map toJson() {
129 | final Map data = new Map();
130 | data['author_email'] = this.authorEmail;
131 | data['author_name'] = this.authorName;
132 | data['created_at'] = this.createdAt;
133 | data['id'] = this.id;
134 | data['message'] = this.message;
135 | data['short_id'] = this.shortId;
136 | data['title'] = this.title;
137 | return data;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/lib/model/merge_request.dart:
--------------------------------------------------------------------------------
1 | class MergeRequest {
2 | late int id;
3 | late int iid;
4 | late int projectId;
5 | late String title;
6 | late String description;
7 | late String state;
8 | MergedBy? mergedBy;
9 | late String? mergedAt;
10 | Author? closedBy;
11 | late String? closedAt;
12 | late String createdAt;
13 | late String updatedAt;
14 | late String targetBranch;
15 | late String sourceBranch;
16 | late int upvotes;
17 | late int downvotes;
18 | Author? author;
19 | Assignee? assignee;
20 | late int sourceProjectId;
21 | late int targetProjectId;
22 | late List labels;
23 | late bool workInProgress;
24 | Milestone? milestone;
25 | late bool mergeWhenPipelineSucceeds;
26 | late String mergeStatus;
27 | late String sha;
28 | String? mergeCommitSha;
29 | late int userNotesCount;
30 | String? discussionLocked;
31 | bool? shouldRemoveSourceBranch;
32 | late bool forceRemoveSourceBranch;
33 | late bool allowCollaboration;
34 | late bool allowMaintainerToPush;
35 | late String webUrl;
36 | TimeStats? timeStats;
37 | late bool squash;
38 | late int divergedCommitsCount;
39 | late bool rebaseInProgress;
40 |
41 | MergeRequest(
42 | {required this.id,
43 | required this.iid,
44 | required this.projectId,
45 | required this.title,
46 | required this.description,
47 | required this.state,
48 | this.mergedBy,
49 | required this.mergedAt,
50 | this.closedBy,
51 | this.closedAt,
52 | required this.createdAt,
53 | required this.updatedAt,
54 | required this.targetBranch,
55 | required this.sourceBranch,
56 | required this.upvotes,
57 | required this.downvotes,
58 | this.author,
59 | this.assignee,
60 | required this.sourceProjectId,
61 | required this.targetProjectId,
62 | required this.labels,
63 | required this.workInProgress,
64 | this.milestone,
65 | required this.mergeWhenPipelineSucceeds,
66 | required this.mergeStatus,
67 | required this.sha,
68 | this.mergeCommitSha,
69 | required this.userNotesCount,
70 | this.discussionLocked,
71 | this.shouldRemoveSourceBranch,
72 | required this.forceRemoveSourceBranch,
73 | required this.allowCollaboration,
74 | required this.allowMaintainerToPush,
75 | required this.webUrl,
76 | this.timeStats,
77 | required this.squash,
78 | required this.divergedCommitsCount,
79 | required this.rebaseInProgress});
80 |
81 | MergeRequest.fromJson(Map json) {
82 | id = json['id'];
83 | iid = json['iid'];
84 | projectId = json['project_id'];
85 | title = json['title'];
86 | description = json['description'];
87 | state = json['state'];
88 | mergedBy = json['merged_by'] != null
89 | ? new MergedBy.fromJson(json['merged_by'])
90 | : null;
91 | mergedAt = json['merged_at'];
92 | closedBy =
93 | json['closed_by'] != null ? Author.fromJson(json['closed_by']) : null;
94 | closedAt = json['closed_at'];
95 | createdAt = json['created_at'];
96 | updatedAt = json['updated_at'];
97 | targetBranch = json['target_branch'];
98 | sourceBranch = json['source_branch'];
99 | upvotes = json['upvotes'];
100 | downvotes = json['downvotes'];
101 | author =
102 | json['author'] != null ? new Author.fromJson(json['author']) : null;
103 | assignee = json['assignee'] != null
104 | ? new Assignee.fromJson(json['assignee'])
105 | : null;
106 | sourceProjectId = json['source_project_id'];
107 | targetProjectId = json['target_project_id'];
108 | labels = (json['labels'] ?? []).cast();
109 | workInProgress = json['work_in_progress'];
110 | milestone = json['milestone'] != null
111 | ? new Milestone.fromJson(json['milestone'])
112 | : null;
113 | mergeWhenPipelineSucceeds = json['merge_when_pipeline_succeeds'];
114 | mergeStatus = json['merge_status'];
115 | sha = json['sha'];
116 | mergeCommitSha = json['merge_commit_sha'];
117 | userNotesCount = json['user_notes_count'];
118 | discussionLocked = json['discussion_locked'];
119 | shouldRemoveSourceBranch = json['should_remove_source_branch'];
120 | forceRemoveSourceBranch = json['force_remove_source_branch'];
121 | webUrl = json['web_url'];
122 | timeStats = json['time_stats'] != null
123 | ? new TimeStats.fromJson(json['time_stats'])
124 | : null;
125 | squash = json['squash'];
126 | divergedCommitsCount = json['diverged_commits_count'] != null
127 | ? json['diverged_commits_count']
128 | : 0;
129 | rebaseInProgress = json['rebase_in_progress'] ?? false;
130 | }
131 | }
132 |
133 | class MergedBy {
134 | late int id;
135 | late String name;
136 | late String username;
137 | late String state;
138 | late String avatarUrl;
139 | late String webUrl;
140 |
141 | MergedBy(
142 | {required this.id,
143 | required this.name,
144 | required this.username,
145 | required this.state,
146 | required this.avatarUrl,
147 | required this.webUrl});
148 |
149 | MergedBy.fromJson(Map json) {
150 | id = json['id'];
151 | name = json['name'];
152 | username = json['username'];
153 | state = json['state'];
154 | avatarUrl = json['avatar_url'];
155 | webUrl = json['web_url'];
156 | }
157 |
158 | Map toJson() {
159 | final Map data = new Map();
160 | data['id'] = this.id;
161 | data['name'] = this.name;
162 | data['username'] = this.username;
163 | data['state'] = this.state;
164 | data['avatar_url'] = this.avatarUrl;
165 | data['web_url'] = this.webUrl;
166 | return data;
167 | }
168 | }
169 |
170 | class Author {
171 | late int id;
172 | late String name;
173 | late String username;
174 | late String state;
175 | late String avatarUrl;
176 | late String webUrl;
177 |
178 | Author(
179 | {required this.id,
180 | required this.name,
181 | required this.username,
182 | required this.state,
183 | required this.avatarUrl,
184 | required this.webUrl});
185 |
186 | Author.fromJson(Map json) {
187 | id = json['id'];
188 | name = json['name'];
189 | username = json['username'];
190 | state = json['state'];
191 | avatarUrl = json['avatar_url'];
192 | webUrl = json['web_url'];
193 | }
194 |
195 | Map toJson() {
196 | final Map data = new Map();
197 | data['id'] = this.id;
198 | data['name'] = this.name;
199 | data['username'] = this.username;
200 | data['state'] = this.state;
201 | data['avatar_url'] = this.avatarUrl;
202 | data['web_url'] = this.webUrl;
203 | return data;
204 | }
205 | }
206 |
207 | class Assignee {
208 | late int id;
209 | late String name;
210 | late String username;
211 | late String state;
212 | late String avatarUrl;
213 | late String webUrl;
214 |
215 | Assignee(
216 | {required this.id,
217 | required this.name,
218 | required this.username,
219 | required this.state,
220 | required this.avatarUrl,
221 | required this.webUrl});
222 |
223 | Assignee.fromJson(Map json) {
224 | id = json['id'];
225 | name = json['name'];
226 | username = json['username'];
227 | state = json['state'];
228 | avatarUrl = json['avatar_url'];
229 | webUrl = json['web_url'];
230 | }
231 |
232 | Map toJson() {
233 | final Map data = new Map();
234 | data['id'] = this.id;
235 | data['name'] = this.name;
236 | data['username'] = this.username;
237 | data['state'] = this.state;
238 | data['avatar_url'] = this.avatarUrl;
239 | data['web_url'] = this.webUrl;
240 | return data;
241 | }
242 | }
243 |
244 | class Milestone {
245 | late int id;
246 | late int iid;
247 | late int projectId;
248 | late String title;
249 | late String description;
250 | late String state;
251 | late String createdAt;
252 | late String updatedAt;
253 | late String dueDate;
254 | late String startDate;
255 | late String webUrl;
256 |
257 | Milestone(
258 | {required this.id,
259 | required this.iid,
260 | required this.projectId,
261 | required this.title,
262 | required this.description,
263 | required this.state,
264 | required this.createdAt,
265 | required this.updatedAt,
266 | required this.dueDate,
267 | required this.startDate,
268 | required this.webUrl});
269 |
270 | Milestone.fromJson(Map json) {
271 | id = json['id'];
272 | iid = json['iid'];
273 | projectId = json['project_id'];
274 | title = json['title'];
275 | description = json['description'];
276 | state = json['state'];
277 | createdAt = json['created_at'];
278 | updatedAt = json['updated_at'];
279 | dueDate = json['due_date'];
280 | startDate = json['start_date'];
281 | webUrl = json['web_url'];
282 | }
283 |
284 | Map toJson() {
285 | final Map data = new Map();
286 | data['id'] = this.id;
287 | data['iid'] = this.iid;
288 | data['project_id'] = this.projectId;
289 | data['title'] = this.title;
290 | data['description'] = this.description;
291 | data['state'] = this.state;
292 | data['created_at'] = this.createdAt;
293 | data['updated_at'] = this.updatedAt;
294 | data['due_date'] = this.dueDate;
295 | data['start_date'] = this.startDate;
296 | data['web_url'] = this.webUrl;
297 | return data;
298 | }
299 | }
300 |
301 | class TimeStats {
302 | late int timeEstimate;
303 | late int totalTimeSpent;
304 | int? humanTimeEstimate;
305 | int? humanTotalTimeSpent;
306 |
307 | TimeStats(
308 | {required this.timeEstimate,
309 | required this.totalTimeSpent,
310 | this.humanTimeEstimate,
311 | this.humanTotalTimeSpent});
312 |
313 | TimeStats.fromJson(Map json) {
314 | timeEstimate = json['time_estimate'];
315 | totalTimeSpent = json['total_time_spent'];
316 | humanTimeEstimate = json['human_time_estimate'];
317 | humanTotalTimeSpent = json['human_total_time_spent'];
318 | }
319 |
320 | Map toJson() {
321 | final Map data = new Map();
322 | data['time_estimate'] = this.timeEstimate;
323 | data['total_time_spent'] = this.totalTimeSpent;
324 | data['human_time_estimate'] = this.humanTimeEstimate;
325 | data['human_total_time_spent'] = this.humanTotalTimeSpent;
326 | return data;
327 | }
328 | }
329 |
--------------------------------------------------------------------------------
/lib/model/pipeline.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/model/user.dart';
2 |
3 | class Pipeline {
4 | late int id;
5 | late String sha;
6 | late String ref;
7 | late String status;
8 | late String webUrl;
9 | late String beforeSha;
10 | late bool tag;
11 | late String yamlErrors;
12 | User? user;
13 | late String createdAt;
14 | late String updatedAt;
15 | late String startedAt;
16 | late String finishedAt;
17 | late String committedAt;
18 | late int duration;
19 | late String coverage;
20 |
21 | Pipeline(
22 | {required this.id,
23 | required this.sha,
24 | required this.ref,
25 | required this.status,
26 | required this.webUrl,
27 | required this.beforeSha,
28 | required this.tag,
29 | required this.yamlErrors,
30 | this.user,
31 | required this.createdAt,
32 | required this.updatedAt,
33 | required this.startedAt,
34 | required this.finishedAt,
35 | required this.committedAt,
36 | required this.duration,
37 | required this.coverage});
38 |
39 | Pipeline.fromJson(Map json) {
40 | id = json['id'];
41 | sha = json['sha'];
42 | ref = json['ref'];
43 | status = json['status'];
44 | webUrl = json['web_url'];
45 | beforeSha = json['before_sha'];
46 | tag = json['tag'];
47 | yamlErrors = json['yaml_errors'];
48 | user = json['user'] != null ? new User.fromJson(json['user']) : null;
49 | createdAt = json['created_at'];
50 | updatedAt = json['updated_at'];
51 | startedAt = json['started_at'];
52 | finishedAt = json['finished_at'];
53 | committedAt = json['committed_at'];
54 | duration = json['duration'];
55 | coverage = json['coverage'];
56 | }
57 |
58 | Map toJson() {
59 | final Map data = new Map();
60 | data['id'] = this.id;
61 | data['sha'] = this.sha;
62 | data['ref'] = this.ref;
63 | data['status'] = this.status;
64 | data['web_url'] = this.webUrl;
65 | data['before_sha'] = this.beforeSha;
66 | data['tag'] = this.tag;
67 | data['yaml_errors'] = this.yamlErrors;
68 | if (this.user != null) {
69 | data['user'] = this.user?.toJson();
70 | }
71 | data['created_at'] = this.createdAt;
72 | data['updated_at'] = this.updatedAt;
73 | data['started_at'] = this.startedAt;
74 | data['finished_at'] = this.finishedAt;
75 | data['committed_at'] = this.committedAt;
76 | data['duration'] = this.duration;
77 | data['coverage'] = this.coverage;
78 | return data;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/lib/model/project.dart:
--------------------------------------------------------------------------------
1 | class Project {
2 | late int id;
3 | late String description;
4 | late String defaultBranch;
5 | late String sshUrlToRepo;
6 | late String httpUrlToRepo;
7 | late String webUrl;
8 | String? readmeUrl;
9 | late List tagList;
10 | late String name;
11 | late String nameWithNamespace;
12 | late String path;
13 | late String pathWithNamespace;
14 | late String createdAt;
15 | late String lastActivityAt;
16 | late int forksCount;
17 | String? avatarUrl;
18 | late int starCount;
19 |
20 | Project(
21 | {required this.id,
22 | required this.description,
23 | required this.defaultBranch,
24 | required this.sshUrlToRepo,
25 | required this.httpUrlToRepo,
26 | required this.webUrl,
27 | this.readmeUrl,
28 | required this.tagList,
29 | required this.name,
30 | required this.nameWithNamespace,
31 | required this.path,
32 | required this.pathWithNamespace,
33 | required this.createdAt,
34 | required this.lastActivityAt,
35 | required this.forksCount,
36 | this.avatarUrl,
37 | required this.starCount});
38 |
39 | Project.fromJson(Map json) {
40 | id = json['id'];
41 | description = json['description'];
42 | defaultBranch = json['default_branch'];
43 | sshUrlToRepo = json['ssh_url_to_repo'];
44 | httpUrlToRepo = json['http_url_to_repo'];
45 | webUrl = json['web_url'];
46 | readmeUrl = json['readme_url'];
47 | tagList = json['tag_list'].cast();
48 | name = json['name'];
49 | nameWithNamespace = json['name_with_namespace'];
50 | path = json['path'];
51 | pathWithNamespace = json['path_with_namespace'];
52 | createdAt = json['created_at'];
53 | lastActivityAt = json['last_activity_at'];
54 | forksCount = json['forks_count'];
55 | avatarUrl = json['avatar_url'];
56 | starCount = json['star_count'];
57 | }
58 |
59 | Map toJson() {
60 | final Map data = new Map();
61 | data['id'] = this.id;
62 | data['description'] = this.description;
63 | data['default_branch'] = this.defaultBranch;
64 | data['ssh_url_to_repo'] = this.sshUrlToRepo;
65 | data['http_url_to_repo'] = this.httpUrlToRepo;
66 | data['web_url'] = this.webUrl;
67 | data['readme_url'] = this.readmeUrl;
68 | data['tag_list'] = this.tagList;
69 | data['name'] = this.name;
70 | data['name_with_namespace'] = this.nameWithNamespace;
71 | data['path'] = this.path;
72 | data['path_with_namespace'] = this.pathWithNamespace;
73 | data['created_at'] = this.createdAt;
74 | data['last_activity_at'] = this.lastActivityAt;
75 | data['forks_count'] = this.forksCount;
76 | data['avatar_url'] = this.avatarUrl;
77 | data['star_count'] = this.starCount;
78 | return data;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/lib/model/runner.dart:
--------------------------------------------------------------------------------
1 | class Runner {
2 | late bool active;
3 | late String description;
4 | late int id;
5 | late bool isShared;
6 | late String ipAddress;
7 | late String name;
8 | late bool online;
9 | late String status;
10 |
11 | Runner(
12 | {required this.active,
13 | required this.description,
14 | required this.id,
15 | required this.isShared,
16 | required this.ipAddress,
17 | required this.name,
18 | required this.online,
19 | required this.status});
20 |
21 | Runner.fromJson(Map json) {
22 | active = json['active'];
23 | description = json['description'];
24 | id = json['id'];
25 | isShared = json['is_shared'];
26 | ipAddress = json['ip_address'];
27 | name = json['name'];
28 | online = json['online'];
29 | status = json['status'];
30 | }
31 |
32 | Map toJson() {
33 | final Map data = new Map();
34 | data['active'] = this.active;
35 | data['description'] = this.description;
36 | data['id'] = this.id;
37 | data['is_shared'] = this.isShared;
38 | data['ip_address'] = this.ipAddress;
39 | data['name'] = this.name;
40 | data['online'] = this.online;
41 | data['status'] = this.status;
42 | return data;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/model/todo.dart:
--------------------------------------------------------------------------------
1 | class Todo {
2 | late int id;
3 | Project? project;
4 | Author? author;
5 | late String actionName;
6 | late String targetType;
7 | Target? target;
8 | late String targetUrl;
9 | late String body;
10 | late String state;
11 | late String createdAt;
12 |
13 | Todo(
14 | {required this.id,
15 | this.project,
16 | this.author,
17 | required this.actionName,
18 | required this.targetType,
19 | this.target,
20 | required this.targetUrl,
21 | required this.body,
22 | required this.state,
23 | required this.createdAt});
24 |
25 | Todo.fromJson(Map json) {
26 | id = json['id'];
27 | project =
28 | json['project'] != null ? new Project.fromJson(json['project']) : null;
29 | author =
30 | json['author'] != null ? new Author.fromJson(json['author']) : null;
31 | actionName = json['action_name'];
32 | targetType = json['target_type'];
33 | target =
34 | json['target'] != null ? new Target.fromJson(json['target']) : null;
35 | targetUrl = json['target_url'];
36 | body = json['body'];
37 | state = json['state'];
38 | createdAt = json['created_at'];
39 | }
40 |
41 | Map toJson() {
42 | final Map data = new Map();
43 | data['id'] = this.id;
44 | if (this.project != null) {
45 | data['project'] = this.project?.toJson();
46 | }
47 | if (this.author != null) {
48 | data['author'] = this.author?.toJson();
49 | }
50 | data['action_name'] = this.actionName;
51 | data['target_type'] = this.targetType;
52 | if (this.target != null) {
53 | data['target'] = this.target?.toJson();
54 | }
55 | data['target_url'] = this.targetUrl;
56 | data['body'] = this.body;
57 | data['state'] = this.state;
58 | data['created_at'] = this.createdAt;
59 | return data;
60 | }
61 | }
62 |
63 | class Project {
64 | late int id;
65 | late String name;
66 | late String nameWithNamespace;
67 | late String path;
68 | late String pathWithNamespace;
69 |
70 | Project(
71 | {required this.id,
72 | required this.name,
73 | required this.nameWithNamespace,
74 | required this.path,
75 | required this.pathWithNamespace});
76 |
77 | Project.fromJson(Map json) {
78 | id = json['id'];
79 | name = json['name'];
80 | nameWithNamespace = json['name_with_namespace'];
81 | path = json['path'];
82 | pathWithNamespace = json['path_with_namespace'];
83 | }
84 |
85 | Map toJson() {
86 | final Map data = new Map();
87 | data['id'] = this.id;
88 | data['name'] = this.name;
89 | data['name_with_namespace'] = this.nameWithNamespace;
90 | data['path'] = this.path;
91 | data['path_with_namespace'] = this.pathWithNamespace;
92 | return data;
93 | }
94 | }
95 |
96 | class Author {
97 | late String name;
98 | late String username;
99 | late int id;
100 | late String state;
101 | late String avatarUrl;
102 | late String webUrl;
103 |
104 | Author(
105 | {required this.name,
106 | required this.username,
107 | required this.id,
108 | required this.state,
109 | required this.avatarUrl,
110 | required this.webUrl});
111 |
112 | Author.fromJson(Map json) {
113 | name = json['name'];
114 | username = json['username'];
115 | id = json['id'];
116 | state = json['state'];
117 | avatarUrl = json['avatar_url'];
118 | webUrl = json['web_url'];
119 | }
120 |
121 | Map toJson() {
122 | final Map data = new Map();
123 | data['name'] = this.name;
124 | data['username'] = this.username;
125 | data['id'] = this.id;
126 | data['state'] = this.state;
127 | data['avatar_url'] = this.avatarUrl;
128 | data['web_url'] = this.webUrl;
129 | return data;
130 | }
131 | }
132 |
133 | class Target {
134 | late int id;
135 | late int iid;
136 | late int projectId;
137 | late String title;
138 | late String description;
139 | late String state;
140 | late String createdAt;
141 | late String updatedAt;
142 | late String targetBranch;
143 | late String sourceBranch;
144 | late int upvotes;
145 | late int downvotes;
146 | Author? author;
147 | Assignee? assignee;
148 | late int sourceProjectId;
149 | late int targetProjectId;
150 | late List labels;
151 | late bool workInProgress;
152 | Milestone? milestone;
153 | late bool mergeWhenPipelineSucceeds;
154 | late String mergeStatus;
155 | late bool subscribed;
156 | late int userNotesCount;
157 |
158 | Target(
159 | {required this.id,
160 | required this.iid,
161 | required this.projectId,
162 | required this.title,
163 | required this.description,
164 | required this.state,
165 | required this.createdAt,
166 | required this.updatedAt,
167 | required this.targetBranch,
168 | required this.sourceBranch,
169 | required this.upvotes,
170 | required this.downvotes,
171 | this.author,
172 | this.assignee,
173 | required this.sourceProjectId,
174 | required this.targetProjectId,
175 | required this.labels,
176 | required this.workInProgress,
177 | this.milestone,
178 | required this.mergeWhenPipelineSucceeds,
179 | required this.mergeStatus,
180 | required this.subscribed,
181 | required this.userNotesCount});
182 |
183 | Target.fromJson(Map json) {
184 | id = json['id'];
185 | iid = json['iid'];
186 | projectId = json['project_id'];
187 | title = json['title'];
188 | description = json['description'];
189 | state = json['state'];
190 | createdAt = json['created_at'];
191 | updatedAt = json['updated_at'];
192 | targetBranch = json['target_branch'];
193 | sourceBranch = json['source_branch'];
194 | upvotes = json['upvotes'];
195 | downvotes = json['downvotes'];
196 | author =
197 | json['author'] != null ? new Author.fromJson(json['author']) : null;
198 | assignee = json['assignee'] != null
199 | ? new Assignee.fromJson(json['assignee'])
200 | : null;
201 | sourceProjectId = json['source_project_id'];
202 | targetProjectId = json['target_project_id'];
203 | labels = json['labels'].cast();
204 | workInProgress = json['work_in_progress'];
205 | milestone = json['milestone'] != null
206 | ? new Milestone.fromJson(json['milestone'])
207 | : null;
208 | mergeWhenPipelineSucceeds = json['merge_when_pipeline_succeeds'];
209 | mergeStatus = json['merge_status'];
210 | subscribed = json['subscribed'];
211 | userNotesCount = json['user_notes_count'];
212 | }
213 |
214 | Map toJson() {
215 | final Map data = new Map();
216 | data['id'] = this.id;
217 | data['iid'] = this.iid;
218 | data['project_id'] = this.projectId;
219 | data['title'] = this.title;
220 | data['description'] = this.description;
221 | data['state'] = this.state;
222 | data['created_at'] = this.createdAt;
223 | data['updated_at'] = this.updatedAt;
224 | data['target_branch'] = this.targetBranch;
225 | data['source_branch'] = this.sourceBranch;
226 | data['upvotes'] = this.upvotes;
227 | data['downvotes'] = this.downvotes;
228 | if (this.author != null) {
229 | data['author'] = this.author?.toJson();
230 | }
231 | if (this.assignee != null) {
232 | data['assignee'] = this.assignee?.toJson();
233 | }
234 | data['source_project_id'] = this.sourceProjectId;
235 | data['target_project_id'] = this.targetProjectId;
236 | data['labels'] = this.labels;
237 | data['work_in_progress'] = this.workInProgress;
238 | if (this.milestone != null) {
239 | data['milestone'] = this.milestone?.toJson();
240 | }
241 | data['merge_when_pipeline_succeeds'] = this.mergeWhenPipelineSucceeds;
242 | data['merge_status'] = this.mergeStatus;
243 | data['subscribed'] = this.subscribed;
244 | data['user_notes_count'] = this.userNotesCount;
245 | return data;
246 | }
247 | }
248 |
249 | class Assignee {
250 | late String name;
251 | late String username;
252 | late int id;
253 | late String state;
254 | late String avatarUrl;
255 | late String webUrl;
256 |
257 | Assignee(
258 | {required this.name,
259 | required this.username,
260 | required this.id,
261 | required this.state,
262 | required this.avatarUrl,
263 | required this.webUrl});
264 |
265 | Assignee.fromJson(Map json) {
266 | name = json['name'];
267 | username = json['username'];
268 | id = json['id'];
269 | state = json['state'];
270 | avatarUrl = json['avatar_url'];
271 | webUrl = json['web_url'];
272 | }
273 |
274 | Map toJson() {
275 | final Map data = new Map();
276 | data['name'] = this.name;
277 | data['username'] = this.username;
278 | data['id'] = this.id;
279 | data['state'] = this.state;
280 | data['avatar_url'] = this.avatarUrl;
281 | data['web_url'] = this.webUrl;
282 | return data;
283 | }
284 | }
285 |
286 | class Milestone {
287 | late int id;
288 | late int iid;
289 | late int projectId;
290 | late String title;
291 | late String description;
292 | late String state;
293 | late String createdAt;
294 | late String updatedAt;
295 | late String dueDate;
296 |
297 | Milestone(
298 | {required this.id,
299 | required this.iid,
300 | required this.projectId,
301 | required this.title,
302 | required this.description,
303 | required this.state,
304 | required this.createdAt,
305 | required this.updatedAt,
306 | required this.dueDate});
307 |
308 | Milestone.fromJson(Map json) {
309 | id = json['id'];
310 | iid = json['iid'];
311 | projectId = json['project_id'];
312 | title = json['title'];
313 | description = json['description'];
314 | state = json['state'];
315 | createdAt = json['created_at'];
316 | updatedAt = json['updated_at'];
317 | dueDate = json['due_date'];
318 | }
319 |
320 | Map toJson() {
321 | final Map data = new Map();
322 | data['id'] = this.id;
323 | data['iid'] = this.iid;
324 | data['project_id'] = this.projectId;
325 | data['title'] = this.title;
326 | data['description'] = this.description;
327 | data['state'] = this.state;
328 | data['created_at'] = this.createdAt;
329 | data['updated_at'] = this.updatedAt;
330 | data['due_date'] = this.dueDate;
331 | return data;
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/lib/model/user.dart:
--------------------------------------------------------------------------------
1 | class User {
2 | late int id;
3 | late String username;
4 | late String email;
5 | late String name;
6 | late String state;
7 | late String avatarUrl;
8 | late String webUrl;
9 | late String createdAt;
10 | late String bio;
11 | late String location;
12 | late String publicEmail;
13 | late String skype;
14 | late String linkedin;
15 | late String twitter;
16 | late String websiteUrl;
17 | late String organization;
18 | late String lastSignInAt;
19 | late String confirmedAt;
20 | late int themeId;
21 | late String lastActivityOn;
22 | late int colorSchemeId;
23 | late int projectsLimit;
24 | late String currentSignInAt;
25 | List identities = [];
26 | late bool canCreateGroup;
27 | late bool canCreateProject;
28 | late bool twoFactorEnabled;
29 | late bool external;
30 | late bool privateProfile;
31 |
32 | User(
33 | {required this.id,
34 | required this.username,
35 | required this.email,
36 | required this.name,
37 | required this.state,
38 | required this.avatarUrl,
39 | required this.webUrl,
40 | required this.createdAt,
41 | required this.bio,
42 | required this.location,
43 | required this.publicEmail,
44 | required this.skype,
45 | required this.linkedin,
46 | required this.twitter,
47 | required this.websiteUrl,
48 | required this.organization,
49 | required this.lastSignInAt,
50 | required this.confirmedAt,
51 | required this.themeId,
52 | required this.lastActivityOn,
53 | required this.colorSchemeId,
54 | required this.projectsLimit,
55 | required this.currentSignInAt,
56 | required this.identities,
57 | required this.canCreateGroup,
58 | required this.canCreateProject,
59 | required this.twoFactorEnabled,
60 | required this.external,
61 | required this.privateProfile});
62 |
63 | User.fromJson(Map json) {
64 | id = json['id'];
65 | username = json['username'];
66 | email = json['email'];
67 | name = json['name'];
68 | state = json['state'];
69 | avatarUrl = json['avatar_url'];
70 | webUrl = json['web_url'];
71 | createdAt = json['created_at'];
72 | bio = json['bio'];
73 | location = json['location'];
74 | publicEmail = json['public_email'];
75 | skype = json['skype'];
76 | linkedin = json['linkedin'];
77 | twitter = json['twitter'];
78 | websiteUrl = json['website_url'];
79 | organization = json['organization'];
80 | lastSignInAt = json['last_sign_in_at'];
81 | confirmedAt = json['confirmed_at'];
82 | themeId = json['theme_id'];
83 | lastActivityOn = json['last_activity_on'];
84 | colorSchemeId = json['color_scheme_id'];
85 | projectsLimit = json['projects_limit'];
86 | currentSignInAt = json['current_sign_in_at'];
87 | if (json['identities'] != null) {
88 | identities = [];
89 | json['identities'].forEach((v) {
90 | identities.add(new Identities.fromJson(v));
91 | });
92 | }
93 | canCreateGroup = json['can_create_group'];
94 | canCreateProject = json['can_create_project'];
95 | twoFactorEnabled = json['two_factor_enabled'];
96 | external = json['external'];
97 | privateProfile = json['private_profile'];
98 | }
99 |
100 | User.fromJsonInJobs(Map json) {
101 | id = json['id'];
102 | name = json['name'];
103 | username = json['username'];
104 | state = json['state'];
105 | avatarUrl = json['avatar_url'];
106 | webUrl = json['web_url'];
107 | createdAt = json['created_at'];
108 | bio = json['bio'];
109 | location = json['location'];
110 | publicEmail = json['public_email'];
111 | skype = json['skype'];
112 | linkedin = json['linkedin'];
113 | twitter = json['twitter'];
114 | websiteUrl = json['website_url'];
115 | organization = json['organization'];
116 | }
117 |
118 | Map toJson() {
119 | final Map data = new Map();
120 | data['id'] = this.id;
121 | data['username'] = this.username;
122 | data['email'] = this.email;
123 | data['name'] = this.name;
124 | data['state'] = this.state;
125 | data['avatar_url'] = this.avatarUrl;
126 | data['web_url'] = this.webUrl;
127 | data['created_at'] = this.createdAt;
128 | data['bio'] = this.bio;
129 | data['location'] = this.location;
130 | data['public_email'] = this.publicEmail;
131 | data['skype'] = this.skype;
132 | data['linkedin'] = this.linkedin;
133 | data['twitter'] = this.twitter;
134 | data['website_url'] = this.websiteUrl;
135 | data['organization'] = this.organization;
136 | data['last_sign_in_at'] = this.lastSignInAt;
137 | data['confirmed_at'] = this.confirmedAt;
138 | data['theme_id'] = this.themeId;
139 | data['last_activity_on'] = this.lastActivityOn;
140 | data['color_scheme_id'] = this.colorSchemeId;
141 | data['projects_limit'] = this.projectsLimit;
142 | data['current_sign_in_at'] = this.currentSignInAt;
143 | if (this.identities != null) {
144 | data['identities'] = this.identities.map((v) => v.toJson()).toList();
145 | }
146 | data['can_create_group'] = this.canCreateGroup;
147 | data['can_create_project'] = this.canCreateProject;
148 | data['two_factor_enabled'] = this.twoFactorEnabled;
149 | data['external'] = this.external;
150 | data['private_profile'] = this.privateProfile;
151 | return data;
152 | }
153 |
154 | Map toJsonInJobs() {
155 | final Map data = new Map();
156 | data['id'] = this.id;
157 | data['name'] = this.name;
158 | data['username'] = this.username;
159 | data['state'] = this.state;
160 | data['avatar_url'] = this.avatarUrl;
161 | data['web_url'] = this.webUrl;
162 | data['created_at'] = this.createdAt;
163 | data['bio'] = this.bio;
164 | data['location'] = this.location;
165 | data['public_email'] = this.publicEmail;
166 | data['skype'] = this.skype;
167 | data['linkedin'] = this.linkedin;
168 | data['twitter'] = this.twitter;
169 | data['website_url'] = this.websiteUrl;
170 | data['organization'] = this.organization;
171 | return data;
172 | }
173 |
174 | @override
175 | String toString() {
176 | return 'User{id: $id, username: $username, name: $name}';
177 | }
178 | }
179 |
180 | class Identities {
181 | late String provider;
182 | late String externUid;
183 |
184 | Identities({required this.provider, required this.externUid});
185 |
186 | Identities.fromJson(Map json) {
187 | provider = json['provider'];
188 | externUid = json['extern_uid'];
189 | }
190 |
191 | Map toJson() {
192 | final Map data = new Map();
193 | data['provider'] = this.provider;
194 | data['extern_uid'] = this.externUid;
195 | return data;
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/lib/providers/package_info.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/util/exception_capture.dart';
2 | import 'package:flutter/widgets.dart';
3 | import 'package:package_info/package_info.dart';
4 |
5 | class PackageInfoProvider extends ChangeNotifier {
6 | PackageInfo _packageInfo =
7 | PackageInfo(appName: "", packageName: "", version: "", buildNumber: "");
8 |
9 | PackageInfo get packageInfo {
10 | if (_packageInfo.appName.isEmpty) {
11 | sentry.captureException(Exception("PackageInfo get null appName"));
12 | _packageInfo = PackageInfo(
13 | appName: "",
14 | packageName: _packageInfo.packageName,
15 | version: _packageInfo.version,
16 | buildNumber: _packageInfo.buildNumber);
17 | }
18 | return _packageInfo;
19 | }
20 |
21 | PackageInfoProvider() {
22 | _loadPackageInfo();
23 | }
24 |
25 | _loadPackageInfo() async {
26 | _packageInfo = await PackageInfo.fromPlatform();
27 | notifyListeners();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/providers/theme.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/widgets.dart';
3 | import 'package:shared_preferences/shared_preferences.dart';
4 |
5 | import '../const.dart';
6 |
7 | class ThemeProvider with ChangeNotifier {
8 | static ThemeData _dark = ThemeData(
9 | primaryColor: Colors.black,
10 | accentColor: Colors.deepOrange,
11 | brightness: Brightness.dark);
12 |
13 | static ThemeData _light = ThemeData(
14 | primaryColor: Colors.deepOrange,
15 | accentColor: Colors.black,
16 | brightness: Brightness.light);
17 |
18 | ThemeData _currentTheme = _dark;
19 |
20 | ThemeData get currentTheme => _currentTheme;
21 |
22 | ThemeProvider() {
23 | _loadFromLocal();
24 | }
25 |
26 | void switchToDark() {
27 | _currentTheme = _dark;
28 | _updateLocal(true);
29 | notifyListeners();
30 | }
31 |
32 | void switchToLight() {
33 | _currentTheme = _light;
34 | _updateLocal(false);
35 | notifyListeners();
36 | }
37 |
38 | void _updateLocal(bool isDark) async {
39 | SharedPreferences prefs = await SharedPreferences.getInstance();
40 | prefs.setBool(KEY_THEME_IS_DARK, isDark);
41 | }
42 |
43 | void _loadFromLocal() async {
44 | SharedPreferences prefs = await SharedPreferences.getInstance();
45 | final isDark = prefs.getBool(KEY_THEME_IS_DARK) ?? true;
46 | _currentTheme = isDark ? _dark : _light;
47 | notifyListeners();
48 | }
49 |
50 | bool get isDark => currentTheme == _dark;
51 | }
52 |
--------------------------------------------------------------------------------
/lib/providers/user.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/api.dart';
2 | import 'package:F4Lab/const.dart';
3 | import 'package:F4Lab/gitlab_client.dart';
4 | import 'package:F4Lab/model/user.dart';
5 | import 'package:F4Lab/user_helper.dart';
6 | import 'package:flutter/widgets.dart';
7 | import 'package:shared_preferences/shared_preferences.dart';
8 |
9 | class UserProvider with ChangeNotifier {
10 | User? _user = UserHelper.getUser();
11 | bool _loading = false;
12 | String? _error;
13 |
14 | User? get user => _user;
15 |
16 | bool get loading => _loading;
17 |
18 | String? get error => _error;
19 |
20 | UserProvider() {
21 | _initUser();
22 | }
23 |
24 | void setUser(User u) {
25 | _user = u;
26 | _error = null;
27 | UserHelper.setUser(u);
28 | }
29 |
30 | void _initUser() async {
31 | _loading = true;
32 | notifyListeners();
33 |
34 | String? err = await UserHelper.initUser();
35 | _loading = false;
36 | _error = err;
37 | _user = UserHelper.getUser();
38 | notifyListeners();
39 | }
40 |
41 | //region config token
42 | String? _testErr;
43 | bool _testSuccess = false;
44 | bool _testing = false;
45 |
46 | String? get testErr => _testErr;
47 |
48 | bool get testSuccess => _testSuccess;
49 |
50 | bool get testing => _testing;
51 |
52 | void testConfig(String host, String token, String? version) async {
53 | _testing = true;
54 | _testSuccess = false;
55 | _testErr = null;
56 | notifyListeners();
57 |
58 | GitlabClient.setUpTokenAndHost(token, host, version ?? DEFAULT_API_VERSION);
59 | final resp = await ApiService.getAuthUser();
60 | if (resp.success && resp.data != null) {
61 | _testSuccess = true;
62 | _testErr = null;
63 | final SharedPreferences sp = await SharedPreferences.getInstance();
64 | sp.setString(KEY_ACCESS_TOKEN, token);
65 | sp.setString(KEY_HOST, host);
66 | sp.setString(KEY_API_VERSION, version ?? DEFAULT_API_VERSION);
67 | setUser(resp.data!);
68 | } else {
69 | _testSuccess = false;
70 | _testErr = resp.err ?? "Error";
71 | }
72 | _testing = false;
73 | notifyListeners();
74 | }
75 |
76 | void resetTestState() {
77 | _testSuccess = false;
78 | _testing = false;
79 | _testErr = null;
80 | }
81 |
82 | // endregion
83 |
84 | void logOut() async {
85 | final sp = await SharedPreferences.getInstance();
86 | sp.remove(KEY_HOST);
87 | sp.remove(KEY_ACCESS_TOKEN);
88 | sp.remove(KEY_API_VERSION);
89 | _user = null;
90 | notifyListeners();
91 | }
92 |
93 | @override
94 | String toString() {
95 | return 'UserProvider{user: $_user, loading: $_loading, error: $_error, configError: $_testErr, testSuccess: $_testSuccess, testing: $_testing}';
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/lib/ui/activity/activity_tab.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:F4Lab/gitlab_client.dart';
4 | import 'package:F4Lab/util/widget_util.dart';
5 | import 'package:F4Lab/widget/comm_ListView.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:flutter/widgets.dart';
8 | import 'package:xml/xml.dart';
9 |
10 | class TabActivity extends CommListWidget {
11 | TabActivity() : super(canPullUp: false);
12 |
13 | @override
14 | State createState() => FeedState();
15 | }
16 |
17 | class FeedState extends CommListState {
18 | @override
19 | loadData({nextPage: 1}) async {
20 | final url = "dashboard/projects.atom";
21 | final client = GitlabClient.newInstance();
22 | final data = await client.getRss(url).then((resp) {
23 | final data = utf8.decode(resp.bodyBytes);
24 | final XmlDocument doc = parse(data);
25 | var entries = doc.findAllElements("entry");
26 | final feeds = entries.map((ele) {
27 | return {
28 | 'title': ele.findElements("title").single.text,
29 | 'updated': ele.findElements('updated').single.text,
30 | 'link': ele.findElements('link').single.getAttribute("href"),
31 | 'avatar':
32 | ele.findElements('media:thumbnail').single.getAttribute('url')
33 | };
34 | });
35 | return feeds.toList();
36 | }).whenComplete(client.close);
37 | return data;
38 | }
39 |
40 | @override
41 | Widget childBuild(BuildContext context, int index) {
42 | final item = data[index];
43 | return Card(
44 | child: ListTile(
45 | leading: loadAvatar(item['avatar'], item['title']),
46 | title: Text(item['title']),
47 | onTap: () {},
48 | ),
49 | );
50 | }
51 |
52 | @override
53 | String? endPoint() => null;
54 | }
55 |
--------------------------------------------------------------------------------
/lib/ui/config/config_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/const.dart';
2 | import 'package:F4Lab/gitlab_client.dart';
3 | import 'package:F4Lab/providers/user.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:provider/provider.dart';
6 | import 'package:shared_preferences/shared_preferences.dart';
7 |
8 | class ConfigPage extends StatefulWidget {
9 | @override
10 | State createState() => _ConfigState();
11 | }
12 |
13 | class _ConfigState extends State {
14 | String? _token, _host, _version;
15 | late UserProvider userProvider;
16 |
17 | @override
18 | void initState() {
19 | super.initState();
20 | _loadConfig();
21 | }
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | userProvider = Provider.of(context);
26 | if (userProvider.testSuccess) {
27 | userProvider.resetTestState();
28 | //TODO: delay pop to wait build finish, maybe have a better way
29 | Future.delayed(Duration(milliseconds: 300), () => Navigator.pop(context));
30 | }
31 | return Scaffold(
32 | appBar: AppBar(
33 | title: Text("Config"),
34 | elevation: 0,
35 | backgroundColor: Theme.of(context).primaryColor,
36 | ),
37 | body: Builder(
38 | builder: (context) {
39 | return Padding(
40 | padding: EdgeInsets.all(10),
41 | child: Column(
42 | mainAxisSize: MainAxisSize.min,
43 | children: [
44 | TextField(
45 | decoration: InputDecoration(
46 | hintText: _token ?? "Access Token:",
47 | helperText:
48 | "You can create personal access token from your GitLab profile."),
49 | textInputAction: TextInputAction.next,
50 | keyboardType: TextInputType.url,
51 | onChanged: (token) => _token = token,
52 | ),
53 | TextField(
54 | decoration: InputDecoration(
55 | hintText: _host ?? "GitLab Host:",
56 | helperText: "Like https://gitlab.example.com"),
57 | textInputAction: TextInputAction.done,
58 | keyboardType: TextInputType.url,
59 | onChanged: (host) => _host = host,
60 | ),
61 | TextField(
62 | decoration: InputDecoration(
63 | hintText: _version ?? "Your gitlab api version",
64 | helperText: "Api version, default v4"),
65 | textInputAction: TextInputAction.done,
66 | keyboardType: TextInputType.text,
67 | maxLines: 1,
68 | onChanged: (v) => _version = v,
69 | ),
70 | userProvider.testErr != null
71 | ? Text(userProvider.testErr ?? "",
72 | style: TextStyle(color: Colors.red))
73 | : const IgnorePointer(ignoring: true),
74 | userProvider.testing
75 | ? Column(
76 | children: [
77 | CircularProgressIndicator(),
78 | Text("Test connectiong")
79 | ],
80 | )
81 | : const IgnorePointer(ignoring: true),
82 | ],
83 | ));
84 | },
85 | ),
86 | bottomSheet: BottomSheet(
87 | onClosing: () {},
88 | builder: (context) {
89 | return Padding(
90 | padding: EdgeInsets.only(bottom: 50),
91 | child: Row(
92 | children: [
93 | Expanded(
94 | child: OutlinedButton(
95 | child: Text("Test&Save"),
96 | onPressed: () {
97 | if (_token == null || _host == null) {
98 | return;
99 | }
100 | _testConfig(context);
101 | },
102 | ),
103 | flex: 2,
104 | ),
105 | Expanded(
106 | child: OutlinedButton(
107 | child: Text("Reset"),
108 | onPressed: () => _reset(),
109 | ),
110 | flex: 1,
111 | ),
112 | ],
113 | ));
114 | }),
115 | );
116 | }
117 |
118 | void _testConfig(BuildContext context) {
119 | userProvider.testConfig(_host!, _token!, _version);
120 | }
121 |
122 | void _loadConfig() async {
123 | final SharedPreferences sp = await SharedPreferences.getInstance();
124 | setState(() {
125 | _token = sp.getString(KEY_ACCESS_TOKEN);
126 | _host = sp.getString(KEY_HOST);
127 | _version = sp.getString(KEY_API_VERSION) ?? DEFAULT_API_VERSION;
128 | });
129 | }
130 |
131 | void _reset() {
132 | Navigator.pop(context);
133 | userProvider.logOut();
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/lib/ui/group/groups_tab.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/model/group.dart';
2 | import 'package:F4Lab/widget/comm_ListView.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class TabGroups extends CommListWidget {
6 | TabGroups() : super(canPullUp: false, withPage: false);
7 |
8 | @override
9 | State createState() => _State();
10 | }
11 |
12 | class _State extends CommListState {
13 | @override
14 | Widget build(BuildContext context) {
15 | return data != null
16 | ? GridView.builder(
17 | itemCount: data.length,
18 | gridDelegate:
19 | SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
20 | itemBuilder: (context, index) {
21 | return childBuild(context, index);
22 | }).build(context)
23 | : super.build(context);
24 | }
25 |
26 | @override
27 | Widget childBuild(BuildContext context, int index) {
28 | final item = data[index];
29 | return _buildItem(item);
30 | }
31 |
32 | Widget _buildItem(item) {
33 | final group = Group.fromJson(item);
34 | return Card(
35 | child: InkWell(
36 | onTap: () {
37 | Scaffold.of(context).hideCurrentSnackBar();
38 | Scaffold.of(context).showSnackBar(
39 | SnackBar(
40 | content: Text("${group.name}: ${group.description}"),
41 | backgroundColor: Theme.of(context).primaryColor,
42 | ),
43 | );
44 | },
45 | child: Center(
46 | child: Padding(
47 | padding: EdgeInsets.all(10),
48 | child: CircleAvatar(
49 | radius: 40,
50 | child: Text(
51 | group.name,
52 | textAlign: TextAlign.center,
53 | overflow: TextOverflow.fade,
54 | ),
55 | ),
56 | ),
57 | ),
58 | ),
59 | );
60 | }
61 |
62 | @override
63 | String endPoint() => "groups";
64 | }
65 |
--------------------------------------------------------------------------------
/lib/ui/home_nav.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/const.dart';
2 | import 'package:F4Lab/model/user.dart';
3 | import 'package:F4Lab/providers/package_info.dart';
4 | import 'package:F4Lab/providers/theme.dart';
5 | import 'package:F4Lab/providers/user.dart';
6 | import 'package:F4Lab/ui/activity/activity_tab.dart';
7 | import 'package:F4Lab/ui/group/groups_tab.dart';
8 | import 'package:F4Lab/ui/project/project_tabs.dart';
9 | import 'package:F4Lab/util/widget_util.dart';
10 | import 'package:flutter/material.dart';
11 | import 'package:flutter/widgets.dart';
12 | import 'package:provider/provider.dart';
13 | import 'package:shared_preferences/shared_preferences.dart';
14 | import 'package:url_launcher/url_launcher.dart';
15 |
16 | class HomeNav extends StatefulWidget {
17 | @override
18 | State createState() => _State();
19 | }
20 |
21 | class TabItem {
22 | final IconData icon;
23 | final String name;
24 | final WidgetBuilder builder;
25 |
26 | TabItem(this.icon, this.name, this.builder);
27 | }
28 |
29 | class _State extends State {
30 | int _currentTab = 0;
31 | List _items = [];
32 |
33 | @override
34 | void initState() {
35 | super.initState();
36 | _items = [
37 | TabItem(Icons.category, "Project", (_) => TabProject()),
38 | TabItem(Icons.today, "Activity", (_) => TabActivity()),
39 | TabItem(Icons.group, "Groups", (_) => TabGroups())
40 | ];
41 | _loadNavIndexFromLocal();
42 | }
43 |
44 | @override
45 | Widget build(BuildContext context) {
46 | final themeProvider = Provider.of(context, listen: false);
47 | final userProvider = Provider.of(context, listen: false);
48 | final user = userProvider.user;
49 | final tabs = _items.map((item) => item.builder(context)).toList();
50 | return Scaffold(
51 | appBar: AppBar(title: Text(_items[_currentTab].name)),
52 | drawer: _buildNav(context, user, themeProvider, _items),
53 | body: Builder(builder: (context) {
54 | return IndexedStack(index: _currentTab, children: tabs);
55 | }),
56 | );
57 | }
58 |
59 | Drawer _buildNav(BuildContext context, User? user,
60 | ThemeProvider themeProvider, List items) {
61 | List widgets = [];
62 |
63 | final header = UserAccountsDrawerHeader(
64 | decoration:
65 | BoxDecoration(color: Theme.of(context).scaffoldBackgroundColor),
66 | accountName: Text(
67 | user?.name ?? "",
68 | style: TextStyle(
69 | color: Theme.of(context).accentColor,
70 | ),
71 | ),
72 | accountEmail: Text(
73 | user?.email ?? "",
74 | style: TextStyle(
75 | color: Theme.of(context).highlightColor,
76 | ),
77 | ),
78 | currentAccountPicture: loadAvatar(user?.avatarUrl, user?.name),
79 | );
80 | final tabs = _buildTabNav(items, context);
81 | final config = ListTile(
82 | leading: Icon(Icons.settings),
83 | title: Text("Config"),
84 | onTap: () => _navigateToConfig(context),
85 | );
86 |
87 | final packageInfoProvider = Provider.of(context);
88 | final about = AboutListTile(
89 | icon: Icon(Icons.apps),
90 | applicationName: packageInfoProvider.packageInfo.appName,
91 | applicationVersion: packageInfoProvider.packageInfo.version,
92 | applicationLegalese: APP_LEGEND,
93 | applicationIcon: Image.network(
94 | APP_ICON_URL,
95 | width: 60,
96 | height: 60,
97 | ),
98 | aboutBoxChildren: [
99 | OutlinedButton(
100 | child: Text("FeedBack"),
101 | onPressed: () => launch(APP_FEED_BACK_URL),
102 | ),
103 | OutlinedButton(
104 | child: Text("See in GitHub"),
105 | onPressed: () => launch(APP_REPO_URL),
106 | )
107 | ],
108 | );
109 | final footer = Padding(
110 | padding: EdgeInsets.only(left: 20),
111 | child: Row(
112 | children: [
113 | Text("Dark Theme"),
114 | Switch(
115 | onChanged: (isDark) => _changeTheme(isDark, themeProvider),
116 | value: themeProvider.isDark,
117 | )
118 | ],
119 | ),
120 | );
121 |
122 | widgets.add(header);
123 | widgets.addAll(tabs);
124 | widgets.add(config);
125 | widgets.add(about);
126 | widgets.add(footer);
127 | return Drawer(child: ListView(children: widgets));
128 | }
129 |
130 | List _buildTabNav(List items, BuildContext context) {
131 | return items.map((item) {
132 | final index = items.indexOf(item);
133 | return ListTile(
134 | selected: _currentTab == index,
135 | leading: Icon(item.icon),
136 | title: Text(item.name),
137 | onTap: () => _switchTab(item, index, context),
138 | );
139 | }).toList();
140 | }
141 |
142 | void _changeTheme(bool isDark, ThemeProvider themeProvider) {
143 | if (isDark) {
144 | themeProvider.switchToDark();
145 | } else {
146 | themeProvider.switchToLight();
147 | }
148 | }
149 |
150 | void _switchTab(TabItem item, int index, BuildContext context) {
151 | Navigator.of(context).pop();
152 | setState(() => _currentTab = index);
153 | _saveNavIndexToLocal(index);
154 | }
155 |
156 | void _navigateToConfig(BuildContext context) {
157 | Navigator.pop(context);
158 | Navigator.pushNamed(context, '/config');
159 | }
160 |
161 | void _saveNavIndexToLocal(int tabIndex) async {
162 | final sp = await SharedPreferences.getInstance();
163 | sp.setInt(KEY_TAB_INDEX, tabIndex);
164 | }
165 |
166 | void _loadNavIndexFromLocal() {
167 | SharedPreferences.getInstance()
168 | .then((sp) => sp.getInt(KEY_TAB_INDEX) ?? 0)
169 | .then((index) {
170 | if (mounted) {
171 | setState(() => _currentTab = index);
172 | }
173 | });
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/lib/ui/home_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/providers/package_info.dart';
2 | import 'package:F4Lab/providers/user.dart';
3 | import 'package:F4Lab/ui/home_nav.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:provider/provider.dart';
6 |
7 | class HomePage extends StatelessWidget {
8 | @override
9 | Widget build(BuildContext context) {
10 | final userProvider = Provider.of(context);
11 | if (userProvider.loading) {
12 | return _buildLoading();
13 | }
14 | if (userProvider.user == null) {
15 | return _buildWelcome(context);
16 | }
17 | return HomeNav();
18 | }
19 |
20 | Widget _buildLoading() =>
21 | Scaffold(body: Center(child: CircularProgressIndicator()));
22 |
23 | Widget _buildWelcome(BuildContext context) {
24 | final provider = Provider.of(context);
25 | return Scaffold(
26 | body: Container(
27 | color: Theme.of(context).primaryColor,
28 | child: Center(
29 | child: Column(
30 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
31 | crossAxisAlignment: CrossAxisAlignment.center,
32 | children: [
33 | Padding(
34 | padding: EdgeInsets.only(top: 100),
35 | child: Text(
36 | provider.packageInfo.appName,
37 | style: TextStyle(
38 | color: Theme.of(context).accentColor,
39 | fontStyle: FontStyle.italic,
40 | fontWeight: FontWeight.bold,
41 | fontSize: 36,
42 | shadows: [
43 | Shadow(
44 | offset: Offset(0, 5),
45 | color: Theme.of(context).accentColor,
46 | blurRadius: 20)
47 | ]),
48 | ),
49 | ),
50 | Padding(
51 | padding: EdgeInsets.only(bottom: 100),
52 | child: Column(children: [
53 | Text("👇", style: TextStyle(fontSize: 50)),
54 | OutlineButton(
55 | onPressed: () => _navigateToConfig(context),
56 | child: Text("Config Access_Token & Host"),
57 | ),
58 | ]),
59 | )
60 | ],
61 | ),
62 | ),
63 | ));
64 | }
65 |
66 | void _navigateToConfig(BuildContext context) {
67 | Navigator.pushNamed(context, '/config');
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/lib/ui/project/jobs/jobs_tab.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/api.dart';
2 | import 'package:F4Lab/model/jobs.dart';
3 | import 'package:F4Lab/widget/comm_ListView.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/widgets.dart';
6 |
7 | class JobsTab extends CommListWidget {
8 | final int projectId;
9 |
10 | JobsTab(this.projectId);
11 |
12 | @override
13 | State createState() => _State();
14 | }
15 |
16 | class _State extends CommListState {
17 | @override
18 | Widget childBuild(BuildContext context, int index) {
19 | return JobWidget(job: Jobs.fromJson(data[index]));
20 | }
21 |
22 | @override
23 | String endPoint() => ApiEndPoint.projectJobs(widget.projectId);
24 | }
25 |
26 | class JobWidget extends StatelessWidget {
27 | final Jobs job;
28 |
29 | const JobWidget({Key? key, required this.job}) : super(key: key);
30 |
31 | @override
32 | Widget build(BuildContext context) {
33 | return Card(child: Padding(padding: EdgeInsets.all(8), child: _item(job)));
34 | }
35 |
36 | Widget _item(Jobs job) {
37 | return Column(
38 | crossAxisAlignment: CrossAxisAlignment.start,
39 | children: [
40 | Text(job.commit?.title ?? "",
41 | style: TextStyle(fontWeight: FontWeight.bold)),
42 | Row(
43 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
44 | children: [
45 | Text("#${job.id} ${job.ref}"),
46 | Row(
47 | children: [
48 | Padding(
49 | child: Text(job.status),
50 | padding: EdgeInsets.all(3),
51 | ),
52 | statusIcons.containsKey(job.status)
53 | ? statusIcons[job.status]
54 | : Icon(Icons.error, size: 18)
55 | ],
56 | )
57 | ],
58 | ),
59 | Row(
60 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
61 | children: [
62 | Text(job.stage),
63 | Text(job.name),
64 | ],
65 | ),
66 | ],
67 | );
68 | }
69 |
70 | static get statusIcons => {
71 | "success": Icon(Icons.check_circle, color: Colors.green, size: 18),
72 | "manual": Icon(Icons.build, size: 18),
73 | "failed": Icon(Icons.error_outline, color: Colors.redAccent, size: 18),
74 | "skipped": Icon(Icons.skip_next, size: 18),
75 | "canceled": Icon(Icons.cancel, size: 18)
76 | };
77 | }
78 |
--------------------------------------------------------------------------------
/lib/ui/project/mr/approve.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/api.dart';
2 | import 'package:F4Lab/model/approvals.dart';
3 | import 'package:F4Lab/user_helper.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/widgets.dart';
6 |
7 | class MrApprove extends StatefulWidget {
8 | final int projectId;
9 | final int mrIID;
10 | final bool showActions;
11 |
12 | MrApprove(this.projectId, this.mrIID, {this.showActions = false});
13 |
14 | @override
15 | State createState() => _MrApproveState();
16 | }
17 |
18 | class _MrApproveState extends State {
19 | Approvals? approval;
20 | bool isApproving = false;
21 |
22 | void _loadApprove() async {
23 | final apiResp =
24 | await ApiService.mrApproveData(widget.projectId, widget.mrIID);
25 | if (mounted) {
26 | setState(() {
27 | approval = apiResp.data;
28 | });
29 | }
30 | }
31 |
32 | @override
33 | void initState() {
34 | super.initState();
35 | _loadApprove();
36 | }
37 |
38 | @override
39 | Widget build(BuildContext context) {
40 | if (approval == null || isApproving) {
41 | return LinearProgressIndicator();
42 | }
43 | return _buildItem(approval);
44 | }
45 |
46 | Widget _buildItem(Approvals? ap) {
47 | int requireApproves = ap?.approvalsRequired ?? 0;
48 | int? hadApproval = ap?.approvedBy != null ? ap?.approvedBy.length : 0;
49 | bool allApproval = requireApproves == hadApproval;
50 | bool iHadApproval = ap?.approvedBy != null
51 | ? ap!.approvedBy.any((item) => item.user?.id == UserHelper.getUser()?.id)
52 | : false;
53 |
54 | return Card(
55 | child: Padding(
56 | padding: EdgeInsets.all(5),
57 | child: Row(
58 | children: [
59 | Icon(Icons.sentiment_neutral),
60 | Expanded(
61 | child: Text(
62 | "Approvals: $hadApproval of $requireApproves",
63 | style:
64 | TextStyle(color: allApproval ? Colors.green : Colors.grey),
65 | ),
66 | ),
67 | widget.showActions ? _buildActions(iHadApproval) : IgnorePointer(),
68 | ],
69 | ),
70 | ),
71 | );
72 | }
73 |
74 | Widget _buildActions(bool iHadApproval) {
75 | return OutlineButton(
76 | onPressed: () => _approveOrUnApprove(!iHadApproval),
77 | child: Text(iHadApproval ? "UnApprove" : "Approve"),
78 | );
79 | }
80 |
81 | void _approveOrUnApprove(bool isApprove) async {
82 | setState(() {
83 | isApproving = true;
84 | });
85 | final ApiResp apiData =
86 | await ApiService.approve(widget.projectId, widget.mrIID, isApprove);
87 | setState(() {
88 | isApproving = false;
89 | });
90 | if (apiData.success) {
91 | _loadApprove();
92 | } else {
93 | ScaffoldMessenger.of(context).showSnackBar(
94 | SnackBar(
95 | content: Text("${apiData.err}"),
96 | backgroundColor: Colors.red,
97 | ),
98 | );
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/lib/ui/project/mr/commit_diff.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/api.dart';
2 | import 'package:F4Lab/model/commit.dart';
3 | import 'package:F4Lab/model/diff.dart';
4 | import 'package:F4Lab/ui/project/mr/diff.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | class PageCommitDiff extends StatefulWidget {
8 | final int projectId;
9 | final Commit commit;
10 |
11 | const PageCommitDiff(this.projectId, this.commit);
12 |
13 | @override
14 | State createState() => _DiffState();
15 | }
16 |
17 | class _DiffState extends State {
18 | List diffs = [];
19 | double _codeFontSize = 14.0;
20 |
21 | _loadDiffs() async {
22 | final resp =
23 | await ApiService.commitDiff(widget.projectId, widget.commit.id);
24 | if (resp.success) {
25 | if (mounted) {
26 | setState(() => diffs = resp.data ?? []);
27 | }
28 | }
29 | }
30 |
31 | @override
32 | void initState() {
33 | super.initState();
34 | _loadDiffs();
35 | }
36 |
37 | _buildFontControl() {
38 | return Row(
39 | children: [
40 | Text(
41 | "Code Size: $_codeFontSize",
42 | ),
43 | OutlinedButton(
44 | child: Text("-"),
45 | onPressed: () => setState(() => _codeFontSize -= 1),
46 | ),
47 | OutlinedButton(
48 | child: Text("+"),
49 | onPressed: () => setState(() => _codeFontSize += 1),
50 | )
51 | ],
52 | );
53 | }
54 |
55 | _buildDiff(Diff item) {
56 | return Card(
57 | child: ExpansionTile(
58 | title: Column(
59 | crossAxisAlignment: CrossAxisAlignment.start,
60 | children: [
61 | item.newFile
62 | ? IgnorePointer()
63 | : Text(
64 | item.oldPath,
65 | style: TextStyle(color: Colors.red),
66 | ),
67 | item.deletedFile
68 | ? IgnorePointer()
69 | : Text(
70 | item.newPath,
71 | style: TextStyle(color: Colors.green),
72 | ),
73 | ],
74 | ),
75 | children: [
76 | _buildFontControl(),
77 | Divider(color: Colors.grey),
78 | SingleChildScrollView(
79 | scrollDirection: Axis.horizontal,
80 | child: Column(
81 | crossAxisAlignment: CrossAxisAlignment.start,
82 | children: diffToText(item.diff, Colors.red, Colors.green, Colors.black,
83 | fontSize: _codeFontSize),
84 | ),
85 | ),
86 | ]));
87 | }
88 |
89 | @override
90 | Widget build(BuildContext context) {
91 | return Scaffold(
92 | appBar: AppBar(
93 | title: Text(widget.commit.title),
94 | ),
95 | body: diffs == null
96 | ? Center(child: CircularProgressIndicator())
97 | : ListView.builder(
98 | itemCount: diffs.length,
99 | itemBuilder: (context, index) {
100 | final item = diffs[index];
101 | return _buildDiff(item);
102 | }),
103 | );
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/lib/ui/project/mr/diff.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | class CodeDiff extends StatelessWidget {
4 | final String diff;
5 | final Color deleteColor;
6 | final Color addColor;
7 | final Color normalColor;
8 |
9 | const CodeDiff(this.diff, this.deleteColor, this.addColor, this.normalColor,
10 | {Key? key})
11 | : super(key: key);
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Column(
16 | crossAxisAlignment: CrossAxisAlignment.start,
17 | children: diffToText(diff, deleteColor, addColor, normalColor),
18 | );
19 | }
20 | }
21 |
22 | List diffToText(
23 | String diff, Color deleteColor, Color addColor, Color normalColor,
24 | {double? fontSize}) {
25 | final lines = diff.split("\n");
26 | return lines.map((line) {
27 | final remove = line.indexOf("-") == 0;
28 | final add = line.indexOf("+") == 0;
29 | if (remove || add) {
30 | line = " " + line.substring(1, line.length);
31 | }
32 | final style = TextStyle(
33 | color: remove ? deleteColor : (add ? addColor : normalColor),
34 | fontSize: fontSize);
35 | return Text(
36 | line,
37 | style: style,
38 | );
39 | }).toList();
40 | }
41 |
--------------------------------------------------------------------------------
/lib/ui/project/mr/merge_request_action.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/api.dart';
2 | import 'package:F4Lab/model/merge_request.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter/widgets.dart';
5 |
6 | class MergeRequestAction extends StatefulWidget {
7 | final MergeRequest mr;
8 | final ValueChanged mergeRequestChange;
9 |
10 | const MergeRequestAction(this.mr, this.mergeRequestChange);
11 |
12 | @override
13 | State createState() => _MergeReqestState();
14 | }
15 |
16 | class _MergeReqestState extends State {
17 | bool _removeBranch = false;
18 | bool _mergeWhenPiplineSuccess = false;
19 | bool _squashCommit = false;
20 |
21 | bool _canMerge = false;
22 | bool _loading = false;
23 |
24 | @override
25 | void initState() {
26 | super.initState();
27 | _mergeWhenPiplineSuccess = widget.mr.mergeWhenPipelineSucceeds;
28 | }
29 |
30 | void _rebase() async {
31 | _showLoading();
32 | final resp = await ApiService.rebaseMr(widget.mr.projectId, widget.mr.iid);
33 | if (resp.success) {
34 | widget.mergeRequestChange(null);
35 | }
36 | _hideLoading();
37 | }
38 |
39 | void _merge() async {
40 | _showLoading();
41 | final resp = await ApiService.acceptMR(widget.mr.projectId, widget.mr.iid,
42 | shouldRemoveSourceBranch: _removeBranch,
43 | squash: _squashCommit,
44 | mergeMrWhenPipelineSuccess: _mergeWhenPiplineSuccess);
45 | if (resp.success) {
46 | widget.mergeRequestChange(null);
47 | }
48 | _hideLoading();
49 | }
50 |
51 | Widget _buildMergeButton() {
52 | final mr = widget.mr;
53 | String title;
54 | VoidCallback onPress;
55 | if (mr.divergedCommitsCount > 0) {
56 | title = mr.rebaseInProgress ? "Rebaseing" : "Rebase";
57 | onPress = mr.rebaseInProgress ? ()=>{} : _rebase;
58 | } else if (mr.workInProgress) {
59 | title = "Remove WIP(Not Suuport)";
60 | onPress = ()=>{};
61 | } else if (mr.state == "merged") {
62 | _canMerge = false;
63 | title = "Merged";
64 | onPress = ()=>{};
65 | } else {
66 | _canMerge = true;
67 | title = "Merge";
68 | onPress = _merge;
69 | }
70 | return ElevatedButton(child: Text(title), onPressed: onPress);
71 | }
72 |
73 | Widget _buildRefreshButton() {
74 | return IconButton(
75 | icon: Icon(Icons.refresh),
76 | onPressed: () {
77 | widget.mergeRequestChange(null);
78 | },
79 | );
80 | }
81 |
82 | @override
83 | Widget build(BuildContext context) {
84 | final checkBoxs = Column(
85 | children: [
86 | CheckboxListTile(
87 | value: _removeBranch,
88 | onChanged: (bool? newValue) {
89 | setState(() => _removeBranch = newValue!);
90 | },
91 | title: Text("Remove Source Branch ?"),
92 | ),
93 | CheckboxListTile(
94 | value: _mergeWhenPiplineSuccess,
95 | onChanged: (bool? newValue) {
96 | setState(() => _mergeWhenPiplineSuccess = newValue!);
97 | },
98 | title: Text("Merge When Pipeline Success ?"),
99 | ),
100 | CheckboxListTile(
101 | value: _squashCommit,
102 | onChanged: (bool? newValue) {
103 | setState(() => _squashCommit = newValue!);
104 | },
105 | title: Text("Squashed into a single commit ?"),
106 | ),
107 | ],
108 | );
109 |
110 | final actions = Row(
111 | children: [
112 | Expanded(
113 | flex: 1,
114 | child: _buildRefreshButton(),
115 | ),
116 | Expanded(
117 | flex: 1,
118 | child: _buildMergeButton(),
119 | ),
120 | ],
121 | );
122 |
123 | final widgets = [
124 | _canMerge ? checkBoxs : IgnorePointer(),
125 | actions,
126 | _loading ? LinearProgressIndicator() : IgnorePointer()
127 | ];
128 |
129 | final content = ListTile(
130 | title: const Text("Merge request actions"),
131 | subtitle: Column(children: widgets),
132 | );
133 |
134 | return Card(child: content);
135 | }
136 |
137 | void _showLoading() {
138 | setState(() => _loading = true);
139 | }
140 |
141 | void _hideLoading() {
142 | setState(() => _loading = false);
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/lib/ui/project/mr/mr_detail_tabs.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/api.dart' show ApiEndPoint;
2 | import 'package:F4Lab/model/commit.dart';
3 | import 'package:F4Lab/model/discussion.dart';
4 | import 'package:F4Lab/ui/project/mr/commit_diff.dart';
5 | import 'package:F4Lab/util/date_util.dart';
6 | import 'package:F4Lab/util/widget_util.dart';
7 | import 'package:F4Lab/widget/comm_ListView.dart';
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter/widgets.dart';
10 |
11 | class CommitTab extends CommListWidget {
12 | final int projectId;
13 | final int mrIId;
14 |
15 | CommitTab(this.projectId, this.mrIId);
16 |
17 | @override
18 | State createState() => _CommitState();
19 | }
20 |
21 | class _CommitState extends CommListState {
22 | @override
23 | Widget childBuild(BuildContext context, int index) {
24 | final Commit commit = Commit.fromJson(data[index]);
25 | return Card(
26 | child: ListTile(
27 | title: Text(commit.title),
28 | subtitle: Text(datetime2String(commit.createdAt)),
29 | onTap: () {
30 | Navigator.of(context).push(MaterialPageRoute(
31 | builder: (context) => PageCommitDiff(widget.projectId, commit)));
32 | },
33 | ),
34 | );
35 | }
36 |
37 | @override
38 | String endPoint() =>
39 | ApiEndPoint.mergeRequestCommit(widget.projectId, widget.mrIId);
40 | }
41 |
42 | class DiscussionTab extends CommListWidget {
43 | final int projectId;
44 | final int mrIId;
45 |
46 | DiscussionTab(this.projectId, this.mrIId);
47 |
48 | @override
49 | State createState() => _DiscussionState();
50 | }
51 |
52 | class _DiscussionState extends CommListState {
53 | @override
54 | Widget childBuild(BuildContext context, int index) {
55 | final Discussion discussion = Discussion.fromJson(data[index]);
56 | final List notes = [];
57 | discussion.notes.forEach((note) {
58 | if (!note.system) {
59 | notes.add(note);
60 | }
61 | });
62 |
63 | final items = notes.map((item) {
64 | return ListTile(
65 | isThreeLine: true,
66 | title: Text(item.body),
67 | subtitle: Text(item.author?.name ?? ""),
68 | leading: loadAvatar(item.author?.avatarUrl, item.author?.name),
69 | trailing: Icon(
70 | item.resolved ? Icons.check_circle : Icons.error_outline,
71 | color: item.resolved ? Colors.green : Colors.redAccent,
72 | semanticLabel: "Resolved?",
73 | ),
74 | );
75 | }).toList();
76 |
77 | return Card(
78 | child: Column(
79 | crossAxisAlignment: CrossAxisAlignment.start,
80 | children: items,
81 | ));
82 | }
83 |
84 | @override
85 | String endPoint() =>
86 | ApiEndPoint.mergeRequestDiscussion(widget.projectId, widget.mrIId);
87 |
88 | @override
89 | bool itemShouldRemove(dynamic item) =>
90 | Discussion.fromJson(item).individualNote;
91 | }
92 |
--------------------------------------------------------------------------------
/lib/ui/project/mr/mr_home.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/api.dart';
2 | import 'package:F4Lab/model/merge_request.dart';
3 | import 'package:F4Lab/ui/project/mr/approve.dart';
4 | import 'package:F4Lab/ui/project/mr/merge_request_action.dart';
5 | import 'package:F4Lab/ui/project/mr/mr_tab_jobs.dart';
6 | import 'package:F4Lab/ui/project/mr/mr_detail_tabs.dart';
7 | import 'package:flutter/material.dart';
8 |
9 | class PageMrDetail extends StatefulWidget {
10 | final int _projectId;
11 | final int _mergeRequestIId;
12 |
13 | PageMrDetail(this._projectId, this._mergeRequestIId);
14 |
15 | @override
16 | State createState() => PageMrState();
17 | }
18 |
19 | class PageMrState extends State {
20 | MergeRequest? _mr;
21 |
22 | void _onMergeRequestChange(void v) {
23 | _loadMergeRequest();
24 | }
25 |
26 | void _loadMergeRequest() async {
27 | setState(() => _mr = null);
28 | final resp = await ApiService.getSingleMR(
29 | widget._projectId, widget._mergeRequestIId);
30 | if (resp.success) {
31 | setState(() => _mr = resp.data);
32 | }
33 | }
34 |
35 | @override
36 | void initState() {
37 | super.initState();
38 | _loadMergeRequest();
39 | }
40 |
41 | @override
42 | Widget build(BuildContext context) {
43 | return DefaultTabController(
44 | length: 4,
45 | child: Scaffold(
46 | appBar: AppBar(
47 | centerTitle: false,
48 | title: Text("MR #${widget._mergeRequestIId}"),
49 | bottom: TabBar(isScrollable: true, tabs: [
50 | Tab(text: "Overview"),
51 | Tab(text: "Commits"),
52 | Tab(text: "Discussions"),
53 | Tab(text: "Jobs"),
54 | ]),
55 | ),
56 | body: _mr == null
57 | ? Center(child: CircularProgressIndicator())
58 | : TabBarView(
59 | children: [
60 | _buildInfo(),
61 | CommitTab(_mr!.projectId, _mr!.iid),
62 | DiscussionTab(_mr!.projectId, _mr!.iid),
63 | MergeRequestJobsTab(_mr!.projectId, _mr!.iid)
64 | ],
65 | ),
66 | ));
67 | }
68 |
69 | Icon _getStatusColor(String status) {
70 | switch (status) {
71 | case "can_be_merged":
72 | return Icon(Icons.check, color: Colors.green);
73 | case "cannot_be_merged":
74 | return Icon(Icons.highlight_off, color: Colors.red);
75 | default:
76 | return Icon(Icons.check, color: Colors.green);
77 | }
78 | }
79 |
80 | Widget _buildInfo() {
81 | final title = Text(
82 | _mr!.title,
83 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 26.0),
84 | );
85 |
86 | final desc =
87 | _mr?.description != null ? Text(_mr!.description) : IgnorePointer();
88 |
89 | final mrPlan = Card(
90 | child: ListTile(
91 | title: Text("Merge: ${_mr!.sourceBranch} -> ${_mr!.targetBranch}"),
92 | trailing: _getStatusColor(_mr!.mergeStatus),
93 | ),
94 | );
95 |
96 | // final approvalAction = _buildApproval(_mr!.projectId, _mr!.iid);
97 |
98 | final mrAction = MergeRequestAction(_mr!, _onMergeRequestChange);
99 |
100 | return SingleChildScrollView(
101 | padding: EdgeInsets.all(10.0),
102 | child: Column(
103 | crossAxisAlignment: CrossAxisAlignment.start,
104 | children: [
105 | title,
106 | desc,
107 | mrPlan,
108 | // approvalAction,
109 | mrAction,
110 | ],
111 | ));
112 | }
113 |
114 | Widget _buildApproval(int projectId, int mrIid) {
115 | return MrApprove(projectId, mrIid, showActions: true);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/lib/ui/project/mr/mr_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:F4Lab/api.dart';
2 | import 'package:F4Lab/model/merge_request.dart';
3 | import 'package:F4Lab/ui/project/mr/mr_list_item.dart';
4 | import 'package:F4Lab/widget/comm_ListView.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | class MRTab extends StatefulWidget {
8 | final int projectId;
9 |
10 | const MRTab(this.projectId);
11 |
12 | @override
13 | State createState() {
14 | return _State();
15 | }
16 | }
17 |
18 | class _State extends State {
19 | late String curState;
20 | late String curScope;
21 |
22 | @override
23 | void initState() {
24 | super.initState();
25 | curState = ApiEndPoint.merge_request_states[0];
26 | curScope = ApiEndPoint.merge_request_scopes[0];
27 | }
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | return Column(
32 | mainAxisSize: MainAxisSize.max,
33 | crossAxisAlignment: CrossAxisAlignment.start,
34 | children: [
35 | _buildFilter(),
36 | Expanded(
37 | child: MrTab(widget.projectId,
38 | mrState: curState,
39 | scope: curScope,
40 | key: ValueKey("$curState-$curScope"))),
41 | ]);
42 | }
43 |
44 | Widget _buildFilter() {
45 | final stateSelector = DropdownButton(
46 | value: curState,
47 | items: ApiEndPoint.merge_request_states
48 | .map>((String value) {
49 | return DropdownMenuItem(
50 | value: value,
51 | child: Text(value),
52 | );
53 | }).toList(),
54 | onChanged: (value) {
55 | if (value != curState) {
56 | setState(() {
57 | curState = value.toString();
58 | });
59 | }
60 | });
61 | final scopeSelector = DropdownButton(
62 | value: curScope,
63 | items: ApiEndPoint.merge_request_scopes
64 | .map>((String value) {
65 | return DropdownMenuItem(
66 | value: value,
67 | child: Text(value),
68 | );
69 | }).toList(),
70 | onChanged: (value) {
71 | if (value != curScope) {
72 | setState(() {
73 | curScope = value.toString();
74 | });
75 | }
76 | });
77 |
78 | return Padding(
79 | padding: EdgeInsets.all(4),
80 | child: Row(
81 | crossAxisAlignment: CrossAxisAlignment.center,
82 | children: