├── .github └── workflows │ └── lint.yml ├── .gitignore ├── .metadata ├── .prettierrc ├── .vscode ├── launch.json └── tasks.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── ic_launcher-playstore.png │ │ ├── ic_launcher-web.png │ │ ├── kotlin │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── pd4d10 │ │ │ │ └── gittouch │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ ├── ic_launcher_background.xml │ │ │ ├── launch_background.xml │ │ │ └── launch_image.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ ├── ic_launcher_background.xml │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── assets ├── google-play-badge.png ├── icons │ ├── icon.png │ ├── icon_cyberblueprint2.png │ └── icon_minimal_clean.png ├── screenshot-android.png ├── screenshot-dark.png ├── screenshot-ios.png └── screenshot-light.png ├── fastlane └── metadata │ └── android │ ├── en-US │ ├── full_description.txt │ ├── images │ │ └── phoneScreenshots │ │ │ ├── login-dark.png │ │ │ ├── login-light.png │ │ │ ├── repo-dark.png │ │ │ ├── repo-light.png │ │ │ ├── trending-dark.png │ │ │ └── trending-light.png │ └── short_description.txt │ └── zh-CN │ ├── full_description.txt │ └── short_description.txt ├── images ├── avatar-dark.png ├── avatar.png └── github-markdown.css ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon-1024.png │ │ ├── icon-20.png │ │ ├── icon-20@2x.png │ │ ├── icon-20@3x.png │ │ ├── icon-29.png │ │ ├── icon-29@2x.png │ │ ├── icon-29@3x.png │ │ ├── icon-40.png │ │ ├── icon-40@2x.png │ │ ├── icon-40@3x.png │ │ ├── icon-60@2x.png │ │ ├── icon-60@3x.png │ │ ├── icon-76.png │ │ ├── icon-76@2x.png │ │ └── icon-83.5@2x.png │ └── LaunchImage.imageset │ │ ├── 144.png │ │ ├── 216.png │ │ ├── 72.png │ │ ├── Contents.json │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── l10n.yaml ├── lib ├── app.dart ├── home.dart ├── l10n │ ├── intl_ca.arb │ ├── intl_de.arb │ ├── intl_en.arb │ ├── intl_es.arb │ ├── intl_fr.arb │ ├── intl_hi.arb │ ├── intl_hu.arb │ ├── intl_id.arb │ ├── intl_ja.arb │ ├── intl_nb.arb │ ├── intl_nb_NO.arb │ ├── intl_nl.arb │ ├── intl_pt.arb │ ├── intl_pt_BR.arb │ ├── intl_ru.arb │ ├── intl_si.arb │ ├── intl_zh.arb │ ├── intl_zh_Hans.arb │ └── intl_zh_Hant.arb ├── main.dart ├── models │ ├── account.dart │ ├── account.freezed.dart │ ├── account.g.dart │ ├── auth.dart │ ├── bitbucket.dart │ ├── bitbucket.g.dart │ ├── code.dart │ ├── gitea.dart │ ├── gitea.g.dart │ ├── gitee.dart │ ├── gitee.g.dart │ ├── github.dart │ ├── github.g.dart │ ├── gitlab.dart │ ├── gitlab.g.dart │ ├── gogs.dart │ ├── gogs.g.dart │ ├── notification.dart │ └── theme.dart ├── router.dart ├── scaffolds │ ├── common.dart │ ├── list_stateful.dart │ ├── long_list.dart │ ├── refresh_stateful.dart │ ├── single.dart │ ├── tab.dart │ ├── tab_stateful.dart │ └── utils.dart ├── screens │ ├── bb_commits.dart │ ├── bb_explore.dart │ ├── bb_issue.dart │ ├── bb_issue_comment.dart │ ├── bb_issue_form.dart │ ├── bb_issues.dart │ ├── bb_object.dart │ ├── bb_pulls.dart │ ├── bb_repo.dart │ ├── bb_teams.dart │ ├── bb_user.dart │ ├── code_theme.dart │ ├── ge_blob.dart │ ├── ge_commit.dart │ ├── ge_commits.dart │ ├── ge_contributors.dart │ ├── ge_files.dart │ ├── ge_issue.dart │ ├── ge_issue_comment.dart │ ├── ge_issue_form.dart │ ├── ge_issues.dart │ ├── ge_pull.dart │ ├── ge_pulls.dart │ ├── ge_repo.dart │ ├── ge_repos.dart │ ├── ge_search.dart │ ├── ge_tree.dart │ ├── ge_user.dart │ ├── ge_users.dart │ ├── gh_commit.dart │ ├── gh_commits.dart │ ├── gh_compare.dart │ ├── gh_contributors.dart │ ├── gh_events.dart │ ├── gh_files.dart │ ├── gh_gist_object.dart │ ├── gh_gists.dart │ ├── gh_gists_files.dart │ ├── gh_issue.dart │ ├── gh_issue_form.dart │ ├── gh_issues.dart │ ├── gh_meta.dart │ ├── gh_news.dart │ ├── gh_notification.dart │ ├── gh_object.dart │ ├── gh_pulls.dart │ ├── gh_releases.dart │ ├── gh_repo.dart │ ├── gh_repos.dart │ ├── gh_search.dart │ ├── gh_trending.dart │ ├── gh_user.dart │ ├── gh_users.dart │ ├── gl_blob.dart │ ├── gl_commit.dart │ ├── gl_commits.dart │ ├── gl_explore.dart │ ├── gl_group.dart │ ├── gl_groups.dart │ ├── gl_issue.dart │ ├── gl_issue_form.dart │ ├── gl_issues.dart │ ├── gl_members.dart │ ├── gl_merge_requests.dart │ ├── gl_project.dart │ ├── gl_project_activity.dart │ ├── gl_search.dart │ ├── gl_starrers.dart │ ├── gl_todos.dart │ ├── gl_tree.dart │ ├── gl_user.dart │ ├── go_commits.dart │ ├── go_issues.dart │ ├── go_object.dart │ ├── go_orgs.dart │ ├── go_repo.dart │ ├── go_repos.dart │ ├── go_search.dart │ ├── go_user.dart │ ├── go_users.dart │ ├── gt_commits.dart │ ├── gt_issue.dart │ ├── gt_issue_comment.dart │ ├── gt_issue_form.dart │ ├── gt_issues.dart │ ├── gt_object.dart │ ├── gt_orgs.dart │ ├── gt_repo.dart │ ├── gt_repos.dart │ ├── gt_status.dart │ ├── gt_user.dart │ ├── gt_users.dart │ ├── login.dart │ └── settings.dart ├── utils │ ├── extensions.dart │ ├── locale.dart │ └── utils.dart └── widgets │ ├── action_button.dart │ ├── action_entry.dart │ ├── avatar.dart │ ├── blob_view.dart │ ├── border_view.dart │ ├── branch_name.dart │ ├── comment_item.dart │ ├── commit_item.dart │ ├── contribution.dart │ ├── contributor_item.dart │ ├── diff_view.dart │ ├── empty.dart │ ├── entry_item.dart │ ├── error_reload.dart │ ├── event_item.dart │ ├── files_item.dart │ ├── gists_item.dart │ ├── hex_color_tag.dart │ ├── html_view.dart │ ├── issue_icon.dart │ ├── issue_item.dart │ ├── language_bar.dart │ ├── link.dart │ ├── list_group.dart │ ├── loading.dart │ ├── markdown_view.dart │ ├── mutation_button.dart │ ├── notification_item.dart │ ├── object_tree.dart │ ├── release_item.dart │ ├── repo_header.dart │ ├── repo_item.dart │ ├── text_field.dart │ ├── text_with_at.dart │ ├── timeline_item.dart │ ├── user_header.dart │ ├── user_item.dart │ └── user_name.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon-1024.png │ │ ├── icon-128.png │ │ ├── icon-16.png │ │ ├── icon-256.png │ │ ├── icon-32.png │ │ ├── icon-512.png │ │ └── icon-64.png │ ├── Base.lproj │ └── MainMenu.xib │ ├── Configs │ ├── AppInfo.xcconfig │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements ├── makefile ├── packages ├── github_trending │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── example │ │ └── github_trending_example.dart │ ├── lib │ │ ├── github_trending.dart │ │ └── src │ │ │ ├── api.dart │ │ │ ├── model.dart │ │ │ └── model.g.dart │ ├── pubspec.yaml │ └── test │ │ └── github_trending_test.dart ├── gql_github │ ├── build.yaml │ ├── lib │ │ ├── commits.ast.gql.dart │ │ ├── commits.data.gql.dart │ │ ├── commits.data.gql.g.dart │ │ ├── commits.graphql │ │ ├── commits.req.gql.dart │ │ ├── commits.req.gql.g.dart │ │ ├── commits.var.gql.dart │ │ ├── commits.var.gql.g.dart │ │ ├── gist.ast.gql.dart │ │ ├── gist.data.gql.dart │ │ ├── gist.data.gql.g.dart │ │ ├── gist.graphql │ │ ├── gist.req.gql.dart │ │ ├── gist.req.gql.g.dart │ │ ├── gist.var.gql.dart │ │ ├── gist.var.gql.g.dart │ │ ├── gists.ast.gql.dart │ │ ├── gists.data.gql.dart │ │ ├── gists.data.gql.g.dart │ │ ├── gists.graphql │ │ ├── gists.req.gql.dart │ │ ├── gists.req.gql.g.dart │ │ ├── gists.var.gql.dart │ │ ├── gists.var.gql.g.dart │ │ ├── issue.ast.gql.dart │ │ ├── issue.data.gql.dart │ │ ├── issue.data.gql.g.dart │ │ ├── issue.graphql │ │ ├── issue.req.gql.dart │ │ ├── issue.req.gql.g.dart │ │ ├── issue.var.gql.dart │ │ ├── issue.var.gql.g.dart │ │ ├── issues.ast.gql.dart │ │ ├── issues.data.gql.dart │ │ ├── issues.data.gql.g.dart │ │ ├── issues.graphql │ │ ├── issues.req.gql.dart │ │ ├── issues.req.gql.g.dart │ │ ├── issues.var.gql.dart │ │ ├── issues.var.gql.g.dart │ │ ├── meta.ast.gql.dart │ │ ├── meta.data.gql.dart │ │ ├── meta.data.gql.g.dart │ │ ├── meta.graphql │ │ ├── meta.req.gql.dart │ │ ├── meta.req.gql.g.dart │ │ ├── meta.var.gql.dart │ │ ├── meta.var.gql.g.dart │ │ ├── releases.ast.gql.dart │ │ ├── releases.data.gql.dart │ │ ├── releases.data.gql.g.dart │ │ ├── releases.graphql │ │ ├── releases.req.gql.dart │ │ ├── releases.req.gql.g.dart │ │ ├── releases.var.gql.dart │ │ ├── releases.var.gql.g.dart │ │ ├── repo.ast.gql.dart │ │ ├── repo.data.gql.dart │ │ ├── repo.data.gql.g.dart │ │ ├── repo.graphql │ │ ├── repo.req.gql.dart │ │ ├── repo.req.gql.g.dart │ │ ├── repo.var.gql.dart │ │ ├── repo.var.gql.g.dart │ │ ├── repos.ast.gql.dart │ │ ├── repos.data.gql.dart │ │ ├── repos.data.gql.g.dart │ │ ├── repos.graphql │ │ ├── repos.req.gql.dart │ │ ├── repos.req.gql.g.dart │ │ ├── repos.var.gql.dart │ │ ├── repos.var.gql.g.dart │ │ ├── schema.ast.gql.dart │ │ ├── schema.graphql │ │ ├── schema.schema.gql.dart │ │ ├── schema.schema.gql.g.dart │ │ ├── serializers.gql.dart │ │ ├── serializers.gql.g.dart │ │ ├── user.ast.gql.dart │ │ ├── user.data.gql.dart │ │ ├── user.data.gql.g.dart │ │ ├── user.graphql │ │ ├── user.req.gql.dart │ │ ├── user.req.gql.g.dart │ │ ├── user.var.gql.dart │ │ ├── user.var.gql.g.dart │ │ ├── users.ast.gql.dart │ │ ├── users.data.gql.dart │ │ ├── users.data.gql.g.dart │ │ ├── users.graphql │ │ ├── users.req.gql.dart │ │ ├── users.req.gql.g.dart │ │ ├── users.var.gql.dart │ │ ├── users.var.gql.g.dart │ │ └── utils │ │ │ └── date_time_serializer.dart │ ├── pubspec.lock │ └── pubspec.yaml └── gql_gitlab │ ├── build.yaml │ ├── lib │ ├── project.ast.gql.dart │ ├── project.data.gql.dart │ ├── project.data.gql.g.dart │ ├── project.graphql │ ├── project.req.gql.dart │ ├── project.req.gql.g.dart │ ├── project.var.gql.dart │ ├── project.var.gql.g.dart │ ├── schema.ast.gql.dart │ ├── schema.graphql │ ├── schema.schema.gql.dart │ ├── schema.schema.gql.g.dart │ ├── serializers.gql.dart │ └── serializers.gql.g.dart │ ├── pubspec.lock │ └── pubspec.yaml ├── pubspec.lock ├── pubspec.yaml ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html └── manifest.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: subosito/flutter-action@v1 15 | with: 16 | channel: stable 17 | - run: flutter analyze 18 | - run: flutter format --dry-run --set-exit-if-changed lib/**/*.dart 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | 49 | packages/*/pubspec.lock 50 | -------------------------------------------------------------------------------- /.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. 5 | 6 | version: 7 | revision: 676cefaaff197f27424942307668886253e1ec35 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 676cefaaff197f27424942307668886253e1ec35 17 | base_revision: 676cefaaff197f27424942307668886253e1ec35 18 | - platform: android 19 | create_revision: 676cefaaff197f27424942307668886253e1ec35 20 | base_revision: 676cefaaff197f27424942307668886253e1ec35 21 | - platform: ios 22 | create_revision: 676cefaaff197f27424942307668886253e1ec35 23 | base_revision: 676cefaaff197f27424942307668886253e1ec35 24 | - platform: linux 25 | create_revision: 676cefaaff197f27424942307668886253e1ec35 26 | base_revision: 676cefaaff197f27424942307668886253e1ec35 27 | - platform: macos 28 | create_revision: 676cefaaff197f27424942307668886253e1ec35 29 | base_revision: 676cefaaff197f27424942307668886253e1ec35 30 | - platform: web 31 | create_revision: 676cefaaff197f27424942307668886253e1ec35 32 | base_revision: 676cefaaff197f27424942307668886253e1ec35 33 | - platform: windows 34 | create_revision: 676cefaaff197f27424942307668886253e1ec35 35 | base_revision: 676cefaaff197f27424942307668886253e1ec35 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "proseWrap": "never" 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Flutter", 9 | "request": "launch", 10 | "type": "dart" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "all", 6 | "dependsOn": ["root", "gql_github", "gql_gitlab", "github_trending"] 7 | }, 8 | { 9 | "label": "root", 10 | "type": "dart", 11 | "command": "dart", 12 | "args": ["run", "build_runner", "watch"] 13 | }, 14 | { 15 | "label": "gql_github", 16 | "type": "dart", 17 | "command": "dart", 18 | "cwd": "packages/gql_github", 19 | "args": ["run", "build_runner", "watch"] 20 | }, 21 | { 22 | "label": "gql_gitlab", 23 | "type": "dart", 24 | "command": "dart", 25 | "cwd": "packages/gql_gitlab", 26 | "args": ["run", "build_runner", "watch"] 27 | }, 28 | { 29 | "label": "github_trending", 30 | "type": "dart", 31 | "command": "dart", 32 | "cwd": "packages/github_trending", 33 | "args": ["run", "build_runner", "watch"] 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Repository structure 4 | 5 | ```sh 6 | lib 7 | ├── main.dart # App entry 8 | ├── graphql 9 | │   ├── # GraphQL queries 10 | ├── models 11 | │   ├── # Provider models and other JSON models 12 | ├── router.dart # Routers here 13 | ├── scaffolds 14 | │   ├── # Several scaffolds for reducing boilerplate code 15 | ├── screens 16 | │   ├── gh_xxx.dart # GitHub screens 17 | │   ├── bb_xxx.dart # Bitbucket screens 18 | │   ├── gl_xxx.dart # GitLab screens 19 | │   ├── gt_xxx.dart # Gitea screens 20 | │   └── # File with no prefix: common screens 21 | ├── utils 22 | │   ├── # Utilities 23 | ├── widgets 24 | │ ├── # Reusable widgets 25 | └── l10n 26 | └── # Arb Files (translation mappings) 27 | ``` 28 | 29 | ## Adding Translations 30 | 31 | - Use the [Flutter Intl](https://marketplace.visualstudio.com/items?itemName=localizely.flutter-intl) extension. See this [demo](https://twitter.com/localizely/status/1255175275454881793) to get a better idea. 32 | 33 | - You can find the keywords to be translated in any of the existing `.arb` files. To add a new language, all you have to do is copy the `json` from one of the files and replace the `value` of the `key` with the correct translation. Create the new `.arb` file for the new language using the extension. 34 | 35 | - To add a new sentence/phrase to be translated, use the extension and proceed as indicated by the demo. Use of extension is recommended as it adds the sentence/phrase to all the `.arb` files instead of having to add them to every file manually. 36 | 37 | Feel free to open an issue to discuss it if you get stuck. 38 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/io/github/pd4d10/gittouch/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.pd4d10.gittouch 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/drawable/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/google-play-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/assets/google-play-badge.png -------------------------------------------------------------------------------- /assets/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/assets/icons/icon.png -------------------------------------------------------------------------------- /assets/icons/icon_cyberblueprint2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/assets/icons/icon_cyberblueprint2.png -------------------------------------------------------------------------------- /assets/icons/icon_minimal_clean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/assets/icons/icon_minimal_clean.png -------------------------------------------------------------------------------- /assets/screenshot-android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/assets/screenshot-android.png -------------------------------------------------------------------------------- /assets/screenshot-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/assets/screenshot-dark.png -------------------------------------------------------------------------------- /assets/screenshot-ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/assets/screenshot-ios.png -------------------------------------------------------------------------------- /assets/screenshot-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/assets/screenshot-light.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | Open source mobile client for GitHub, GitLab, Bitbucket, Gitea, and Gitee(码云), built with Flutter 2 | 3 | https://github.com/git-touch/git-touch 4 | 5 | Features: 6 | - View code with syntax highlighting 7 | - View Github trending 8 | - View repositories and users 9 | - View pull requests and issues 10 | - View notifications 11 | - Star and unstar repositories 12 | - Follow and unfollow users 13 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/login-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/fastlane/metadata/android/en-US/images/phoneScreenshots/login-dark.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/login-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/fastlane/metadata/android/en-US/images/phoneScreenshots/login-light.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/repo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/fastlane/metadata/android/en-US/images/phoneScreenshots/repo-dark.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/repo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/fastlane/metadata/android/en-US/images/phoneScreenshots/repo-light.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/trending-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/fastlane/metadata/android/en-US/images/phoneScreenshots/trending-dark.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/trending-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/fastlane/metadata/android/en-US/images/phoneScreenshots/trending-light.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | An app for GitHub, GitLab, Bitbucket, Gitea, and Gitee(码云) 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/full_description.txt: -------------------------------------------------------------------------------- 1 | GitHub, GitLab, Bitbucket, Gitea, 和 Gitee(码云) 的开源移动客户端,使用 Flutter 构建 2 | 3 | https://github.com/git-touch/git-touch 4 | 5 | 功能: 6 | - 查看代码,支持语法高亮 7 | - 查看 Github 趋势 8 | - 查看存储库和用户 9 | - 查看拉取请求和议题 10 | - 查看通知 11 | - 标星和取消标星存储库 12 | - 关注和取消关注用户 13 | -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/short_description.txt: -------------------------------------------------------------------------------- 1 | GitHub, GitLab, Bitbucket, Gitea, 和 Gitee(码云) 应用 2 | -------------------------------------------------------------------------------- /images/avatar-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/images/avatar-dark.png -------------------------------------------------------------------------------- /images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/images/avatar.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /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 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/LaunchImage.imageset/144.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/216.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/LaunchImage.imageset/216.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/ios/Runner/Assets.xcassets/LaunchImage.imageset/72.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "72.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "144.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "216.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /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/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | GitTouch 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | git_touch 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleTypeRole 27 | Editor 28 | CFBundleURLSchemes 29 | 30 | gittouch 31 | 32 | 33 | 34 | CFBundleVersion 35 | $(FLUTTER_BUILD_NUMBER) 36 | LSApplicationQueriesSchemes 37 | 38 | itms 39 | 40 | LSRequiresIPhoneOS 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | UIViewControllerBasedStatusBarAppearance 60 | 61 | CADisableMinimumFrameDurationOnPhone 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/l10n 2 | template-arb-file: intl_en.arb 3 | output-localization-file: S.dart -------------------------------------------------------------------------------- /lib/l10n/intl_hu.arb: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /lib/l10n/intl_nb.arb: -------------------------------------------------------------------------------- 1 | intl_nb_NO.arb -------------------------------------------------------------------------------- /lib/l10n/intl_si.arb: -------------------------------------------------------------------------------- 1 | { 2 | "notification": "දැනුම්දීම", 3 | "@notification": { 4 | "description": "The Notification tab" 5 | }, 6 | "news": "පුවත්", 7 | "@news": { 8 | "description": "The News tab" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/l10n/intl_zh.arb: -------------------------------------------------------------------------------- 1 | intl_zh_Hans.arb -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:git_touch/app.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/code.dart'; 5 | import 'package:git_touch/models/notification.dart'; 6 | import 'package:git_touch/models/theme.dart'; 7 | import 'package:provider/provider.dart'; 8 | import 'package:sentry_flutter/sentry_flutter.dart'; 9 | 10 | void main() async { 11 | await SentryFlutter.init( 12 | (options) { 13 | options.dsn = 14 | 'https://006354525fa244289c48169790fa3757@o71119.ingest.sentry.io/5814819'; 15 | }, 16 | // Init your App. 17 | appRunner: () async { 18 | final notificationModel = NotificationModel(); 19 | final themeModel = ThemeModel(); 20 | final authModel = AuthModel(); 21 | final codeModel = CodeModel(); 22 | await Future.wait([ 23 | themeModel.init(), 24 | authModel.init(), 25 | codeModel.init(), 26 | ]); 27 | 28 | runApp(MultiProvider( 29 | providers: [ 30 | ChangeNotifierProvider(create: (context) => notificationModel), 31 | ChangeNotifierProvider(create: (context) => themeModel), 32 | ChangeNotifierProvider(create: (context) => authModel), 33 | ChangeNotifierProvider(create: (context) => codeModel), 34 | ], 35 | child: const MyApp(), 36 | )); 37 | }, 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /lib/models/account.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'account.freezed.dart'; 4 | part 'account.g.dart'; 5 | 6 | @freezed 7 | class Account with _$Account { 8 | @JsonSerializable(includeIfNull: false) 9 | factory Account({ 10 | required String platform, 11 | required String domain, 12 | required String token, 13 | required String login, 14 | required String avatarUrl, 15 | int? gitlabId, // For GitLab 16 | String? appPassword, // For Bitbucket 17 | String? accountId, // For Bitbucket 18 | }) = _Account; 19 | 20 | factory Account.fromJson(Map json) => 21 | _$AccountFromJson(json); 22 | } 23 | -------------------------------------------------------------------------------- /lib/models/account.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'account.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Account _$$_AccountFromJson(Map json) => _$_Account( 10 | platform: json['platform'] as String, 11 | domain: json['domain'] as String, 12 | token: json['token'] as String, 13 | login: json['login'] as String, 14 | avatarUrl: json['avatarUrl'] as String, 15 | gitlabId: json['gitlabId'] as int?, 16 | appPassword: json['appPassword'] as String?, 17 | accountId: json['accountId'] as String?, 18 | ); 19 | 20 | Map _$$_AccountToJson(_$_Account instance) { 21 | final val = { 22 | 'platform': instance.platform, 23 | 'domain': instance.domain, 24 | 'token': instance.token, 25 | 'login': instance.login, 26 | 'avatarUrl': instance.avatarUrl, 27 | }; 28 | 29 | void writeNotNull(String key, dynamic value) { 30 | if (value != null) { 31 | val[key] = value; 32 | } 33 | } 34 | 35 | writeNotNull('gitlabId', instance.gitlabId); 36 | writeNotNull('appPassword', instance.appPassword); 37 | writeNotNull('accountId', instance.accountId); 38 | return val; 39 | } 40 | -------------------------------------------------------------------------------- /lib/models/notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:git_touch/models/github.dart'; 3 | import 'package:git_touch/utils/utils.dart'; 4 | import 'package:tuple/tuple.dart'; 5 | 6 | class NotificationGroup { 7 | NotificationGroup(this.fullName); 8 | String? fullName; 9 | List items = []; 10 | 11 | Tuple2? _repo; 12 | String get owner { 13 | _repo ??= parseRepositoryFullName(fullName!); 14 | return _repo!.item1; 15 | } 16 | 17 | String get name { 18 | _repo ??= parseRepositoryFullName(fullName!); 19 | return _repo!.item2; 20 | } 21 | 22 | String get key => '_$hashCode'; 23 | } 24 | 25 | class NotificationModel with ChangeNotifier { 26 | int _count = 0; 27 | int get count => _count; 28 | 29 | setCount(int v) { 30 | _count = v; 31 | notifyListeners(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/scaffolds/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:git_touch/models/theme.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | class CommonScaffold extends StatelessWidget { 6 | const CommonScaffold({ 7 | required this.title, 8 | required this.body, 9 | this.action, 10 | }); 11 | final Widget title; 12 | final Widget body; 13 | final Widget? action; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | final theme = Provider.of(context); 18 | // FIXME: A hack to get brightness before MaterialApp been built 19 | theme.setSystemBrightness(MediaQuery.of(context).platformBrightness); 20 | 21 | return CupertinoPageScaffold( 22 | navigationBar: CupertinoNavigationBar( 23 | middle: title, 24 | trailing: action, 25 | ), 26 | child: SafeArea(child: body), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/scaffolds/single.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:git_touch/scaffolds/common.dart'; 3 | 4 | class SingleScaffold extends StatelessWidget { 5 | const SingleScaffold({ 6 | required this.title, 7 | required this.body, 8 | this.action, 9 | }); 10 | final Widget title; 11 | final Widget body; 12 | final Widget? action; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return CommonScaffold( 17 | title: title, 18 | body: CupertinoScrollbar(child: SingleChildScrollView(child: body)), 19 | action: action, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/scaffolds/tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:git_touch/scaffolds/common.dart'; 3 | import 'package:git_touch/scaffolds/utils.dart'; 4 | 5 | class TabScaffold extends StatelessWidget { 6 | const TabScaffold({ 7 | required this.title, 8 | required this.body, 9 | this.action, 10 | required this.onRefresh, 11 | required this.tabs, 12 | required this.activeTab, 13 | required this.onTabSwitch, 14 | }); 15 | final Widget title; 16 | final Widget body; 17 | final Widget? action; 18 | final void Function() onRefresh; 19 | final List tabs; 20 | final int activeTab; 21 | final Function(int index) onTabSwitch; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return CommonScaffold( 26 | title: DefaultTextStyle( 27 | style: DefaultTextStyle.of(context).style.copyWith(fontSize: 14), 28 | child: Row( 29 | children: [ 30 | Expanded( 31 | child: CupertinoSlidingSegmentedControl( 32 | groupValue: activeTab, 33 | onValueChanged: (v) { 34 | if (v == null) return; 35 | onTabSwitch(v); 36 | }, 37 | children: tabs.asMap().map((key, text) => MapEntry( 38 | key, 39 | Padding( 40 | padding: const EdgeInsets.symmetric(horizontal: 8), 41 | child: Text(text), 42 | ))), 43 | ), 44 | ), 45 | ], 46 | ), 47 | ), 48 | body: RefreshWrapper(body: body, onRefresh: onRefresh), 49 | // action: action, // TODO: 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/scaffolds/utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:git_touch/widgets/error_reload.dart'; 3 | import 'package:git_touch/widgets/loading.dart'; 4 | 5 | class RefreshWrapper extends StatelessWidget { 6 | const RefreshWrapper({ 7 | required this.onRefresh, 8 | required this.body, 9 | }); 10 | final Widget body; 11 | final void Function() onRefresh; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return CupertinoScrollbar( 16 | child: CustomScrollView( 17 | slivers: [ 18 | CupertinoSliverRefreshControl( 19 | onRefresh: onRefresh as Future Function()?), 20 | SliverToBoxAdapter(child: body), 21 | ], 22 | ), 23 | ); 24 | } 25 | } 26 | 27 | class ErrorLoadingWrapper extends StatelessWidget { 28 | const ErrorLoadingWrapper({ 29 | required this.error, 30 | required this.loading, 31 | required this.reload, 32 | required this.bodyBuilder, 33 | }); 34 | final String error; 35 | final bool loading; 36 | final void Function() reload; 37 | final Widget? Function() bodyBuilder; 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | if (error.isNotEmpty) { 42 | return ErrorReload(text: error, onTap: reload); 43 | } 44 | 45 | if (loading) { 46 | return const Loading(); 47 | } 48 | 49 | return bodyBuilder()!; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/screens/bb_commits.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/bitbucket.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/commit_item.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class BbCommitsScreen extends StatelessWidget { 10 | const BbCommitsScreen(this.owner, this.name, this.ref); 11 | final String owner; 12 | final String name; 13 | final String ref; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | final auth = Provider.of(context); 18 | return ListStatefulScaffold( 19 | title: Text(AppLocalizations.of(context)!.commits), 20 | fetch: (nextUrl) async { 21 | final res = await context.read().fetchBbWithPage( 22 | nextUrl ?? '/repositories/$owner/$name/commits/$ref'); 23 | return ListPayload( 24 | cursor: res.cursor, 25 | hasMore: res.hasMore, 26 | items: [ 27 | for (var v in res.items) BbCommit.fromJson(v), 28 | ], 29 | ); 30 | }, 31 | itemBuilder: (v) { 32 | return CommitItem( 33 | url: '${auth.activeAccount!.domain}/$owner/$name/commits/${v.hash}', 34 | avatarUrl: v.author!.user?.avatarUrl, 35 | avatarLink: null, 36 | author: v.author!.raw!.replaceFirst(RegExp(r' <.*>'), ''), 37 | createdAt: v.date, 38 | message: v.message, 39 | ); 40 | }, 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/screens/bb_explore.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/bitbucket.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/repo_item.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class BbExploreScreen extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | return ListStatefulScaffold( 13 | title: Text(AppLocalizations.of(context)!.explore), 14 | fetch: (nextUrl) async { 15 | final res = await context.read().fetchBbWithPage( 16 | nextUrl ?? '/repositories?role=member&sort=-updated_on'); 17 | return ListPayload( 18 | cursor: res.cursor, 19 | hasMore: res.hasMore, 20 | items: [ 21 | for (var v in res.items) BbRepo.fromJson(v), 22 | ], 23 | ); 24 | }, 25 | itemBuilder: (v) { 26 | return RepoItem.bb(payload: v); 27 | }, 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/screens/bb_issues.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:flutter_vector_icons/flutter_vector_icons.dart'; 4 | import 'package:git_touch/models/auth.dart'; 5 | import 'package:git_touch/models/bitbucket.dart'; 6 | import 'package:git_touch/scaffolds/list_stateful.dart'; 7 | import 'package:git_touch/widgets/action_entry.dart'; 8 | import 'package:git_touch/widgets/issue_item.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class BbIssuesScreen extends StatelessWidget { 12 | const BbIssuesScreen(this.owner, this.name); 13 | final String owner; 14 | final String name; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return ListStatefulScaffold( 19 | title: Text(AppLocalizations.of(context)!.issues), 20 | actionBuilder: () { 21 | return ActionEntry( 22 | iconData: Octicons.plus, url: '/bitbucket/$owner/$name/issues/new'); 23 | }, 24 | fetch: (nextUrl) async { 25 | final res = await context 26 | .read() 27 | .fetchBbWithPage(nextUrl ?? '/repositories/$owner/$name/issues'); 28 | return ListPayload( 29 | cursor: res.cursor, 30 | hasMore: res.hasMore, 31 | items: [ 32 | for (var v in res.items) BbIssues.fromJson(v), 33 | ], 34 | ); 35 | }, 36 | itemBuilder: (v) { 37 | final issueNumber = 38 | int.parse(v.issueLink!.replaceFirst(RegExp(r'.*\/'), '')); 39 | return IssueItem( 40 | avatarUrl: v.reporter!.avatarUrl, 41 | author: v.reporter!.displayName, 42 | title: v.title, 43 | subtitle: '#$issueNumber', 44 | commentCount: 0, 45 | updatedAt: v.createdOn, 46 | url: '/bitbucket/$owner/$name/issues/$issueNumber', 47 | ); 48 | }, 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/screens/bb_pulls.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/bitbucket.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/issue_item.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class BbPullsScreen extends StatelessWidget { 10 | const BbPullsScreen(this.owner, this.name); 11 | final String owner; 12 | final String name; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | final auth = Provider.of(context); 17 | return ListStatefulScaffold( 18 | title: Text(AppLocalizations.of(context)!.pullRequests), 19 | fetch: (nextUrl) async { 20 | final res = await context.read().fetchBbWithPage( 21 | nextUrl ?? '/repositories/$owner/$name/pullrequests'); 22 | return ListPayload( 23 | cursor: res.cursor, 24 | hasMore: res.hasMore, 25 | items: [ 26 | for (var v in res.items) BbPulls.fromJson(v), 27 | ], 28 | ); 29 | }, 30 | itemBuilder: (v) { 31 | final pullNumber = 32 | int.parse(v.pullRequestLink!.replaceFirst(RegExp(r'.*\/'), '')); 33 | return IssueItem( 34 | avatarUrl: v.author!.avatarUrl, 35 | author: v.author!.displayName, 36 | title: v.title, 37 | subtitle: '#$pullNumber', 38 | commentCount: 0, 39 | updatedAt: v.createdOn, 40 | url: 41 | '${auth.activeAccount!.domain}/$owner/$name/pull-requests/$pullNumber', 42 | ); 43 | }, 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/screens/bb_teams.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/bitbucket.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/user_item.dart'; 7 | import 'package:provider/provider.dart'; 8 | import 'package:timeago/timeago.dart' as timeago; 9 | 10 | class BbTeamsScreen extends StatelessWidget { 11 | @override 12 | Widget build(BuildContext context) { 13 | return ListStatefulScaffold( 14 | title: Text(AppLocalizations.of(context)!.teams), 15 | fetch: (nextUrl) async { 16 | final res = await context 17 | .read() 18 | .fetchBbWithPage(nextUrl ?? '/teams?role=member'); 19 | return ListPayload( 20 | cursor: res.cursor, 21 | hasMore: res.hasMore, 22 | items: [ 23 | for (var v in res.items) BbUser.fromJson(v), 24 | ], 25 | ); 26 | }, 27 | itemBuilder: (v) { 28 | return UserItem.bitbucket( 29 | login: v.username, 30 | name: v.nickname, 31 | avatarUrl: v.avatarUrl, 32 | bio: Text('Created ${timeago.format(v.createdOn!)}'), 33 | ); 34 | }, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/screens/ge_blob.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:flutter_vector_icons/flutter_vector_icons.dart'; 4 | import 'package:git_touch/models/auth.dart'; 5 | import 'package:git_touch/models/gitee.dart'; 6 | import 'package:git_touch/scaffolds/refresh_stateful.dart'; 7 | import 'package:git_touch/widgets/action_entry.dart'; 8 | import 'package:git_touch/widgets/blob_view.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class GeBlobScreen extends StatelessWidget { 12 | const GeBlobScreen(this.owner, this.name, this.sha, this.path); 13 | final String owner; 14 | final String name; 15 | final String sha; 16 | final String path; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return RefreshStatefulScaffold( 21 | title: Text(AppLocalizations.of(context)!.file), 22 | fetch: () async { 23 | final auth = context.read(); 24 | final res = await auth.fetchGitee('/repos/$owner/$name/git/blobs/$sha'); 25 | return GiteeBlob.fromJson(res).content; 26 | }, 27 | action: 28 | const ActionEntry(iconData: Ionicons.cog, url: '/choose-code-theme'), 29 | bodyBuilder: (content, _) { 30 | return BlobView(path, base64Text: content); 31 | }, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/screens/ge_commits.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/gitee.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/commit_item.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class GeCommitsScreen extends StatelessWidget { 10 | const GeCommitsScreen(this.owner, this.name, {this.branch}); 11 | final String owner; 12 | final String name; 13 | final String? branch; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ListStatefulScaffold( 18 | title: Text(AppLocalizations.of(context)!.commits), 19 | fetch: (page) async { 20 | final res = await context.read().fetchGiteeWithPage( 21 | '/repos/$owner/$name/commits?sha=$branch', 22 | page: page); 23 | return ListPayload( 24 | cursor: res.cursor, 25 | hasMore: res.hasMore, 26 | items: [for (var v in res.data) GiteeCommit.fromJson(v)], 27 | ); 28 | }, 29 | itemBuilder: (c) { 30 | return CommitItem( 31 | author: c.commit!.author!.name, 32 | avatarUrl: c.author!.avatarUrl, 33 | avatarLink: '/gitee/${c.author!.login}', 34 | createdAt: c.commit!.author!.date, 35 | message: c.commit!.message, 36 | url: '/gitee/$owner/$name/commits/${c.sha}', 37 | ); 38 | }, 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/screens/ge_files.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_gen/gen_l10n/S.dart'; 4 | import 'package:git_touch/models/auth.dart'; 5 | import 'package:git_touch/models/gitee.dart'; 6 | import 'package:git_touch/scaffolds/list_stateful.dart'; 7 | import 'package:git_touch/widgets/action_button.dart'; 8 | import 'package:git_touch/widgets/files_item.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class GeFilesScreen extends StatelessWidget { 12 | const GeFilesScreen(this.owner, this.name, this.pullNumber); 13 | final String owner; 14 | final String name; 15 | final String pullNumber; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return ListStatefulScaffold( 20 | title: Text(AppLocalizations.of(context)!.files), 21 | actionBuilder: () { 22 | return ActionButton( 23 | title: 'Actions', 24 | items: [ 25 | ...ActionItem.getUrlActions( 26 | 'https://gitee.com/$owner/$name/pulls/$pullNumber/files'), 27 | ], 28 | ); 29 | }, 30 | fetch: (page) async { 31 | page = page ?? 1; 32 | final res = await context 33 | .read() 34 | .fetchGiteeWithPage( 35 | '/repos/$owner/$name/pulls/$pullNumber/files?page=$page', 36 | ) 37 | .then((v) { 38 | return [for (var file in v.data) GiteePullFile.fromJson(file)]; 39 | }); 40 | return ListPayload( 41 | cursor: page + 1, 42 | items: res, 43 | hasMore: res.isNotEmpty, 44 | ); 45 | }, 46 | itemBuilder: (v) { 47 | return FilesItem( 48 | filename: v.filename, 49 | additions: int.parse(v.additions!), 50 | deletions: int.parse(v.deletions!), 51 | status: v.status, 52 | patch: v.patch!.diff, 53 | ); 54 | }, 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/screens/ge_issues.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_vector_icons/flutter_vector_icons.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/gitee.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/action_entry.dart'; 7 | import 'package:git_touch/widgets/hex_color_tag.dart'; 8 | import 'package:git_touch/widgets/issue_item.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class GeIssuesScreen extends StatelessWidget { 12 | const GeIssuesScreen(this.owner, this.name, {this.isPr = false}); 13 | final String owner; 14 | final String name; 15 | final bool isPr; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return ListStatefulScaffold( 20 | title: Text(isPr ? 'Pull Requests' : 'Issues'), 21 | fetch: (page) async { 22 | final res = await context 23 | .read() 24 | .fetchGiteeWithPage('/repos/$owner/$name/issues', page: page); 25 | return ListPayload( 26 | cursor: res.cursor, 27 | hasMore: res.hasMore, 28 | items: [for (var v in res.data) GiteeIssue.fromJson(v)], 29 | ); 30 | }, 31 | actionBuilder: () => ActionEntry( 32 | iconData: Octicons.plus, 33 | url: '/gitee/$owner/$name/issues/new', 34 | ), 35 | itemBuilder: (p) => IssueItem( 36 | author: p.user!.login, 37 | avatarUrl: p.user!.avatarUrl, 38 | commentCount: p.comments, 39 | subtitle: '#${p.number!}', 40 | title: p.title, 41 | updatedAt: DateTime.parse(p.updatedAt!), 42 | url: '/gitee/$owner/$name/issues/${p.number}', 43 | labels: p.labels!.isEmpty 44 | ? null 45 | : Wrap(spacing: 4, runSpacing: 4, children: [ 46 | for (var label in p.labels!) 47 | HexColorTag(name: label.name!, color: label.color!) 48 | ]), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/screens/ge_pulls.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:git_touch/models/auth.dart'; 3 | import 'package:git_touch/models/gitee.dart'; 4 | import 'package:git_touch/scaffolds/list_stateful.dart'; 5 | import 'package:git_touch/widgets/hex_color_tag.dart'; 6 | import 'package:git_touch/widgets/issue_item.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class GePullsScreen extends StatelessWidget { 10 | const GePullsScreen(this.owner, this.name, {this.isPr = false}); 11 | final String owner; 12 | final String name; 13 | final bool isPr; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ListStatefulScaffold( 18 | title: Text(isPr ? 'Pull Requests' : 'Issues'), 19 | fetch: (page) async { 20 | final res = await context 21 | .read() 22 | .fetchGiteeWithPage('/repos/$owner/$name/pulls', page: page); 23 | return ListPayload( 24 | cursor: res.cursor, 25 | hasMore: res.hasMore, 26 | items: [for (var v in res.data) GiteePull.fromJson(v)], 27 | ); 28 | }, 29 | itemBuilder: (p) => IssueItem( 30 | author: p.user!.login, 31 | avatarUrl: p.user!.avatarUrl, 32 | commentCount: 0, // fix this 33 | subtitle: '#${p.number}', 34 | title: p.title, 35 | updatedAt: DateTime.parse(p.updatedAt!), 36 | url: '/gitee/$owner/$name/pulls/${p.number}', 37 | labels: p.labels!.isEmpty 38 | ? null 39 | : Wrap(spacing: 4, runSpacing: 4, children: [ 40 | for (var label in p.labels!) 41 | HexColorTag(name: label.name!, color: label.color!) 42 | ]), 43 | ), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/screens/ge_repos.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:git_touch/models/auth.dart'; 3 | import 'package:git_touch/models/gitee.dart'; 4 | import 'package:git_touch/scaffolds/list_stateful.dart'; 5 | import 'package:git_touch/widgets/repo_item.dart'; 6 | import 'package:provider/provider.dart'; 7 | import 'package:timeago/timeago.dart' as timeago; 8 | 9 | class GeReposScreen extends StatelessWidget { 10 | const GeReposScreen(String owner) 11 | : api = '/users/$owner/repos', 12 | title = 'Repositories'; 13 | const GeReposScreen.star(String owner) 14 | : api = '/users/$owner/starred', 15 | title = 'Stars'; 16 | const GeReposScreen.forks(String owner, String name) 17 | : api = '/repos/$owner/$name/forks', 18 | title = 'Forks'; 19 | final String api; 20 | final String title; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return ListStatefulScaffold( 25 | title: Text(title), 26 | fetch: (page) async { 27 | final res = 28 | await context.read().fetchGiteeWithPage(api, page: page); 29 | return ListPayload( 30 | cursor: res.cursor, 31 | hasMore: res.hasMore, 32 | items: [for (var v in res.data) GiteeRepo.fromJson(v)], 33 | ); 34 | }, 35 | itemBuilder: (v) { 36 | return RepoItem( 37 | owner: v.namespace!.path, 38 | avatarUrl: v.owner!.avatarUrl, 39 | name: v.path, 40 | description: v.description, 41 | starCount: v.stargazersCount, 42 | forkCount: v.forksCount, 43 | note: 'Updated ${timeago.format(v.updatedAt!)}', 44 | url: '/gitee/${v.namespace!.path}/${v.path}', 45 | avatarLink: '/gitee/${v.namespace!.path}', 46 | ); 47 | }, 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/screens/ge_tree.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_gen/gen_l10n/S.dart'; 5 | import 'package:git_touch/models/auth.dart'; 6 | import 'package:git_touch/models/gitee.dart'; 7 | import 'package:git_touch/scaffolds/refresh_stateful.dart'; 8 | import 'package:git_touch/utils/utils.dart'; 9 | import 'package:git_touch/widgets/object_tree.dart'; 10 | import 'package:provider/provider.dart'; 11 | 12 | class GeTreeScreen extends StatelessWidget { 13 | const GeTreeScreen(this.owner, this.name, this.sha); 14 | final String owner; 15 | final String name; 16 | final String sha; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return RefreshStatefulScaffold>( 21 | title: Text(AppLocalizations.of(context)!.files), 22 | fetch: () async { 23 | final res = await context 24 | .read() 25 | .fetchGitee('/repos/$owner/$name/git/trees/$sha'); 26 | final items = [for (var v in res['tree']) GiteeTreeItem.fromJson(v)]; 27 | items.sort((a, b) { 28 | return sortByKey('tree', a.type, b.type); 29 | }); 30 | return items; 31 | }, 32 | bodyBuilder: (data, _) { 33 | return AntList( 34 | children: [ 35 | for (var item in data) 36 | createObjectTreeItem( 37 | type: item.type, 38 | name: item.path, 39 | size: item.size, 40 | downloadUrl: '', // TODO: 41 | url: item.type == 'tree' 42 | ? '/gitee/$owner/$name/tree/${item.sha}?path=${item.path.urlencode}' 43 | : item.type == 'blob' 44 | ? '/gitee/$owner/$name/blob/${item.sha}?path=${item.path.urlencode}' 45 | : '', 46 | ) 47 | ], 48 | ); 49 | }, 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/screens/ge_users.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:git_touch/models/auth.dart'; 3 | import 'package:git_touch/models/gitee.dart'; 4 | import 'package:git_touch/scaffolds/list_stateful.dart'; 5 | import 'package:git_touch/widgets/user_item.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | class GeUsersScreen extends StatelessWidget { 9 | const GeUsersScreen.followers(String login) 10 | : api = '/users/$login/followers', 11 | title = 'Followers'; 12 | const GeUsersScreen.following(String login) 13 | : api = '/users/$login/following', 14 | title = 'Following'; 15 | // GeUsersScreen.member(String login) 16 | // : api = '/orgs/$login/members', 17 | // title = "Members"; 18 | const GeUsersScreen.stargazers(String owner, String repo) 19 | : api = '/repos/$owner/$repo/stargazers', 20 | title = 'Stargazers'; 21 | const GeUsersScreen.watchers(String owner, String repo) 22 | : api = '/repos/$owner/$repo/subscribers', 23 | title = 'Watchers'; 24 | final String api; 25 | final String title; 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return ListStatefulScaffold( 30 | title: Text(title), 31 | fetch: (page) async { 32 | final res = 33 | await context.read().fetchGiteeWithPage(api, page: page); 34 | return ListPayload( 35 | cursor: res.cursor, 36 | hasMore: res.hasMore, 37 | items: [for (var v in res.data) GiteeListUser.fromJson(v)], 38 | ); 39 | }, 40 | itemBuilder: (p) { 41 | return UserItem.gitee( 42 | login: p.login, 43 | name: p.name, 44 | avatarUrl: p.avatarUrl, 45 | bio: Text(p.htmlUrl!), 46 | ); 47 | }, 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/screens/gh_compare.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_gen/gen_l10n/S.dart'; 4 | import 'package:git_touch/models/auth.dart'; 5 | import 'package:git_touch/models/github.dart'; 6 | import 'package:git_touch/scaffolds/refresh_stateful.dart'; 7 | import 'package:git_touch/widgets/action_button.dart'; 8 | import 'package:git_touch/widgets/files_item.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class GhComparisonScreen extends StatelessWidget { 12 | const GhComparisonScreen(this.owner, this.name, this.before, this.head); 13 | final String owner; 14 | final String name; 15 | final String before; 16 | final String head; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return RefreshStatefulScaffold( 21 | title: Text(AppLocalizations.of(context)!.files), 22 | fetch: () async { 23 | final res = await context.read().ghClient.getJSON( 24 | '/repos/$owner/$name/compare/$before...$head', 25 | convert: (dynamic vs) => GithubComparisonItem.fromJson(vs)); 26 | return res.files; 27 | }, 28 | actionBuilder: (dynamic v, _) { 29 | return ActionButton( 30 | title: AppLocalizations.of(context)!.actions, 31 | items: [ 32 | ...ActionItem.getUrlActions( 33 | 'https://github.com/$owner/$name/compare/$before...$head'), 34 | ], 35 | ); 36 | }, 37 | bodyBuilder: (dynamic v, _) { 38 | return Wrap( 39 | children: v 40 | .map((vs) => FilesItem( 41 | filename: vs.filename, 42 | additions: vs.additions, 43 | deletions: vs.deletions, 44 | status: vs.status, 45 | patch: vs.patch ?? AppLocalizations.of(context)!.blankDiff, 46 | )) 47 | .toList(), 48 | ); 49 | }, 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/screens/gh_contributors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_gen/gen_l10n/S.dart'; 4 | import 'package:git_touch/models/auth.dart'; 5 | import 'package:git_touch/models/github.dart'; 6 | import 'package:git_touch/scaffolds/list_stateful.dart'; 7 | import 'package:git_touch/widgets/contributor_item.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | class GhContributorsScreen extends StatelessWidget { 11 | const GhContributorsScreen(this.owner, this.name); 12 | final String owner; 13 | final String name; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ListStatefulScaffold( 18 | title: Text(AppLocalizations.of(context)!.contributors), 19 | fetch: (page) async { 20 | page = page ?? 1; 21 | final res = await context 22 | .read() 23 | .ghClient 24 | .getJSON>( 25 | '/repos/$owner/$name/contributors?page=$page', 26 | convert: (vs) => 27 | [for (var v in vs) GithubContributorItem.fromJson(v)], 28 | ); 29 | return ListPayload( 30 | cursor: page + 1, 31 | items: res, 32 | hasMore: res.isNotEmpty, 33 | ); 34 | }, 35 | itemBuilder: (v) { 36 | final login = v.login; 37 | return ContributorItem( 38 | avatarUrl: v.avatarUrl, 39 | commits: v.contributions, 40 | login: v.login, 41 | url: '/github/$login?tab=contributors', 42 | ); 43 | }, 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/screens/gh_events.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_gen/gen_l10n/S.dart'; 4 | import 'package:git_touch/models/auth.dart'; 5 | import 'package:git_touch/models/github.dart'; 6 | import 'package:git_touch/scaffolds/list_stateful.dart'; 7 | import 'package:git_touch/widgets/event_item.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | class GhEventsScreen extends StatelessWidget { 11 | const GhEventsScreen(this.login); 12 | final String login; 13 | 14 | @override 15 | Widget build(context) { 16 | return ListStatefulScaffold( 17 | title: Text(AppLocalizations.of(context)!.events), 18 | itemBuilder: (payload) => EventItem(payload), 19 | fetch: (page) async { 20 | page = page ?? 1; 21 | final events = await context.read().ghClient.getJSON( 22 | '/users/$login/events?page=$page&per_page=$kPageSize', 23 | convert: (dynamic vs) => 24 | [for (var v in vs) GithubEvent.fromJson(v)]); 25 | return ListPayload( 26 | cursor: page + 1, 27 | hasMore: events.length == kPageSize, 28 | items: events, 29 | ); 30 | }, 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/screens/gh_files.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_gen/gen_l10n/S.dart'; 4 | import 'package:git_touch/models/auth.dart'; 5 | import 'package:git_touch/models/github.dart'; 6 | import 'package:git_touch/scaffolds/list_stateful.dart'; 7 | import 'package:git_touch/widgets/action_button.dart'; 8 | import 'package:git_touch/widgets/files_item.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class GhFilesScreen extends StatelessWidget { 12 | const GhFilesScreen(this.owner, this.name, this.pullNumber); 13 | final String owner; 14 | final String name; 15 | final int pullNumber; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return ListStatefulScaffold( 20 | title: Text(AppLocalizations.of(context)!.files), 21 | actionBuilder: () { 22 | return ActionButton( 23 | title: 'Actions', 24 | items: [ 25 | ...ActionItem.getUrlActions( 26 | 'https://github.com/$owner/$name/pull/$pullNumber/files'), 27 | ], 28 | ); 29 | }, 30 | fetch: (page) async { 31 | page = page ?? 1; 32 | final res = await context 33 | .read() 34 | .ghClient 35 | .getJSON>( 36 | '/repos/$owner/$name/pulls/$pullNumber/files?page=$page', 37 | convert: (vs) => [for (var v in vs) GithubFilesItem.fromJson(v)], 38 | ); 39 | return ListPayload( 40 | cursor: page + 1, 41 | items: res, 42 | hasMore: res.isNotEmpty, 43 | ); 44 | }, 45 | itemBuilder: (v) { 46 | return FilesItem( 47 | filename: v.filename, 48 | additions: v.additions, 49 | deletions: v.deletions, 50 | status: v.status, 51 | patch: v.patch, 52 | ); 53 | }, 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/screens/gh_gist_object.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_vector_icons/flutter_vector_icons.dart'; 3 | import 'package:git_touch/scaffolds/common.dart'; 4 | import 'package:git_touch/widgets/action_entry.dart'; 5 | import 'package:git_touch/widgets/blob_view.dart'; 6 | 7 | class GistObjectScreen extends StatelessWidget { 8 | const GistObjectScreen(this.login, this.id, this.file, 9 | {this.raw, this.content}); 10 | final String login; 11 | final String id; 12 | final String file; 13 | final String? raw; 14 | final String? content; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return CommonScaffold( 19 | title: Text(file), 20 | action: const ActionEntry( 21 | iconData: Ionicons.cog, 22 | url: '/choose-code-theme', 23 | ), 24 | body: SingleChildScrollView( 25 | scrollDirection: Axis.vertical, 26 | child: BlobView( 27 | file, 28 | text: content, 29 | ))); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/screens/gh_gists.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_gen/gen_l10n/S.dart'; 4 | import 'package:git_touch/models/auth.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/gists_item.dart'; 7 | import 'package:gql_github/gists.data.gql.dart'; 8 | import 'package:gql_github/gists.req.gql.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class GhGistsScreen extends StatelessWidget { 12 | const GhGistsScreen(this.login); 13 | final String login; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ListStatefulScaffold( 18 | title: Text(AppLocalizations.of(context)!.gists), 19 | fetch: (page) async { 20 | final req = GGistsReq((b) => b 21 | ..vars.login = login 22 | ..vars.after = page); 23 | final res = 24 | await context.read().ghGqlClient.request(req).first; 25 | final gists = res.data!.user!.gists; 26 | return ListPayload( 27 | cursor: gists.pageInfo.endCursor, 28 | items: gists.nodes ?? [], 29 | hasMore: gists.pageInfo.hasNextPage, 30 | ); 31 | }, 32 | itemBuilder: (v) { 33 | final filenames = [for (var file in v.files!) file.name]; 34 | // TODO: add gist comments 35 | return GistsItem( 36 | description: v.description, 37 | login: login, 38 | filenames: filenames, 39 | language: v.files![0].language!.name, 40 | avatarUrl: v.owner!.avatarUrl, 41 | updatedAt: v.updatedAt, 42 | id: v.name, 43 | ); 44 | }, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/screens/gh_gists_files.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_gen/gen_l10n/S.dart'; 5 | import 'package:git_touch/models/auth.dart'; 6 | import 'package:git_touch/scaffolds/refresh_stateful.dart'; 7 | import 'package:git_touch/widgets/object_tree.dart'; 8 | import 'package:gql_github/gist.data.gql.dart'; 9 | import 'package:gql_github/gist.req.gql.dart'; 10 | import 'package:provider/provider.dart'; 11 | 12 | class GhGistsFilesScreen extends StatelessWidget { 13 | const GhGistsFilesScreen(this.login, this.id); 14 | final String id; 15 | final String login; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return RefreshStatefulScaffold( 20 | title: Text(AppLocalizations.of(context)!.files), 21 | fetch: () async { 22 | final req = GGistReq((b) => b 23 | ..vars.login = login 24 | ..vars.name = id); 25 | final res = 26 | await context.read().ghGqlClient.request(req).first; 27 | final gist = res.data!.user!.gist; 28 | return gist; 29 | }, 30 | bodyBuilder: (payload, _) { 31 | return AntList( 32 | children: payload!.files!.map((v) { 33 | final uri = Uri( 34 | path: '/github/$login/gists/$id/${v.name}', 35 | queryParameters: { 36 | 'content': v.text, 37 | }, 38 | ).toString(); 39 | return createObjectTreeItem( 40 | url: uri, 41 | type: 'file', 42 | name: v.name ?? '', 43 | downloadUrl: null, 44 | size: v.size, 45 | ); 46 | }).toList(), 47 | ); 48 | }, 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/screens/gh_meta.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/scaffolds/refresh_stateful.dart'; 5 | import 'package:gql_github/meta.data.gql.dart'; 6 | import 'package:gql_github/meta.req.gql.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class GhMetaScreen extends StatelessWidget { 10 | const GhMetaScreen({super.key}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return RefreshStatefulScaffold( 15 | title: const Text('Meta'), 16 | fetch: () async { 17 | final req = GMetaReq(); 18 | final res = 19 | await context.read().ghGqlClient.request(req).first; 20 | return res.data!.meta; 21 | }, 22 | bodyBuilder: (meta, _) { 23 | return AntList( 24 | children: [ 25 | AntListItem( 26 | extra: Text(meta.gitHubServicesSha), 27 | child: const Text('Service SHA'), 28 | ), 29 | ], 30 | ); 31 | }, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/screens/gh_news.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_gen/gen_l10n/S.dart'; 4 | import 'package:git_touch/models/auth.dart'; 5 | import 'package:git_touch/models/github.dart'; 6 | import 'package:git_touch/models/notification.dart'; 7 | import 'package:git_touch/scaffolds/list_stateful.dart'; 8 | import 'package:git_touch/widgets/event_item.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class GhNewsScreen extends StatefulWidget { 12 | @override 13 | GhNewsScreenState createState() => GhNewsScreenState(); 14 | } 15 | 16 | class GhNewsScreenState extends State { 17 | @override 18 | initState() { 19 | super.initState(); 20 | Future.microtask(() async { 21 | // Check if there are unread notification items. 22 | // 1 item is enough since count is not displayed for now. 23 | final items = await context 24 | .read() 25 | .ghClient 26 | .getJSON('/notifications?per_page=1'); 27 | 28 | if (items is List && items.isNotEmpty) { 29 | context.read().setCount(1); 30 | } 31 | }); 32 | } 33 | 34 | @override 35 | Widget build(context) { 36 | return ListStatefulScaffold( 37 | title: Text(AppLocalizations.of(context)!.news), 38 | itemBuilder: (payload) => EventItem(payload), 39 | fetch: (page) async { 40 | page = page ?? 1; 41 | final auth = context.read(); 42 | final login = auth.activeAccount!.login; 43 | 44 | final events = await auth.ghClient.getJSON( 45 | '/users/$login/received_events?page=$page&per_page=$kPageSize', 46 | convert: (dynamic vs) => [for (var v in vs) GithubEvent.fromJson(v)], 47 | ); 48 | return ListPayload( 49 | cursor: page + 1, 50 | hasMore: events.length == kPageSize, 51 | items: events, 52 | ); 53 | }, 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/screens/gh_pulls.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/scaffolds/list_stateful.dart'; 5 | import 'package:git_touch/widgets/hex_color_tag.dart'; 6 | import 'package:git_touch/widgets/issue_item.dart'; 7 | import 'package:gql_github/issues.data.gql.dart'; 8 | import 'package:gql_github/issues.req.gql.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class GhPullsScreen extends StatelessWidget { 12 | const GhPullsScreen(this.owner, this.name); 13 | final String owner; 14 | final String name; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return ListStatefulScaffold( 20 | title: Text(AppLocalizations.of(context)!.pullRequests), 21 | fetch: (cursor) async { 22 | final req = GPullsReq((b) { 23 | b.vars.owner = owner; 24 | b.vars.name = name; 25 | b.vars.cursor = cursor; 26 | }); 27 | final res = 28 | await context.read().ghGqlClient.request(req).first; 29 | final pulls = res.data!.repository!.pullRequests; 30 | return ListPayload( 31 | cursor: pulls.pageInfo.endCursor, 32 | hasMore: pulls.pageInfo.hasNextPage, 33 | items: pulls.nodes!.toList(), 34 | ); 35 | }, 36 | itemBuilder: (p) => IssueItem( 37 | isPr: true, 38 | author: p.author?.login, 39 | avatarUrl: p.author?.avatarUrl, 40 | commentCount: p.comments.totalCount, 41 | subtitle: '#${p.number}', 42 | title: p.title, 43 | updatedAt: p.updatedAt, 44 | labels: p.labels!.nodes!.isEmpty 45 | ? null 46 | : Wrap(spacing: 4, runSpacing: 4, children: [ 47 | for (var label in p.labels!.nodes!) 48 | HexColorTag(name: label.name, color: label.color) 49 | ]), 50 | url: '/github/$owner/$name/pull/${p.number}', 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/screens/gh_releases.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/scaffolds/list_stateful.dart'; 5 | import 'package:git_touch/widgets/release_item.dart'; 6 | import 'package:gql_github/releases.data.gql.dart'; 7 | import 'package:gql_github/releases.req.gql.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | class GhReleasesScreen extends StatelessWidget { 11 | const GhReleasesScreen(this.owner, this.name); 12 | final String owner; 13 | final String name; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ListStatefulScaffold( 19 | title: const Text('Releases'), 20 | fetch: (page) async { 21 | final req = GReleasesReq((b) => b 22 | ..vars.owner = owner 23 | ..vars.name = name 24 | ..vars.cursor = page); 25 | final res = 26 | await context.read().ghGqlClient.request(req).first; 27 | final releases = res.data!.repository!.releases; 28 | return ListPayload( 29 | cursor: releases.pageInfo.endCursor, 30 | items: releases.nodes ?? [], 31 | hasMore: releases.pageInfo.hasNextPage, 32 | ); 33 | }, 34 | itemBuilder: (v) { 35 | return ReleaseItem( 36 | tagName: v.tagName, 37 | publishedAt: v.publishedAt, 38 | avatarUrl: v.author!.avatarUrl, 39 | login: v.author!.name, 40 | name: v.name, 41 | description: v.description, 42 | releaseAssets: v.releaseAssets, 43 | ); 44 | }, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/screens/gl_blob.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_vector_icons/flutter_vector_icons.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/gitlab.dart'; 5 | import 'package:git_touch/scaffolds/refresh_stateful.dart'; 6 | import 'package:git_touch/utils/utils.dart'; 7 | import 'package:git_touch/widgets/action_entry.dart'; 8 | import 'package:git_touch/widgets/blob_view.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class GlBlobScreen extends StatelessWidget { 12 | const GlBlobScreen(this.id, this.ref, {this.path}); 13 | final int id; 14 | final String ref; 15 | final String? path; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return RefreshStatefulScaffold( 20 | title: Text(path ?? ''), 21 | fetch: () async { 22 | final auth = context.read(); 23 | final res = await auth.fetchGitlab( 24 | '/projects/$id/repository/files/${path!.urlencode}?ref=$ref'); 25 | return GitlabBlob.fromJson(res); 26 | }, 27 | action: 28 | const ActionEntry(iconData: Ionicons.cog, url: '/choose-code-theme'), 29 | bodyBuilder: (data, _) { 30 | return BlobView(path, base64Text: data.content); 31 | }, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/screens/gl_commits.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/gitlab.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/commit_item.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class GlCommitsScreen extends StatelessWidget { 10 | const GlCommitsScreen(this.id, {this.prefix, this.branch}); 11 | final String id; 12 | final String? prefix; 13 | final String? branch; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ListStatefulScaffold( 18 | title: Text(AppLocalizations.of(context)!.commits), 19 | fetch: (page) async { 20 | page = page ?? 1; 21 | final auth = context.read(); 22 | final res = await auth.fetchGitlabWithPage( 23 | '/projects/$id/repository/commits?ref_name=$branch&page=$page'); 24 | return ListPayload( 25 | cursor: res.cursor, 26 | hasMore: res.hasMore, 27 | items: 28 | (res.data as List).map((v) => GitlabCommit.fromJson(v)).toList(), 29 | ); 30 | }, 31 | itemBuilder: (c) { 32 | return CommitItem( 33 | author: c.authorName, 34 | avatarUrl: null, 35 | avatarLink: null, 36 | createdAt: c.createdAt, 37 | message: c.message, 38 | url: '$prefix/commit/${c.id}', // TODO: 39 | // url: '/gitlab/projects/$id/commit/${c.id}', // TODO: 40 | ); 41 | }, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/screens/gl_explore.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/gitlab.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/repo_item.dart'; 7 | import 'package:provider/provider.dart'; 8 | import 'package:timeago/timeago.dart' as timeago; 9 | 10 | class GlExploreScreen extends StatelessWidget { 11 | @override 12 | Widget build(BuildContext context) { 13 | return ListStatefulScaffold( 14 | title: Text(AppLocalizations.of(context)!.explore), 15 | fetch: (page) async { 16 | page = page ?? 1; 17 | final auth = context.read(); 18 | final res = await auth.fetchGitlabWithPage( 19 | '/projects?order_by=last_activity_at&page=$page'); 20 | return ListPayload( 21 | cursor: res.cursor, 22 | hasMore: res.hasMore, 23 | items: [ 24 | for (var v in res.data) GitlabProject.fromJson(v), 25 | ], 26 | ); 27 | }, 28 | itemBuilder: (v) { 29 | return RepoItem.gl( 30 | payload: v, 31 | note: 'Updated ${timeago.format(v.lastActivityAt!)}', 32 | ); 33 | }, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/screens/gl_groups.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/gitlab.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/user_item.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class GlGroupsScreenn extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | return ListStatefulScaffold( 13 | title: Text(AppLocalizations.of(context)!.groups), 14 | fetch: (page) async { 15 | page = page ?? 1; 16 | final auth = context.read(); 17 | final res = await auth.fetchGitlabWithPage('/groups?page=$page'); 18 | return ListPayload( 19 | cursor: res.cursor, 20 | hasMore: res.hasMore, 21 | items: [ 22 | for (var v in res.data) GitlabGroup.fromJson(v), 23 | ], 24 | ); 25 | }, 26 | itemBuilder: (v) { 27 | return UserItem.gitlabGroup( 28 | avatarUrl: v.avatarUrl, 29 | login: v.path, 30 | name: v.name, 31 | bio: Text(v.description ?? ''), 32 | id: v.id, 33 | ); 34 | }, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/screens/gl_issues.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:flutter_vector_icons/flutter_vector_icons.dart'; 4 | import 'package:git_touch/models/auth.dart'; 5 | import 'package:git_touch/models/gitlab.dart'; 6 | import 'package:git_touch/scaffolds/list_stateful.dart'; 7 | import 'package:git_touch/widgets/action_entry.dart'; 8 | import 'package:git_touch/widgets/hex_color_tag.dart'; 9 | import 'package:git_touch/widgets/issue_item.dart'; 10 | import 'package:provider/provider.dart'; 11 | 12 | class GlIssuesScreen extends StatelessWidget { 13 | const GlIssuesScreen(this.id, {this.prefix}); 14 | final String id; 15 | final String? prefix; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return ListStatefulScaffold( 20 | title: Text(AppLocalizations.of(context)!.issues), 21 | fetch: (page) async { 22 | page = page ?? 1; 23 | final auth = context.read(); 24 | final res = await auth.fetchGitlabWithPage( 25 | '/projects/$id/issues?state=opened&page=$page'); 26 | return ListPayload( 27 | cursor: res.cursor, 28 | hasMore: res.hasMore, 29 | items: 30 | (res.data as List).map((v) => GitlabIssue.fromJson(v)).toList(), 31 | ); 32 | }, 33 | actionBuilder: () => ActionEntry( 34 | iconData: Octicons.plus, 35 | url: '/gitlab/projects/$id/issues/new', 36 | ), 37 | itemBuilder: (p) => IssueItem( 38 | author: p.author!.username, 39 | avatarUrl: p.author!.avatarUrl, 40 | commentCount: p.userNotesCount, 41 | subtitle: '#${p.iid}', 42 | title: p.title, 43 | updatedAt: p.updatedAt, 44 | labels: p.labels!.isEmpty 45 | ? null 46 | : Wrap(spacing: 4, runSpacing: 4, children: [ 47 | for (var label in p.labels!) 48 | HexColorTag(name: label, color: '428BCA') 49 | ]), 50 | url: '/gitlab/projects/${p.projectId}/issues/${p.iid}', 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/screens/gl_members.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/gitlab.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/user_item.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class GlMembersScreen extends StatelessWidget { 10 | const GlMembersScreen(this.id, this.type); 11 | final int id; 12 | final String type; 13 | 14 | // https://docs.gitlab.com/ee/api/access_requests.html#valid-access-levels 15 | static const accessLevelMap = { 16 | 10: 'Guest', 17 | 20: 'Reporter', 18 | 30: 'Developer', 19 | 40: 'Maintainer', 20 | 50: 'Owner', 21 | }; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return ListStatefulScaffold( 26 | title: Text(AppLocalizations.of(context)!.members), 27 | fetch: (page) async { 28 | page = page ?? 1; 29 | final auth = context.read(); 30 | final res = 31 | await auth.fetchGitlabWithPage('/$type/$id/members?page=$page'); 32 | return ListPayload( 33 | cursor: res.cursor, 34 | hasMore: res.hasMore, 35 | items: [ 36 | for (var v in res.data) GitlabUser.fromJson(v), 37 | ], 38 | ); 39 | }, 40 | itemBuilder: (v) { 41 | return UserItem.gitlab( 42 | avatarUrl: v.avatarUrl, 43 | login: v.username, 44 | name: v.name, 45 | bio: Text(accessLevelMap[v.accessLevel!] ?? ''), 46 | id: v.id, 47 | ); 48 | }, 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/screens/gl_merge_requests.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/gitlab.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/hex_color_tag.dart'; 7 | import 'package:git_touch/widgets/issue_item.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | class GlMergeRequestsScreen extends StatelessWidget { 11 | const GlMergeRequestsScreen(this.id, {this.prefix}); 12 | final String id; 13 | final String? prefix; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ListStatefulScaffold( 18 | title: Text(AppLocalizations.of(context)!.mergeRequests), 19 | fetch: (page) async { 20 | page = page ?? 1; 21 | final res = await context.read().fetchGitlabWithPage( 22 | '/projects/$id/merge_requests?state=opened&page=$page'); 23 | return ListPayload( 24 | cursor: res.cursor, 25 | hasMore: res.hasMore, 26 | items: 27 | (res.data as List).map((v) => GitlabIssue.fromJson(v)).toList(), 28 | ); 29 | }, 30 | itemBuilder: (p) => IssueItem( 31 | isPr: true, 32 | author: p.author!.username, 33 | avatarUrl: p.author!.avatarUrl, 34 | commentCount: p.userNotesCount, 35 | subtitle: '#${p.iid}', 36 | title: p.title, 37 | updatedAt: p.updatedAt, 38 | labels: p.labels!.isEmpty 39 | ? null 40 | : Wrap(spacing: 4, runSpacing: 4, children: [ 41 | for (var label in p.labels!) 42 | HexColorTag(name: label, color: '428BCA') 43 | ]), 44 | // url: '/gitlab/projects/${p.projectId}/merge_requests/${p.iid}', 45 | url: '$prefix/merge_requests/${p.iid}', // TODO: 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/screens/gl_starrers.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/gitlab.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/user_item.dart'; 7 | import 'package:provider/provider.dart'; 8 | import 'package:timeago/timeago.dart' as timeago; 9 | 10 | class GlStarrersScreen extends StatelessWidget { 11 | const GlStarrersScreen(this.id); 12 | final int id; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return ListStatefulScaffold( 17 | title: Text(AppLocalizations.of(context)!.members), 18 | fetch: (page) async { 19 | page = page ?? 1; 20 | final res = await context 21 | .read() 22 | .fetchGitlabWithPage('/projects/$id/starrers?page=$page'); 23 | return ListPayload( 24 | cursor: res.cursor, 25 | hasMore: res.hasMore, 26 | items: [ 27 | for (var v in res.data) GitlabStarrer.fromJson(v), 28 | ], 29 | ); 30 | }, 31 | itemBuilder: (v) { 32 | return UserItem.gitlab( 33 | avatarUrl: v.user!.avatarUrl, 34 | login: v.user!.username, 35 | name: v.user!.name, 36 | bio: Text('Starred ${timeago.format(v.starredSince!)}'), 37 | id: v.user!.id, 38 | ); 39 | }, 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/screens/gl_tree.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_gen/gen_l10n/S.dart'; 4 | import 'package:git_touch/models/auth.dart'; 5 | import 'package:git_touch/models/gitlab.dart'; 6 | import 'package:git_touch/scaffolds/list_stateful.dart'; 7 | import 'package:git_touch/widgets/object_tree.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | class GlTreeScreen extends StatelessWidget { 11 | const GlTreeScreen(this.id, this.ref, {this.path}); 12 | final int id; 13 | final String ref; 14 | final String? path; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final auth = Provider.of(context); 19 | 20 | return ListStatefulScaffold( 21 | title: Text(path ?? AppLocalizations.of(context)!.files), 22 | fetch: (page) async { 23 | final uri = Uri( 24 | path: '/projects/$id/repository/tree', 25 | queryParameters: { 26 | 'ref': ref, 27 | 'page': page?.toString(), 28 | ...(path == null ? {} : {'path': path}) 29 | }, 30 | ); 31 | final res = await auth.fetchGitlabWithPage(uri.toString()); 32 | return ListPayload( 33 | cursor: res.cursor, 34 | hasMore: res.hasMore, 35 | items: [for (var v in res.data) GitlabTreeItem.fromJson(v)], 36 | ); 37 | }, 38 | itemBuilder: (item) { 39 | return createObjectTreeItem( 40 | type: item.type, 41 | name: item.name, 42 | downloadUrl: 43 | '${auth.activeAccount!.domain}/api/v4/projects/$id/repository/files/${item.path.urlencode}/raw?ref=master', // TODO: 44 | url: item.type == 'tree' 45 | ? '/gitlab/projects/$id/tree/$ref?path=${item.path.urlencode}' 46 | : item.type == 'blob' 47 | ? '/gitlab/projects/$id/blob/$ref?path=${item.path.urlencode}' 48 | : '', 49 | ); 50 | }, 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/screens/go_commits.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/gogs.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/commit_item.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class GoCommitsScreen extends StatelessWidget { 10 | const GoCommitsScreen(this.owner, this.name, {this.branch = 'master'}); 11 | final String owner; 12 | final String name; 13 | final String? branch; 14 | 15 | // TODO: API only returns most recent commit. No provision for all commits. 16 | @override 17 | Widget build(BuildContext context) { 18 | return ListStatefulScaffold( 19 | title: Text(AppLocalizations.of(context)!.commits), 20 | fetch: (page) async { 21 | final res = await context.read().fetchGogsWithPage( 22 | '/repos/$owner/$name/commits/$branch', 23 | page: page); 24 | return ListPayload( 25 | cursor: res.cursor, 26 | hasMore: res.hasMore, 27 | items: [GogsCommit.fromJson(res.data)], 28 | ); 29 | }, 30 | itemBuilder: (c) { 31 | return CommitItem( 32 | author: c.author?.username ?? c.commit!.author!.name, 33 | avatarUrl: c.author!.avatarUrl, 34 | avatarLink: '/gogs/${c.author!.username}', 35 | createdAt: c.commit!.author!.date, 36 | message: c.commit!.message, 37 | url: c.htmlUrl, 38 | ); 39 | }, 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/screens/go_orgs.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/gogs.dart'; 5 | import 'package:git_touch/scaffolds/refresh_stateful.dart'; 6 | import 'package:git_touch/utils/utils.dart'; 7 | import 'package:git_touch/widgets/user_item.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | class GoOrgsScreen extends StatelessWidget { 11 | // TODO: implement list of orgs screen when API is available 12 | const GoOrgsScreen.ofUser(String login, {required this.isViewer}) 13 | : api = isViewer ? '/users/$login/orgs' : '/user/orgs'; 14 | final String api; 15 | final bool isViewer; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return RefreshStatefulScaffold>( 20 | title: Text(AppLocalizations.of(context)!.organizations), 21 | fetch: () async { 22 | final res = await context.read().fetchGogs(api); 23 | return [for (var v in res) GogsOrg.fromJson(v)]; 24 | }, 25 | bodyBuilder: (p, _) { 26 | return Column( 27 | children: [ 28 | for (var org in p) ...[ 29 | UserItem.gogs( 30 | avatarUrl: org.avatarUrl, 31 | login: org.username, 32 | name: org.fullName, 33 | bio: Text(org.description ?? org.website ?? org.location!), 34 | ), 35 | CommonStyle.border, 36 | ] 37 | ], 38 | ); 39 | }, 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/screens/go_repos.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:git_touch/models/auth.dart'; 3 | import 'package:git_touch/models/gogs.dart'; 4 | import 'package:git_touch/scaffolds/list_stateful.dart'; 5 | import 'package:git_touch/widgets/repo_item.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | class GoReposScreen extends StatelessWidget { 9 | const GoReposScreen(String owner, {this.isViewer = false}) 10 | : api = isViewer ? '/users/$owner/repos' : '/user/repos', 11 | title = 'Repositories'; 12 | const GoReposScreen.org(String owner) 13 | : api = '/orgs/$owner/repos', 14 | title = 'Repositories', 15 | isViewer = false; 16 | final String api; 17 | final String title; 18 | final bool isViewer; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return ListStatefulScaffold( 23 | title: Text(title), 24 | fetch: (page) async { 25 | final res = 26 | await context.read().fetchGogsWithPage(api, page: page); 27 | return ListPayload( 28 | cursor: res.cursor, 29 | hasMore: res.hasMore, 30 | items: [for (var v in res.data) GogsRepository.fromJson(v)], 31 | ); 32 | }, 33 | itemBuilder: (v) { 34 | return RepoItem.go( 35 | payload: v, 36 | name: v.fullName!.split('/')[1], 37 | owner: v.owner!.username, 38 | ); 39 | }, 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/screens/go_search.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | class GoSearchScreen extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return const Center(child: Text('Coming Soon...')); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/screens/go_users.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:git_touch/models/auth.dart'; 3 | import 'package:git_touch/models/gogs.dart'; 4 | import 'package:git_touch/scaffolds/list_stateful.dart'; 5 | import 'package:git_touch/widgets/user_item.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | class GoUsersScreen extends StatelessWidget { 9 | const GoUsersScreen.followers(String login) 10 | : api = '/users/$login/followers', 11 | title = 'Followers'; 12 | const GoUsersScreen.following(String login) 13 | : api = '/users/$login/following', 14 | title = 'Following'; 15 | final String api; 16 | final String title; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return ListStatefulScaffold( 21 | title: Text(title), 22 | fetch: (page) async { 23 | final res = 24 | await context.read().fetchGogsWithPage(api, page: page); 25 | return ListPayload( 26 | cursor: res.cursor, 27 | hasMore: res.hasMore, 28 | items: [for (var v in res.data) GogsUser.fromJson(v)], 29 | ); 30 | }, 31 | itemBuilder: (payload) { 32 | return UserItem.gogs( 33 | login: payload.username, 34 | name: payload.fullName, 35 | avatarUrl: payload.avatarUrl, 36 | bio: null, 37 | ); 38 | }, 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/screens/gt_commits.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/gitea.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/commit_item.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class GtCommitsScreen extends StatelessWidget { 10 | // final String branch; // TODO: 11 | const GtCommitsScreen(this.owner, this.name); 12 | final String owner; 13 | final String name; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ListStatefulScaffold( 18 | title: Text(AppLocalizations.of(context)!.commits), 19 | fetch: (page) async { 20 | final res = await context 21 | .read() 22 | .fetchGiteaWithPage('/repos/$owner/$name/commits', page: page); 23 | return ListPayload( 24 | cursor: res.cursor, 25 | hasMore: res.hasMore, 26 | items: 27 | (res.data as List).map((v) => GiteaCommit.fromJson(v)).toList(), 28 | ); 29 | }, 30 | itemBuilder: (c) { 31 | return CommitItem( 32 | author: c.author?.login ?? c.commit!.author!.name, 33 | avatarUrl: null, 34 | avatarLink: null, 35 | createdAt: c.commit!.author!.date, 36 | message: c.commit!.message, 37 | url: c.htmlUrl, 38 | ); 39 | }, 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/screens/gt_orgs.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/S.dart'; 3 | import 'package:git_touch/models/auth.dart'; 4 | import 'package:git_touch/models/gitea.dart'; 5 | import 'package:git_touch/scaffolds/list_stateful.dart'; 6 | import 'package:git_touch/widgets/user_item.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class GtOrgsScreen extends StatelessWidget { 10 | const GtOrgsScreen() : api = '/orgs'; 11 | const GtOrgsScreen.ofUser(String login) : api = '/users/$login/orgs'; 12 | final String api; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return ListStatefulScaffold( 17 | title: Text(AppLocalizations.of(context)!.organizations), 18 | fetch: (page) async { 19 | final res = 20 | await context.read().fetchGiteaWithPage(api, page: page); 21 | return ListPayload( 22 | cursor: res.cursor, 23 | hasMore: res.hasMore, 24 | items: [for (var v in res.data) GiteaOrg.fromJson(v)], 25 | ); 26 | }, 27 | itemBuilder: (v) { 28 | return UserItem.gitea( 29 | avatarUrl: v.avatarUrl, 30 | login: v.username, 31 | name: v.fullName, 32 | bio: Text(v.description ?? v.website ?? v.location!), 33 | ); 34 | }, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/screens/gt_repos.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:git_touch/models/auth.dart'; 3 | import 'package:git_touch/models/gitea.dart'; 4 | import 'package:git_touch/scaffolds/list_stateful.dart'; 5 | import 'package:git_touch/widgets/repo_item.dart'; 6 | import 'package:provider/provider.dart'; 7 | import 'package:timeago/timeago.dart' as timeago; 8 | 9 | class GtReposScreen extends StatelessWidget { 10 | const GtReposScreen(String owner) 11 | : api = '/users/$owner/repos', 12 | title = 'Repositories'; 13 | const GtReposScreen.star(String owner) 14 | : api = '/users/$owner/starred', 15 | title = 'Stars'; 16 | const GtReposScreen.org(String owner) 17 | : api = '/orgs/$owner/repos', 18 | title = 'Repositories'; 19 | const GtReposScreen.forks(String owner, String repo) 20 | : api = '/repos/$owner/$repo/forks', 21 | title = 'Forks'; 22 | final String api; 23 | final String title; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return ListStatefulScaffold( 28 | title: Text(title), 29 | fetch: (page) async { 30 | final res = 31 | await context.read().fetchGiteaWithPage(api, page: page); 32 | return ListPayload( 33 | cursor: res.cursor, 34 | hasMore: res.hasMore, 35 | items: [for (var v in res.data) GiteaRepository.fromJson(v)], 36 | ); 37 | }, 38 | itemBuilder: (v) { 39 | return RepoItem( 40 | owner: v.owner!.login, 41 | avatarUrl: v.owner!.avatarUrl, 42 | name: v.name, 43 | description: v.description, 44 | starCount: v.starsCount, 45 | forkCount: v.forksCount, 46 | note: 'Updated ${timeago.format(v.updatedAt!)}', 47 | url: '/gitea/${v.owner!.login}/${v.name}', 48 | avatarLink: '/gitea/${v.owner!.login}', 49 | ); 50 | }, 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/screens/gt_status.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_gen/gen_l10n/S.dart'; 5 | import 'package:git_touch/models/auth.dart'; 6 | import 'package:git_touch/scaffolds/refresh_stateful.dart'; 7 | import 'package:git_touch/widgets/blob_view.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | class GtStatusScreen extends StatelessWidget { 11 | @override 12 | Widget build(BuildContext context) { 13 | return RefreshStatefulScaffold( 14 | title: Text(AppLocalizations.of(context)!.giteaStatus), 15 | fetch: () async { 16 | final auth = context.read(); 17 | final res = await Future.wait([ 18 | auth.fetchGitea('/version'), 19 | auth.fetchGitea('/settings/attachment'), 20 | auth.fetchGitea('/settings/api'), 21 | auth.fetchGitea('/settings/repository'), 22 | auth.fetchGitea('/settings/ui'), 23 | ]); 24 | return const JsonEncoder.withIndent(' ').convert({ 25 | ...res[0], 26 | 'attachment': res[1], 27 | 'api': res[2], 28 | 'repository': res[3], 29 | 'ui': res[4], 30 | }); 31 | }, 32 | bodyBuilder: (jsonStr, _) { 33 | return BlobView('0.json', text: jsonStr); 34 | }, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/screens/gt_users.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:git_touch/models/auth.dart'; 3 | import 'package:git_touch/models/gitea.dart'; 4 | import 'package:git_touch/scaffolds/list_stateful.dart'; 5 | import 'package:git_touch/widgets/user_item.dart'; 6 | import 'package:provider/provider.dart'; 7 | import 'package:timeago/timeago.dart' as timeago; 8 | 9 | class GtUsersScreen extends StatelessWidget { 10 | const GtUsersScreen.followers(String login) 11 | : api = '/users/$login/followers', 12 | title = 'Followers'; 13 | const GtUsersScreen.following(String login) 14 | : api = '/users/$login/following', 15 | title = 'Following'; 16 | const GtUsersScreen.member(String login) 17 | : api = '/orgs/$login/members', 18 | title = 'Members'; 19 | const GtUsersScreen.stargazers(String owner, String repo) 20 | : api = '/repos/$owner/$repo/stargazers', 21 | title = 'Stargazers'; 22 | const GtUsersScreen.watchers(String owner, String repo) 23 | : api = '/repos/$owner/$repo/subscribers', 24 | title = 'Watchers'; 25 | final String api; 26 | final String title; 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return ListStatefulScaffold( 31 | title: Text(title), 32 | fetch: (page) async { 33 | final res = 34 | await context.read().fetchGiteaWithPage(api, page: page); 35 | return ListPayload( 36 | cursor: res.cursor, 37 | hasMore: res.hasMore, 38 | items: [for (var v in res.data) GiteaUser.fromJson(v)], 39 | ); 40 | }, 41 | itemBuilder: (payload) { 42 | return UserItem.gitea( 43 | login: payload.login, 44 | name: payload.fullName, 45 | avatarUrl: payload.avatarUrl, 46 | bio: Text('Joined on ${timeago.format(payload.created!)}'), 47 | ); 48 | }, 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/utils/extensions.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:path/path.dart' as p; 4 | 5 | extension MyString on String { 6 | int get toInt => int.parse(this); 7 | String get urlencode => Uri.encodeComponent(this); 8 | String get urldecode => Uri.decodeComponent(this); 9 | String get dropLineBreak => replaceAll('\n', ''); 10 | String get base64ToUtf8 => utf8.decode(base64.decode(this)); 11 | 12 | /// Get extension by file name/path, returns `null` instead of empty string 13 | /// 14 | /// 1.dart -> 'dart' 15 | /// 16 | /// license -> null 17 | String? get ext { 18 | final dotext = p.extension(this); 19 | if (dotext.isEmpty) return null; 20 | return dotext.substring(1); 21 | } 22 | 23 | String get normalizedHtml { 24 | return '
${this}
'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/widgets/action_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter_vector_icons/flutter_vector_icons.dart'; 3 | import 'package:git_touch/models/theme.dart'; 4 | import 'package:git_touch/utils/utils.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:share_plus/share_plus.dart'; 7 | 8 | class ActionItem { 9 | ActionItem({ 10 | required this.text, 11 | this.onTap, 12 | this.danger = false, 13 | }); 14 | String? text; 15 | bool danger; 16 | void Function(BuildContext context)? onTap; 17 | 18 | static List getUrlActions(String? url) { 19 | return [ 20 | ActionItem( 21 | text: 'Share', 22 | onTap: (_) { 23 | Share.share(url!); 24 | }, 25 | ), 26 | ActionItem( 27 | text: 'Open in Browser', 28 | onTap: (_) { 29 | launchStringUrl(url); 30 | }, 31 | ), 32 | ]; 33 | } 34 | } 35 | 36 | class ActionButton extends StatelessWidget { 37 | const ActionButton({ 38 | required this.title, 39 | required this.items, 40 | this.iconData = Ionicons.ellipsis_horizontal, 41 | this.selected, 42 | }); 43 | 44 | final String title; 45 | final List items; 46 | final IconData iconData; 47 | final int? selected; 48 | // TODO: selected, font bold 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | final theme = Provider.of(context); 53 | return CupertinoButton( 54 | minSize: 0, 55 | padding: EdgeInsets.zero, 56 | onPressed: () async { 57 | await theme.showActions(context, items); 58 | }, 59 | child: Icon(iconData, size: 22), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/widgets/action_entry.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:git_touch/utils/utils.dart'; 3 | 4 | class ActionEntry extends StatelessWidget { 5 | const ActionEntry({this.url, this.iconData, this.onTap}); 6 | final IconData? iconData; 7 | final String? url; 8 | final VoidCallback? onTap; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return CupertinoButton( 13 | minSize: 0, 14 | padding: EdgeInsets.zero, 15 | onPressed: () { 16 | if (onTap != null) onTap!(); 17 | if (url != null) context.pushUrl(url!); 18 | }, 19 | child: Icon(iconData, size: 22), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/widgets/avatar.dart: -------------------------------------------------------------------------------- 1 | import 'package:fimber/fimber.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:git_touch/models/theme.dart'; 5 | import 'package:git_touch/utils/utils.dart'; 6 | import 'package:git_touch/widgets/link.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class AvatarSize { 10 | static const double extraSmall = 20; 11 | static const double small = 24; 12 | static const double medium = 36; 13 | static const double large = 48; 14 | static const double extraLarge = 64; 15 | } 16 | 17 | class Avatar extends StatelessWidget { 18 | const Avatar({ 19 | required this.url, 20 | this.size = AvatarSize.medium, 21 | this.linkUrl, 22 | this.square = false, 23 | }); 24 | final String? url; 25 | final double size; 26 | final String? linkUrl; 27 | final bool square; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | final theme = Provider.of(context); 32 | final fallback = theme.brightness == Brightness.light 33 | ? 'images/avatar.png' 34 | : 'images/avatar-dark.png'; 35 | 36 | final fallbackWidget = Image.asset(fallback, width: size, height: size); 37 | 38 | final widget = ClipRRect( 39 | borderRadius: BorderRadius.circular(square ? 4 : size), 40 | child: url == null 41 | ? fallbackWidget 42 | : FadeInImage.assetNetwork( 43 | placeholder: fallback, 44 | image: url!, 45 | width: size, 46 | height: size, 47 | fadeInDuration: const Duration(milliseconds: 200), 48 | fadeOutDuration: const Duration(milliseconds: 100), 49 | imageErrorBuilder: (_, __, ___) { 50 | Fimber.e('image error: ${url!}'); 51 | return fallbackWidget; 52 | }, 53 | ), 54 | ); 55 | if (linkUrl == null) return widget; 56 | return LinkWidget( 57 | child: widget, 58 | onTap: () { 59 | context.pushUrl(linkUrl!); 60 | }, 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/widgets/border_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | class BorderView extends StatelessWidget { 5 | const BorderView({ 6 | this.height, 7 | this.leftPadding = 0, 8 | }); 9 | final double? height; 10 | final double leftPadding; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | if (height == null) { 15 | // Physical pixel 16 | return Container( 17 | margin: EdgeInsets.only(left: leftPadding), 18 | decoration: BoxDecoration( 19 | border: Border( 20 | top: BorderSide(color: AntTheme.of(context).colorBorder, width: 1), 21 | ), 22 | ), 23 | ); 24 | } 25 | 26 | return Row( 27 | children: [ 28 | SizedBox( 29 | width: leftPadding, 30 | height: height, 31 | child: DecoratedBox( 32 | decoration: 33 | BoxDecoration(color: AntTheme.of(context).colorBackground), 34 | ), 35 | ), 36 | Expanded( 37 | child: SizedBox( 38 | height: height, 39 | child: DecoratedBox( 40 | decoration: 41 | BoxDecoration(color: AntTheme.of(context).colorBorder), 42 | ), 43 | ), 44 | ), 45 | ], 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/widgets/branch_name.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | class BranchName extends StatelessWidget { 5 | const BranchName(this.name, {super.key}); 6 | final String name; 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return AntTag( 11 | fill: AntTagFill.outline, 12 | color: AntTheme.of(context).colorPrimary, 13 | round: true, 14 | child: Text(name), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/widgets/contributor_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:git_touch/utils/utils.dart'; 4 | import 'package:git_touch/widgets/avatar.dart'; 5 | 6 | class ContributorItem extends StatelessWidget { 7 | const ContributorItem({ 8 | required this.login, 9 | required this.avatarUrl, 10 | required this.commits, 11 | required this.url, 12 | }); 13 | final String? login; 14 | final String? avatarUrl; 15 | final int? commits; 16 | final String url; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return AntListItem( 21 | onClick: () { 22 | context.pushUrl(url); 23 | }, 24 | child: Row( 25 | children: [ 26 | Avatar(url: avatarUrl, size: AvatarSize.large), 27 | const SizedBox(width: 10), 28 | Expanded( 29 | child: Column( 30 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 31 | crossAxisAlignment: CrossAxisAlignment.start, 32 | children: [ 33 | Row( 34 | children: [ 35 | Text( 36 | login!, 37 | style: TextStyle( 38 | color: AntTheme.of(context).colorPrimary, 39 | fontSize: 18, 40 | fontWeight: FontWeight.w600, 41 | ), 42 | ), 43 | ], 44 | ), 45 | const SizedBox(height: 6), 46 | if (commits != null) 47 | DefaultTextStyle( 48 | style: TextStyle( 49 | color: AntTheme.of(context).colorTextSecondary, 50 | fontSize: 16, 51 | ), 52 | child: Text('Commits: $commits'), 53 | ), 54 | ], 55 | ), 56 | ) 57 | ], 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/widgets/empty.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | class EmptyWidget extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Container( 7 | padding: const EdgeInsets.symmetric(vertical: 50), 8 | child: const Center(child: Text('Empty', style: TextStyle(fontSize: 18))), 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/widgets/entry_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:git_touch/utils/utils.dart'; 4 | 5 | class EntryItem extends StatelessWidget { 6 | const EntryItem({ 7 | required this.text, 8 | this.count, 9 | this.url, 10 | }); 11 | final int? count; 12 | final String text; 13 | final String? url; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | final theme = AntTheme.of(context); 18 | 19 | return Expanded( 20 | child: Container( 21 | color: theme.colorBackground, 22 | child: AntButton( 23 | block: true, 24 | size: AntButtonSize.large, 25 | fill: AntButtonFill.none, 26 | color: theme.colorPrimary, 27 | onClick: () { 28 | if (url != null) context.pushUrl(url!); 29 | }, 30 | child: Column( 31 | children: [ 32 | Text( 33 | count == null ? '?' : numberFormat.format(count), 34 | style: TextStyle( 35 | fontSize: 17, 36 | fontWeight: FontWeight.w600, 37 | color: theme.colorText, 38 | ), 39 | ), 40 | Text( 41 | text, 42 | style: TextStyle( 43 | fontSize: 14, 44 | color: theme.colorTextSecondary, 45 | fontWeight: FontWeight.w500, 46 | // overflow: TextOverflow.ellipsis, 47 | ), 48 | ) 49 | ], 50 | ), 51 | ), 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/widgets/error_reload.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | class ErrorReload extends StatelessWidget { 5 | const ErrorReload({required this.text, required this.onTap}); 6 | final String text; 7 | final Function onTap; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), 13 | child: Column( 14 | children: [ 15 | const Text( 16 | 'Woops, something bad happened. Error message:', 17 | style: TextStyle(fontSize: 16), 18 | ), 19 | const Padding(padding: EdgeInsets.only(top: 10)), 20 | Text( 21 | text, 22 | style: TextStyle( 23 | fontSize: 14, 24 | fontWeight: FontWeight.w300, 25 | color: AntTheme.of(context).colorDanger, 26 | ), 27 | ), 28 | const Padding(padding: EdgeInsets.only(top: 10)), 29 | GestureDetector( 30 | onTap: onTap as void Function()?, 31 | child: Text( 32 | 'Reload', 33 | style: TextStyle( 34 | fontSize: 20, color: AntTheme.of(context).colorPrimary), 35 | ), 36 | ), 37 | ], 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/widgets/files_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_highlight/flutter_highlight.dart'; 5 | import 'package:flutter_highlight/theme_map.dart'; 6 | import 'package:git_touch/models/code.dart'; 7 | import 'package:git_touch/models/theme.dart'; 8 | import 'package:git_touch/utils/utils.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class FilesItem extends StatelessWidget { 12 | const FilesItem({ 13 | required this.filename, 14 | required this.status, 15 | required this.deletions, 16 | required this.additions, 17 | required this.patch, 18 | }); 19 | final String? filename; 20 | final String? status; 21 | final int? additions; 22 | final int? deletions; 23 | final String? patch; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | final theme = Provider.of(context); 28 | final codeProvider = Provider.of(context); 29 | return AntCollapse( 30 | activeKey: const {}, 31 | onChange: (_) { 32 | // TODO: set active 33 | }, 34 | panels: [ 35 | AntCollapsePanel( 36 | key: '', 37 | title: Text( 38 | filename!, 39 | style: TextStyle( 40 | color: AntTheme.of(context).colorPrimary, 41 | fontSize: 18, 42 | fontWeight: FontWeight.w600, 43 | ), 44 | ), 45 | child: SingleChildScrollView( 46 | scrollDirection: Axis.horizontal, 47 | child: HighlightView( 48 | patch!, 49 | language: 'diff', 50 | padding: CommonStyle.padding, 51 | theme: themeMap[theme.brightness == Brightness.dark 52 | ? codeProvider.themeDark 53 | : codeProvider.theme]!, 54 | textStyle: codeProvider.fontStyle, 55 | ), 56 | ), 57 | ), 58 | ], 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/widgets/hex_color_tag.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:from_css_color/from_css_color.dart'; 4 | 5 | class HexColorTag extends StatelessWidget { 6 | const HexColorTag({ 7 | super.key, 8 | required this.name, 9 | required this.color, 10 | }); 11 | final String name; 12 | final String color; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | final c = fromCssColor('#$color'); 17 | 18 | return AntTag( 19 | round: true, 20 | color: c, 21 | child: Text( 22 | name, 23 | style: TextStyle( 24 | color: c.computeLuminance() > 0.5 25 | ? CupertinoColors.black 26 | : CupertinoColors.white, 27 | ), 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/widgets/issue_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_vector_icons/flutter_vector_icons.dart'; 3 | import 'package:git_touch/utils/utils.dart'; 4 | 5 | enum IssueIconState { 6 | open, 7 | closed, 8 | prOpen, 9 | prClosed, 10 | prMerged, 11 | } 12 | 13 | class IssueIcon extends StatelessWidget { 14 | const IssueIcon(this.state, {this.size}); 15 | final IssueIconState state; 16 | final double? size; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | switch (state) { 21 | case IssueIconState.open: 22 | return Icon(Octicons.issue_opened, 23 | color: GithubPalette.open, size: size); 24 | case IssueIconState.closed: 25 | return Icon(Octicons.issue_closed, 26 | color: GithubPalette.closed, size: size); 27 | case IssueIconState.prOpen: 28 | return Icon(Octicons.git_pull_request, 29 | color: GithubPalette.open, size: size); 30 | case IssueIconState.prClosed: 31 | return Icon(Octicons.git_pull_request, 32 | color: GithubPalette.closed, size: size); 33 | case IssueIconState.prMerged: 34 | return Icon(Octicons.git_merge, 35 | color: GithubPalette.merged, size: size); 36 | default: 37 | return Container(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/widgets/link.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:git_touch/utils/utils.dart'; 3 | 4 | class LinkWidget extends StatelessWidget { 5 | const LinkWidget({ 6 | required this.child, 7 | this.url, 8 | this.onTap, 9 | }); 10 | final Widget child; 11 | final String? url; 12 | final Function? onTap; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | final Widget w = CupertinoButton( 17 | minSize: 0, 18 | padding: EdgeInsets.zero, 19 | onPressed: () async { 20 | if (onTap != null) onTap!(); 21 | if (url != null) context.pushUrl(url!); 22 | }, 23 | child: child, 24 | ); 25 | return w; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/widgets/list_group.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:git_touch/widgets/empty.dart'; 4 | 5 | class ListGroup extends StatelessWidget { 6 | const ListGroup({ 7 | required this.title, 8 | required this.items, 9 | required this.itemBuilder, 10 | this.padding = const EdgeInsets.only(left: 10, right: 10, bottom: 10), 11 | }); 12 | final Widget title; 13 | final List items; 14 | final Widget Function(T item, int index) itemBuilder; 15 | final EdgeInsetsGeometry padding; 16 | 17 | Widget _buildItem(BuildContext context, MapEntry entry) { 18 | return Container( 19 | decoration: BoxDecoration( 20 | border: 21 | Border(top: BorderSide(color: AntTheme.of(context).colorBorder)), 22 | ), 23 | child: itemBuilder(entry.value, entry.key), 24 | ); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Container( 30 | padding: padding, 31 | child: Container( 32 | decoration: BoxDecoration( 33 | border: Border.all(color: AntTheme.of(context).colorBorder), 34 | borderRadius: const BorderRadius.all(Radius.circular(3)), 35 | ), 36 | child: Column( 37 | crossAxisAlignment: CrossAxisAlignment.stretch, 38 | children: [ 39 | Container(padding: const EdgeInsets.all(8), child: title), 40 | items.isEmpty 41 | ? EmptyWidget() 42 | : Column( 43 | children: items 44 | .asMap() 45 | .entries 46 | .map((e) => _buildItem(context, e)) 47 | .toList()) 48 | ], 49 | ), 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/widgets/loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class Loading extends StatelessWidget { 4 | const Loading({this.more = false}); 5 | final bool more; 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Center( 10 | child: Padding( 11 | padding: EdgeInsets.symmetric(vertical: more ? 20 : 100), 12 | child: const CupertinoActivityIndicator(radius: 12), 13 | ), 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/widgets/mutation_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | 4 | class MutationButton extends StatelessWidget { 5 | const MutationButton({ 6 | super.key, 7 | this.active = false, 8 | required this.text, 9 | required this.onTap, 10 | }); 11 | final bool active; 12 | final String text; 13 | final VoidCallback onTap; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return AntButton( 18 | color: AntTheme.of(context).colorPrimary, 19 | fill: active ? AntButtonFill.solid : AntButtonFill.outline, 20 | shape: AntButtonShape.rounded, 21 | onClick: onTap, 22 | child: Text(text), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/widgets/object_tree.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:file_icon/file_icon.dart'; 3 | import 'package:filesize/filesize.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:git_touch/utils/utils.dart'; 6 | 7 | Widget _buildIcon(String type, String name) { 8 | switch (type) { 9 | case 'blob': // github gql, gitlab 10 | case 'file': // github rest, gitea 11 | case 'commit_file': // bitbucket 12 | return FileIcon(name, size: 26); // TODO: size 13 | case 'tree': // github gql, gitlab 14 | case 'dir': // github rest, gitea 15 | case 'commit_directory': // bitbucket 16 | return const Icon(AntIcons.folderOutline); 17 | case 'commit': 18 | return const Icon(AntIcons.fileOutline); 19 | default: 20 | throw 'object type error'; 21 | } 22 | } 23 | 24 | AntListItem createObjectTreeItem({ 25 | required String name, 26 | required String type, 27 | required String url, 28 | String? downloadUrl, 29 | int? size, 30 | }) { 31 | return AntListItem( 32 | prefix: _buildIcon(type, name), 33 | extra: size == null ? null : Text(filesize(size)), 34 | onClick: () async { 35 | final finalUrl = [ 36 | // Let system browser handle these files 37 | // 38 | // TODO: 39 | // Unhandled Exception: PlatformException(Error, Error while launching 40 | // https://github.com/flutter/flutter/issues/49162 41 | 42 | // Docs 43 | 'pdf', 'docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls', 44 | // Fonts 45 | 'ttf', 'otf', 'eot', 'woff', 'woff2', 46 | 'svg', 47 | ].contains(name.ext) 48 | ? downloadUrl 49 | : url; 50 | await launchStringUrl(finalUrl); 51 | }, 52 | arrow: size == null ? const Icon(AntIcons.rightOutline) : null, 53 | child: Text(name), 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /lib/widgets/text_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | 4 | class MyTextField extends StatelessWidget { 5 | const MyTextField({required this.controller, this.placeholder}); 6 | final TextEditingController controller; 7 | final String? placeholder; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return CupertinoTextField( 12 | controller: controller, 13 | placeholder: placeholder, 14 | style: TextStyle(color: AntTheme.of(context).colorText), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/widgets/text_with_at.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:git_touch/utils/utils.dart'; 3 | 4 | class TextWithAt extends StatelessWidget { 5 | const TextWithAt({ 6 | required this.text, 7 | required this.linkFactory, 8 | this.style, 9 | this.oneLine = false, 10 | }); 11 | final String text; 12 | final String Function(String text) linkFactory; 13 | final TextStyle? style; 14 | final bool oneLine; 15 | 16 | static final _reg = RegExp(r'@[A-Za-z-]+'); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | final matches = _reg.allMatches(text).map((m) => m.group(0)).toList(); 21 | final chunks = text.split(_reg); 22 | 23 | final spans = []; 24 | for (final index in List.generate(matches.length, (i) => (i))) { 25 | if (chunks[index].isNotEmpty) { 26 | spans.add(TextSpan(text: chunks[index])); 27 | } 28 | spans.add(createLinkSpan( 29 | context, matches[index], linkFactory(matches[index] ?? ''))); 30 | } 31 | if (chunks.last.isNotEmpty) { 32 | spans.add(TextSpan(text: chunks.last)); 33 | } 34 | 35 | return Text.rich( 36 | TextSpan(children: spans, style: style), 37 | overflow: oneLine ? TextOverflow.ellipsis : TextOverflow.clip, 38 | maxLines: oneLine ? 1 : null, 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/widgets/user_name.dart: -------------------------------------------------------------------------------- 1 | import 'package:antd_mobile/antd_mobile.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:git_touch/widgets/link.dart'; 4 | 5 | class UserName extends StatelessWidget { 6 | const UserName(this.login, this.prefix); 7 | final String? login; 8 | final String prefix; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return LinkWidget( 13 | url: '/$prefix/$login', 14 | child: Container( 15 | // padding: EdgeInsets.all(2), 16 | decoration: const BoxDecoration( 17 | borderRadius: BorderRadius.all(Radius.circular(4)), 18 | ), 19 | child: Text( 20 | login!, 21 | style: TextStyle( 22 | fontWeight: FontWeight.w600, 23 | color: AntTheme.of(context).colorPrimary, 24 | ), 25 | ), 26 | ), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | void fl_register_plugins(FlPluginRegistry* registry) { 14 | g_autoptr(FlPluginRegistrar) maps_launcher_registrar = 15 | fl_plugin_registry_get_registrar_for_plugin(registry, "MapsLauncherPlugin"); 16 | maps_launcher_plugin_register_with_registrar(maps_launcher_registrar); 17 | g_autoptr(FlPluginRegistrar) sentry_flutter_registrar = 18 | fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin"); 19 | sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar); 20 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 21 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 22 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 23 | } 24 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | maps_launcher 7 | sentry_flutter 8 | url_launcher_linux 9 | ) 10 | 11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 12 | ) 13 | 14 | set(PLUGIN_BUNDLED_LIBRARIES) 15 | 16 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 17 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 18 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 21 | endforeach(plugin) 22 | 23 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 26 | endforeach(ffi_plugin) 27 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import maps_launcher 9 | import package_info_plus_macos 10 | import path_provider_macos 11 | import sentry_flutter 12 | import share_plus_macos 13 | import shared_preferences_macos 14 | import url_launcher_macos 15 | 16 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 17 | MapsLauncherPlugin.register(with: registry.registrar(forPlugin: "MapsLauncherPlugin")) 18 | FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) 19 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 20 | SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin")) 21 | SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) 22 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 23 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 24 | } 25 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.11' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon-16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon-32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon-32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon-64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon-128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon-256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon-256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon-512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon-512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon-1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = git_touch 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = io.github.pd4d10.gitTouch 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2022 io.github.pd4d10. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: 3 | npx conventional-changelog-cli -p angular -i CHANGELOG.md -s -r 0 4 | flutter build ios --no-tree-shake-icons 5 | flutter build apk --no-tree-shake-icons 6 | 7 | schema: 8 | # https://docs.github.com/en/graphql/overview/public-schema 9 | curl -o packages/gql_github/lib/schema.graphql https://docs.github.com/public/schema.docs.graphql 10 | npx --yes get-graphql-schema https://gitlab.com/api/graphql > packages/gql_gitlab/lib/schema.graphql 11 | 12 | format: 13 | dartfmt --overwrite lib/**/*.dart 14 | 15 | upgrade: 16 | cd packages/github_trending && flutter pub upgrade --major-versions && \ 17 | cd ../gql_github && flutter pub upgrade --major-versions && \ 18 | cd ../gql_gitlab && flutter pub upgrade --major-versions && \ 19 | cd ../.. && flutter pub upgrade --major-versions 20 | -------------------------------------------------------------------------------- /packages/github_trending/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Rongjian Zhang 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 | -------------------------------------------------------------------------------- /packages/github_trending/README.md: -------------------------------------------------------------------------------- 1 | # github_trending 2 | 3 | [![pub](https://img.shields.io/pub/v/github_trending.svg)](https://pub.dev/packages/github_trending) [![test](https://github.com/pd4d10/github-trending/workflows/test/badge.svg)](https://github.com/pd4d10/github-trending/actions?query=workflow:test) 4 | 5 | A Dart library to get GitHub trending repositories and developers via [github-trending-api](https://github.com/huchenme/github-trending-api). 6 | 7 | ## Installation 8 | 9 | Add `github_trending` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/packages-and-plugins/using-packages) 10 | 11 | ## Usage 12 | 13 | ```dart 14 | import 'package:github_trending/github_trending.dart'; 15 | 16 | void main() async { 17 | final trending = GithubTrending(prefix: 'https://gtrend.yapie.me'); 18 | 19 | // get trending repositories 20 | var repos = await trending.getTrendingRepositories(); 21 | print(repos[0].name); 22 | 23 | // specify time period 24 | var weeklyRepos = await trending.getTrendingRepositories(since: 'weekly'); 25 | print(weeklyRepos[0].name); 26 | 27 | // specify language 28 | var dartRepos = await trending.getTrendingRepositories(language: 'dart'); 29 | print(dartRepos[0].language); // Dart 30 | print(dartRepos[0].languageColor); // #00B4AB 31 | } 32 | 33 | ``` 34 | 35 | ## Credits 36 | 37 | - [github-trending-api](https://github.com/huchenme/github-trending-api) 38 | 39 | ## License 40 | 41 | MIT 42 | -------------------------------------------------------------------------------- /packages/github_trending/example/github_trending_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:github_trending/github_trending.dart'; 2 | 3 | void main() async { 4 | final trending = GithubTrending(); 5 | 6 | // get trending repositories 7 | final repos = await trending.getTrendingRepositories(); 8 | print(repos[0].name); 9 | 10 | // specify time period 11 | final weeklyRepos = await trending.getTrendingRepositories(since: 'weekly'); 12 | print(weeklyRepos[0].name); 13 | 14 | // specify language 15 | final dartRepos = await trending.getTrendingRepositories(language: 'dart'); 16 | print(dartRepos[0].language); // Dart 17 | print(dartRepos[0].languageColor); // #00B4AB 18 | } 19 | -------------------------------------------------------------------------------- /packages/github_trending/lib/github_trending.dart: -------------------------------------------------------------------------------- 1 | export 'src/model.dart'; 2 | export 'src/api.dart'; 3 | -------------------------------------------------------------------------------- /packages/github_trending/lib/src/api.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:github_trending/src/model.dart'; 4 | import 'package:http/http.dart' as http; 5 | 6 | class TrendingRepositoryPrimaryLanguage { 7 | TrendingRepositoryPrimaryLanguage({this.name, this.color}); 8 | String? name; 9 | String? color; 10 | } 11 | 12 | class GithubTrending { 13 | GithubTrending({this.prefix = 'https://ghapi.huchen.dev'}); 14 | String prefix; 15 | 16 | Future _getJson(Uri url) async { 17 | final res = await http.get(url); 18 | return json.decode(utf8.decode(res.bodyBytes)); 19 | } 20 | 21 | /// Get trending repositories 22 | /// 23 | /// https://github.com/huchenme/github-trending-api#trending-repositories 24 | Future> getTrendingRepositories({ 25 | /// daily, weekly, monthly 26 | String? since, 27 | String? language, 28 | String? spokenLanguageCode, 29 | }) async { 30 | final res = await _getJson(Uri.parse('$prefix/repositories').replace( 31 | queryParameters: { 32 | if (since != null) 'since': since, 33 | if (language != null) 'language': language, 34 | if (spokenLanguageCode != null) 35 | 'spoken_language_code': spokenLanguageCode 36 | }, 37 | )); 38 | return (res as List) 39 | .map((v) => GithubTrendingRepository.fromJson(v)) 40 | .toList(); 41 | } 42 | 43 | /// Get trending developers 44 | /// 45 | /// https://github.com/huchenme/github-trending-api#trending-developers 46 | Future> getTrendingDevelopers({ 47 | /// daily, weekly, monthly 48 | String? since, 49 | String? language, 50 | }) async { 51 | final res = await _getJson(Uri.parse('$prefix/developers').replace( 52 | queryParameters: { 53 | if (since != null) 'since': since, 54 | if (language != null) 'language': language, 55 | }, 56 | )); 57 | return (res as List) 58 | .map((v) => GithubTrendingDeveloper.fromJson(v)) 59 | .toList(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/github_trending/lib/src/model.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'model.g.dart'; 4 | 5 | @JsonSerializable() 6 | class GithubTrendingRepository { 7 | GithubTrendingRepository(); 8 | factory GithubTrendingRepository.fromJson(Map json) => 9 | _$GithubTrendingRepositoryFromJson(json); 10 | String? author; 11 | String? name; 12 | String? avatar; 13 | String? url; 14 | String? description; 15 | String? language; 16 | String? languageColor; 17 | int? stars; 18 | int? forks; 19 | int? currentPeriodStars; 20 | List? builtBy; 21 | } 22 | 23 | @JsonSerializable() 24 | class GithubTrendingRepositoryBuiltBy { 25 | GithubTrendingRepositoryBuiltBy(); 26 | factory GithubTrendingRepositoryBuiltBy.fromJson(Map json) => 27 | _$GithubTrendingRepositoryBuiltByFromJson(json); 28 | String? href; 29 | String? avatar; 30 | String? username; 31 | } 32 | 33 | @JsonSerializable() 34 | class GithubTrendingDeveloper { 35 | GithubTrendingDeveloper(); 36 | factory GithubTrendingDeveloper.fromJson(Map json) => 37 | _$GithubTrendingDeveloperFromJson(json); 38 | String? username; 39 | String? name; 40 | String? type; 41 | String? url; 42 | String? avatar; 43 | GithubTrendingDeveloperRepository? repo; 44 | } 45 | 46 | @JsonSerializable() 47 | class GithubTrendingDeveloperRepository { 48 | GithubTrendingDeveloperRepository(); 49 | factory GithubTrendingDeveloperRepository.fromJson( 50 | Map json) => 51 | _$GithubTrendingDeveloperRepositoryFromJson(json); 52 | String? name; 53 | String? description; 54 | String? url; 55 | } 56 | -------------------------------------------------------------------------------- /packages/github_trending/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: github_trending 2 | description: A library to get GitHub trending repositories and developers, with languages and period options. 3 | version: 1.1.0 4 | homepage: https://github.com/git-touch/github-trending 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | 9 | dependencies: 10 | http: ^0.13.0 11 | json_annotation: ^4.0.0 12 | 13 | dev_dependencies: 14 | test: ^1.16.5 15 | build_runner: ^2.3.0 16 | json_serializable: ^6.5.1 17 | -------------------------------------------------------------------------------- /packages/github_trending/test/github_trending_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:github_trending/github_trending.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | // https://github.com/huchenme/github-trending-api/issues/130#issuecomment-703483762 5 | final trending = GithubTrending(prefix: 'https://gtrend.yapie.me'); 6 | 7 | void main() { 8 | group('repositories', () { 9 | List? items; 10 | 11 | setUpAll(() async { 12 | items = await trending.getTrendingRepositories(); 13 | }); 14 | 15 | test('has data', () { 16 | expect(items, isList); 17 | }); 18 | 19 | test('owner, name are not null', () { 20 | items!.forEach((item) { 21 | expect(item.author, isNotNull); 22 | expect(item.name, isNotNull); 23 | }); 24 | }); 25 | 26 | test('star and fork count', () { 27 | // make sure at least one item has star or fork 28 | // to ensure no parse error 29 | final itemHasStar = items!.where((item) => item.stars != null); 30 | expect(itemHasStar, isNotEmpty); 31 | 32 | final itemHasFork = items!.where((item) => item.forks != null); 33 | expect(itemHasFork, isNotEmpty); 34 | }); 35 | 36 | test('language color', () { 37 | items!.forEach((item) { 38 | if (item.languageColor != null) { 39 | expect( 40 | RegExp(r'#[0-9a-fA-F]{3,6}').hasMatch(item.languageColor!), 41 | isTrue, 42 | ); 43 | } 44 | }); 45 | }); 46 | }); 47 | 48 | group('developers', () { 49 | List? items; 50 | 51 | setUpAll(() async { 52 | items = await trending.getTrendingDevelopers(); 53 | }); 54 | 55 | test('has data', () { 56 | expect(items, isList); 57 | }); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /packages/gql_github/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | ferry_generator:graphql_builder: 5 | enabled: true 6 | options: 7 | schema: gql_github|lib/schema.graphql 8 | output_dir: "" 9 | type_overrides: 10 | DateTime: 11 | name: DateTime 12 | URI: 13 | name: String 14 | GitObjectID: 15 | name: String 16 | ferry_generator:serializer_builder: 17 | enabled: true 18 | options: 19 | schema: gql_github|lib/schema.graphql 20 | output_dir: "" 21 | custom_serializers: 22 | - import: package:gql_github/utils/date_time_serializer.dart 23 | name: DateTimeSerializer 24 | -------------------------------------------------------------------------------- /packages/gql_github/lib/commits.graphql: -------------------------------------------------------------------------------- 1 | fragment CommitsRefCommit on Commit { 2 | history(first: 30, after: $after) { 3 | pageInfo { 4 | hasNextPage 5 | endCursor 6 | } 7 | nodes { 8 | oid 9 | messageHeadline 10 | committedDate 11 | author { 12 | name 13 | avatarUrl 14 | user { 15 | login 16 | } 17 | } 18 | status { 19 | state 20 | } 21 | } 22 | } 23 | } 24 | 25 | fragment CommitsRef on Ref { 26 | target { 27 | ... on Commit { 28 | ...CommitsRefCommit 29 | } 30 | } 31 | } 32 | 33 | query Commits( 34 | $owner: String! 35 | $name: String! 36 | $ref: String! 37 | $hasRef: Boolean! 38 | $after: String 39 | ) { 40 | repository(owner: $owner, name: $name) { 41 | defaultBranchRef @skip(if: $hasRef) { 42 | ...CommitsRef 43 | } 44 | ref(qualifiedName: $ref) @include(if: $hasRef) { 45 | ...CommitsRef 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/gql_github/lib/gist.graphql: -------------------------------------------------------------------------------- 1 | query Gist($login: String!, $name: String!) { 2 | user(login: $login) { 3 | gist(name: $name) { 4 | name 5 | files { 6 | name 7 | language { 8 | name 9 | } 10 | text 11 | size 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/gql_github/lib/gist.var.gql.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: type=lint 3 | 4 | // ignore_for_file: no_leading_underscores_for_library_prefixes 5 | import 'package:built_value/built_value.dart'; 6 | import 'package:built_value/serializer.dart'; 7 | import 'package:gql_github/serializers.gql.dart' as _i1; 8 | 9 | part 'gist.var.gql.g.dart'; 10 | 11 | abstract class GGistVars implements Built { 12 | GGistVars._(); 13 | 14 | factory GGistVars([Function(GGistVarsBuilder b) updates]) = _$GGistVars; 15 | 16 | String get login; 17 | String get name; 18 | static Serializer get serializer => _$gGistVarsSerializer; 19 | Map toJson() => (_i1.serializers.serializeWith( 20 | GGistVars.serializer, 21 | this, 22 | ) as Map); 23 | static GGistVars? fromJson(Map json) => 24 | _i1.serializers.deserializeWith( 25 | GGistVars.serializer, 26 | json, 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /packages/gql_github/lib/gists.graphql: -------------------------------------------------------------------------------- 1 | query Gists($login: String!, $after: String) { 2 | user(login: $login) { 3 | gists(first: 30, after: $after) { 4 | pageInfo { 5 | hasNextPage 6 | endCursor 7 | } 8 | nodes { 9 | name 10 | description 11 | files { 12 | name 13 | language { 14 | name 15 | } 16 | text 17 | } 18 | updatedAt 19 | id 20 | owner { 21 | avatarUrl 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/gql_github/lib/gists.var.gql.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: type=lint 3 | 4 | // ignore_for_file: no_leading_underscores_for_library_prefixes 5 | import 'package:built_value/built_value.dart'; 6 | import 'package:built_value/serializer.dart'; 7 | import 'package:gql_github/serializers.gql.dart' as _i1; 8 | 9 | part 'gists.var.gql.g.dart'; 10 | 11 | abstract class GGistsVars implements Built { 12 | GGistsVars._(); 13 | 14 | factory GGistsVars([Function(GGistsVarsBuilder b) updates]) = _$GGistsVars; 15 | 16 | String get login; 17 | String? get after; 18 | static Serializer get serializer => _$gGistsVarsSerializer; 19 | Map toJson() => (_i1.serializers.serializeWith( 20 | GGistsVars.serializer, 21 | this, 22 | ) as Map); 23 | static GGistsVars? fromJson(Map json) => 24 | _i1.serializers.deserializeWith( 25 | GGistsVars.serializer, 26 | json, 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /packages/gql_github/lib/issues.graphql: -------------------------------------------------------------------------------- 1 | query Issues($owner: String!, $name: String!, $cursor: String) { 2 | repository(owner: $owner, name: $name) { 3 | issues( 4 | states: OPEN 5 | orderBy: { field: CREATED_AT, direction: DESC } 6 | first: 30 7 | after: $cursor 8 | ) { 9 | pageInfo { 10 | hasNextPage 11 | endCursor 12 | } 13 | nodes { 14 | number 15 | title 16 | updatedAt 17 | author { 18 | login 19 | avatarUrl 20 | } 21 | labels(first: 10) { 22 | nodes { 23 | name 24 | color 25 | } 26 | } 27 | comments { 28 | totalCount 29 | } 30 | } 31 | } 32 | } 33 | } 34 | 35 | ## pulls 36 | query Pulls($owner: String!, $name: String!, $cursor: String) { 37 | repository(owner: $owner, name: $name) { 38 | pullRequests( 39 | states: OPEN 40 | orderBy: { field: CREATED_AT, direction: DESC } 41 | first: 30 42 | after: $cursor 43 | ) { 44 | pageInfo { 45 | hasNextPage 46 | endCursor 47 | } 48 | nodes { 49 | number 50 | title 51 | updatedAt 52 | author { 53 | login 54 | avatarUrl 55 | } 56 | labels(first: 10) { 57 | nodes { 58 | name 59 | color 60 | } 61 | } 62 | comments { 63 | totalCount 64 | } 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/gql_github/lib/issues.var.gql.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: type=lint 3 | 4 | // ignore_for_file: no_leading_underscores_for_library_prefixes 5 | import 'package:built_value/built_value.dart'; 6 | import 'package:built_value/serializer.dart'; 7 | import 'package:gql_github/serializers.gql.dart' as _i1; 8 | 9 | part 'issues.var.gql.g.dart'; 10 | 11 | abstract class GIssuesVars implements Built { 12 | GIssuesVars._(); 13 | 14 | factory GIssuesVars([Function(GIssuesVarsBuilder b) updates]) = _$GIssuesVars; 15 | 16 | String get owner; 17 | String get name; 18 | String? get cursor; 19 | static Serializer get serializer => _$gIssuesVarsSerializer; 20 | Map toJson() => (_i1.serializers.serializeWith( 21 | GIssuesVars.serializer, 22 | this, 23 | ) as Map); 24 | static GIssuesVars? fromJson(Map json) => 25 | _i1.serializers.deserializeWith( 26 | GIssuesVars.serializer, 27 | json, 28 | ); 29 | } 30 | 31 | abstract class GPullsVars implements Built { 32 | GPullsVars._(); 33 | 34 | factory GPullsVars([Function(GPullsVarsBuilder b) updates]) = _$GPullsVars; 35 | 36 | String get owner; 37 | String get name; 38 | String? get cursor; 39 | static Serializer get serializer => _$gPullsVarsSerializer; 40 | Map toJson() => (_i1.serializers.serializeWith( 41 | GPullsVars.serializer, 42 | this, 43 | ) as Map); 44 | static GPullsVars? fromJson(Map json) => 45 | _i1.serializers.deserializeWith( 46 | GPullsVars.serializer, 47 | json, 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /packages/gql_github/lib/meta.ast.gql.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: type=lint 3 | 4 | // ignore_for_file: no_leading_underscores_for_library_prefixes 5 | import 'package:gql/ast.dart' as _i1; 6 | 7 | const Meta = _i1.OperationDefinitionNode( 8 | type: _i1.OperationType.query, 9 | name: _i1.NameNode(value: 'Meta'), 10 | variableDefinitions: [], 11 | directives: [], 12 | selectionSet: _i1.SelectionSetNode(selections: [ 13 | _i1.FieldNode( 14 | name: _i1.NameNode(value: 'meta'), 15 | alias: null, 16 | arguments: [], 17 | directives: [], 18 | selectionSet: _i1.SelectionSetNode(selections: [ 19 | _i1.FieldNode( 20 | name: _i1.NameNode(value: 'gitHubServicesSha'), 21 | alias: null, 22 | arguments: [], 23 | directives: [], 24 | selectionSet: null, 25 | ), 26 | _i1.FieldNode( 27 | name: _i1.NameNode(value: 'gitIpAddresses'), 28 | alias: null, 29 | arguments: [], 30 | directives: [], 31 | selectionSet: null, 32 | ), 33 | _i1.FieldNode( 34 | name: _i1.NameNode(value: 'hookIpAddresses'), 35 | alias: null, 36 | arguments: [], 37 | directives: [], 38 | selectionSet: null, 39 | ), 40 | _i1.FieldNode( 41 | name: _i1.NameNode(value: 'importerIpAddresses'), 42 | alias: null, 43 | arguments: [], 44 | directives: [], 45 | selectionSet: null, 46 | ), 47 | _i1.FieldNode( 48 | name: _i1.NameNode(value: 'isPasswordAuthenticationVerifiable'), 49 | alias: null, 50 | arguments: [], 51 | directives: [], 52 | selectionSet: null, 53 | ), 54 | _i1.FieldNode( 55 | name: _i1.NameNode(value: 'pagesIpAddresses'), 56 | alias: null, 57 | arguments: [], 58 | directives: [], 59 | selectionSet: null, 60 | ), 61 | ]), 62 | ) 63 | ]), 64 | ); 65 | const document = _i1.DocumentNode(definitions: [Meta]); 66 | -------------------------------------------------------------------------------- /packages/gql_github/lib/meta.graphql: -------------------------------------------------------------------------------- 1 | query Meta { 2 | meta { 3 | gitHubServicesSha 4 | gitIpAddresses 5 | hookIpAddresses 6 | importerIpAddresses 7 | isPasswordAuthenticationVerifiable 8 | pagesIpAddresses 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/gql_github/lib/meta.var.gql.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: type=lint 3 | 4 | // ignore_for_file: no_leading_underscores_for_library_prefixes 5 | import 'package:built_value/built_value.dart'; 6 | import 'package:built_value/serializer.dart'; 7 | import 'package:gql_github/serializers.gql.dart' as _i1; 8 | 9 | part 'meta.var.gql.g.dart'; 10 | 11 | abstract class GMetaVars implements Built { 12 | GMetaVars._(); 13 | 14 | factory GMetaVars([Function(GMetaVarsBuilder b) updates]) = _$GMetaVars; 15 | 16 | static Serializer get serializer => _$gMetaVarsSerializer; 17 | Map toJson() => (_i1.serializers.serializeWith( 18 | GMetaVars.serializer, 19 | this, 20 | ) as Map); 21 | static GMetaVars? fromJson(Map json) => 22 | _i1.serializers.deserializeWith( 23 | GMetaVars.serializer, 24 | json, 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /packages/gql_github/lib/releases.graphql: -------------------------------------------------------------------------------- 1 | query Releases($name: String!, $owner: String!, $cursor: String) { 2 | repository(name: $name, owner: $owner) { 3 | releases( 4 | first: 30 5 | after: $cursor 6 | orderBy: { field: CREATED_AT, direction: DESC } 7 | ) { 8 | pageInfo { 9 | hasNextPage 10 | endCursor 11 | } 12 | nodes { 13 | tagName 14 | description 15 | name 16 | author { 17 | name 18 | avatarUrl 19 | } 20 | publishedAt 21 | url 22 | releaseAssets(first: 30) { 23 | nodes { 24 | name 25 | downloadUrl 26 | downloadCount 27 | } 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/gql_github/lib/releases.var.gql.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: type=lint 3 | 4 | // ignore_for_file: no_leading_underscores_for_library_prefixes 5 | import 'package:built_value/built_value.dart'; 6 | import 'package:built_value/serializer.dart'; 7 | import 'package:gql_github/serializers.gql.dart' as _i1; 8 | 9 | part 'releases.var.gql.g.dart'; 10 | 11 | abstract class GReleasesVars 12 | implements Built { 13 | GReleasesVars._(); 14 | 15 | factory GReleasesVars([Function(GReleasesVarsBuilder b) updates]) = 16 | _$GReleasesVars; 17 | 18 | String get name; 19 | String get owner; 20 | String? get cursor; 21 | static Serializer get serializer => _$gReleasesVarsSerializer; 22 | Map toJson() => (_i1.serializers.serializeWith( 23 | GReleasesVars.serializer, 24 | this, 25 | ) as Map); 26 | static GReleasesVars? fromJson(Map json) => 27 | _i1.serializers.deserializeWith( 28 | GReleasesVars.serializer, 29 | json, 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /packages/gql_github/lib/repos.graphql: -------------------------------------------------------------------------------- 1 | fragment RepoParts on Repository { 2 | owner { 3 | login 4 | avatarUrl 5 | } 6 | name 7 | description 8 | isPrivate 9 | isFork 10 | stargazers { 11 | totalCount 12 | } 13 | forks { 14 | totalCount 15 | } 16 | primaryLanguage { 17 | color 18 | name 19 | } 20 | updatedAt 21 | } 22 | 23 | query Repos($login: String!, $after: String) { 24 | repositoryOwner(login: $login) { 25 | repositories( 26 | first: 30 27 | after: $after 28 | orderBy: { field: UPDATED_AT, direction: DESC } 29 | ) { 30 | pageInfo { 31 | hasNextPage 32 | endCursor 33 | } 34 | nodes { 35 | ...RepoParts 36 | } 37 | } 38 | } 39 | } 40 | 41 | query Stars($login: String!, $after: String) { 42 | user(login: $login) { 43 | starredRepositories( 44 | first: 30 45 | after: $after 46 | orderBy: { field: STARRED_AT, direction: DESC } 47 | ) { 48 | pageInfo { 49 | hasNextPage 50 | endCursor 51 | } 52 | nodes { 53 | ...RepoParts 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/gql_github/lib/utils/date_time_serializer.dart: -------------------------------------------------------------------------------- 1 | import 'package:built_value/serializer.dart'; 2 | 3 | class DateTimeSerializer implements PrimitiveSerializer { 4 | @override 5 | DateTime deserialize( 6 | Serializers serializers, 7 | Object serialized, { 8 | FullType specifiedType = FullType.unspecified, 9 | }) { 10 | assert(serialized is String, 11 | "DateTimeSerializer expected 'String' but got ${serialized.runtimeType}"); 12 | return DateTime.parse(serialized as String); 13 | } 14 | 15 | @override 16 | Object serialize( 17 | Serializers serializers, 18 | DateTime dt, { 19 | FullType specifiedType = FullType.unspecified, 20 | }) => 21 | dt.toString(); 22 | 23 | @override 24 | Iterable get types => [DateTime]; 25 | 26 | @override 27 | String get wireName => 'DateTime'; 28 | } 29 | -------------------------------------------------------------------------------- /packages/gql_github/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: gql_github 2 | publish_to: none 3 | environment: 4 | sdk: ">=2.17.5 <3.0.0" 5 | dev_dependencies: 6 | build_runner: ^2.3.0 7 | ferry_generator: ^0.6.1 8 | -------------------------------------------------------------------------------- /packages/gql_gitlab/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | ferry_generator:graphql_builder: 5 | enabled: true 6 | options: 7 | schema: gql_gitlab|lib/schema.graphql 8 | output_dir: "" 9 | ferry_generator:serializer_builder: 10 | enabled: true 11 | options: 12 | schema: gql_gitlab|lib/schema.graphql 13 | output_dir: "" 14 | -------------------------------------------------------------------------------- /packages/gql_gitlab/lib/project.graphql: -------------------------------------------------------------------------------- 1 | query Project($fullPath: String!) { 2 | project(fullPath: $fullPath) { 3 | name 4 | avatarUrl 5 | description 6 | starCount 7 | forksCount 8 | visibility 9 | webUrl 10 | issuesEnabled 11 | openIssuesCount 12 | mergeRequestsEnabled 13 | createdAt 14 | lastActivityAt 15 | statistics { 16 | commitCount 17 | repositorySize 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/gql_gitlab/lib/project.var.gql.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: type=lint 3 | 4 | // ignore_for_file: no_leading_underscores_for_library_prefixes 5 | import 'package:built_value/built_value.dart'; 6 | import 'package:built_value/serializer.dart'; 7 | import 'package:gql_gitlab/serializers.gql.dart' as _i1; 8 | 9 | part 'project.var.gql.g.dart'; 10 | 11 | abstract class GProjectVars 12 | implements Built { 13 | GProjectVars._(); 14 | 15 | factory GProjectVars([Function(GProjectVarsBuilder b) updates]) = 16 | _$GProjectVars; 17 | 18 | String get fullPath; 19 | static Serializer get serializer => _$gProjectVarsSerializer; 20 | Map toJson() => (_i1.serializers.serializeWith( 21 | GProjectVars.serializer, 22 | this, 23 | ) as Map); 24 | static GProjectVars? fromJson(Map json) => 25 | _i1.serializers.deserializeWith( 26 | GProjectVars.serializer, 27 | json, 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /packages/gql_gitlab/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: gql_gitlab 2 | publish_to: none 3 | environment: 4 | sdk: ">=2.17.5 <3.0.0" 5 | dev_dependencies: 6 | build_runner: ^2.3.0 7 | ferry_generator: ^0.6.1 8 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GitTouch", 3 | "short_name": "GitTouch", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void RegisterPlugins(flutter::PluginRegistry* registry) { 15 | MapsLauncherPluginRegisterWithRegistrar( 16 | registry->GetRegistrarForPlugin("MapsLauncherPlugin")); 17 | SentryFlutterPluginRegisterWithRegistrar( 18 | registry->GetRegistrarForPlugin("SentryFlutterPlugin")); 19 | SharePlusWindowsPluginCApiRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); 21 | UrlLauncherWindowsRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 23 | } 24 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | maps_launcher 7 | sentry_flutter 8 | share_plus_windows 9 | url_launcher_windows 10 | ) 11 | 12 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Disable Windows macros that collide with C++ standard library functions. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 25 | 26 | # Add dependency libraries and include directories. Add any application-specific 27 | # dependencies here. 28 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 29 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 30 | 31 | # Run the Flutter tool portions of the build. This must not be removed. 32 | add_dependencies(${BINARY_NAME} flutter_assemble) 33 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | return true; 30 | } 31 | 32 | void FlutterWindow::OnDestroy() { 33 | if (flutter_controller_) { 34 | flutter_controller_ = nullptr; 35 | } 36 | 37 | Win32Window::OnDestroy(); 38 | } 39 | 40 | LRESULT 41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 42 | WPARAM const wparam, 43 | LPARAM const lparam) noexcept { 44 | // Give Flutter, including plugins, an opportunity to handle window messages. 45 | if (flutter_controller_) { 46 | std::optional result = 47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 48 | lparam); 49 | if (result) { 50 | return *result; 51 | } 52 | } 53 | 54 | switch (message) { 55 | case WM_FONTCHANGE: 56 | flutter_controller_->engine()->ReloadSystemFonts(); 57 | break; 58 | } 59 | 60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 61 | } 62 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.CreateAndShow(L"git_touch", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pd4d10/git-touch/c30895ab1ee192048d8977ecaa1aa43dd4aa0745/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | std::string utf8_string; 52 | if (target_length == 0 || target_length > utf8_string.max_size()) { 53 | return utf8_string; 54 | } 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | --------------------------------------------------------------------------------