├── .env.example ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── analysis_options.yaml ├── assets ├── alfredhatcog.png ├── google.png └── icon.png ├── bin ├── main.dart ├── main_helpers.dart └── src │ ├── constants │ └── units.dart │ ├── converters │ └── utc_date_time_converter.dart │ ├── env │ ├── .gitignore │ └── env.dart │ ├── extensions │ └── num_helper.dart │ ├── mixins │ ├── convert.dart │ └── rfc_822.dart │ ├── models │ ├── currency.dart │ ├── exchange_rate.dart │ ├── exchange_rate.g.dart │ ├── exchange_rates.dart │ └── exchange_rates.g.dart │ └── services │ ├── ecb_exchange_rates.dart │ └── emoji_downloader.dart ├── build.sh ├── default_currency.png ├── demo.gif ├── entitlements.plist ├── info.plist ├── pubspec.lock └── pubspec.yaml /.env.example: -------------------------------------------------------------------------------- 1 | APP_VERSION= 2 | GITHUB_REPOSITORY_URL= 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: techouse 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pub" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build package 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | runs-on: 7 | description: 'The runner type' 8 | required: true 9 | type: string 10 | defaults: 11 | run: 12 | shell: bash 13 | env: 14 | PUB_ENVIRONMENT: bot.github 15 | permissions: read-all 16 | 17 | jobs: 18 | publish: 19 | name: "Build" 20 | runs-on: "${{ inputs.runs-on }}" 21 | environment: build 22 | env: 23 | GITHUB_REPOSITORY_URL: ${{ github.server_url }}/${{ github.repository }} 24 | steps: 25 | - uses: dart-lang/setup-dart@v1 26 | with: 27 | sdk: stable 28 | - id: checkout 29 | uses: actions/checkout@v4 30 | - name: Compare version with ref/tag 31 | if: startsWith(github.ref, 'refs/tags/') 32 | id: compare_version_with_tag 33 | run: | 34 | set -e 35 | VERSION=$(awk '/^version: / {print $2}' pubspec.yaml) 36 | TAG=${GITHUB_REF_NAME#v} 37 | if [[ "$VERSION" != "$TAG" ]]; then 38 | echo "Version in pubspec.yaml ($VERSION) does not match tag ($TAG)" 39 | exit 1 40 | fi 41 | echo "VERSION=$VERSION" >> $GITHUB_ENV 42 | - name: Check CHANGELOG.md 43 | id: check_changelog 44 | run: | 45 | set -e 46 | if ! grep -q "## ${VERSION}" CHANGELOG.md; then 47 | echo "CHANGELOG.md does not contain a section for ${VERSION}" 48 | exit 1 49 | fi 50 | - name: Create tag-specific CHANGELOG 51 | id: create_changelog 52 | run: | 53 | set -e 54 | CHANGELOG_PATH=$RUNNER_TEMP/CHANGELOG.md 55 | awk '/^##[[:space:]].*/ { if (count == 1) exit; count++; print } count == 1 && !/^##[[:space:]].*/ { print }' CHANGELOG.md | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' > $CHANGELOG_PATH 56 | echo "CHANGELOG_PATH=$CHANGELOG_PATH" >> $GITHUB_ENV 57 | - name: Configure .env file 58 | id: generate_env_file 59 | run: | 60 | set -e 61 | mv .env.example .env 62 | sed -i '' "s#APP_VERSION=.*#APP_VERSION=$VERSION#" .env 63 | sed -i '' "s#GITHUB_REPOSITORY_URL=.*#GITHUB_REPOSITORY_URL=$GITHUB_REPOSITORY_URL#" .env 64 | - name: Configure the info.plist 65 | id: info_plist 66 | run: | 67 | set -e 68 | /usr/libexec/PlistBuddy -c "Set :version $VERSION" info.plist 69 | /usr/libexec/PlistBuddy -c "Set :webaddress $GITHUB_REPOSITORY_URL" info.plist 70 | - name: Install dependencies 71 | id: install_dependencies 72 | run: | 73 | dart pub get 74 | dart pub global activate -sgit https://github.com/techouse/dart_pubspec_licenses_lite 75 | - name: Run Dart code generation 76 | id: generate_code 77 | run: dart run build_runner build --delete-conflicting-outputs 78 | - name: Check formatting 79 | run: dart format --output=none --set-exit-if-changed . 80 | - name: Analyze 81 | run: dart analyze --fatal-infos 82 | - name: Build executable 83 | id: build_executable 84 | run: bash build.sh 85 | - name: Install the Apple certificate 86 | id: install_certificate 87 | env: 88 | BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} 89 | P12_PASSWORD: ${{ secrets.P12_PASSWORD }} 90 | KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} 91 | run: | 92 | set -e 93 | CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 94 | KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db 95 | 96 | # import certificate 97 | echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH 98 | 99 | # create temporary keychain 100 | security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH 101 | security set-keychain-settings -lut 21600 $KEYCHAIN_PATH 102 | security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH 103 | 104 | # import certificate to keychain 105 | security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH 106 | security list-keychain -d user -s $KEYCHAIN_PATH 107 | - name: Sign executable 108 | id: sign_executable 109 | env: 110 | BUILD_CERTIFICATE_SHA1: ${{ secrets.BUILD_CERTIFICATE_SHA1 }} 111 | run: | 112 | set -e 113 | BUNDLE_ID=$(/usr/libexec/PlistBuddy -c 'print ":bundleid"' info.plist) 114 | codesign \ 115 | --sign="$BUILD_CERTIFICATE_SHA1" \ 116 | --identifier="$BUNDLE_ID" \ 117 | --deep \ 118 | --force \ 119 | --options=runtime \ 120 | --entitlement="entitlements.plist" \ 121 | --timestamp \ 122 | build/dist/workflow 123 | - name: Verify signature 124 | id: verify_executable_signature 125 | env: 126 | TEAM_ID: ${{ secrets.TEAM_ID }} 127 | run: | 128 | set -e 129 | if [[ $(codesign -dv build/dist/workflow 2>&1 | awk -F= '/TeamIdentifier/{print $2}') != "$TEAM_ID" ]]; then 130 | echo "The TeamIdentifier in the signature does not match the signing TeamIdentifier." 131 | exit 1 132 | fi 133 | - name: Package executable into ZIP archive 134 | id: zip_executable 135 | run: zip -j build/dist/workflow.zip build/dist/workflow 136 | - name: Create notarytool Keychain profile 137 | id: create_keychain_profile 138 | env: 139 | APPLE_ID: ${{ secrets.APPLE_ID }} 140 | TEAM_ID: ${{ secrets.TEAM_ID }} 141 | NOTARYTOOL_PASSWORD: ${{ secrets.NOTARYTOOL_PASSWORD }} 142 | NOTARYTOOL_KEYCHAIN_PROFILE: ${{ vars.NOTARYTOOL_KEYCHAIN_PROFILE }} 143 | run: | 144 | set -e 145 | xcrun notarytool \ 146 | store-credentials "$NOTARYTOOL_KEYCHAIN_PROFILE" \ 147 | --apple-id "$APPLE_ID" \ 148 | --team-id "$TEAM_ID" \ 149 | --password "$NOTARYTOOL_PASSWORD" 150 | - name: Notarize executable 151 | id: notarize_executable 152 | env: 153 | NOTARYTOOL_KEYCHAIN_PROFILE: ${{ vars.NOTARYTOOL_KEYCHAIN_PROFILE }} 154 | run: | 155 | set -e 156 | xcrun notarytool \ 157 | submit build/dist/workflow.zip \ 158 | --keychain-profile "$NOTARYTOOL_KEYCHAIN_PROFILE" \ 159 | --wait 160 | - name: Delete obsolete ZIP archive 161 | id: delete_zip_archive 162 | run: rm -rf build/dist/workflow.zip 163 | - name: Compress artifacts 164 | id: compress_artifacts 165 | env: 166 | WORKFLOW_NAME: ${{ vars.WORKFLOW_NAME }} 167 | working-directory: build/dist 168 | run: | 169 | set -e 170 | ARTIFACT_NAME=${WORKFLOW_NAME}-v${VERSION}-$(uname -m) 171 | echo "ARTIFACT_NAME=$ARTIFACT_NAME" >> $GITHUB_ENV 172 | find . -not -path "./*_cache*" -exec zip --symlinks "../${ARTIFACT_NAME}.zip" {} + 173 | echo "ARTIFACT_PATH=build/${ARTIFACT_NAME}.zip" >> $GITHUB_ENV 174 | - name: Artifact 175 | id: success_artifact 176 | uses: actions/upload-artifact@v4 177 | with: 178 | name: ${{ env.ARTIFACT_NAME }} 179 | path: ${{ env.ARTIFACT_PATH }} 180 | retention-days: 1 181 | - name: Clean up keychain and build directory 182 | id: clean_up 183 | if: ${{ always() }} 184 | run: | 185 | security delete-keychain $RUNNER_TEMP/app-signing.keychain-db 186 | rm -rf $RUNNER_TEMP/build_certificate.p12 187 | rm .env 188 | rm -rf build 189 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release package 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v[0-9]+.[0-9]+.[0-9]+*' 7 | defaults: 8 | run: 9 | shell: bash 10 | env: 11 | PUB_ENVIRONMENT: bot.github 12 | permissions: read-all 13 | 14 | jobs: 15 | build: 16 | name: "Build" 17 | strategy: 18 | matrix: 19 | os: [macos-latest, macos-13] 20 | fail-fast: true 21 | uses: ./.github/workflows/build.yml 22 | with: 23 | runs-on: ${{ matrix.os }} 24 | secrets: inherit 25 | github_release: 26 | name: "Github Release" 27 | needs: build 28 | environment: build 29 | runs-on: ubuntu-latest 30 | permissions: 31 | contents: write 32 | steps: 33 | - uses: actions/checkout@v4 34 | - name: Read pubspec.yaml version 35 | id: read_pubspec_version 36 | run: | 37 | set -e 38 | VERSION=$(yq -r '.version' pubspec.yaml) 39 | echo "VERSION=$VERSION" >> $GITHUB_ENV 40 | - name: Get arm64 artifact 41 | id: download_arm64_artifact 42 | uses: actions/download-artifact@v4 43 | env: 44 | WORKFLOW_NAME: ${{ vars.WORKFLOW_NAME }} 45 | ARCH: arm64 46 | with: 47 | name: ${{ env.WORKFLOW_NAME }}-v${{ env.VERSION }}-${{ env.ARCH }} 48 | path: ${{ runner.temp }}/download 49 | - name: Get x86_64 artifact 50 | id: download_x86_64_artifact 51 | uses: actions/download-artifact@v4 52 | env: 53 | WORKFLOW_NAME: ${{ vars.WORKFLOW_NAME }} 54 | ARCH: x86_64 55 | with: 56 | name: ${{ env.WORKFLOW_NAME }}-v${{ env.VERSION }}-${{ env.ARCH }} 57 | path: ${{ runner.temp }}/download 58 | - name: Unzip artifacts 59 | env: 60 | WORKFLOW_NAME: ${{ vars.WORKFLOW_NAME }} 61 | id: unzip_artifacts 62 | working-directory: ${{ runner.temp }}/download 63 | run: | 64 | set -e 65 | for ARCH in "arm64" "x86_64"; do 66 | mkdir -p $ARCH 67 | unzip -q ${{ env.WORKFLOW_NAME }}-v${{ env.VERSION }}-${ARCH}.zip -d $ARCH 68 | done 69 | - name: Create Alfred Workflow 70 | id: create_alfred_workflow 71 | env: 72 | WORKFLOW_NAME: ${{ vars.WORKFLOW_NAME }} 73 | working-directory: ${{ runner.temp }}/download 74 | run: | 75 | set -e 76 | mv x86_64/workflow arm64/workflow_intel 77 | pushd arm64 78 | find . -not -path "./*_cache*" -exec zip --symlinks "../${WORKFLOW_NAME}-v${VERSION}.alfredworkflow" {} + 79 | popd 80 | - name: Release 81 | id: release_workflow 82 | env: 83 | WORKFLOW_NAME: ${{ vars.WORKFLOW_NAME }} 84 | uses: softprops/action-gh-release@v2 85 | with: 86 | files: ${{ runner.temp }}/download/${{ env.WORKFLOW_NAME }}-v${{ env.VERSION }}.alfredworkflow 87 | - name: Clean up keychain and build directory 88 | id: clean_up 89 | if: ${{ always() }} 90 | run: | 91 | rm -rf $RUNNER_TEMP/download 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build output. 6 | build/ 7 | 8 | # IDE specific 9 | .idea/ 10 | 11 | # macOS specific 12 | .DS_Store 13 | 14 | bin/*_cache/ 15 | sign.sh 16 | 17 | .env 18 | prefs.plist -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.2.3 2 | 3 | - [CHORE] update [_fe_analyzer_shared](https://pub.dev/packages/_fe_analyzer_shared) tofe_analyzer_shared 80.0.0 (was 68.0.0) 4 | - [CHORE] update [alfred_workflow](https://pub.dev/packages/alfred_workflow) to 1.1.3 (was 1.0.1) 5 | - [CHORE] update [analyzer](https://pub.dev/packages/analyzer) to 7.3.0 (was 6.5.0) 6 | - [CHORE] update [args](https://pub.dev/packages/args) to 2.7.0 (was 2.5.0) 7 | - [CHORE] update [async](https://pub.dev/packages/async) to 2.13.0 (was 2.11.0) 8 | - [CHORE] update [autoequal_gen](https://pub.dev/packages/autoequal_gen) to 0.9.5 (was 0.9.1) 9 | - [CHORE] update [boolean_selector](https://pub.dev/packages/boolean_selector) to 2.1.2 (was 2.1.1) 10 | - [CHORE] update [build](https://pub.dev/packages/build) to 2.4.2 (was 2.4.1) 11 | - [CHORE] update [build_config](https://pub.dev/packages/build_config) to 1.1.2 (was 1.1.1) 12 | - [CHORE] update [build_daemon](https://pub.dev/packages/build_daemon) to 4.0.4 (was 4.0.2) 13 | - [CHORE] update [build_resolvers](https://pub.dev/packages/build_resolvers) to 2.4.4 (was 2.4.2) 14 | - [CHORE] update [build_runner](https://pub.dev/packages/build_runner) to 2.4.15 (was 2.4.11) 15 | - [CHORE] update [build_runner_core](https://pub.dev/packages/build_runner_core) to 8.0.0 (was 7.3.1) 16 | - [CHORE] update [built_value](https://pub.dev/packages/built_value) to 8.9.5 (was 8.9.2) 17 | - [CHORE] update [change_case](https://pub.dev/packages/change_case) to 2.2.0 (was 2.1.0) 18 | - [CHORE] update [charcode](https://pub.dev/packages/charcode) to 1.4.0 (was 1.3.1) 19 | - [CHORE] update [clock](https://pub.dev/packages/clock) to 1.1.2 (was 1.1.1) 20 | - [CHORE] update [code_builder](https://pub.dev/packages/code_builder) to 4.10.1 (was 4.10.0) 21 | - [CHORE] update [collection](https://pub.dev/packages/collection) to 1.19.1 (was 1.19.0) 22 | - [CHORE] update [convert](https://pub.dev/packages/convert) to 3.1.2 (was 3.1.1) 23 | - [CHORE] update [copy_with_extension](https://pub.dev/packages/copy_with_extension) to 6.0.1 (was 5.0.4) 24 | - [CHORE] update [crypto](https://pub.dev/packages/crypto) to 3.0.6 (was 3.0.3) 25 | - [CHORE] update [dart_style](https://pub.dev/packages/dart_style) to 3.0.1 (was 2.3.6) 26 | - [CHORE] update [decimal](https://pub.dev/packages/decimal) to 3.2.1 (was 3.0.2) 27 | - [CHORE] update [envied](https://pub.dev/packages/envied) to 1.1.1 (was 0.5.4+1) 28 | - [CHORE] update [envied_generator](https://pub.dev/packages/envied_generator) to 1.1.1 (was 0.5.4+1) 29 | - [CHORE] update [equatable](https://pub.dev/packages/equatable) to 2.0.7 (was 2.0.5) 30 | - [CHORE] update [file](https://pub.dev/packages/file) to 7.0.1 (was 7.0.0) 31 | - [CHORE] update [fixnum](https://pub.dev/packages/fixnum) to 1.1.1 (was 1.1.0) 32 | - [CHORE] update [glob](https://pub.dev/packages/glob) to 2.1.3 (was 2.1.2) 33 | - [CHORE] update [graphs](https://pub.dev/packages/graphs) to 2.3.2 (was 2.3.1) 34 | - [CHORE] update [http](https://pub.dev/packages/http) to 1.3.0 (was 1.2.1) 35 | - [CHORE] update [http_multi_server](https://pub.dev/packages/http_multi_server) to 3.2.2 (was 3.2.1) 36 | - [CHORE] update [http_parser](https://pub.dev/packages/http_parser) to 4.1.2 (was 4.1.0) 37 | - [CHORE] update [intl](https://pub.dev/packages/intl) to 0.20.2 (was 0.19.0) 38 | - [CHORE] update [io](https://pub.dev/packages/io) to 1.0.5 (was 1.0.4) 39 | - [CHORE] update [js](https://pub.dev/packages/js) to 0.7.2 (was 0.7.1) 40 | - [CHORE] update [json_serializable](https://pub.dev/packages/json_serializable) to 6.9.4 (was 6.8.0) 41 | - [CHORE] update [lints](https://pub.dev/packages/lints) to 5.1.1 (was 4.0.0) 42 | - [CHORE] update [logging](https://pub.dev/packages/logging) to 1.3.0 (was 1.2.0) 43 | - [CHORE] update [matcher](https://pub.dev/packages/matcher) to 0.12.17 (was 0.12.16+1) 44 | - [CHORE] update [meta](https://pub.dev/packages/meta) to 1.16.0 (was 1.15.0) 45 | - [CHORE] update [mime](https://pub.dev/packages/mime) to 2.0.0 (was 1.0.5) 46 | - [CHORE] update [package_config](https://pub.dev/packages/package_config) to 2.2.0 (was 2.1.0) 47 | - [CHORE] update [path](https://pub.dev/packages/path) to 1.9.1 (was 1.9.0) 48 | - [CHORE] update [petitparser](https://pub.dev/packages/petitparser) to 6.1.0 (was 6.0.2) 49 | - [CHORE] update [pub_semver](https://pub.dev/packages/pub_semver) to 2.2.0 (was 2.1.4) 50 | - [CHORE] update [pubspec_parse](https://pub.dev/packages/pubspec_parse) to 1.5.0 (was 1.3.0) 51 | - [CHORE] update [shelf_web_socket](https://pub.dev/packages/shelf_web_socket) to 3.0.0 (was 2.0.0) 52 | - [CHORE] update [source_gen](https://pub.dev/packages/source_gen) to 2.0.0 (was 1.5.0) 53 | - [CHORE] update [source_helper](https://pub.dev/packages/source_helper) to 1.3.5 (was 1.3.4) 54 | - [CHORE] update [source_span](https://pub.dev/packages/source_span) to 1.10.1 (was 1.10.0) 55 | - [CHORE] update [stack_trace](https://pub.dev/packages/stack_trace) to 1.12.1 (was 1.11.1) 56 | - [CHORE] update [stream_channel](https://pub.dev/packages/stream_channel) to 2.1.4 (was 2.1.2) 57 | - [CHORE] update [stream_transform](https://pub.dev/packages/stream_transform) to 2.1.1 (was 2.1.0) 58 | - [CHORE] update [string_scanner](https://pub.dev/packages/string_scanner) to 1.4.1 (was 1.3.0) 59 | - [CHORE] update [term_glyph](https://pub.dev/packages/term_glyph) to 1.2.2 (was 1.2.1) 60 | - [CHORE] update [test_api](https://pub.dev/packages/test_api) to 0.7.4 (was 0.7.3) 61 | - [CHORE] update [timezone](https://pub.dev/packages/timezone) to 0.10.0 (was 0.9.4) 62 | - [CHORE] update [timing](https://pub.dev/packages/timing) to 1.0.2 (was 1.0.1) 63 | - [CHORE] update [typed_data](https://pub.dev/packages/typed_data) to 1.4.0 (was 1.3.2) 64 | - [CHORE] update [units_converter](https://pub.dev/packages/units_converter) to 3.0.2 (was 3.0.0) 65 | - [CHORE] update [uuid](https://pub.dev/packages/uuid) to 4.5.1 (was 4.4.2) 66 | - [CHORE] update [watcher](https://pub.dev/packages/watcher) to 1.1.1 (was 1.1.0) 67 | - [CHORE] update [web](https://pub.dev/packages/web) to 1.1.1 (was 0.5.1) 68 | - [CHORE] update [web_socket](https://pub.dev/packages/web_socket) to 0.1.6 (was 0.1.5) 69 | - [CHORE] update [web_socket_channel](https://pub.dev/packages/web_socket_channel) to 3.0.2 (was 3.0.0) 70 | - [CHORE] update [yaml](https://pub.dev/packages/yaml) to 3.1.3 (was 3.1.2) 71 | 72 | ## 1.2.2 73 | 74 | - [CHORE] update Dart SDK to [3.4.0](https://medium.com/dartlang/dart-3-4-bd8d23b4462a) (was 3.2.0) 75 | - [CHORE] update [_fe_analyzer_shared](https://pub.dev/packages/_fe_analyzer_shared) to 68.0.0 (was 67.0.0) (72.0.0 available) 76 | - [CHORE] update [analyzer](https://pub.dev/packages/analyzer) to 6.5.0 (was 6.4.1) (6.7.0 available) 77 | - [CHORE] update [build_daemon](https://pub.dev/packages/build_daemon) to 4.0.2 (was 4.0.1) 78 | - [CHORE] update [build_runner](https://pub.dev/packages/build_runner) to 2.4.11 (was 2.4.9) 79 | - [CHORE] update [build_runner_core](https://pub.dev/packages/build_runner_core) to 7.3.1 (was 7.3.0) 80 | - [CHORE] update [change_case](https://pub.dev/packages/change_case) to 2.1.0 (was 2.0.1) 81 | - [CHORE] update [collection](https://pub.dev/packages/collection) to 1.19.0 (was 1.18.0) 82 | - [CHORE] update [decimal](https://pub.dev/packages/decimal) to 3.0.2 (was 2.3.3) 83 | - [CHORE] update [http_parser](https://pub.dev/packages/http_parser) to 4.1.0 (was 4.0.2) 84 | - [CHORE] add [macros](https://pub.dev/packages/macros) 0.1.0-main.0 (0.1.2-main.4 available) 85 | - [CHORE] update [pubspec_parse](https://pub.dev/packages/pubspec_parse) to 1.3.0 (was 1.2.3) 86 | - [CHORE] update [rational](https://pub.dev/packages/rational) to 2.2.3 (was 2.2.2) 87 | - [CHORE] update [shelf](https://pub.dev/packages/shelf) to 1.4.2 (was 1.4.1) 88 | - [CHORE] update [shelf_web_socket](https://pub.dev/packages/shelf_web_socket) to 2.0.0 (was 1.0.4) 89 | - [CHORE] update [string_scanner](https://pub.dev/packages/string_scanner) to 1.3.0 (was 1.2.0) 90 | - [CHORE] update [test_api](https://pub.dev/packages/test_api) to 0.7.3 (was 0.7.1) 91 | - [CHORE] update [uuid](https://pub.dev/packages/uuid) to 4.4.2 (was 4.4.0) 92 | - [CHORE] add [web_socket](https://pub.dev/packages/web_socket) 0.1.5 93 | - [CHORE] update [web_socket_channel](https://pub.dev/packages/web_socket_channel) to 3.0.0 (was 2.4.5) 94 | 95 | ## 1.2.1 96 | 97 | - [CHORE] update dependencies 98 | 99 | ## 1.2.0 100 | 101 | - [CHORE] compile the binary on both ARM64 and x86_64 architectures to make it run on M1 Macs (arm64) as well 102 | 103 | ## 1.1.3 104 | 105 | - [CHORE] update dependencies 106 | 107 | ## 1.1.2 108 | 109 | - [CHORE] update [alfred_workflow](https://pub.dev/packages/alfred_workflow) to 0.6.0 (was 0.5.1) 110 | 111 | ## 1.1.1 112 | 113 | - [CHORE] update [_fe_analyzer_shared](https://pub.dev/packages/_fe_analyzer_shared) to 65.0.0 (was 61.0.0) 114 | - [CHORE] update [alfred_workflow](https://pub.dev/packages/alfred_workflow) to 0.5.1 (was 0.5.0) 115 | - [CHORE] update [analyzer](https://pub.dev/packages/analyzer) to 6.3.0 (was 5.13.0) 116 | - [CHORE] update [autoequal](https://pub.dev/packages/autoequal) to 0.7.1 (was 0.5.1) 117 | - [CHORE] update [autoequal_gen](https://pub.dev/packages/autoequal_gen) to 0.7.1 (was 0.5.1) 118 | - [CHORE] update [build_resolvers](https://pub.dev/packages/build_resolvers) to 2.4.2 (was 2.4.1) 119 | - [CHORE] update [build_runner](https://pub.dev/packages/build_runner) to 2.4.7 (was 2.4.6) 120 | - [CHORE] update [built_value](https://pub.dev/packages/built_value) to 8.8.1 (was 8.7.0) 121 | - [CHORE] update [code_builder](https://pub.dev/packages/code_builder) to 4.9.0 (was 4.7.0) 122 | - [CHORE] update [dart_style](https://pub.dev/packages/dart_style) to 2.3.4 (was 2.3.2) 123 | - [CHORE] update [envied](https://pub.dev/packages/envied) to 0.5.2 (was 0.5.1) 124 | - [CHORE] update [envied_generator](https://pub.dev/packages/envied_generator) to 0.5.2 (was 0.5.1) 125 | - [CHORE] update [http](https://pub.dev/packages/http) to 1.1.2 (was 1.1.0) 126 | - [CHORE] update [intl](https://pub.dev/packages/intl) to 0.19.0 (was 0.18.1) 127 | - [CHORE] update [matcher](https://pub.dev/packages/matcher) to 0.12.16+1 (was 0.12.16) 128 | - [CHORE] update [path](https://pub.dev/packages/path) to 1.9.0 (was 1.8.3) 129 | - [CHORE] update [petitparser](https://pub.dev/packages/petitparser) to 6.0.2 (was 6.0.1) 130 | - [CHORE] update [plist_parser](https://pub.dev/packages/plist_parser) to 0.0.11 (was 0.0.10) 131 | - [CHORE] update [source_gen](https://pub.dev/packages/source_gen) to 1.5.0 (was 1.4.0) 132 | - [CHORE] update [stash](https://pub.dev/packages/stash) to 5.0.3 (was 5.0.2) 133 | - [CHORE] update [stash_file](https://pub.dev/packages/stash_file) to 5.0.3 (was 5.0.2) 134 | - [CHORE] update [test_api](https://pub.dev/packages/test_api) to 0.7.0 (was 0.6.1) 135 | - [CHORE] update [uuid](https://pub.dev/packages/uuid) to 4.3.1 (was 4.2.1) 136 | - [CHORE] update [web_socket_channel](https://pub.dev/packages/web_socket_channel) to 2.4.1 (was 2.4.0) 137 | - [CHORE] update [xml](https://pub.dev/packages/xml) to 6.5.0 (was 6.4.2) 138 | 139 | ## 1.1.0 140 | 141 | - [FEAT] modify setting the `default_currency` via the Workflow Configuration 142 | 143 | ## 1.0.7 144 | 145 | - [FEAT] modify the copy to clipboard behaviour for currency related conversions 146 | - [CHORE] update [alfred_workflow](https://pub.dev/packages/alfred_workflow) to 0.4.2 (was 0.4.1) 147 | - [CHORE] update [build_daemon](https://pub.dev/packages/build_daemon) to 4.0.1 (was 4.0.0) 148 | - [CHORE] update [build_resolvers](https://pub.dev/packages/build_resolvers) to 2.4.1 (was 2.2.1) 149 | - [CHORE] update [build_runner_core](https://pub.dev/packages/build_runner_core) to 7.2.11 (was 7.2.10) 150 | - [CHORE] update [built_value](https://pub.dev/packages/built_value) to 8.7.0 (was 8.6.1) 151 | - [CHORE] update [code_builder](https://pub.dev/packages/code_builder) to 4.7.0 (was 4.5.0) 152 | - [CHORE] update [envied](https://pub.dev/packages/envied) to 0.5.1 (was 0.3.0+3) 153 | - [CHORE] update [envied_generator](https://pub.dev/packages/envied_generator) to 0.5.1 (was 0.3.0+3) 154 | - [CHORE] update [lints](https://pub.dev/packages/lints) to 3.0.0 (was 2.1.1) 155 | - [CHORE] update [meta](https://pub.dev/packages/meta) to 1.11.0 (was 1.9.1) 156 | - [CHORE] update [petitparser](https://pub.dev/packages/petitparser) to 6.0.1 (was 6.0.0) 157 | - [CHORE] update [stash](https://pub.dev/packages/stash) to 5.0.2 (was 5.0.0) 158 | - [CHORE] update [stash_file](https://pub.dev/packages/stash_file) to 5.0.2 (was 5.0.0) 159 | - [CHORE] update [uuid](https://pub.dev/packages/uuid) to 4.2.1 (was 3.0.7) 160 | - [CHORE] update [xml](https://pub.dev/packages/xml) to 6.4.2 (was 6.4.0) 161 | 162 | ## 1.0.6 163 | 164 | - [CHORE] update [alfred_workflow](https://pub.dev/packages/alfred_workflow) to 0.4.1 (was 0.4.0) 165 | - [CHORE] update [collection](https://pub.dev/packages/collection) to 1.18.0 (was 1.17.2) 166 | - [CHORE] update [copy_with_extension](https://pub.dev/packages/copy_with_extension) to 5.0.4 (was 5.0.3) 167 | - [CHORE] update [petitparser](https://pub.dev/packages/petitparser) to 6.0.0 (was 5.4.0) 168 | - [CHORE] update [units_converter](https://pub.dev/packages/units_converter) to 2.1.1 (was 2.1.0) 169 | - [CHORE] update [xml](https://pub.dev/packages/xml) to 6.4.0 (was 6.3.0) 170 | 171 | ## 1.0.5 172 | 173 | - Remove [dart_code_metrics](https://pub.dev/packages/dart_code_metrics) from dev dependencies as it is being discontinued on 16 July 2023. 174 | 175 | ## 1.0.4 176 | 177 | - Update workflow to Dart 3 178 | 179 | ## 1.0.3 180 | 181 | - Remove the Croatian Kuna (HRK) from the list of supported currencies as Croatia starts using the Euro on 1 January 2023. 182 | 183 | ## 1.0.2 184 | 185 | - Disable Alfred smart result ordering for currency results in favor of alphabetical ordering 186 | - Add option+return (⌥↵) shortcut to get the inverse currency conversion 187 | 188 | ## 1.0.1 189 | 190 | - Added `home_currency` environment variable to enable a user to set a default currency. If unset it defaults to `USD`. 191 | - Call the compiled binary with `arch -x86_64` to make it run on M1 Macs (arm64) as well. 192 | 193 | ## 1.0.0 194 | 195 | - Initial version. 196 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Klemen Tušar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | help: 4 | @printf "%-20s %s\n" "Target" "Description" 5 | @printf "%-20s %s\n" "------" "-----------" 6 | @make -pqR : 2>/dev/null \ 7 | | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' \ 8 | | sort \ 9 | | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' \ 10 | | xargs -I _ sh -c 'printf "%-20s " _; make _ -nB | (grep -i "^# Help:" || echo "") | tail -1 | sed "s/^# Help: //g"' 11 | 12 | analyze: 13 | @# Help: Analyze the project's Dart code. 14 | dart analyze --fatal-infos 15 | 16 | compile: 17 | @# Help: Compile the executable binary 18 | bash ./build.sh 19 | 20 | check_format: 21 | @# Help: Check the formatting of one or more Dart files. 22 | dart format --output=none --set-exit-if-changed . 23 | 24 | check_outdated: 25 | @# Help: Check which of the project's packages are outdated. 26 | dart pub outdated 27 | 28 | check_style: 29 | @# Help: Analyze the project's Dart code and check the formatting one or more Dart files. 30 | make analyze && make check_format 31 | 32 | clean_code_gen: 33 | @# Help: Clean up the generated Dart code 34 | dart run build_runner clean 35 | 36 | code_gen: 37 | @# Help: Run the build system for Dart code generation and modular compilation. 38 | dart run build_runner build --delete-conflicting-outputs 39 | 40 | code_gen_watcher: 41 | @# Help: Run the build system for Dart code generation and modular compilation as a watcher. 42 | dart run build_runner watch --delete-conflicting-outputs 43 | 44 | format: 45 | @# Help: Format one or more Dart files. 46 | dart format . 47 | 48 | install: 49 | @# Help: Install all the project's packages 50 | dart pub get 51 | 52 | upgrade: 53 | @# Help: Upgrade all the project's packages. 54 | dart pub upgrade 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Alfred Convert Workflow 2 | 3 | ![GitHub release](https://img.shields.io/github/release/techouse/alfred-convert.svg) 4 | ![GitHub All Releases](https://img.shields.io/github/downloads/techouse/alfred-convert/total.svg) 5 | ![GitHub](https://img.shields.io/github/license/techouse/alfred-convert.svg) 6 | 7 | Convert between different units in Alfred. 8 | 9 | Heavily inspired by [deanishe/alfred-convert](https://github.com/deanishe/alfred-convert) 😊 10 | 11 | ![demo](demo.gif) 12 | 13 | ## Installation 14 | 15 | 1. [Download the latest version](https://github.com/techouse/alfred-convert/releases/latest) 16 | 2. Install the workflow by double-clicking the `.alfredworkflow` file 17 | 3. You can add the workflow to a category, then click "Import" to finish importing. You'll now see the workflow listed 18 | in the left sidebar of your Workflows preferences pane. 19 | 20 | ## Usage 21 | 22 | - `conv ` - Perform a conversion 23 | - When performing a monetary conversion pressing `⌘Y` or return (↵) will open the currency-pair chart on [Xe.com](http://www.xe.com). 24 | - When pressing option+return (⌥↵) you will get the inverse currency conversion, i.e. `12 USD to EUR` becomes `12 EUR to USD`. 25 | - When pressing cmd+return (⌘↵) you will copy the converted value to your clipboard. 26 | - When performing a physical unit conversion pressing `⌘Y` or return (↵) will open up detailed the conversion 27 | explanation on [WolframAlpha.com](https://www.wolframalpha.com). 28 | - `conv money` - View a list of all the supported currencies 29 | - When pressing `⌘Y` or return (↵) on a certain currency you will be directed to the chart with the home currency on [Xe.com](http://www.xe.com). 30 | - When pressing option+return (⌥↵) you will get the inverse currency conversion, i.e. `1 AUD = 0.558 GBP` becomes `1 GBP = 1.792 AUD`. 31 | - When pressing cmd+return (⌘↵) you will copy the equivalent in the home currency to your clipboard. 32 | - `conv units` - View a list of all the supported physical units 33 | - When selecting a certain unit and pressing return (↵) that unit's symbol will get copied to the clipboard. 34 | 35 | ### Default currency 36 | 37 | In order to set a default currency, you can set it in the Workflow Configuration. 38 | 39 | ![default_currency](default_currency.png) 40 | 41 | Valid values are the [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency codes: AUD, BGN, BRL, CAD, CHF, CNY, CZK, 42 | DKK, EUR, GBP, HKD, HUF, IDR, ILS, INR, ISK, JPY, KRW, MXN, MYR, NOK, NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, USD, ZAR. 43 | 44 | ### Notes 45 | 46 | - All [the reference exchange rates are from the ECB](https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html). 47 | The reference rates are usually updated around 16:00 CET on every working day, except 48 | on [TARGET closing days](https://www.ecb.europa.eu/home/contacts/working-hours/html/index.en.html). 49 | 50 | - All non-monetary conversions performed using [ferraridamiano/units_converter](https://github.com/ferraridamiano/units_converter). 51 | 52 | - The displayed emoji images are from [joypixels/emoji-assets](https://github.com/joypixels/emoji-assets). -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | 3 | analyzer: 4 | exclude: 5 | - "**.g.dart" 6 | - "**.mocks.dart" 7 | 8 | linter: 9 | rules: 10 | avoid_print: true 11 | prefer_single_quotes: true 12 | -------------------------------------------------------------------------------- /assets/alfredhatcog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techouse/alfred-convert/8aee8358ce1791de8463d0723cbb2a0a49377a50/assets/alfredhatcog.png -------------------------------------------------------------------------------- /assets/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techouse/alfred-convert/8aee8358ce1791de8463d0723cbb2a0a49377a50/assets/google.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techouse/alfred-convert/8aee8358ce1791de8463d0723cbb2a0a49377a50/assets/icon.png -------------------------------------------------------------------------------- /bin/main.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: long-method 2 | 3 | import 'dart:io' show File, exitCode, stdout; 4 | 5 | import 'package:alfred_workflow/alfred_workflow.dart'; 6 | import 'package:args/args.dart'; 7 | import 'package:cli_script/cli_script.dart'; 8 | import 'package:decimal/decimal.dart'; 9 | import 'package:decimal/intl.dart'; 10 | import 'package:intl/intl.dart'; 11 | import 'package:logging/logging.dart'; 12 | import 'package:recase/recase.dart'; 13 | import 'package:units_converter/models/unit.dart'; 14 | 15 | import 'src/constants/units.dart'; 16 | import 'src/env/env.dart'; 17 | import 'src/mixins/convert.dart'; 18 | import 'src/models/currency.dart'; 19 | import 'src/models/exchange_rate.dart'; 20 | import 'src/models/exchange_rates.dart'; 21 | import 'src/services/ecb_exchange_rates.dart'; 22 | import 'src/services/emoji_downloader.dart'; 23 | 24 | part 'main_helpers.dart'; 25 | 26 | bool _verbose = false; 27 | bool _update = false; 28 | 29 | void main(List arguments) { 30 | wrapMain(() async { 31 | Logger.root.level = Level.ALL; 32 | 33 | try { 34 | exitCode = 0; 35 | 36 | _workflow.clearItems(); 37 | 38 | final ArgParser parser = ArgParser() 39 | ..addOption('query', abbr: 'q', defaultsTo: '') 40 | ..addFlag('currencies', abbr: 'C', defaultsTo: false) 41 | ..addFlag('units', abbr: 'U', defaultsTo: false) 42 | ..addFlag('verbose', abbr: 'v', defaultsTo: false) 43 | ..addFlag('update', abbr: 'u', defaultsTo: false); 44 | final ArgResults args = parser.parse(arguments); 45 | 46 | _update = args['update']; 47 | if (_update) { 48 | stdout.writeln('Updating workflow...'); 49 | 50 | return await _updater.update(); 51 | } 52 | 53 | _verbose = args['verbose']; 54 | 55 | if (_verbose) Logger.root.onRecord.listen(_logListener); 56 | 57 | final Map? userDefaults = 58 | await _workflow.getUserDefaults(); 59 | 60 | final AlfredUserConfigurationSelect? defaultCurrency = 61 | userDefaults?['default_currency'] as AlfredUserConfigurationSelect?; 62 | 63 | final Currency homeCurrency = Currency.values.firstWhere( 64 | (Currency currency) => currency.name == defaultCurrency?.value, 65 | orElse: () => Currency.USD, 66 | ); 67 | 68 | if (args['currencies']) { 69 | await _listCurrencies(homeCurrency: homeCurrency); 70 | } else if (args['units']) { 71 | await _listUnits(); 72 | } else { 73 | final String query = 74 | args['query'].replaceAll(RegExp(r'\s+'), ' ').trim(); 75 | 76 | if (_verbose) log.info('Query: "$query"'); 77 | 78 | if (query.isEmpty) { 79 | _showPlaceholder(); 80 | } else { 81 | if ((await _workflow.getItems()).isEmpty) { 82 | await _convert(query, homeCurrency: homeCurrency); 83 | } 84 | } 85 | } 86 | } on FormatException catch (err) { 87 | exitCode = 2; 88 | _workflow.addItem(AlfredItem(title: err.toString())); 89 | } catch (err) { 90 | exitCode = 1; 91 | _workflow.addItem(AlfredItem(title: err.toString())); 92 | if (_verbose) rethrow; 93 | } finally { 94 | if (!_update) { 95 | if (await _updater.updateAvailable()) { 96 | _workflow.run(addToBeginning: updateItem); 97 | } else { 98 | _workflow.run(); 99 | } 100 | } 101 | } 102 | }); 103 | } 104 | -------------------------------------------------------------------------------- /bin/main_helpers.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: long-method 2 | 3 | part of 'main.dart'; 4 | 5 | final Logger log = Logger('alfred_convert_workflow'); 6 | 7 | void _logListener(LogRecord record) { 8 | if (record.error != null && record.stackTrace != null) { 9 | stdout.write( 10 | '${record.level.name}: ${record.time}: ${record.message}\n${record.error}\n${record.stackTrace}' 11 | .trim(), 12 | ); 13 | } else if (record.error != null) { 14 | stdout.write( 15 | '${record.level.name}: ${record.time}: ${record.message}\n${record.error}' 16 | .trim(), 17 | ); 18 | } else { 19 | stdout.write( 20 | '${record.level.name}: ${record.time}: ${record.message}'.trim(), 21 | ); 22 | } 23 | } 24 | 25 | final DecimalFormatter numberFormat = DecimalFormatter(NumberFormat()); 26 | 27 | final AlfredWorkflow _workflow = AlfredWorkflow(); 28 | 29 | final AlfredUpdater _updater = AlfredUpdater( 30 | githubRepositoryUrl: Uri.parse(Env.githubRepositoryUrl), 31 | currentVersion: Env.appVersion, 32 | updateInterval: Duration(days: 7), 33 | ); 34 | 35 | const updateItem = AlfredItem( 36 | title: 'Auto-Update available!', 37 | subtitle: 'Press to auto-update to a new version of this workflow.', 38 | arg: 'update:workflow', 39 | match: 40 | 'Auto-Update available! Press to auto-update to a new version of this workflow.', 41 | icon: AlfredItemIcon(path: 'alfredhatcog.png'), 42 | valid: true, 43 | ); 44 | 45 | void _showPlaceholder() { 46 | _workflow.addItem( 47 | const AlfredItem( 48 | title: 'Convert from ... to ...', 49 | icon: AlfredItemIcon(path: 'icon.png'), 50 | ), 51 | ); 52 | } 53 | 54 | void _invalidFormat([String? message]) { 55 | _workflow.addItem( 56 | AlfredItem( 57 | title: message ?? 'Invalid format.', 58 | subtitle: 'Usage: conv 123.45 gbp usd', 59 | icon: AlfredItemIcon(path: 'icon.png'), 60 | valid: false, 61 | ), 62 | ); 63 | } 64 | 65 | Future _listCurrencies({Currency homeCurrency = Currency.USD}) async { 66 | final ExchangeRates? rates = await EcbExchangeRates().getLatest(); 67 | 68 | final AlfredItems items = AlfredItems( 69 | await Future.wait(Currency.values.map((currency) async { 70 | final File? image = await EmojiDownloader( 71 | '${currency.flag.runes.map((int cp) => cp.toRadixString(16)).join('-')}.png', 72 | ).downloadImage(); 73 | 74 | if (currency != homeCurrency) { 75 | try { 76 | final ExchangeRate? rate = rates?.convert(currency, homeCurrency); 77 | 78 | final Uri xeUrl = Uri.https('www.xe.com', 'currencycharts', { 79 | 'from': currency.name, 80 | 'to': homeCurrency.name, 81 | }); 82 | 83 | if (rate != null) { 84 | return AlfredItem( 85 | title: '${currency.fullName} (${currency.name})', 86 | subtitle: '1 ${currency.name} ≃' 87 | ' ${numberFormat.format(rate.rate)}' 88 | ' ${homeCurrency.name}', 89 | arg: xeUrl.toString(), 90 | quickLookUrl: xeUrl.toString(), 91 | match: '${currency.fullName} (${currency.name})', 92 | text: AlfredItemText( 93 | copy: currency.name, 94 | largeType: currency.name, 95 | ), 96 | icon: AlfredItemIcon( 97 | path: image != null ? image.absolute.path : 'icon.png', 98 | ), 99 | valid: true, 100 | mods: { 101 | {AlfredItemModKey.alt}: AlfredItemMod( 102 | subtitle: '1 ${homeCurrency.name} ≃' 103 | ' ${numberFormat.format(rate.invertedRate)}' 104 | ' ${currency.name}', 105 | valid: true, 106 | ), 107 | {AlfredItemModKey.cmd}: AlfredItemMod( 108 | subtitle: 'Copy ${numberFormat.format(rate.rate)}' 109 | ' ${homeCurrency.name} ${homeCurrency.flag} to clipboard', 110 | arg: '${numberFormat.format(rate.rate)} ' 111 | '${homeCurrency.name}', 112 | valid: true, 113 | ), 114 | }, 115 | ); 116 | } 117 | } catch (error, stackTrace) { 118 | if (_verbose) { 119 | log.warning( 120 | 'Error getting exchange rate for ${currency.name}', 121 | error, 122 | stackTrace, 123 | ); 124 | } 125 | } 126 | } 127 | 128 | final Uri oandaUrl = Uri.https( 129 | 'www.oanda.com', 130 | 'currency-converter/en/currencies/majors/${currency.name.toLowerCase()}/', 131 | ); 132 | 133 | return AlfredItem( 134 | title: '${currency.fullName} (${currency.name})', 135 | subtitle: 'Open currency fact sheet', 136 | arg: oandaUrl.toString(), 137 | quickLookUrl: oandaUrl.toString(), 138 | match: '${currency.fullName} (${currency.name})', 139 | text: AlfredItemText( 140 | copy: currency.name, 141 | largeType: currency.name, 142 | ), 143 | icon: AlfredItemIcon( 144 | path: image != null ? image.absolute.path : 'icon.png', 145 | ), 146 | valid: true, 147 | mods: { 148 | {AlfredItemModKey.cmd}: AlfredItemMod( 149 | subtitle: 'Copy ${homeCurrency.fullName} (${homeCurrency.name}) ' 150 | '${homeCurrency.flag} to clipboard', 151 | arg: '${homeCurrency.fullName} ${homeCurrency.name}', 152 | valid: true, 153 | ), 154 | }, 155 | ); 156 | }).toList()), 157 | ); 158 | _workflow.addItems(items.items); 159 | } 160 | 161 | Future _listUnits() async { 162 | final Map items = {}; 163 | 164 | for (final Map property in properties) { 165 | for (final MapEntry entry in property.entries) { 166 | final Unit? unit = Convert.getProperty(entry.value)?.getUnit(entry.value); 167 | final String? emoji = propertyEmojis[entry.value.runtimeType]; 168 | final File? image = emoji != null 169 | ? await EmojiDownloader( 170 | '${emoji.runes.first.toRadixString(16)}.png', 171 | ).downloadImage() 172 | : null; 173 | 174 | if (!items.containsKey(entry.value.name)) { 175 | items[entry.value.name] = AlfredItem( 176 | uid: entry.value.name, 177 | title: entry.value.name.sentenceCase, 178 | subtitle: unit?.symbol ?? entry.value.name, 179 | arg: entry.key, 180 | match: 181 | '${entry.value.name.sentenceCase} [${unit?.symbol ?? entry.value.name}]', 182 | text: AlfredItemText( 183 | copy: entry.key, 184 | largeType: entry.key, 185 | ), 186 | icon: AlfredItemIcon( 187 | path: image != null ? image.absolute.path : 'icon.png', 188 | ), 189 | valid: true, 190 | ); 191 | } 192 | } 193 | } 194 | 195 | _workflow.addItems(items.values.toList()); 196 | } 197 | 198 | Future _convert( 199 | String query, { 200 | Currency homeCurrency = Currency.USD, 201 | }) async { 202 | final List parts = query.split(' '); 203 | 204 | if (parts.length < 2) return _invalidFormat(); 205 | 206 | final Decimal? value = Decimal.tryParse(parts[0]); 207 | if (value == null) return _invalidFormat(); 208 | 209 | final String fromUnitSymbol = parts[1].trim(); 210 | 211 | try { 212 | /// First try to convert a currency 213 | late final String toUnitSymbol; 214 | 215 | switch (parts.length) { 216 | case 3: 217 | toUnitSymbol = parts[2].trim().toLowerCase() == 'to' 218 | ? homeCurrency.name 219 | : parts[2].trim(); 220 | break; 221 | case 4: 222 | if (parts[2].trim().toLowerCase() != 'to') { 223 | return _invalidFormat(); 224 | } 225 | toUnitSymbol = parts[3].trim(); 226 | break; 227 | default: 228 | toUnitSymbol = homeCurrency.name; 229 | } 230 | 231 | final AlfredItem? currencyItem = await Convert.convertCurrency( 232 | value, 233 | fromUnitSymbol, 234 | toUnitSymbol, 235 | ); 236 | if (currencyItem != null) { 237 | _workflow.addItem(currencyItem); 238 | } else { 239 | if (parts.length < 3 || parts.length > 4) return _invalidFormat(); 240 | 241 | /// Then try the others 242 | _workflow.addItem( 243 | await Convert.convertUnit( 244 | value.toDouble(), 245 | fromUnitSymbol, 246 | parts.length == 4 && parts[2].trim().toLowerCase() == 'to' 247 | ? parts[3].trim() 248 | : parts[2].trim(), 249 | ), 250 | ); 251 | } 252 | } on ArgumentError catch (error, stackTrace) { 253 | if (_verbose) { 254 | log.severe(error.message, error, stackTrace); 255 | } 256 | _invalidFormat('${error.message}: "${error.invalidValue}"'); 257 | } catch (error, stackTrace) { 258 | if (_verbose) { 259 | log.severe('Error calling _convert', error, stackTrace); 260 | } 261 | _invalidFormat(error.toString()); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /bin/src/constants/units.dart: -------------------------------------------------------------------------------- 1 | import 'package:emojis/emojis.dart'; 2 | import 'package:units_converter/units_converter.dart'; 3 | 4 | const Set> properties = { 5 | _angle, 6 | _area, 7 | _digitalData, 8 | _energy, 9 | _force, 10 | _fuelConsumption, 11 | _illuminance, 12 | _length, 13 | _mass, 14 | _numeralSystems, 15 | _power, 16 | _pressure, 17 | _speed, 18 | _temperature, 19 | _time, 20 | _torque, 21 | _volume, 22 | }; 23 | 24 | const Map propertyEmojis = { 25 | ANGLE: Emojis.triangularRuler, 26 | AREA: Emojis.house, 27 | DIGITAL_DATA: Emojis.computerDisk, 28 | ENERGY: Emojis.sun, 29 | FORCE: Emojis.elephant, 30 | FUEL_CONSUMPTION: Emojis.fuelPump, 31 | LENGTH: Emojis.straightRuler, 32 | MASS: Emojis.balanceScale, 33 | NUMERAL_SYSTEMS: Emojis.inputNumbers, 34 | POWER: Emojis.highVoltage, 35 | PRESSURE: Emojis.fireExtinguisher, 36 | SPEED: Emojis.racingCar, 37 | TEMPERATURE: Emojis.thermometer, 38 | TIME: Emojis.stopwatch, 39 | TORQUE: Emojis.locomotive, 40 | VOLUME: Emojis.testTube, 41 | }; 42 | 43 | const Map _angle = { 44 | '°': ANGLE.degree, 45 | 'deg': ANGLE.degree, 46 | "'": ANGLE.minutes, 47 | "''": ANGLE.seconds, 48 | '"': ANGLE.seconds, 49 | 'rad': ANGLE.radians, 50 | }; 51 | 52 | const Map _area = { 53 | 'm2': AREA.squareMeters, 54 | 'cm2': AREA.squareCentimeters, 55 | 'in2': AREA.squareInches, 56 | 'ft2': AREA.squareFeet, 57 | 'mi2': AREA.squareMiles, 58 | 'yd2': AREA.squareYard, 59 | 'mm2': AREA.squareMillimeters, 60 | 'km2': AREA.squareKilometers, 61 | 'ha': AREA.hectares, 62 | 'ac': AREA.acres, 63 | 'a': AREA.are, 64 | }; 65 | 66 | const Map _digitalData = { 67 | 'b': DIGITAL_DATA.bit, 68 | 'kb': DIGITAL_DATA.kilobit, 69 | 'Mb': DIGITAL_DATA.megabit, 70 | 'Gb': DIGITAL_DATA.gigabit, 71 | 'Tb': DIGITAL_DATA.terabit, 72 | 'Pb': DIGITAL_DATA.petabit, 73 | 'Eb': DIGITAL_DATA.exabit, 74 | 'Kibit': DIGITAL_DATA.kibibit, 75 | 'Mibit': DIGITAL_DATA.mebibit, 76 | 'Gibit': DIGITAL_DATA.gibibit, 77 | 'Tibit': DIGITAL_DATA.tebibit, 78 | 'Pibit': DIGITAL_DATA.pebibit, 79 | 'Eibit': DIGITAL_DATA.exbibit, 80 | 'B': DIGITAL_DATA.byte, 81 | 'kB': DIGITAL_DATA.kilobyte, 82 | 'MB': DIGITAL_DATA.megabyte, 83 | 'GB': DIGITAL_DATA.gigabyte, 84 | 'TB': DIGITAL_DATA.terabyte, 85 | 'PB': DIGITAL_DATA.petabyte, 86 | 'EB': DIGITAL_DATA.exabyte, 87 | 'KiB': DIGITAL_DATA.kibibyte, 88 | 'MiB': DIGITAL_DATA.mebibyte, 89 | 'GiB': DIGITAL_DATA.gibibyte, 90 | 'TiB': DIGITAL_DATA.tebibyte, 91 | 'PiB': DIGITAL_DATA.pebibyte, 92 | 'EiB': DIGITAL_DATA.exbibyte, 93 | }; 94 | 95 | const Map _energy = { 96 | 'J': ENERGY.joules, 97 | 'j': ENERGY.joules, 98 | 'kJ': ENERGY.kilojoules, 99 | 'cal': ENERGY.calories, 100 | 'kcal': ENERGY.kilocalories, 101 | 'kwh': ENERGY.kilowattHours, 102 | 'eV': ENERGY.electronvolts, 103 | 'ev': ENERGY.electronvolts, 104 | 'ft⋅lbf': ENERGY.energyFootPound, 105 | 'ftlbf': ENERGY.energyFootPound, 106 | }; 107 | 108 | const Map _force = { 109 | 'N': FORCE.newton, 110 | 'n': FORCE.newton, 111 | 'dyn': FORCE.dyne, 112 | 'lbf': FORCE.poundForce, 113 | 'kgf': FORCE.kilogramForce, 114 | 'pdl': FORCE.poundal, 115 | }; 116 | 117 | const Map _fuelConsumption = { 118 | 'km/l': FUEL_CONSUMPTION.kilometersPerLiter, 119 | 'l/100km': FUEL_CONSUMPTION.litersPer100km, 120 | 'us.mpg': FUEL_CONSUMPTION.milesPerUsGallon, 121 | 'mpg': FUEL_CONSUMPTION.milesPerImperialGallon, 122 | }; 123 | 124 | const Map _illuminance = { 125 | 'lx': ILLUMINANCE.lux, 126 | 'fc': ILLUMINANCE.footCandle, 127 | }; 128 | 129 | const Map _length = { 130 | 'm': LENGTH.meters, 131 | 'cm': LENGTH.centimeters, 132 | 'in': LENGTH.inches, 133 | 'ft': LENGTH.feet, 134 | 'M': LENGTH.nauticalMiles, 135 | 'yd': LENGTH.yards, 136 | 'mi': LENGTH.miles, 137 | 'mm': LENGTH.millimeters, 138 | 'µm': LENGTH.micrometers, 139 | 'nm': LENGTH.nanometers, 140 | 'Å': LENGTH.angstroms, 141 | 'å': LENGTH.angstroms, 142 | 'pm': LENGTH.picometers, 143 | 'km': LENGTH.kilometers, 144 | 'au': LENGTH.astronomicalUnits, 145 | 'ly': LENGTH.lightYears, 146 | 'pc': LENGTH.parsec, 147 | 'th': LENGTH.mils, 148 | }; 149 | 150 | const Map _mass = { 151 | 'g': MASS.grams, 152 | 'hg': MASS.ettograms, 153 | 'kg': MASS.kilograms, 154 | 'lb': MASS.pounds, 155 | 'oz': MASS.ounces, 156 | 't': MASS.tons, 157 | 'mg': MASS.milligrams, 158 | 'u': MASS.uma, 159 | 'ct': MASS.carats, 160 | 'cg': MASS.centigrams, 161 | 'dwt': MASS.pennyweights, 162 | 'ozt': MASS.troyOunces, 163 | 'st': MASS.stones, 164 | }; 165 | 166 | const Map _numeralSystems = { 167 | 'dec': NUMERAL_SYSTEMS.decimal, 168 | 'decimal': NUMERAL_SYSTEMS.decimal, 169 | 'hex': NUMERAL_SYSTEMS.hexadecimal, 170 | 'hexadecimal': NUMERAL_SYSTEMS.hexadecimal, 171 | 'oct': NUMERAL_SYSTEMS.octal, 172 | 'octal': NUMERAL_SYSTEMS.octal, 173 | 'bin': NUMERAL_SYSTEMS.binary, 174 | 'binary': NUMERAL_SYSTEMS.binary, 175 | }; 176 | 177 | const Map _power = { 178 | 'W': POWER.watt, 179 | 'mW': POWER.milliwatt, 180 | 'kW': POWER.kilowatt, 181 | 'MW': POWER.megawatt, 182 | 'GW': POWER.gigawatt, 183 | 'eu.hp': POWER.europeanHorsePower, 184 | 'hp': POWER.imperialHorsePower, 185 | }; 186 | 187 | const Map _pressure = { 188 | 'pa': PRESSURE.pascal, 189 | 'atm': PRESSURE.atmosphere, 190 | 'bar': PRESSURE.bar, 191 | 'mbar': PRESSURE.millibar, 192 | 'psi': PRESSURE.psi, 193 | 'mmhg': PRESSURE.torr, 194 | 'torr': PRESSURE.torr, 195 | 'kpa': PRESSURE.kiloPascal, 196 | 'hpa': PRESSURE.hectoPascal, 197 | 'inhg': PRESSURE.inchOfMercury, 198 | }; 199 | 200 | const Map _speed = { 201 | 'm/s': SPEED.metersPerSecond, 202 | 'km/h': SPEED.kilometersPerHour, 203 | 'kph': SPEED.kilometersPerHour, 204 | 'mi/h': SPEED.milesPerHour, 205 | 'mph': SPEED.milesPerHour, 206 | 'kts': SPEED.knots, 207 | 'ft/s': SPEED.feetsPerSecond, 208 | 'min/km': SPEED.minutesPerKilometer, 209 | }; 210 | 211 | const Map _temperature = { 212 | '°F': TEMPERATURE.fahrenheit, 213 | 'F': TEMPERATURE.fahrenheit, 214 | '°C': TEMPERATURE.celsius, 215 | 'C': TEMPERATURE.celsius, 216 | 'K': TEMPERATURE.kelvin, 217 | '°Re': TEMPERATURE.reamur, 218 | 'Re': TEMPERATURE.reamur, 219 | '°Rø': TEMPERATURE.romer, 220 | 'Rø': TEMPERATURE.romer, 221 | '°De': TEMPERATURE.delisle, 222 | 'De': TEMPERATURE.delisle, 223 | '°R': TEMPERATURE.rankine, 224 | 'R': TEMPERATURE.rankine, 225 | }; 226 | 227 | const Map _time = { 228 | 's': TIME.seconds, 229 | 'ds': TIME.deciseconds, 230 | 'cs': TIME.centiseconds, 231 | 'ms': TIME.milliseconds, 232 | 'µs': TIME.microseconds, 233 | 'ns': TIME.nanoseconds, 234 | 'min': TIME.minutes, 235 | 'h': TIME.hours, 236 | 'd': TIME.days, 237 | 'a': TIME.years365, 238 | 'c.': TIME.centuries, 239 | }; 240 | 241 | const Map _torque = { 242 | 'N·m': TORQUE.newtonMeter, 243 | 'Nm': TORQUE.newtonMeter, 244 | 'dyn·m': TORQUE.dyneMeter, 245 | 'dynm': TORQUE.dyneMeter, 246 | 'lbf·ft': TORQUE.poundForceFeet, 247 | 'lbfft': TORQUE.poundForceFeet, 248 | 'kgf·m': TORQUE.kilogramForceMeter, 249 | 'kgfm': TORQUE.kilogramForceMeter, 250 | 'pdl·m': TORQUE.poundalMeter, 251 | 'pdlm': TORQUE.poundalMeter, 252 | }; 253 | 254 | const Map _volume = { 255 | 'm3': VOLUME.cubicMeters, 256 | 'l': VOLUME.liters, 257 | 'L': VOLUME.liters, 258 | 'gal': VOLUME.imperialGallons, 259 | 'us.gal': VOLUME.usGallons, 260 | 'pt': VOLUME.imperialPints, 261 | 'us.pt': VOLUME.usPints, 262 | 'ml': VOLUME.milliliters, 263 | 'tbsp.': VOLUME.tablespoonsUs, 264 | 'cup': VOLUME.cups, 265 | 'cm3': VOLUME.cubicCentimeters, 266 | 'ft3': VOLUME.cubicFeet, 267 | 'in3': VOLUME.cubicInches, 268 | 'mm3': VOLUME.cubicMillimeters, 269 | 'fl.oz': VOLUME.imperialFluidOunces, 270 | 'floz': VOLUME.imperialFluidOunces, 271 | 'us.fl.oz': VOLUME.usFluidOunces, 272 | 'us.floz': VOLUME.usFluidOunces, 273 | }; 274 | -------------------------------------------------------------------------------- /bin/src/converters/utc_date_time_converter.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | class UtcDateTimeConverter implements JsonConverter { 4 | const UtcDateTimeConverter(); 5 | 6 | static const instance = UtcDateTimeConverter(); 7 | 8 | @override 9 | DateTime fromJson(String json) => DateTime.parse(json); 10 | 11 | @override 12 | String toJson(DateTime dateTime) => dateTime.toUtc().toIso8601String(); 13 | } 14 | -------------------------------------------------------------------------------- /bin/src/env/.gitignore: -------------------------------------------------------------------------------- 1 | *.g.dart 2 | !.gitignore -------------------------------------------------------------------------------- /bin/src/env/env.dart: -------------------------------------------------------------------------------- 1 | import 'package:envied/envied.dart'; 2 | 3 | part 'env.g.dart'; 4 | 5 | @Envied(path: '.env') 6 | abstract class Env { 7 | @EnviedField(varName: 'APP_VERSION') 8 | static const String appVersion = _Env.appVersion; 9 | 10 | @EnviedField(varName: 'GITHUB_REPOSITORY_URL') 11 | static const String githubRepositoryUrl = _Env.githubRepositoryUrl; 12 | } 13 | -------------------------------------------------------------------------------- /bin/src/extensions/num_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' show pow; 2 | 3 | import 'package:decimal/decimal.dart' show Decimal; 4 | import 'package:rational/rational.dart' show Rational; 5 | 6 | extension RoundedNum on num { 7 | num get rounded => this - toInt() > 0 ? roundDouble(2) : toInt(); 8 | 9 | num roundDouble([int places = 0]) { 10 | if (places > 0) { 11 | final num mod = pow(10.0, places); 12 | 13 | return (this * mod).roundToDouble() / mod; 14 | } 15 | 16 | return roundToDouble(); 17 | } 18 | } 19 | 20 | extension DecimalNum on num { 21 | Decimal toDecimal() => Decimal.parse(toString()); 22 | 23 | Rational toRational() => Rational.parse(toString()); 24 | } 25 | -------------------------------------------------------------------------------- /bin/src/mixins/convert.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show File; 2 | 3 | import 'package:alfred_workflow/alfred_workflow.dart'; 4 | import 'package:collection/collection.dart'; 5 | import 'package:decimal/decimal.dart'; 6 | import 'package:decimal/intl.dart'; 7 | import 'package:intl/intl.dart'; 8 | import 'package:units_converter/models/double_property.dart'; 9 | import 'package:units_converter/units_converter.dart'; 10 | 11 | import '../constants/units.dart'; 12 | import '../extensions/num_helper.dart'; 13 | import '../models/currency.dart'; 14 | import '../models/exchange_rate.dart'; 15 | import '../models/exchange_rates.dart'; 16 | import '../services/ecb_exchange_rates.dart'; 17 | import '../services/emoji_downloader.dart'; 18 | 19 | class Convert { 20 | Convert._(); 21 | 22 | static final DecimalFormatter numberFormat = DecimalFormatter(NumberFormat()); 23 | 24 | static Future convertCurrency( 25 | Decimal value, 26 | String fromUnitSymbol, 27 | String toUnitSymbol, 28 | ) async { 29 | final Currency? fromCurrency = 30 | Currency.byNameOrNull(fromUnitSymbol.toUpperCase()); 31 | 32 | if (fromCurrency != null) { 33 | final ExchangeRates? rates = await EcbExchangeRates().getLatest(); 34 | 35 | if (rates != null) { 36 | final Currency? toCurrency = 37 | Currency.byNameOrNull(toUnitSymbol.toUpperCase()); 38 | 39 | if (toCurrency != null) { 40 | final ExchangeRate rate = rates.convert(fromCurrency, toCurrency); 41 | 42 | final Decimal convertedValue = value * rate.rate; 43 | final Decimal invertedValue = value * rate.invertedRate; 44 | 45 | final File? image = await EmojiDownloader( 46 | '${toCurrency.flag.runes.map((int cp) => cp.toRadixString(16)).join('-')}.png', 47 | ).downloadImage(); 48 | 49 | final Uri xeUrl = Uri.https('www.xe.com', 'currencycharts', { 50 | 'from': fromCurrency.name, 51 | 'to': toCurrency.name, 52 | }); 53 | 54 | return AlfredItem( 55 | title: '${numberFormat.format(value)}' 56 | ' ${fromCurrency.name} ${fromCurrency.flag} ≃' 57 | ' ${numberFormat.format(convertedValue)}' 58 | ' ${toCurrency.name} ${toCurrency.flag}', 59 | subtitle: 'Based on ECB exchange rates from ' 60 | '${DateFormat.yMMMd().format(rates.date)} ' 61 | '${rates.date.toUtc().hour > 0 && rates.date.toUtc().minute > 0 ? "${DateFormat.Hm().format(rates.date.toUtc())} UTC" : ''}' 62 | .trim(), 63 | arg: xeUrl.toString(), 64 | quickLookUrl: xeUrl.toString(), 65 | icon: AlfredItemIcon( 66 | path: image != null ? image.absolute.path : 'icon.png', 67 | ), 68 | valid: true, 69 | mods: { 70 | {AlfredItemModKey.alt}: AlfredItemMod( 71 | subtitle: '${numberFormat.format(value)} ' 72 | '${toCurrency.name} ${toCurrency.flag} ≃' 73 | ' ${numberFormat.format(invertedValue)}' 74 | ' ${fromCurrency.name} ${fromCurrency.flag}', 75 | valid: true, 76 | ), 77 | {AlfredItemModKey.cmd}: AlfredItemMod( 78 | subtitle: 'Copy ${numberFormat.format(convertedValue)}' 79 | ' ${toCurrency.name} ${toCurrency.flag} to clipboard', 80 | arg: '${numberFormat.format(convertedValue)}' 81 | ' ${toCurrency.name}', 82 | valid: true, 83 | ), 84 | }, 85 | ); 86 | } else { 87 | return _invalidFormat( 88 | 'Can not convert ${fromCurrency.fullName} to "$toUnitSymbol"', 89 | ); 90 | } 91 | } 92 | } 93 | 94 | return null; 95 | } 96 | 97 | static Future convertUnit( 98 | num value, 99 | String fromUnitSymbol, 100 | String toUnitSymbol, 101 | ) async { 102 | final Enum? from = _identifyUnit(fromUnitSymbol); 103 | final Enum? to = _identifyUnit(toUnitSymbol); 104 | 105 | if (from != null && to != null) { 106 | final DoubleProperty? fromProperty = getProperty(from) 107 | ?..convert(from, value.toDouble()); 108 | final Unit? fromUnit = fromProperty?.getUnit(from); 109 | 110 | try { 111 | final String? emoji = propertyEmojis[from.runtimeType]; 112 | final File? image = emoji != null 113 | ? await EmojiDownloader( 114 | '${emoji.runes.first.toRadixString(16)}.png', 115 | ).downloadImage() 116 | : null; 117 | 118 | final Unit? toUnit = fromProperty?.getUnit(to); 119 | 120 | final Decimal convertedQuantity = 121 | toUnit?.value?.toDecimal() ?? Decimal.fromInt(0); 122 | final Decimal singleConvertedQuantity = 123 | 1.convertFromTo(from, to)?.toDecimal() ?? Decimal.fromInt(0); 124 | 125 | final Uri wolframAlphaUrl = Uri.https('www.wolframalpha.com', 'input', { 126 | 'i': '$value ${fromUnit?.symbol ?? fromUnitSymbol} to ' 127 | '${toUnit?.symbol ?? toUnitSymbol}', 128 | }); 129 | 130 | return AlfredItem( 131 | title: 132 | '${numberFormat.format(value.toDecimal())} ${fromUnit?.symbol} =' 133 | ' ${numberFormat.format(convertedQuantity)}' 134 | ' ${toUnit?.symbol}', 135 | subtitle: 'Based on the fact that 1 ${fromUnit?.symbol} =' 136 | ' ${numberFormat.format(singleConvertedQuantity)}' 137 | ' ${toUnit?.symbol}', 138 | arg: wolframAlphaUrl.toString(), 139 | quickLookUrl: wolframAlphaUrl.toString(), 140 | icon: AlfredItemIcon( 141 | path: image != null ? image.absolute.path : 'icon.png', 142 | ), 143 | valid: true, 144 | ); 145 | } on TypeError { 146 | return _invalidFormat( 147 | 'Can not convert ${fromUnit?.symbol ?? fromUnitSymbol} to "$toUnitSymbol"', 148 | ); 149 | } 150 | } 151 | 152 | return _invalidFormat(); 153 | } 154 | 155 | static Enum? _identifyUnit(String unitSymbol) => properties.firstWhereOrNull( 156 | (Map property) => property.containsKey(unitSymbol), 157 | )?[unitSymbol]; 158 | 159 | static DoubleProperty? getProperty(Enum unit) { 160 | if (unit is ANGLE) return Angle(); 161 | if (unit is AREA) return Area(); 162 | if (unit is DIGITAL_DATA) return DigitalData(); 163 | if (unit is ENERGY) return Energy(); 164 | if (unit is FORCE) return Force(); 165 | if (unit is FUEL_CONSUMPTION) return FuelConsumption(); 166 | if (unit is LENGTH) return Length(); 167 | if (unit is MASS) return Mass(); 168 | if (unit is POWER) return Power(); 169 | if (unit is PRESSURE) return Pressure(); 170 | if (unit is SPEED) return Speed(); 171 | if (unit is TEMPERATURE) return Temperature(); 172 | if (unit is TIME) return Time(); 173 | if (unit is TORQUE) return Torque(); 174 | if (unit is VOLUME) return Volume(); 175 | 176 | return null; 177 | } 178 | 179 | static AlfredItem _invalidFormat([String? message]) => AlfredItem( 180 | title: message ?? 'Invalid format.', 181 | subtitle: 'Usage: conv 123.45 gbp usd', 182 | icon: AlfredItemIcon(path: 'icon.png'), 183 | valid: false, 184 | ); 185 | } 186 | -------------------------------------------------------------------------------- /bin/src/mixins/rfc_822.dart: -------------------------------------------------------------------------------- 1 | mixin Rfc822 { 2 | static const Map _months = { 3 | 'Jan': '01', 4 | 'Feb': '02', 5 | 'Mar': '03', 6 | 'Apr': '04', 7 | 'May': '05', 8 | 'Jun': '06', 9 | 'Jul': '07', 10 | 'Aug': '08', 11 | 'Sep': '09', 12 | 'Oct': '10', 13 | 'Nov': '11', 14 | 'Dec': '12', 15 | }; 16 | 17 | static DateTime? parse(String input) { 18 | if (input.isEmpty) return null; 19 | 20 | final List splits = input 21 | .replaceFirst('GMT', '+0000') 22 | .replaceFirst('UTC', '+0000') 23 | .split(' '); 24 | 25 | final String year = splits[3]; 26 | 27 | final String? month = _months[splits[2]]; 28 | if (month == null) return null; 29 | 30 | String day = splits[1]; 31 | if (day.length == 1) day = '0$day'; 32 | 33 | final String splitTime = splits[4], splitZone = splits[5]; 34 | 35 | return DateTime.tryParse( 36 | '$year-$month-$day $splitTime $splitZone', 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /bin/src/models/currency.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: constant_identifier_names 2 | 3 | import 'package:collection/collection.dart'; 4 | import 'package:emojis/emojis.dart'; 5 | import 'package:json_annotation/json_annotation.dart'; 6 | 7 | enum Currency { 8 | @JsonValue('AUD') 9 | AUD('Australian dollar', Emojis.flagAustralia), 10 | @JsonValue('BGN') 11 | BGN('Bulgarian lev', Emojis.flagBulgaria), 12 | @JsonValue('BRL') 13 | BRL('Brazilian real', Emojis.flagBrazil), 14 | @JsonValue('CAD') 15 | CAD('Canadian dollar', Emojis.flagCanada), 16 | @JsonValue('CHF') 17 | CHF('Swiss franc', Emojis.flagSwitzerland), 18 | @JsonValue('CNY') 19 | CNY('Chinese yuan renminbi', Emojis.flagChina), 20 | @JsonValue('CZK') 21 | CZK('Czech koruna', Emojis.flagCzechia), 22 | @JsonValue('DKK') 23 | DKK('Danish krone', Emojis.flagDenmark), 24 | @JsonValue('EUR') 25 | EUR('Euro', Emojis.flagEuropeanUnion), 26 | @JsonValue('GBP') 27 | GBP('Pound sterling', Emojis.flagUnitedKingdom), 28 | @JsonValue('HKD') 29 | HKD('Hong Kong dollar', Emojis.flagHongKongSarChina), 30 | @JsonValue('HUF') 31 | HUF('Hungarian forint', Emojis.flagHungary), 32 | @JsonValue('IDR') 33 | IDR('Indonesian rupiah', Emojis.flagIndonesia), 34 | @JsonValue('ILS') 35 | ILS('Israeli shekel', Emojis.flagIsrael), 36 | @JsonValue('INR') 37 | INR('Indian rupee', Emojis.flagIndia), 38 | @JsonValue('ISK') 39 | ISK('Icelandic krona', Emojis.flagIceland), 40 | @JsonValue('JPY') 41 | JPY('Japanese yen', Emojis.flagJapan), 42 | @JsonValue('KRW') 43 | KRW('South Korean won', Emojis.flagSouthKorea), 44 | @JsonValue('MXN') 45 | MXN('Mexican peso', Emojis.flagMexico), 46 | @JsonValue('MYR') 47 | MYR('Malaysian ringgit', Emojis.flagMalaysia), 48 | @JsonValue('NOK') 49 | NOK('Norwegian krone', Emojis.flagNorway), 50 | @JsonValue('NZD') 51 | NZD('New Zealand dollar', Emojis.flagNewZealand), 52 | @JsonValue('PHP') 53 | PHP('Philippine peso', Emojis.flagPhilippines), 54 | @JsonValue('PLN') 55 | PLN('Polish zloty', Emojis.flagPoland), 56 | @JsonValue('RON') 57 | RON('Romanian leu', Emojis.flagRomania), 58 | @JsonValue('RUB') 59 | RUB('Russian rouble', Emojis.flagRussia), 60 | @JsonValue('SEK') 61 | SEK('Swedish krona', Emojis.flagSweden), 62 | @JsonValue('SGD') 63 | SGD('Singapore dollar', Emojis.flagSingapore), 64 | @JsonValue('THB') 65 | THB('Thai baht', Emojis.flagThailand), 66 | @JsonValue('TRY') 67 | TRY('Turkish lira', Emojis.flagTurkey), 68 | @JsonValue('USD') 69 | USD('US dollar', Emojis.flagUnitedStates), 70 | @JsonValue('ZAR') 71 | ZAR('South African rand', Emojis.flagSouthAfrica); 72 | 73 | const Currency(this.fullName, this.flag); 74 | 75 | final String fullName; 76 | final String flag; 77 | 78 | @override 79 | String toString() => '$name ($fullName $flag)'; 80 | 81 | static Currency? byNameOrNull(String name) => 82 | values.firstWhereOrNull((Currency value) => value.name == name); 83 | } 84 | -------------------------------------------------------------------------------- /bin/src/models/exchange_rate.dart: -------------------------------------------------------------------------------- 1 | import 'package:autoequal/autoequal.dart'; 2 | import 'package:decimal/decimal.dart'; 3 | import 'package:equatable/equatable.dart' show EquatableMixin; 4 | import 'package:json_annotation/json_annotation.dart'; 5 | 6 | import 'currency.dart'; 7 | 8 | part 'exchange_rate.g.dart'; 9 | 10 | @autoequal 11 | @JsonSerializable() 12 | class ExchangeRate with EquatableMixin { 13 | const ExchangeRate({ 14 | required this.currency, 15 | required this.rate, 16 | }); 17 | 18 | final Currency currency; 19 | @JsonKey(fromJson: _rateFromJson, toJson: _rateToJson) 20 | final Decimal rate; 21 | 22 | @ignore 23 | @JsonKey(includeFromJson: false, includeToJson: false) 24 | Decimal get invertedRate => 25 | (Decimal.one / rate).toDecimal(scaleOnInfinitePrecision: 4); 26 | 27 | static _rateFromJson(String rate) => Decimal.fromJson(rate); 28 | 29 | static _rateToJson(Decimal rate) => rate.toJson(); 30 | 31 | factory ExchangeRate.fromJson(Map json) => 32 | _$ExchangeRateFromJson(json); 33 | 34 | Map toJson() => _$ExchangeRateToJson(this); 35 | 36 | @override 37 | List get props => _$props; 38 | } 39 | -------------------------------------------------------------------------------- /bin/src/models/exchange_rate.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'exchange_rate.dart'; 4 | 5 | // ************************************************************************** 6 | // AutoequalGenerator 7 | // ************************************************************************** 8 | 9 | extension _$ExchangeRateAutoequal on ExchangeRate { 10 | List get _$props => [currency, rate]; 11 | } 12 | 13 | // ************************************************************************** 14 | // JsonSerializableGenerator 15 | // ************************************************************************** 16 | 17 | ExchangeRate _$ExchangeRateFromJson(Map json) => ExchangeRate( 18 | currency: $enumDecode(_$CurrencyEnumMap, json['currency']), 19 | rate: ExchangeRate._rateFromJson(json['rate'] as String), 20 | ); 21 | 22 | Map _$ExchangeRateToJson(ExchangeRate instance) => 23 | { 24 | 'currency': _$CurrencyEnumMap[instance.currency]!, 25 | 'rate': ExchangeRate._rateToJson(instance.rate), 26 | }; 27 | 28 | const _$CurrencyEnumMap = { 29 | Currency.AUD: 'AUD', 30 | Currency.BGN: 'BGN', 31 | Currency.BRL: 'BRL', 32 | Currency.CAD: 'CAD', 33 | Currency.CHF: 'CHF', 34 | Currency.CNY: 'CNY', 35 | Currency.CZK: 'CZK', 36 | Currency.DKK: 'DKK', 37 | Currency.EUR: 'EUR', 38 | Currency.GBP: 'GBP', 39 | Currency.HKD: 'HKD', 40 | Currency.HUF: 'HUF', 41 | Currency.IDR: 'IDR', 42 | Currency.ILS: 'ILS', 43 | Currency.INR: 'INR', 44 | Currency.ISK: 'ISK', 45 | Currency.JPY: 'JPY', 46 | Currency.KRW: 'KRW', 47 | Currency.MXN: 'MXN', 48 | Currency.MYR: 'MYR', 49 | Currency.NOK: 'NOK', 50 | Currency.NZD: 'NZD', 51 | Currency.PHP: 'PHP', 52 | Currency.PLN: 'PLN', 53 | Currency.RON: 'RON', 54 | Currency.RUB: 'RUB', 55 | Currency.SEK: 'SEK', 56 | Currency.SGD: 'SGD', 57 | Currency.THB: 'THB', 58 | Currency.TRY: 'TRY', 59 | Currency.USD: 'USD', 60 | Currency.ZAR: 'ZAR', 61 | }; 62 | -------------------------------------------------------------------------------- /bin/src/models/exchange_rates.dart: -------------------------------------------------------------------------------- 1 | import 'package:autoequal/autoequal.dart'; 2 | import 'package:collection/collection.dart'; 3 | import 'package:decimal/decimal.dart'; 4 | import 'package:equatable/equatable.dart' show EquatableMixin; 5 | import 'package:json_annotation/json_annotation.dart'; 6 | import 'package:timezone/data/latest.dart' as tz; 7 | import 'package:timezone/timezone.dart' as tz; 8 | import 'package:xml/xml.dart'; 9 | 10 | import '../converters/utc_date_time_converter.dart'; 11 | import 'currency.dart'; 12 | import 'exchange_rate.dart'; 13 | 14 | part 'exchange_rates.g.dart'; 15 | 16 | @autoequal 17 | @JsonSerializable(explicitToJson: true) 18 | @UtcDateTimeConverter.instance 19 | class ExchangeRates with EquatableMixin { 20 | const ExchangeRates({ 21 | required this.date, 22 | required this.rates, 23 | }); 24 | 25 | final DateTime date; 26 | 27 | @JsonKey(fromJson: _itemsFromJson) 28 | final List rates; 29 | 30 | ExchangeRate convert(Currency from, Currency to) { 31 | final ExchangeRate? fromRate = 32 | rates.firstWhereOrNull((rate) => rate.currency == from); 33 | if (fromRate == null) { 34 | throw ArgumentError.value(from, 'from', 'Invalid from currency pair.'); 35 | } 36 | 37 | final ExchangeRate? toRate = 38 | rates.firstWhereOrNull((rate) => rate.currency == to); 39 | if (toRate == null) { 40 | throw ArgumentError.value(to, 'to', 'Invalid to currency pair.'); 41 | } 42 | 43 | return ExchangeRate( 44 | currency: to, 45 | rate: 46 | (toRate.rate / fromRate.rate).toDecimal(scaleOnInfinitePrecision: 4), 47 | ); 48 | } 49 | 50 | factory ExchangeRates.fromXml(XmlDocument doc, [DateTime? updatedAt]) { 51 | if (updatedAt == null) { 52 | tz.initializeTimeZones(); 53 | final tz.Location brusselsTimeZone = tz.getLocation('Europe/Brussels'); 54 | 55 | final DateTime now = DateTime.now().toUtc(); 56 | 57 | final String? time = doc.descendantElements 58 | .firstWhereOrNull((XmlElement el) => el.getAttribute('time') != null) 59 | ?.getAttribute('time'); 60 | 61 | updatedAt = time != null 62 | ? tz.TZDateTime.from( 63 | DateTime.parse('${time}T00:00:00Z'), 64 | brusselsTimeZone, 65 | ) 66 | : tz.TZDateTime.from( 67 | DateTime.utc(now.year, now.month, now.day), 68 | brusselsTimeZone, 69 | ); 70 | } 71 | 72 | return ExchangeRates( 73 | date: updatedAt, 74 | rates: [ 75 | ExchangeRate(currency: Currency.EUR, rate: Decimal.one), 76 | ...doc.descendantElements.where((XmlElement el) { 77 | if (el.getAttribute('currency') != null && 78 | el.getAttribute('rate') != null) { 79 | try { 80 | Currency.values.byName(el.getAttribute('currency')!); 81 | 82 | return true; 83 | } catch (_) {} 84 | } 85 | 86 | return false; 87 | }).map( 88 | (XmlElement el) => ExchangeRate( 89 | currency: Currency.values.byName(el.getAttribute('currency')!), 90 | rate: Decimal.parse(el.getAttribute('rate')!), 91 | ), 92 | ), 93 | ], 94 | ); 95 | } 96 | 97 | static List _itemsFromJson(List items) => items 98 | .map((e) => ExchangeRate.fromJson(Map.from(e))) 99 | .toList(); 100 | 101 | factory ExchangeRates.fromJson(Map json) => 102 | _$ExchangeRatesFromJson(json); 103 | 104 | Map toJson() => _$ExchangeRatesToJson(this); 105 | 106 | @override 107 | List get props => _$props; 108 | } 109 | -------------------------------------------------------------------------------- /bin/src/models/exchange_rates.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'exchange_rates.dart'; 4 | 5 | // ************************************************************************** 6 | // AutoequalGenerator 7 | // ************************************************************************** 8 | 9 | extension _$ExchangeRatesAutoequal on ExchangeRates { 10 | List get _$props => [date, rates]; 11 | } 12 | 13 | // ************************************************************************** 14 | // JsonSerializableGenerator 15 | // ************************************************************************** 16 | 17 | ExchangeRates _$ExchangeRatesFromJson(Map json) => 18 | ExchangeRates( 19 | date: UtcDateTimeConverter.instance.fromJson(json['date'] as String), 20 | rates: ExchangeRates._itemsFromJson(json['rates'] as List), 21 | ); 22 | 23 | Map _$ExchangeRatesToJson(ExchangeRates instance) => 24 | { 25 | 'date': UtcDateTimeConverter.instance.toJson(instance.date), 26 | 'rates': instance.rates.map((e) => e.toJson()).toList(), 27 | }; 28 | -------------------------------------------------------------------------------- /bin/src/services/ecb_exchange_rates.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer' as developer show log; 2 | import 'dart:io'; 3 | 4 | import 'package:alfred_workflow/alfred_workflow.dart' show AlfredCache; 5 | import 'package:http/http.dart' as http show get, Response; 6 | import 'package:stash/stash_api.dart' 7 | show Cache, CreatedExpiryPolicy, ExpiryPolicy, FifoEvictionPolicy; 8 | import 'package:timezone/data/latest.dart' as tz; 9 | import 'package:timezone/timezone.dart' as tz; 10 | import 'package:xml/xml.dart'; 11 | 12 | import '../mixins/rfc_822.dart'; 13 | import '../models/exchange_rates.dart'; 14 | 15 | class EcbExchangeRates with Rfc822 { 16 | /// https://www.ecb.europa.eu/services/contacts/working-hours/html/index.en.html 17 | static const List _holidays = [ 18 | '01-01', // New Year's Day 19 | '05-01', // Labour Day 20 | '12-25', // Christmas Day 21 | '12-26', // Christmas Holiday 22 | ]; 23 | 24 | static const List _easters = [ 25 | '2025-04-20', 26 | '2026-04-05', 27 | '2027-03-28', 28 | '2028-04-16', 29 | '2029-04-01', 30 | '2030-04-21', 31 | '2031-04-13', 32 | '2032-03-28', 33 | '2033-04-17', 34 | '2034-04-09', 35 | '2035-03-25', 36 | '2036-04-13', 37 | '2037-04-05', 38 | '2038-04-25', 39 | '2039-04-10', 40 | '2040-04-01', 41 | '2041-04-21', 42 | '2042-04-06', 43 | '2043-03-29', 44 | '2044-04-17', 45 | '2045-04-09', 46 | '2046-03-25', 47 | '2047-04-14', 48 | '2048-04-05', 49 | '2049-04-18', 50 | '2050-04-10', 51 | ]; 52 | 53 | final DateTime _now = DateTime.now().toUtc(); 54 | late final DateTime _today = DateTime.utc( 55 | _now.year, 56 | _now.month, 57 | _now.day, 58 | ); 59 | 60 | late final AlfredCache _cache = AlfredCache( 61 | fromEncodable: (Map json) => ExchangeRates.fromJson(json), 62 | maxEntries: 1, 63 | name: 'exchange_rates_cache', 64 | expiryPolicy: const CreatedExpiryPolicy(ExpiryPolicy.eternal), 65 | evictionPolicy: const FifoEvictionPolicy(), 66 | ); 67 | 68 | Future getLatest() async { 69 | final Cache cache = await _cache.cache; 70 | final ExchangeRates? cached = await cache.get('latest'); 71 | 72 | if (cached != null) { 73 | final DateTime cachedDate = DateTime.utc( 74 | cached.date.year, 75 | cached.date.month, 76 | cached.date.day, 77 | ); 78 | 79 | if (cachedDate.difference(_today).inDays >= 0) { 80 | return cached; 81 | } else { 82 | if (_today.difference(cachedDate).inDays > 2 || await _shouldUpdate()) { 83 | final ExchangeRates? latest = await _downloadLatest(); 84 | if (latest != null) { 85 | await cache.put('latest', latest); 86 | 87 | return latest; 88 | } 89 | } 90 | } 91 | } else { 92 | final ExchangeRates? latest = await _downloadLatest(); 93 | 94 | if (latest != null) { 95 | await cache.put('latest', latest); 96 | 97 | return latest; 98 | } 99 | } 100 | 101 | return cached; 102 | } 103 | 104 | /// The reference rates are usually updated around 16:00 CET on every working day, except on TARGET closing days. 105 | Future _shouldUpdate() async { 106 | tz.initializeTimeZones(); 107 | 108 | final tz.Location brusselsTimeZone = tz.getLocation('Europe/Brussels'); 109 | late final DateTime brusselsNow = 110 | tz.TZDateTime.from(_now, brusselsTimeZone); 111 | 112 | return brusselsNow.weekday != DateTime.saturday && 113 | brusselsNow.weekday != DateTime.sunday && 114 | 115 | /// check that it's not a public holiday 116 | !_holidays.any((String date) { 117 | final DateTime holiday = tz.TZDateTime.from( 118 | DateTime.parse('${brusselsNow.year}-${date}T00:00:00Z'), 119 | brusselsTimeZone, 120 | ); 121 | 122 | return brusselsNow.year == holiday.year && 123 | brusselsNow.month == holiday.month && 124 | brusselsNow.day == holiday.day; 125 | }) && 126 | 127 | /// check that it's not Good Friday or Easter Monday 128 | !_easters 129 | .where((String easter) => 130 | tz.TZDateTime.from( 131 | DateTime.parse('${easter}T00:00:00Z'), 132 | brusselsTimeZone, 133 | ).year >= 134 | brusselsNow.year) 135 | .any((String easter) => [ 136 | tz.TZDateTime.from( 137 | // Good Friday 138 | DateTime.parse('${easter}T00:00:00Z').subtract( 139 | Duration(days: 2), 140 | ), 141 | brusselsTimeZone, 142 | ), 143 | tz.TZDateTime.from( 144 | // Easter Monday 145 | DateTime.parse('${easter}T00:00:00Z').add( 146 | Duration(days: 1), 147 | ), 148 | brusselsTimeZone, 149 | ), 150 | ].any((DateTime holiday) => 151 | brusselsNow.year == holiday.year && 152 | brusselsNow.month == holiday.month && 153 | brusselsNow.day == holiday.day)) && 154 | 155 | /// check if it's after 16:00 156 | (brusselsNow.hour > 16 || 157 | (brusselsNow.hour == 16 && brusselsNow.minute > 0)); 158 | } 159 | 160 | static Future _downloadLatest() async { 161 | final http.Response response = await http.get( 162 | Uri.https( 163 | 'www.ecb.europa.eu', 164 | '/stats/eurofxref/eurofxref-daily.xml', 165 | ), 166 | ); 167 | 168 | if (response.statusCode < 400) { 169 | try { 170 | return ExchangeRates.fromXml( 171 | XmlDocument.parse(response.body), 172 | Rfc822.parse(response.headers[HttpHeaders.lastModifiedHeader] ?? ''), 173 | ); 174 | } on XmlException catch (error, stackTrace) { 175 | developer.log( 176 | 'XML document can not be parsed', 177 | error: error, 178 | stackTrace: stackTrace, 179 | ); 180 | } catch (error, stackTrace) { 181 | developer.log(error.toString(), error: error, stackTrace: stackTrace); 182 | } 183 | } 184 | 185 | return null; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /bin/src/services/emoji_downloader.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Directory, File, Platform; 2 | 3 | import 'package:http/http.dart' as http show get, Response; 4 | import 'package:path/path.dart' as path show dirname, join; 5 | 6 | class EmojiDownloader { 7 | const EmojiDownloader( 8 | this.emoji, { 9 | String? directoryPath, 10 | }) : _directoryPath = directoryPath; 11 | 12 | final String emoji; 13 | final String? _directoryPath; 14 | 15 | Future downloadImage() async { 16 | final String filePath = 17 | _directoryPath != null && await Directory(_directoryPath).exists() 18 | ? path.join(_directoryPath, emoji) 19 | : path.join( 20 | path.dirname(Platform.script.toFilePath()), 21 | 'image_cache', 22 | emoji, 23 | ); 24 | final File file = File(filePath); 25 | 26 | if (!await file.exists()) { 27 | await file.create(recursive: true); 28 | 29 | final http.Response response = await http.get( 30 | Uri.https( 31 | 'raw.githubusercontent.com', 32 | '/joypixels/emoji-assets/master/png/128/$emoji', 33 | ), 34 | ); 35 | 36 | if (response.statusCode < 400) { 37 | await file.writeAsBytes(response.bodyBytes); 38 | } else { 39 | return null; 40 | } 41 | } 42 | 43 | return file; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Function to build header 4 | oss_header() { 5 | first=$(printf '%.s#' {1..78}) 6 | second=$( 7 | printf '##' 8 | printf '%.s ' {1..74} 9 | printf '##' 10 | ) 11 | third=$( 12 | printf '##' 13 | printf '%.s ' {1..27} 14 | printf 'OPEN-SOURCE LICENSES' 15 | printf '%.s ' {1..27} 16 | printf '##' 17 | ) 18 | printf "\n\n%s\n%s\n%s\n%s\n%s\n\n" "$first" "$second" "$third" "$second" "$first" 19 | } 20 | 21 | if [ -d "build/dist" ]; then 22 | rm -rf build/dist 23 | fi 24 | 25 | if [ -d "build/debug_info" ]; then 26 | rm -rf build/debug_info 27 | fi 28 | 29 | mkdir -p build/dist build/debug_info 30 | cp -r info.plist assets/* LICENSE README.md build/dist 31 | 32 | if command -v dart-pubspec-licenses-lite; then 33 | oss_header >>build/dist/LICENSE 34 | dart-pubspec-licenses-lite --pubspec-lock pubspec.lock >>build/dist/LICENSE 35 | else 36 | echo 'Info: Unable to generate OSS LICENSES. Please install https://github.com/techouse/dart_pubspec_licenses_lite' 37 | fi 38 | 39 | dart compile exe bin/main.dart -o build/dist/workflow -S build/debug_info/workflow 40 | -------------------------------------------------------------------------------- /default_currency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techouse/alfred-convert/8aee8358ce1791de8463d0723cbb2a0a49377a50/default_currency.png -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techouse/alfred-convert/8aee8358ce1791de8463d0723cbb2a0a49377a50/demo.gif -------------------------------------------------------------------------------- /entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | 8 | -------------------------------------------------------------------------------- /info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | org.techouse.alfred-convert 7 | connections 8 | 9 | 2F6849DA-7BAF-42CB-8502-EA7F75606985 10 | 11 | 12 | destinationuid 13 | DF31843F-F908-4B26-BA46-A24A1C902306 14 | modifiers 15 | 0 16 | modifiersubtext 17 | 18 | vitoclose 19 | 20 | 21 | 22 | destinationuid 23 | 572C1CAB-814A-4C59-8D8C-04C69F788090 24 | modifiers 25 | 1048576 26 | modifiersubtext 27 | Only copy to clipboard 28 | vitoclose 29 | 30 | 31 | 32 | 572C1CAB-814A-4C59-8D8C-04C69F788090 33 | 34 | DF31843F-F908-4B26-BA46-A24A1C902306 35 | 36 | 37 | destinationuid 38 | 663E877B-ABDD-4AE3-97E1-CB0E6122C6D0 39 | modifiers 40 | 0 41 | modifiersubtext 42 | 43 | sourceoutputuid 44 | D850B7E1-B6F7-4A5E-8EF6-522CBF4E0A21 45 | vitoclose 46 | 47 | 48 | 49 | destinationuid 50 | 207C6354-422F-4E46-8D70-34A3B0D8EC97 51 | modifiers 52 | 0 53 | modifiersubtext 54 | 55 | sourceoutputuid 56 | 951AA20A-5B2D-4C8A-885A-95BE6660EAD0 57 | vitoclose 58 | 59 | 60 | 61 | destinationuid 62 | 207C6354-422F-4E46-8D70-34A3B0D8EC97 63 | modifiers 64 | 0 65 | modifiersubtext 66 | 67 | sourceoutputuid 68 | CE9DC55C-ADBF-43C1-ACD4-9AD37038BA47 69 | vitoclose 70 | 71 | 72 | 73 | destinationuid 74 | 207C6354-422F-4E46-8D70-34A3B0D8EC97 75 | modifiers 76 | 0 77 | modifiersubtext 78 | 79 | sourceoutputuid 80 | AE48C113-0A75-4597-859D-26959DA44FAC 81 | vitoclose 82 | 83 | 84 | 85 | destinationuid 86 | 1BBB07BA-11B1-4386-AB63-A9CB78D9A1A9 87 | modifiers 88 | 0 89 | modifiersubtext 90 | 91 | vitoclose 92 | 93 | 94 | 95 | 96 | createdby 97 | Klemen Tušar 98 | description 99 | Convert between different units 100 | disabled 101 | 102 | name 103 | Convert 104 | objects 105 | 106 | 107 | config 108 | 109 | concurrently 110 | 111 | escaping 112 | 102 113 | script 114 | [ "$(uname -m)" = "arm64" ] && ./workflow -u || ./workflow_intel -u 115 | scriptargtype 116 | 1 117 | scriptfile 118 | 119 | type 120 | 0 121 | 122 | type 123 | alfred.workflow.action.script 124 | uid 125 | 663E877B-ABDD-4AE3-97E1-CB0E6122C6D0 126 | version 127 | 2 128 | 129 | 130 | config 131 | 132 | conditions 133 | 134 | 135 | inputstring 136 | 137 | matchcasesensitive 138 | 139 | matchmode 140 | 0 141 | matchstring 142 | update:workflow 143 | outputlabel 144 | Update Workflow 145 | uid 146 | D850B7E1-B6F7-4A5E-8EF6-522CBF4E0A21 147 | 148 | 149 | inputstring 150 | 151 | matchcasesensitive 152 | 153 | matchmode 154 | 4 155 | matchstring 156 | ^https?:\/\/(www.)?xe.com\/.*$ 157 | outputlabel 158 | Open Xe.com 159 | uid 160 | 951AA20A-5B2D-4C8A-885A-95BE6660EAD0 161 | 162 | 163 | inputstring 164 | 165 | matchcasesensitive 166 | 167 | matchmode 168 | 4 169 | matchstring 170 | ^https?:\/\/(www.)?oanda.com\/.*$ 171 | outputlabel 172 | Open Oanda.com 173 | uid 174 | CE9DC55C-ADBF-43C1-ACD4-9AD37038BA47 175 | 176 | 177 | inputstring 178 | 179 | matchcasesensitive 180 | 181 | matchmode 182 | 4 183 | matchstring 184 | ^https?:\/\/(www.)?wolframalpha.com\/.*$ 185 | outputlabel 186 | Open WolframAlpha.com 187 | uid 188 | AE48C113-0A75-4597-859D-26959DA44FAC 189 | 190 | 191 | elselabel 192 | Copy to Clipboard 193 | hideelse 194 | 195 | 196 | type 197 | alfred.workflow.utility.conditional 198 | uid 199 | DF31843F-F908-4B26-BA46-A24A1C902306 200 | version 201 | 1 202 | 203 | 204 | config 205 | 206 | alfredfiltersresults 207 | 208 | alfredfiltersresultsmatchmode 209 | 0 210 | argumenttreatemptyqueryasnil 211 | 212 | argumenttrimmode 213 | 0 214 | argumenttype 215 | 0 216 | escaping 217 | 102 218 | keyword 219 | conv 220 | queuedelaycustom 221 | 3 222 | queuedelayimmediatelyinitially 223 | 224 | queuedelaymode 225 | 2 226 | queuemode 227 | 1 228 | runningsubtext 229 | Working ... 230 | script 231 | case "{query}" in 232 | "money") 233 | [ "$(uname -m)" = "arm64" ] && ./workflow -C || ./workflow_intel -C 234 | ;; 235 | "units") 236 | [ "$(uname -m)" = "arm64" ] && ./workflow -U || ./workflow_intel -U 237 | ;; 238 | "update:workflow") 239 | [ "$(uname -m)" = "arm64" ] && ./workflow -u || ./workflow_intel -u 240 | ;; 241 | *) 242 | [ "$(uname -m)" = "arm64" ] && ./workflow -q "{query}" || ./workflow_intel -q "{query}" 243 | ;; 244 | esac 245 | scriptargtype 246 | 0 247 | scriptfile 248 | 249 | subtext 250 | 251 | title 252 | Convert between different units 253 | type 254 | 0 255 | withspace 256 | 257 | 258 | type 259 | alfred.workflow.input.scriptfilter 260 | uid 261 | 2F6849DA-7BAF-42CB-8502-EA7F75606985 262 | version 263 | 3 264 | 265 | 266 | config 267 | 268 | browser 269 | 270 | skipqueryencode 271 | 272 | skipvarencode 273 | 274 | spaces 275 | 276 | url 277 | {query} 278 | 279 | type 280 | alfred.workflow.action.openurl 281 | uid 282 | 207C6354-422F-4E46-8D70-34A3B0D8EC97 283 | version 284 | 1 285 | 286 | 287 | config 288 | 289 | autopaste 290 | 291 | clipboardtext 292 | {query} 293 | ignoredynamicplaceholders 294 | 295 | transient 296 | 297 | 298 | type 299 | alfred.workflow.output.clipboard 300 | uid 301 | 572C1CAB-814A-4C59-8D8C-04C69F788090 302 | version 303 | 3 304 | 305 | 306 | config 307 | 308 | autopaste 309 | 310 | clipboardtext 311 | {query} 312 | ignoredynamicplaceholders 313 | 314 | transient 315 | 316 | 317 | type 318 | alfred.workflow.output.clipboard 319 | uid 320 | 1BBB07BA-11B1-4386-AB63-A9CB78D9A1A9 321 | version 322 | 3 323 | 324 | 325 | readme 326 | Convert between different units in Alfred. 327 | 328 | Heavily inspired by [deanishe/alfred-convert](https://github.com/deanishe/alfred-convert) 😊 329 | 330 | ## Usage 331 | 332 | - `conv <quantity> <from unit> <to unit>` - Perform a conversion 333 | - When performing a monetary conversion pressing `⌘Y` or <kbd>return</kbd> (↵) will open the currency-pair chart on [Xe.com](http://www.xe.com). 334 | - When pressing <kbd>option+return</kbd> (⌥↵) you will get the inverse currency conversion, i.e. `12 USD to EUR` becomes `12 EUR to USD`. 335 | - When pressing <kbd>cmd+return</kbd> (⇧↵) you will copy the converted value to your clipboard. 336 | - When performing a physical unit conversion pressing `⌘Y` or <kbd>return</kbd> (↵) will open up detailed the conversion 337 | explanation on [WolframAlpha.com](https://www.wolframalpha.com). 338 | - `conv money` - View a list of all the supported currencies 339 | - When pressing `⌘Y` or <kbd>return</kbd> (↵) on a certain currency you will be directed to the chart with the home currency on [Xe.com](http://www.xe.com). 340 | - When pressing <kbd>option+return</kbd> (⌥↵) you will get the inverse currency conversion, i.e. `1 AUD = 0.558 GBP` becomes `1 GBP = 1.792 AUD`. 341 | - When pressing <kbd>cmd+return</kbd> (⇧↵) you will copy the equivalent in the home currency to your clipboard. 342 | - `conv units` - View a list of all the supported physical units 343 | - When selecting a certain unit and pressing <kbd>return</kbd> (↵) that unit's symbol will get copied to the clipboard. 344 | uidata 345 | 346 | 1BBB07BA-11B1-4386-AB63-A9CB78D9A1A9 347 | 348 | xpos 349 | 630 350 | ypos 351 | 720 352 | 353 | 207C6354-422F-4E46-8D70-34A3B0D8EC97 354 | 355 | xpos 356 | 625 357 | ypos 358 | 475 359 | 360 | 2F6849DA-7BAF-42CB-8502-EA7F75606985 361 | 362 | xpos 363 | 125 364 | ypos 365 | 475 366 | 367 | 572C1CAB-814A-4C59-8D8C-04C69F788090 368 | 369 | xpos 370 | 375 371 | ypos 372 | 720 373 | 374 | 663E877B-ABDD-4AE3-97E1-CB0E6122C6D0 375 | 376 | xpos 377 | 625 378 | ypos 379 | 170 380 | 381 | DF31843F-F908-4B26-BA46-A24A1C902306 382 | 383 | xpos 384 | 370 385 | ypos 386 | 450 387 | 388 | 389 | userconfigurationconfig 390 | 391 | 392 | config 393 | 394 | default 395 | USD 396 | pairs 397 | 398 | 399 | Australian dollar (AUD) 400 | AUD 401 | 402 | 403 | Bulgarian lev (BGN) 404 | BGN 405 | 406 | 407 | Brazilian real (BRL) 408 | BRL 409 | 410 | 411 | Canadian dollar (CAD) 412 | CAD 413 | 414 | 415 | Swiss franc (CHF) 416 | CHF 417 | 418 | 419 | Chinese yuan renminbi (CNY) 420 | CNY 421 | 422 | 423 | Czech koruna (CZK) 424 | CZK 425 | 426 | 427 | Danish krone (DKK) 428 | DKK 429 | 430 | 431 | Euro (EUR) 432 | EUR 433 | 434 | 435 | Pound sterling (GBP) 436 | GBP 437 | 438 | 439 | Hong Kong dollar (HKD) 440 | HKD 441 | 442 | 443 | Hungarian forint (HUF) 444 | HUF 445 | 446 | 447 | Indonesian rupiah (IDR) 448 | IDR 449 | 450 | 451 | Israeli shekel (ILS) 452 | ILS 453 | 454 | 455 | Indian rupee (INR) 456 | INR 457 | 458 | 459 | Icelandic krona (ISK) 460 | ISK 461 | 462 | 463 | Japanese yen (JPY) 464 | JPY 465 | 466 | 467 | South Korean won (KRW) 468 | KRW 469 | 470 | 471 | Mexican peso (MXN) 472 | MXN 473 | 474 | 475 | Malaysian ringgit (MYR) 476 | MYR 477 | 478 | 479 | Norwegian krone (NOK) 480 | NOK 481 | 482 | 483 | New Zealand dollar (NZD) 484 | NZD 485 | 486 | 487 | Philippine peso (PHP) 488 | PHP 489 | 490 | 491 | Polish zloty (PLN) 492 | PLN 493 | 494 | 495 | Romanian leu (RON) 496 | RON 497 | 498 | 499 | Russian rouble (RUB) 500 | RUB 501 | 502 | 503 | Swedish krona (SEK) 504 | SEK 505 | 506 | 507 | Singapore dollar (SGD) 508 | SGD 509 | 510 | 511 | Thai baht (THB) 512 | THB 513 | 514 | 515 | Turkish lira (TRY) 516 | TRY 517 | 518 | 519 | US dollar (USD) 520 | USD 521 | 522 | 523 | South African rand (ZAR) 524 | ZAR 525 | 526 | 527 | 528 | description 529 | 530 | label 531 | Default currency 532 | type 533 | popupbutton 534 | variable 535 | default_currency 536 | 537 | 538 | version 539 | 540 | webaddress 541 | 542 | 543 | 544 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "80.0.0" 12 | alfred_workflow: 13 | dependency: "direct main" 14 | description: 15 | name: alfred_workflow 16 | sha256: ef9787de3b4bdb8cde9be15b9cbca19004f07d389d19126e21afad28a8e23c80 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "1.1.3" 20 | analyzer: 21 | dependency: transitive 22 | description: 23 | name: analyzer 24 | sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "7.3.0" 28 | args: 29 | dependency: "direct main" 30 | description: 31 | name: args 32 | sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "2.7.0" 36 | async: 37 | dependency: transitive 38 | description: 39 | name: async 40 | sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "2.13.0" 44 | autoequal: 45 | dependency: "direct main" 46 | description: 47 | name: autoequal 48 | sha256: "186f99b614d8dd5b5756b26b6208d21ab948d7798ecfdf84669fff18eed7aab2" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "0.9.1" 52 | autoequal_gen: 53 | dependency: "direct dev" 54 | description: 55 | name: autoequal_gen 56 | sha256: "9ee3f043ca3a55eab183df8059c2e9b27f233a34398dd96a69c5770a0a76a8ef" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "0.9.5" 60 | boolean_selector: 61 | dependency: transitive 62 | description: 63 | name: boolean_selector 64 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "2.1.2" 68 | build: 69 | dependency: transitive 70 | description: 71 | name: build 72 | sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "2.4.2" 76 | build_config: 77 | dependency: transitive 78 | description: 79 | name: build_config 80 | sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "1.1.2" 84 | build_daemon: 85 | dependency: transitive 86 | description: 87 | name: build_daemon 88 | sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "4.0.4" 92 | build_resolvers: 93 | dependency: transitive 94 | description: 95 | name: build_resolvers 96 | sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "2.4.4" 100 | build_runner: 101 | dependency: "direct dev" 102 | description: 103 | name: build_runner 104 | sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "2.4.15" 108 | build_runner_core: 109 | dependency: transitive 110 | description: 111 | name: build_runner_core 112 | sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "8.0.0" 116 | built_collection: 117 | dependency: transitive 118 | description: 119 | name: built_collection 120 | sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "5.1.1" 124 | built_value: 125 | dependency: transitive 126 | description: 127 | name: built_value 128 | sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "8.9.5" 132 | change_case: 133 | dependency: transitive 134 | description: 135 | name: change_case 136 | sha256: e41ef3df58521194ef8d7649928954805aeb08061917cf658322305e61568003 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "2.2.0" 140 | charcode: 141 | dependency: transitive 142 | description: 143 | name: charcode 144 | sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "1.4.0" 148 | checked_yaml: 149 | dependency: transitive 150 | description: 151 | name: checked_yaml 152 | sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "2.0.3" 156 | cli_script: 157 | dependency: "direct main" 158 | description: 159 | name: cli_script 160 | sha256: "4d4d47dcac7f2e6fb68a6b3e806787b031eeb042bc330ba6dd333ad1e7f83252" 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "1.0.0" 164 | clock: 165 | dependency: transitive 166 | description: 167 | name: clock 168 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b 169 | url: "https://pub.dev" 170 | source: hosted 171 | version: "1.1.2" 172 | code_builder: 173 | dependency: transitive 174 | description: 175 | name: code_builder 176 | sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" 177 | url: "https://pub.dev" 178 | source: hosted 179 | version: "4.10.1" 180 | collection: 181 | dependency: "direct main" 182 | description: 183 | name: collection 184 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" 185 | url: "https://pub.dev" 186 | source: hosted 187 | version: "1.19.1" 188 | convert: 189 | dependency: transitive 190 | description: 191 | name: convert 192 | sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 193 | url: "https://pub.dev" 194 | source: hosted 195 | version: "3.1.2" 196 | copy_with_extension: 197 | dependency: transitive 198 | description: 199 | name: copy_with_extension 200 | sha256: "0447e5ea09845b275fbeaa7605bc85e74da759788678760b2a6c4e06ca622410" 201 | url: "https://pub.dev" 202 | source: hosted 203 | version: "6.0.1" 204 | crypto: 205 | dependency: transitive 206 | description: 207 | name: crypto 208 | sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" 209 | url: "https://pub.dev" 210 | source: hosted 211 | version: "3.0.6" 212 | dart_style: 213 | dependency: transitive 214 | description: 215 | name: dart_style 216 | sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" 217 | url: "https://pub.dev" 218 | source: hosted 219 | version: "3.0.1" 220 | decimal: 221 | dependency: "direct main" 222 | description: 223 | name: decimal 224 | sha256: "28239b8b929c1bd8618702e6dbc96e2618cf99770bbe9cb040d6cf56a11e4ec3" 225 | url: "https://pub.dev" 226 | source: hosted 227 | version: "3.2.1" 228 | emojis: 229 | dependency: "direct main" 230 | description: 231 | name: emojis 232 | sha256: "2e4d847c3f1e2670f30dc355909ce6fa7808b4e626c34a4dd503a360995a38bf" 233 | url: "https://pub.dev" 234 | source: hosted 235 | version: "0.9.9" 236 | envied: 237 | dependency: "direct main" 238 | description: 239 | name: envied 240 | sha256: a4e2b1d0caa479b5d61332ae516518c175a6d09328a35a0bc0a53894cc5d7e4d 241 | url: "https://pub.dev" 242 | source: hosted 243 | version: "1.1.1" 244 | envied_generator: 245 | dependency: "direct dev" 246 | description: 247 | name: envied_generator 248 | sha256: "894f6c5eb624c60a1ce6f642b6fd7ec68bc3440aa6f1881837aa9acbbeade0c8" 249 | url: "https://pub.dev" 250 | source: hosted 251 | version: "1.1.1" 252 | equatable: 253 | dependency: "direct main" 254 | description: 255 | name: equatable 256 | sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" 257 | url: "https://pub.dev" 258 | source: hosted 259 | version: "2.0.7" 260 | file: 261 | dependency: transitive 262 | description: 263 | name: file 264 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 265 | url: "https://pub.dev" 266 | source: hosted 267 | version: "7.0.1" 268 | fixnum: 269 | dependency: transitive 270 | description: 271 | name: fixnum 272 | sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be 273 | url: "https://pub.dev" 274 | source: hosted 275 | version: "1.1.1" 276 | frontend_server_client: 277 | dependency: transitive 278 | description: 279 | name: frontend_server_client 280 | sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 281 | url: "https://pub.dev" 282 | source: hosted 283 | version: "4.0.0" 284 | glob: 285 | dependency: transitive 286 | description: 287 | name: glob 288 | sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de 289 | url: "https://pub.dev" 290 | source: hosted 291 | version: "2.1.3" 292 | graphs: 293 | dependency: transitive 294 | description: 295 | name: graphs 296 | sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" 297 | url: "https://pub.dev" 298 | source: hosted 299 | version: "2.3.2" 300 | http: 301 | dependency: "direct main" 302 | description: 303 | name: http 304 | sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f 305 | url: "https://pub.dev" 306 | source: hosted 307 | version: "1.3.0" 308 | http_multi_server: 309 | dependency: transitive 310 | description: 311 | name: http_multi_server 312 | sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 313 | url: "https://pub.dev" 314 | source: hosted 315 | version: "3.2.2" 316 | http_parser: 317 | dependency: transitive 318 | description: 319 | name: http_parser 320 | sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" 321 | url: "https://pub.dev" 322 | source: hosted 323 | version: "4.1.2" 324 | intl: 325 | dependency: "direct main" 326 | description: 327 | name: intl 328 | sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" 329 | url: "https://pub.dev" 330 | source: hosted 331 | version: "0.20.2" 332 | io: 333 | dependency: transitive 334 | description: 335 | name: io 336 | sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b 337 | url: "https://pub.dev" 338 | source: hosted 339 | version: "1.0.5" 340 | js: 341 | dependency: transitive 342 | description: 343 | name: js 344 | sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" 345 | url: "https://pub.dev" 346 | source: hosted 347 | version: "0.7.2" 348 | json_annotation: 349 | dependency: "direct main" 350 | description: 351 | name: json_annotation 352 | sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" 353 | url: "https://pub.dev" 354 | source: hosted 355 | version: "4.9.0" 356 | json_serializable: 357 | dependency: "direct dev" 358 | description: 359 | name: json_serializable 360 | sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a" 361 | url: "https://pub.dev" 362 | source: hosted 363 | version: "6.9.4" 364 | lints: 365 | dependency: "direct dev" 366 | description: 367 | name: lints 368 | sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 369 | url: "https://pub.dev" 370 | source: hosted 371 | version: "5.1.1" 372 | logging: 373 | dependency: "direct main" 374 | description: 375 | name: logging 376 | sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 377 | url: "https://pub.dev" 378 | source: hosted 379 | version: "1.3.0" 380 | matcher: 381 | dependency: transitive 382 | description: 383 | name: matcher 384 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 385 | url: "https://pub.dev" 386 | source: hosted 387 | version: "0.12.17" 388 | meta: 389 | dependency: transitive 390 | description: 391 | name: meta 392 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c 393 | url: "https://pub.dev" 394 | source: hosted 395 | version: "1.16.0" 396 | mime: 397 | dependency: transitive 398 | description: 399 | name: mime 400 | sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" 401 | url: "https://pub.dev" 402 | source: hosted 403 | version: "2.0.0" 404 | package_config: 405 | dependency: transitive 406 | description: 407 | name: package_config 408 | sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc 409 | url: "https://pub.dev" 410 | source: hosted 411 | version: "2.2.0" 412 | path: 413 | dependency: "direct main" 414 | description: 415 | name: path 416 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 417 | url: "https://pub.dev" 418 | source: hosted 419 | version: "1.9.1" 420 | petitparser: 421 | dependency: transitive 422 | description: 423 | name: petitparser 424 | sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" 425 | url: "https://pub.dev" 426 | source: hosted 427 | version: "6.1.0" 428 | plist_parser: 429 | dependency: transitive 430 | description: 431 | name: plist_parser 432 | sha256: e2a6f9abfa0c45c0253656b7360abb0dfb84af9937bace74605b93d2aad2bf0c 433 | url: "https://pub.dev" 434 | source: hosted 435 | version: "0.0.11" 436 | pool: 437 | dependency: transitive 438 | description: 439 | name: pool 440 | sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" 441 | url: "https://pub.dev" 442 | source: hosted 443 | version: "1.5.1" 444 | pub_semver: 445 | dependency: transitive 446 | description: 447 | name: pub_semver 448 | sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" 449 | url: "https://pub.dev" 450 | source: hosted 451 | version: "2.2.0" 452 | pubspec_parse: 453 | dependency: transitive 454 | description: 455 | name: pubspec_parse 456 | sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" 457 | url: "https://pub.dev" 458 | source: hosted 459 | version: "1.5.0" 460 | rational: 461 | dependency: "direct main" 462 | description: 463 | name: rational 464 | sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336 465 | url: "https://pub.dev" 466 | source: hosted 467 | version: "2.2.3" 468 | recase: 469 | dependency: "direct main" 470 | description: 471 | name: recase 472 | sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 473 | url: "https://pub.dev" 474 | source: hosted 475 | version: "4.1.0" 476 | shelf: 477 | dependency: transitive 478 | description: 479 | name: shelf 480 | sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 481 | url: "https://pub.dev" 482 | source: hosted 483 | version: "1.4.2" 484 | shelf_web_socket: 485 | dependency: transitive 486 | description: 487 | name: shelf_web_socket 488 | sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" 489 | url: "https://pub.dev" 490 | source: hosted 491 | version: "3.0.0" 492 | source_gen: 493 | dependency: transitive 494 | description: 495 | name: source_gen 496 | sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" 497 | url: "https://pub.dev" 498 | source: hosted 499 | version: "2.0.0" 500 | source_helper: 501 | dependency: transitive 502 | description: 503 | name: source_helper 504 | sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" 505 | url: "https://pub.dev" 506 | source: hosted 507 | version: "1.3.5" 508 | source_span: 509 | dependency: transitive 510 | description: 511 | name: source_span 512 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" 513 | url: "https://pub.dev" 514 | source: hosted 515 | version: "1.10.1" 516 | sprintf: 517 | dependency: transitive 518 | description: 519 | name: sprintf 520 | sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" 521 | url: "https://pub.dev" 522 | source: hosted 523 | version: "7.0.0" 524 | stack_trace: 525 | dependency: transitive 526 | description: 527 | name: stack_trace 528 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" 529 | url: "https://pub.dev" 530 | source: hosted 531 | version: "1.12.1" 532 | stash: 533 | dependency: "direct main" 534 | description: 535 | name: stash 536 | sha256: c1482f64dcaa9b2d9eb112106ef018aad49adb284e1ba371c892c2ad9f99fc12 537 | url: "https://pub.dev" 538 | source: hosted 539 | version: "5.2.0" 540 | stash_file: 541 | dependency: "direct main" 542 | description: 543 | name: stash_file 544 | sha256: f2a68f2e7dcf7ca8f2b617959fca305983d5557355ede43cb15601e9533b495b 545 | url: "https://pub.dev" 546 | source: hosted 547 | version: "5.2.0" 548 | stream_channel: 549 | dependency: transitive 550 | description: 551 | name: stream_channel 552 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" 553 | url: "https://pub.dev" 554 | source: hosted 555 | version: "2.1.4" 556 | stream_transform: 557 | dependency: transitive 558 | description: 559 | name: stream_transform 560 | sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 561 | url: "https://pub.dev" 562 | source: hosted 563 | version: "2.1.1" 564 | string_scanner: 565 | dependency: transitive 566 | description: 567 | name: string_scanner 568 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" 569 | url: "https://pub.dev" 570 | source: hosted 571 | version: "1.4.1" 572 | term_glyph: 573 | dependency: transitive 574 | description: 575 | name: term_glyph 576 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" 577 | url: "https://pub.dev" 578 | source: hosted 579 | version: "1.2.2" 580 | test_api: 581 | dependency: transitive 582 | description: 583 | name: test_api 584 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd 585 | url: "https://pub.dev" 586 | source: hosted 587 | version: "0.7.4" 588 | timezone: 589 | dependency: "direct main" 590 | description: 591 | name: timezone 592 | sha256: ffc9d5f4d1193534ef051f9254063fa53d588609418c84299956c3db9383587d 593 | url: "https://pub.dev" 594 | source: hosted 595 | version: "0.10.0" 596 | timing: 597 | dependency: transitive 598 | description: 599 | name: timing 600 | sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" 601 | url: "https://pub.dev" 602 | source: hosted 603 | version: "1.0.2" 604 | tuple: 605 | dependency: transitive 606 | description: 607 | name: tuple 608 | sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 609 | url: "https://pub.dev" 610 | source: hosted 611 | version: "2.0.2" 612 | typed_data: 613 | dependency: transitive 614 | description: 615 | name: typed_data 616 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 617 | url: "https://pub.dev" 618 | source: hosted 619 | version: "1.4.0" 620 | units_converter: 621 | dependency: "direct main" 622 | description: 623 | name: units_converter 624 | sha256: "0e0b62a7ac10e6c79942aa52a14225733ce3bc68b70b4d64f99e79fc4477ad6f" 625 | url: "https://pub.dev" 626 | source: hosted 627 | version: "3.0.2" 628 | uuid: 629 | dependency: transitive 630 | description: 631 | name: uuid 632 | sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff 633 | url: "https://pub.dev" 634 | source: hosted 635 | version: "4.5.1" 636 | watcher: 637 | dependency: transitive 638 | description: 639 | name: watcher 640 | sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" 641 | url: "https://pub.dev" 642 | source: hosted 643 | version: "1.1.1" 644 | web: 645 | dependency: transitive 646 | description: 647 | name: web 648 | sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" 649 | url: "https://pub.dev" 650 | source: hosted 651 | version: "1.1.1" 652 | web_socket: 653 | dependency: transitive 654 | description: 655 | name: web_socket 656 | sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" 657 | url: "https://pub.dev" 658 | source: hosted 659 | version: "0.1.6" 660 | web_socket_channel: 661 | dependency: transitive 662 | description: 663 | name: web_socket_channel 664 | sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" 665 | url: "https://pub.dev" 666 | source: hosted 667 | version: "3.0.2" 668 | xml: 669 | dependency: "direct main" 670 | description: 671 | name: xml 672 | sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 673 | url: "https://pub.dev" 674 | source: hosted 675 | version: "6.5.0" 676 | yaml: 677 | dependency: transitive 678 | description: 679 | name: yaml 680 | sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce 681 | url: "https://pub.dev" 682 | source: hosted 683 | version: "3.1.3" 684 | sdks: 685 | dart: ">=3.7.0-0 <4.0.0" 686 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: alfred_convert_workflow 2 | description: Convert between different units with Alfred 3 | 4 | # Prevent accidental publishing to pub.dev. 5 | publish_to: 'none' 6 | 7 | version: 1.2.3 8 | 9 | environment: 10 | sdk: ^3.4.0 11 | 12 | dependencies: 13 | alfred_workflow: ^1.1.3 14 | autoequal: ^0.9.1 15 | args: ^2.7.0 16 | cli_script: ^1.0.0 17 | collection: ^1.19.1 18 | decimal: ^3.2.1 19 | emojis: ^0.9.9 20 | envied: ^1.1.1 21 | equatable: ^2.0.7 22 | http: ^1.3.0 23 | intl: ^0.20.2 24 | json_annotation: ^4.9.0 25 | logging: ^1.3.0 26 | path: ^1.9.1 27 | rational: ^2.2.3 28 | recase: ^4.1.0 29 | stash: ^5.2.0 30 | stash_file: ^5.2.0 31 | timezone: ^0.10.0 32 | units_converter: ^3.0.2 33 | xml: ^6.5.0 34 | 35 | dev_dependencies: 36 | autoequal_gen: ^0.9.5 37 | build_runner: ^2.4.15 38 | envied_generator: ^1.1.1 39 | lints: ^5.1.1 40 | json_serializable: ^6.9.4 41 | --------------------------------------------------------------------------------