├── .buckconfig ├── .eslintrc.js ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── main.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierrc.js ├── .storybook ├── default-noop.js ├── fs.js ├── gesture-handler.js ├── main.js ├── masked-view.js ├── preview-head.html ├── preview.js ├── react-i18next.js ├── reanimated.js └── webpack.config.js ├── .watchmanconfig ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── android ├── app │ ├── _BUCK │ ├── build.gradle │ ├── build_defs.bzl │ ├── debug.keystore │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ └── fonts │ │ │ ├── icomoon.ttf │ │ │ ├── rubik_Italic.ttf │ │ │ ├── rubik_black.ttf │ │ │ ├── rubik_blackItalic.ttf │ │ │ ├── rubik_bold.ttf │ │ │ ├── rubik_boldItalic.ttf │ │ │ ├── rubik_light.ttf │ │ │ ├── rubik_lightItalic.ttf │ │ │ ├── rubik_medium.ttf │ │ │ ├── rubik_medium_italic.ttf │ │ │ └── rubik_regular.ttf │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ └── dev │ │ │ └── oceanbit │ │ │ └── gitshark │ │ │ ├── DirectoryPickerModule.java │ │ │ ├── DirectoryPickerPackage.java │ │ │ ├── Git │ │ │ ├── GhTokenUtils.java │ │ │ ├── GitAddToStaged.java │ │ │ ├── GitCheckout.java │ │ │ ├── GitClone.java │ │ │ ├── GitCommit.java │ │ │ ├── GitCurrentBranch.java │ │ │ ├── GitFetch.java │ │ │ ├── GitGetFileStateChanges.java │ │ │ ├── GitGetTrackedBranch.java │ │ │ ├── GitInit.java │ │ │ ├── GitLocalBranch.java │ │ │ ├── GitLog.java │ │ │ ├── GitPull.java │ │ │ ├── GitPush.java │ │ │ ├── GitReadCommit.java │ │ │ ├── GitRemote.java │ │ │ ├── GitRemoteBranch.java │ │ │ ├── GitRemoveFromStaged.java │ │ │ ├── GitRepo.java │ │ │ ├── GitResetPaths.java │ │ │ ├── GitRevList.java │ │ │ ├── GitStatus.java │ │ │ └── GitUtils.java │ │ │ ├── GitModule.java │ │ │ ├── GitPackage.java │ │ │ ├── MainActivity.java │ │ │ ├── MainApplication.java │ │ │ └── Throttle.java │ │ └── res │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_background.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── app.json ├── assets ├── fonts │ ├── icomoon.ttf │ ├── rubik_Italic.ttf │ ├── rubik_black.ttf │ ├── rubik_blackItalic.ttf │ ├── rubik_bold.ttf │ ├── rubik_boldItalic.ttf │ ├── rubik_light.ttf │ ├── rubik_lightItalic.ttf │ ├── rubik_medium.ttf │ ├── rubik_medium_italic.ttf │ └── rubik_regular.ttf ├── images │ ├── default-profile-pic.png │ ├── split.png │ └── split_dark.png └── videos │ ├── sheet_down.mp4 │ ├── sheet_down_dark.mp4 │ ├── sheet_up.mp4 │ └── sheet_up_dark.mp4 ├── babel.config.js ├── commitlint.config.js ├── index.js ├── ios ├── Cartfile ├── Cartfile.resolved ├── GitModule.h ├── GitModule.m ├── GitShark-tvOS │ └── Info.plist ├── GitShark-tvOSTests │ └── Info.plist ├── GitShark.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── GitShark-tvOS.xcscheme │ │ └── GitShark.xcscheme ├── GitShark.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── GitShark │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Masked1024w.png │ │ │ ├── Masked120w-1.png │ │ │ ├── Masked120w.png │ │ │ ├── Masked180w.png │ │ │ ├── Masked40w.png │ │ │ ├── Masked58w.png │ │ │ ├── Masked60w.png │ │ │ ├── Masked80w.png │ │ │ └── Masked87w.png │ │ └── Contents.json │ ├── Info.plist │ ├── LaunchScreen.storyboard │ └── main.m ├── GitSharkTests │ ├── GitSharkTests.m │ └── Info.plist ├── Podfile ├── Podfile.lock └── resetXcode.sh ├── metro.config.js ├── package.json ├── patches └── @gorhom+bottom-sheet+4.1.5.patch ├── promo ├── changes.png ├── commit.png ├── icon.png ├── repositories.png └── settings.png ├── public ├── favicon.ico ├── fonts │ ├── icomoon.ttf │ ├── rubik_Italic.ttf │ ├── rubik_black.ttf │ ├── rubik_blackItalic.ttf │ ├── rubik_bold.ttf │ ├── rubik_boldItalic.ttf │ ├── rubik_light.ttf │ ├── rubik_lightItalic.ttf │ ├── rubik_medium.ttf │ ├── rubik_medium_italic.ttf │ └── rubik_regular.ttf ├── index.html ├── manifest.json └── robots.txt ├── react-native.config.js ├── src ├── App.tsx ├── components │ ├── animated-dropdown-arrow │ │ ├── animated-dropdown-arrow.tsx │ │ └── index.ts │ ├── app-bar │ │ ├── app-bar.tsx │ │ └── index.ts │ ├── checkmark-base │ │ ├── checkmark-base.tsx │ │ └── index.ts │ ├── commit-list │ │ ├── commit-card │ │ │ ├── commit-card-push-pull.tsx │ │ │ └── commit-card.tsx │ │ ├── commit-list.tsx │ │ └── index.ts │ ├── commit-pill │ │ ├── commit-pill.tsx │ │ ├── im-the-map.tsx │ │ └── index.ts │ ├── dialog │ │ ├── dialog.tsx │ │ └── index.ts │ ├── dropdown-content │ │ ├── dropdown-content.tsx │ │ └── index.ts │ ├── error-message-box │ │ ├── error-message-box.tsx │ │ └── index.ts │ ├── error-prompt │ │ ├── error-prompt-common.tsx │ │ ├── error-prompt-mobile.tsx │ │ ├── error-prompt-tablet.tsx │ │ ├── error-prompt.stories.tsx │ │ ├── error-prompt.tsx │ │ └── index.ts │ ├── extended-action-fab │ │ ├── extended-action-fab.tsx │ │ └── index.ts │ ├── file-change-list-item │ │ ├── file-change-list-item-with-checkbox.tsx │ │ ├── file-change-list-item.tsx │ │ └── index.ts │ ├── folder-select-button │ │ ├── folder-select-button.tsx │ │ └── index.ts │ ├── grow-width-content │ │ ├── grow-width-content.tsx │ │ └── index.ts │ ├── navigation-aware-portal │ │ ├── index.ts │ │ └── navigation-aware-portal.tsx │ ├── overlay-dropdown-content │ │ ├── index.ts │ │ └── overlay-dropdown-content.tsx │ ├── progress-error-dialog │ │ ├── index.ts │ │ └── progress-error-dialog.tsx │ ├── push-pull-arrows │ │ ├── index.ts │ │ └── push-pull-arrows.tsx │ ├── rename-repository-dialog │ │ ├── index.ts │ │ └── rename-repository-dialog.tsx │ ├── repository-header │ │ ├── header-action-number │ │ │ └── header-action-number.tsx │ │ ├── index.ts │ │ └── repository-header.tsx │ ├── scrim │ │ ├── index.ts │ │ └── scrim.tsx │ ├── seaside-switch │ │ ├── index.ts │ │ ├── seaside-switch.stories.tsx │ │ └── seaside-switch.tsx │ ├── seaside-text-input │ │ ├── index.ts │ │ ├── seaside-text-input.stories.tsx │ │ └── seaside-text-input.tsx │ ├── shack-snackbar │ │ ├── index.ts │ │ └── shack-snackbar.tsx │ ├── shark-bottom-sheet │ │ ├── index.ts │ │ └── shark-bottom-sheet.tsx │ ├── shark-button-toggle-group │ │ ├── index.ts │ │ └── shark-button-toggle-group.tsx │ ├── shark-button │ │ ├── index.ts │ │ ├── shark-button.stories.tsx │ │ └── shark-button.tsx │ ├── shark-checkbox │ │ ├── index.ts │ │ ├── shark-checkbox.stories.tsx │ │ └── shark-checkbox.tsx │ ├── shark-divider │ │ ├── index.ts │ │ └── shark-divider.tsx │ ├── shark-icon-button │ │ ├── index.ts │ │ └── shark-icon-button.tsx │ ├── shark-icon │ │ ├── icon.ts │ │ ├── index.ts │ │ ├── selection.json │ │ └── shark-icon.tsx │ ├── shark-menu │ │ ├── index.ts │ │ └── shark-menu.tsx │ ├── shark-picker │ │ ├── index.ts │ │ └── shark-picker.tsx │ ├── shark-profile-pic │ │ ├── index.ts │ │ └── shark-profile-pic.tsx │ ├── shark-radio │ │ ├── index.ts │ │ ├── shark-radio.stories.tsx │ │ └── shark-radio.tsx │ ├── shark-safe-top │ │ ├── index.ts │ │ └── shark-safe-top.tsx │ ├── shark-subheader │ │ ├── index.ts │ │ └── shark-subheader.tsx │ ├── shark-text-input │ │ ├── index.ts │ │ ├── shark-text-input.stories.tsx │ │ └── shark-text-input.tsx │ ├── sr-only │ │ ├── index.ts │ │ └── sr-only.tsx │ └── storybook-provider │ │ ├── index.ts │ │ └── storybook-provider.tsx ├── constants │ ├── contexts │ │ ├── dialogs-context.tsx │ │ ├── index.ts │ │ ├── repo-header-context.ts │ │ ├── set-dark-mode-context.ts │ │ ├── style-of-staging-context.ts │ │ └── user-context.ts │ ├── index.ts │ ├── oauth.ts │ ├── repo-config.ts │ ├── storage-keys.ts │ ├── text-styles.ts │ └── theme.ts ├── entities │ ├── Commit.ts │ ├── Repo.ts │ └── index.ts ├── hooks │ ├── index.ts │ ├── use-foreground-effect.ts │ ├── use-get-android-permissions.ts │ ├── use-github-user-data.ts │ ├── use-local-dark-mode.ts │ ├── use-manual-user-data.ts │ ├── use-thunk-dispatch.ts │ └── use-user.ts ├── services │ ├── debug.ts │ ├── git │ │ ├── add-to-staged-android.ts │ │ ├── add-to-staged.ts │ │ ├── checkout-branch-android.ts │ │ ├── checkout-branch.ts │ │ ├── clone-repo-android.ts │ │ ├── clone-repo-ios.ts │ │ ├── clone-repo.ts │ │ ├── commit-android.ts │ │ ├── commit.ts │ │ ├── create-branch-android.ts │ │ ├── create-branch.ts │ │ ├── create-remote-android.ts │ │ ├── create-remote.ts │ │ ├── create-repo.ts │ │ ├── current-branch-android.ts │ │ ├── current-branch.ts │ │ ├── delete-local-branch-android.ts │ │ ├── delete-local-branch.ts │ │ ├── delete-repo.ts │ │ ├── fetch-android.ts │ │ ├── fetch.ts │ │ ├── get-commit-header-body.ts │ │ ├── get-file-state-changes-android.ts │ │ ├── get-file-state-changes.ts │ │ ├── get-push-pull.ts │ │ ├── get-tracked-branch-android.ts │ │ ├── get-tracked-branch.ts │ │ ├── git-init-android.ts │ │ ├── git-init.ts │ │ ├── git-log-android.ts │ │ ├── git-log.ts │ │ ├── index.ts │ │ ├── list-local-branches-android.ts │ │ ├── list-local-branches.ts │ │ ├── list-remote-branches-android.ts │ │ ├── list-remote-branches.ts │ │ ├── list-remotes-android.ts │ │ ├── list-remotes.ts │ │ ├── pull-android.ts │ │ ├── pull.ts │ │ ├── push-android.ts │ │ ├── push.ts │ │ ├── read-commit-android.ts │ │ ├── read-commit.ts │ │ ├── remove-from-staged-android.ts │ │ ├── remove-from-staged.ts │ │ ├── rename-branch-android.ts │ │ ├── rename-branch.ts │ │ ├── rename-repo.ts │ │ ├── reset-files-android.ts │ │ ├── reset-files.ts │ │ ├── rev-list-android.ts │ │ ├── rev-list.ts │ │ ├── status-android.ts │ │ └── status.ts │ ├── github │ │ ├── base-request.ts │ │ ├── constants.ts │ │ ├── create-issue-with-api.ts │ │ ├── get-current-user-emails.ts │ │ ├── get-current-user.ts │ │ ├── get-file-contents.ts │ │ ├── index.ts │ │ └── open-issue.ts │ ├── index.ts │ └── translations.ts ├── store │ ├── database-slice.ts │ ├── debug.ts │ ├── git-branches-slice.ts │ ├── git-changes-slice.ts │ ├── git-log-slice.ts │ ├── index.ts │ ├── repo-list-slice.ts │ ├── repo-slice.ts │ ├── root-reducer.ts │ └── store.ts ├── types │ ├── cached-github-user.ts │ ├── errors.ts │ ├── index.ts │ ├── json.d.ts │ ├── manual-user.ts │ ├── media.d.ts │ ├── navigation.ts │ ├── restart.d.ts │ ├── service-types.ts │ ├── storeTypes.ts │ └── thunk-types.ts ├── utils │ └── index.ts └── views │ ├── account │ ├── account.tsx │ └── github-logout │ │ └── github-logout.tsx │ ├── branches │ ├── branches.tsx │ ├── branches.ui.tsx │ ├── components │ │ ├── branch-list-item │ │ │ ├── branch-list-item.tsx │ │ │ └── index.ts │ │ ├── confirm-checkout-dialog │ │ │ ├── confirm-checkout-dialog.tsx │ │ │ └── index.ts │ │ ├── create-branch-dialog │ │ │ ├── create-branch-dialog.tsx │ │ │ └── index.ts │ │ ├── create-remote-dialog │ │ │ ├── create-remote-dialog.tsx │ │ │ └── index.ts │ │ ├── delete-branch-dialog │ │ │ ├── delete-branch-dialog.tsx │ │ │ └── index.ts │ │ ├── on-checkout-action-dialog │ │ │ ├── index.ts │ │ │ └── on-checkout-action-dialog.tsx │ │ ├── on-create-remote-action-dialog │ │ │ ├── index.ts │ │ │ └── on-create-remote-action-dialog.tsx │ │ ├── remote-branch-list-item │ │ │ ├── index.ts │ │ │ └── remote-branch-list-item.tsx │ │ └── rename-branch-dialog │ │ │ ├── index.ts │ │ │ └── rename-branch-dialog.tsx │ └── index.ts │ ├── commit-action │ ├── commit-action.tsx │ ├── commit-action.ui.tsx │ └── on-commit-action-dialog │ │ ├── index.ts │ │ └── on-commit-action-dialog.tsx │ ├── commit-details │ ├── commit-details.stories.tsx │ ├── commit-details.tsx │ ├── commit-details.ui.tsx │ └── components │ │ └── commit-details-header │ │ ├── commit-detail-dual-author │ │ ├── commit-detail-dual-author.tsx │ │ └── index.ts │ │ ├── commit-detail-single-author │ │ ├── commit-detail-single-author.tsx │ │ └── index.ts │ │ ├── commit-details-header.tsx │ │ ├── commit-details-more-info │ │ ├── commit-details-more-info.tsx │ │ └── index.ts │ │ ├── commit-message-dropdown │ │ ├── commit-message-dropdown.tsx │ │ └── index.ts │ │ └── index.ts │ ├── repository-changes │ ├── components │ │ ├── file-actions-bar │ │ │ ├── file-actions-bar-toggle-button │ │ │ │ ├── file-actions-bar-toggle-button.tsx │ │ │ │ └── index.ts │ │ │ ├── file-actions-bar.tsx │ │ │ ├── index.ts │ │ │ └── stage-button-toggle │ │ │ │ ├── index.ts │ │ │ │ └── stage-button-toggle.tsx │ │ └── staging-screen-options │ │ │ ├── index.ts │ │ │ ├── stage-sheet-view.tsx │ │ │ ├── stage-split-view.tsx │ │ │ ├── staged-changes │ │ │ ├── index.ts │ │ │ └── staged-changes.tsx │ │ │ └── unstaged-changes │ │ │ ├── index.ts │ │ │ └── unstaged-changes.tsx │ ├── repository-changes-split.stories.tsx │ └── repository-changes.tsx │ ├── repository-history │ ├── components │ │ └── history-branch-dropdown │ │ │ ├── history-branch-dropdown.tsx │ │ │ └── index.ts │ ├── repository-history.stories.tsx │ ├── repository-history.tsx │ └── repository-history.ui.tsx │ ├── repository-list │ ├── components │ │ ├── add-existing-repository-dialog │ │ │ ├── add-existing-repository-dialog.tsx │ │ │ └── index.ts │ │ ├── clone-repository-dialog │ │ │ ├── clone-repository-dialog.tsx │ │ │ └── index.ts │ │ ├── clone-repository-progress-dialog │ │ │ ├── clone-repository-progress-dialog.tsx │ │ │ └── index.ts │ │ ├── create-repository-dialog │ │ │ ├── create-repository-dialog.tsx │ │ │ └── index.ts │ │ ├── delete-repository-dialog │ │ │ ├── delete-repository-dialog.tsx │ │ │ └── index.ts │ │ ├── dialogs-and-fab │ │ │ ├── dialogs-and-fab.tsx │ │ │ ├── fab-actions.tsx │ │ │ ├── index.ts │ │ │ ├── new-repo-fab.tsx │ │ │ ├── repo-list-extended-fab.tsx │ │ │ └── types.ts │ │ ├── repo-card │ │ │ ├── index.ts │ │ │ ├── repo-card.styles.ts │ │ │ └── repo-card.tsx │ │ └── repo-list-loading │ │ │ ├── index.ts │ │ │ ├── repo-list-card.tsx │ │ │ └── repo-list-loading.tsx │ ├── repository-list.stories.tsx │ ├── repository-list.tsx │ └── repository-list.ui.tsx │ ├── repository │ ├── components │ │ ├── fetch-dialog │ │ │ ├── fetch-dialog.tsx │ │ │ └── index.ts │ │ ├── on-fetch-action-dialog │ │ │ ├── index.ts │ │ │ └── on-fetch-action-dialog.tsx │ │ ├── on-pull-action-dialog │ │ │ ├── index.ts │ │ │ └── on-pull-action-dialog.tsx │ │ ├── on-push-action-dialog │ │ │ ├── index.ts │ │ │ └── on-push-action-dialog.tsx │ │ └── push-dialog │ │ │ ├── index.ts │ │ │ └── push-dialog.tsx │ ├── repository.tsx │ └── repository.ui.tsx │ ├── settings │ ├── account-button │ │ └── account-button.tsx │ └── settings.tsx │ └── staging-layout │ ├── components │ └── slide-up-down-settings-animation │ │ ├── index.ts │ │ └── slide-up-down-settings-animation.tsx │ └── staging-layout.tsx ├── translations ├── de.json ├── en.json ├── es.json └── pt.json ├── tsconfig.json └── yarn.lock /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | '@react-native-community', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'prettier/@typescript-eslint', 7 | 'plugin:prettier/recommended', 8 | ], 9 | parser: '@typescript-eslint/parser', 10 | plugins: ['prettier'], 11 | rules: { 12 | 'no-extra-boolean-cast': 'off', 13 | '@typescript-eslint/no-use-before-define': 'off', 14 | '@typescript-eslint/no-empty-function': 'off', 15 | '@typescript-eslint/ban-ts-comment': 'off', 16 | '@typescript-eslint/camelcase': 'off', 17 | '@typescript-eslint/ban-ts-ignore': 'off', 18 | '@typescript-eslint/no-var-requires': 'off', 19 | '@typescript-eslint/explicit-function-return-type': 'off', 20 | '@typescript-eslint/class-name-casing': 'off', 21 | 'react-native/no-inline-styles': 'off', 22 | '@typescript-eslint/no-non-null-assertion': 'off', 23 | '@typescript-eslint/explicit-module-boundary-types': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Windows files should use crlf line endings 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | *.bat text eol=crlf 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [crutchcorn] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] Bug report" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Error code** 21 | 22 |
23 | {{Put the simple error code here}} 24 | {{Put the stack trace here}} 25 |
26 | 27 | **Screenshots** 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | **Smartphone (please complete the following information):** 31 | - Device: [e.g. iPhone6] 32 | - OS: [e.g. iOS8.1] 33 | - Version [e.g. 22, leave "N/A" if unsure] 34 | 35 | **Additional context** 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEAT] Feature request" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the main branch 8 | push: 9 | branches: [ main ] 10 | pull_request: 11 | branches: [ main ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v2 27 | - uses: actions/setup-node@v2 28 | with: 29 | node-version: '12' 30 | # Install packages 31 | - name: Install packages 32 | run: yarn install --frozen-lockfile 33 | 34 | # Runs a set of commands using the runners shell 35 | - name: Build storybook 36 | run: yarn build-storybook 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.pbxuser 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | *.hprof 33 | 34 | # Visual Studio Code 35 | # 36 | .vscode/ 37 | 38 | # node.js 39 | # 40 | node_modules/ 41 | npm-debug.log 42 | yarn-error.log 43 | 44 | # BUCK 45 | buck-out/ 46 | \.buckd/ 47 | *.keystore 48 | !debug.keystore 49 | 50 | # fastlane 51 | # 52 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 53 | # screenshots whenever they are needed. 54 | # For more information about the recommended setup visit: 55 | # https://docs.fastlane.tools/best-practices/source-control/ 56 | 57 | */fastlane/report.xml 58 | */fastlane/Preview.html 59 | */fastlane/screenshots 60 | 61 | # Bundle artifact 62 | *.jsbundle 63 | 64 | # CocoaPods 65 | /ios/Pods/ 66 | /storybook-static 67 | 68 | # Carthage 69 | /ios/Carthage/Checkouts/ 70 | /ios/Carthage/Build/ 71 | 72 | # Java Heapmap 73 | *.hprof 74 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | bracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | endOfLine: 'auto', 7 | arrowParens: "avoid" 8 | }; 9 | -------------------------------------------------------------------------------- /.storybook/default-noop.js: -------------------------------------------------------------------------------- 1 | export default null; -------------------------------------------------------------------------------- /.storybook/gesture-handler.js: -------------------------------------------------------------------------------- 1 | export {ScrollView} from 'react-native-web'; 2 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../src/**/*.stories.tsx'], 3 | addons: [ 4 | '@storybook/addon-controls', 5 | '@storybook/addon-viewport', 6 | '@storybook/addon-actions', 7 | '@storybook/addon-links' 8 | ], 9 | }; 10 | -------------------------------------------------------------------------------- /.storybook/masked-view.js: -------------------------------------------------------------------------------- 1 | const MaskedView = ({children}) => { 2 | return <>{children}; 3 | } 4 | export default MaskedView; -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 56 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | import relativeTime from 'dayjs/plugin/relativeTime'; 3 | dayjs.extend(relativeTime); 4 | -------------------------------------------------------------------------------- /.storybook/react-i18next.js: -------------------------------------------------------------------------------- 1 | const en = require('../translations/en.json'); 2 | 3 | module.exports = { 4 | initReactI18next: () => {}, 5 | useTranslation: () => { 6 | return { 7 | t: (key, obj) => { 8 | // TODO: This does not handle dot notation or anything like that 9 | const str = en[key]; 10 | if (obj && Object.keys(obj)) { 11 | let finalStr = str; 12 | for (const key of Object.keys(obj)) { 13 | finalStr = finalStr.replace(`{{${key}}}`, obj[key]); 14 | } 15 | return finalStr; 16 | } 17 | return en[key]; 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.storybook/reanimated.js: -------------------------------------------------------------------------------- 1 | import {View} from 'react-native'; 2 | 3 | const Reanimated = { 4 | Value: () => {}, 5 | event: () => {}, 6 | add: () => {}, 7 | eq: () => {}, 8 | set: () => {}, 9 | cond: () => {}, 10 | interpolate: () => {}, 11 | View: View, 12 | Extrapolate: {CLAMP: () => {}}, 13 | Clock: () => {}, 14 | greaterThan: () => {}, 15 | lessThan: () => {}, 16 | startClock: () => {}, 17 | stopClock: () => {}, 18 | clockRunning: () => {}, 19 | not: () => {}, 20 | or: () => {}, 21 | and: () => {}, 22 | spring: () => {}, 23 | decay: () => {}, 24 | defined: () => {}, 25 | call: () => {}, 26 | Code: View, 27 | block: () => {}, 28 | abs: () => {}, 29 | greaterOrEq: () => {}, 30 | lessOrEq: () => {}, 31 | debug: () => {}, 32 | Transition: { 33 | Out: 'Out', 34 | }, 35 | }; 36 | 37 | export default Reanimated; -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## iOS Setup 2 | 3 | In order to deploy to iOS, you'll need to install: 4 | 5 | - XCode 6 | - [CocoaPods](https://guides.cocoapods.org/using/getting-started.html) 7 | - [Homebrew](https://brew.sh/) 8 | - `brew install cmake libtool autoconf automake pkg-config libssh2 carthage` 9 | 10 | Then, once that's done, you'll need to: 11 | 12 | - `cd ios` 13 | - `pod install` 14 | - `carthage update` 15 | 16 | ## Font Update 17 | 18 | We use a custom font for our icons in the app. Some resources for that include: 19 | 20 | - https://medium.com/mabiloft/we-designed-an-icon-font-with-figma-and-fontello-and-it-has-not-been-a-piece-of-cake-b2948973738e 21 | 22 | Once the files are exported from Figma and downloaded from Icomoon, a few files need to be replaced: 23 | 24 | - `assets\fonts\icomoon.ttf` - iOS Font file 25 | - `android\app\src\main\assets\fonts\icomoon.ttf` - Android Font File 26 | - `public\fonts\icomoon.ttf` - Storybook Font File 27 | - `src\components\shark-icon\selection.json` - Icon shortname mapping 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | GitShark 3 |

4 |

5 | GitShark 6 |

7 | 8 | GitShark is a Git GUI in a similar vein to [Git Fork](https://git-fork.com/), [GitKraken](https://www.gitkraken.com/), and others. The big difference? It runs on mobile! 9 | 10 | We're currently targeting Android as the initial launch platform, but have long-term goals to port this to iOS as well as desktop OSes such as Windows and MacOS. We're able to do this because we're building on top of React Native, which allows us to more easily port our code. 11 | 12 | > **This app is still under active development. We are currently running a closed alpha for Android.** In the near future, we hope to run a closed alpha for iOS as well. 13 | 14 | # Screenshots 15 | 16 | While design is an ever-changing goal post and the current design is subject to change prior to release, these are screenshots from the currently running alpha: 17 | 18 |
19 | The list of repositories 20 | The changes screen 21 | The commit page 22 | The settings screen 23 |
24 | 25 | We also have a wide variety of animations thrown throughout the application to make the experience more pleasant. -------------------------------------------------------------------------------- /android/app/_BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "dev.oceanbit.gitshark", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "dev.oceanbit.gitshark", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/debug.keystore -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/assets/fonts/icomoon.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/rubik_Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/assets/fonts/rubik_Italic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/rubik_black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/assets/fonts/rubik_black.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/rubik_blackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/assets/fonts/rubik_blackItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/rubik_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/assets/fonts/rubik_bold.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/rubik_boldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/assets/fonts/rubik_boldItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/rubik_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/assets/fonts/rubik_light.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/rubik_lightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/assets/fonts/rubik_lightItalic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/rubik_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/assets/fonts/rubik_medium.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/rubik_medium_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/assets/fonts/rubik_medium_italic.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/rubik_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/assets/fonts/rubik_regular.ttf -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /android/app/src/main/java/dev/oceanbit/gitshark/DirectoryPickerPackage.java: -------------------------------------------------------------------------------- 1 | package dev.oceanbit.gitshark; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.NativeModule; 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.uimanager.ViewManager; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public class DirectoryPickerPackage implements ReactPackage { 13 | 14 | @Override 15 | public List createViewManagers(ReactApplicationContext reactContext) { 16 | return Collections.emptyList(); 17 | } 18 | 19 | @Override 20 | public List createNativeModules( 21 | ReactApplicationContext reactContext) { 22 | List modules = new ArrayList<>(); 23 | 24 | modules.add(new DirectoryPickerModule(reactContext)); 25 | 26 | return modules; 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /android/app/src/main/java/dev/oceanbit/gitshark/Git/GhTokenUtils.java: -------------------------------------------------------------------------------- 1 | package dev.oceanbit.gitshark.Git; 2 | 3 | import com.facebook.react.bridge.ReactApplicationContext; 4 | import com.reactlibrary.securekeystore.RNSecureKeyStoreModule; 5 | 6 | import java.io.FileNotFoundException; 7 | 8 | public class GhTokenUtils { 9 | static public String getGitHubToken(ReactApplicationContext reactContext) { 10 | 11 | RNSecureKeyStoreModule keyStoreModule = new RNSecureKeyStoreModule(reactContext); 12 | 13 | String ghToken = ""; 14 | try { 15 | ghToken = keyStoreModule.getPlainText("ghToken"); 16 | // User is not logged into GH 17 | } catch (FileNotFoundException e) { 18 | ; 19 | } catch (Throwable e) { 20 | e.printStackTrace(); 21 | } 22 | 23 | return ghToken; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /android/app/src/main/java/dev/oceanbit/gitshark/Git/GitAddToStaged.java: -------------------------------------------------------------------------------- 1 | package dev.oceanbit.gitshark.Git; 2 | 3 | import com.facebook.react.bridge.Promise; 4 | import com.facebook.react.bridge.ReadableArray; 5 | 6 | import org.eclipse.jgit.api.AddCommand; 7 | import org.eclipse.jgit.api.Git; 8 | import org.eclipse.jgit.api.RmCommand; 9 | 10 | import java.io.File; 11 | 12 | public class GitAddToStaged { 13 | static public void add( 14 | String path, ReadableArray changes, Promise promise 15 | ) { 16 | Git git; 17 | try { 18 | git = Git.open(new File(path)); 19 | } catch (Throwable e) { 20 | promise.reject(e); 21 | return; 22 | } 23 | 24 | try { 25 | Integer addItems = 0; 26 | Integer rmItems = 0; 27 | AddCommand addCmd = git.add(); 28 | RmCommand rmCmd = git.rm(); 29 | 30 | for (int i = 0; i < changes.size(); i++) { 31 | String relPath = changes.getString(i); 32 | File file = new File(path, relPath); 33 | Boolean toAdd = file.exists(); 34 | 35 | if (toAdd) { 36 | addItems++; 37 | addCmd.addFilepattern(relPath); 38 | } else { 39 | rmItems++; 40 | rmCmd.addFilepattern(relPath); 41 | } 42 | } 43 | 44 | if (addItems > 0) { 45 | addCmd.call(); 46 | } 47 | if (rmItems > 0) { 48 | rmCmd.call(); 49 | } 50 | promise.resolve(true); 51 | } catch (Throwable e) { 52 | promise.reject(e); 53 | return; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /android/app/src/main/java/dev/oceanbit/gitshark/Git/GitCommit.java: -------------------------------------------------------------------------------- 1 | package dev.oceanbit.gitshark.Git; 2 | 3 | import com.facebook.react.bridge.Promise; 4 | import com.facebook.react.bridge.ReactApplicationContext; 5 | 6 | import org.eclipse.jgit.api.CommitCommand; 7 | import org.eclipse.jgit.api.Git; 8 | import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; 9 | 10 | import java.io.File; 11 | 12 | public class GitCommit { 13 | private static ReactApplicationContext reactContext; 14 | 15 | public GitCommit(ReactApplicationContext context) { 16 | reactContext = context; 17 | } 18 | 19 | public void commit( 20 | String path, String authorEmail, String authorName, String message, Promise promise 21 | ) { 22 | Git git; 23 | try { 24 | git = Git.open(new File(path)); 25 | } catch (Throwable e) { 26 | promise.reject(e); 27 | return; 28 | } 29 | 30 | try { 31 | CommitCommand commitCmd = git.commit(); 32 | 33 | commitCmd 34 | // Until we can figure out a way to call into an SSH instance or something, 35 | // it makes no sense to try to use the hooks 36 | .setNoVerify(true) 37 | .setAuthor(authorName, authorEmail) 38 | .setCommitter(authorName, authorEmail) 39 | .setMessage(message); 40 | 41 | commitCmd.call(); 42 | promise.resolve(true); 43 | } catch (Throwable e) { 44 | promise.reject(e); 45 | return; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /android/app/src/main/java/dev/oceanbit/gitshark/Git/GitCurrentBranch.java: -------------------------------------------------------------------------------- 1 | package dev.oceanbit.gitshark.Git; 2 | 3 | import com.facebook.react.bridge.Promise; 4 | import com.facebook.react.bridge.ReactApplicationContext; 5 | 6 | import org.eclipse.jgit.api.Git; 7 | import org.eclipse.jgit.lib.Repository; 8 | 9 | import java.io.File; 10 | 11 | public class GitCurrentBranch { 12 | private static ReactApplicationContext reactContext; 13 | 14 | public GitCurrentBranch(ReactApplicationContext context) { 15 | reactContext = context; 16 | } 17 | 18 | public static void currentBranch( 19 | String path, Promise promise 20 | ) { 21 | Git git; 22 | Repository repo; 23 | String localBranch; 24 | try { 25 | git = Git.open(new File(path)); 26 | repo = git.getRepository(); 27 | localBranch = repo.getBranch(); 28 | promise.resolve(localBranch); 29 | } catch (Throwable e) { 30 | promise.reject(e); 31 | return; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /android/app/src/main/java/dev/oceanbit/gitshark/Git/GitGetTrackedBranch.java: -------------------------------------------------------------------------------- 1 | package dev.oceanbit.gitshark.Git; 2 | 3 | import com.facebook.react.bridge.Promise; 4 | 5 | import org.eclipse.jgit.api.Git; 6 | import org.eclipse.jgit.lib.BranchConfig; 7 | import org.eclipse.jgit.lib.Repository; 8 | 9 | import java.io.File; 10 | 11 | public class GitGetTrackedBranch { 12 | static public void getTrackedBranch( 13 | String path, 14 | String branchName, 15 | Promise promise 16 | ) { 17 | Git git; 18 | Repository repo; 19 | try { 20 | git = Git.open(new File(path)); 21 | repo = git.getRepository(); 22 | } catch (Throwable e) { 23 | promise.reject(e); 24 | return; 25 | } 26 | 27 | try { 28 | String trackedBranch = new BranchConfig(repo.getConfig(), branchName).getTrackingBranch(); 29 | 30 | promise.resolve(trackedBranch); 31 | } catch (Throwable e) { 32 | promise.reject(e); 33 | return; 34 | } 35 | 36 | promise.resolve(true); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /android/app/src/main/java/dev/oceanbit/gitshark/Git/GitInit.java: -------------------------------------------------------------------------------- 1 | package dev.oceanbit.gitshark.Git; 2 | 3 | import com.facebook.react.bridge.Promise; 4 | import com.facebook.react.bridge.ReactApplicationContext; 5 | import com.facebook.react.bridge.WritableArray; 6 | import com.facebook.react.bridge.WritableNativeArray; 7 | 8 | import org.eclipse.jgit.api.Git; 9 | import org.eclipse.jgit.lib.Ref; 10 | 11 | import java.io.File; 12 | import java.util.List; 13 | 14 | public class GitInit { 15 | private static ReactApplicationContext reactContext; 16 | 17 | public GitInit(ReactApplicationContext context) { 18 | reactContext = context; 19 | } 20 | 21 | public static void gitInit( 22 | String path, Promise promise 23 | ) { 24 | Git git; 25 | try { 26 | git = Git.init().setDirectory(new File(path)).call(); 27 | promise.resolve(null); 28 | } catch (Throwable e) { 29 | promise.reject(e); 30 | return; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android/app/src/main/java/dev/oceanbit/gitshark/Git/GitLog.java: -------------------------------------------------------------------------------- 1 | package dev.oceanbit.gitshark.Git; 2 | 3 | import com.facebook.react.bridge.Promise; 4 | import com.facebook.react.bridge.WritableArray; 5 | import com.facebook.react.bridge.WritableNativeArray; 6 | 7 | import org.eclipse.jgit.api.Git; 8 | import org.eclipse.jgit.api.LogCommand; 9 | import org.eclipse.jgit.lib.Constants; 10 | import org.eclipse.jgit.lib.ObjectId; 11 | import org.eclipse.jgit.lib.Repository; 12 | import org.eclipse.jgit.revwalk.RevCommit; 13 | 14 | import java.io.File; 15 | 16 | public class GitLog { 17 | static public void gitLog( 18 | String path, String oidRef, Promise promise 19 | ) { 20 | Git git; 21 | Repository repo; 22 | try { 23 | git = Git.open(new File(path)); 24 | repo = git.getRepository(); 25 | } catch (Throwable e) { 26 | promise.reject(e); 27 | return; 28 | } 29 | WritableArray result; 30 | ObjectId ref; 31 | try { 32 | if (oidRef.isEmpty()) { 33 | ref = repo.resolve(Constants.HEAD); 34 | } else { 35 | ref = repo.resolve(oidRef); 36 | } 37 | 38 | LogCommand cmd = git.log().add(ref); 39 | 40 | Iterable commits = cmd.call(); 41 | result = new WritableNativeArray(); 42 | for (RevCommit commit : commits) { 43 | result.pushMap(GitUtils.revCommitToMap(commit)); 44 | } 45 | promise.resolve(result); 46 | } catch (Throwable e) { 47 | promise.reject(e); 48 | return; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /android/app/src/main/java/dev/oceanbit/gitshark/Git/GitReadCommit.java: -------------------------------------------------------------------------------- 1 | package dev.oceanbit.gitshark.Git; 2 | 3 | import com.facebook.react.bridge.Promise; 4 | import com.facebook.react.bridge.WritableMap; 5 | 6 | import org.eclipse.jgit.api.Git; 7 | import org.eclipse.jgit.lib.ObjectId; 8 | import org.eclipse.jgit.lib.Repository; 9 | import org.eclipse.jgit.revwalk.RevCommit; 10 | 11 | import java.io.File; 12 | 13 | public class GitReadCommit { 14 | static public void readCommit( 15 | String path, String oid, Promise promise 16 | ) { 17 | Git git; 18 | Repository repo; 19 | try { 20 | git = Git.open(new File(path)); 21 | repo = git.getRepository(); 22 | } catch (Throwable e) { 23 | promise.reject(e); 24 | return; 25 | } 26 | try { 27 | ObjectId newCommitId = repo.resolve(oid); 28 | Iterable mCommits = git.log().add(newCommitId).setMaxCount(1).call(); 29 | RevCommit revCommit = mCommits.iterator().next(); 30 | WritableMap commit = GitUtils.revCommitToMap(revCommit); 31 | promise.resolve(commit); 32 | return; 33 | } catch (Throwable e) { 34 | promise.reject(e); 35 | return; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /android/app/src/main/java/dev/oceanbit/gitshark/Git/GitRemoteBranch.java: -------------------------------------------------------------------------------- 1 | package dev.oceanbit.gitshark.Git; 2 | 3 | import com.facebook.react.bridge.Promise; 4 | import com.facebook.react.bridge.ReactApplicationContext; 5 | import com.facebook.react.bridge.WritableArray; 6 | import com.facebook.react.bridge.WritableNativeArray; 7 | 8 | import org.eclipse.jgit.api.Git; 9 | import org.eclipse.jgit.api.ListBranchCommand; 10 | import org.eclipse.jgit.lib.Ref; 11 | 12 | import java.io.File; 13 | import java.util.List; 14 | 15 | public class GitRemoteBranch { 16 | private static ReactApplicationContext reactContext; 17 | 18 | public GitRemoteBranch(ReactApplicationContext context) { 19 | reactContext = context; 20 | } 21 | 22 | public static void listRemoteBranches( 23 | String path, String remoteName, Promise promise 24 | ) { 25 | Git git; 26 | try { 27 | WritableArray branchNames = new WritableNativeArray(); 28 | 29 | git = Git.open(new File(path)); 30 | List branches = git.branchList().setListMode(ListBranchCommand.ListMode.REMOTE).call(); 31 | for (Ref branch: branches) { 32 | String branchName = branch.getName(); 33 | if (branchName.startsWith("refs/remotes/" + remoteName)) { 34 | branchNames.pushString(branch.getName().replaceFirst("^refs/remotes/" + remoteName + "/", "")); 35 | } 36 | } 37 | promise.resolve(branchNames); 38 | } catch (Throwable e) { 39 | promise.reject(e); 40 | return; 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /android/app/src/main/java/dev/oceanbit/gitshark/Git/GitRemoveFromStaged.java: -------------------------------------------------------------------------------- 1 | package dev.oceanbit.gitshark.Git; 2 | 3 | import com.facebook.react.bridge.Promise; 4 | import com.facebook.react.bridge.ReadableArray; 5 | 6 | import org.eclipse.jgit.api.Git; 7 | import org.eclipse.jgit.api.ResetCommand; 8 | import org.eclipse.jgit.lib.Repository; 9 | 10 | import java.io.File; 11 | 12 | public class GitRemoveFromStaged { 13 | static public void remove( 14 | String path, ReadableArray changes, Promise promise 15 | ) { 16 | Git git; 17 | try { 18 | git = Git.open(new File(path)); 19 | } catch (Throwable e) { 20 | promise.reject(e); 21 | return; 22 | } 23 | 24 | try { 25 | ResetCommand cmd = git.reset(); 26 | 27 | for (int i = 0; i < changes.size(); i++) { 28 | cmd.addPath(changes.getString(i)); 29 | } 30 | 31 | cmd.call(); 32 | promise.resolve(true); 33 | } catch (Throwable e) { 34 | promise.reject(e); 35 | return; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /android/app/src/main/java/dev/oceanbit/gitshark/GitPackage.java: -------------------------------------------------------------------------------- 1 | package dev.oceanbit.gitshark; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.NativeModule; 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.uimanager.ViewManager; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public class GitPackage implements ReactPackage { 13 | 14 | @Override 15 | public List createViewManagers(ReactApplicationContext reactContext) { 16 | return Collections.emptyList(); 17 | } 18 | 19 | @Override 20 | public List createNativeModules( 21 | ReactApplicationContext reactContext) { 22 | List modules = new ArrayList<>(); 23 | 24 | modules.add(new GitModule(reactContext)); 25 | 26 | return modules; 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /android/app/src/main/java/dev/oceanbit/gitshark/MainActivity.java: -------------------------------------------------------------------------------- 1 | package dev.oceanbit.gitshark; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | import com.rnimmersivebars.ImmersiveBars; 6 | import android.os.Bundle; 7 | 8 | public class MainActivity extends ReactActivity { 9 | 10 | /** 11 | * Returns the name of the main component registered from JavaScript. This is used to schedule 12 | * rendering of the component. 13 | */ 14 | @Override 15 | protected String getMainComponentName() { 16 | return "GitShark"; 17 | } 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | ImmersiveBars.changeBarColors(this, false); 22 | super.onCreate(savedInstanceState); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /android/app/src/main/java/dev/oceanbit/gitshark/Throttle.java: -------------------------------------------------------------------------------- 1 | package dev.oceanbit.gitshark; 2 | 3 | import android.view.animation.AnimationUtils; 4 | 5 | /** 6 | * This is present because otherwise it will spam so many event listeners that it will actually slow down 7 | * the UI thread trying to draw all the updates. It's not pretty 8 | * 9 | * Thank you kindly for the code, friend 10 | * 11 | * @see @link http://blog.moagrius.com/android/android-throttle-and-debounce/ 12 | */ 13 | public class Throttle { 14 | private long mLastFiredTimestamp; 15 | private long mInterval; 16 | 17 | public Throttle(long interval) { 18 | mInterval = interval; 19 | } 20 | 21 | public void attempt(Runnable runnable) { 22 | if (hasSatisfiedInterval()) { 23 | runnable.run(); 24 | mLastFiredTimestamp = getNow(); 25 | } 26 | } 27 | 28 | private boolean hasSatisfiedInterval() { 29 | long elapsed = getNow() - mLastFiredTimestamp; 30 | return elapsed >= mInterval; 31 | } 32 | 33 | private long getNow() { 34 | return AnimationUtils.currentAnimationTimeMillis(); 35 | } 36 | } -------------------------------------------------------------------------------- /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/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/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/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/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/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/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/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/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/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/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/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/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/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/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/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/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/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/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/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | GitShark 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "30.0.2" 6 | minSdkVersion = 23 7 | compileSdkVersion = 29 8 | targetSdkVersion = 29 9 | ndkVersion = "21.4.7075529" 10 | } 11 | repositories { 12 | google() 13 | mavenCentral() 14 | } 15 | dependencies { 16 | classpath('com.android.tools.build:gradle:4.2.2') 17 | 18 | // NOTE: Do not place your application dependencies here; they belong 19 | // in the individual module build.gradle files 20 | } 21 | } 22 | 23 | allprojects { 24 | repositories { 25 | mavenCentral() 26 | mavenLocal() 27 | maven { 28 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 29 | url("$rootDir/../node_modules/react-native/android") 30 | } 31 | maven { 32 | // Android JSC is installed from npm 33 | url("$rootDir/../node_modules/jsc-android/dist") 34 | } 35 | jcenter() { 36 | content { 37 | includeModule("com.yqritc", "android-scalablevideoview") 38 | } 39 | } 40 | google() 41 | maven { url 'https://www.jitpack.io' } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.99.0 29 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'GitShark' 2 | include ':react-native-restart' 3 | project(':react-native-restart').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-restart/android') 4 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 5 | include ':app' 6 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GitShark", 3 | "displayName": "GitShark" 4 | } 5 | -------------------------------------------------------------------------------- /assets/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/fonts/icomoon.ttf -------------------------------------------------------------------------------- /assets/fonts/rubik_Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/fonts/rubik_Italic.ttf -------------------------------------------------------------------------------- /assets/fonts/rubik_black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/fonts/rubik_black.ttf -------------------------------------------------------------------------------- /assets/fonts/rubik_blackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/fonts/rubik_blackItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/rubik_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/fonts/rubik_bold.ttf -------------------------------------------------------------------------------- /assets/fonts/rubik_boldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/fonts/rubik_boldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/rubik_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/fonts/rubik_light.ttf -------------------------------------------------------------------------------- /assets/fonts/rubik_lightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/fonts/rubik_lightItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/rubik_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/fonts/rubik_medium.ttf -------------------------------------------------------------------------------- /assets/fonts/rubik_medium_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/fonts/rubik_medium_italic.ttf -------------------------------------------------------------------------------- /assets/fonts/rubik_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/fonts/rubik_regular.ttf -------------------------------------------------------------------------------- /assets/images/default-profile-pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/images/default-profile-pic.png -------------------------------------------------------------------------------- /assets/images/split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/images/split.png -------------------------------------------------------------------------------- /assets/images/split_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/images/split_dark.png -------------------------------------------------------------------------------- /assets/videos/sheet_down.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/videos/sheet_down.mp4 -------------------------------------------------------------------------------- /assets/videos/sheet_down_dark.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/videos/sheet_down_dark.mp4 -------------------------------------------------------------------------------- /assets/videos/sheet_up.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/videos/sheet_up.mp4 -------------------------------------------------------------------------------- /assets/videos/sheet_up_dark.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/assets/videos/sheet_up_dark.mp4 -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | plugins: [ 4 | ['@babel/plugin-proposal-decorators', {legacy: true}], 5 | [ 6 | 'module-resolver', 7 | { 8 | cwd: 'babelrc', 9 | extensions: ['.ts', '.tsx', '.js', '.ios.js', '.android.js'], 10 | alias: { 11 | '@components': './src/components', 12 | '@constants': './src/constants', 13 | '@entities': './src/entities', 14 | '@hooks': './src/hooks', 15 | '@types': './src/types', 16 | '@utils': './src/utils', 17 | '@store': './src/store', 18 | '@assets': './assets', 19 | '@services': './src/services', 20 | }, 21 | }, 22 | ], 23 | 'react-native-reanimated/plugin', 24 | ], 25 | }; 26 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {extends: ['@commitlint/config-conventional']}; 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import {AppRegistry} from 'react-native'; 2 | import App from './src/App'; 3 | import {name as appName} from './app.json'; 4 | import 'react-native-gesture-handler'; 5 | 6 | // Used to enable 'uuid' 7 | import 'react-native-get-random-values'; 8 | 9 | import dayjs from 'dayjs'; 10 | import relativeTime from 'dayjs/plugin/relativeTime'; 11 | dayjs.extend(relativeTime); 12 | 13 | AppRegistry.registerComponent(appName, () => App); 14 | -------------------------------------------------------------------------------- /ios/Cartfile: -------------------------------------------------------------------------------- 1 | github "libgit2/objective-git" 2 | -------------------------------------------------------------------------------- /ios/Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "libgit2/objective-git" "0.14.2" 2 | -------------------------------------------------------------------------------- /ios/GitModule.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface GitModule : RCTEventEmitter 5 | @end 6 | -------------------------------------------------------------------------------- /ios/GitShark-tvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSExceptionDomains 28 | 29 | localhost 30 | 31 | NSExceptionAllowsInsecureHTTPLoads 32 | 33 | 34 | 35 | 36 | NSLocationWhenInUseUsageDescription 37 | 38 | UILaunchStoryboardName 39 | LaunchScreen 40 | UIRequiredDeviceCapabilities 41 | 42 | armv7 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UIViewControllerBasedStatusBarAppearance 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /ios/GitShark-tvOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/GitShark.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/GitShark.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/GitShark/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : UIResponder 5 | 6 | @property (nonatomic, strong) UIWindow *window; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /ios/GitShark/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Masked40w.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "Masked60w.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "Masked58w.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "Masked87w.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "Masked80w.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "Masked120w.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "Masked120w-1.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "Masked180w.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "Masked1024w.png", 53 | "idiom" : "ios-marketing", 54 | "scale" : "1x", 55 | "size" : "1024x1024" 56 | } 57 | ], 58 | "info" : { 59 | "author" : "xcode", 60 | "version" : 1 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked1024w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked1024w.png -------------------------------------------------------------------------------- /ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked120w-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked120w-1.png -------------------------------------------------------------------------------- /ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked120w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked120w.png -------------------------------------------------------------------------------- /ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked180w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked180w.png -------------------------------------------------------------------------------- /ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked40w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked40w.png -------------------------------------------------------------------------------- /ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked58w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked58w.png -------------------------------------------------------------------------------- /ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked60w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked60w.png -------------------------------------------------------------------------------- /ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked80w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked80w.png -------------------------------------------------------------------------------- /ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked87w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/ios/GitShark/Images.xcassets/AppIcon.appiconset/Masked87w.png -------------------------------------------------------------------------------- /ios/GitShark/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/GitShark/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ios/GitSharkTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | require_relative '../node_modules/react-native/scripts/react_native_pods' 2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 3 | 4 | platform :ios, '11.0' 5 | 6 | target 'GitShark' do 7 | config = use_native_modules! 8 | 9 | use_react_native!( 10 | :path => config[:reactNativePath], 11 | # to enable hermes on iOS, change `false` to `true` and then install pods 12 | :hermes_enabled => false 13 | ) 14 | 15 | target 'GitSharkTests' do 16 | inherit! :complete 17 | # Pods for testing 18 | end 19 | 20 | # Enables Flipper. 21 | # 22 | # Note that if you have use_frameworks! enabled, Flipper will not work and 23 | # you should disable the next line. 24 | use_flipper!() 25 | 26 | post_install do |installer| 27 | react_native_post_install(installer) 28 | __apply_Xcode_12_5_M1_post_install_workaround(installer) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /ios/resetXcode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | killall Xcode 3 | xcrun -k 4 | xcodebuild -alltargets clean 5 | rm -rf "$(getconf DARWIN_USER_CACHE_DIR)/org.llvm.clang/ModuleCache" 6 | rm -rf "$(getconf DARWIN_USER_CACHE_DIR)/org.llvm.clang.$(whoami)/ModuleCache" 7 | rm -rf ~/Library/Developer/Xcode/DerivedData/* 8 | rm -rf ~/Library/Caches/com.apple.dt.Xcode/* 9 | open /Applications/Xcode.app -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: true, 14 | }, 15 | }), 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /promo/changes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/promo/changes.png -------------------------------------------------------------------------------- /promo/commit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/promo/commit.png -------------------------------------------------------------------------------- /promo/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/promo/icon.png -------------------------------------------------------------------------------- /promo/repositories.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/promo/repositories.png -------------------------------------------------------------------------------- /promo/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/promo/settings.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/public/fonts/icomoon.ttf -------------------------------------------------------------------------------- /public/fonts/rubik_Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/public/fonts/rubik_Italic.ttf -------------------------------------------------------------------------------- /public/fonts/rubik_black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/public/fonts/rubik_black.ttf -------------------------------------------------------------------------------- /public/fonts/rubik_blackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/public/fonts/rubik_blackItalic.ttf -------------------------------------------------------------------------------- /public/fonts/rubik_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/public/fonts/rubik_bold.ttf -------------------------------------------------------------------------------- /public/fonts/rubik_boldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/public/fonts/rubik_boldItalic.ttf -------------------------------------------------------------------------------- /public/fonts/rubik_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/public/fonts/rubik_light.ttf -------------------------------------------------------------------------------- /public/fonts/rubik_lightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/public/fonts/rubik_lightItalic.ttf -------------------------------------------------------------------------------- /public/fonts/rubik_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/public/fonts/rubik_medium.ttf -------------------------------------------------------------------------------- /public/fonts/rubik_medium_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/public/fonts/rubik_medium_italic.ttf -------------------------------------------------------------------------------- /public/fonts/rubik_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/GitShark/cd7c5011947692c8385f55602cb5e2644fdf1a2d/public/fonts/rubik_regular.ttf -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /react-native.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | project: { 3 | ios: {}, 4 | android: {}, // grouped into "project" 5 | }, 6 | assets: ['./assets/fonts/'], // stays the same 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/animated-dropdown-arrow/animated-dropdown-arrow.tsx: -------------------------------------------------------------------------------- 1 | import {Animated, StyleProp, ViewStyle} from 'react-native'; 2 | import * as React from 'react'; 3 | import {SharkIconButton} from '../shark-icon-button'; 4 | import {SharkIcon} from '@components/shark-icon'; 5 | 6 | interface AnimatedDropdownArrowProps { 7 | expanded: boolean; 8 | animDuration?: number; 9 | style?: StyleProp; 10 | } 11 | 12 | export const AnimatedDropdownArrow = ({ 13 | expanded, 14 | animDuration = 150, 15 | style, 16 | }: AnimatedDropdownArrowProps) => { 17 | const [rotatevalue] = React.useState(new Animated.Value(0)); 18 | React.useEffect(() => { 19 | if (expanded) { 20 | Animated.timing(rotatevalue, { 21 | toValue: 1, 22 | duration: animDuration, 23 | useNativeDriver: true, 24 | }).start(); 25 | } else { 26 | Animated.timing(rotatevalue, { 27 | toValue: 0, 28 | duration: animDuration, 29 | useNativeDriver: true, 30 | }).start(); 31 | } 32 | }, [expanded, rotatevalue, animDuration]); 33 | 34 | const rotation = rotatevalue.interpolate({ 35 | inputRange: [0, 1], 36 | outputRange: ['0deg', '180deg'], 37 | }); 38 | 39 | return ( 40 | 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /src/components/animated-dropdown-arrow/index.ts: -------------------------------------------------------------------------------- 1 | export * from './animated-dropdown-arrow'; 2 | -------------------------------------------------------------------------------- /src/components/app-bar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app-bar'; 2 | -------------------------------------------------------------------------------- /src/components/checkmark-base/index.ts: -------------------------------------------------------------------------------- 1 | export * from './checkmark-base'; 2 | -------------------------------------------------------------------------------- /src/components/commit-list/commit-card/commit-card-push-pull.tsx: -------------------------------------------------------------------------------- 1 | import {View} from 'react-native'; 2 | import * as React from 'react'; 3 | import {Icon} from '@components/shark-icon'; 4 | import {theme} from '@constants'; 5 | import {DynamicStyleSheet, useDynamicValue} from 'react-native-dynamic'; 6 | 7 | interface CommitCardPushPullProps { 8 | needsPushing?: boolean; 9 | needsPulling?: boolean; 10 | } 11 | 12 | export const CommitCardPushPull = ({ 13 | needsPushing, 14 | needsPulling, 15 | }: CommitCardPushPullProps) => { 16 | const accent = useDynamicValue(theme.colors.primary); 17 | const styles = useDynamicValue(dynamicStyles); 18 | 19 | if (!needsPushing && !needsPulling) { 20 | return null; 21 | } 22 | return ( 23 | 24 | {!!needsPushing && ( 25 | 26 | 27 | 28 | )} 29 | {!!needsPushing && needsPulling && } 30 | {!!needsPulling && ( 31 | 32 | 33 | 34 | )} 35 | 36 | ); 37 | }; 38 | 39 | const dynamicStyles = new DynamicStyleSheet({ 40 | arrowContainer: { 41 | borderStyle: 'solid', 42 | borderColor: theme.colors.tint_on_surface_01, 43 | borderRadius: theme.borderRadius.small, 44 | borderWidth: theme.borders.normal, 45 | flexDirection: 'row', 46 | }, 47 | middleLine: { 48 | width: 1, 49 | backgroundColor: theme.colors.tint_on_surface_01, 50 | }, 51 | commitNumberView: { 52 | padding: 6, 53 | flexDirection: 'row', 54 | alignItems: 'center', 55 | }, 56 | }); 57 | -------------------------------------------------------------------------------- /src/components/commit-list/commit-list.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {FlatList} from 'react-native'; 3 | import {CommitCard} from './commit-card/commit-card'; 4 | import {GitLogCommit} from '@services'; 5 | import {SharkDivider} from '@components/shark-divider'; 6 | import {ReduxRepo} from '@entities'; 7 | 8 | interface CommitListProps { 9 | commits: GitLogCommit[]; 10 | onPress: (commit: GitLogCommit) => void; 11 | repo: ReduxRepo; 12 | } 13 | 14 | interface RenderItemProps { 15 | index: number; 16 | item: GitLogCommit; 17 | } 18 | 19 | type CommitItemProps = Omit & RenderItemProps; 20 | 21 | const CommitItem = React.memo( 22 | ({item: commit, index: i, onPress, repo}: CommitItemProps) => { 23 | return ( 24 | 25 | {i !== 0 && } 26 | 32 | 33 | ); 34 | }, 35 | ); 36 | 37 | export const CommitList = ({commits, onPress, repo}: CommitListProps) => { 38 | const renderItemFn = React.useMemo(() => { 39 | return (props: RenderItemProps) => ( 40 | 41 | ); 42 | }, [onPress, repo]); 43 | 44 | return ( 45 | commit.oid} 48 | renderItem={renderItemFn} 49 | /> 50 | ); 51 | }; 52 | -------------------------------------------------------------------------------- /src/components/commit-list/index.ts: -------------------------------------------------------------------------------- 1 | export * from './commit-list'; 2 | -------------------------------------------------------------------------------- /src/components/commit-pill/im-the-map.tsx: -------------------------------------------------------------------------------- 1 | import {Text, View} from 'react-native'; 2 | 3 | const ImTheMap = () => ( 4 | 5 | 6 | If there’s a place you got to go I’m the one you need to know I’m the map 7 | I’m the map, I’m the map If there’s a place you got to get I can get you 8 | there, I bet I’m the map I’m the map, I’m the map I’m the map, I’m the map 9 | I’m the map, I’m the map I’m the map, I’m the map I’m the map, I’m the map 10 | I’m the map 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/components/commit-pill/index.ts: -------------------------------------------------------------------------------- 1 | export * from './commit-pill'; 2 | -------------------------------------------------------------------------------- /src/components/dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dialog'; 2 | -------------------------------------------------------------------------------- /src/components/dropdown-content/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dropdown-content'; 2 | -------------------------------------------------------------------------------- /src/components/error-message-box/error-message-box.tsx: -------------------------------------------------------------------------------- 1 | import {StyleProp, Text, View, ViewStyle} from 'react-native'; 2 | import * as React from 'react'; 3 | import {Icon} from '@components/shark-icon'; 4 | import {theme} from '@constants'; 5 | import {DynamicStyleSheet, useDynamicValue} from 'react-native-dynamic'; 6 | 7 | interface ErrorMessageBoxProps { 8 | message: string; 9 | style?: StyleProp; 10 | } 11 | 12 | export const ErrorMessageBox = ({ 13 | message, 14 | style = {}, 15 | }: ErrorMessageBoxProps) => { 16 | const styles = useDynamicValue(dynamicStyles); 17 | const error = useDynamicValue(theme.colors.error); 18 | 19 | return ( 20 | 21 | 28 | 29 | {message} 30 | 31 | 32 | ); 33 | }; 34 | 35 | const dynamicStyles = new DynamicStyleSheet({ 36 | errorBoxContainer: { 37 | backgroundColor: theme.colors.error_background, 38 | flexDirection: 'row', 39 | paddingLeft: 7, 40 | paddingRight: theme.spacing.s, 41 | paddingVertical: 7, 42 | borderRadius: theme.borderRadius.regular, 43 | }, 44 | errorText: { 45 | marginLeft: 7, 46 | color: theme.colors.error, 47 | ...theme.textStyles.caption_01, 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /src/components/error-message-box/index.ts: -------------------------------------------------------------------------------- 1 | export * from './error-message-box'; 2 | -------------------------------------------------------------------------------- /src/components/error-prompt/error-prompt.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {theme} from '@constants'; 3 | import {mediaQuery, useDimensions} from 'react-native-responsive-ui'; 4 | import {ErrorPromptTablet} from './error-prompt-tablet'; 5 | import {ErrorPromptMobile} from './error-prompt-mobile'; 6 | import {FullError} from '@types'; 7 | 8 | export const ErrorPrompt = (props: FullError) => { 9 | const {width, height} = useDimensions(); 10 | 11 | const isTablet = mediaQuery( 12 | {minWidth: theme.breakpoints.tablet}, 13 | width, 14 | height, 15 | ); 16 | 17 | if (isTablet) return ; 18 | 19 | // Mobile view 20 | return ; 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/error-prompt/index.ts: -------------------------------------------------------------------------------- 1 | export * from './error-prompt'; 2 | -------------------------------------------------------------------------------- /src/components/extended-action-fab/index.ts: -------------------------------------------------------------------------------- 1 | export * from './extended-action-fab'; 2 | -------------------------------------------------------------------------------- /src/components/file-change-list-item/index.ts: -------------------------------------------------------------------------------- 1 | export * from './file-change-list-item-with-checkbox'; 2 | export * from './file-change-list-item'; 3 | -------------------------------------------------------------------------------- /src/components/folder-select-button/index.ts: -------------------------------------------------------------------------------- 1 | export * from './folder-select-button'; 2 | -------------------------------------------------------------------------------- /src/components/grow-width-content/index.ts: -------------------------------------------------------------------------------- 1 | export * from './grow-width-content'; 2 | -------------------------------------------------------------------------------- /src/components/navigation-aware-portal/index.ts: -------------------------------------------------------------------------------- 1 | export * from './navigation-aware-portal'; 2 | -------------------------------------------------------------------------------- /src/components/navigation-aware-portal/navigation-aware-portal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Animated, StyleSheet} from 'react-native'; 3 | import {Portal} from 'react-native-paper'; 4 | import {useIsFocused, useNavigation} from '@react-navigation/native'; 5 | 6 | export const NavigationAwarePortal: React.FC = ({children}) => { 7 | const navigation = useNavigation(); 8 | const isFocused = useIsFocused(); 9 | 10 | const [open, setOpen] = React.useState(false); 11 | const opacity = React.useRef(new Animated.Value(0)); 12 | 13 | const _show = React.useCallback(() => { 14 | setOpen(true); 15 | 16 | Animated.timing(opacity.current, { 17 | toValue: 1, 18 | duration: 200, 19 | useNativeDriver: true, 20 | }).start(); 21 | }, []); 22 | 23 | const _hide = React.useCallback(() => { 24 | setOpen(false); 25 | 26 | Animated.timing(opacity.current, { 27 | toValue: 0, 28 | duration: 150, 29 | useNativeDriver: true, 30 | }).start(); 31 | }, []); 32 | 33 | React.useEffect(() => { 34 | navigation.addListener('focus', _show); 35 | navigation.addListener('blur', _hide); 36 | 37 | if (isFocused) { 38 | _show(); 39 | } 40 | 41 | return () => { 42 | navigation.removeListener('focus', _show); 43 | navigation.removeListener('blur', _hide); 44 | }; 45 | }, [_hide, _show, isFocused, navigation]); 46 | 47 | return ( 48 | 49 | 52 | {children} 53 | 54 | 55 | ); 56 | }; 57 | 58 | const styles = StyleSheet.create({ 59 | container: { 60 | flex: 1, 61 | }, 62 | }); 63 | -------------------------------------------------------------------------------- /src/components/overlay-dropdown-content/index.ts: -------------------------------------------------------------------------------- 1 | export * from './overlay-dropdown-content'; 2 | -------------------------------------------------------------------------------- /src/components/progress-error-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './progress-error-dialog'; 2 | -------------------------------------------------------------------------------- /src/components/push-pull-arrows/index.ts: -------------------------------------------------------------------------------- 1 | export * from './push-pull-arrows'; 2 | -------------------------------------------------------------------------------- /src/components/rename-repository-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './rename-repository-dialog'; 2 | -------------------------------------------------------------------------------- /src/components/repository-header/index.ts: -------------------------------------------------------------------------------- 1 | export * from './repository-header'; 2 | -------------------------------------------------------------------------------- /src/components/scrim/index.ts: -------------------------------------------------------------------------------- 1 | export * from './scrim'; 2 | -------------------------------------------------------------------------------- /src/components/seaside-switch/index.ts: -------------------------------------------------------------------------------- 1 | export * from './seaside-switch'; 2 | -------------------------------------------------------------------------------- /src/components/seaside-switch/seaside-switch.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {SeaSwitch} from './seaside-switch'; 3 | 4 | const SeaSwitchDemo = ({...props}: any) => { 5 | const [value, setValue] = React.useState(false); 6 | return ; 7 | }; 8 | 9 | export default {title: 'Seaside Components/Switch'}; 10 | 11 | export const DefaultStyling = (args: {disabled: boolean}) => ( 12 | 13 | ); 14 | 15 | DefaultStyling.args = {disabled: false}; 16 | -------------------------------------------------------------------------------- /src/components/seaside-text-input/index.ts: -------------------------------------------------------------------------------- 1 | export * from './seaside-text-input'; 2 | -------------------------------------------------------------------------------- /src/components/seaside-text-input/seaside-text-input.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {SeaTextInput} from './seaside-text-input'; 3 | 4 | const SeaInputDemo = ({...props}: any) => { 5 | const [value, setValue] = React.useState(''); 6 | return ( 7 | 13 | ); 14 | }; 15 | 16 | export default {title: 'Seaside Components/Text Input'}; 17 | 18 | export const DefaultStyling = (args: {disabled: boolean}) => ( 19 | 20 | ); 21 | 22 | DefaultStyling.args = {disabled: false}; 23 | -------------------------------------------------------------------------------- /src/components/shack-snackbar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shack-snackbar'; 2 | -------------------------------------------------------------------------------- /src/components/shack-snackbar/shack-snackbar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Snackbar} from 'react-native-paper'; 3 | import {useSafeAreaInsets} from 'react-native-safe-area-context'; 4 | 5 | interface SharkSnackbarProps { 6 | visible: boolean; 7 | onDismiss: () => void; 8 | action?: React.ComponentProps['action']; 9 | message: string; 10 | } 11 | export const SharkSnackbar = ({ 12 | visible, 13 | onDismiss, 14 | action, 15 | message, 16 | }: SharkSnackbarProps) => { 17 | const insets = useSafeAreaInsets(); 18 | 19 | return ( 20 | 26 | {message} 27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/shark-bottom-sheet/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shark-bottom-sheet'; 2 | -------------------------------------------------------------------------------- /src/components/shark-button-toggle-group/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shark-button-toggle-group'; 2 | -------------------------------------------------------------------------------- /src/components/shark-button-toggle-group/shark-button-toggle-group.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {StyleProp, ViewStyle} from 'react-native'; 3 | import {DynamicStyleSheet, useDynamicValue} from 'react-native-dynamic'; 4 | import {theme} from '@constants'; 5 | import ButtonToggleGroup from 'react-native-button-toggle-group'; 6 | 7 | interface SharkButtonToggleGroupProps { 8 | values: string[]; 9 | value: string; 10 | onSelect: (val: string) => void; 11 | style?: StyleProp; 12 | } 13 | 14 | export const SharkButtonToggleGroup = ({ 15 | values, 16 | value, 17 | onSelect, 18 | style = {}, 19 | }: SharkButtonToggleGroupProps) => { 20 | const styles = useDynamicValue(dynamicStyles); 21 | 22 | const primary = useDynamicValue(theme.colors.primary); 23 | const on_primary = useDynamicValue(theme.colors.on_primary); 24 | const label_medium_emphasis = useDynamicValue( 25 | theme.colors.label_medium_emphasis, 26 | ); 27 | 28 | return ( 29 | 40 | ); 41 | }; 42 | 43 | const dynamicStyles = new DynamicStyleSheet({ 44 | container: { 45 | borderColor: theme.colors.tint_on_surface_01, 46 | padding: theme.spacing.xxs, 47 | borderWidth: theme.borders.normal, 48 | borderRadius: theme.borderRadius.regular, 49 | overflow: 'hidden', 50 | }, 51 | buttonText: { 52 | ...theme.textStyles.callout_01, 53 | }, 54 | }); 55 | -------------------------------------------------------------------------------- /src/components/shark-button/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shark-button'; 2 | -------------------------------------------------------------------------------- /src/components/shark-button/shark-button.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {SharkButton} from './shark-button'; 3 | 4 | export default {title: 'Shark Components/Button'}; 5 | 6 | export const DefaultStyling = () => ( 7 | {}} /> 8 | ); 9 | -------------------------------------------------------------------------------- /src/components/shark-checkbox/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shark-checkbox'; 2 | -------------------------------------------------------------------------------- /src/components/shark-checkbox/shark-checkbox.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View} from 'react-native'; 3 | import {SharkCheckbox} from './shark-checkbox'; 4 | 5 | const SharkCheckboxDemo = () => { 6 | const [checked, setChecked] = React.useState(false); 7 | return ( 8 | 9 | setChecked(v => !v)} 12 | /> 13 | 14 | ); 15 | }; 16 | 17 | export default {title: 'Shark Components/Checkbox'}; 18 | 19 | export const DefaultStyling = () => ; 20 | -------------------------------------------------------------------------------- /src/components/shark-divider/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shark-divider'; 2 | -------------------------------------------------------------------------------- /src/components/shark-divider/shark-divider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {StyleProp, View, ViewStyle} from 'react-native'; 3 | import {DynamicStyleSheet, useDynamicValue} from 'react-native-dynamic'; 4 | import {theme} from '@constants'; 5 | 6 | interface SharkDividerProps { 7 | style?: StyleProp; 8 | } 9 | 10 | export const SharkDivider = ({style = {}}: SharkDividerProps) => { 11 | const styles = useDynamicValue(dynamicStyles); 12 | return ; 13 | }; 14 | 15 | const dynamicStyles = new DynamicStyleSheet({ 16 | divider: { 17 | width: '100%', 18 | borderTopWidth: theme.borders.normal, 19 | borderTopColor: theme.colors.tint_on_surface_01, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /src/components/shark-icon-button/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shark-icon-button'; 2 | -------------------------------------------------------------------------------- /src/components/shark-icon-button/shark-icon-button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Icon, SharkIcon} from '@components/shark-icon'; 3 | import {theme} from '@constants'; 4 | import {TouchableRipple} from 'react-native-paper'; 5 | import {Animated, StyleProp, StyleSheet, ViewStyle} from 'react-native'; 6 | import {useDynamicValue} from 'react-native-dynamic'; 7 | 8 | interface SharkIconButtonProps { 9 | iconName: string; 10 | onPress: () => void; 11 | style?: StyleProp; 12 | iconStyle?: any; 13 | disabled?: boolean; 14 | color?: string; 15 | label: string; 16 | buttonProps?: Partial< 17 | Omit< 18 | React.ComponentProps, 19 | 'style' | 'onPress' | 'disabled' 20 | > 21 | >; 22 | } 23 | 24 | export const SharkIconButton = ({ 25 | iconName, 26 | onPress, 27 | style = {}, 28 | iconStyle = {}, 29 | disabled = false, 30 | color, 31 | label, 32 | }: SharkIconButtonProps) => { 33 | return ( 34 | 40 | 45 | 46 | ); 47 | }; 48 | 49 | const styles = StyleSheet.create({ 50 | iconPadding: { 51 | padding: theme.spacing.xs, 52 | }, 53 | noPadIcon: { 54 | padding: 0, 55 | }, 56 | }); 57 | -------------------------------------------------------------------------------- /src/components/shark-icon/icon.ts: -------------------------------------------------------------------------------- 1 | import {createIconSetFromIcoMoon} from 'react-native-vector-icons'; 2 | import icomoonConfig from './selection.json'; 3 | 4 | export const Icon = createIconSetFromIcoMoon(icomoonConfig); 5 | -------------------------------------------------------------------------------- /src/components/shark-icon/index.ts: -------------------------------------------------------------------------------- 1 | export * from './icon'; 2 | export * from './shark-icon'; 3 | -------------------------------------------------------------------------------- /src/components/shark-icon/shark-icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Animated, StyleProp, StyleSheet, View, ViewStyle} from 'react-native'; 3 | import {Icon} from './icon'; 4 | import {useDynamicValue} from 'react-native-dynamic'; 5 | import {theme} from '@constants'; 6 | 7 | interface SharkIconButtonProps { 8 | iconName: string; 9 | style?: React.ComponentProps['style']; 10 | color?: string; 11 | } 12 | 13 | export const SharkIcon = ({color, style, iconName}: SharkIconButtonProps) => { 14 | const accentColor = useDynamicValue(theme.colors.primary); 15 | 16 | const iconColor = !color ? accentColor : color; 17 | 18 | return ( 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | const styles = StyleSheet.create({ 26 | iconPadding: { 27 | padding: theme.spacing.xs, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /src/components/shark-menu/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shark-menu'; 2 | -------------------------------------------------------------------------------- /src/components/shark-menu/shark-menu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Menu} from 'react-native-paper'; 3 | import {DynamicStyleSheet, useDynamicValue} from 'react-native-dynamic'; 4 | import {theme} from '@constants'; 5 | import {useSafeAreaInsets} from 'react-native-safe-area-context'; 6 | 7 | export const SharkMenu = ({ 8 | children, 9 | contentStyle = {}, 10 | ...props 11 | }: React.ComponentProps) => { 12 | const styles = useDynamicValue(dynamicStyles); 13 | const insets = useSafeAreaInsets(); 14 | 15 | return ( 16 | 19 | {children} 20 | 21 | ); 22 | }; 23 | 24 | const dynamicStyles = new DynamicStyleSheet({ 25 | menu: { 26 | backgroundColor: theme.colors.floating_surface, 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /src/components/shark-picker/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shark-picker'; 2 | -------------------------------------------------------------------------------- /src/components/shark-picker/shark-picker.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Picker} from '@react-native-picker/picker'; 3 | import {DynamicStyleSheet, useDynamicValue} from 'react-native-dynamic'; 4 | import {theme} from '@constants'; 5 | import tinycolor from 'tinycolor2'; 6 | 7 | type SharkPickerProps = Omit< 8 | React.ComponentProps, 9 | 'style' | 'mode' | 'itemStyle' | 'dropdownIconColor' 10 | >; 11 | 12 | type SharkPicker = React.FC & { 13 | Item: typeof Picker.Item; 14 | }; 15 | 16 | export const SharkPicker: SharkPicker = ({children, ...props}) => { 17 | const styles = useDynamicValue(dynamicStyles); 18 | const high_emphasis = useDynamicValue(theme.colors.label_high_emphasis); 19 | 20 | // A limitation of the native code for the picker 21 | // component is that you have to use a hex color 22 | // for the dropdown color input. 23 | const high_emphasis_hex = tinycolor(high_emphasis).toHex(); 24 | 25 | return ( 26 | 32 | {children} 33 | 34 | ); 35 | }; 36 | 37 | SharkPicker.Item = Picker.Item; 38 | 39 | const dynamicStyles = new DynamicStyleSheet({ 40 | pickerStyle: { 41 | color: theme.colors.label_high_emphasis, 42 | }, 43 | pickerLabel: { 44 | color: theme.colors.label_high_emphasis, 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /src/components/shark-profile-pic/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shark-profile-pic'; 2 | -------------------------------------------------------------------------------- /src/components/shark-radio/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shark-radio'; 2 | -------------------------------------------------------------------------------- /src/components/shark-radio/shark-radio.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View} from 'react-native'; 3 | import {SharkRadio} from './shark-radio'; 4 | 5 | const SharkRadioDemo = () => { 6 | const [index, setIndex] = React.useState(0); 7 | return ( 8 | 9 | 10 | setIndex(0)} /> 11 | 12 | 13 | setIndex(1)} /> 14 | 15 | 16 | setIndex(2)} /> 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default {title: 'Shark Components/Radio'}; 23 | 24 | export const DefaultStyling = () => ; 25 | -------------------------------------------------------------------------------- /src/components/shark-radio/shark-radio.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {CheckmarkBase} from '../checkmark-base'; 3 | import {useDynamicValue} from 'react-native-dynamic'; 4 | import {theme} from '@constants'; 5 | 6 | interface SharkRadioProps { 7 | checked: boolean; 8 | onValueChange?: (val: boolean) => void; 9 | } 10 | 11 | export const SharkRadio = ({checked, onValueChange}: SharkRadioProps) => { 12 | const accent = useDynamicValue(theme.colors.primary); 13 | const label_medium_emphasis = useDynamicValue( 14 | theme.colors.label_medium_emphasis, 15 | ); 16 | 17 | return ( 18 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/shark-safe-top/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shark-safe-top'; 2 | -------------------------------------------------------------------------------- /src/components/shark-safe-top/shark-safe-top.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useSafeAreaInsets} from 'react-native-safe-area-context'; 3 | import {View} from 'react-native'; 4 | import {useDynamicValue} from 'react-native-dynamic'; 5 | import {theme} from '@constants'; 6 | 7 | interface SharkSafeTopProps { 8 | isFloating?: boolean; 9 | } 10 | 11 | export const SharkSafeTop: React.FC = ({ 12 | children, 13 | isFloating, 14 | }) => { 15 | const floating = useDynamicValue(theme.colors.floating_surface); 16 | const insets = useSafeAreaInsets(); 17 | 18 | const floatingStyle = isFloating ? {backgroundColor: floating} : {}; 19 | 20 | return ( 21 | 22 | {children} 23 | 24 | ); 25 | }; 26 | 27 | interface SpacerViewProps { 28 | additionalSpacing?: number; 29 | isFloating?: boolean; 30 | } 31 | 32 | export const TopSpacerView = ({ 33 | additionalSpacing = 0, 34 | isFloating, 35 | }: SpacerViewProps) => { 36 | const floating = useDynamicValue(theme.colors.floating_surface); 37 | const insets = useSafeAreaInsets(); 38 | 39 | const floatingStyle = isFloating ? {backgroundColor: floating} : {}; 40 | 41 | return ( 42 | 43 | ); 44 | }; 45 | 46 | export const BottomSpacerView = ({additionalSpacing = 0}: SpacerViewProps) => { 47 | const insets = useSafeAreaInsets(); 48 | 49 | return ; 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/shark-subheader/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shark-subheader'; 2 | -------------------------------------------------------------------------------- /src/components/shark-text-input/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shark-text-input'; 2 | -------------------------------------------------------------------------------- /src/components/shark-text-input/shark-text-input.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {SharkTextInput} from './shark-text-input'; 3 | 4 | const SharkInputDemo = ({...props}: any) => { 5 | const [value, setValue] = React.useState(''); 6 | return ( 7 | 14 | ); 15 | }; 16 | 17 | export default {title: 'Shark Components/Text Input'}; 18 | 19 | export const DefaultStyling = () => ; 20 | -------------------------------------------------------------------------------- /src/components/sr-only/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sr-only'; 2 | -------------------------------------------------------------------------------- /src/components/sr-only/sr-only.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {View} from 'react-native'; 3 | 4 | export const SrOnly: React.FC = ({children}) => { 5 | return ( 6 | 12 | {children} 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/storybook-provider/index.ts: -------------------------------------------------------------------------------- 1 | export * from './storybook-provider'; 2 | -------------------------------------------------------------------------------- /src/components/storybook-provider/storybook-provider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {SafeAreaProvider} from 'react-native-safe-area-context'; 3 | import {NavigationContainer} from '@react-navigation/native'; 4 | import {createStackNavigator} from '@react-navigation/stack'; 5 | import {lightNavTheme, lightPaperTheme} from '@constants'; 6 | import {Provider as PaperProvider} from 'react-native-paper'; 7 | import {View} from 'react-native'; 8 | 9 | export const StorybookProvider: React.FC = ({children}) => { 10 | const Stack = createStackNavigator(); 11 | 12 | const Comp = () => <>{children}; 13 | 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/constants/contexts/dialogs-context.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ViewProps} from 'react-native'; 3 | 4 | interface DialogsContextType { 5 | openDialogs: number; 6 | setOpenDialogs: (val: (v: number) => number | number) => void; 7 | } 8 | 9 | export const DialogsContext = React.createContext({ 10 | openDialogs: 0, 11 | setOpenDialogs: () => {}, 12 | }); 13 | 14 | export const DialogContextProvider: React.FC = ({children}) => { 15 | const [openDialogs, setOpenDialogs] = React.useState(0); 16 | 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | }; 23 | 24 | export const useInDialogProps = (): ViewProps => { 25 | const {openDialogs} = React.useContext(DialogsContext); 26 | 27 | const isInDialog = openDialogs > 0; 28 | 29 | return isInDialog 30 | ? { 31 | importantForAccessibility: 'no-hide-descendants', 32 | accessibilityElementsHidden: true, 33 | } 34 | : {}; 35 | }; 36 | -------------------------------------------------------------------------------- /src/constants/contexts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dialogs-context'; 2 | export * from './repo-header-context'; 3 | export * from './user-context'; 4 | export * from './set-dark-mode-context'; 5 | export * from './style-of-staging-context'; 6 | -------------------------------------------------------------------------------- /src/constants/contexts/repo-header-context.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {PushPull} from '@entities'; 3 | 4 | export type RepoHeaderDialogType = 'rename' | 'fetch' | 'push' | 'pull' | ''; 5 | 6 | interface RepoHeaderContextType { 7 | activeDialog: RepoHeaderDialogType | null; 8 | setActiveDialog: (val: RepoHeaderDialogType) => void; 9 | pushPull: PushPull | null; 10 | } 11 | 12 | export const RepoHeaderContext = React.createContext({ 13 | activeDialog: null, 14 | setActiveDialog: () => {}, 15 | pushPull: null, 16 | }); 17 | -------------------------------------------------------------------------------- /src/constants/contexts/set-dark-mode-context.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export type DarkModeOptionTypes = 'light' | 'dark' | 'auto'; 4 | 5 | interface SetDarkModeContextType { 6 | setDarkMode: (val: DarkModeOptionTypes) => void; 7 | localDarkMode: DarkModeOptionTypes; 8 | } 9 | 10 | export const SetDarkModeContext = React.createContext({ 11 | setDarkMode: () => {}, 12 | localDarkMode: 'auto', 13 | }); 14 | -------------------------------------------------------------------------------- /src/constants/contexts/style-of-staging-context.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export type StagingTypes = 'split' | 'sheet'; 4 | 5 | interface StyleOfStagingContextType { 6 | styleOfStaging: StagingTypes | null; 7 | setStyleOfStaging: (val: StagingTypes) => void; 8 | } 9 | 10 | export const StyleOfStagingContext = 11 | React.createContext({ 12 | styleOfStaging: null, 13 | setStyleOfStaging: () => {}, 14 | }); 15 | -------------------------------------------------------------------------------- /src/constants/contexts/user-context.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {CachedGithubUser, ManualUser} from '@types'; 3 | 4 | interface GitHubUserContextType { 5 | gitHubUser: null | CachedGithubUser; 6 | manualUser: null | ManualUser; 7 | setManualUser: (val: ManualUser) => void; 8 | useGitHub: boolean; 9 | setUseGithub: (val: boolean) => void; 10 | logoutGitHub: () => void; 11 | } 12 | 13 | export const UserContext = React.createContext({ 14 | gitHubUser: null, 15 | manualUser: null, 16 | setManualUser: () => {}, 17 | setUseGithub: () => {}, 18 | useGitHub: false, 19 | logoutGitHub: () => {}, 20 | }); 21 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './contexts'; 2 | export * from './storage-keys'; 3 | export * from './text-styles'; 4 | export * from './theme'; 5 | -------------------------------------------------------------------------------- /src/constants/oauth.ts: -------------------------------------------------------------------------------- 1 | export const githubOauthLink = `https://auth.gitshark.dev/authorize`; 2 | -------------------------------------------------------------------------------- /src/constants/repo-config.ts: -------------------------------------------------------------------------------- 1 | export const RepoConfig = { 2 | owner: 'oceanbit', 3 | name: 'GitShark', 4 | }; 5 | -------------------------------------------------------------------------------- /src/constants/storage-keys.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When using DefaultSettings and RNSecureKeyStore, we have to keep a string constant for storing data 3 | * This file simply exports the lot of strings, enabling us to keep track of the keys 4 | */ 5 | export const GITHUB_TOKEN_STORAGE_KEY = 'ghToken'; 6 | 7 | export const DARK_MODE_STORAGE_KEY = 'darkMode'; 8 | 9 | export const STAGING_STYLE_STORAGE_KEY = 'styleOfStaging'; 10 | 11 | export const GITHUB_USER_STORAGE_KEY = 'githubUser'; 12 | 13 | export const MANUAL_USER_STORAGE_KEY = 'manualUser'; 14 | 15 | export const SHOULD_USE_GITHUB_CREDS_KEY = 'shouldUseGHCreds'; 16 | -------------------------------------------------------------------------------- /src/constants/text-styles.ts: -------------------------------------------------------------------------------- 1 | import {getTextStyles} from '@oceanbit/styles'; 2 | import {Platform} from 'react-native'; 3 | 4 | const iOS = Platform.OS === 'ios'; 5 | 6 | export const epilogueLight = { 7 | fontFamily: iOS ? 'Rubik' : 'rubik_light', 8 | fontWeight: iOS ? ('300' as const) : ('normal' as const), 9 | }; 10 | 11 | export const epilogueRegular = { 12 | fontFamily: iOS ? 'Rubik' : 'rubik_regular', 13 | fontWeight: 'normal' as const, 14 | }; 15 | 16 | export const epilogueSemiBold = { 17 | fontFamily: iOS ? 'Rubik' : 'rubik_medium', 18 | fontWeight: iOS ? ('500' as const) : ('normal' as const), 19 | }; 20 | 21 | export const epilogueBold = { 22 | fontFamily: iOS ? 'Rubik' : 'rubik_bold', 23 | fontWeight: iOS ? ('bold' as const) : ('normal' as const), 24 | }; 25 | 26 | export const textStyles = getTextStyles({ 27 | epilogueBold, 28 | epilogueSemiBold, 29 | epilogueRegular, 30 | robotoCode: epilogueRegular, 31 | }); 32 | -------------------------------------------------------------------------------- /src/entities/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Commit'; 2 | export * from './Repo'; 3 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './use-get-android-permissions'; 2 | export * from './use-github-user-data'; 3 | export * from './use-local-dark-mode'; 4 | export * from './use-manual-user-data'; 5 | export * from './use-thunk-dispatch'; 6 | export * from './use-user'; 7 | -------------------------------------------------------------------------------- /src/hooks/use-foreground-effect.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {AppState, AppStateStatus} from 'react-native'; 3 | 4 | export const useForegroundEffect = (fn: () => void) => { 5 | React.useEffect(() => { 6 | const listener = (e: AppStateStatus) => { 7 | if (e === 'active') { 8 | fn(); 9 | } 10 | }; 11 | const changeListener = AppState.addEventListener('change', listener); 12 | 13 | return () => changeListener.remove(); 14 | }, [fn]); 15 | }; 16 | -------------------------------------------------------------------------------- /src/hooks/use-get-android-permissions.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Platform, PermissionsAndroid} from 'react-native'; 3 | 4 | export const useGetAndroidPermissions = () => { 5 | React.useEffect(() => { 6 | if (Platform.OS === 'android') { 7 | const askedPermission = PermissionsAndroid.requestMultiple([ 8 | PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, 9 | PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, 10 | ]); 11 | askedPermission.then(granted => { 12 | if ( 13 | granted[PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE] === 14 | PermissionsAndroid.RESULTS.GRANTED && 15 | granted[PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE] === 16 | PermissionsAndroid.RESULTS.GRANTED 17 | ) { 18 | console.log('You can use external storage'); 19 | } else { 20 | console.log('External storage permission denied'); 21 | } 22 | }); 23 | } 24 | }, []); 25 | }; 26 | -------------------------------------------------------------------------------- /src/hooks/use-local-dark-mode.ts: -------------------------------------------------------------------------------- 1 | import {Platform, useColorScheme} from 'react-native'; 2 | import * as React from 'react'; 3 | import { 4 | DARK_MODE_STORAGE_KEY, 5 | DarkModeOptionTypes, 6 | darkPaperTheme, 7 | lightPaperTheme, 8 | } from '@constants'; 9 | import {changeBarColors} from 'react-native-immersive-bars'; 10 | import DefaultPreference from 'react-native-default-preference'; 11 | 12 | export const useLocalDarkMode = () => { 13 | const systemColorTheme = useColorScheme(); 14 | const isSystemDarkMode = systemColorTheme === 'dark'; 15 | 16 | const [localDarkMode, setLocalDarkMode] = 17 | React.useState('auto'); 18 | 19 | const isDarkMode = 20 | localDarkMode === 'auto' ? isSystemDarkMode : localDarkMode === 'dark'; 21 | 22 | React.useEffect(() => { 23 | if (Platform.OS === 'android') { 24 | changeBarColors(isDarkMode); 25 | } 26 | }, [isDarkMode]); 27 | 28 | React.useEffect(() => { 29 | DefaultPreference.get(DARK_MODE_STORAGE_KEY).then(val => { 30 | if (val) { 31 | setLocalDarkMode(val as DarkModeOptionTypes); 32 | } 33 | }); 34 | }, []); 35 | 36 | const updateLocalDarkMode = (val: DarkModeOptionTypes) => { 37 | DefaultPreference.set(DARK_MODE_STORAGE_KEY, val); 38 | setLocalDarkMode(val); 39 | }; 40 | 41 | const paperTheme = isDarkMode ? darkPaperTheme : lightPaperTheme; 42 | 43 | return { 44 | isDarkMode, 45 | paperTheme, 46 | updateLocalDarkMode, 47 | localDarkMode, 48 | }; 49 | }; 50 | -------------------------------------------------------------------------------- /src/hooks/use-manual-user-data.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import DefaultPreference from 'react-native-default-preference'; 3 | import {ManualUser} from '@types'; 4 | import {MANUAL_USER_STORAGE_KEY} from '@constants'; 5 | 6 | export const useManualUserData = () => { 7 | const [manualUser, setManualUserLocal] = React.useState( 8 | null, 9 | ); 10 | 11 | /** 12 | * If data is already cached, set the user on initial load 13 | */ 14 | React.useEffect(() => { 15 | DefaultPreference.get(MANUAL_USER_STORAGE_KEY) 16 | .then(data => { 17 | if (data) { 18 | setManualUserLocal(JSON.parse(data) as ManualUser); 19 | } 20 | }) 21 | .catch(e => console.error(e)); 22 | }, []); 23 | 24 | const setManualUser = React.useCallback((user: ManualUser) => { 25 | DefaultPreference.set(MANUAL_USER_STORAGE_KEY, JSON.stringify(user)) 26 | .then(() => setManualUserLocal(user)) 27 | .catch(e => console.error(e)); 28 | }, []); 29 | 30 | return {manualUser, setManualUser}; 31 | }; 32 | -------------------------------------------------------------------------------- /src/hooks/use-thunk-dispatch.ts: -------------------------------------------------------------------------------- 1 | import {useDispatch} from 'react-redux'; 2 | import {store} from '@store/store'; 3 | 4 | export const useThunkDispatch = () => useDispatch(); 5 | export type ThunkDispatchType = ReturnType; 6 | -------------------------------------------------------------------------------- /src/hooks/use-user.ts: -------------------------------------------------------------------------------- 1 | import {useManualUserData} from './use-manual-user-data'; 2 | import {useGitHubUserData} from './use-github-user-data'; 3 | 4 | export const useUserData = () => { 5 | const {manualUser} = useManualUserData(); 6 | const {gitHubUser, useGitHub} = useGitHubUserData(); 7 | const {email: ghEmail, name: ghName} = gitHubUser || {}; 8 | const {email: uEmail, name: uName} = manualUser || {}; 9 | 10 | return useGitHub 11 | ? {name: ghName, email: ghEmail} 12 | : {name: uName, email: uEmail}; 13 | }; 14 | -------------------------------------------------------------------------------- /src/services/debug.ts: -------------------------------------------------------------------------------- 1 | export const logService = false; 2 | 3 | export class NotImplemented extends Error { 4 | constructor(serviceName: string) { 5 | super(`${serviceName} is not implemented on your platform`); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/services/git/add-to-staged-android.ts: -------------------------------------------------------------------------------- 1 | import type {AddToStagedProps} from '@services'; 2 | import {NativeModules} from 'react-native'; 3 | 4 | export const addToStagedAndroid = async ({changes, repo}: AddToStagedProps) => { 5 | const fileNames = changes.map(c => c.fileName); 6 | await NativeModules.GitModule.addToStage(repo.path, fileNames); 7 | }; 8 | -------------------------------------------------------------------------------- /src/services/git/add-to-staged.ts: -------------------------------------------------------------------------------- 1 | import {ChangesArrayItem} from '@services'; 2 | import {ReduxRepo} from '@entities'; 3 | import {Platform} from 'react-native'; 4 | import {addToStagedAndroid} from './add-to-staged-android'; 5 | import {NotImplemented} from '@services/debug'; 6 | 7 | export interface AddToStagedProps { 8 | changes: ChangesArrayItem[]; 9 | repo: ReduxRepo; 10 | } 11 | 12 | export const addToStaged = ({ 13 | changes, 14 | repo, 15 | }: AddToStagedProps): Promise => { 16 | if (Platform.OS === 'android') { 17 | return addToStagedAndroid({changes, repo}); 18 | } 19 | 20 | throw new NotImplemented('addToStaged'); 21 | }; 22 | -------------------------------------------------------------------------------- /src/services/git/checkout-branch-android.ts: -------------------------------------------------------------------------------- 1 | import {changeBranch} from '@store'; 2 | import type {CheckoutBranchProps} from './checkout-branch'; 3 | import {NativeModules} from 'react-native'; 4 | 5 | export const checkoutBranchAndroid = async ({ 6 | repo, 7 | branchName, 8 | dispatch, 9 | remote, 10 | }: CheckoutBranchProps) => { 11 | // Absolute path of branchName required, as GitCheckoutModule relies on the full path 12 | // to base it's decision of cloning locally or remotely 13 | // Local path 14 | let fullBranchPath = `refs/heads/${branchName}`; 15 | // Remote path 16 | if (remote) { 17 | fullBranchPath = `refs/remotes/${remote}/${branchName}`; 18 | } 19 | 20 | await NativeModules.GitModule.checkout(repo.path, fullBranchPath, ''); 21 | 22 | dispatch(changeBranch({repoId: repo.id, branchName})); 23 | }; 24 | -------------------------------------------------------------------------------- /src/services/git/checkout-branch.ts: -------------------------------------------------------------------------------- 1 | import {ReduxRepo} from '@entities'; 2 | import {ThunkDispatchType} from '@hooks'; 3 | import {logService, NotImplemented} from '../debug'; 4 | import {Platform} from 'react-native'; 5 | import {checkoutBranchAndroid} from '@services/git/checkout-branch-android'; 6 | import {ProgressCallback} from '@types'; 7 | 8 | export interface CheckoutBranchProps { 9 | repo: ReduxRepo; 10 | branchName: string; 11 | remote: false | string; 12 | dispatch: ThunkDispatchType; 13 | onProgress?: ProgressCallback; 14 | } 15 | export const checkoutBranch = async ({ 16 | repo, 17 | branchName, 18 | dispatch, 19 | onProgress, 20 | remote, 21 | }: CheckoutBranchProps) => { 22 | logService && console.log('service - checkoutBranch'); 23 | 24 | if (Platform.OS === 'android') { 25 | return await checkoutBranchAndroid({ 26 | repo, 27 | branchName, 28 | dispatch, 29 | onProgress, 30 | remote, 31 | }); 32 | } 33 | 34 | throw new NotImplemented('checkoutBranch'); 35 | }; 36 | -------------------------------------------------------------------------------- /src/services/git/clone-repo-android.ts: -------------------------------------------------------------------------------- 1 | import {createNewRepo} from './create-repo'; 2 | import {getRepoNameFromUri} from '@utils'; 3 | import {NativeEventEmitter, NativeModules} from 'react-native'; 4 | import type {CloneRepoProps} from './clone-repo'; 5 | 6 | export const cloneRepoAndroid = ({ 7 | path, 8 | name, 9 | uri, 10 | onProgress, 11 | }: CloneRepoProps) => { 12 | const newFolderName = getRepoNameFromUri(uri); 13 | const repoName = name || newFolderName; 14 | const repoDir = `${path}/${repoName}`; 15 | 16 | const eventEmitter = new NativeEventEmitter(NativeModules.GitModule); 17 | 18 | const eventListener = eventEmitter.addListener( 19 | 'CloneProgress', 20 | (event: {phase: string; loaded: number; total: number}) => { 21 | const {phase, loaded, total} = event; 22 | onProgress({phase, loaded, total}); 23 | }, 24 | ); 25 | 26 | return new Promise((resolve, reject) => { 27 | NativeModules.GitModule.clone(uri, repoDir) 28 | .then(() => { 29 | eventListener.remove(); 30 | }) 31 | .then(() => createNewRepo(repoDir, repoName)) 32 | .then(() => resolve()) 33 | .catch((e: Error) => reject(e)); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /src/services/git/clone-repo-ios.ts: -------------------------------------------------------------------------------- 1 | import {createNewRepo} from './create-repo'; 2 | import {getRepoNameFromUri} from '@utils'; 3 | import {NativeEventEmitter, NativeModules} from 'react-native'; 4 | import type {CloneRepoProps} from './clone-repo'; 5 | 6 | export const cloneRepoIOS = ({path, name, uri, onProgress}: CloneRepoProps) => { 7 | const newFolderName = getRepoNameFromUri(uri); 8 | const repoName = name || newFolderName; 9 | const repoDir = `${path}/${repoName}`; 10 | 11 | const eventEmitter = new NativeEventEmitter(NativeModules.GitModule); 12 | 13 | const eventListener = eventEmitter.addListener( 14 | 'CloneProgress', 15 | (event: {phase: string; loaded: number; total: number}) => { 16 | const {phase, loaded, total} = event; 17 | onProgress({phase, loaded, total}); 18 | }, 19 | ); 20 | 21 | return new Promise((resolve, reject) => { 22 | NativeModules.GitModule.clone(uri, repoDir) 23 | .then(() => { 24 | eventListener.remove(); 25 | }) 26 | .then(() => createNewRepo(repoDir, repoName)) 27 | .then(() => resolve()) 28 | .catch((e: Error) => reject(e)); 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /src/services/git/clone-repo.ts: -------------------------------------------------------------------------------- 1 | import {getRepoNameFromUri} from '@utils'; 2 | import {Platform} from 'react-native'; 3 | import {cloneRepoAndroid} from '@services/git/clone-repo-android'; 4 | import {cloneRepoIOS} from '@services/git/clone-repo-ios'; 5 | import {logService, NotImplemented} from '../debug'; 6 | import {ProgressCallback} from '@types'; 7 | 8 | export interface CloneRepoProps { 9 | path: string; 10 | name?: string; 11 | uri: string; 12 | onProgress: ProgressCallback; 13 | } 14 | 15 | export const cloneRepo = ({path, name, uri, onProgress}: CloneRepoProps) => { 16 | logService && console.log('service - cloneRepo'); 17 | const newFolderName = getRepoNameFromUri(uri); 18 | const repoName = name || newFolderName; 19 | const repoDir = `${path}/${repoName}`; 20 | 21 | if (Platform.OS === 'android') { 22 | return cloneRepoAndroid({path, name, uri, onProgress}); 23 | } 24 | 25 | if (Platform.OS === 'ios') { 26 | return cloneRepoIOS({path, name, uri, onProgress}); 27 | } 28 | 29 | throw new NotImplemented('cloneRepo'); 30 | }; 31 | -------------------------------------------------------------------------------- /src/services/git/commit-android.ts: -------------------------------------------------------------------------------- 1 | import {ReduxRepo} from '@entities'; 2 | import {NativeModules} from 'react-native'; 3 | 4 | interface commitProps { 5 | repo: ReduxRepo; 6 | name: string; 7 | email: string; 8 | message: string; 9 | } 10 | 11 | export const commitAndroid = async ({ 12 | message, 13 | repo, 14 | email, 15 | name, 16 | }: commitProps) => { 17 | return await NativeModules.GitModule.commit(repo.path, email, name, message); 18 | }; 19 | -------------------------------------------------------------------------------- /src/services/git/commit.ts: -------------------------------------------------------------------------------- 1 | import {ReduxRepo} from '@entities'; 2 | import {getCommitRev, getGitStatus} from '@store'; 3 | import {ThunkDispatchType} from '@hooks'; 4 | import {logService, NotImplemented} from '../debug'; 5 | import {getRepoPath} from '@utils'; 6 | import {commitAndroid} from '@services/git/commit-android'; 7 | import {Platform} from 'react-native'; 8 | 9 | interface commitProps { 10 | title?: string; 11 | description?: string; 12 | repo: ReduxRepo; 13 | name: string; 14 | email: string; 15 | dispatch: ThunkDispatchType; 16 | } 17 | 18 | export const commit = async ({ 19 | title, 20 | description, 21 | repo, 22 | email, 23 | name, 24 | dispatch, 25 | }: commitProps) => { 26 | logService && console.log('service - commit'); 27 | 28 | const message = `${title}\n${description}`; 29 | 30 | const repoPath = getRepoPath(repo.path); 31 | 32 | if (Platform.OS === 'android') { 33 | await commitAndroid({message, email, name, repo}); 34 | } else { 35 | throw new NotImplemented('commit'); 36 | } 37 | 38 | /** 39 | * While these may have `.then`, they're not promises in themselves. As such, we cannot use `await`. This should fix that 40 | */ 41 | const gitStatusProm = new Promise(resolve => { 42 | dispatch(getGitStatus()).then(() => { 43 | resolve(); 44 | }); 45 | }); 46 | 47 | const gitRevProm = new Promise(resolve => { 48 | dispatch(getCommitRev({path: repoPath, repoId: repo.id})).then(() => { 49 | resolve(); 50 | }); 51 | }); 52 | 53 | await Promise.all([gitStatusProm, gitRevProm]); 54 | }; 55 | -------------------------------------------------------------------------------- /src/services/git/create-branch-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeModules} from 'react-native'; 2 | import type {CreateBranchProps} from './create-branch'; 3 | 4 | export const createBranchAndroid = async ({ 5 | repo, 6 | branchName, 7 | }: CreateBranchProps) => { 8 | return await NativeModules.GitModule.createBranch(repo.path, branchName); 9 | }; 10 | -------------------------------------------------------------------------------- /src/services/git/create-branch.ts: -------------------------------------------------------------------------------- 1 | import {ReduxRepo} from '@entities'; 2 | import {ThunkDispatchType} from '@hooks'; 3 | import {checkoutBranch} from './checkout-branch'; 4 | import {logService, NotImplemented} from '../debug'; 5 | import {Platform} from 'react-native'; 6 | import {createBranchAndroid} from '@services/git/create-branch-android'; 7 | 8 | export interface CreateBranchProps { 9 | repo: ReduxRepo; 10 | branchName: string; 11 | checkAfterCreate: boolean; 12 | dispatch: ThunkDispatchType; 13 | } 14 | export const createBranch = async ({ 15 | repo, 16 | branchName, 17 | checkAfterCreate, 18 | dispatch, 19 | }: CreateBranchProps) => { 20 | logService && console.log('service - createBranch'); 21 | 22 | if (Platform.OS === 'android') { 23 | await createBranchAndroid({repo, branchName, dispatch, checkAfterCreate}); 24 | } else { 25 | throw new NotImplemented('createBranch'); 26 | } 27 | 28 | if (checkAfterCreate) { 29 | await checkoutBranch({branchName, repo, dispatch, remote: false}); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/services/git/create-remote-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeModules} from 'react-native'; 2 | import {CreateRemoteProps} from './create-remote'; 3 | 4 | export const createRemoteAndroid = async ({ 5 | repo, 6 | remoteName, 7 | remoteURL, 8 | }: CreateRemoteProps) => { 9 | return await NativeModules.GitModule.addRemote( 10 | repo.path, 11 | remoteName, 12 | remoteURL, 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /src/services/git/create-remote.ts: -------------------------------------------------------------------------------- 1 | import {ReduxRepo} from '@entities'; 2 | import {ThunkDispatchType} from '@hooks'; 3 | import {fetch} from './fetch'; 4 | import {logService, NotImplemented} from '../debug'; 5 | import {getRepoPath} from '@utils'; 6 | import {Platform} from 'react-native'; 7 | import {createRemoteAndroid} from '@services/git/create-remote-android'; 8 | import {ProgressCallback} from '@types'; 9 | 10 | export interface CreateRemoteProps { 11 | repo: ReduxRepo; 12 | remoteName: string; 13 | remoteURL: string; 14 | onProgress: ProgressCallback; 15 | dispatch: ThunkDispatchType; 16 | } 17 | 18 | export const createRemote = async ({ 19 | repo, 20 | remoteName, 21 | remoteURL, 22 | onProgress, 23 | dispatch, 24 | }: CreateRemoteProps) => { 25 | logService && console.log('service - createRemote'); 26 | const repoPath = getRepoPath(repo.path); 27 | 28 | if (Platform.OS === 'android') { 29 | await createRemoteAndroid({ 30 | repo, 31 | remoteName, 32 | remoteURL, 33 | onProgress, 34 | dispatch, 35 | }); 36 | } else { 37 | throw new NotImplemented('createRemote'); 38 | } 39 | 40 | await fetch({ 41 | dir: repoPath, 42 | remote: remoteName, 43 | fetchAll: true, 44 | prune: false, 45 | onProgress, 46 | repo, 47 | dispatch, 48 | }); 49 | }; 50 | -------------------------------------------------------------------------------- /src/services/git/create-repo.ts: -------------------------------------------------------------------------------- 1 | import {Repo} from '@entities'; 2 | import {getRepoNameFromPath} from '@utils'; 3 | import {logService} from '../debug'; 4 | import {Platform} from 'react-native'; 5 | import {currentBranch} from './current-branch'; 6 | 7 | const iOS = Platform.OS === 'ios'; 8 | 9 | export const createNewRepo = async (path: string, name?: string) => { 10 | logService && console.log('service - createNewRepo'); 11 | const newRepo = new Repo(); 12 | const currentBranchName = await currentBranch({path}); 13 | 14 | if (!currentBranchName) { 15 | throw 'This path is not a git repository'; 16 | } 17 | newRepo.currentBranchName = currentBranchName; 18 | 19 | const repoName = name || getRepoNameFromPath(path); 20 | 21 | newRepo.name = repoName; 22 | // iOS needs this workaround - see also the 23 | newRepo.path = iOS ? repoName : path; 24 | newRepo.lastUpdated = new Date(Date.now()); 25 | await newRepo.save(); 26 | return newRepo; 27 | }; 28 | -------------------------------------------------------------------------------- /src/services/git/current-branch-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeModules} from 'react-native'; 2 | import type {CurrentBranchProps} from '@services/git/current-branch'; 3 | 4 | export const currentBranchAndroid = async ({path}: CurrentBranchProps) => { 5 | return await NativeModules.GitModule.currentBranch(path); 6 | }; 7 | -------------------------------------------------------------------------------- /src/services/git/current-branch.ts: -------------------------------------------------------------------------------- 1 | import {Platform} from 'react-native'; 2 | import {currentBranchAndroid} from '@services/git/current-branch-android'; 3 | import {NotImplemented} from '@services/debug'; 4 | 5 | export interface CurrentBranchProps { 6 | path: string; 7 | } 8 | 9 | export const currentBranch = async (props: CurrentBranchProps) => { 10 | if (Platform.OS === 'android') { 11 | return await currentBranchAndroid(props); 12 | } 13 | 14 | throw new NotImplemented('currentBranch'); 15 | }; 16 | -------------------------------------------------------------------------------- /src/services/git/delete-local-branch-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeModules} from 'react-native'; 2 | import type {DeleteLocalBranchProps} from './delete-local-branch'; 3 | 4 | export const deleteLocalBranchAndroid = async ({ 5 | repo, 6 | branchName, 7 | }: DeleteLocalBranchProps) => { 8 | return await NativeModules.GitModule.deleteLocalBranch(repo.path, branchName); 9 | }; 10 | -------------------------------------------------------------------------------- /src/services/git/delete-local-branch.ts: -------------------------------------------------------------------------------- 1 | import {ReduxRepo} from '@entities'; 2 | import {ThunkDispatchType} from '@hooks'; 3 | import {getLocalBranches} from '@store'; 4 | import {logService, NotImplemented} from '../debug'; 5 | import {Platform} from 'react-native'; 6 | import {deleteLocalBranchAndroid} from '@services/git/delete-local-branch-android'; 7 | 8 | export interface DeleteLocalBranchProps { 9 | repo: ReduxRepo; 10 | branchName: string; 11 | dispatch: ThunkDispatchType; 12 | } 13 | export const deleteLocalBranch = async ({ 14 | repo, 15 | branchName, 16 | dispatch, 17 | }: DeleteLocalBranchProps) => { 18 | logService && console.log('service - deleteLocalBranch'); 19 | 20 | if (Platform.OS === 'android') { 21 | await deleteLocalBranchAndroid({ 22 | repo, 23 | branchName, 24 | dispatch, 25 | }); 26 | } else { 27 | throw new NotImplemented('deleteLocalBranch'); 28 | } 29 | 30 | dispatch(getLocalBranches(repo.path)); 31 | }; 32 | -------------------------------------------------------------------------------- /src/services/git/delete-repo.ts: -------------------------------------------------------------------------------- 1 | import {Repo, ReduxRepo} from '@entities'; 2 | import {getConnection} from 'typeorm'; 3 | import {logService} from '../debug'; 4 | 5 | export const deleteRepo = async (repo: ReduxRepo) => { 6 | logService && console.log('service - deleteRepo'); 7 | 8 | await getConnection() 9 | .createQueryBuilder() 10 | .delete() 11 | .from(Repo) 12 | .where('id = :id', {id: repo.id}) 13 | .execute(); 14 | }; 15 | -------------------------------------------------------------------------------- /src/services/git/fetch-android.ts: -------------------------------------------------------------------------------- 1 | import type {FetchProps} from '@services'; 2 | import {NativeEventEmitter, NativeModules} from 'react-native'; 3 | 4 | export const fetchAndroid = async ({ 5 | remote, 6 | fetchAll, 7 | prune, 8 | onProgress, 9 | repo, 10 | }: FetchProps) => { 11 | const eventEmitter = new NativeEventEmitter(NativeModules.GitModule); 12 | 13 | const eventListener = eventEmitter.addListener( 14 | 'FetchProgress', 15 | (event: {phase: string; loaded: number; total: number}) => { 16 | const {phase, loaded, total} = event; 17 | onProgress({phase, loaded, total}); 18 | }, 19 | ); 20 | 21 | await NativeModules.GitModule.fetch(repo.path, remote, !fetchAll, prune); 22 | eventListener.remove(); 23 | }; 24 | -------------------------------------------------------------------------------- /src/services/git/fetch.ts: -------------------------------------------------------------------------------- 1 | import {getCommitRev, getRemotesAndBranches} from '@store'; 2 | import {ReduxRepo} from '@entities'; 3 | import {ThunkDispatchType} from '@hooks'; 4 | import {logService, NotImplemented} from '../debug'; 5 | import {Platform} from 'react-native'; 6 | import {fetchAndroid} from '@services/git/fetch-android'; 7 | import {ProgressCallback} from '@types'; 8 | 9 | export interface FetchProps { 10 | dir: string; 11 | remote: string; 12 | fetchAll: boolean; 13 | prune: boolean; 14 | onProgress: ProgressCallback; 15 | repo: ReduxRepo; 16 | dispatch: ThunkDispatchType; 17 | } 18 | 19 | export const fetch = async (props: FetchProps) => { 20 | const {dir, remote, fetchAll, prune, onProgress, repo, dispatch} = props; 21 | 22 | logService && console.log('service - fetch'); 23 | 24 | if (Platform.OS === 'android') { 25 | await fetchAndroid(props); 26 | 27 | dispatch(getCommitRev({path: repo.path, repoId: repo.id})); 28 | dispatch(getRemotesAndBranches(repo.path)); 29 | 30 | return; 31 | } 32 | 33 | throw new NotImplemented('fetch'); 34 | }; 35 | -------------------------------------------------------------------------------- /src/services/git/get-commit-header-body.ts: -------------------------------------------------------------------------------- 1 | import {GitLogCommit} from './git-log'; 2 | import {logService} from '../debug'; 3 | 4 | /** 5 | * Either grab until the first newline if shorter than 55 chars 6 | * Or grab the first 56 and call _that_ the title 7 | */ 8 | const headerBodyRegex = /(.{0,55}(?:\n|\r\n)|.{0,56})([\s\S]*)/; 9 | 10 | interface GetCommitHeaderBody { 11 | commit: GitLogCommit; 12 | } 13 | 14 | export const getCommitHeaderBody = ({commit}: GetCommitHeaderBody) => { 15 | logService && console.log('service - getCommitHeaderBody'); 16 | 17 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 18 | const [_, title = '', message = ''] = 19 | headerBodyRegex.exec(commit.message) || []; 20 | return {title: title.trim(), message: message.trim()}; 21 | }; 22 | -------------------------------------------------------------------------------- /src/services/git/get-file-state-changes-android.ts: -------------------------------------------------------------------------------- 1 | import {ChangesArrayItem} from '@services/git/status'; 2 | import {NativeModules} from 'react-native'; 3 | 4 | export const getFileStateChangesAndroid = async ( 5 | commitHash1: string, 6 | commitHash2: string, 7 | dir: string, 8 | ): Promise => { 9 | return await NativeModules.GitModule.getFileStateChanges( 10 | dir, 11 | commitHash1, 12 | commitHash2, 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /src/services/git/get-file-state-changes.ts: -------------------------------------------------------------------------------- 1 | import {ChangesArrayItem} from '@services/git/status'; 2 | import {logService, NotImplemented} from '../debug'; 3 | import {Platform} from 'react-native'; 4 | import {getFileStateChangesAndroid} from './get-file-state-changes-android'; 5 | 6 | export const getFileStateChanges = async ( 7 | commitHash1: string, 8 | commitHash2: string, 9 | dir: string, 10 | ): Promise => { 11 | logService && console.log('service - getFileStateChanges'); 12 | 13 | if (Platform.OS === 'android') { 14 | return await getFileStateChangesAndroid(commitHash1, commitHash2, dir); 15 | } 16 | 17 | throw new NotImplemented('getFileStateChanges'); 18 | }; 19 | -------------------------------------------------------------------------------- /src/services/git/get-push-pull.ts: -------------------------------------------------------------------------------- 1 | import {revList} from './rev-list'; 2 | import {logService} from '../debug'; 3 | import {getRepoPath} from '@utils'; 4 | import {currentBranch} from './current-branch'; 5 | import {getTrackedBranch} from './get-tracked-branch'; 6 | 7 | interface GetPushPullProps { 8 | path: string; 9 | } 10 | 11 | export const getPushPull = async ({path}: GetPushPullProps) => { 12 | logService && console.log('service - getPushPull'); 13 | 14 | const repoPath = getRepoPath(path); 15 | 16 | const currBranch = (await currentBranch({ 17 | path: repoPath, 18 | })) as string; 19 | 20 | const trackedBranch = await getTrackedBranch({ 21 | path: repoPath, 22 | branchName: currBranch, 23 | }); 24 | 25 | if (!trackedBranch) 26 | return { 27 | toPush: [], 28 | toPull: [], 29 | }; 30 | 31 | const {branch1Diff: toPull, branch2Diff: toPush} = await revList({ 32 | dir: repoPath, 33 | branchName1: currBranch, 34 | branchName2: trackedBranch, 35 | }); 36 | 37 | return { 38 | toPush, 39 | toPull, 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /src/services/git/get-tracked-branch-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeModules} from 'react-native'; 2 | 3 | interface FindTrackedRemoteBranchProps { 4 | branchName: string; 5 | path: string; 6 | } 7 | export const getTrackedBranchAndroid = async ({ 8 | branchName, 9 | path, 10 | }: FindTrackedRemoteBranchProps) => { 11 | const trackingBranch = await NativeModules.GitModule.getTrackedBranch( 12 | path, 13 | branchName, 14 | ); 15 | 16 | return trackingBranch || null; 17 | }; 18 | -------------------------------------------------------------------------------- /src/services/git/get-tracked-branch.ts: -------------------------------------------------------------------------------- 1 | import {logService, NotImplemented} from '../debug'; 2 | import {Platform} from 'react-native'; 3 | import {getTrackedBranchAndroid} from '@services/git/get-tracked-branch-android'; 4 | 5 | interface FindTrackedRemoteBranchProps { 6 | branchName: string; 7 | path: string; 8 | } 9 | export const getTrackedBranch = async ({ 10 | branchName, 11 | path, 12 | }: FindTrackedRemoteBranchProps) => { 13 | logService && console.log('service - findTrackedRemoteBranch'); 14 | 15 | if (Platform.OS === 'android') { 16 | return await getTrackedBranchAndroid({ 17 | branchName, 18 | path, 19 | }); 20 | } 21 | 22 | throw new NotImplemented('findTrackedRemoteBranch'); 23 | }; 24 | -------------------------------------------------------------------------------- /src/services/git/git-init-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeModules} from 'react-native'; 2 | import type {GitInitProps} from './git-init'; 3 | 4 | export const gitInitAndroid = async ({path}: GitInitProps) => { 5 | return await NativeModules.GitModule.listRemotes(path); 6 | }; 7 | -------------------------------------------------------------------------------- /src/services/git/git-init.ts: -------------------------------------------------------------------------------- 1 | import {logService, NotImplemented} from '../debug'; 2 | import {Platform} from 'react-native'; 3 | import {gitInitAndroid} from './git-init-android'; 4 | 5 | export interface GitInitProps { 6 | path: string; 7 | } 8 | export const gitInit = async ({path}: GitInitProps) => { 9 | logService && console.log('service - gitInit'); 10 | 11 | if (Platform.OS === 'android') { 12 | return await gitInitAndroid({ 13 | path, 14 | }); 15 | } 16 | 17 | throw new NotImplemented('gitInit'); 18 | }; 19 | -------------------------------------------------------------------------------- /src/services/git/git-log-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeModules} from 'react-native'; 2 | import {jgitToIsoCommit} from '@utils'; 3 | import type {GitLogCommit, GitLogProps} from './git-log'; 4 | 5 | export const gitLogAndroid = async ({repo, ref}: GitLogProps) => { 6 | const res = (await NativeModules.GitModule.gitLog( 7 | repo!.path, 8 | ref || '', 9 | )) as GitLogCommit[]; 10 | 11 | return res.map(c => { 12 | c.oid = jgitToIsoCommit(c.oid); 13 | c.parent = c.parent.map(p => jgitToIsoCommit(p)); 14 | return c; 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /src/services/git/git-log.ts: -------------------------------------------------------------------------------- 1 | import {ReduxRepo} from '@entities'; 2 | import {Platform} from 'react-native'; 3 | import {gitLogAndroid} from './git-log-android'; 4 | import {logService, NotImplemented} from '../debug'; 5 | import {CommitObject} from '@types'; 6 | 7 | export type GitLogCommit = CommitObject & { 8 | oid: string; 9 | }; 10 | 11 | export interface GitLogProps { 12 | repo: Partial; 13 | ref?: string; 14 | } 15 | 16 | export const gitLog = async ({repo, ref}: GitLogProps) => { 17 | logService && console.log('service - gitLog'); 18 | 19 | if (Platform.OS === 'android') { 20 | return gitLogAndroid({repo, ref}); 21 | } 22 | 23 | throw new NotImplemented('gitLog'); 24 | }; 25 | -------------------------------------------------------------------------------- /src/services/git/index.ts: -------------------------------------------------------------------------------- 1 | export * from './add-to-staged'; 2 | export * from './checkout-branch'; 3 | export * from './clone-repo'; 4 | export * from './commit'; 5 | export * from './create-branch'; 6 | export * from './create-remote'; 7 | export * from './create-repo'; 8 | export * from './current-branch'; 9 | export * from './delete-local-branch'; 10 | export * from './delete-repo'; 11 | export * from './fetch'; 12 | export * from './get-tracked-branch'; 13 | export * from './get-commit-header-body'; 14 | export * from './get-file-state-changes'; 15 | export * from './get-push-pull'; 16 | export * from './git-init'; 17 | export * from './git-log'; 18 | export * from './list-local-branches'; 19 | export * from './list-remote-branches'; 20 | export * from './list-remotes'; 21 | export * from './pull'; 22 | export * from './push'; 23 | export * from './read-commit'; 24 | export * from './remove-from-staged'; 25 | export * from './rename-branch'; 26 | export * from './rename-repo'; 27 | export * from './reset-files'; 28 | export * from './rev-list'; 29 | export * from './status'; 30 | -------------------------------------------------------------------------------- /src/services/git/list-local-branches-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeModules} from 'react-native'; 2 | import type {ListLocalBranchesProps} from './list-local-branches'; 3 | 4 | export const listLocalBranchesAndroid = async ({ 5 | path, 6 | }: ListLocalBranchesProps) => { 7 | return await NativeModules.GitModule.listLocalBranches(path); 8 | }; 9 | -------------------------------------------------------------------------------- /src/services/git/list-local-branches.ts: -------------------------------------------------------------------------------- 1 | import {logService, NotImplemented} from '../debug'; 2 | import {Platform} from 'react-native'; 3 | import {listLocalBranchesAndroid} from './list-local-branches-android'; 4 | 5 | export interface ListLocalBranchesProps { 6 | path: string; 7 | } 8 | export const listLocalBranches = async ({path}: ListLocalBranchesProps) => { 9 | logService && console.log('service - listLocalBranches'); 10 | 11 | if (Platform.OS === 'android') { 12 | return await listLocalBranchesAndroid({ 13 | path, 14 | }); 15 | } 16 | 17 | throw new NotImplemented('listLocalBranches'); 18 | }; 19 | -------------------------------------------------------------------------------- /src/services/git/list-remote-branches-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeModules} from 'react-native'; 2 | import type {ListRemoteBranchesProps} from './list-remote-branches'; 3 | 4 | export const listRemoteBranchesAndroid = async ({ 5 | path, 6 | remote, 7 | }: ListRemoteBranchesProps) => { 8 | return await NativeModules.GitModule.listRemoteBranches(path, remote); 9 | }; 10 | -------------------------------------------------------------------------------- /src/services/git/list-remote-branches.ts: -------------------------------------------------------------------------------- 1 | import {logService, NotImplemented} from '../debug'; 2 | import {Platform} from 'react-native'; 3 | import {listRemoteBranchesAndroid} from './list-remote-branches-android'; 4 | 5 | export interface ListRemoteBranchesProps { 6 | path: string; 7 | remote: string; 8 | } 9 | export const listRemoteBranches = async ({ 10 | path, 11 | remote, 12 | }: ListRemoteBranchesProps): Promise => { 13 | logService && console.log('service - listRemoteBranches'); 14 | 15 | if (Platform.OS === 'android') { 16 | return await listRemoteBranchesAndroid({ 17 | path, 18 | remote, 19 | }); 20 | } 21 | 22 | throw new NotImplemented('listRemoteBranches'); 23 | }; 24 | -------------------------------------------------------------------------------- /src/services/git/list-remotes-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeModules} from 'react-native'; 2 | import type {ListRemotesProps} from './list-remotes'; 3 | 4 | export const listRemotesAndroid = async ({path}: ListRemotesProps) => { 5 | return await NativeModules.GitModule.listRemotes(path); 6 | }; 7 | -------------------------------------------------------------------------------- /src/services/git/list-remotes.ts: -------------------------------------------------------------------------------- 1 | import {logService, NotImplemented} from '../debug'; 2 | import {Platform} from 'react-native'; 3 | import {listRemotesAndroid} from './list-remotes-android'; 4 | import {Remotes} from '@types'; 5 | 6 | export interface ListRemotesProps { 7 | path: string; 8 | } 9 | export const listRemotes = async ({ 10 | path, 11 | }: ListRemotesProps): Promise => { 12 | logService && console.log('service - listRemotes'); 13 | 14 | if (Platform.OS === 'android') { 15 | return await listRemotesAndroid({ 16 | path, 17 | }); 18 | } 19 | 20 | throw new NotImplemented('listRemotes'); 21 | }; 22 | -------------------------------------------------------------------------------- /src/services/git/pull-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeEventEmitter, NativeModules} from 'react-native'; 2 | import {ProgressCallback} from '@types'; 3 | 4 | interface PullAndroidProps { 5 | remote: string; 6 | remoteRef: string; 7 | authToken: string; 8 | onProgress: ProgressCallback; 9 | path: string; 10 | } 11 | 12 | export const pullAndroid = ({ 13 | path, 14 | remote, 15 | remoteRef, 16 | authToken, 17 | onProgress, 18 | }: PullAndroidProps) => { 19 | const eventEmitter = new NativeEventEmitter(NativeModules.GitModule); 20 | 21 | const eventListener = eventEmitter.addListener( 22 | 'PullProgress', 23 | (event: {phase: string; loaded: number; total: number}) => { 24 | const {phase, loaded, total} = event; 25 | onProgress({phase, loaded, total}); 26 | }, 27 | ); 28 | 29 | return new Promise((resolve, reject) => { 30 | NativeModules.GitModule.pull(path, remote, remoteRef, authToken) 31 | .then(() => { 32 | eventListener.remove(); 33 | }) 34 | .then(() => resolve()) 35 | .catch((e: Error) => reject(e)); 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /src/services/git/pull.ts: -------------------------------------------------------------------------------- 1 | import {GITHUB_TOKEN_STORAGE_KEY} from '@constants'; 2 | import {getCommitRev, getGitLog} from '@store'; 3 | import RNSecureKeyStore from 'react-native-secure-key-store'; 4 | 5 | import {ReduxRepo} from '@entities'; 6 | import {ThunkDispatchType} from '@hooks'; 7 | import {ProgressCallback, RemoteBranch} from '@types'; 8 | import {logService, NotImplemented} from '../debug'; 9 | import {getRepoPath} from '@utils'; 10 | import {pullAndroid} from './pull-android'; 11 | import {Platform} from 'react-native'; 12 | 13 | interface PushProps { 14 | destination: RemoteBranch; 15 | branch: string; 16 | onProgress: ProgressCallback; 17 | repo: ReduxRepo; 18 | dispatch: ThunkDispatchType; 19 | email: string; 20 | name: string; 21 | } 22 | 23 | export const pull = async ({ 24 | destination, 25 | branch, 26 | repo, 27 | dispatch, 28 | onProgress, 29 | email, 30 | name, 31 | }: PushProps) => { 32 | logService && console.log('service - pull'); 33 | 34 | let GH_TOKEN = ''; 35 | 36 | try { 37 | GH_TOKEN = await RNSecureKeyStore.get(GITHUB_TOKEN_STORAGE_KEY); 38 | } catch (e) {} 39 | 40 | const repoPath = getRepoPath(repo.path); 41 | 42 | if (Platform.OS === 'android') { 43 | await pullAndroid({ 44 | path: repo.path, 45 | remote: destination.remote, 46 | remoteRef: destination.name, 47 | authToken: GH_TOKEN, 48 | onProgress, 49 | }); 50 | } else { 51 | throw new NotImplemented('pull'); 52 | } 53 | 54 | dispatch(getCommitRev({path: repoPath, repoId: repo.id})); 55 | dispatch(getGitLog()); 56 | }; 57 | -------------------------------------------------------------------------------- /src/services/git/push-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeEventEmitter, NativeModules} from 'react-native'; 2 | import {ProgressCallback} from '@types'; 3 | 4 | interface PullAndroidProps { 5 | remote: string; 6 | remoteRef: string; 7 | authToken: string; 8 | onProgress: ProgressCallback; 9 | path: string; 10 | forcePush: boolean; 11 | } 12 | 13 | export const pushAndroid = ({ 14 | path, 15 | remote, 16 | remoteRef, 17 | authToken, 18 | forcePush, 19 | onProgress, 20 | }: PullAndroidProps) => { 21 | const eventEmitter = new NativeEventEmitter(NativeModules.GitModule); 22 | 23 | const eventListener = eventEmitter.addListener( 24 | 'PushProgress', 25 | (event: {phase: string; loaded: number; total: number}) => { 26 | const {phase, loaded, total} = event; 27 | onProgress({phase, loaded, total}); 28 | }, 29 | ); 30 | 31 | return new Promise((resolve, reject) => { 32 | NativeModules.GitModule.push(path, remote, remoteRef, authToken, forcePush) 33 | .then(() => { 34 | eventListener.remove(); 35 | }) 36 | .then(() => resolve()) 37 | .catch((e: Error) => reject(e)); 38 | }); 39 | }; 40 | -------------------------------------------------------------------------------- /src/services/git/push.ts: -------------------------------------------------------------------------------- 1 | import {GITHUB_TOKEN_STORAGE_KEY} from '@constants'; 2 | import {getCommitRev} from '@store'; 3 | import RNSecureKeyStore from 'react-native-secure-key-store'; 4 | 5 | import {ReduxRepo} from '@entities'; 6 | import {ThunkDispatchType} from '@hooks'; 7 | import {ProgressCallback, RemoteBranch} from '@types'; 8 | import {logService, NotImplemented} from '../debug'; 9 | import {Platform} from 'react-native'; 10 | import {pushAndroid} from './push-android'; 11 | 12 | interface PushProps { 13 | destination: RemoteBranch; 14 | forcePush: boolean; 15 | branch: string; 16 | onProgress: ProgressCallback; 17 | repo: ReduxRepo; 18 | dispatch: ThunkDispatchType; 19 | } 20 | 21 | export const push = async ({ 22 | destination, 23 | forcePush, 24 | branch, 25 | repo, 26 | dispatch, 27 | onProgress, 28 | }: PushProps) => { 29 | logService && console.log('service - push'); 30 | 31 | let GH_TOKEN = ''; 32 | 33 | try { 34 | GH_TOKEN = await RNSecureKeyStore.get(GITHUB_TOKEN_STORAGE_KEY); 35 | } catch (e) {} 36 | 37 | if (Platform.OS === 'android') { 38 | await pushAndroid({ 39 | path: repo.path, 40 | remote: destination.remote, 41 | remoteRef: destination.name, 42 | authToken: GH_TOKEN, 43 | forcePush: forcePush, 44 | onProgress, 45 | }); 46 | 47 | dispatch(getCommitRev({path: repo.path, repoId: repo.id})); 48 | 49 | return; 50 | } 51 | 52 | throw new NotImplemented('push'); 53 | }; 54 | -------------------------------------------------------------------------------- /src/services/git/read-commit-android.ts: -------------------------------------------------------------------------------- 1 | import type {ReadCommitProps} from './read-commit'; 2 | import {NativeModules} from 'react-native'; 3 | import {jgitToIsoCommit} from '@utils'; 4 | import type {GitLogCommit} from './git-log'; 5 | 6 | export const readCommitAndroid = async ({path, oid}: ReadCommitProps) => { 7 | const res = (await NativeModules.GitModule.readCommit( 8 | path, 9 | oid, 10 | )) as GitLogCommit; 11 | 12 | res.oid = jgitToIsoCommit(res.oid); 13 | res.parent = res.parent.map(p => jgitToIsoCommit(p)); 14 | 15 | return res; 16 | }; 17 | -------------------------------------------------------------------------------- /src/services/git/read-commit.ts: -------------------------------------------------------------------------------- 1 | import {logService, NotImplemented} from '../debug'; 2 | import {Platform} from 'react-native'; 3 | import {readCommitAndroid} from './read-commit-android'; 4 | 5 | export interface ReadCommitProps { 6 | oid: string; 7 | path: string; 8 | } 9 | 10 | export const readCommit = async ({path, oid}: ReadCommitProps) => { 11 | logService && console.log('service - readCommit'); 12 | 13 | if (Platform.OS === 'android') { 14 | return readCommitAndroid({path, oid}); 15 | } 16 | 17 | throw new NotImplemented('readCommit'); 18 | }; 19 | -------------------------------------------------------------------------------- /src/services/git/remove-from-staged-android.ts: -------------------------------------------------------------------------------- 1 | import type {RemoveFromStagedProps} from '@services'; 2 | import {NativeModules} from 'react-native'; 3 | 4 | export const removeFromStageAndroid = async ({ 5 | changes, 6 | repo, 7 | }: RemoveFromStagedProps) => { 8 | const fileNames = changes.map(c => c.fileName); 9 | await NativeModules.GitModule.removeFromStage(repo.path, fileNames); 10 | }; 11 | -------------------------------------------------------------------------------- /src/services/git/remove-from-staged.ts: -------------------------------------------------------------------------------- 1 | import {ChangesArrayItem} from '@services'; 2 | import {ReduxRepo} from '@entities'; 3 | import {Platform} from 'react-native'; 4 | import {removeFromStageAndroid} from './remove-from-staged-android'; 5 | import {NotImplemented} from '@services/debug'; 6 | 7 | export interface RemoveFromStagedProps { 8 | changes: ChangesArrayItem[]; 9 | repo: ReduxRepo; 10 | } 11 | 12 | export const removeFromStaged = ({changes, repo}: RemoveFromStagedProps) => { 13 | if (Platform.OS === 'android') { 14 | return removeFromStageAndroid({changes, repo}); 15 | } 16 | 17 | throw new NotImplemented('removeFromStaged'); 18 | }; 19 | -------------------------------------------------------------------------------- /src/services/git/rename-branch-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeModules} from 'react-native'; 2 | import type {RenameBranchProps} from './rename-branch'; 3 | 4 | export const renameBranchAndroid = async ({ 5 | repo, 6 | oldBranchName, 7 | branchName, 8 | }: RenameBranchProps) => { 9 | return await NativeModules.GitModule.renameBranch( 10 | repo.path, 11 | oldBranchName, 12 | branchName, 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /src/services/git/rename-branch.ts: -------------------------------------------------------------------------------- 1 | import {ThunkDispatchType} from '@hooks'; 2 | import {changeBranch, getLocalBranches} from '@store'; 3 | import {ReduxRepo} from '@entities'; 4 | import {logService, NotImplemented} from '../debug'; 5 | import {getRepoPath} from '@utils'; 6 | import {Platform} from 'react-native'; 7 | import {renameBranchAndroid} from '@services/git/rename-branch-android'; 8 | 9 | export interface RenameBranchProps { 10 | branchName: string; 11 | oldBranchName: string; 12 | checkout: boolean; 13 | repo: ReduxRepo; 14 | dispatch: ThunkDispatchType; 15 | } 16 | export const renameBranch = async ({ 17 | branchName, 18 | oldBranchName, 19 | checkout, 20 | repo, 21 | dispatch, 22 | }: RenameBranchProps) => { 23 | logService && console.log('service - renameBranch'); 24 | 25 | const repoPath = getRepoPath(repo.path); 26 | 27 | if (Platform.OS === 'android') { 28 | await renameBranchAndroid({ 29 | branchName, 30 | oldBranchName, 31 | checkout, 32 | repo, 33 | dispatch, 34 | }); 35 | } else { 36 | throw new NotImplemented('renameBranch'); 37 | } 38 | 39 | dispatch(getLocalBranches(repoPath)); 40 | 41 | if (checkout) { 42 | dispatch(changeBranch({repoId: repo.id, branchName})); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/services/git/rename-repo.ts: -------------------------------------------------------------------------------- 1 | import {Repo} from '@entities'; 2 | import {getConnection} from 'typeorm'; 3 | import {ThunkDispatchType} from '@hooks'; 4 | import {findRepoList} from '@store'; 5 | import {logService} from '../debug'; 6 | 7 | interface RenameRepoProps { 8 | repoId: string | number; 9 | name: string; 10 | dispatch: ThunkDispatchType; 11 | } 12 | 13 | export const renameRepo = async ({repoId, name, dispatch}: RenameRepoProps) => { 14 | logService && console.log('service - renameRepo'); 15 | 16 | await getConnection() 17 | .createQueryBuilder() 18 | .update(Repo) 19 | .set({ 20 | name, 21 | }) 22 | .where('id = :id', {id: repoId}) 23 | .execute(); 24 | 25 | dispatch(findRepoList()); 26 | }; 27 | -------------------------------------------------------------------------------- /src/services/git/reset-files-android.ts: -------------------------------------------------------------------------------- 1 | import {getGitStatus} from '@store'; 2 | import type {ResetFilesProps} from './reset-files'; 3 | import {NativeModules} from 'react-native'; 4 | 5 | export const resetFilesAndroid = async ({ 6 | path, 7 | files, 8 | dispatch, 9 | }: ResetFilesProps) => { 10 | await NativeModules.GitModule.resetPaths(path, files); 11 | 12 | dispatch(getGitStatus()); 13 | }; 14 | -------------------------------------------------------------------------------- /src/services/git/reset-files.ts: -------------------------------------------------------------------------------- 1 | import {Platform} from 'react-native'; 2 | import {ThunkDispatchType} from '@hooks'; 3 | import {getGitStatus} from '@store'; 4 | import {logService, NotImplemented} from '../debug'; 5 | import {resetFilesAndroid} from './reset-files-android'; 6 | 7 | export interface ResetFilesProps { 8 | path: string; 9 | // Filepaths 10 | files: string[]; 11 | dispatch: ThunkDispatchType; 12 | } 13 | 14 | export const resetFiles = async ({path, files, dispatch}: ResetFilesProps) => { 15 | logService && console.log('service - resetFiles'); 16 | 17 | if (Platform.OS === 'android') { 18 | await resetFilesAndroid({path, files, dispatch}); 19 | } else { 20 | throw new NotImplemented('resetFiles'); 21 | } 22 | 23 | dispatch(getGitStatus()); 24 | }; 25 | -------------------------------------------------------------------------------- /src/services/git/rev-list-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeModules} from 'react-native'; 2 | import type {RevListProps} from './rev-list'; 3 | import {jgitToIsoCommit} from '@utils'; 4 | 5 | export const revListAndroid = async ({ 6 | dir, 7 | branchName1, 8 | branchName2, 9 | }: RevListProps) => { 10 | let {branch1Diff, branch2Diff} = await NativeModules.GitModule.revList( 11 | dir, 12 | branchName1, 13 | branchName2, 14 | ); 15 | 16 | branch1Diff = branch1Diff.map((c: string) => jgitToIsoCommit(c)); 17 | branch2Diff = branch2Diff.map((c: string) => jgitToIsoCommit(c)); 18 | 19 | return { 20 | // What was in "branch 2" but not in branch 1 21 | branch1Diff, 22 | // What was in "branch 1" but not in branch 2 23 | branch2Diff, 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /src/services/git/rev-list.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an implementation of: 3 | * `git rev-list --count main ^asdf` 4 | * 5 | * It's meant to report the difference between two branches (one remote) 6 | * so that we can easily report "needs to push" and "needs to pull" 7 | * 8 | */ 9 | import {Platform} from 'react-native'; 10 | import {revListAndroid} from './rev-list-android'; 11 | import {logService, NotImplemented} from '../debug'; 12 | 13 | export interface RevListProps { 14 | dir: string; 15 | branchName1: string; 16 | branchName2: string; 17 | } 18 | 19 | export const revList = async ({ 20 | dir, 21 | branchName1, 22 | branchName2, 23 | }: RevListProps) => { 24 | logService && console.log('service - revList'); 25 | 26 | if (Platform.OS === 'android') { 27 | return await revListAndroid({dir, branchName1, branchName2}); 28 | } 29 | 30 | throw new NotImplemented('revList'); 31 | }; 32 | -------------------------------------------------------------------------------- /src/services/git/status-android.ts: -------------------------------------------------------------------------------- 1 | import {NativeModules} from 'react-native'; 2 | import {ChangesArrayItem} from './status'; 3 | 4 | export const getRepoStatusAndroid = async (path: string) => { 5 | return (await NativeModules.GitModule.status(path)) as ChangesArrayItem[]; 6 | }; 7 | -------------------------------------------------------------------------------- /src/services/git/status.ts: -------------------------------------------------------------------------------- 1 | import {Platform} from 'react-native'; 2 | import {getRepoStatusAndroid} from './status-android'; 3 | import {logService, NotImplemented} from '../debug'; 4 | 5 | export interface ChangesArrayItem { 6 | fileName: string; 7 | staged: boolean; 8 | unstagedChanges: boolean; 9 | fileStatus: 'unmodified' | 'added' | 'deleted' | 'modified' | 'conflicted'; 10 | } 11 | 12 | export const getRepoStatus = async (path: string) => { 13 | logService && console.log('service - getRepoStatus'); 14 | 15 | if (Platform.OS === 'android') { 16 | return getRepoStatusAndroid(path); 17 | } 18 | 19 | throw new NotImplemented('getRepoStatus'); 20 | }; 21 | -------------------------------------------------------------------------------- /src/services/github/base-request.ts: -------------------------------------------------------------------------------- 1 | import {GHBase} from './constants'; 2 | 3 | type ResponseType = Promise< 4 | Omit & {json: () => Promise} 5 | >; 6 | export interface baseRequestProps { 7 | path: string; 8 | method: 'GET' | 'POST'; 9 | gh_token: string; 10 | body?: RequestInit['body']; 11 | } 12 | export const baseRequest = ({ 13 | path, 14 | method, 15 | gh_token, 16 | body, 17 | }: baseRequestProps) => { 18 | return fetch(`${GHBase}${path}`, { 19 | method, 20 | headers: { 21 | ...(gh_token ? {Authorization: `token ${gh_token}`} : {}), 22 | Accept: 'application/vnd.github.v3+json', 23 | }, 24 | body, 25 | }) as ResponseType; 26 | }; 27 | -------------------------------------------------------------------------------- /src/services/github/constants.ts: -------------------------------------------------------------------------------- 1 | export const GHBase = `https://api.github.com`; 2 | -------------------------------------------------------------------------------- /src/services/github/create-issue-with-api.ts: -------------------------------------------------------------------------------- 1 | import {RepoConfig} from '@constants/repo-config'; 2 | import {baseRequest} from '@services/github/base-request'; 3 | import RNSecureKeyStore from 'react-native-secure-key-store'; 4 | import {GITHUB_TOKEN_STORAGE_KEY} from '@constants'; 5 | 6 | interface IssueAPIReturn { 7 | id: number; 8 | node_id: string; 9 | url: string; 10 | repository_url: string; 11 | labels_url: string; 12 | comments_url: string; 13 | events_url: string; 14 | html_url: string; 15 | number: number; 16 | state: string; 17 | title: string; 18 | body: string; 19 | locked: true; 20 | active_lock_reason: string; 21 | comments: number; 22 | } 23 | 24 | export const createIssueWithAPI = async (contents: string) => { 25 | const token = await RNSecureKeyStore.get(GITHUB_TOKEN_STORAGE_KEY); 26 | 27 | const issueContents = await baseRequest({ 28 | path: `/repos/${RepoConfig.owner}/${RepoConfig.name}/issues`, 29 | method: 'POST', 30 | gh_token: token, 31 | body: JSON.stringify({ 32 | title: '[BUG] {PLEASE EDIT} Report filed from app', 33 | body: contents, 34 | }), 35 | }).then(res => res.json()); 36 | 37 | return `https://github.com/${RepoConfig.owner}/${RepoConfig.name}/issues/${issueContents.number}`; 38 | }; 39 | -------------------------------------------------------------------------------- /src/services/github/get-current-user-emails.ts: -------------------------------------------------------------------------------- 1 | import {baseRequest} from './base-request'; 2 | 3 | interface CurrentUserEmails { 4 | email: string; 5 | primary: boolean; 6 | verified: boolean; 7 | visibility: null | 'public'; 8 | } 9 | 10 | export const getCurrentUserEmails = (gh_token: string) => { 11 | return baseRequest({ 12 | path: '/user/emails', 13 | method: 'GET', 14 | gh_token, 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /src/services/github/get-file-contents.ts: -------------------------------------------------------------------------------- 1 | import {baseRequest} from '@services/github/base-request'; 2 | import {RepoConfig} from '@constants/repo-config'; 3 | import {Buffer} from 'buffer'; 4 | 5 | interface ContentsAPIReturn { 6 | name: string; 7 | path: string; 8 | sha: string; 9 | size: number; 10 | url: string; 11 | html_url: string; 12 | git_url: string; 13 | download_url: string; 14 | type: 'file'; 15 | content: string; 16 | encoding: 'base64'; 17 | _links: { 18 | self: string; 19 | git: string; 20 | html: string; 21 | }; 22 | } 23 | 24 | export const getFileContents = async (path: string) => { 25 | const fileContents = await baseRequest({ 26 | path: `/repos/${RepoConfig.owner}/${RepoConfig.name}/contents/${path}`, 27 | method: 'GET', 28 | gh_token: '', 29 | }).then(res => res.json()); 30 | 31 | return Buffer.from(fileContents.content, 'base64').toString('utf-8'); 32 | }; 33 | -------------------------------------------------------------------------------- /src/services/github/index.ts: -------------------------------------------------------------------------------- 1 | export * from './get-current-user'; 2 | export * from './get-current-user-emails'; 3 | export * from './open-issue'; 4 | -------------------------------------------------------------------------------- /src/services/github/open-issue.ts: -------------------------------------------------------------------------------- 1 | import {Linking} from 'react-native'; 2 | import {getFileContents} from './get-file-contents'; 3 | import {createIssueWithAPI} from '@services/github/create-issue-with-api'; 4 | import {FullError} from '@types'; 5 | 6 | export async function openGitHubIssue(err: FullError) { 7 | const bugReport = await getFileContents( 8 | '.github/ISSUE_TEMPLATE/bug_report.md', 9 | ); 10 | 11 | const body = bugReport 12 | .replace( 13 | '{{Put the simple error code here}}', 14 | `**${err.explainMessage}**: ${err.errorMessage}`, 15 | ) 16 | .replace('{{Put the stack trace here}}', err.callStack); 17 | 18 | const issueEditURL = await createIssueWithAPI(body); 19 | 20 | return await Linking.openURL(issueEditURL); 21 | } 22 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './git'; 2 | export * from './github'; 3 | -------------------------------------------------------------------------------- /src/store/database-slice.ts: -------------------------------------------------------------------------------- 1 | import {createAsyncThunk, createSlice} from '@reduxjs/toolkit'; 2 | import {createConnection, getConnectionManager} from 'typeorm'; 3 | import {Commit, Repo} from '@entities'; 4 | import {logStore} from './debug'; 5 | 6 | export const setupDatabase = createAsyncThunk( 7 | 'database/setupDatabase', 8 | async (_, {rejectWithValue}) => { 9 | try { 10 | logStore && console.log('store - SETUP DB'); 11 | await createConnection({ 12 | type: 'react-native', 13 | database: 'gitshark', 14 | location: 'default', 15 | logging: ['error', 'query', 'schema'], 16 | synchronize: true, 17 | entities: [Commit, Repo], 18 | }); 19 | } catch (err: unknown) { 20 | if ((err as any).name === 'AlreadyHasActiveConnectionError') { 21 | getConnectionManager().get('default'); 22 | return; 23 | } 24 | rejectWithValue((err as any).message || err); 25 | } 26 | }, 27 | ); 28 | 29 | const databaseSlice = createSlice({ 30 | name: 'database', 31 | initialState: { 32 | isLoaded: false, 33 | error: '', 34 | }, 35 | reducers: {}, 36 | extraReducers: { 37 | [setupDatabase.fulfilled.toString()]: state => { 38 | state.isLoaded = true; 39 | }, 40 | [setupDatabase.rejected.toString()]: (state, action) => { 41 | state.isLoaded = true; 42 | state.error = action.payload; 43 | }, 44 | }, 45 | }); 46 | 47 | export const databaseReducer = databaseSlice.reducer; 48 | -------------------------------------------------------------------------------- /src/store/debug.ts: -------------------------------------------------------------------------------- 1 | export const logStore = false; 2 | export const throwError = false; 3 | -------------------------------------------------------------------------------- /src/store/git-log-slice.ts: -------------------------------------------------------------------------------- 1 | import {createAsyncThunk, createSlice, PayloadAction} from '@reduxjs/toolkit'; 2 | import {gitLog, GitLogCommit} from '@services'; 3 | import {logStore, throwError} from './debug'; 4 | import { 5 | getSerializedErrorStr, 6 | PayloadSerializedError, 7 | StoreError, 8 | } from '@types'; 9 | 10 | export const getGitLog = createAsyncThunk( 11 | 'commits/getGitLog', 12 | async (_, {dispatch, getState}) => { 13 | logStore && console.log('store - getGitLog'); 14 | if (throwError) throw Error('This is a test error code'); 15 | 16 | const {repository} = getState() as any; 17 | const repo = repository.repo; 18 | if (!repo) return; 19 | const commits = await gitLog({repo}); 20 | return commits; 21 | }, 22 | ); 23 | 24 | const initialState = { 25 | commits: [] as GitLogCommit[], 26 | // Unused AFAIK 27 | loading: 'idle', 28 | error: null as null | StoreError, 29 | }; 30 | 31 | const commitsSlice = createSlice({ 32 | name: 'commits', 33 | initialState, 34 | reducers: { 35 | clearLog() { 36 | return initialState; 37 | }, 38 | }, 39 | extraReducers: { 40 | [getGitLog.fulfilled.toString()]: ( 41 | state, 42 | action: PayloadAction, 43 | ) => { 44 | state.commits = action.payload; 45 | }, 46 | 47 | [getGitLog.rejected.toString()]: ( 48 | state, 49 | action: PayloadSerializedError, 50 | ) => { 51 | state.error = getSerializedErrorStr(action.error); 52 | }, 53 | }, 54 | }); 55 | 56 | export const {clearLog} = commitsSlice.actions; 57 | export const commitsReducer = commitsSlice.reducer; 58 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | export * from './database-slice'; 2 | export * from './git-branches-slice'; 3 | export * from './git-changes-slice'; 4 | export * from './git-log-slice'; 5 | export * from './repo-list-slice'; 6 | export * from './repo-slice'; 7 | export * from './root-reducer'; 8 | export * from './store'; 9 | -------------------------------------------------------------------------------- /src/store/repo-list-slice.ts: -------------------------------------------------------------------------------- 1 | import {createAsyncThunk, createSlice} from '@reduxjs/toolkit'; 2 | import {Repo, getReduxRepo, ReduxRepo} from '@entities'; 3 | import {getRepository} from 'typeorm'; 4 | import {logStore} from './debug'; 5 | import { 6 | getSerializedErrorStr, 7 | PayloadSerializedError, 8 | StoreError, 9 | } from '@types'; 10 | 11 | export const findRepoList = createAsyncThunk( 12 | 'repository/findRepoList', 13 | async (_, {getState}) => { 14 | logStore && console.log('store - findRepoList'); 15 | 16 | const {database} = getState() as any; 17 | if (!database.isLoaded) return; 18 | const repoRepository = getRepository(Repo); 19 | const foundRepos = await repoRepository.find({}); 20 | if (!foundRepos) return null; 21 | return foundRepos.map(repo => getReduxRepo(repo)); 22 | }, 23 | ); 24 | 25 | const initialState = { 26 | repoList: null as ReduxRepo[] | null, 27 | error: null as null | StoreError, 28 | }; 29 | const repoListSlice = createSlice({ 30 | name: 'repository', 31 | initialState, 32 | reducers: {}, 33 | extraReducers: { 34 | [findRepoList.fulfilled.toString()]: (state, action) => { 35 | state.repoList = action.payload; 36 | }, 37 | [findRepoList.rejected.toString()]: ( 38 | state, 39 | action: PayloadSerializedError, 40 | ) => { 41 | state.error = getSerializedErrorStr(action.error); 42 | }, 43 | }, 44 | }); 45 | 46 | export const repoListReducer = repoListSlice.reducer; 47 | -------------------------------------------------------------------------------- /src/store/root-reducer.ts: -------------------------------------------------------------------------------- 1 | import {repositoryReducer} from './repo-slice'; 2 | import {combineReducers} from '@reduxjs/toolkit'; 3 | import {databaseReducer} from './database-slice'; 4 | import {changesReducer} from './git-changes-slice'; 5 | import {commitsReducer} from './git-log-slice'; 6 | import {branchesReducer} from './git-branches-slice'; 7 | import {repoListReducer} from './repo-list-slice'; 8 | 9 | export const rootReducer = combineReducers({ 10 | changes: changesReducer, 11 | commits: commitsReducer, 12 | database: databaseReducer, 13 | branches: branchesReducer, 14 | repository: repositoryReducer, 15 | repoList: repoListReducer, 16 | }); 17 | 18 | export type RootState = ReturnType; 19 | -------------------------------------------------------------------------------- /src/store/store.ts: -------------------------------------------------------------------------------- 1 | import {configureStore} from '@reduxjs/toolkit'; 2 | 3 | import {rootReducer} from './root-reducer'; 4 | 5 | export const store = configureStore({ 6 | reducer: rootReducer, 7 | }); 8 | 9 | export type AppDispatch = typeof store.dispatch; 10 | -------------------------------------------------------------------------------- /src/types/cached-github-user.ts: -------------------------------------------------------------------------------- 1 | import {CurrentUser} from '@services'; 2 | 3 | export type CachedGithubUser = Pick< 4 | CurrentUser, 5 | 'name' | 'avatar_url' | 'email' 6 | >; 7 | -------------------------------------------------------------------------------- /src/types/errors.ts: -------------------------------------------------------------------------------- 1 | export interface StoreError { 2 | // EG: undefined is not a function 3 | errorMessage: string; 4 | // EG: "_callee2$@http://localhost:8081/ind..." 5 | callStack: string; 6 | } 7 | 8 | export interface FullError extends StoreError { 9 | // EG: "An error occured while loading staged files." 10 | explainMessage: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cached-github-user'; 2 | export * from './errors'; 3 | export * from './manual-user'; 4 | export * from './navigation'; 5 | export * from './service-types'; 6 | export * from './storeTypes'; 7 | export * from './thunk-types'; 8 | -------------------------------------------------------------------------------- /src/types/json.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.json' { 2 | const content: string; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /src/types/manual-user.ts: -------------------------------------------------------------------------------- 1 | export interface ManualUser { 2 | name: string; 3 | email: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/types/media.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.mp4' { 2 | const content: {uri: string}; 3 | export default content; 4 | } 5 | 6 | declare module '*.png' { 7 | const content: {uri: string}; 8 | export default content; 9 | } 10 | -------------------------------------------------------------------------------- /src/types/navigation.ts: -------------------------------------------------------------------------------- 1 | import {StackNavigationProp} from '@react-navigation/stack'; 2 | 3 | type ScreenNames = 4 | | 'RepoList' 5 | | 'Settings' 6 | | 'Account' 7 | | 'StagingLayout' 8 | | 'RepoDetails' 9 | | 'Repository' 10 | | 'CommitAction' 11 | | 'CommitDetails' 12 | | 'ChangesTab' 13 | | 'HistoryTab'; 14 | 15 | // TODO: Improve the typings 16 | export type ScreenOpts = Record; 17 | 18 | export type NavProps = StackNavigationProp; 19 | -------------------------------------------------------------------------------- /src/types/restart.d.ts: -------------------------------------------------------------------------------- 1 | // TODO: Remove when this PR addressed: 2 | // https://github.com/avishayil/react-native-restart/pull/160 3 | declare module 'react-native-restart' { 4 | import RNRestart from 'react-native-restart/lib/typescript'; 5 | export default RNRestart; 6 | } 7 | -------------------------------------------------------------------------------- /src/types/service-types.ts: -------------------------------------------------------------------------------- 1 | export type GitProgressEvent = { 2 | phase: string; 3 | loaded: number; 4 | total: number; 5 | }; 6 | 7 | export type ProgressCallback = ( 8 | progress: GitProgressEvent, 9 | ) => void | Promise; 10 | 11 | export type CommitObject = { 12 | /** 13 | * Commit message 14 | */ 15 | message: string; 16 | /** 17 | * SHA-1 object id of corresponding file tree 18 | */ 19 | tree: string; 20 | /** 21 | * an array of zero or more SHA-1 object ids 22 | */ 23 | parent: string[]; 24 | author: { 25 | /** 26 | * The author's name 27 | */ 28 | name: string; 29 | /** 30 | * The author's email 31 | */ 32 | email: string; 33 | /** 34 | * UTC Unix timestamp in seconds 35 | */ 36 | timestamp: number; 37 | /** 38 | * Timezone difference from UTC in minutes 39 | */ 40 | timezoneOffset: number; 41 | }; 42 | committer: { 43 | /** 44 | * The committer's name 45 | */ 46 | name: string; 47 | /** 48 | * The committer's email 49 | */ 50 | email: string; 51 | /** 52 | * UTC Unix timestamp in seconds 53 | */ 54 | timestamp: number; 55 | /** 56 | * Timezone difference from UTC in minutes 57 | */ 58 | timezoneOffset: number; 59 | }; 60 | /** 61 | * PGP signature (if present) 62 | */ 63 | gpgsig?: string; 64 | }; 65 | -------------------------------------------------------------------------------- /src/types/storeTypes.ts: -------------------------------------------------------------------------------- 1 | export interface RemoteBranch { 2 | name: string; 3 | remote: string; 4 | } 5 | 6 | export interface Remotes { 7 | remote: string; 8 | url: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/types/thunk-types.ts: -------------------------------------------------------------------------------- 1 | import {PayloadAction, SerializedError} from '@reduxjs/toolkit'; 2 | import {StoreError} from '@types'; 3 | 4 | export type PayloadSerializedError = PayloadAction< 5 | void, 6 | string, 7 | never, 8 | SerializedError | string 9 | >; 10 | 11 | export const getSerializedErrorStr = (e: SerializedError | Error | string) => { 12 | if (typeof e === 'string') { 13 | const [msg, ...stack] = e.split('\n'); 14 | return { 15 | errorMessage: msg, 16 | callStack: stack.join('\n'), 17 | }; 18 | } 19 | 20 | return { 21 | errorMessage: `${e.name} - ${e.message}`, 22 | callStack: e.stack, 23 | } as StoreError; 24 | }; 25 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import {Platform} from 'react-native'; 2 | import {DocumentDirectoryPath} from 'react-native-fs'; 3 | 4 | export const getRepoNameFromPath = (path: string) => { 5 | /** 6 | * This regex is overkill but should get `test` in all of these examples: 7 | * 8 | * /here/path/test.git 9 | * /here/path/test 10 | * /test 11 | * /test.git 12 | * test 13 | * test.git 14 | */ 15 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 16 | const [_, repoName] = /(?:.*\/|^)(.*?)(?:\.git)?$/.exec(path) || []; 17 | return repoName; 18 | }; 19 | 20 | export const getRepoNameFromUri = (path: string) => { 21 | /** 22 | * This regex is overkill but should get `test` in all of these examples: 23 | * 24 | * https://github.com/unicorn-utterances/unicorn-utterances.git 25 | * https://github.com/unicorn-utterances/unicorn-utterances 26 | */ 27 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 28 | const [_, repoName] = /(?:.*\/|^)(.*?)(?:\.git)?$/.exec(path) || []; 29 | return repoName; 30 | }; 31 | 32 | export const validateEmail = (email: string) => { 33 | const re = 34 | /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 35 | return re.test(String(email).toLowerCase()); 36 | }; 37 | 38 | export const jgitToIsoCommit = (commitId: string) => { 39 | const commitRegex = /commit\s*(.*?)\s/; 40 | const [_, oid] = commitRegex.exec(commitId) || []; 41 | return oid; 42 | }; 43 | 44 | export const iOS = Platform.OS === 'ios'; 45 | export const iOSPath = DocumentDirectoryPath; 46 | 47 | export const getRepoPath = (path: string) => { 48 | if (!iOS) return path; 49 | if (path.includes('/') || path.includes('\\')) return path; 50 | return `${iOSPath}/${path}`; 51 | }; 52 | -------------------------------------------------------------------------------- /src/views/account/github-logout/github-logout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Text, View} from 'react-native'; 3 | import {DynamicStyleSheet, useDynamicValue} from 'react-native-dynamic'; 4 | import {SharkButton} from '@components/shark-button'; 5 | import {theme, UserContext} from '@constants'; 6 | import {useTranslation} from 'react-i18next'; 7 | 8 | export const GitHubLogout = () => { 9 | const {t} = useTranslation(); 10 | 11 | const styles = useDynamicValue(dynamicStyles); 12 | 13 | const {gitHubUser, logoutGitHub} = React.useContext(UserContext); 14 | 15 | return ( 16 | 17 | 24 | {gitHubUser?.name} 25 | {gitHubUser?.email} 26 | 27 | logoutGitHub()} text={t('signOut')} /> 28 | 29 | ); 30 | }; 31 | 32 | const dynamicStyles = new DynamicStyleSheet({ 33 | container: { 34 | padding: theme.spacing.m, 35 | display: 'flex', 36 | flexDirection: 'row', 37 | marginBottom: theme.spacing.xs, 38 | }, 39 | ghUserContainer: { 40 | flexGrow: 1, 41 | marginRight: theme.spacing.m, 42 | }, 43 | callout: { 44 | ...theme.textStyles.callout_01, 45 | color: theme.colors.label_high_emphasis, 46 | }, 47 | body2: { 48 | ...theme.textStyles.body_02, 49 | color: theme.colors.label_high_emphasis, 50 | opacity: theme.opacity.secondary, 51 | }, 52 | }); 53 | -------------------------------------------------------------------------------- /src/views/branches/components/branch-list-item/index.ts: -------------------------------------------------------------------------------- 1 | export * from './branch-list-item'; 2 | -------------------------------------------------------------------------------- /src/views/branches/components/confirm-checkout-dialog/confirm-checkout-dialog.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {AppDialog} from '@components/dialog'; 3 | import {SharkButton} from '@components/shark-button'; 4 | import {DynamicStyleSheet, useDynamicValue} from 'react-native-dynamic'; 5 | import {theme} from '@constants'; 6 | import {useTranslation} from 'react-i18next'; 7 | 8 | interface ConfirmCheckoutDialogProps { 9 | onDismiss: (didUpdate: boolean) => void; 10 | visible: boolean; 11 | } 12 | 13 | export const ConfirmCheckoutDialog = ({ 14 | onDismiss, 15 | visible, 16 | }: ConfirmCheckoutDialogProps) => { 17 | const {t} = useTranslation(); 18 | 19 | const styles = useDynamicValue(dynamicStyles); 20 | 21 | const parentOnDismiss = (bool: boolean) => { 22 | onDismiss(bool); 23 | }; 24 | 25 | return ( 26 | parentOnDismiss(false)} 29 | title={t('confirmCheckoutDialogTitle')} 30 | text={t('confirmCheckoutDialogText')} 31 | actions={ 32 | <> 33 | parentOnDismiss(false)} 35 | type="outline" 36 | style={styles.cancelBtn} 37 | text={t('cancelAction')} 38 | /> 39 | parentOnDismiss(true)} 41 | type="primary" 42 | text={t('discardCheckoutAction')} 43 | /> 44 | 45 | } 46 | /> 47 | ); 48 | }; 49 | 50 | const dynamicStyles = new DynamicStyleSheet({ 51 | cancelBtn: { 52 | borderColor: theme.colors.tint_on_surface_01, 53 | borderWidth: theme.borders.thick, 54 | marginRight: theme.spacing.m, 55 | }, 56 | }); 57 | -------------------------------------------------------------------------------- /src/views/branches/components/confirm-checkout-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './confirm-checkout-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/branches/components/create-branch-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-branch-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/branches/components/create-remote-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-remote-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/branches/components/delete-branch-dialog/delete-branch-dialog.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {AppDialog} from '@components/dialog'; 3 | import {SharkButton} from '@components/shark-button'; 4 | import {DynamicStyleSheet, useDynamicValue} from 'react-native-dynamic'; 5 | import {theme} from '@constants'; 6 | 7 | interface DeleteBranchDialogProps { 8 | onDismiss: (didUpdate: boolean) => void; 9 | visible: boolean; 10 | } 11 | 12 | export const DeleteBranchDialog = ({ 13 | onDismiss, 14 | visible, 15 | }: DeleteBranchDialogProps) => { 16 | const styles = useDynamicValue(dynamicStyles); 17 | 18 | const parentOnDismiss = (bool: boolean) => { 19 | onDismiss(bool); 20 | }; 21 | 22 | return ( 23 | parentOnDismiss(false)} 26 | title={'Delete branch'} 27 | text={ 28 | 'Are you sure that you want to delete the local branch from your repository?' 29 | } 30 | actions={ 31 | <> 32 | parentOnDismiss(false)} 34 | type="outline" 35 | style={styles.cancelBtn} 36 | text={'Cancel'} 37 | /> 38 | parentOnDismiss(true)} 40 | type="primary" 41 | text={'Delete'} 42 | /> 43 | 44 | } 45 | /> 46 | ); 47 | }; 48 | 49 | const dynamicStyles = new DynamicStyleSheet({ 50 | cancelBtn: { 51 | borderColor: theme.colors.tint_on_surface_01, 52 | borderWidth: theme.borders.thick, 53 | marginRight: theme.spacing.m, 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /src/views/branches/components/delete-branch-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './delete-branch-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/branches/components/on-checkout-action-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './on-checkout-action-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/branches/components/on-create-remote-action-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './on-create-remote-action-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/branches/components/remote-branch-list-item/index.ts: -------------------------------------------------------------------------------- 1 | export * from './remote-branch-list-item'; 2 | -------------------------------------------------------------------------------- /src/views/branches/components/rename-branch-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './rename-branch-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/branches/index.ts: -------------------------------------------------------------------------------- 1 | export * from './branches'; 2 | -------------------------------------------------------------------------------- /src/views/commit-action/on-commit-action-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './on-commit-action-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/commit-action/on-commit-action-dialog/on-commit-action-dialog.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {ProgressErrorDialog} from '@components/progress-error-dialog'; 3 | import {useTranslation} from 'react-i18next'; 4 | 5 | interface OnCommitActionsDialogProps { 6 | visible: boolean; 7 | commitErr?: string; 8 | } 9 | 10 | export const OnCommitActionsDialog = ({ 11 | visible, 12 | commitErr, 13 | }: OnCommitActionsDialogProps) => { 14 | const {t} = useTranslation(); 15 | 16 | return ( 17 | {}} 21 | onDismiss={() => {}} 22 | visible={visible} 23 | progress={0} 24 | indeterminate={true} 25 | bodyText={''} 26 | errorStr={commitErr || ''} 27 | /> 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/views/commit-details/components/commit-details-header/commit-detail-dual-author/index.ts: -------------------------------------------------------------------------------- 1 | export * from './commit-detail-dual-author'; 2 | -------------------------------------------------------------------------------- /src/views/commit-details/components/commit-details-header/commit-detail-single-author/index.ts: -------------------------------------------------------------------------------- 1 | export * from './commit-detail-single-author'; 2 | -------------------------------------------------------------------------------- /src/views/commit-details/components/commit-details-header/commit-details-more-info/index.ts: -------------------------------------------------------------------------------- 1 | export * from './commit-details-more-info'; 2 | -------------------------------------------------------------------------------- /src/views/commit-details/components/commit-details-header/commit-message-dropdown/index.ts: -------------------------------------------------------------------------------- 1 | export * from './commit-message-dropdown'; 2 | -------------------------------------------------------------------------------- /src/views/commit-details/components/commit-details-header/index.ts: -------------------------------------------------------------------------------- 1 | export * from './commit-details-header'; 2 | -------------------------------------------------------------------------------- /src/views/repository-changes/components/file-actions-bar/file-actions-bar-toggle-button/index.ts: -------------------------------------------------------------------------------- 1 | export * from './file-actions-bar-toggle-button'; 2 | -------------------------------------------------------------------------------- /src/views/repository-changes/components/file-actions-bar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './file-actions-bar'; 2 | -------------------------------------------------------------------------------- /src/views/repository-changes/components/file-actions-bar/stage-button-toggle/index.ts: -------------------------------------------------------------------------------- 1 | export * from './stage-button-toggle'; 2 | -------------------------------------------------------------------------------- /src/views/repository-changes/components/staging-screen-options/index.ts: -------------------------------------------------------------------------------- 1 | export * from './stage-sheet-view'; 2 | export * from './stage-split-view'; 3 | -------------------------------------------------------------------------------- /src/views/repository-changes/components/staging-screen-options/staged-changes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './staged-changes'; 2 | -------------------------------------------------------------------------------- /src/views/repository-changes/components/staging-screen-options/unstaged-changes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './unstaged-changes'; 2 | -------------------------------------------------------------------------------- /src/views/repository-changes/repository-changes-split.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StorybookProvider} from '@components/storybook-provider'; 3 | import {StageSplitView} from './components/staging-screen-options'; 4 | import {RepositoryHeader} from '@components/repository-header'; 5 | 6 | const RepositoryChangesSplitDemo = () => { 7 | const repo = {currentBranchName: 'master'} as any; 8 | 9 | return ( 10 | 11 | 12 | Promise.resolve()} 14 | unstagedChanges={[]} 15 | removeFromStaged={() => Promise.resolve()} 16 | stagedChanges={[]} 17 | onCommit={() => {}} 18 | onDiscard={() => Promise.resolve()} 19 | onIgnore={() => Promise.resolve()} 20 | /> 21 | 22 | ); 23 | }; 24 | 25 | export default {title: 'Screens/Repo Changes'}; 26 | 27 | export const SplitView = () => ; 28 | -------------------------------------------------------------------------------- /src/views/repository-history/components/history-branch-dropdown/index.ts: -------------------------------------------------------------------------------- 1 | export * from './history-branch-dropdown'; 2 | -------------------------------------------------------------------------------- /src/views/repository-history/repository-history.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {RepositoryHistoryUI} from './repository-history.ui'; 3 | import {BranchesUI} from '../branches/branches.ui'; 4 | import {StorybookProvider} from '@components/storybook-provider'; 5 | 6 | const RepositoryListDemo = ({...props}: any) => { 7 | const topLayer = React.useMemo( 8 | () => ( 9 | Promise.resolve()} 15 | onCheckoutBranch={() => Promise.resolve()} 16 | onCheckoutRemoteBranch={() => Promise.resolve()} 17 | onCreateRemote={() => {}} 18 | onCreateBranch={() => Promise.resolve()} 19 | onDeleteLocalBranch={() => Promise.resolve()} 20 | /> 21 | ), 22 | [], 23 | ); 24 | 25 | return ( 26 | 27 | {}} 30 | topLayer={topLayer} 31 | repo={{} as any} 32 | branchName={'the_big_branch'} 33 | error={null} 34 | /> 35 | 36 | ); 37 | }; 38 | 39 | export default {title: 'Screens/Repo History'}; 40 | 41 | export const DefaultStyling = () => ; 42 | -------------------------------------------------------------------------------- /src/views/repository-history/repository-history.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useSelector} from 'react-redux'; 3 | import {getGitLog, RootState} from '@store'; 4 | import {useThunkDispatch} from '@hooks'; 5 | import {GitLogCommit} from '@services'; 6 | import {useNavigation} from '@react-navigation/native'; 7 | import {RepositoryHistoryUI} from './repository-history.ui'; 8 | import {Branches} from '../branches'; 9 | import {NavProps} from '@types'; 10 | 11 | export const RepositoryHistory = () => { 12 | const {repo} = useSelector((state: RootState) => state.repository); 13 | const {commits, error: commitsError} = useSelector( 14 | (state: RootState) => state.commits, 15 | ); 16 | const dispatch = useThunkDispatch(); 17 | 18 | React.useEffect(() => { 19 | dispatch(getGitLog()).then(({error}: any) => { 20 | if (error) console.error(error); 21 | }); 22 | }, [dispatch]); 23 | 24 | const history = useNavigation(); 25 | 26 | const onCommitNavigate = (commit: GitLogCommit) => { 27 | history.navigate('CommitDetails', { 28 | commitId: commit.oid, 29 | }); 30 | }; 31 | 32 | const topLayer = React.useMemo(() => , []); 33 | 34 | return ( 35 | 43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /src/views/repository-list/components/add-existing-repository-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './add-existing-repository-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/repository-list/components/clone-repository-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './clone-repository-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/repository-list/components/clone-repository-progress-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './clone-repository-progress-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/repository-list/components/create-repository-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './create-repository-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/repository-list/components/delete-repository-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './delete-repository-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/repository-list/components/dialogs-and-fab/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export * from './dialogs-and-fab'; 3 | -------------------------------------------------------------------------------- /src/views/repository-list/components/dialogs-and-fab/new-repo-fab.tsx: -------------------------------------------------------------------------------- 1 | import {TouchableRipple} from 'react-native-paper'; 2 | import {Text} from 'react-native'; 3 | import * as React from 'react'; 4 | import {ExtendedFabBase} from './types'; 5 | import {theme} from '@constants'; 6 | import {DynamicStyleSheet, useDynamicValue} from 'react-native-dynamic'; 7 | import {useTranslation} from 'react-i18next'; 8 | 9 | export const NewRepoFab = ({toggleAnimation}: ExtendedFabBase) => { 10 | const styles = useDynamicValue(dynamicStyles); 11 | 12 | const {t} = useTranslation(); 13 | 14 | return ( 15 | { 18 | toggleAnimation(); 19 | }}> 20 | {t('newFABAction')} 21 | 22 | ); 23 | }; 24 | 25 | const dynamicStyles = new DynamicStyleSheet({ 26 | fab: { 27 | paddingVertical: theme.spacing.s, 28 | paddingHorizontal: 28, 29 | }, 30 | fabActionText: { 31 | ...theme.textStyles.callout_01, 32 | color: theme.colors.on_primary, 33 | textAlign: 'center', 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /src/views/repository-list/components/dialogs-and-fab/types.ts: -------------------------------------------------------------------------------- 1 | export interface ExtendedFabBase { 2 | toggleAnimation: () => void; 3 | } 4 | 5 | export type DialogSelection = 'clone' | 'create' | 'existing'; 6 | -------------------------------------------------------------------------------- /src/views/repository-list/components/repo-card/index.ts: -------------------------------------------------------------------------------- 1 | export * from './repo-card'; 2 | -------------------------------------------------------------------------------- /src/views/repository-list/components/repo-card/repo-card.styles.ts: -------------------------------------------------------------------------------- 1 | import {theme} from '@constants'; 2 | import {DynamicStyleSheet} from 'react-native-dynamic'; 3 | 4 | export const dynamicStyles = new DynamicStyleSheet({ 5 | cardContainer: { 6 | borderStyle: 'solid', 7 | borderColor: theme.colors.tint_on_surface_01, 8 | borderRadius: theme.borderRadius.regular, 9 | borderWidth: 1, 10 | paddingTop: 8, 11 | paddingBottom: 16, 12 | paddingLeft: 16, 13 | marginBottom: 16, 14 | }, 15 | topDisplayRow: { 16 | flexDirection: 'row', 17 | justifyContent: 'space-between', 18 | alignItems: 'center', 19 | marginBottom: 8, 20 | }, 21 | repoNameAndDate: { 22 | paddingTop: 8, 23 | flexDirection: 'column', 24 | }, 25 | moreButtonContainer: { 26 | height: 56, 27 | width: 48, 28 | justifyContent: 'center', 29 | alignItems: 'center', 30 | }, 31 | displayRow: { 32 | flexDirection: 'row', 33 | justifyContent: 'space-between', 34 | alignItems: 'center', 35 | }, 36 | cardName: { 37 | flexGrow: 1, 38 | flexShrink: 1, 39 | marginRight: 8, 40 | ...theme.textStyles.headline_06, 41 | color: theme.colors.label_high_emphasis, 42 | }, 43 | lastUpdated: { 44 | color: theme.colors.label_high_emphasis, 45 | opacity: theme.opacity.secondary, 46 | ...theme.textStyles.caption_02, 47 | }, 48 | branchView: { 49 | flexDirection: 'row', 50 | alignItems: 'center', 51 | }, 52 | branchName: { 53 | color: theme.colors.primary, 54 | marginLeft: 4, 55 | ...theme.textStyles.caption_01, 56 | }, 57 | statusComponent: { 58 | marginRight: 16, 59 | }, 60 | }); 61 | -------------------------------------------------------------------------------- /src/views/repository-list/components/repo-list-loading/index.ts: -------------------------------------------------------------------------------- 1 | export * from './repo-list-loading'; 2 | -------------------------------------------------------------------------------- /src/views/repository-list/components/repo-list-loading/repo-list-loading.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {RepoListCardLoading} from './repo-list-card'; 3 | import {View} from 'react-native'; 4 | import MaskedView from '@react-native-masked-view/masked-view'; 5 | import LinearGradient from 'react-native-linear-gradient'; 6 | 7 | export const RepoListLoading = () => { 8 | return ( 9 | 16 | }> 17 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/views/repository/components/fetch-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './fetch-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/repository/components/on-fetch-action-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './on-fetch-action-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/repository/components/on-pull-action-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './on-pull-action-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/repository/components/on-push-action-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './on-push-action-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/repository/components/push-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './push-dialog'; 2 | -------------------------------------------------------------------------------- /src/views/staging-layout/components/slide-up-down-settings-animation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './slide-up-down-settings-animation'; 2 | --------------------------------------------------------------------------------