├── .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 | 
4 | 
5 | 
6 |
7 | Convert between different units in Alfred.
8 |
9 | Heavily inspired by [deanishe/alfred-convert](https://github.com/deanishe/alfred-convert) 😊
10 |
11 | 
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 | 
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